mirror of
https://github.com/anotherhadi/settuings.git
synced 2026-10-05 08:18:23 +02:00
88 lines
2.1 KiB
Go
88 lines
2.1 KiB
Go
package audio
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"io"
|
|
"math"
|
|
"os/exec"
|
|
"strconv"
|
|
)
|
|
|
|
// levelSampleRate and levelFrameSamples pick a small chunk size (~100ms) so
|
|
// the UI can poll for a fresh peak reading at a responsive rate without
|
|
// reading much audio data.
|
|
const (
|
|
levelSampleRate = 16000
|
|
levelFrameFrames = levelSampleRate / 10 // 100ms
|
|
levelFrameBytes = levelFrameFrames * 4 // 4 bytes per float32 sample
|
|
)
|
|
|
|
// LevelMonitor streams raw audio from a source and reports its peak
|
|
// amplitude in short chunks, for a live input-level meter. Callers must call
|
|
// Stop when done to release the underlying pw-cat process.
|
|
type LevelMonitor struct {
|
|
cmd *exec.Cmd
|
|
stdout io.ReadCloser
|
|
}
|
|
|
|
// StartLevelMonitor begins capturing raw audio from sourceID.
|
|
func StartLevelMonitor(sourceID string) (*LevelMonitor, error) {
|
|
target, err := nodeTarget(sourceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cmd := exec.Command("pw-cat", // #nosec G204 -- target is a node.name read back from `wpctl inspect`, never shell-interpreted
|
|
"--record",
|
|
"--target", target,
|
|
"--format", "f32",
|
|
"--rate", strconv.Itoa(levelSampleRate),
|
|
"--channels", "1",
|
|
"--raw",
|
|
"-",
|
|
)
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &LevelMonitor{cmd: cmd, stdout: stdout}, nil
|
|
}
|
|
|
|
// Next blocks until one ~100ms chunk of audio has been captured and returns
|
|
// its peak amplitude as a value in [0, 1]. It returns an error once the
|
|
// monitor is stopped or the source disappears.
|
|
func (l *LevelMonitor) Next() (float64, error) {
|
|
buf := make([]byte, levelFrameBytes)
|
|
n, err := io.ReadFull(l.stdout, buf)
|
|
if n == 0 {
|
|
return 0, err
|
|
}
|
|
|
|
var peak float32
|
|
for i := 0; i+4 <= n; i += 4 {
|
|
v := math.Float32frombits(binary.LittleEndian.Uint32(buf[i : i+4]))
|
|
if v < 0 {
|
|
v = -v
|
|
}
|
|
if v > peak {
|
|
peak = v
|
|
}
|
|
}
|
|
if peak > 1 {
|
|
peak = 1
|
|
}
|
|
return float64(peak), nil
|
|
}
|
|
|
|
// Stop terminates the capture process. Safe to call more than once.
|
|
func (l *LevelMonitor) Stop() {
|
|
if l.cmd.Process != nil {
|
|
_ = l.cmd.Process.Kill()
|
|
}
|
|
_ = l.cmd.Wait()
|
|
}
|