mirror of
https://github.com/anotherhadi/settuings.git
synced 2026-10-05 08:18:23 +02:00
98 lines
2.4 KiB
Go
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[:])
|
|
}
|