///|
/// Decode a WAV file from raw bytes into an AudioBuffer.
/// Supports PCM format (format=1), 8-bit and 16-bit.
pub fn decode_wav(raw : Bytes) -> AudioBuffer raise AudioError {
// Minimum WAV header size
if raw.length() < 44 {
raise AudioError::InvalidFormat("WAV too short")
}
// RIFF header
if raw[0] != b'R' || raw[1] != b'I' || raw[2] != b'F' || raw[3] != b'F' {
raise AudioError::InvalidFormat("Missing RIFF header")
}
// WAVE format
if raw[8] != b'W' || raw[9] != b'A' || raw[10] != b'V' || raw[11] != b'E' {
raise AudioError::InvalidFormat("Missing WAVE format")
}
// Find fmt chunk
let (fmt_offset, _fmt_size) = find_chunk(raw, b'f', b'm', b't', b' ') catch {
_ => raise AudioError::InvalidFormat("Missing fmt chunk")
}
let audio_format = read_u16le(raw, fmt_offset)
if audio_format != 1 {
raise AudioError::InvalidFormat("Not PCM format")
}
let channels = read_u16le(raw, fmt_offset + 2)
let sample_rate = read_u32le(raw, fmt_offset + 4)
let bits_per_sample = read_u16le(raw, fmt_offset + 14)
if bits_per_sample != 8 && bits_per_sample != 16 {
raise AudioError::DecodeFailed("Unsupported bits per sample")
}
// Find data chunk
let (data_offset, data_size) = find_chunk(raw, b'd', b'a', b't', b'a') catch {
_ => raise AudioError::InvalidFormat("Missing data chunk")
}
let bytes_per_sample = bits_per_sample / 8
let total_samples = data_size / bytes_per_sample
let frames = total_samples / channels
let buf = new_audio_buffer(channels, sample_rate, frames)
for i in 0.. [-1.0, 1.0]
let raw_val = raw[byte_offset].to_int()
(Float::from_int(raw_val) - 128.0) / 128.0
}
let frame = i / channels
let channel = i % channels
set_sample(buf, frame, channel, sample)
}
buf
}
///|
/// Find a RIFF chunk by its 4-byte ID. Returns (data_offset, data_size).
fn find_chunk(
raw : Bytes,
c0 : Byte,
c1 : Byte,
c2 : Byte,
c3 : Byte,
) -> (Int, Int) raise AudioError {
let len = raw.length()
// Start after RIFF header (12 bytes)
let mut offset = 12
while offset + 8 <= len {
if raw[offset] == c0 &&
raw[offset + 1] == c1 &&
raw[offset + 2] == c2 &&
raw[offset + 3] == c3 {
let chunk_size = read_u32le(raw, offset + 4)
return (offset + 8, chunk_size)
}
let chunk_size = read_u32le(raw, offset + 4)
offset = offset + 8 + chunk_size
// Chunks are word-aligned
if offset % 2 != 0 {
offset = offset + 1
}
}
raise AudioError::InvalidFormat("Chunk not found")
}