wav-codec 0.1.0

WAV (RIFF/WAVE) reader and writer over std I/O. PCM and IEEE-float WAV only. Compressed WAV (ADPCM, mu-law, A-law) not decoded.
Documentation
//! Gate (f): large-file read throughput, std-only (`harness = false`).
//!
//! Regenerates the 5.3 MB perf fixture (30 s stereo i16 LCG noise), writes
//! it to a temporary file, and times three full decodes: a raw `fs::read`
//! plus `chunks_exact` baseline, `WavReader::samples`, and
//! `WavReader::read_samples`. Each figure is the best of several runs.
//! Under `cargo bench` (release) the two reader figures are checked against
//! the pinned ratio below and the run fails on a regression. Under a debug
//! build (`cargo test --benches`) the bench only reports.

use std::fs;
use std::io::Write;
use std::time::{Duration, Instant};

use wav_codec::WavReader;

/// Reader time divided by raw decode time must stay at or below this.
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");
    }
}