use core::f32::consts::PI;
use crate::mbe_params::{L_MAX, MbeParams, MbeParamsError};
use super::priority::deprioritize;
include!(concat!(env!("OUT_DIR"), "/annex_e_gain.rs"));
#[derive(Clone, Copy, Debug)]
pub(crate) struct GainAlloc {
pub b_m: u8,
pub delta_m: f32,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HocAlloc {
pub c_i: u8,
pub c_k: u8,
pub b_m: u8,
pub b_m_bits: u8,
}
include!(concat!(env!("OUT_DIR"), "/annex_f_gain_alloc.rs"));
include!(concat!(env!("OUT_DIR"), "/annex_g_hoc_alloc.rs"));
include!(concat!(env!("OUT_DIR"), "/annex_j_blocks.rs"));
pub const PITCH_INDEX_MAX: u8 = 207;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PitchInfo {
pub omega_0: f32,
pub l: u8,
pub k: u8,
}
pub fn pitch_decode(b0: u8) -> Option<PitchInfo> {
if b0 > PITCH_INDEX_MAX {
return None;
}
let omega_0 = 4.0 * PI / (f32::from(b0) + 39.5);
let l = MbeParams::harmonic_count_for(omega_0);
let k = vuv_band_count(l);
Some(PitchInfo { omega_0, l, k })
}
#[inline]
pub const fn vuv_band_count(l: u8) -> u8 {
if l <= 36 { (l + 2) / 3 } else { 12 }
}
#[inline]
pub const fn band_for_harmonic(l: u8, k: u8) -> u8 {
let raw = if l <= 36 { (l + 2) / 3 } else { 12 };
if raw > k { k } else { raw }
}
pub const GAIN_LEVELS: usize = 64;
#[inline]
pub fn decode_gain(b2: u8) -> f32 {
debug_assert!(b2 < 64, "b̂₂ is a 6-bit value");
IMBE_GAIN_LEVELS[b2 as usize]
}
pub fn encode_gain(g1: f32) -> u8 {
if g1 <= IMBE_GAIN_LEVELS[0] {
return 0;
}
if g1 >= IMBE_GAIN_LEVELS[GAIN_LEVELS - 1] {
return (GAIN_LEVELS - 1) as u8;
}
let mut lo = 0usize;
let mut hi = GAIN_LEVELS - 1;
while lo + 1 < hi {
let mid = (lo + hi) / 2;
if IMBE_GAIN_LEVELS[mid] <= g1 { lo = mid; } else { hi = mid; }
}
let lo_dist = (g1 - IMBE_GAIN_LEVELS[lo]).abs();
let hi_dist = (IMBE_GAIN_LEVELS[hi] - g1).abs();
if hi_dist < lo_dist { hi as u8 } else { lo as u8 }
}
#[inline]
pub fn decode_uniform(b_m: u16, b_m_bits: u8, delta_m: f32) -> f32 {
if b_m_bits == 0 {
return 0.0;
}
let half = 1u32 << (b_m_bits - 1);
delta_m * (f32::from(b_m) - half as f32 + 0.5)
}
const HOC_STEP: [f32; 11] = [
0.0, 1.20, 0.85, 0.65, 0.40, 0.28, 0.15, 0.08, 0.04, 0.02, 0.01,
];
const HOC_SIGMA: [f32; 11] = [
0.0, 0.0, 0.307, 0.241, 0.207, 0.190, 0.179, 0.173, 0.165, 0.170, 0.170,
];
#[inline]
pub fn hoc_step_size(b_m_bits: u8, k: u8) -> f32 {
let step_idx = (b_m_bits as usize).min(10);
let k_idx = (k as usize).min(10);
HOC_STEP[step_idx] * HOC_SIGMA[k_idx]
}
pub fn decode_gain_dct(b: &[u16; 59], l: u8) -> [f32; 6] {
debug_assert!((9..=56).contains(&l));
let l_idx = (l - 9) as usize;
let mut g = [0f32; 6];
g[0] = decode_gain((b[2] & 0x3F) as u8); for m_idx in 0..5 {
let alloc = IMBE_GAIN_ALLOC[l_idx][m_idx];
g[m_idx + 1] = decode_uniform(b[m_idx + 3], alloc.b_m, alloc.delta_m);
}
g
}
pub fn gain_to_residuals(g: &[f32; 6]) -> [f32; 6] {
let cos_tab = dct6_cos();
let mut r = [0f32; 6];
for i_0 in 0..6 {
let mut acc = 0.0f32;
for m_0 in 0..6 {
let alpha = if m_0 == 0 { 1.0 } else { 2.0 };
acc += alpha * g[m_0] * cos_tab[m_0 * 6 + i_0];
}
r[i_0] = acc;
}
r
}
pub const MAX_BLOCK_SIZE: usize = 10;
fn dct_cos(j_i: usize) -> &'static [f32] {
use std::sync::OnceLock;
static TABLES: OnceLock<[Vec<f32>; MAX_BLOCK_SIZE + 1]> = OnceLock::new();
let tables = TABLES.get_or_init(|| {
core::array::from_fn(|j| {
if j == 0 {
Vec::new()
} else {
let mut t = vec![0f32; j * j];
for k_0 in 0..j {
for j_0 in 0..j {
t[k_0 * j + j_0] =
(PI * (k_0 as f32) * (j_0 as f32 + 0.5) / j as f32).cos();
}
}
t
}
})
});
&tables[j_i]
}
fn dct6_cos() -> &'static [f32; 36] {
use std::sync::OnceLock;
static TABLE: OnceLock<[f32; 36]> = OnceLock::new();
TABLE.get_or_init(|| {
let mut t = [0f32; 36];
for m_0 in 0..6 {
for i_0 in 0..6 {
t[m_0 * 6 + i_0] =
(PI * (m_0 as f32) * (i_0 as f32 + 0.5) / 6.0).cos();
}
}
t
})
}
pub fn assemble_hoc_matrix(
b: &[u16; 59],
l: u8,
r_i: &[f32; 6],
) -> [[f32; MAX_BLOCK_SIZE]; 6] {
debug_assert!((9..=56).contains(&l));
let l_idx = (l - 9) as usize;
let (off, len) = IMBE_HOC_OFFSETS[l_idx];
let mut c = [[0f32; MAX_BLOCK_SIZE]; 6];
for i in 0..6 {
c[i][0] = r_i[i];
}
for j in 0..len as usize {
let entry = IMBE_HOC_ENTRIES[off as usize + j];
let val = decode_uniform(
b[entry.b_m as usize],
entry.b_m_bits,
hoc_step_size(entry.b_m_bits, entry.c_k),
);
c[(entry.c_i - 1) as usize][(entry.c_k - 1) as usize] = val;
}
c
}
pub fn inverse_block_dct(
c: &[[f32; MAX_BLOCK_SIZE]; 6],
blocks: &[u8; 6],
) -> [f32; L_MAX as usize] {
let mut t = [0f32; L_MAX as usize];
let mut l_offset = 0usize;
for i in 0..6 {
let j_i = blocks[i] as usize;
if j_i == 0 {
continue;
}
let cos_tab = dct_cos(j_i);
for j_0 in 0..j_i {
let mut acc = 0.0f32;
for k_0 in 0..j_i {
let alpha = if k_0 == 0 { 1.0 } else { 2.0 };
acc += alpha * c[i][k_0] * cos_tab[k_0 * j_i + j_0];
}
t[l_offset + j_0] = acc;
}
l_offset += j_i;
}
t
}
#[inline]
pub fn imbe_rho(l: u8) -> f32 {
if l <= 15 {
0.40
} else if l <= 24 {
0.03 * f32::from(l) - 0.05
} else {
0.70
}
}
pub const INIT_PREV_L: u8 = 30;
#[derive(Clone, Debug)]
pub struct DecoderState {
prev_m_linear: [f32; L_MAX as usize + 2],
prev_l: u8,
prev_sync: bool,
}
impl DecoderState {
pub fn new() -> Self {
Self {
prev_m_linear: [1.0; L_MAX as usize + 2],
prev_l: INIT_PREV_L,
prev_sync: false,
}
}
pub fn from_amplitudes(amplitudes: &[f32], l_prev: u8) -> Self {
let mut s = Self::new();
let n = amplitudes.len().min(l_prev as usize);
for i in 0..n {
s.prev_m_linear[i + 1] = amplitudes[i];
}
s.prev_l = l_prev;
s
}
fn prev_m_at(&self, l: u8) -> f32 {
if l == 0 {
return 1.0; }
let idx = (l as usize).min(self.prev_l as usize);
self.prev_m_linear[idx]
}
pub fn previous_l(&self) -> u8 {
self.prev_l
}
pub fn previous_sync(&self) -> bool {
self.prev_sync
}
}
impl Default for DecoderState {
fn default() -> Self {
Self::new()
}
}
pub fn apply_log_prediction(
t: &[f32; L_MAX as usize],
l: u8,
state: &DecoderState,
) -> [f32; L_MAX as usize + 2] {
let mut log_m = [0f32; L_MAX as usize + 2];
let l_curr = f32::from(l);
let l_prev = f32::from(state.prev_l);
let mut mean_sum = 0f32;
for lambda in 1..=l {
let k_lambda = l_prev * f32::from(lambda) / l_curr;
let k_floor = k_lambda.floor();
let delta = k_lambda - k_floor;
let log_lo = state.prev_m_at(k_floor as u8).log2();
let log_hi = state.prev_m_at(k_floor as u8 + 1).log2();
mean_sum += (1.0 - delta) * log_lo + delta * log_hi;
}
let mean = mean_sum / l_curr;
for l_h in 1..=l {
let k_l = l_prev * f32::from(l_h) / l_curr;
let k_floor = k_l.floor();
let delta = k_l - k_floor;
let log_lo = state.prev_m_at(k_floor as u8).log2();
let log_hi = state.prev_m_at(k_floor as u8 + 1).log2();
let rho = imbe_rho(l);
log_m[l_h as usize] = t[(l_h - 1) as usize]
+ rho * (1.0 - delta) * log_lo
+ rho * delta * log_hi
- rho * mean;
}
log_m
}
#[inline]
pub fn expected_sync_bit(prev_sync: bool) -> bool {
!prev_sync
}
#[inline]
pub fn extract_pitch_index(u: &[u16; 8]) -> u8 {
let msbs = ((u[0] >> 6) & 0x3F) as u8; let lsbs = ((u[7] >> 1) & 0x03) as u8; (msbs << 2) | lsbs
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DecodeError {
BadPitch,
InvalidParams(MbeParamsError),
}
pub fn reconstruct_amplitudes_from_bits(
b: &[u16; 59],
l: u8,
state: &DecoderState,
) -> [f32; L_MAX as usize] {
let blocks = IMBE_BLOCK_LENGTHS[(l - 9) as usize];
let g = decode_gain_dct(b, l);
let r_i = gain_to_residuals(&g);
let c = assemble_hoc_matrix(b, l, &r_i);
let t = inverse_block_dct(&c, &blocks);
let log_m = apply_log_prediction(&t, l, state);
let mut out = [0f32; L_MAX as usize];
for i in 0..l as usize {
out[i] = log_m[i + 1].exp2();
}
out
}
pub fn dequantize(
u: &[u16; 8],
state: &mut DecoderState,
) -> Result<MbeParams, DecodeError> {
let b0 = extract_pitch_index(u);
let pitch = pitch_decode(b0).ok_or(DecodeError::BadPitch)?;
let l = pitch.l;
let k = pitch.k;
let b = deprioritize(u, l);
let voiced = expand_vuv(b[1], l, k);
let g = decode_gain_dct(&b, l);
let blocks = IMBE_BLOCK_LENGTHS[(l - 9) as usize];
let r_i = gain_to_residuals(&g);
let c = assemble_hoc_matrix(&b, l, &r_i);
let t = inverse_block_dct(&c, &blocks);
let log_m = apply_log_prediction(&t, l, state);
let mut amplitudes = [0f32; L_MAX as usize];
for i in 0..l as usize {
amplitudes[i] = log_m[i + 1].exp2();
}
let params = MbeParams::new(
pitch.omega_0,
l,
&voiced[..l as usize],
&litudes[..l as usize],
)
.map_err(DecodeError::InvalidParams)?;
state.prev_m_linear[0] = 1.0;
for i in 1..=l as usize {
state.prev_m_linear[i] = amplitudes[i - 1];
}
state.prev_l = l;
state.prev_sync = (b[(l + 2) as usize] & 1) != 0;
Ok(params)
}
pub type FullrateDecoderState = DecoderState;
#[derive(Clone, Debug)]
pub enum Decoded {
Voice(MbeParams),
Erasure,
}
pub fn decode_to_params(
frame: &crate::imbe_wire::frame::Frame,
state: &mut FullrateDecoderState,
) -> Decoded {
match dequantize(&frame.info, state) {
Ok(params) => Decoded::Voice(params),
Err(_) => Decoded::Erasure,
}
}
pub fn encode_pitch(omega_0: f32) -> Option<u8> {
if !(omega_0 > 0.0 && omega_0 < PI) {
return None;
}
let raw = (4.0 * PI / omega_0 - 39.0).floor() as i32;
if (0..=PITCH_INDEX_MAX as i32).contains(&raw) {
Some(raw as u8)
} else {
None
}
}
pub fn collapse_vuv(voiced: &[bool], k: u8) -> u16 {
debug_assert!(k > 0 && k <= 12);
let l = voiced.len() as u8;
let mut b1 = 0u16;
for band in 1..=k {
let first = (1..=l).find(|&l_h| band_for_harmonic(l_h, k) == band);
let v = first.map_or(false, |l_h| voiced[(l_h - 1) as usize]);
if v {
b1 |= 1u16 << (k - band);
}
}
b1
}
#[inline]
pub fn encode_uniform(value: f32, b_m_bits: u8, delta_m: f32) -> u16 {
if b_m_bits == 0 {
return 0;
}
let half = 1u32 << (b_m_bits - 1);
let max = (1u32 << b_m_bits) - 1;
let raw = (value / delta_m + half as f32 - 0.5).round() as i32;
raw.clamp(0, max as i32) as u16
}
pub fn encode_gain_dct(g: &[f32; 6], l: u8) -> [u16; 6] {
debug_assert!((9..=56).contains(&l));
let l_idx = (l - 9) as usize;
let mut b = [0u16; 6];
b[0] = u16::from(encode_gain(g[0]));
for m_idx in 0..5 {
let alloc = IMBE_GAIN_ALLOC[l_idx][m_idx];
b[m_idx + 1] = encode_uniform(g[m_idx + 1], alloc.b_m, alloc.delta_m);
}
b
}
pub fn residuals_to_gain(r: &[f32; 6]) -> [f32; 6] {
let cos_tab = dct6_cos();
let mut g = [0f32; 6];
for m_0 in 0..6 {
let mut acc = 0f32;
for i_0 in 0..6 {
acc += r[i_0] * cos_tab[m_0 * 6 + i_0];
}
g[m_0] = acc / 6.0;
}
g
}
pub fn forward_block_dct(samples: &[f32], n: usize) -> [f32; MAX_BLOCK_SIZE] {
let mut c = [0f32; MAX_BLOCK_SIZE];
if n == 0 {
return c;
}
let cos_tab = dct_cos(n);
let inv_n = 1.0 / n as f32;
for k_0 in 0..n {
let mut acc = 0f32;
for j_0 in 0..n {
acc += samples[j_0] * cos_tab[k_0 * n + j_0];
}
c[k_0] = acc * inv_n;
}
c
}
pub fn disassemble_hoc_matrix(
c: &[[f32; MAX_BLOCK_SIZE]; 6],
l: u8,
b: &mut [u16; 59],
) {
debug_assert!((9..=56).contains(&l));
let l_idx = (l - 9) as usize;
let (off, len) = IMBE_HOC_OFFSETS[l_idx];
for j in 0..len as usize {
let entry = IMBE_HOC_ENTRIES[off as usize + j];
let val = c[(entry.c_i - 1) as usize][(entry.c_k - 1) as usize];
b[entry.b_m as usize] = encode_uniform(
val,
entry.b_m_bits,
hoc_step_size(entry.b_m_bits, entry.c_k),
);
}
}
pub fn forward_log_prediction(
log_m: &[f32; L_MAX as usize + 2],
l: u8,
state: &DecoderState,
) -> [f32; L_MAX as usize] {
let mut t = [0f32; L_MAX as usize];
let l_curr = f32::from(l);
let l_prev = f32::from(state.prev_l);
let mut mean_sum = 0f32;
for lambda in 1..=l {
let k_lambda = l_prev * f32::from(lambda) / l_curr;
let k_floor = k_lambda.floor();
let delta = k_lambda - k_floor;
let log_lo = state.prev_m_at(k_floor as u8).log2();
let log_hi = state.prev_m_at(k_floor as u8 + 1).log2();
mean_sum += (1.0 - delta) * log_lo + delta * log_hi;
}
let mean = mean_sum / l_curr;
for l_h in 1..=l {
let k_l = l_prev * f32::from(l_h) / l_curr;
let k_floor = k_l.floor();
let delta = k_l - k_floor;
let log_lo = state.prev_m_at(k_floor as u8).log2();
let log_hi = state.prev_m_at(k_floor as u8 + 1).log2();
let rho = imbe_rho(l);
t[(l_h - 1) as usize] = log_m[l_h as usize]
- rho * (1.0 - delta) * log_lo
- rho * delta * log_hi
+ rho * mean;
}
t
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EncodeError {
PitchOutOfRange,
}
pub fn quantize(
params: &MbeParams,
state: &mut DecoderState,
) -> Result<[u16; 59], EncodeError> {
let l = params.harmonic_count();
let omega_0 = params.omega_0();
let b0 = encode_pitch(omega_0).ok_or(EncodeError::PitchOutOfRange)?;
let k = vuv_band_count(l);
let mut b = [0u16; 59];
b[0] = u16::from(b0);
b[1] = collapse_vuv(params.voiced_slice(), k);
let mut log_m = [0f32; L_MAX as usize + 2];
log_m[0] = 0.0; for i in 0..l as usize {
let m = params.amplitudes_slice()[i];
log_m[i + 1] = if m > 0.0 { m.log2() } else { f32::NEG_INFINITY };
}
let t = forward_log_prediction(&log_m, l, state);
let blocks = IMBE_BLOCK_LENGTHS[(l - 9) as usize];
let mut c_matrix = [[0f32; MAX_BLOCK_SIZE]; 6];
let mut l_offset = 0usize;
for i in 0..6 {
let j_i = blocks[i] as usize;
let mut block = [0f32; MAX_BLOCK_SIZE];
for j in 0..j_i {
block[j] = t[l_offset + j];
}
c_matrix[i] = forward_block_dct(&block, j_i);
l_offset += j_i;
}
let r_i: [f32; 6] = std::array::from_fn(|i| c_matrix[i][0]);
let g = residuals_to_gain(&r_i);
let g_indices = encode_gain_dct(&g, l);
for m_idx in 0..6 {
b[m_idx + 2] = g_indices[m_idx];
}
disassemble_hoc_matrix(&c_matrix, l, &mut b);
let sync = !state.prev_sync;
b[(l + 2) as usize] = u16::from(sync);
let m_reconstructed = reconstruct_amplitudes_from_bits(&b, l, state);
state.prev_m_linear[0] = 1.0;
for i in 1..=l as usize {
state.prev_m_linear[i] = m_reconstructed[i - 1];
}
state.prev_l = l;
state.prev_sync = sync;
Ok(b)
}
pub fn expand_vuv(b1: u16, l: u8, k: u8) -> [bool; L_MAX as usize] {
debug_assert!(l <= L_MAX, "L exceeds L_MAX");
debug_assert!(k > 0 && k <= 12, "K out of range");
let mut out = [false; L_MAX as usize];
for harm in 1..=l {
let band = band_for_harmonic(harm, k); let bit_pos = k - band; out[(harm - 1) as usize] = ((b1 >> bit_pos) & 1) == 1;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pitch_decode_reserved_returns_none() {
for b0 in 208u8..=255 {
assert_eq!(pitch_decode(b0), None, "b0={b0}");
}
}
#[test]
fn pitch_decode_b0_zero_gives_l9_k3() {
let p = pitch_decode(0).unwrap();
assert_eq!(p.l, 9);
assert_eq!(p.k, 3);
assert!((p.omega_0 - 4.0 * PI / 39.5).abs() < 1e-6);
}
#[test]
fn pitch_decode_b0_max_gives_l56_k12() {
let p = pitch_decode(PITCH_INDEX_MAX).unwrap();
assert_eq!(p.l, 56);
assert_eq!(p.k, 12);
assert!((p.omega_0 - 4.0 * PI / 246.5).abs() < 1e-6);
}
#[test]
fn pitch_decode_omega_strictly_decreasing_in_b0() {
let mut prev = f32::INFINITY;
for b0 in 0..=PITCH_INDEX_MAX {
let p = pitch_decode(b0).unwrap();
assert!(p.omega_0 < prev, "ω̃₀ not decreasing at b0={b0}");
prev = p.omega_0;
}
}
#[test]
fn pitch_decode_l_in_valid_range() {
for b0 in 0..=PITCH_INDEX_MAX {
let p = pitch_decode(b0).unwrap();
assert!((9..=56).contains(&p.l), "L={} out of range at b0={b0}", p.l);
}
}
#[test]
fn vuv_band_count_at_boundary() {
assert_eq!(vuv_band_count(36), 12);
for l in 37..=56 {
assert_eq!(vuv_band_count(l), 12, "L={l}");
}
assert_eq!(vuv_band_count(9), 3);
}
#[test]
fn annex_f_table_shape_and_spot_values() {
assert_eq!(IMBE_GAIN_ALLOC.len(), 48);
for row in IMBE_GAIN_ALLOC.iter() {
assert_eq!(row.len(), 5);
for entry in row {
assert!(entry.b_m >= 1 && entry.b_m <= 10);
assert!(entry.delta_m > 0.0);
}
}
let r = IMBE_GAIN_ALLOC[0][0];
assert_eq!(r.b_m, 10);
assert!((r.delta_m - 0.003100).abs() < 1e-6);
let r = IMBE_GAIN_ALLOC[0][1];
assert_eq!(r.b_m, 9);
assert!((r.delta_m - 0.004020).abs() < 1e-6);
}
#[test]
fn annex_g_table_shape_and_offsets() {
assert_eq!(IMBE_HOC_ENTRIES.len(), 1272);
assert_eq!(IMBE_HOC_OFFSETS.len(), 48);
for (i, &(_off, len)) in IMBE_HOC_OFFSETS.iter().enumerate() {
let l = (i + 9) as u32;
assert_eq!(len as u32, l - 6, "L={l}");
}
let (last_off, last_len) = IMBE_HOC_OFFSETS[47];
assert_eq!(last_off + last_len, IMBE_HOC_ENTRIES.len() as u32);
let r = IMBE_HOC_ENTRIES[0];
assert_eq!((r.c_i, r.c_k, r.b_m, r.b_m_bits), (4, 2, 8, 9));
}
#[test]
fn annex_g_b_m_walks_8_upward_per_l() {
for (l_idx, &(off, len)) in IMBE_HOC_OFFSETS.iter().enumerate() {
let entries = &IMBE_HOC_ENTRIES[off as usize..(off + len) as usize];
for (j, e) in entries.iter().enumerate() {
assert_eq!(
e.b_m,
8 + j as u8,
"L={}: row {j} expected b_m={}, got {}",
l_idx + 9,
8 + j as u8,
e.b_m
);
}
}
}
#[test]
fn annex_j_block_lengths_sum_to_l() {
for (l_idx, row) in IMBE_BLOCK_LENGTHS.iter().enumerate() {
let l = (l_idx + 9) as u32;
let sum: u32 = row.iter().map(|&x| x as u32).sum();
assert_eq!(sum, l, "L={l}");
for i in 0..5 {
assert!(row[i] <= row[i + 1], "L={l}: blocks not non-decreasing");
}
}
}
#[test]
fn gain_table_endpoints_match_spec() {
assert!((decode_gain(0) - (-2.842205)).abs() < 1e-6);
assert!((decode_gain(63) - 8.695827).abs() < 1e-6);
}
#[test]
fn gain_table_is_strictly_monotone() {
for i in 0..63 {
assert!(
decode_gain(i) < decode_gain(i + 1),
"gain table not strictly increasing at b̂₂={i}"
);
}
}
#[test]
fn gain_encode_decode_roundtrip_on_table_values() {
for i in 0..64u8 {
let g = decode_gain(i);
assert_eq!(encode_gain(g), i, "roundtrip failed at b̂₂={i}");
}
}
#[test]
fn gain_encode_saturates_outside_range() {
assert_eq!(encode_gain(-100.0), 0);
assert_eq!(encode_gain(100.0), 63);
assert_eq!(encode_gain(f32::NEG_INFINITY), 0);
assert_eq!(encode_gain(f32::INFINITY), 63);
}
#[test]
fn gain_encode_picks_nearest_neighbour() {
for i in 0..63u8 {
let lo = decode_gain(i);
let hi = decode_gain(i + 1);
let mid = (lo + hi) / 2.0;
let just_below = mid - (hi - lo) * 0.01;
assert_eq!(encode_gain(just_below), i, "below midpoint at i={i}");
let just_above = mid + (hi - lo) * 0.01;
assert_eq!(encode_gain(just_above), i + 1, "above midpoint at i={i}");
}
}
#[test]
fn decode_uniform_zero_bits_is_zero() {
for delta in [0.001f32, 1.0, 100.0] {
assert_eq!(decode_uniform(0, 0, delta), 0.0);
assert_eq!(decode_uniform(7, 0, delta), 0.0);
}
}
#[test]
fn decode_uniform_midtread_centre_offset() {
let delta = 0.0964f32; assert!((decode_uniform(7, 4, delta) - (-0.5 * delta)).abs() < 1e-7);
assert!((decode_uniform(8, 4, delta) - 0.5 * delta).abs() < 1e-7);
assert!((decode_uniform(0, 4, delta) - (-7.5 * delta)).abs() < 1e-7);
assert!((decode_uniform(15, 4, delta) - 7.5 * delta).abs() < 1e-7);
}
#[test]
fn hoc_step_size_matches_spec_example() {
let s = hoc_step_size(4, 3);
assert!((s - 0.0964).abs() < 1e-6);
}
#[test]
fn hoc_step_size_clamps_high_k() {
let s10 = hoc_step_size(5, 10);
for k in 10..=20u8 {
assert_eq!(hoc_step_size(5, k), s10);
}
}
#[test]
fn decode_gain_dct_only_g1_with_zero_quantizer_indices() {
let mut b = [0u16; 59];
b[2] = 31; for m_idx in 0..5 {
let alloc = IMBE_GAIN_ALLOC[0][m_idx]; b[m_idx + 3] = 1u16 << (alloc.b_m - 1);
}
let g = decode_gain_dct(&b, 9);
assert!((g[0] - decode_gain(31)).abs() < 1e-6);
for m_idx in 0..5 {
let alloc = IMBE_GAIN_ALLOC[0][m_idx];
let expected = alloc.delta_m * 0.5;
assert!(
(g[m_idx + 1] - expected).abs() < 1e-6,
"G̃_{}: expected {expected}, got {}",
m_idx + 2,
g[m_idx + 1]
);
}
}
#[test]
fn gain_to_residuals_constant_g1_propagates_uniformly() {
let g = [3.5f32, 0.0, 0.0, 0.0, 0.0, 0.0];
let r = gain_to_residuals(&g);
for i in 0..6 {
assert!((r[i] - 3.5).abs() < 1e-5, "R̃_{}: {}", i + 1, r[i]);
}
}
#[test]
fn gain_to_residuals_uses_fixed_six_denominator() {
let mut g = [0f32; 6];
g[2] = 1.0; let r = gain_to_residuals(&g);
for i_0 in 0..6usize {
let expected =
2.0 * (std::f32::consts::PI * 2.0 * (i_0 as f32 + 0.5) / 6.0).cos();
assert!(
(r[i_0] - expected).abs() < 1e-5,
"R̃_{}: got {}, expected {}",
i_0 + 1,
r[i_0],
expected
);
}
}
#[test]
fn block_dct_forward_then_inverse_is_identity() {
for &n in &[1usize, 2, 3, 5, 7, 10] {
let samples: [f32; MAX_BLOCK_SIZE] = std::array::from_fn(|i| {
if i < n { (i as f32 + 1.0).sin() * 2.0 } else { 0.0 }
});
let c = forward_block_dct(&samples, n);
let mut matrix = [[0f32; MAX_BLOCK_SIZE]; 6];
matrix[0] = c;
let mut blocks = [0u8; 6];
blocks[0] = n as u8;
let t = inverse_block_dct(&matrix, &blocks);
for i in 0..n {
assert!(
(t[i] - samples[i]).abs() < 1e-4,
"n={n}, i={i}: expected {}, got {}",
samples[i],
t[i]
);
}
}
}
#[test]
fn assemble_hoc_matrix_block_means_match_r_i() {
let b = [0u16; 59];
let r = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let c = assemble_hoc_matrix(&b, 9, &r);
for i in 0..6 {
assert_eq!(c[i][0], r[i]);
}
}
#[test]
fn assemble_hoc_matrix_routes_via_eq_72() {
let mut b = [0u16; 59];
b[8] = (1u16 << 8) + 1; b[9] = (1u16 << 7) + 1; b[10] = (1u16 << 6) + 1; let r = [0f32; 6];
let c = assemble_hoc_matrix(&b, 9, &r);
for i in 0..6 { assert_eq!(c[i][0], 0.0); }
assert_ne!(c[3][1], 0.0, "(C_i=4, C_k=2)");
assert_ne!(c[4][1], 0.0, "(C_i=5, C_k=2)");
assert_ne!(c[5][1], 0.0, "(C_i=6, C_k=2)");
assert_eq!(c[0][1], 0.0);
assert_eq!(c[1][1], 0.0);
assert_eq!(c[2][1], 0.0);
}
#[test]
fn decoder_state_init_matches_spec() {
let s = DecoderState::new();
assert_eq!(s.previous_l(), 30);
assert!(!s.previous_sync());
for i in 0..=L_MAX as usize + 1 {
assert_eq!(s.prev_m_linear[i], 1.0);
}
}
#[test]
fn prev_m_at_clamps_for_indices_above_prev_l() {
let mut s = DecoderState::new();
s.prev_l = 9;
for i in 0..=L_MAX as usize + 1 {
s.prev_m_linear[i] = 0.5; }
s.prev_m_linear[0] = 1.0;
for i in 1..=9 {
s.prev_m_linear[i] = i as f32; }
assert_eq!(s.prev_m_at(0), 1.0);
assert_eq!(s.prev_m_at(1), 1.0);
assert_eq!(s.prev_m_at(9), 9.0);
assert_eq!(s.prev_m_at(10), 9.0);
assert_eq!(s.prev_m_at(56), 9.0);
assert_eq!(s.prev_m_at(57), 9.0);
}
#[test]
fn log_prediction_with_unit_prev_m_subtracts_to_zero() {
let mut t = [0f32; L_MAX as usize];
for i in 0..30 {
t[i] = (i as f32) * 0.1 - 1.5; }
let state = DecoderState::new();
let log_m = apply_log_prediction(&t, 30, &state);
for l in 1..=30usize {
assert!(
(log_m[l] - t[l - 1]).abs() < 1e-5,
"l={l}: expected {}, got {}",
t[l - 1],
log_m[l]
);
}
}
#[test]
fn log_prediction_constant_prev_amplitude_subtracts_constant() {
let mut state = DecoderState::new();
state.prev_l = 20;
for i in 0..=L_MAX as usize + 1 {
state.prev_m_linear[i] = 4.0;
}
state.prev_m_linear[0] = 1.0; let mut t = [0f32; L_MAX as usize];
for i in 0..15 { t[i] = 0.5; }
let log_m = apply_log_prediction(&t, 15, &state);
for l in 1..=15 {
assert!(
(log_m[l] - t[l - 1]).abs() < 1e-5,
"l={l}: expected {}, got {}",
t[l - 1],
log_m[l]
);
}
}
#[test]
fn expected_sync_bit_toggles() {
assert!(expected_sync_bit(false));
assert!(!expected_sync_bit(true));
}
#[test]
fn extract_pitch_index_round_trip_via_priority_table() {
for b0 in 0..=255u8 {
let mut b = [0u16; 59];
b[0] = u16::from(b0);
let u = crate::imbe_wire::priority::prioritize(&b, 9);
assert_eq!(extract_pitch_index(&u), b0, "b̂₀ = {b0}");
}
}
#[test]
fn dequantize_rejects_reserved_pitch() {
let mut b = [0u16; 59];
b[0] = 220; let u = crate::imbe_wire::priority::prioritize(&b, 9);
let mut state = DecoderState::new();
assert_eq!(
dequantize(&u, &mut state),
Err(DecodeError::BadPitch)
);
}
#[test]
fn dequantize_produces_sane_silence_for_zero_b() {
let b = [0u16; 59];
let u = crate::imbe_wire::priority::prioritize(&b, 9);
let mut state = DecoderState::new();
let p = dequantize(&u, &mut state).expect("decode");
assert_eq!(p.harmonic_count(), 9);
assert!((p.omega_0() - 4.0 * PI / 39.5).abs() < 1e-6);
for l in 1..=9 {
assert!(!p.voiced(l));
}
for l in 1..=9 {
let a = p.amplitude(l);
assert!(a.is_finite() && a >= 0.0, "l={l}: M̃ = {a}");
}
assert_eq!(state.previous_l(), 9);
}
#[test]
fn dequantize_advances_decoder_state() {
let b = [0u16; 59];
let u = crate::imbe_wire::priority::prioritize(&b, 9);
let mut state = DecoderState::new();
let p1 = dequantize(&u, &mut state).unwrap();
let prev_l_after_first = state.previous_l();
assert_eq!(prev_l_after_first, p1.harmonic_count());
let p2 = dequantize(&u, &mut state).unwrap();
assert_eq!(p2.harmonic_count(), p1.harmonic_count());
assert_eq!(state.previous_l(), p2.harmonic_count());
}
fn frame_with_pitch(b0: u8) -> crate::imbe_wire::frame::Frame {
let mut b = [0u16; 59];
b[0] = u16::from(b0);
let u = crate::imbe_wire::priority::prioritize(&b, 9);
crate::imbe_wire::frame::Frame {
info: u,
errors: [0u8; 8],
}
}
#[test]
fn decode_to_params_dispatches_voice_on_valid_pitch() {
let frame = frame_with_pitch(0);
let mut state = FullrateDecoderState::new();
match decode_to_params(&frame, &mut state) {
Decoded::Voice(p) => {
assert_eq!(p.harmonic_count(), 9);
assert_eq!(state.previous_l(), 9);
}
Decoded::Erasure => panic!("expected Voice"),
}
}
#[test]
fn decode_to_params_dispatches_erasure_on_reserved_pitch() {
let frame = frame_with_pitch(220); let mut state = FullrateDecoderState::new();
state.prev_l = 15;
state.prev_sync = true;
match decode_to_params(&frame, &mut state) {
Decoded::Erasure => {}
Decoded::Voice(_) => panic!("expected Erasure"),
}
assert_eq!(state.previous_l(), 15);
assert!(state.prev_sync);
}
#[test]
fn decode_to_params_voice_advances_state_erasure_does_not() {
let voice_frame = frame_with_pitch(0);
let erasure_frame = frame_with_pitch(230);
let mut state = FullrateDecoderState::new();
assert!(matches!(
decode_to_params(&voice_frame, &mut state),
Decoded::Voice(_)
));
let l_after_voice = state.previous_l();
let sync_after_voice = state.prev_sync;
assert!(matches!(
decode_to_params(&erasure_frame, &mut state),
Decoded::Erasure
));
assert_eq!(state.previous_l(), l_after_voice);
assert_eq!(state.prev_sync, sync_after_voice);
}
#[test]
fn imbe_decoder_state_alias_is_decoder_state() {
fn takes_decoder_state(_: &mut DecoderState) {}
let mut s: FullrateDecoderState = FullrateDecoderState::new();
takes_decoder_state(&mut s);
}
#[test]
fn encode_pitch_roundtrip_across_full_range() {
for b0 in 0..=PITCH_INDEX_MAX {
let p = pitch_decode(b0).unwrap();
assert_eq!(encode_pitch(p.omega_0), Some(b0), "b̂₀ = {b0}");
}
}
#[test]
fn encode_pitch_rejects_out_of_range_omega() {
assert_eq!(encode_pitch(0.0), None);
assert_eq!(encode_pitch(-0.1), None);
assert_eq!(encode_pitch(PI), None);
assert_eq!(encode_pitch(PI + 0.1), None);
assert_eq!(encode_pitch(0.5), None);
}
#[test]
fn collapse_vuv_roundtrips_against_expand() {
for l in [9u8, 16, 36, 37, 56] {
let k = vuv_band_count(l);
let max = 1u32 << k;
for b1 in 0..max as u16 {
let v = expand_vuv(b1, l, k);
let b1_back = collapse_vuv(&v[..l as usize], k);
assert_eq!(b1_back, b1, "L={l}, K={k}, b̂₁={b1:#x}");
}
}
}
#[test]
fn encode_uniform_roundtrips_at_quantizer_grid() {
for b_bits in 1..=10u8 {
let max = 1u32 << b_bits;
let delta = 0.5f32; for code in 0..max as u16 {
let v = decode_uniform(code, b_bits, delta);
let back = encode_uniform(v, b_bits, delta);
assert_eq!(back, code, "B={b_bits}, code={code}");
}
}
}
#[test]
fn encode_uniform_zero_bits_returns_zero() {
assert_eq!(encode_uniform(0.0, 0, 1.0), 0);
assert_eq!(encode_uniform(123.0, 0, 1.0), 0);
}
#[test]
fn encode_uniform_clamps_outside_range() {
assert_eq!(encode_uniform(-1000.0, 4, 1.0), 0);
assert_eq!(encode_uniform(1000.0, 4, 1.0), 15);
}
#[test]
fn forward_block_dct_inverts_inverse_block_dct() {
for &n in &[1usize, 2, 3, 5, 7, 10] {
let samples: [f32; MAX_BLOCK_SIZE] =
std::array::from_fn(|i| if i < n { (i as f32 + 1.0).sin() * 2.0 } else { 0.0 });
let c = forward_block_dct(&samples, n);
let mut matrix = [[0f32; MAX_BLOCK_SIZE]; 6];
matrix[0] = c;
let mut blocks = [0u8; 6];
blocks[0] = n as u8;
let t = inverse_block_dct(&matrix, &blocks);
for i in 0..n {
assert!((t[i] - samples[i]).abs() < 1e-4, "n={n}, i={i}");
}
}
}
#[test]
fn gain_dct_is_self_inverse_post_eq_69_correction() {
let r: [f32; 6] = [1.0, -2.5, 0.3, 0.0, -0.7, 1.8];
let g = residuals_to_gain(&r);
let r2 = gain_to_residuals(&g);
for i in 0..6 {
assert!(
(r2[i] - r[i]).abs() < 1e-4,
"round-trip: i={i}: {} vs {}",
r2[i],
r[i]
);
}
}
#[test]
fn gain_dct_dc_only_input_propagates_uniformly() {
let c = 3.5;
let r = [c; 6];
let g = residuals_to_gain(&r);
assert!((g[0] - c).abs() < 1e-5);
for m in 1..6 {
assert!(g[m].abs() < 1e-5, "Ĝ_{m} = {}", g[m]);
}
}
#[test]
fn forward_log_prediction_inverts_apply_log_prediction() {
let state = DecoderState::new();
let l = 30u8;
let mut log_m = [0f32; L_MAX as usize + 2];
log_m[0] = 0.0; for i in 1..=l as usize {
log_m[i] = (i as f32 * 0.1) - 1.5; }
let t = forward_log_prediction(&log_m, l, &state);
let log_m_back = apply_log_prediction(&t, l, &state);
for i in 1..=l as usize {
assert!(
(log_m_back[i] - log_m[i]).abs() < 1e-4,
"i={i}: {} vs {}",
log_m_back[i],
log_m[i]
);
}
}
#[test]
fn quantize_pitch_and_vuv_roundtrip_against_dequantize() {
let l = 9u8;
let k = vuv_band_count(l);
let mut b = [0u16; 59];
b[0] = 0;
b[1] = 0b101 & ((1u16 << k) - 1);
let u = crate::imbe_wire::priority::prioritize(&b, l);
let mut state_dec = DecoderState::new();
let params = dequantize(&u, &mut state_dec).unwrap();
let mut state_enc = DecoderState::new();
let b_back = quantize(¶ms, &mut state_enc).unwrap();
assert_eq!(b_back[0], b[0], "b̂₀ pitch");
assert_eq!(b_back[1], b[1], "b̂₁ V/UV");
}
#[test]
fn quantize_multi_frame_predictor_state_tracks_decoder() {
use crate::imbe_wire::priority::prioritize;
let target_b0 = 0u8;
let pitch = pitch_decode(target_b0).unwrap();
let l = pitch.l;
assert_eq!(l, 9, "sanity: b̂₀=0 pairs with L=9");
let omega_0 = pitch.omega_0;
assert_eq!(encode_pitch(omega_0), Some(target_b0));
let voiced = [true; 9];
let frames: Vec<MbeParams> = [[100.0f32; 9], [400.0; 9], [200.0; 9]]
.iter()
.map(|amps| MbeParams::new(omega_0, l, &voiced, amps).unwrap())
.collect();
let mut enc = DecoderState::new();
let mut dec = DecoderState::new();
for (i, params) in frames.iter().enumerate() {
let b = quantize(params, &mut enc).unwrap();
let u = prioritize(&b, l);
let _ = dequantize(&u, &mut dec).unwrap();
assert_eq!(
enc.prev_l, dec.prev_l,
"frame {i}: prev_l {} vs {}", enc.prev_l, dec.prev_l
);
for k in 0..=l as usize {
assert!(
(enc.prev_m_linear[k] - dec.prev_m_linear[k]).abs() < 1e-4,
"frame {i}, harmonic {k}: enc={} dec={}",
enc.prev_m_linear[k],
dec.prev_m_linear[k]
);
}
}
}
#[test]
fn expand_vuv_all_voiced() {
let v = expand_vuv(0b111, 9, 3);
for l in 0..9 {
assert!(v[l], "harmonic {} not voiced", l + 1);
}
for l in 9..L_MAX as usize {
assert!(!v[l], "padding bit set at l={}", l + 1);
}
}
#[test]
fn expand_vuv_all_unvoiced() {
let v = expand_vuv(0, 9, 3);
for l in 0..9 {
assert!(!v[l], "harmonic {} unexpectedly voiced", l + 1);
}
}
#[test]
fn expand_vuv_band_groups_three_harmonics_each() {
let v = expand_vuv(0b101, 9, 3);
for l in 1..=3 { assert!(v[l - 1], "band 1: l={l}"); }
for l in 4..=6 { assert!(!v[l - 1], "band 2: l={l}"); }
for l in 7..=9 { assert!(v[l - 1], "band 3: l={l}"); }
}
#[test]
fn expand_vuv_band_msb_first() {
let v = expand_vuv(0b100, 9, 3);
for l in 1..=3 { assert!(v[l - 1], "band 1 MSB→bit K-1, l={l}"); }
for l in 4..=9 { assert!(!v[l - 1], "expected unvoiced, l={l}"); }
}
#[test]
fn expand_vuv_l_above_36_absorbs_into_band_12() {
let mut b1 = 0u16;
b1 |= 1 << 0; let v = expand_vuv(b1, 56, 12);
for l in 1..=33 { assert!(!v[l - 1], "low band, l={l}"); }
for l in 34..=56 { assert!(v[l - 1], "band 12 absorbing, l={l}"); }
}
#[test]
fn expand_vuv_l37_boundary() {
let mut b1 = 0u16;
b1 |= 1 << 0; let v = expand_vuv(b1, 37, 12);
assert!(v[36], "harmonic 37 should be band 12 (voiced)");
for l in 1..=33 { assert!(!v[l - 1], "low band, l={l}"); }
for l in 34..=37 { assert!(v[l - 1], "band 12, l={l}"); }
}
#[test]
fn expand_vuv_l_under_36_no_clamping_needed() {
let v_all_v = expand_vuv(0xFFFu16, 36, 12);
for l in 1..=36 { assert!(v_all_v[l - 1], "l={l}"); }
let v_all_uv = expand_vuv(0, 36, 12);
for l in 1..=36 { assert!(!v_all_uv[l - 1], "l={l}"); }
}
#[test]
fn band_for_harmonic_matches_spec_examples() {
assert_eq!(band_for_harmonic(1, 3), 1);
assert_eq!(band_for_harmonic(3, 3), 1);
assert_eq!(band_for_harmonic(4, 3), 2);
assert_eq!(band_for_harmonic(6, 3), 2);
assert_eq!(band_for_harmonic(7, 3), 3);
assert_eq!(band_for_harmonic(9, 3), 3);
assert_eq!(band_for_harmonic(33, 12), 11);
assert_eq!(band_for_harmonic(34, 12), 12);
assert_eq!(band_for_harmonic(36, 12), 12);
assert_eq!(band_for_harmonic(37, 12), 12);
assert_eq!(band_for_harmonic(56, 12), 12);
}
}