use std::fs;
use std::io::Write;
use std::time::{Duration, Instant};
use wav_codec::WavReader;
const MAX_RATIO: f64 = 3.0;
const RUNS: usize = 10;
const FRAMES: usize = 44_100 * 30;
fn fixture() -> Vec<u8> {
let data_len = (FRAMES * 4) as u32;
let mut out = Vec::with_capacity(44 + FRAMES * 4);
out.extend_from_slice(b"RIFF");
out.extend_from_slice(&(36 + data_len).to_le_bytes());
out.extend_from_slice(b"WAVEfmt ");
out.extend_from_slice(&16u32.to_le_bytes());
out.extend_from_slice(&1u16.to_le_bytes());
out.extend_from_slice(&2u16.to_le_bytes());
out.extend_from_slice(&44_100u32.to_le_bytes());
out.extend_from_slice(&176_400u32.to_le_bytes());
out.extend_from_slice(&4u16.to_le_bytes());
out.extend_from_slice(&16u16.to_le_bytes());
out.extend_from_slice(b"data");
out.extend_from_slice(&data_len.to_le_bytes());
let mut x: u32 = 0;
for _ in 0..FRAMES {
x = x.wrapping_mul(1_103_515_245).wrapping_add(12_345) & 0x7FFF_FFFF;
let l = (x & 0xFFFF) as i32 - 32_768;
let r = ((x >> 15) & 0xFFFF) as i32 - 32_768;
out.extend_from_slice(&(l as i16).to_le_bytes());
out.extend_from_slice(&(r as i16).to_le_bytes());
}
out
}
fn checksum(samples: &[i16]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for s in samples {
h ^= *s as u16 as u64;
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
fn raw_decode(path: &std::path::Path) -> u64 {
let bytes = fs::read(path).unwrap();
let samples: Vec<i16> = bytes[44..]
.chunks_exact(2)
.map(|c| i16::from_le_bytes([c[0], c[1]]))
.collect();
checksum(&samples)
}
fn via_iterator(path: &std::path::Path) -> u64 {
let mut reader = WavReader::open(path).unwrap();
let samples: Vec<i16> = reader.samples::<i16>().map(|s| s.unwrap()).collect();
checksum(&samples)
}
fn via_read_samples(path: &std::path::Path) -> u64 {
let mut reader = WavReader::open(path).unwrap();
let mut out: Vec<i16> = Vec::with_capacity(reader.len() as usize);
let mut buf = [0i16; 4096];
loop {
let n = reader.read_samples(&mut buf).unwrap();
if n == 0 {
break;
}
out.extend_from_slice(&buf[..n]);
}
checksum(&out)
}
fn best_of<F: FnMut() -> u64>(mut f: F, expected: u64) -> Duration {
let mut best = Duration::MAX;
for _ in 0..RUNS {
let start = Instant::now();
let got = f();
best = best.min(start.elapsed());
assert_eq!(got, expected, "checksum mismatch");
}
best
}
fn main() {
let bytes = fixture();
let mib = bytes.len() as f64 / (1024.0 * 1024.0);
let path = std::env::temp_dir().join(format!("wav-codec-bench-{}.wav", std::process::id()));
fs::File::create(&path).unwrap().write_all(&bytes).unwrap();
let expected = {
let samples: Vec<i16> = bytes[44..]
.chunks_exact(2)
.map(|c| i16::from_le_bytes([c[0], c[1]]))
.collect();
checksum(&samples)
};
drop(bytes);
raw_decode(&path);
let raw = best_of(|| raw_decode(&path), expected);
let iter = best_of(|| via_iterator(&path), expected);
let bulk = best_of(|| via_read_samples(&path), expected);
let _ = fs::remove_file(&path);
let report = |name: &str, t: Duration| {
let secs = t.as_secs_f64().max(1e-9);
println!(
"{:<14} {:>9.3} ms {:>9.0} MiB/s {:>6.2}x raw",
name,
secs * 1e3,
mib / secs,
secs / raw.as_secs_f64().max(1e-9)
);
};
println!("perf fixture: {:.1} MiB, best of {} runs", mib, RUNS);
report("raw decode", raw);
report("samples()", iter);
report("read_samples", bulk);
let release = !cfg!(debug_assertions);
if release {
let raw_s = raw.as_secs_f64().max(1e-9);
for (name, t) in [("samples()", iter), ("read_samples", bulk)] {
let ratio = t.as_secs_f64() / raw_s;
assert!(
ratio <= MAX_RATIO,
"{} is {:.2}x the raw decode, limit {}x",
name,
ratio,
MAX_RATIO
);
}
println!(
"ratio gate: both paths within {}x of the raw decode",
MAX_RATIO
);
} else {
println!("debug build: ratio gate not enforced");
}
}