Files
settuings/internal/audio/play.go
T
Hadi 59b31e5ca3 init
Signed-off-by: Hadi <[email protected]>
2026-08-13 00:58:37 +02:00

98 lines
2.4 KiB
Go

package audio
import (
"bytes"
"context"
"encoding/binary"
"math"
"os/exec"
)
const sampleRate = 44100
// toneStep is one segment of the test chime: a tone at freqHz lasting dur,
// with a short fade in/out to avoid clicks.
type toneStep struct {
freqHz float64
dur float64 // seconds
}
// testChime is a short two-tone "ding-dong" beep, the same shape used by
// most desktop sound settings panels to preview an output device.
var testChime = []toneStep{
{freqHz: 880, dur: 0.15},
{freqHz: 660, dur: 0.22},
}
// PlayTestTone plays a short chime through the given sink, without changing
// the system default output.
func PlayTestTone(sinkID string) error {
target, err := nodeTarget(sinkID)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, "pw-play", "--target", target, "-") // #nosec G204 -- target is a node.name read back from `wpctl inspect`, never shell-interpreted
cmd.Stdin = bytes.NewReader(synthChime(testChime))
return cmd.Run()
}
// synthChime renders steps as a 16-bit PCM mono WAV file in memory.
func synthChime(steps []toneStep) []byte {
const amplitude = 0.35 * 32767
const fade = 0.015 // seconds of fade in/out per step, to avoid clicks
var samples []int16
for _, step := range steps {
n := int(step.dur * sampleRate)
for i := 0; i < n; i++ {
t := float64(i) / sampleRate
env := 1.0
if t < fade {
env = t / fade
} else if remaining := step.dur - t; remaining < fade {
env = remaining / fade
}
v := amplitude * env * math.Sin(2*math.Pi*step.freqHz*t)
samples = append(samples, int16(v))
}
}
pcm := make([]byte, len(samples)*2)
for i, s := range samples {
binary.LittleEndian.PutUint16(pcm[i*2:], uint16(s))
}
var buf bytes.Buffer
buf.WriteString("RIFF")
writeU32(&buf, uint32(36+len(pcm)))
buf.WriteString("WAVE")
buf.WriteString("fmt ")
writeU32(&buf, 16)
writeU16(&buf, 1) // PCM
writeU16(&buf, 1) // mono
writeU32(&buf, sampleRate)
writeU32(&buf, sampleRate*2)
writeU16(&buf, 2) // block align
writeU16(&buf, 16) // bits per sample
buf.WriteString("data")
writeU32(&buf, uint32(len(pcm)))
buf.Write(pcm)
return buf.Bytes()
}
func writeU32(buf *bytes.Buffer, v uint32) {
var b [4]byte
binary.LittleEndian.PutUint32(b[:], v)
buf.Write(b[:])
}
func writeU16(buf *bytes.Buffer, v uint16) {
var b [2]byte
binary.LittleEndian.PutUint16(b[:], v)
buf.Write(b[:])
}