use std::{fs, path::Path};
use ncw::{NcwReader, SampleFormat};
struct Wav {
channels: u16,
sample_rate: u32,
bits: u16,
float: bool,
samples: Vec<i32>,
}
fn read_wav(path: &Path) -> Wav {
let d = fs::read(path).unwrap();
assert_eq!(&d[0..4], b"RIFF");
assert_eq!(&d[8..12], b"WAVE");
let mut fmt = None;
let mut data = None;
let mut pos = 12;
while pos + 8 <= d.len() {
let id = &d[pos..pos + 4];
let size = u32::from_le_bytes(d[pos + 4..pos + 8].try_into().unwrap()) as usize;
let body = &d[pos + 8..(pos + 8 + size).min(d.len())];
match id {
b"fmt " => fmt = Some(body.to_vec()),
b"data" => data = Some(body.to_vec()),
_ => {}
}
pos += 8 + size + (size & 1);
}
let fmt = fmt.expect("fmt chunk");
let data = data.expect("data chunk");
let tag = u16::from_le_bytes([fmt[0], fmt[1]]);
let channels = u16::from_le_bytes([fmt[2], fmt[3]]);
let sample_rate = u32::from_le_bytes(fmt[4..8].try_into().unwrap());
let bits = u16::from_le_bytes([fmt[14], fmt[15]]);
let float = tag == 3;
let bytes = bits as usize / 8;
let samples = data
.chunks_exact(bytes)
.map(|c| {
let mut raw = [0u8; 4];
raw[..bytes].copy_from_slice(c);
let v = u32::from_le_bytes(raw);
if float {
v as i32
} else {
let shift = 32 - bits as u32;
((v << shift) as i32) >> shift
}
})
.collect();
Wav {
channels,
sample_rate,
bits,
float,
samples,
}
}
fn check(name: &str) {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data");
let ncw_path = dir.join(format!("{name}.ncw"));
let wav_path = dir.join(format!("{name}.wav"));
let mut ncw = NcwReader::read(fs::File::open(&ncw_path).unwrap()).unwrap();
let decoded = ncw.decode_samples().unwrap();
let wav = read_wav(&wav_path);
assert_eq!(ncw.header.channels, wav.channels, "{name}: channels");
assert_eq!(ncw.header.sample_rate, wav.sample_rate, "{name}: sample rate");
assert_eq!(ncw.header.bits_per_sample, wav.bits, "{name}: bit depth");
assert_eq!(
ncw.sample_format == SampleFormat::Float,
wav.float,
"{name}: sample format"
);
let expected_len = ncw.header.num_samples as usize * ncw.header.channels as usize;
assert_eq!(decoded.len(), expected_len, "{name}: decoded length");
assert!(
wav.samples.len() >= decoded.len(),
"{name}: reference wav shorter than decoded output"
);
let mismatches: Vec<usize> = decoded
.iter()
.zip(&wav.samples)
.enumerate()
.filter(|(_, (a, b))| a != b)
.map(|(i, _)| i)
.collect();
assert!(
mismatches.is_empty(),
"{name}: {} mismatched samples, first at index {} (got {}, expected {})",
mismatches.len(),
mismatches[0],
decoded[mismatches[0]],
wav.samples[mismatches[0]]
);
}
#[test]
fn decode_16_bit_mono() {
check("16-bit-mono");
}
#[test]
fn decode_16_bit_stereo() {
check("16-bit-stereo");
}
#[test]
fn decode_24_bit_mono() {
check("24-bit-mono");
}
#[test]
fn decode_32_bit_mono_float() {
check("32-bit-mono-float");
}
#[test]
fn decode_32_bit_stereo_float() {
check("32-bit-stereo-float");
}
#[test]
fn decode_onezero_single_block() {
check("testfile-onezero-16-bit-stereo");
}
#[test]
fn decode_onezero_multiblock() {
check("testfile-onezero-16-bit-stereo-multiblock");
}
#[test]
fn decode_24_bit_stereo_has_no_reference_but_decodes() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/24-bit-stereo.ncw");
let mut ncw = NcwReader::read(fs::File::open(path).unwrap()).unwrap();
let samples = ncw.decode_samples().unwrap();
assert_eq!(
samples.len(),
ncw.header.num_samples as usize * ncw.header.channels as usize
);
}
#[test]
fn truncated_file_is_an_error_not_a_panic() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/16-bit-stereo.ncw");
let bytes = fs::read(path).unwrap();
let cut = &bytes[..bytes.len() / 2];
let mut ncw = NcwReader::read(std::io::Cursor::new(cut)).unwrap();
assert!(ncw.decode_samples().is_err());
}