Initial Commit
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
sampleRate = 48000
|
||||
channels = 2
|
||||
bytesPerSample = 2 // Signed 16-bit PCM
|
||||
windowDuration = 100 * time.Millisecond
|
||||
minimumDBFS = -96.0
|
||||
meterWidth = 50
|
||||
)
|
||||
|
||||
func main() {
|
||||
for _, program := range []string{"pactl", "parec"} {
|
||||
if _, err := exec.LookPath(program); err != nil {
|
||||
log.Fatalf("%s was not found in PATH: %v", program, err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(
|
||||
context.Background(),
|
||||
os.Interrupt,
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
defer stop()
|
||||
|
||||
defaultSink, err := commandOutput(
|
||||
ctx,
|
||||
"pactl",
|
||||
"get-default-sink",
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("get default output sink: %v", err)
|
||||
}
|
||||
|
||||
monitorSource := defaultSink + ".monitor"
|
||||
|
||||
fmt.Printf("Default output: %s\n", defaultSink)
|
||||
fmt.Printf("Capturing from: %s\n", monitorSource)
|
||||
fmt.Println("Press Ctrl+C to stop.")
|
||||
|
||||
cmd := exec.CommandContext(
|
||||
ctx,
|
||||
"parec",
|
||||
"--device="+monitorSource,
|
||||
"--format=s16le",
|
||||
fmt.Sprintf("--rate=%d", sampleRate),
|
||||
fmt.Sprintf("--channels=%d", channels),
|
||||
"--raw",
|
||||
"--latency-msec=50",
|
||||
)
|
||||
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
log.Fatalf("open parec output: %v", err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatalf("start parec: %v", err)
|
||||
}
|
||||
|
||||
framesPerWindow := int(
|
||||
float64(sampleRate) * windowDuration.Seconds(),
|
||||
)
|
||||
|
||||
buffer := make(
|
||||
[]byte,
|
||||
framesPerWindow*channels*bytesPerSample,
|
||||
)
|
||||
|
||||
for {
|
||||
n, readErr := io.ReadFull(stdout, buffer)
|
||||
|
||||
if n > 0 {
|
||||
rmsDBFS, peakDBFS := measurePCM16LE(buffer[:n])
|
||||
|
||||
fmt.Printf(
|
||||
"\rRMS %6.1f dBFS Peak %6.1f dBFS |%s|",
|
||||
rmsDBFS,
|
||||
peakDBFS,
|
||||
meter(rmsDBFS, -60, 0, meterWidth),
|
||||
)
|
||||
}
|
||||
|
||||
if readErr != nil {
|
||||
if ctx.Err() != nil ||
|
||||
errors.Is(readErr, io.EOF) ||
|
||||
errors.Is(readErr, io.ErrUnexpectedEOF) {
|
||||
break
|
||||
}
|
||||
|
||||
log.Fatalf("read PCM audio: %v", readErr)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
if err := cmd.Wait(); err != nil && ctx.Err() == nil {
|
||||
log.Fatalf("parec stopped unexpectedly: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func commandOutput(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
args ...string,
|
||||
) (string, error) {
|
||||
output, err := exec.CommandContext(
|
||||
ctx,
|
||||
name,
|
||||
args...,
|
||||
).Output()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
func measurePCM16LE(pcm []byte) (
|
||||
rmsDBFS float64,
|
||||
peakDBFS float64,
|
||||
) {
|
||||
sampleCount := len(pcm) / bytesPerSample
|
||||
|
||||
if sampleCount == 0 {
|
||||
return minimumDBFS, minimumDBFS
|
||||
}
|
||||
|
||||
var sumSquares float64
|
||||
var peak float64
|
||||
|
||||
for offset := 0; offset+1 < len(pcm); offset += bytesPerSample {
|
||||
sample := int16(
|
||||
binary.LittleEndian.Uint16(
|
||||
pcm[offset : offset+2],
|
||||
),
|
||||
)
|
||||
|
||||
value := float64(sample) / 32768.0
|
||||
absolute := math.Abs(value)
|
||||
|
||||
sumSquares += value * value
|
||||
|
||||
if absolute > peak {
|
||||
peak = absolute
|
||||
}
|
||||
}
|
||||
|
||||
rms := math.Sqrt(
|
||||
sumSquares / float64(sampleCount),
|
||||
)
|
||||
|
||||
return amplitudeToDBFS(rms), amplitudeToDBFS(peak)
|
||||
}
|
||||
|
||||
func amplitudeToDBFS(amplitude float64) float64 {
|
||||
if amplitude <= 0 {
|
||||
return minimumDBFS
|
||||
}
|
||||
|
||||
db := 20 * math.Log10(amplitude)
|
||||
|
||||
if db < minimumDBFS {
|
||||
return minimumDBFS
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func meter(
|
||||
db float64,
|
||||
minimum float64,
|
||||
maximum float64,
|
||||
width int,
|
||||
) string {
|
||||
ratio := (db - minimum) / (maximum - minimum)
|
||||
|
||||
if ratio < 0 {
|
||||
ratio = 0
|
||||
}
|
||||
|
||||
if ratio > 1 {
|
||||
ratio = 1
|
||||
}
|
||||
|
||||
filled := int(
|
||||
math.Round(ratio * float64(width)),
|
||||
)
|
||||
|
||||
return strings.Repeat("#", filled) +
|
||||
strings.Repeat(" ", width-filled)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"model_type": "nemo-conformer-tdt",
|
||||
"features_size": 128,
|
||||
"subsampling_factor": 8
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8193
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user