use std::sync::OnceLock;
use crate::bitio::BitReader;
use crate::frame::{BlockType, GranuleSideInfo, GRANULE_LINES};
use crate::header::FrameHeader;
use crate::tables;
use super::codebooks::{PAIR_TABLES, QUAD_A, QUAD_B};
const LUT_BITS_CAP: u8 = 12;
#[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 {
pub const fn new(codes: &'static [u16], lens: &'static [u8]) -> HuffBook {
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
};
HuffBook {
codes,
lens,
max_len: max,
lut_bits,
lut: OnceLock::new(),
}
}
fn lut(&self) -> &[LutSlot] {
self.lut.get_or_init(|| {
let bits = self.lut_bits as u32;
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 || len as u32 > bits {
continue; }
let shift = bits - len as u32;
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(self.lut_bits as u32) as usize];
if slot.len != 0 {
r.skip(slot.len as u32);
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 && self.codes[i] as u32 == 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) {
Some(&c) => Some((c, self.lens[idx])),
None => None,
}
}
#[cfg(test)]
pub fn kraft_sum(&self) -> f64 {
self.lens.iter().map(|&l| 2f64.powi(-(l as i32))).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 (long as u32 >> (ll - ls)) == short as u32 {
return false; }
}
}
true
}
}
pub struct PairTable {
pub book: HuffBook,
pub dim: u8,
pub linbits: u8,
}
impl PairTable {
fn read(&self, r: &mut BitReader) -> (i32, i32) {
if self.dim == 0 {
return (0, 0);
}
let idx = match self.book.decode_index(r) {
Some(i) => i,
None => return (0, 0),
};
let mut x = (idx / self.dim as usize) as i32;
let mut y = (idx % self.dim as usize) as i32;
let maxc = self.dim as i32 - 1;
if self.linbits > 0 && x == maxc {
x += r.read(self.linbits as u32) as i32;
}
if x != 0 && r.read(1) == 1 {
x = -x;
}
if self.linbits > 0 && y == maxc {
y += r.read(self.linbits as u32) as i32;
}
if y != 0 && r.read(1) == 1 {
y = -y;
}
(x, y)
}
}
pub struct QuadTable {
pub book: HuffBook,
}
impl QuadTable {
fn read(&self, r: &mut BitReader) -> (i32, i32, i32, i32) {
let idx = self.book.decode_index(r).unwrap_or(0);
let mut q = [
((idx >> 3) & 1) as i32,
((idx >> 2) & 1) as i32,
((idx >> 1) & 1) as i32,
(idx & 1) as i32,
];
for v in q.iter_mut() {
if *v != 0 && r.read(1) == 1 {
*v = -*v;
}
}
(q[0], q[1], q[2], q[3])
}
}
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 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
};
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;
}
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));
}
#[test]
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(code as u32, len as u32);
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.iter() {
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
);
}
}