//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 }