use flac_io::{decode, encode, encode_ogg, info, FlacAudio, FlacError};
fn make(channels: u8, bps: u8, rate: u32, samples: Vec<Vec<i32>>) -> FlacAudio {
FlacAudio {
sample_rate: rate,
channels,
bits_per_sample: bps,
samples,
}
}
fn range(bps: u8) -> (i64, i64) {
((-(1i64 << (bps - 1))), (1i64 << (bps - 1)) - 1)
}
fn assert_round_trips(a: &FlacAudio, what: &str) {
let nat = encode(a).unwrap_or_else(|e| panic!("encode {what}: {e}"));
let dn = decode(&nat).unwrap_or_else(|e| panic!("decode native {what}: {e}"));
assert_eq!(dn.samples, a.samples, "native round-trip {what}");
assert_eq!(dn.sample_rate, a.sample_rate, "native rate {what}");
assert_eq!(dn.channels, a.channels, "native channels {what}");
assert_eq!(dn.bits_per_sample, a.bits_per_sample, "native bps {what}");
assert_eq!(encode(a).unwrap(), nat, "native determinism {what}");
let ogg = encode_ogg(a).unwrap_or_else(|e| panic!("encode_ogg {what}: {e}"));
let dg = decode(&ogg).unwrap_or_else(|e| panic!("decode ogg {what}: {e}"));
assert_eq!(dg.samples, a.samples, "ogg round-trip {what}");
assert_eq!(dg.bits_per_sample, a.bits_per_sample, "ogg bps {what}");
assert_eq!(encode_ogg(a).unwrap(), ogg, "ogg determinism {what}");
for (label, bytes) in [("native", &nat), ("ogg", &ogg)] {
let i = info(bytes).unwrap_or_else(|e| panic!("info {label} {what}: {e}"));
assert_eq!(i.sample_rate, a.sample_rate, "info rate {label} {what}");
assert_eq!(i.channels, a.channels, "info channels {label} {what}");
assert_eq!(
i.bits_per_sample, a.bits_per_sample,
"info bps {label} {what}"
);
assert_eq!(
i.total_samples as usize,
a.samples_per_channel(),
"info total {label} {what}"
);
}
}
#[test]
fn every_bit_depth_round_trips() {
for bps in 4u8..=32 {
let (lo, hi) = range(bps);
let span = (hi - lo) as i128;
let ch: Vec<i32> = (0..2000usize)
.map(|n| {
let v = lo as i128 + ((n as i128 * 2_654_435_761) % (span + 1));
v as i64 as i32
})
.collect();
let a = make(1, bps, 44100, vec![ch]);
assert_round_trips(&a, &format!("{bps}-bit"));
}
}
#[test]
fn value_boundaries_for_every_depth() {
for bps in 4u8..=32 {
let (lo, hi) = range(bps);
let ch: Vec<i32> = (0..1500)
.map(|n| if n % 2 == 0 { lo as i32 } else { hi as i32 })
.collect();
let a = make(2, bps, 48000, vec![ch.clone(), ch]);
assert_round_trips(&a, &format!("{bps}-bit extremes"));
}
}
#[test]
fn all_channel_counts() {
for channels in 1u8..=8 {
let (lo, hi) = range(16);
let samples: Vec<Vec<i32>> = (0..channels)
.map(|c| {
(0..3000usize)
.map(|n| {
let v = lo + ((n as i64 * (c as i64 * 37 + 11)) % (hi - lo + 1));
v as i32
})
.collect()
})
.collect();
let a = make(channels, 16, 48000, samples);
assert_round_trips(&a, &format!("{channels} channels"));
}
}
#[test]
fn pathological_signal_shapes() {
let (lo64, hi64) = range(16);
let (lo, hi) = (lo64 as i32, hi64 as i32);
let span = hi64 - lo64 + 1;
let n = 9000usize;
for name in [
"silence",
"dc_max",
"dc_min",
"alt_extremes",
"ramp",
"impulse",
] {
let ch: Vec<i32> = (0..n)
.map(|k| match name {
"silence" => 0,
"dc_max" => hi,
"dc_min" => lo,
"alt_extremes" => {
if k % 2 == 0 {
lo
} else {
hi
}
}
"ramp" => (lo64 + (k as i64 % span)) as i32,
"impulse" => {
if k == 4096 {
hi
} else if k == 5000 {
lo
} else {
0
}
}
_ => unreachable!(),
})
.collect();
let a = make(1, 16, 44100, vec![ch]);
assert_round_trips(&a, name);
}
}
#[test]
fn awkward_lengths() {
let (lo, hi) = range(16);
for len in [0usize, 1, 2, 3, 7, 4095, 4096, 4097, 8191, 8192, 8193, 9973] {
let ch: Vec<i32> = (0..len)
.map(|n| (lo + (n as i64 * 131 % (hi - lo + 1))) as i32)
.collect();
let a = make(1, 16, 44100, vec![ch]);
assert_round_trips(&a, &format!("len {len}"));
}
}
#[test]
fn sample_rate_boundaries() {
for rate in [1u32, 8000, 44100, 192_000, 655_350, 1_048_575] {
let ch: Vec<i32> = (0..2000).map(|n| (n % 1000) - 500).collect();
let a = make(1, 16, rate, vec![ch]);
assert_round_trips(&a, &format!("rate {rate}"));
}
}
#[test]
fn thirty_two_bit_full_scale_both_containers() {
let ch: Vec<i32> = (0..5000)
.map(|n| if n % 2 == 0 { i32::MIN } else { i32::MAX })
.collect();
let a = make(2, 32, 48000, vec![ch.clone(), ch]);
assert_round_trips(&a, "32-bit full scale");
}
#[test]
fn single_sample_each_depth_and_container() {
for bps in [4u8, 8, 12, 16, 20, 24, 32] {
let (lo, hi) = range(bps);
for v in [lo as i32, 0, hi as i32] {
let a = make(1, bps, 44100, vec![vec![v]]);
assert_round_trips(&a, &format!("single {bps}-bit value {v}"));
}
}
}
#[test]
fn encoder_rejects_invalid_inputs() {
let good = || make(2, 16, 44100, vec![vec![1, 2, 3], vec![4, 5, 6]]);
let cases: Vec<(&str, FlacAudio)> = vec![
("zero channels", make(0, 16, 44100, vec![])),
("nine channels", make(9, 16, 44100, vec![vec![0]; 9])),
("bps 3", make(1, 3, 44100, vec![vec![0]])),
("bps 33", make(1, 33, 44100, vec![vec![0]])),
("rate 0", make(1, 16, 0, vec![vec![0]])),
("rate too high", make(1, 16, 1 << 20, vec![vec![0]])),
("channel count mismatch", make(2, 16, 44100, vec![vec![0]])),
(
"ragged channels",
make(2, 16, 44100, vec![vec![1, 2, 3], vec![4, 5]]),
),
(
"sample over range",
make(1, 8, 44100, vec![vec![200]]), ),
("sample under range", make(1, 8, 44100, vec![vec![-200]])),
];
for (name, bad) in cases {
assert!(
matches!(encode(&bad), Err(FlacError::InvalidInput(_))),
"native encode should reject: {name}"
);
assert!(
matches!(encode_ogg(&bad), Err(FlacError::InvalidInput(_))),
"ogg encode should reject: {name}"
);
}
assert!(encode(&good()).is_ok());
assert!(encode_ogg(&good()).is_ok());
}
#[test]
fn not_flac_inputs_are_rejected_cleanly() {
for junk in [
&b""[..],
&b"x"[..],
&b"RIFFxxxxWAVE"[..],
&b"OggS"[..], &b"fLa"[..], &[0u8; 64][..], &[0xFFu8; 64][..], ] {
assert!(decode(junk).is_err(), "decode should reject junk");
assert!(info(junk).is_err(), "info should reject junk");
}
}
#[test]
fn cross_container_samples_identical() {
for bps in [8u8, 16, 24, 32] {
let (lo, hi) = range(bps);
let l: Vec<i32> = (0..7000)
.map(|n| (lo + (n as i64 * 97 % (hi - lo + 1))) as i32)
.collect();
let r: Vec<i32> = (0..7000)
.map(|n| (hi - (n as i64 * 53 % (hi - lo + 1))) as i32)
.collect();
let a = make(2, bps, 96000, vec![l, r]);
let from_native = decode(&encode(&a).unwrap()).unwrap();
let from_ogg = decode(&encode_ogg(&a).unwrap()).unwrap();
assert_eq!(
from_native.samples, from_ogg.samples,
"{bps}-bit cross-container"
);
assert_eq!(
info(&encode(&a).unwrap()).unwrap().md5,
info(&encode_ogg(&a).unwrap()).unwrap().md5,
"{bps}-bit md5 across containers"
);
}
}