mirror of
https://github.com/anotherhadi/settuings.git
synced 2026-10-05 16:18:24 +02:00
@@ -0,0 +1,97 @@
|
||||
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[:])
|
||||
}
|
||||
Reference in New Issue
Block a user