use crate::silk_lsf_stage2::{D_LPC_MAX, D_LPC_NB_MB, D_LPC_WB};
use crate::toc::Bandwidth;
use crate::Error;
const ORDERING_NB_MB: [u8; D_LPC_NB_MB] = [0, 9, 6, 3, 4, 5, 8, 1, 2, 7];
const ORDERING_WB: [u8; D_LPC_WB] = [0, 15, 8, 7, 4, 11, 12, 3, 2, 13, 10, 5, 6, 9, 14, 1];
pub fn ordering(bandwidth: Bandwidth) -> Result<&'static [u8], Error> {
match bandwidth {
Bandwidth::Nb | Bandwidth::Mb => Ok(&ORDERING_NB_MB),
Bandwidth::Wb => Ok(&ORDERING_WB),
Bandwidth::Swb | Bandwidth::Fb => Err(Error::MalformedPacket),
}
}
#[rustfmt::skip]
const COS_Q12: [i32; 129] = [
4096, 4095, 4091, 4085,
4076, 4065, 4052, 4036,
4017, 3997, 3973, 3948,
3920, 3889, 3857, 3822,
3784, 3745, 3703, 3659,
3613, 3564, 3513, 3461,
3406, 3349, 3290, 3229,
3166, 3102, 3035, 2967,
2896, 2824, 2751, 2676,
2599, 2520, 2440, 2359,
2276, 2191, 2106, 2019,
1931, 1842, 1751, 1660,
1568, 1474, 1380, 1285,
1189, 1093, 995, 897,
799, 700, 601, 501,
401, 301, 201, 101,
0, -101, -201, -301,
-401, -501, -601, -700,
-799, -897, -995, -1093,
-1189, -1285, -1380, -1474,
-1568, -1660, -1751, -1842,
-1931, -2019, -2106, -2191,
-2276, -2359, -2440, -2520,
-2599, -2676, -2751, -2824,
-2896, -2967, -3035, -3102,
-3166, -3229, -3290, -3349,
-3406, -3461, -3513, -3564,
-3613, -3659, -3703, -3745,
-3784, -3822, -3857, -3889,
-3920, -3948, -3973, -3997,
-4017, -4036, -4052, -4065,
-4076, -4085, -4091, -4095,
-4096,
];
pub fn nlsf_to_c_q17(bandwidth: Bandwidth, nlsf_q15: &[i16]) -> Result<[i32; D_LPC_MAX], Error> {
let ord = ordering(bandwidth)?;
let d_lpc = ord.len();
if nlsf_q15.len() != d_lpc {
return Err(Error::MalformedPacket);
}
let mut c_q17 = [0i32; D_LPC_MAX];
for (k, &n) in nlsf_q15.iter().enumerate() {
let n = n as i32;
let i = (n >> 8) as usize;
let f = n & 0xFF;
let a = COS_Q12[i];
let b = COS_Q12[i + 1];
let v = (a * 256 + (b - a) * f + 4) >> 3;
c_q17[ord[k] as usize] = v;
}
Ok(c_q17)
}
fn find_poly(c_q17: &[i32], d_lpc: usize, parity: usize) -> [i64; D_LPC_MAX / 2 + 1] {
debug_assert!(d_lpc == D_LPC_NB_MB || d_lpc == D_LPC_WB);
debug_assert!(parity < 2);
let d2 = d_lpc / 2;
let mut prev = [0i64; D_LPC_MAX / 2 + 1];
let mut curr = [0i64; D_LPC_MAX / 2 + 1];
prev[0] = 1i64 << 16;
prev[1] = -(c_q17[parity] as i64);
for k in 1..d2 {
let c = c_q17[2 * k + parity] as i64;
for j in 0..=k + 1 {
let pj0 = if j < prev.len() { prev[j] } else { 0 };
let pjm2 = if j >= 2 { prev[j - 2] } else { 0 };
let pjm1 = if j >= 1 { prev[j - 1] } else { 0 };
curr[j] = pj0 + pjm2 - ((c * pjm1 + 32768) >> 16);
}
prev.copy_from_slice(&curr);
for cell in prev.iter_mut().skip(k + 2) {
*cell = 0;
}
}
prev
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LpcQ17 {
len: u8,
a32_q17: [i32; D_LPC_MAX],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LpcQ12 {
len: u8,
a_q12: [i32; D_LPC_MAX],
rounds: u8,
}
impl LpcQ12 {
pub fn a_q12(&self) -> &[i32] {
&self.a_q12[..self.len as usize]
}
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn rounds(&self) -> usize {
self.rounds as usize
}
}
impl LpcQ17 {
pub fn from_nlsf(bandwidth: Bandwidth, nlsf_q15: &[i16]) -> Result<Self, Error> {
let ord = ordering(bandwidth)?;
let d_lpc = ord.len();
if nlsf_q15.len() != d_lpc {
return Err(Error::MalformedPacket);
}
let c_q17 = nlsf_to_c_q17(bandwidth, nlsf_q15)?;
let d2 = d_lpc / 2;
let p_last = find_poly(&c_q17[..d_lpc], d_lpc, 0);
let q_last = find_poly(&c_q17[..d_lpc], d_lpc, 1);
let mut a32_q17 = [0i32; D_LPC_MAX];
for k in 0..d2 {
let q_diff = q_last[k + 1] - q_last[k];
let p_sum = p_last[k + 1] + p_last[k];
let lo = -(q_diff + p_sum);
let hi = q_diff - p_sum;
a32_q17[k] = lo as i32;
a32_q17[d_lpc - k - 1] = hi as i32;
}
Ok(Self {
len: d_lpc as u8,
a32_q17,
})
}
pub fn a32_q17(&self) -> &[i32] {
&self.a32_q17[..self.len as usize]
}
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn range_limited(&self) -> LpcQ17 {
let mut a32 = self.a32_q17;
let d_lpc = self.len as usize;
limit_lpc_range(&mut a32[..d_lpc]);
LpcQ17 {
len: self.len,
a32_q17: a32,
}
}
pub fn prediction_gain_limited(&self) -> LpcQ12 {
let d_lpc = self.len as usize;
let mut a32_q17 = self.a32_q17;
for round in 0..16u32 {
let mut a32_q12 = [0i32; D_LPC_MAX];
for k in 0..d_lpc {
a32_q12[k] = (a32_q17[k] + 16) >> 5;
}
if is_lpc_stable(&a32_q12[..d_lpc]) {
let mut a_q12 = [0i32; D_LPC_MAX];
a_q12[..d_lpc].copy_from_slice(&a32_q12[..d_lpc]);
return LpcQ12 {
len: self.len,
a_q12,
rounds: round as u8,
};
}
let sc_q16_0 = 65536i64 - (2i64 << round);
bwexpander_32(&mut a32_q17[..d_lpc], sc_q16_0);
}
let mut a_q12 = [0i32; D_LPC_MAX];
for k in 0..d_lpc {
a_q12[k] = (a32_q17[k] + 16) >> 5;
}
LpcQ12 {
len: self.len,
a_q12,
rounds: 16,
}
}
}
fn is_lpc_stable(a32_q12: &[i32]) -> bool {
let d_lpc = a32_q12.len();
let dc_resp: i64 = a32_q12.iter().map(|&c| c as i64).sum();
if dc_resp > 4096 {
return false;
}
let mut row: [i64; D_LPC_MAX] = [0; D_LPC_MAX];
for k in 0..d_lpc {
row[k] = (a32_q12[k] as i64) << 12;
}
let mut inv_gain_q30: i64 = 1 << 30;
for k in (0..d_lpc).rev() {
let akk = row[k];
if akk.unsigned_abs() > 16_773_022 {
return false;
}
let rc_q31 = -akk << 7;
let div_q30 = (1i64 << 30) - ((rc_q31 * rc_q31) >> 32);
inv_gain_q30 = ((inv_gain_q30 * div_q30) >> 32) << 2;
if inv_gain_q30 < 107_374 {
return false;
}
if k > 0 {
let b1 = ilog64(div_q30) as i64;
let b2 = b1 - 16;
let inv_qb2 = ((1i64 << 29) - 1) / (div_q30 >> (b2 + 1));
let err_q29 = (1i64 << 29) - (((div_q30 << (15 - b2)) * inv_qb2) >> 16);
let gain_qb1 = (inv_qb2 << 16) + ((err_q29 * inv_qb2) >> 13);
let cur = row;
let round_b1 = 1i64 << (b1 - 1);
for n in 0..k {
let num_q24 = cur[n] - (((cur[k - n - 1] * rc_q31) + (1i64 << 30)) >> 31);
row[n] = ((num_q24 * gain_qb1) + round_b1) >> b1;
}
}
}
true
}
fn ilog64(n: i64) -> u32 {
if n <= 0 {
0
} else {
64 - (n as u64).leading_zeros()
}
}
fn limit_lpc_range(a32_q17: &mut [i32]) {
for _round in 0..10 {
let mut max_idx = 0usize;
let mut maxabs_q17: i64 = a32_q17[0].unsigned_abs() as i64;
for (k, &c) in a32_q17.iter().enumerate().skip(1) {
let abs = c.unsigned_abs() as i64;
if abs > maxabs_q17 {
maxabs_q17 = abs;
max_idx = k;
}
}
let maxabs_q12 = ((maxabs_q17 + 16) >> 5).min(163838);
if maxabs_q12 <= 32767 {
return;
}
let numer = (maxabs_q12 - 32767) << 14;
let denom = (maxabs_q12 * (max_idx as i64 + 1)) >> 2;
let sc_q16_0 = 65470 - numer / denom;
bwexpander_32(a32_q17, sc_q16_0);
}
let maxabs_q17 = a32_q17
.iter()
.map(|&c| c.unsigned_abs() as i64)
.max()
.unwrap_or(0);
let maxabs_q12 = ((maxabs_q17 + 16) >> 5).min(163838);
if maxabs_q12 > 32767 {
for c in a32_q17.iter_mut() {
let q12 = (((*c as i64 + 16) >> 5).clamp(-32768, 32767)) as i32;
*c = q12 << 5;
}
}
}
fn bwexpander_32(a32_q17: &mut [i32], sc_q16_0: i64) {
let mut sc_q16_k: u64 = sc_q16_0 as u64;
for c in a32_q17.iter_mut() {
*c = ((*c as i64 * sc_q16_k as i64) >> 16) as i32;
sc_q16_k = (sc_q16_0 as u64 * sc_q16_k + 32768) >> 16;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn table27_nbmb_is_permutation_of_0_to_9() {
let mut sorted = ORDERING_NB_MB.to_vec();
sorted.sort_unstable();
assert_eq!(sorted, (0u8..10).collect::<Vec<_>>());
}
#[test]
fn table27_wb_is_permutation_of_0_to_15() {
let mut sorted = ORDERING_WB.to_vec();
sorted.sort_unstable();
assert_eq!(sorted, (0u8..16).collect::<Vec<_>>());
}
#[test]
fn table27_row_widths_match_d_lpc() {
assert_eq!(ORDERING_NB_MB.len(), D_LPC_NB_MB);
assert_eq!(ORDERING_WB.len(), D_LPC_WB);
}
#[test]
fn table27_ordering_helper_routes_per_bandwidth() {
assert_eq!(ordering(Bandwidth::Nb).unwrap(), &ORDERING_NB_MB);
assert_eq!(ordering(Bandwidth::Mb).unwrap(), &ORDERING_NB_MB);
assert_eq!(ordering(Bandwidth::Wb).unwrap(), &ORDERING_WB);
assert!(ordering(Bandwidth::Swb).is_err());
assert!(ordering(Bandwidth::Fb).is_err());
}
#[test]
fn table27_first_row_spot_checks() {
assert_eq!(ORDERING_NB_MB[0], 0);
assert_eq!(ORDERING_NB_MB[1], 9);
assert_eq!(ORDERING_NB_MB[5], 5); assert_eq!(ORDERING_NB_MB[9], 7);
assert_eq!(ORDERING_WB[0], 0);
assert_eq!(ORDERING_WB[1], 15);
assert_eq!(ORDERING_WB[8], 2);
assert_eq!(ORDERING_WB[15], 1);
}
#[test]
fn table28_has_129_entries() {
assert_eq!(COS_Q12.len(), 129);
}
#[test]
fn table28_anchor_values() {
assert_eq!(COS_Q12[0], 4096);
assert_eq!(COS_Q12[64], 0);
assert_eq!(COS_Q12[128], -4096);
}
#[test]
fn table28_anti_symmetric_about_64() {
for i in 0..=128 {
assert_eq!(
COS_Q12[128 - i],
-COS_Q12[i],
"table 28 anti-symmetry broken at i = {i}: {} vs -{}",
COS_Q12[128 - i],
COS_Q12[i]
);
}
}
#[test]
fn table28_strictly_decreasing() {
for i in 0..128 {
assert!(
COS_Q12[i] > COS_Q12[i + 1],
"table 28 not strictly decreasing at i = {i}: {} -> {}",
COS_Q12[i],
COS_Q12[i + 1]
);
}
}
#[test]
fn table28_in_q12_range() {
for (i, &v) in COS_Q12.iter().enumerate() {
assert!(
(-4096..=4096).contains(&v),
"table 28 cell {i} = {v} outside Q12 range"
);
}
}
#[test]
fn table28_spot_checks() {
assert_eq!(COS_Q12[0..4], [4096, 4095, 4091, 4085]);
assert_eq!(COS_Q12[16..20], [3784, 3745, 3703, 3659]);
assert_eq!(COS_Q12[60..64], [401, 301, 201, 101]);
assert_eq!(COS_Q12[64..68], [0, -101, -201, -301]);
assert_eq!(COS_Q12[124..128], [-4076, -4085, -4091, -4095]);
}
#[test]
fn cos_lookup_at_table_anchor_points_matches_table_entries() {
let mut nlsf = vec![0i16; D_LPC_NB_MB];
for (k, slot) in nlsf.iter_mut().enumerate() {
*slot = ((8 * k as i32) << 8) as i16;
}
let c = nlsf_to_c_q17(Bandwidth::Nb, &nlsf).unwrap();
for (k, &dest) in ORDERING_NB_MB.iter().enumerate() {
let i = 8 * k;
let expected = (COS_Q12[i] * 256 + 4) >> 3;
assert_eq!(c[dest as usize], expected, "anchor mismatch at k = {k}");
}
}
#[test]
fn cos_lookup_rejects_swb_fb() {
let nlsf = vec![0i16; D_LPC_NB_MB];
assert!(nlsf_to_c_q17(Bandwidth::Swb, &nlsf).is_err());
assert!(nlsf_to_c_q17(Bandwidth::Fb, &nlsf).is_err());
}
#[test]
fn cos_lookup_rejects_length_mismatch() {
let nlsf = vec![0i16; 16];
assert!(nlsf_to_c_q17(Bandwidth::Nb, &nlsf).is_err());
let nlsf = vec![0i16; 10];
assert!(nlsf_to_c_q17(Bandwidth::Wb, &nlsf).is_err());
}
#[test]
fn cos_lookup_linear_interp_midpoint() {
let mut nlsf = vec![0i16; D_LPC_NB_MB];
nlsf[0] = (10 << 8) | 128;
for (k, slot) in nlsf.iter_mut().enumerate().take(D_LPC_NB_MB).skip(1) {
*slot = ((20 + k as i32) << 8) as i16;
}
let c = nlsf_to_c_q17(Bandwidth::Nb, &nlsf).unwrap();
let a = COS_Q12[10];
let b = COS_Q12[11];
let expected = (a * 256 + (b - a) * 128 + 4) >> 3;
assert_eq!(c[ORDERING_NB_MB[0] as usize], expected);
}
#[test]
fn lpc_length_matches_bandwidth() {
let nb = vec![1638i16; D_LPC_NB_MB]; let mut nb_mono = nb.clone();
for (k, v) in nb_mono.iter_mut().enumerate() {
*v = (1000 + 2000 * k as i32) as i16;
}
let lpc = LpcQ17::from_nlsf(Bandwidth::Nb, &nb_mono).unwrap();
assert_eq!(lpc.len(), D_LPC_NB_MB);
assert_eq!(lpc.a32_q17().len(), D_LPC_NB_MB);
let mut wb_mono = vec![0i16; D_LPC_WB];
for (k, v) in wb_mono.iter_mut().enumerate() {
*v = (500 + 1900 * k as i32) as i16;
}
let lpc = LpcQ17::from_nlsf(Bandwidth::Wb, &wb_mono).unwrap();
assert_eq!(lpc.len(), D_LPC_WB);
assert_eq!(lpc.a32_q17().len(), D_LPC_WB);
}
#[test]
fn lpc_rejects_swb_fb_and_length_mismatch() {
let nlsf = vec![0i16; D_LPC_NB_MB];
assert!(LpcQ17::from_nlsf(Bandwidth::Swb, &nlsf).is_err());
assert!(LpcQ17::from_nlsf(Bandwidth::Fb, &nlsf).is_err());
let nlsf = vec![0i16; 12];
assert!(LpcQ17::from_nlsf(Bandwidth::Nb, &nlsf).is_err());
}
fn oracle_a32_q17(c_q17: &[i32], d_lpc: usize) -> Vec<i32> {
let d2 = d_lpc / 2;
let run_side = |parity: usize| -> Vec<i64> {
let cols = d2 + 2;
let mut p = vec![vec![0i64; cols]; d2];
p[0][0] = 1 << 16;
p[0][1] = -(c_q17[parity] as i64);
for k in 1..d2 {
let c = c_q17[2 * k + parity] as i64;
for j in 0..=k + 1 {
let pj0 = p[k - 1][j];
let pjm2 = if j >= 2 { p[k - 1][j - 2] } else { 0 };
let pjm1 = if j >= 1 { p[k - 1][j - 1] } else { 0 };
p[k][j] = pj0 + pjm2 - ((c * pjm1 + 32768) >> 16);
}
}
p.pop().unwrap()
};
let p_last = run_side(0);
let q_last = run_side(1);
let mut a = vec![0i32; d_lpc];
for k in 0..d2 {
let q_diff = q_last[k + 1] - q_last[k];
let p_sum = p_last[k + 1] + p_last[k];
a[k] = (-(q_diff + p_sum)) as i32;
a[d_lpc - k - 1] = (q_diff - p_sum) as i32;
}
a
}
fn ascending_nlsf(d_lpc: usize, start: i16, step: i16) -> Vec<i16> {
(0..d_lpc as i16)
.map(|k| start.saturating_add(k.saturating_mul(step)))
.collect()
}
#[test]
fn lpc_matches_oracle_nb() {
let nlsf = ascending_nlsf(D_LPC_NB_MB, 1500, 2700); let lpc = LpcQ17::from_nlsf(Bandwidth::Nb, &nlsf).unwrap();
let c = nlsf_to_c_q17(Bandwidth::Nb, &nlsf).unwrap();
let expected = oracle_a32_q17(&c[..D_LPC_NB_MB], D_LPC_NB_MB);
assert_eq!(lpc.a32_q17(), expected.as_slice());
}
#[test]
fn lpc_matches_oracle_wb() {
let nlsf = ascending_nlsf(D_LPC_WB, 800, 1900); let lpc = LpcQ17::from_nlsf(Bandwidth::Wb, &nlsf).unwrap();
let c = nlsf_to_c_q17(Bandwidth::Wb, &nlsf).unwrap();
let expected = oracle_a32_q17(&c[..D_LPC_WB], D_LPC_WB);
assert_eq!(lpc.a32_q17(), expected.as_slice());
}
#[test]
fn lpc_matches_oracle_against_real_decoder_pipeline() {
use crate::range_decoder::RangeDecoder;
use crate::silk_lsf_recon::NlsfReconstructed;
use crate::silk_lsf_stabilize::NlsfStabilized;
use crate::silk_lsf_stage2::LsfStage2;
let buf = [
0x5Au8, 0xC3, 0x17, 0x9E, 0x42, 0xFB, 0x08, 0x71, 0x2D, 0xB6, 0x4C, 0x8E,
];
for bw in [Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb] {
for i1 in 0u8..32 {
let mut rd = RangeDecoder::new(&buf);
let stage2 = LsfStage2::decode(&mut rd, bw, i1).expect("stage-2");
let recon =
NlsfReconstructed::from_stage1_and_stage2(bw, i1, &stage2).expect("recon");
let stab = NlsfStabilized::from_reconstructed(bw, &recon).expect("stab");
let lpc = LpcQ17::from_nlsf(bw, stab.nlsf_q15()).unwrap();
let c = nlsf_to_c_q17(bw, stab.nlsf_q15()).unwrap();
let d_lpc = stab.nlsf_q15().len();
let expected = oracle_a32_q17(&c[..d_lpc], d_lpc);
assert_eq!(
lpc.a32_q17(),
expected.as_slice(),
"production/oracle divergence: bw={bw:?} i1={i1}"
);
}
}
}
#[test]
fn lpc_leading_term_uses_full_row_sum() {
let nlsf = ascending_nlsf(D_LPC_NB_MB, 1500, 2700);
let lpc = LpcQ17::from_nlsf(Bandwidth::Nb, &nlsf).unwrap();
let a = lpc.a32_q17();
assert_eq!((a[0] - a[D_LPC_NB_MB - 1]) % 2, 0);
assert_eq!((a[0] + a[D_LPC_NB_MB - 1]) % 2, 0);
}
fn oracle_range_limited(input: &[i32]) -> Vec<i32> {
let mut a: Vec<i64> = input.iter().map(|&c| c as i64).collect();
let d = a.len();
let maxabs_q12_of = |a: &[i64]| -> (usize, i64) {
let mut idx = 0usize;
let mut best = a[0].abs();
for (k, &c) in a.iter().enumerate().skip(1) {
if c.abs() > best {
best = c.abs();
idx = k;
}
}
let q12 = ((best + 16) >> 5).min(163838);
(idx, q12)
};
for _ in 0..10 {
let (k, q12) = maxabs_q12_of(&a);
if q12 <= 32767 {
return a.iter().map(|&c| c as i32).collect();
}
let numer = (q12 - 32767) << 14;
let denom = (q12 * (k as i64 + 1)) >> 2;
let sc0 = 65470 - numer / denom;
let mut sc_k: i128 = sc0 as i128;
for c in a.iter_mut() {
*c = ((*c as i128 * sc_k) >> 16) as i64;
sc_k = (sc0 as i128 * sc_k + 32768) >> 16;
}
}
let (_, q12_after) = maxabs_q12_of(&a);
if q12_after > 32767 {
for c in a.iter_mut() {
let q12 = ((*c + 16) >> 5).clamp(-32768, 32767);
*c = q12 << 5;
}
}
debug_assert_eq!(a.len(), d);
a.iter().map(|&c| c as i32).collect()
}
fn assert_fits_q12(a: &[i32]) {
for (k, &c) in a.iter().enumerate() {
let q12 = (c as i64 + 16) >> 5;
assert!(
(-32768..=32767).contains(&q12),
"coeff {k} = {c} does not fit Q12 (q12 = {q12})"
);
}
}
#[test]
fn range_limit_leaves_small_coeffs_untouched() {
let nlsf = ascending_nlsf(D_LPC_NB_MB, 1500, 2700);
let lpc = LpcQ17::from_nlsf(Bandwidth::Nb, &nlsf).unwrap();
let raw = lpc.a32_q17().to_vec();
let maxabs = raw.iter().map(|&c| (c as i64).abs()).max().unwrap();
let maxabs_q12 = ((maxabs + 16) >> 5).min(163838);
let limited = lpc.range_limited();
if maxabs_q12 <= 32767 {
assert_eq!(limited.a32_q17(), raw.as_slice());
}
assert_fits_q12(limited.a32_q17());
}
#[test]
fn range_limit_matches_oracle_on_synthetic_overflow() {
let cases: &[[i32; D_LPC_NB_MB]] = &[
[1_100_000, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 2_500_000, -100_000, 0, 0, 0, 0, 0],
[
3_000_000, -2_800_000, 2_600_000, -2_400_000, 2_200_000, -2_000_000, 1_800_000,
-1_600_000, 1_400_000, -1_200_000,
],
[1_200_000, -1_150_000, 0, 0, 0, 0, 0, 0, 0, 0],
];
for case in cases {
let lpc = LpcQ17 {
len: D_LPC_NB_MB as u8,
a32_q17: {
let mut a = [0i32; D_LPC_MAX];
a[..D_LPC_NB_MB].copy_from_slice(case);
a
},
};
let limited = lpc.range_limited();
let expected = oracle_range_limited(case);
assert_eq!(
limited.a32_q17(),
expected.as_slice(),
"production/oracle divergence on {case:?}"
);
assert_fits_q12(limited.a32_q17());
}
}
#[test]
fn range_limit_extreme_input_at_maxabs_cap_converges() {
let huge = 163838i64 << 5; let mut a = [0i32; D_LPC_MAX];
a[0] = huge as i32;
a[5] = -(huge as i32);
a[9] = (huge as i32) / 2;
let lpc = LpcQ17 {
len: D_LPC_NB_MB as u8,
a32_q17: a,
};
let limited = lpc.range_limited();
let expected = oracle_range_limited(&a[..D_LPC_NB_MB]);
assert_eq!(limited.a32_q17(), expected.as_slice());
assert_fits_q12(limited.a32_q17());
}
#[test]
fn range_limit_post_loop_saturation_formula() {
let saturate = |c: i64| -> i32 {
let q12 = ((c + 16) >> 5).clamp(-32768, 32767);
(q12 << 5) as i32
};
assert_eq!(saturate(32767i64 << 5), 32767 << 5);
assert_eq!(saturate((32767i64 << 5) + (1 << 5)), 32767 << 5);
assert_eq!(saturate(i32::MAX as i64), 32767 << 5);
assert_eq!(saturate(-(32768i64 << 5) - (1 << 5)), -32768 << 5);
assert_eq!(saturate(i32::MIN as i64), -32768 << 5);
assert_eq!(saturate(0), 0);
}
#[test]
fn range_limit_handles_i32_min_without_panic() {
let mut a = [0i32; D_LPC_MAX];
a[0] = i32::MIN;
a[3] = i32::MAX;
let lpc = LpcQ17 {
len: D_LPC_NB_MB as u8,
a32_q17: a,
};
let limited = lpc.range_limited();
assert_fits_q12(limited.a32_q17());
let expected = oracle_range_limited(&a[..D_LPC_NB_MB]);
assert_eq!(limited.a32_q17(), expected.as_slice());
}
#[test]
fn range_limit_real_pipeline_fits_q12_across_bandwidth_x_i1_sweep() {
use crate::range_decoder::RangeDecoder;
use crate::silk_lsf_recon::NlsfReconstructed;
use crate::silk_lsf_stabilize::NlsfStabilized;
use crate::silk_lsf_stage2::LsfStage2;
let bufs: &[&[u8]] = &[
&[
0x5A, 0xC3, 0x17, 0x9E, 0x42, 0xFB, 0x08, 0x71, 0x2D, 0xB6, 0x4C, 0x8E,
],
&[
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
],
];
for buf in bufs {
for bw in [Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb] {
for i1 in 0u8..32 {
let mut rd = RangeDecoder::new(buf);
let stage2 = LsfStage2::decode(&mut rd, bw, i1).expect("stage-2");
let recon =
NlsfReconstructed::from_stage1_and_stage2(bw, i1, &stage2).expect("recon");
let stab = NlsfStabilized::from_reconstructed(bw, &recon).expect("stab");
let lpc = LpcQ17::from_nlsf(bw, stab.nlsf_q15()).unwrap();
let limited = lpc.range_limited();
assert_eq!(limited.len(), lpc.len());
assert_fits_q12(limited.a32_q17());
let expected = oracle_range_limited(lpc.a32_q17());
assert_eq!(
limited.a32_q17(),
expected.as_slice(),
"production/oracle divergence: bw={bw:?} i1={i1}"
);
}
}
}
}
fn oracle_is_stable(a32_q12: &[i32]) -> bool {
let d = a32_q12.len();
let dc: i64 = a32_q12.iter().map(|&c| c as i64).sum();
if dc > 4096 {
return false;
}
let mut a = vec![vec![0i64; d]; d];
for n in 0..d {
a[d - 1][n] = (a32_q12[n] as i64) << 12;
}
let mut inv_gain_q30: i64 = 1 << 30;
for k in (0..d).rev() {
let akk = a[k][k];
if akk.abs() > 16_773_022 {
return false;
}
let rc_q31 = -akk << 7;
let div_q30 = (1i64 << 30) - ((rc_q31 * rc_q31) >> 32);
inv_gain_q30 = ((inv_gain_q30 * div_q30) >> 32) << 2;
if inv_gain_q30 < 107_374 {
return false;
}
if k > 0 {
let b1 = {
if div_q30 <= 0 {
0i64
} else {
(64 - (div_q30 as u64).leading_zeros()) as i64
}
};
let b2 = b1 - 16;
let inv_qb2 = ((1i64 << 29) - 1) / (div_q30 >> (b2 + 1));
let err_q29 = (1i64 << 29) - (((div_q30 << (15 - b2)) * inv_qb2) >> 16);
let gain_qb1 = (inv_qb2 << 16) + ((err_q29 * inv_qb2) >> 13);
for n in 0..k {
let num_q24 = a[k][n] - (((a[k][k - n - 1] * rc_q31) + (1i64 << 30)) >> 31);
a[k - 1][n] = ((num_q24 * gain_qb1) + (1i64 << (b1 - 1))) >> b1;
}
}
}
true
}
#[test]
fn stability_check_agrees_with_oracle_on_hand_built_filters() {
let cases: &[&[i32]] = &[
&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
&[400, -200, 100, -50, 25, -12, 6, -3, 1, 0],
&[4096, 0, 0, 0, 0, 0, 0, 0, 0, 0],
&[4097, 0, 0, 0, 0, 0, 0, 0, 0, 0],
&[1500, -1200, 900, -600, 300, -150, 75, -30, 10, -5],
];
for case in cases {
assert_eq!(
is_lpc_stable(case),
oracle_is_stable(case),
"stability divergence on {case:?}"
);
}
}
#[test]
fn stability_check_zero_filter_is_stable() {
assert!(is_lpc_stable(&[0i32; D_LPC_NB_MB]));
assert!(is_lpc_stable(&[0i32; D_LPC_WB]));
}
#[test]
fn stability_check_dc_response_over_4096_is_unstable() {
let mut a = [0i32; D_LPC_NB_MB];
a[0] = 4097;
assert!(!is_lpc_stable(&a));
let mut b = [0i32; D_LPC_NB_MB];
b[0] = 4096;
assert_eq!(is_lpc_stable(&b), oracle_is_stable(&b));
}
#[test]
fn pred_gain_limit_stable_input_passes_through_in_zero_rounds() {
let nlsf = ascending_nlsf(D_LPC_NB_MB, 1500, 2700);
let limited = LpcQ17::from_nlsf(Bandwidth::Nb, &nlsf)
.unwrap()
.range_limited();
let pg = limited.prediction_gain_limited();
if pg.rounds() == 0 {
let expected: Vec<i32> = limited.a32_q17().iter().map(|&c| (c + 16) >> 5).collect();
assert_eq!(pg.a_q12(), expected.as_slice());
}
assert!(is_lpc_stable(pg.a_q12()));
assert_eq!(pg.len(), D_LPC_NB_MB);
}
#[test]
fn pred_gain_limit_always_emits_a_stable_filter() {
let cases: &[[i32; D_LPC_NB_MB]] = &[
[4096 << 5, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[
3500 << 5,
-3400 << 5,
3300 << 5,
-3200 << 5,
3100 << 5,
-3000 << 5,
2900 << 5,
-2800 << 5,
2700 << 5,
-2600 << 5,
],
[4000 << 5, 4000 << 5, 4000 << 5, 0, 0, 0, 0, 0, 0, 0],
];
for case in cases {
let lpc = LpcQ17 {
len: D_LPC_NB_MB as u8,
a32_q17: {
let mut a = [0i32; D_LPC_MAX];
a[..D_LPC_NB_MB].copy_from_slice(case);
a
},
};
let pg = lpc.prediction_gain_limited();
assert!(
is_lpc_stable(pg.a_q12()),
"emitted unstable filter for input {case:?} (rounds={})",
pg.rounds()
);
assert!(pg.rounds() <= 16);
}
}
#[test]
fn pred_gain_limit_round15_zeroes_a_persistently_unstable_filter() {
let mut a = [0i32; D_LPC_MAX];
a[0] = 32767 << 5; a[1] = 32767 << 5;
let lpc = LpcQ17 {
len: D_LPC_NB_MB as u8,
a32_q17: a,
};
let pg = lpc.prediction_gain_limited();
assert!(is_lpc_stable(pg.a_q12()));
if pg.rounds() == 16 {
assert!(pg.a_q12().iter().all(|&c| c == 0));
}
}
#[test]
fn pred_gain_limit_emitted_q12_fits_signed_16bit() {
let nlsf = ascending_nlsf(D_LPC_WB, 800, 1900);
let pg = LpcQ17::from_nlsf(Bandwidth::Wb, &nlsf)
.unwrap()
.range_limited()
.prediction_gain_limited();
for (k, &c) in pg.a_q12().iter().enumerate() {
assert!(
(i16::MIN as i32..=i16::MAX as i32).contains(&c),
"a_Q12[{k}] = {c} does not fit i16"
);
}
}
#[test]
fn pred_gain_limit_real_pipeline_stable_across_bandwidth_x_i1_sweep() {
use crate::range_decoder::RangeDecoder;
use crate::silk_lsf_recon::NlsfReconstructed;
use crate::silk_lsf_stabilize::NlsfStabilized;
use crate::silk_lsf_stage2::LsfStage2;
let bufs: &[&[u8]] = &[
&[
0x5A, 0xC3, 0x17, 0x9E, 0x42, 0xFB, 0x08, 0x71, 0x2D, 0xB6, 0x4C, 0x8E,
],
&[
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
],
&[
0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0xFE, 0xDC, 0xBA, 0x98,
],
];
for buf in bufs {
for bw in [Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb] {
for i1 in 0u8..32 {
let mut rd = RangeDecoder::new(buf);
let stage2 = LsfStage2::decode(&mut rd, bw, i1).expect("stage-2");
let recon =
NlsfReconstructed::from_stage1_and_stage2(bw, i1, &stage2).expect("recon");
let stab = NlsfStabilized::from_reconstructed(bw, &recon).expect("stab");
let pg = LpcQ17::from_nlsf(bw, stab.nlsf_q15())
.unwrap()
.range_limited()
.prediction_gain_limited();
assert_eq!(pg.len(), stab.nlsf_q15().len());
assert!(pg.rounds() <= 16);
assert!(
is_lpc_stable(pg.a_q12()),
"unstable Q12 filter: bw={bw:?} i1={i1} rounds={}",
pg.rounds()
);
assert_eq!(
is_lpc_stable(pg.a_q12()),
oracle_is_stable(pg.a_q12()),
"stability classification divergence: bw={bw:?} i1={i1}"
);
}
}
}
}
#[test]
fn ilog64_matches_spec_definition() {
assert_eq!(ilog64(-1), 0);
assert_eq!(ilog64(0), 0);
assert_eq!(ilog64(1), 1);
assert_eq!(ilog64(2), 2);
assert_eq!(ilog64(3), 2);
assert_eq!(ilog64(4), 3);
assert_eq!(ilog64(7), 3);
assert_eq!(ilog64(1 << 30), 31);
assert_eq!(ilog64((1 << 30) - 1), 30);
}
#[test]
fn lpc_real_pipeline_does_not_panic_across_bandwidth_x_i1_sweep() {
use crate::range_decoder::RangeDecoder;
use crate::silk_lsf_recon::NlsfReconstructed;
use crate::silk_lsf_stabilize::NlsfStabilized;
use crate::silk_lsf_stage2::LsfStage2;
let bufs: &[&[u8]] = &[
&[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC,
],
&[
0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0xFE, 0xDC, 0xBA, 0x98,
],
&[
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
],
];
for buf in bufs {
for bw in [Bandwidth::Nb, Bandwidth::Mb, Bandwidth::Wb] {
for i1 in 0u8..32 {
let mut rd = RangeDecoder::new(buf);
let stage2 = LsfStage2::decode(&mut rd, bw, i1).expect("stage-2");
let recon =
NlsfReconstructed::from_stage1_and_stage2(bw, i1, &stage2).expect("recon");
let stab = NlsfStabilized::from_reconstructed(bw, &recon).expect("stab");
let lpc = LpcQ17::from_nlsf(bw, stab.nlsf_q15()).unwrap();
assert_eq!(lpc.len(), stab.nlsf_q15().len());
}
}
}
}
}