#![warn(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
use std::sync::OnceLock;
use crate::bitio::BitReader;
use crate::frame::{BlockType, GranuleSideInfo, GRANULE_LINES};
use crate::header::FrameHeader;
use crate::tables;
use super::scalefactors::ScaleFactors;
use super::codebooks::{PAIR_TABLES, QUAD_A, QUAD_B};
const LUT_BITS_CAP: u8 = 12;
#[allow(clippy::cast_possible_truncation)] const DIV_TAB: [[u8; 256]; 17] = {
let mut t = [[0u8; 256]; 17];
let mut d = 1;
while d < 17 {
let mut i = 0;
while i < 256 {
t[d][i] = (i / d) as u8;
i += 1;
}
d += 1;
}
t
};
#[derive(Clone, Copy)]
struct LutSlot {
sym: u16,
len: u8,
}
pub struct HuffBook {
codes: &'static [u16],
lens: &'static [u8],
max_len: u8,
lut_bits: u8,
lut: OnceLock<Vec<LutSlot>>,
}
impl HuffBook {
#[must_use]
pub const fn new(codes: &'static [u16], lens: &'static [u8]) -> Self {
let mut max = 0u8;
let mut i = 0;
while i < lens.len() {
if lens[i] > max {
max = lens[i];
}
i += 1;
}
let lut_bits = if max < LUT_BITS_CAP {
max
} else {
LUT_BITS_CAP
};
Self {
codes,
lens,
max_len: max,
lut_bits,
lut: OnceLock::new(),
}
}
#[allow(clippy::cast_possible_truncation)]
fn lut(&self) -> &[LutSlot] {
self.lut.get_or_init(|| {
let bits = u32::from(self.lut_bits);
let mut t = vec![LutSlot { sym: 0, len: 0 }; 1usize << bits];
for (i, (&code, &len)) in self.codes.iter().zip(self.lens.iter()).enumerate() {
if len == 0 || u32::from(len) > bits {
continue; }
let shift = bits - u32::from(len);
let start = (code as usize) << shift;
for slot in &mut t[start..start + (1usize << shift)] {
*slot = LutSlot { sym: i as u16, len };
}
}
t
})
}
pub fn decode_index(&self, r: &mut BitReader) -> Option<usize> {
if self.codes.is_empty() {
return Some(0); }
let slot = self.lut()[r.peek(u32::from(self.lut_bits)) as usize];
if slot.len != 0 {
r.skip(u32::from(slot.len));
return Some(slot.sym as usize);
}
self.decode_linear(r)
}
fn decode_linear(&self, r: &mut BitReader) -> Option<usize> {
let mut code = 0u32;
for len in 1..=self.max_len {
code = (code << 1) | r.read(1);
for i in 0..self.codes.len() {
if self.lens[i] == len && u32::from(self.codes[i]) == code {
return Some(i);
}
}
}
None
}
pub fn code_len(&self, idx: usize) -> Option<(u16, u8)> {
if self.codes.is_empty() {
return if idx == 0 { Some((0, 0)) } else { None };
}
match (self.codes.get(idx), self.lens.get(idx)) {
(Some(&c), Some(&l)) => Some((c, l)),
_ => None,
}
}
#[cfg(test)]
pub fn kraft_sum(&self) -> f64 {
self.lens.iter().map(|&l| 2f64.powi(-i32::from(l))).sum()
}
#[cfg(test)]
pub fn is_prefix_free(&self) -> bool {
for a in 0..self.codes.len() {
for b in (a + 1)..self.codes.len() {
let (la, lb) = (self.lens[a], self.lens[b]);
let (short, long, ls, ll) = if la <= lb {
(self.codes[a], self.codes[b], la, lb)
} else {
(self.codes[b], self.codes[a], lb, la)
};
if (u32::from(long) >> (ll - ls)) == u32::from(short) {
return false; }
}
}
true
}
}
pub struct PairTable {
pub book: HuffBook,
pub dim: u8,
pub linbits: u8,
}
impl PairTable {
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn read(&self, r: &mut BitReader) -> (i32, i32) {
if self.dim == 0 {
return (0, 0);
}
let Some(idx) = self.book.decode_index(r) else {
return (0, 0);
};
let d = self.dim as usize;
let mut x = i32::from(DIV_TAB[d.min(16)][idx & 0xFF]);
let mut y = (idx - x as usize * d) as i32;
let maxc = i32::from(self.dim) - 1;
if self.linbits > 0 && x == maxc {
x += r.read(u32::from(self.linbits)) as i32;
}
if x != 0 && r.read(1) == 1 {
x = -x;
}
if self.linbits > 0 && y == maxc {
y += r.read(u32::from(self.linbits)) as i32;
}
if y != 0 && r.read(1) == 1 {
y = -y;
}
(x, y)
}
}
pub struct QuadTable {
pub book: HuffBook,
}
impl QuadTable {
#[cfg(test)]
fn read(&self, r: &mut BitReader) -> (i32, i32, i32, i32) {
let idx = self.book.decode_index(r).unwrap_or(0);
let mut q = [
i32::from(idx & 8 != 0),
i32::from(idx & 4 != 0),
i32::from(idx & 2 != 0),
i32::from(idx & 1 != 0),
];
for v in &mut q {
if *v != 0 && r.read(1) == 1 {
*v = -*v;
}
}
q.into()
}
}
fn region_bounds(
gi: &GranuleSideInfo,
sfb_long: &[u16; 23],
bv2: usize,
sample_rate: u32,
) -> (usize, usize) {
if gi.window_switching && gi.block_type != BlockType::Long {
let r0 = if gi.block_type == BlockType::Short && !gi.mixed_block {
3 * tables::sfb_short_offsets(sample_rate)[3] as usize
} else {
sfb_long[8] as usize };
(r0.min(bv2), bv2)
} else {
let i1 = (gi.region0_count as usize + 1).min(22);
let i2 = (gi.region0_count as usize + gi.region1_count as usize + 2).min(22);
let r1 = (sfb_long[i1] as usize).min(bv2);
let r2 = (sfb_long[i2] as usize).min(bv2).max(r1);
(r1, r2)
}
}
pub fn decode(
main: &[u8],
bit_pos: &mut usize,
part2_3_end: usize,
header: &FrameHeader,
gi: &GranuleSideInfo,
) -> ([i32; GRANULE_LINES], usize) {
let mut out = [0i32; GRANULE_LINES];
let nz = decode_into(
main,
bit_pos,
part2_3_end,
header,
gi,
&mut IntSink(&mut out),
);
(out, nz)
}
pub fn decode_dequant_long(
main: &[u8],
bit_pos: &mut usize,
part2_3_end: usize,
header: &FrameHeader,
gi: &GranuleSideInfo,
sf: &ScaleFactors,
out: &mut [f32; GRANULE_LINES],
) -> usize {
out.fill(0.0);
let pow2q = super::requantize::pow2_quarter_table();
let gain = i32::from(gi.global_gain) - 210;
let mut scales = [0f64; 22];
for (b, s) in scales.iter_mut().enumerate() {
let pre = i32::from(if gi.preflag { tables::PRETAB[b] } else { 0 });
*s = super::requantize::band_scale(
pow2q,
gain,
gi.scalefac_scale,
i32::from(sf.long[b]) + pre,
);
}
let off = tables::sfb_long_offsets(header.sample_rate);
let mut sink = DeqSink {
out,
pow: super::requantize::pow43_table(),
scales,
off,
band: 0,
band_end: off[1] as usize,
};
decode_into(main, bit_pos, part2_3_end, header, gi, &mut sink)
}
trait LineSink {
fn put(&mut self, i: usize, v: i32);
fn clear4(&mut self, i: usize);
}
struct IntSink<'a>(&'a mut [i32; GRANULE_LINES]);
impl LineSink for IntSink<'_> {
#[inline]
fn put(&mut self, i: usize, v: i32) {
self.0[i] = v;
}
fn clear4(&mut self, i: usize) {
self.0[i..i + 4].fill(0);
}
}
struct DeqSink<'a> {
out: &'a mut [f32; GRANULE_LINES],
pow: &'static [f64],
scales: [f64; 22],
off: &'static [u16; 23],
band: usize,
band_end: usize,
}
impl LineSink for DeqSink<'_> {
#[inline]
fn put(&mut self, i: usize, v: i32) {
while i >= self.band_end && self.band < 21 {
self.band += 1;
self.band_end = self.off[self.band + 1] as usize;
}
self.out[i] = super::requantize::dequant(v, self.pow, self.scales[self.band]);
}
fn clear4(&mut self, i: usize) {
self.out[i..i + 4].fill(0.0);
}
}
#[inline]
fn decode_into<S: LineSink>(
main: &[u8],
bit_pos: &mut usize,
part2_3_end: usize,
header: &FrameHeader,
gi: &GranuleSideInfo,
out: &mut S,
) -> usize {
let mut r = BitCache::new(main, *bit_pos);
let sfb_long = tables::sfb_long_offsets(header.sample_rate);
let bv2 = (gi.big_values as usize * 2).min(GRANULE_LINES);
let (r1, r2) = region_bounds(gi, sfb_long, bv2, header.sample_rate);
let fast = fast_tables();
let mut i = 0;
for (end, sel) in [(r1, 0), (r2, 1), (bv2, 2)] {
let t = (gi.table_select[sel] as usize).min(PAIR_TABLES.len() - 1);
decode_pairs(
&mut r,
&fast.pairs[t],
PAIR_TABLES[t],
out,
&mut i,
end,
part2_3_end,
);
}
let quad = if gi.count1table_select {
&fast.quad_b
} else {
&fast.quad_a
};
let count1_start = i;
while i + 3 < GRANULE_LINES && r.pos < part2_3_end {
if r.avail < QUAD_BITS {
r.refill();
}
let e = quad[r.peek(QUAD_BITS) as usize & ((1 << QUAD_BITS) - 1)];
r.consume(u32::from(e & 0xF));
let e = i32::from(e);
out.put(i, (e << 26) >> 30);
out.put(i + 1, (e << 24) >> 30);
out.put(i + 2, (e << 22) >> 30);
out.put(i + 3, (e << 20) >> 30);
i += 4;
}
if i > count1_start && i + 3 < GRANULE_LINES && r.pos > part2_3_end {
i -= 4;
out.clear4(i);
}
let nonzero = i.min(GRANULE_LINES);
*bit_pos = part2_3_end;
nonzero
}
const E_CODE: u16 = 1 << 15;
const PAIR_BITS_CAP: u32 = 11;
const QUAD_BITS: u32 = 10;
const PAIR_MAX_BITS: u32 = PAIR_BITS_CAP + 2 * 13 + 2;
struct FastPair {
lut: Box<[u16]>,
bits: u32,
}
struct FastTables {
pairs: Vec<FastPair>,
quad_a: Box<[u16]>,
quad_b: Box<[u16]>,
}
fn fast_tables() -> &'static FastTables {
static T: OnceLock<FastTables> = OnceLock::new();
T.get_or_init(|| FastTables {
pairs: PAIR_TABLES.iter().map(|t| build_pair(t)).collect(),
quad_a: build_quad(&QUAD_A),
quad_b: build_quad(&QUAD_B),
})
}
fn fill(lut: &mut [u16], bits: u32, prefix: u32, len: u32, e: u16) {
let shift = bits - len;
let start = (prefix as usize) << shift;
lut[start..start + (1usize << shift)].fill(e);
}
fn apply_signs<const N: usize>(mags: [i32; N], signs: u32, s: u32) -> [i32; N] {
let mut out = mags;
let mut k = s;
for v in &mut out {
if *v != 0 {
k -= 1;
if (signs >> k) & 1 == 1 {
*v = -*v;
}
}
}
out
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn build_pair(t: &PairTable) -> FastPair {
if t.dim == 0 || t.book.codes.is_empty() {
return FastPair {
lut: Box::new([]),
bits: 0,
};
}
let bits = (u32::from(t.book.max_len) + 2).min(PAIR_BITS_CAP);
let mut lut = vec![0u16; 1 << bits];
let d = usize::from(t.dim);
let maxc = i32::from(t.dim) - 1;
for (idx, (&code, &len)) in t.book.codes.iter().zip(t.book.lens.iter()).enumerate() {
let len = u32::from(len);
if len == 0 || len > bits {
continue; }
let (x, y) = ((idx / d) as i32, (idx % d) as i32);
let esc = t.linbits > 0 && (x == maxc || y == maxc);
let s = u32::from(x != 0) + u32::from(y != 0);
if !esc && len + s <= bits {
for sb in 0..(1u32 << s) {
let [vx, vy] = apply_signs([x, y], sb, s);
let e = (len + s) as u16 | ((vx as u16 & 31) << 5) | ((vy as u16 & 31) << 10);
fill(&mut lut, bits, (u32::from(code) << s) | sb, len + s, e);
}
} else {
let e = E_CODE | len as u16 | ((x as u16) << 5) | ((y as u16) << 10);
fill(&mut lut, bits, u32::from(code), len, e);
}
}
FastPair {
lut: lut.into_boxed_slice(),
bits,
}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn build_quad(q: &QuadTable) -> Box<[u16]> {
let mut lut = vec![0u16; 1 << QUAD_BITS];
for (idx, (&code, &len)) in q.book.codes.iter().zip(q.book.lens.iter()).enumerate() {
let len = u32::from(len);
let mags = [
i32::from(idx & 8 != 0),
i32::from(idx & 4 != 0),
i32::from(idx & 2 != 0),
i32::from(idx & 1 != 0),
];
let s = (idx as u32).count_ones();
for sb in 0..(1u32 << s) {
let v = apply_signs(mags, sb, s);
let mut e = (len + s) as u16;
for (k, &vk) in v.iter().enumerate() {
e |= (vk as u16 & 3) << (4 + 2 * k);
}
fill(&mut lut, QUAD_BITS, (u32::from(code) << s) | sb, len + s, e);
}
}
lut.into_boxed_slice()
}
#[allow(clippy::cast_possible_wrap)]
#[inline]
fn decode_pairs<S: LineSink>(
r: &mut BitCache,
fast: &FastPair,
table: &PairTable,
out: &mut S,
i: &mut usize,
end: usize,
part2_3_end: usize,
) {
if fast.bits == 0 {
if *i < end && r.pos < part2_3_end {
*i = end;
}
return;
}
let lut = &*fast.lut;
let mask = lut.len() - 1;
let linbits = u32::from(table.linbits);
let maxc = i32::from(table.dim) - 1;
while *i < end && r.pos < part2_3_end {
if r.avail < PAIR_MAX_BITS {
r.refill();
}
let e = lut[r.peek(fast.bits) as usize & mask];
let len = u32::from(e & 31);
let (x, y) = if e & E_CODE == 0 && len != 0 {
r.consume(len);
let e = i32::from(e);
((e << 22) >> 27, (e << 17) >> 27)
} else if len != 0 {
r.consume(len);
let mut x = i32::from((e >> 5) & 31);
let mut y = i32::from((e >> 10) & 31);
if linbits > 0 && x == maxc {
x += r.take(linbits) as i32;
}
if x != 0 && r.take(1) == 1 {
x = -x;
}
if linbits > 0 && y == maxc {
y += r.take(linbits) as i32;
}
if y != 0 && r.take(1) == 1 {
y = -y;
}
(x, y)
} else {
let mut br = BitReader::new(r.data);
br.seek_bits(r.pos);
let xy = table.read(&mut br);
r.seek(br.bit_pos());
xy
};
out.put(*i, x);
out.put(*i + 1, y);
*i += 2;
}
}
struct BitCache<'a> {
data: &'a [u8],
pos: usize,
cache: u64,
avail: u32,
}
impl<'a> BitCache<'a> {
fn new(data: &'a [u8], pos: usize) -> Self {
let mut c = Self {
data,
pos,
cache: 0,
avail: 0,
};
c.refill();
c
}
#[inline]
fn refill(&mut self) {
let b = self.pos >> 3;
let word = if let Some(w) = self.data.get(b..b + 8) {
u64::from_be_bytes(w.try_into().unwrap_or([0; 8]))
} else {
let start = b.min(self.data.len());
let take = (self.data.len() - start).min(8);
let mut buf = [0u8; 8];
buf[..take].copy_from_slice(&self.data[start..start + take]);
u64::from_be_bytes(buf)
};
#[allow(clippy::cast_possible_truncation)] let off = (self.pos & 7) as u32;
self.cache = word << off;
self.avail = 64 - off;
}
fn seek(&mut self, pos: usize) {
self.pos = pos;
self.refill();
}
#[inline]
fn peek(&self, n: u32) -> u32 {
#[allow(clippy::cast_possible_truncation)] let v = (self.cache >> (64 - n)) as u32;
v
}
#[inline]
fn consume(&mut self, n: u32) {
self.cache <<= n;
self.avail -= n;
self.pos += n as usize;
}
#[inline]
fn take(&mut self, n: u32) -> u32 {
let v = self.peek(n);
self.consume(n);
v
}
}
#[cfg(test)]
pub fn decode_reference(
main: &[u8],
bit_pos: &mut usize,
part2_3_end: usize,
header: &FrameHeader,
gi: &GranuleSideInfo,
) -> ([i32; GRANULE_LINES], usize) {
let mut r = BitReader::new(main);
r.seek_bits(*bit_pos);
let mut out = [0i32; GRANULE_LINES];
let sfb_long = tables::sfb_long_offsets(header.sample_rate);
let bv2 = (gi.big_values as usize * 2).min(GRANULE_LINES);
let (r1, r2) = region_bounds(gi, sfb_long, bv2, header.sample_rate);
let mut i = 0;
while i + 1 < bv2 && r.bit_pos() < part2_3_end {
let t = if i < r1 {
gi.table_select[0]
} else if i < r2 {
gi.table_select[1]
} else {
gi.table_select[2]
} as usize;
let (x, y) = PAIR_TABLES[t.min(PAIR_TABLES.len() - 1)].read(&mut r);
out[i] = x;
out[i + 1] = y;
i += 2;
}
let quad = if gi.count1table_select {
&QUAD_B
} else {
&QUAD_A
};
let count1_start = i;
while i + 3 < GRANULE_LINES && r.bit_pos() < part2_3_end {
let (v, w, x, y) = quad.read(&mut r);
out[i] = v;
out[i + 1] = w;
out[i + 2] = x;
out[i + 3] = y;
i += 4;
}
if i > count1_start && i + 3 < GRANULE_LINES && r.bit_pos() > part2_3_end {
i -= 4;
out[i..i + 4].fill(0);
}
let nonzero = i.min(GRANULE_LINES);
*bit_pos = part2_3_end;
(out, nonzero)
}
#[cfg(test)]
mod tests {
use super::*;
fn pack(tokens: &[(u32, u32)]) -> Vec<u8> {
let mut w = crate::bitio::BitWriter::new();
for &(v, n) in tokens {
w.write(v, n);
}
w.finish()
}
#[test]
fn all_codebooks_complete_and_prefix_free() {
for (n, t) in PAIR_TABLES.iter().enumerate() {
if t.dim == 0 {
continue; }
let k = t.book.kraft_sum();
assert!((k - 1.0).abs() < 1e-9, "pair table {n}: Kraft = {k}");
assert!(t.book.is_prefix_free(), "pair table {n} not prefix-free");
}
for (name, q) in [("A", &QUAD_A), ("B", &QUAD_B)] {
let k = q.book.kraft_sum();
assert!((k - 1.0).abs() < 1e-9, "count1 table {name}: Kraft = {k}");
assert!(
q.book.is_prefix_free(),
"count1 table {name} not prefix-free"
);
}
}
#[test]
fn pair_decode_with_signs() {
let bits = pack(&[(0b000, 3), (1, 1), (0, 1)]);
let mut r = BitReader::new(&bits);
assert_eq!(PAIR_TABLES[1].read(&mut r), (-1, 1));
let bits = pack(&[(0b1, 1)]);
let mut r = BitReader::new(&bits);
assert_eq!(PAIR_TABLES[1].read(&mut r), (0, 0));
}
#[test]
fn pair_decode_with_linbits() {
static TL: PairTable = PairTable {
book: HuffBook::new(&[0b1, 0b001, 0b01, 0b000], &[1, 3, 2, 3]),
dim: 2,
linbits: 4,
};
let bits = pack(&[(0b000, 3), (0b0100, 4), (0, 1), (0b0001, 4), (1, 1)]);
let mut r = BitReader::new(&bits);
assert_eq!(TL.read(&mut r), (5, -2));
}
#[test]
fn quad_decode_bits_and_signs() {
let bits = pack(&[(0b0101, 4), (1, 1), (0, 1)]);
let mut r = BitReader::new(&bits);
assert_eq!(QUAD_B.read(&mut r), (-1, 0, 1, 0));
}
fn mono_44k() -> FrameHeader {
FrameHeader {
version: crate::header::MpegVersion::V1,
crc_protected: false,
bitrate_kbps: 128,
sample_rate: 44100,
padding: false,
channel_mode: crate::frame::ChannelMode::Mono,
copyright: false,
original: false,
emphasis: 0,
}
}
const ZERO_QUAD: (u32, u32) = (0b1111, 4);
#[test]
fn count1_quad_straddling_the_budget_is_dropped() {
let bits = pack(&[ZERO_QUAD, (0b0111, 4), (1, 1), ZERO_QUAD, ZERO_QUAD]);
let gi = GranuleSideInfo {
count1table_select: true,
..Default::default()
};
let mut pos = 0;
let (out, nz) = decode(&bits, &mut pos, 6, &mono_44k(), &gi);
assert_eq!(out[4], 0, "the straddling quad must not be decoded");
assert_eq!(nz, 4);
assert_eq!(pos, 6, "the cursor still ends at the granule's budget");
}
#[test]
fn count1_quad_reaching_line_576_is_kept_even_if_it_overruns() {
let mut toks = vec![ZERO_QUAD; 143];
toks.extend([(0b0111, 4), (0, 1)]);
let bits = pack(&toks);
let gi = GranuleSideInfo {
count1table_select: true,
..Default::default()
};
let mut pos = 0;
let (out, nz) = decode(&bits, &mut pos, 574, &mono_44k(), &gi);
assert_eq!(out[572], 1, "a quad that completes the spectrum is kept");
assert_eq!(nz, 576);
}
#[test]
#[cfg_attr(
miri,
ignore = "end-to-end / exhaustive: too slow to interpret under Miri (> 5 min)"
)]
fn lut_matches_linear_for_every_codeword() {
fn check(book: &HuffBook) {
for (i, &len) in book.lens.iter().enumerate() {
if len == 0 {
continue;
}
let code = book.codes[i];
for &filler in &[0u32, 0xFFFF_FFFF, 0xAAAA_AAAA, 0x5555_5555] {
let mut w = crate::bitio::BitWriter::new();
w.write(u32::from(code), u32::from(len));
w.write(filler, 24); let bits = w.finish();
let mut rl = BitReader::new(&bits);
let sym_lin = book.decode_linear(&mut rl);
let mut ru = BitReader::new(&bits);
let sym_lut = book.decode_index(&mut ru);
assert_eq!(sym_lin, Some(i), "linear must decode its own codeword");
assert_eq!(sym_lut, sym_lin, "LUT vs linear symbol (book idx {i})");
assert_eq!(
ru.bit_pos(),
rl.bit_pos(),
"LUT vs linear bits consumed (book idx {i})"
);
assert_eq!(rl.bit_pos(), len as usize, "consumed == codeword length");
}
}
}
for t in &PAIR_TABLES {
if t.dim != 0 {
check(&t.book);
}
}
check(&QUAD_A.book);
check(&QUAD_B.book);
}
#[test]
fn region_bounds_long_and_short() {
use crate::frame::GranuleSideInfo;
let sfb = tables::sfb_long_offsets(44100);
let mut gi = GranuleSideInfo {
big_values: 100,
region0_count: 7,
region1_count: 2,
..Default::default()
};
assert_eq!(region_bounds(&gi, sfb, 200, 44100), (36, 62));
gi.window_switching = true;
gi.block_type = BlockType::Short;
assert_eq!(region_bounds(&gi, sfb, 200, 44100), (36, 200));
gi.block_type = BlockType::Start;
assert_eq!(region_bounds(&gi, sfb, 200, 44100), (36, 200));
assert_eq!(
region_bounds(&gi, tables::sfb_long_offsets(22050), 200, 22050).0,
tables::sfb_long_offsets(22050)[8] as usize
);
}
#[test]
#[cfg_attr(miri, ignore = "randomised sweep: too slow to interpret under Miri")]
#[allow(clippy::cast_possible_truncation)]
fn fused_dequant_matches_two_pass() {
let mut s = 0xD1B5_4A32_D192_ED03u64;
let mut rnd = move |n: u64| {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s % n
};
let rates = [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000];
let blocks = [BlockType::Long, BlockType::Start, BlockType::Stop];
let mut hdr = mono_44k();
let mut coded = 0usize;
for trial in 0..20_000u32 {
hdr.sample_rate = rates[rnd(rates.len() as u64) as usize];
let bt = blocks[rnd(3) as usize];
let gi = GranuleSideInfo {
big_values: rnd(320) as u16,
global_gain: rnd(256) as u8,
table_select: [rnd(32) as u8, rnd(32) as u8, rnd(32) as u8],
region0_count: rnd(16) as u8,
region1_count: rnd(8) as u8,
count1table_select: rnd(2) == 1,
window_switching: bt != BlockType::Long,
block_type: bt,
mixed_block: bt != BlockType::Long && rnd(2) == 1,
preflag: rnd(2) == 1,
scalefac_scale: rnd(2) == 1,
..Default::default()
};
let mut sf = ScaleFactors::default();
for v in &mut sf.long {
*v = rnd(16) as u8;
}
let len = rnd(520) as usize;
let main: Vec<u8> = (0..len)
.map(|_| match trial % 4 {
0 => (rnd(256) & rnd(256) & rnd(256)) as u8,
1 => 0xFF,
_ => rnd(256) as u8,
})
.collect();
let start = rnd(80) as usize;
let end = start + rnd(4200) as usize;
let (mut pa, mut pb) = (start, start);
let mut got = [1f32; GRANULE_LINES];
let na = decode_dequant_long(&main, &mut pa, end, &hdr, &gi, &sf, &mut got);
let (coeffs, nb) = decode(&main, &mut pb, end, &hdr, &gi);
let mut want = [0f32; GRANULE_LINES];
crate::decode::requantize::apply(&hdr, &gi, &sf, &coeffs, nb, &mut want);
assert_eq!(na, nb, "rzero count, trial {trial}");
assert_eq!(pa, pb, "cursor, trial {trial}");
for i in 0..GRANULE_LINES {
assert_eq!(
got[i].to_bits(),
want[i].to_bits(),
"line {i}, trial {trial}"
);
}
coded += na;
}
assert!(coded > 500_000, "{coded} coded lines");
}
#[test]
#[cfg_attr(miri, ignore = "randomised sweep: too slow to interpret under Miri")]
#[allow(clippy::cast_possible_truncation)]
fn fast_decode_matches_reference() {
let mut s = 0x9E37_79B9_7F4A_7C15u64;
let mut rnd = move |n: u64| {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s % n
};
let rates = [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000];
let blocks = [
BlockType::Long,
BlockType::Start,
BlockType::Short,
BlockType::Stop,
];
let mut hdr = mono_44k();
let (mut coded, mut long_codes) = (0usize, 0usize);
for trial in 0..40_000u32 {
hdr.sample_rate = rates[rnd(rates.len() as u64) as usize];
let ws = rnd(3) == 0;
let gi = GranuleSideInfo {
big_values: rnd(320) as u16,
table_select: [rnd(32) as u8, rnd(32) as u8, rnd(32) as u8],
region0_count: rnd(16) as u8,
region1_count: rnd(8) as u8,
count1table_select: rnd(2) == 1,
window_switching: ws,
block_type: if ws {
blocks[rnd(4) as usize]
} else {
BlockType::Long
},
mixed_block: ws && rnd(2) == 1,
..Default::default()
};
let len = rnd(520) as usize;
let mode = trial % 5;
let main: Vec<u8> = (0..len)
.map(|_| match mode {
0 => 0,
1 => 0xFF,
2 => (rnd(256) & rnd(256) & rnd(256)) as u8,
_ => rnd(256) as u8,
})
.collect();
let start = rnd(80) as usize;
let end = start + rnd(4200) as usize;
let (mut pa, mut pb) = (start, start);
let (a, na) = decode(&main, &mut pa, end, &hdr, &gi);
let (b, nb) = decode_reference(&main, &mut pb, end, &hdr, &gi);
assert_eq!(na, nb, "rzero count, trial {trial}");
assert_eq!(pa, pb, "cursor, trial {trial}");
assert!(a == b, "coefficients, trial {trial}");
coded += na;
long_codes += a.iter().filter(|&&c| c.unsigned_abs() > 15).count();
}
assert!(coded > 1_000_000, "{coded} coded lines");
assert!(long_codes > 10_000, "{long_codes} escaped values");
}
}