Files
OpsLog/internal/audio/wav.go
T
2026-06-04 00:46:35 +02:00

87 lines
2.4 KiB
Go

//go:build windows
package audio
import (
"encoding/binary"
"fmt"
"os"
)
// The DVK/recorder pipeline uses a single fixed PCM format end-to-end: 16 kHz
// mono 16-bit. That's plenty for SSB voice (3 kHz audio bandwidth), keeps files
// tiny (~32 KB/s), and — fed through WASAPI's AUTOCONVERTPCM — plays/records on
// any device regardless of its native mix format.
const (
sampleRate = 16000
channels = 1
bitsPerSample = 16
blockAlign = channels * bitsPerSample / 8 // bytes per frame (=2)
bytesPerSec = sampleRate * blockAlign // =32000
)
// writeWAV writes 16-bit PCM as a canonical RIFF/WAVE file.
func writeWAV(path string, pcm []byte) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
dataLen := len(pcm)
put := func(v any) { _ = binary.Write(f, binary.LittleEndian, v) }
f.WriteString("RIFF")
put(uint32(36 + dataLen))
f.WriteString("WAVE")
f.WriteString("fmt ")
put(uint32(16)) // PCM fmt chunk size
put(uint16(1)) // WAVE_FORMAT_PCM
put(uint16(channels)) //
put(uint32(sampleRate)) //
put(uint32(bytesPerSec)) // byte rate
put(uint16(blockAlign)) //
put(uint16(bitsPerSample)) //
f.WriteString("data")
put(uint32(dataLen))
_, err = f.Write(pcm)
return err
}
// readWAV reads a PCM WAV and returns the raw sample bytes plus its format.
// Handles arbitrary chunk ordering (walks the RIFF chunk list).
func readWAV(path string) (pcm []byte, rate, ch, bits int, err error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, 0, 0, 0, err
}
if len(b) < 12 || string(b[0:4]) != "RIFF" || string(b[8:12]) != "WAVE" {
return nil, 0, 0, 0, fmt.Errorf("not a WAVE file")
}
i := 12
for i+8 <= len(b) {
id := string(b[i : i+4])
size := int(binary.LittleEndian.Uint32(b[i+4 : i+8]))
body := i + 8
if body+size > len(b) {
size = len(b) - body
}
switch id {
case "fmt ":
if size >= 16 {
ch = int(binary.LittleEndian.Uint16(b[body+2 : body+4]))
rate = int(binary.LittleEndian.Uint32(b[body+4 : body+8]))
bits = int(binary.LittleEndian.Uint16(b[body+14 : body+16]))
}
case "data":
pcm = b[body : body+size]
}
i = body + size
if size%2 == 1 {
i++ // chunks are word-aligned
}
}
if pcm == nil || rate == 0 {
return nil, 0, 0, 0, fmt.Errorf("WAV missing fmt/data")
}
return pcm, rate, ch, bits, nil
}