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
//! Shared harness for the conformance replay tests: manifest access, fixture
//! loading pinned by sha256, expected-sample decoding, and the generated
//! perf fixture. Std only.

#![allow(dead_code)]

pub mod json;
pub mod sha256;

use std::io::Cursor;

use json::Json;
use wav_codec::{SampleFormat, WavReader, WavSpec};

pub use sha256::sha256_hex;

/// The conformance manifest shipped beside the tests.
pub fn manifest() -> Json {
    json::parse(include_str!("../conformance-vectors.json"))
}

/// All vectors in manifest order.
pub fn vectors() -> Vec<Json> {
    manifest().field("vectors").as_array().to_vec()
}

/// The vector with the given id.
pub fn vector(id: &str) -> Json {
    vectors()
        .into_iter()
        .find(|v| v.field("id").as_str() == id)
        .unwrap_or_else(|| panic!("no vector {}", id))
}

/// Absolute path of a fixture named relative to `tests/`.
pub fn fixture_path(rel: &str) -> String {
    format!("{}/tests/{}", env!("CARGO_MANIFEST_DIR"), rel)
}

/// Bytes of a vector's fixture, checked against the manifest's length and
/// sha256 before use. The perf vector is generated in memory.
pub fn fixture_bytes(v: &Json) -> Vec<u8> {
    let rel = v.field("file").as_str();
    let bytes = if v.field("kind").as_str() == "perf" {
        perf_fixture()
    } else {
        std::fs::read(fixture_path(rel)).unwrap_or_else(|e| panic!("read {}: {}", rel, e))
    };
    assert_eq!(
        bytes.len() as u64,
        v.field("len").as_u64(),
        "{} length",
        rel
    );
    assert_eq!(
        sha256_hex(&bytes),
        v.field("sha256").as_str(),
        "{} sha256",
        rel
    );
    bytes
}

/// Open a fixture from memory.
pub fn open(bytes: &[u8]) -> wav_codec::Result<WavReader<Cursor<&[u8]>>> {
    WavReader::new(Cursor::new(bytes))
}

/// The manifest's `spec` object as a `WavSpec`.
pub fn expected_spec(v: &Json) -> WavSpec {
    let s = v.field("spec");
    WavSpec {
        channels: s.field("channels").as_u64() as u16,
        sample_rate: s.field("sample_rate").as_u64() as u32,
        bits_per_sample: s.field("bits_per_sample").as_u64() as u16,
        sample_format: match s.field("sample_format").as_str() {
            "int" => SampleFormat::Int,
            "float" => SampleFormat::Float,
            other => panic!("unknown sample_format {}", other),
        },
    }
}

/// Expected samples in the manifest's own encoding.
#[derive(Clone, Debug, PartialEq)]
pub enum Expected {
    /// `samples_u8`: stored bytes of an 8-bit file, exposed as `byte - 128`.
    I8(Vec<i8>),
    I16(Vec<i16>),
    I32(Vec<i32>),
    /// `samples_f32_lebits`: exact little-endian bit patterns.
    F32(Vec<u32>),
    /// `samples_f64_lebits`: exact little-endian bit patterns.
    F64(Vec<u64>),
}

impl Expected {
    pub fn len(&self) -> usize {
        match self {
            Expected::I8(v) => v.len(),
            Expected::I16(v) => v.len(),
            Expected::I32(v) => v.len(),
            Expected::F32(v) => v.len(),
            Expected::F64(v) => v.len(),
        }
    }
}

/// The vector's expected samples, or `None` when it lists none.
pub fn expected_samples(v: &Json) -> Option<Expected> {
    let e = v.get("expect")?;
    if let Some(a) = e.get("samples_u8") {
        let out = a
            .as_array()
            .iter()
            .map(|x| (x.as_i64() - 128) as i8)
            .collect();
        return Some(Expected::I8(out));
    }
    if let Some(a) = e.get("samples_i16") {
        return Some(Expected::I16(
            a.as_array().iter().map(|x| x.as_i64() as i16).collect(),
        ));
    }
    if let Some(a) = e.get("samples_i32") {
        return Some(Expected::I32(
            a.as_array().iter().map(|x| x.as_i64() as i32).collect(),
        ));
    }
    if let Some(a) = e.get("samples_f32_lebits") {
        return Some(Expected::F32(
            a.as_array()
                .iter()
                .map(|x| u32::from_le_bytes(hex_bytes(x.as_str()).try_into().unwrap()))
                .collect(),
        ));
    }
    if let Some(a) = e.get("samples_f64_lebits") {
        return Some(Expected::F64(
            a.as_array()
                .iter()
                .map(|x| u64::from_le_bytes(hex_bytes(x.as_str()).try_into().unwrap()))
                .collect(),
        ));
    }
    None
}

pub fn hex_bytes(hex: &str) -> Vec<u8> {
    (0..hex.len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
        .collect()
}

/// Frames of the perf fixture: 30 s at 44100 Hz.
pub const PERF_FRAMES: usize = 44100 * 30;

/// The LCG behind the perf fixture (C `rand` constants, seed 0). Each call
/// returns the next `(left, right)` frame.
pub struct PerfLcg {
    state: u32,
}

impl PerfLcg {
    pub fn new() -> Self {
        PerfLcg { state: 0 }
    }

    pub fn next_frame(&mut self) -> (i16, i16) {
        self.state = self.state.wrapping_mul(1_103_515_245).wrapping_add(12_345) & 0x7FFF_FFFF;
        let l = (self.state & 0xFFFF) as i32 - 32_768;
        let r = ((self.state >> 15) & 0xFFFF) as i32 - 32_768;
        (l as i16, r as i16)
    }
}

impl Default for PerfLcg {
    fn default() -> Self {
        Self::new()
    }
}

/// Regenerate the P01 perf fixture byte-for-byte (the manifest pins its
/// sha256, and `fixture_bytes` checks it).
pub fn perf_fixture() -> Vec<u8> {
    let data_len = (PERF_FRAMES * 4) as u32;
    let mut out = Vec::with_capacity(44 + PERF_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 lcg = PerfLcg::new();
    for _ in 0..PERF_FRAMES {
        let (l, r) = lcg.next_frame();
        out.extend_from_slice(&l.to_le_bytes());
        out.extend_from_slice(&r.to_le_bytes());
    }
    out
}

/// Order-sensitive checksum over a sample stream.
pub fn checksum(samples: impl IntoIterator<Item = 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
}

/// The checksum the LCG itself produces, independent of any file.
pub fn perf_expected_checksum() -> u64 {
    let mut lcg = PerfLcg::new();
    checksum((0..PERF_FRAMES).flat_map(|_| {
        let (l, r) = lcg.next_frame();
        [l, r]
    }))
}