package audio import ( "encoding/binary" "os" "path/filepath" "testing" ) func TestPCM16ToSample(t *testing.T) { if pcm16ToSample(32767) <= 0 { t.Fatal("expected positive sample") } if pcm16ToSample(-32768) < -1.0 { t.Fatal("expected clamped lower bound") } } func TestLoadWAVSource(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.wav") wav := buildMinimalWAV(48000, 1, []int16{0, 32767, -32768, 0}) if err := os.WriteFile(path, wav, 0o644); err != nil { t.Fatalf("write wav: %v", err) } src, err := LoadWAVSource(path) if err != nil { t.Fatalf("LoadWAVSource failed: %v", err) } if src.SampleRate != 48000 { t.Fatalf("unexpected sample rate: %d", src.SampleRate) } if src.Channels != 1 { t.Fatalf("unexpected channels: %d", src.Channels) } _ = src.NextFrame() } func TestLoadWAVWithExtraChunks(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "extra.wav") // Build a WAV with a LIST chunk between fmt and data wav := buildWAVWithExtraChunks(44100, 1, []int16{100, -100}) if err := os.WriteFile(path, wav, 0o644); err != nil { t.Fatalf("write wav: %v", err) } src, err := LoadWAVSource(path) if err != nil { t.Fatalf("LoadWAVSource with extra chunks failed: %v", err) } if src.SampleRate != 44100 { t.Fatalf("unexpected sample rate: %d", src.SampleRate) } if len(src.frames) != 2 { t.Fatalf("expected 2 frames, got %d", len(src.frames)) } } func TestRejectInvalidWAV(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "bad.wav") if err := os.WriteFile(path, []byte("nope"), 0o644); err != nil { t.Fatalf("write wav: %v", err) } if _, err := LoadWAVSource(path); err == nil { t.Fatal("expected wav load error") } } func TestStereoWAV(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "stereo.wav") wav := buildMinimalWAV(48000, 2, []int16{1000, -1000, 2000, -2000}) if err := os.WriteFile(path, wav, 0o644); err != nil { t.Fatalf("write wav: %v", err) } src, err := LoadWAVSource(path) if err != nil { t.Fatalf("LoadWAVSource stereo failed: %v", err) } if src.Channels != 2 { t.Fatalf("expected 2 channels, got %d", src.Channels) } if len(src.frames) != 2 { t.Fatalf("expected 2 frames, got %d", len(src.frames)) } } // -- helpers -- func buildMinimalWAV(sampleRate int, channels int, samples []int16) []byte { dataSize := len(samples) * 2 fileSize := 36 + dataSize buf := make([]byte, 0, 44+dataSize) buf = append(buf, []byte("RIFF")...) buf = binary.LittleEndian.AppendUint32(buf, uint32(fileSize)) buf = append(buf, []byte("WAVE")...) // fmt chunk buf = append(buf, []byte("fmt ")...) buf = binary.LittleEndian.AppendUint32(buf, 16) buf = binary.LittleEndian.AppendUint16(buf, 1) // PCM buf = binary.LittleEndian.AppendUint16(buf, uint16(channels)) buf = binary.LittleEndian.AppendUint32(buf, uint32(sampleRate)) buf = binary.LittleEndian.AppendUint32(buf, uint32(sampleRate*channels*2)) // byte rate buf = binary.LittleEndian.AppendUint16(buf, uint16(channels*2)) // block align buf = binary.LittleEndian.AppendUint16(buf, 16) // bits per sample // data chunk buf = append(buf, []byte("data")...) buf = binary.LittleEndian.AppendUint32(buf, uint32(dataSize)) for _, s := range samples { buf = binary.LittleEndian.AppendUint16(buf, uint16(s)) } return buf } func buildWAVWithExtraChunks(sampleRate int, channels int, samples []int16) []byte { dataSize := len(samples) * 2 // Add a fake LIST chunk of 12 bytes between fmt and data listChunkData := []byte("INFOtest") // 8 bytes listChunkSize := uint32(len(listChunkData)) totalExtraChunk := 8 + len(listChunkData) // "LIST" + size + data fileSize := 36 + totalExtraChunk + dataSize buf := make([]byte, 0, 44+totalExtraChunk+dataSize) buf = append(buf, []byte("RIFF")...) buf = binary.LittleEndian.AppendUint32(buf, uint32(fileSize)) buf = append(buf, []byte("WAVE")...) // fmt chunk buf = append(buf, []byte("fmt ")...) buf = binary.LittleEndian.AppendUint32(buf, 16) buf = binary.LittleEndian.AppendUint16(buf, 1) buf = binary.LittleEndian.AppendUint16(buf, uint16(channels)) buf = binary.LittleEndian.AppendUint32(buf, uint32(sampleRate)) buf = binary.LittleEndian.AppendUint32(buf, uint32(sampleRate*channels*2)) buf = binary.LittleEndian.AppendUint16(buf, uint16(channels*2)) buf = binary.LittleEndian.AppendUint16(buf, 16) // LIST chunk (extra, should be skipped) buf = append(buf, []byte("LIST")...) buf = binary.LittleEndian.AppendUint32(buf, listChunkSize) buf = append(buf, listChunkData...) // data chunk buf = append(buf, []byte("data")...) buf = binary.LittleEndian.AppendUint32(buf, uint32(dataSize)) for _, s := range samples { buf = binary.LittleEndian.AppendUint16(buf, uint16(s)) } return buf }