package utils
import (
"bufio"
"encoding/binary"
"fmt"
"os"
)
// WriteWAVFile writes audio samples to a WAV file.
// Samples should be in the range -1.0 to 1.0.
// Output is mono 16-bit PCM.
func WriteWAVFile(filepath string, samples []float64, sampleRate int) error {
if len(samples) == 0 {
return fmt.Errorf("no samples to write")
}
file, err := os.Create(filepath)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
w := bufio.NewWriterSize(file, 64*1024)
// Write WAV and flush; check close to ensure data is persisted.
err = func() error {
// WAV parameters
channels := 1
bitsPerSample := 16
bytesPerSample := bitsPerSample / 8
byteRate := sampleRate * channels * bytesPerSample
blockAlign := channels * bytesPerSample
dataSize := len(samples) * bytesPerSample
totalSize := 36 + dataSize // 36 = header size before data chunk
// Write 44-byte WAV header in one go
header := make([]byte, 44)
copy(header[0:4], "RIFF")
binary.LittleEndian.PutUint32(header[4:8], uint32(totalSize))
copy(header[8:12], "WAVE")
copy(header[12:16], "fmt ")
binary.LittleEndian.PutUint32(header[16:20], 16) // chunk size
binary.LittleEndian.PutUint16(header[20:22], 1) // PCM format
binary.LittleEndian.PutUint16(header[22:24], uint16(channels))
binary.LittleEndian.PutUint32(header[24:28], uint32(sampleRate))
binary.LittleEndian.PutUint32(header[28:32], uint32(byteRate))
binary.LittleEndian.PutUint16(header[32:34], uint16(blockAlign))
binary.LittleEndian.PutUint16(header[34:36], uint16(bitsPerSample))
copy(header[36:40], "data")
binary.LittleEndian.PutUint32(header[40:44], uint32(dataSize))
if _, err := w.Write(header); err != nil {
return err
}
// Convert all float64 samples to 16-bit PCM in a single buffer
buf := Float64ToPCM16(samples)
if _, err := w.Write(buf); err != nil {
return err
}
return w.Flush()
}()
if err2 := file.Close(); err2 != nil {
if err == nil {
err = fmt.Errorf("failed to close file: %w", err2)
}
}
return err
}