if len(data) < 44 {
return 0, 0, fmt.Errorf("file too small to be valid WAV")
}
// Verify RIFF header
if string(data[0:4]) != "RIFF" {
return 0, 0, fmt.Errorf("not a valid WAV file (missing RIFF header)")
}
// Verify WAVE format
if string(data[8:12]) != "WAVE" {
return 0, 0, fmt.Errorf("not a valid WAV file (missing WAVE format)")
}
var channels, bitsPerSample int
// Parse chunks - stop after finding data chunk
offset := 12
for offset < len(data)-8 {
chunkID := string(data[offset : offset+4])
chunkSize := int(binary.LittleEndian.Uint32(data[offset+4 : offset+8]))
offset += 8
switch chunkID {
case "fmt ":
// Parse format chunk
if chunkSize >= 16 && offset+16 <= len(data) {
channels = int(binary.LittleEndian.Uint16(data[offset+2 : offset+4]))
sampleRate = int(binary.LittleEndian.Uint32(data[offset+4 : offset+8]))
bitsPerSample = int(binary.LittleEndian.Uint16(data[offset+14 : offset+16]))
}
case "data":
// Found data chunk - calculate duration and return
if sampleRate > 0 && channels > 0 && bitsPerSample > 0 {
bytesPerSample := bitsPerSample / 8
bytesPerSecond := sampleRate * channels * bytesPerSample
if bytesPerSecond > 0 {
duration = float64(chunkSize) / float64(bytesPerSecond)
return sampleRate, duration, nil
}
}
return 0, 0, fmt.Errorf("invalid WAV: fmt chunk missing or corrupt before data chunk")
}
// Move to next chunk (word-aligned)
offset += chunkSize
if chunkSize%2 != 0 {
offset++
}