use crate::error::Error;
use crate::quantum::bits::BitReader;
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct ModelSym {
pub sym: u16,
pub cumfreq: u16,
}
#[derive(Debug, Clone)]
pub(crate) struct Model {
pub shiftsleft: i32,
pub entries: usize,
pub syms: [ModelSym; 65],
}
impl Model {
pub(crate) fn new(start: u16, len: usize) -> Self {
assert!(len <= 64, "Quantum model size out of range");
let mut syms = [ModelSym::default(); 65];
for (i, slot) in syms.iter_mut().take(len + 1).enumerate() {
slot.sym = start + i as u16;
slot.cumfreq = (len - i) as u16;
}
Self {
shiftsleft: 4,
entries: len,
syms,
}
}
fn update(&mut self) {
self.shiftsleft -= 1;
if self.shiftsleft != 0 {
for i in (0..self.entries).rev() {
self.syms[i].cumfreq >>= 1;
if self.syms[i].cumfreq <= self.syms[i + 1].cumfreq {
self.syms[i].cumfreq = self.syms[i + 1].cumfreq + 1;
}
}
} else {
self.shiftsleft = 50;
for i in 0..self.entries {
self.syms[i].cumfreq -= self.syms[i + 1].cumfreq;
self.syms[i].cumfreq += 1;
self.syms[i].cumfreq >>= 1;
}
for i in 0..self.entries.saturating_sub(1) {
for j in (i + 1)..self.entries {
if self.syms[i].cumfreq < self.syms[j].cumfreq {
self.syms.swap(i, j);
}
}
}
for i in (0..self.entries).rev() {
self.syms[i].cumfreq += self.syms[i + 1].cumfreq;
}
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct ArithDecoder {
pub h: u16,
pub l: u16,
pub c: u16,
}
impl ArithDecoder {
pub(crate) const fn new() -> Self {
Self { h: 0, l: 0, c: 0 }
}
pub(crate) fn init_frame(&mut self, br: &mut BitReader, buf: &[u8]) -> Result<(), Error> {
self.h = 0xFFFF;
self.l = 0;
self.c = br.read_bits(16, buf)? as u16;
Ok(())
}
pub(crate) fn get_symbol(
&mut self,
model: &mut Model,
br: &mut BitReader,
buf: &[u8],
) -> Result<u16, Error> {
let h = self.h as u32;
let l = self.l as u32;
let c = self.c as u32;
let range = ((h.wrapping_sub(l)) & 0xFFFF) + 1;
let cumfreq0 = model.syms[0].cumfreq as u32;
if cumfreq0 == 0 {
return Err(Error::Corrupt);
}
let symf = ((c.wrapping_sub(l).wrapping_add(1).wrapping_mul(cumfreq0)).wrapping_sub(1)
/ range)
& 0xFFFF;
let mut i: usize = 1;
while i < model.entries {
if (model.syms[i].cumfreq as u32) <= symf {
break;
}
i += 1;
}
let sym = model.syms[i - 1].sym;
let range2 = h.wrapping_sub(l) + 1;
let total = model.syms[0].cumfreq as u32;
let cf_lo = model.syms[i - 1].cumfreq as u32;
let cf_hi = model.syms[i].cumfreq as u32;
let new_h = (l + (cf_lo.wrapping_mul(range2) / total)).wrapping_sub(1) & 0xFFFF;
let new_l = (l + (cf_hi.wrapping_mul(range2) / total)) & 0xFFFF;
let mut h = new_h;
let mut l = new_l;
let mut k = i;
loop {
k -= 1;
model.syms[k].cumfreq = model.syms[k].cumfreq.wrapping_add(8);
if k == 0 {
break;
}
}
if model.syms[0].cumfreq > 3800 {
model.update();
}
let mut c = c & 0xFFFF;
loop {
if (l & 0x8000) != (h & 0x8000) {
if (l & 0x4000) != 0 && (h & 0x4000) == 0 {
c ^= 0x4000;
l &= 0x3FFF;
h |= 0x4000;
} else {
break;
}
}
l = (l << 1) & 0xFFFF;
h = ((h << 1) | 1) & 0xFFFF;
br.ensure_bits(1, buf)?;
let bit = br.peek_bits(1);
br.remove_bits(1);
c = ((c << 1) | bit) & 0xFFFF;
}
self.h = h as u16;
self.l = l as u16;
self.c = c as u16;
Ok(sym)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn model_initial_cumfreq_table() {
let m = Model::new(0, 4);
assert_eq!(m.syms[0].sym, 0);
assert_eq!(m.syms[0].cumfreq, 4);
assert_eq!(m.syms[1].sym, 1);
assert_eq!(m.syms[1].cumfreq, 3);
assert_eq!(m.syms[4].sym, 4);
assert_eq!(m.syms[4].cumfreq, 0);
assert_eq!(m.entries, 4);
assert_eq!(m.shiftsleft, 4);
}
#[test]
fn model_update_minor_pass_keeps_monotone() {
let mut m = Model::new(10, 5);
m.syms[0].cumfreq = 100;
m.syms[1].cumfreq = 80;
m.syms[2].cumfreq = 60;
m.syms[3].cumfreq = 40;
m.syms[4].cumfreq = 20;
m.syms[5].cumfreq = 0;
m.shiftsleft = 4;
m.update();
for i in 0..m.entries {
assert!(
m.syms[i].cumfreq > m.syms[i + 1].cumfreq,
"non-monotone at {i}: {} {}",
m.syms[i].cumfreq,
m.syms[i + 1].cumfreq
);
}
}
#[test]
fn model_update_major_pass_sorts_descending() {
let mut m = Model::new(0, 4);
m.shiftsleft = 1;
m.syms[0].cumfreq = 11;
m.syms[1].cumfreq = 10;
m.syms[2].cumfreq = 5;
m.syms[3].cumfreq = 3;
m.syms[4].cumfreq = 0;
m.update();
assert_eq!(m.syms[0].sym, 1);
assert_eq!(m.syms[1].sym, 3);
assert_eq!(m.syms[4].cumfreq, 0);
assert_eq!(m.shiftsleft, 50);
}
}