pub fn write_wav_pcm_f32(samples: &[f32], sample_rate: u32, channels: u16) -> Vec<u8> {
let byte_rate = sample_rate * u32::from(channels) * 4;
let block_align = channels * 4;
let data_size = (samples.len() * 4) as u32;
let chunk_size = 36 + data_size;
let mut buf = Vec::with_capacity(44 + samples.len() * 4);
buf.extend_from_slice(b"RIFF");
buf.extend_from_slice(&chunk_size.to_le_bytes());
buf.extend_from_slice(b"WAVE");
buf.extend_from_slice(b"fmt ");
buf.extend_from_slice(&16_u32.to_le_bytes()); buf.extend_from_slice(&3_u16.to_le_bytes()); buf.extend_from_slice(&channels.to_le_bytes());
buf.extend_from_slice(&sample_rate.to_le_bytes());
buf.extend_from_slice(&byte_rate.to_le_bytes());
buf.extend_from_slice(&block_align.to_le_bytes());
buf.extend_from_slice(&32_u16.to_le_bytes());
buf.extend_from_slice(b"data");
buf.extend_from_slice(&data_size.to_le_bytes());
for s in samples {
buf.extend_from_slice(&s.to_le_bytes());
}
buf
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_layout_matches_spec() {
let buf = write_wav_pcm_f32(&[0.0; 4], 16_000, 1);
assert_eq!(buf.len(), 44 + 16);
assert_eq!(&buf[0..4], b"RIFF");
assert_eq!(&buf[8..12], b"WAVE");
assert_eq!(&buf[12..16], b"fmt ");
assert_eq!(u16::from_le_bytes([buf[20], buf[21]]), 3);
assert_eq!(u16::from_le_bytes([buf[22], buf[23]]), 1);
assert_eq!(
u32::from_le_bytes([buf[24], buf[25], buf[26], buf[27]]),
16_000
);
assert_eq!(u16::from_le_bytes([buf[34], buf[35]]), 32);
assert_eq!(&buf[36..40], b"data");
}
#[test]
fn chunk_size_accounts_for_data_bytes() {
let buf = write_wav_pcm_f32(&[0.0; 4], 16_000, 1);
assert_eq!(u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]), 52);
}
#[test]
fn stereo_doubles_data_bytes() {
let stereo = write_wav_pcm_f32(&[0.0; 8], 16_000, 2);
let mono = write_wav_pcm_f32(&[0.0; 8], 16_000, 1);
assert_eq!(mono.len(), stereo.len());
assert_eq!(
u32::from_le_bytes([mono[28], mono[29], mono[30], mono[31]]),
16_000 * 4
);
assert_eq!(
u32::from_le_bytes([stereo[28], stereo[29], stereo[30], stereo[31]]),
16_000 * 4 * 2
);
assert_eq!(u16::from_le_bytes([mono[32], mono[33]]), 4);
assert_eq!(u16::from_le_bytes([stereo[32], stereo[33]]), 8);
}
#[test]
fn samples_round_trip_through_hound() {
let samples: Vec<f32> = (0..32).map(|i| (i as f32 / 32.0) * 2.0 - 1.0).collect();
let buf = write_wav_pcm_f32(&samples, 16_000, 1);
let cursor = std::io::Cursor::new(buf);
let mut reader = hound::WavReader::new(cursor).expect("hound reader");
assert_eq!(reader.spec().sample_rate, 16_000);
assert_eq!(reader.spec().channels, 1);
assert_eq!(reader.spec().bits_per_sample, 32);
let decoded: Vec<f32> = reader
.samples::<f32>()
.map(|s| s.expect("decode sample"))
.collect();
assert_eq!(decoded, samples);
}
}