use crate::silk::decoder_set_fs::MAX_SUB_FRAME_LENGTH;
use crate::silk::lpc_analysis_filter::lpc_analysis_filter;
use crate::silk::sum_sqr_shift::sum_sqr_shift;
use crate::silk::{MAX_LPC_ORDER, MAX_NB_SUBFR};
const HALF_MAX_SUBFR: usize = MAX_NB_SUBFR / 2;
const MAX_RESIDUAL: usize = HALF_MAX_SUBFR * (MAX_LPC_ORDER + MAX_SUB_FRAME_LENGTH);
#[allow(clippy::too_many_arguments)]
pub fn residual_energy(
nrgs: &mut [i32; MAX_NB_SUBFR],
nrgs_q: &mut [i32; MAX_NB_SUBFR],
input: &[i16],
coeffs_q12: &[[i16; MAX_LPC_ORDER]; HALF_MAX_SUBFR],
gains_q16: &[i32; MAX_NB_SUBFR],
subfr_length: usize,
nb_subfr: usize,
lpc_order: usize,
arch: i32,
) {
let _ = arch;
assert!(
nb_subfr == MAX_NB_SUBFR || nb_subfr == HALF_MAX_SUBFR,
"nb_subfr must be {} or {}",
MAX_NB_SUBFR,
HALF_MAX_SUBFR
);
assert!(
subfr_length > 0 && subfr_length <= MAX_SUB_FRAME_LENGTH,
"invalid subframe length {subfr_length}"
);
assert!(
(6..=MAX_LPC_ORDER).contains(&lpc_order) && lpc_order.is_multiple_of(2),
"LPC order must be an even number within 6..={MAX_LPC_ORDER}"
);
let offset = lpc_order + subfr_length;
assert!(
offset <= MAX_LPC_ORDER + MAX_SUB_FRAME_LENGTH,
"offset exceeds stack buffer"
);
let block_len = HALF_MAX_SUBFR * offset;
let total_needed = (nb_subfr >> 1) * block_len;
assert!(
input.len() >= total_needed,
"input slice too short: need {total_needed} samples"
);
let mut lpc_res = [0i16; MAX_RESIDUAL];
let mut input_idx = 0;
for (half, coeffs) in coeffs_q12.iter().enumerate().take(nb_subfr >> 1) {
let res_slice = &mut lpc_res[..block_len];
let window = &input[input_idx..input_idx + block_len];
lpc_analysis_filter(
res_slice,
window,
&coeffs[..lpc_order],
block_len,
lpc_order,
);
let mut res_ptr = lpc_order;
for j in 0..HALF_MAX_SUBFR {
let idx = half * HALF_MAX_SUBFR + j;
let start = res_ptr;
let end = start + subfr_length;
let (energy, shift) = sum_sqr_shift(&res_slice[start..end]);
nrgs[idx] = energy;
nrgs_q[idx] = -shift;
res_ptr += offset;
}
input_idx += block_len;
}
for i in 0..nb_subfr {
let lz1 = clz32(nrgs[i]) - 1;
let lz2 = clz32(gains_q16[i]) - 1;
let gain_shifted = lshift32(gains_q16[i], lz2);
let gain_squared = smmul(gain_shifted, gain_shifted);
nrgs[i] = smmul(gain_squared, lshift32(nrgs[i], lz1));
nrgs_q[i] += lz1 + (lz2 * 2) - 64;
}
}
fn clz32(value: i32) -> i32 {
if value == 0 {
32
} else {
(value as u32).leading_zeros() as i32
}
}
fn lshift32(value: i32, shift: i32) -> i32 {
debug_assert!(shift >= 0);
value.wrapping_shl(shift as u32)
}
fn smmul(a: i32, b: i32) -> i32 {
((i64::from(a) * i64::from(b)) >> 32) as i32
}
#[cfg(test)]
mod tests {
use super::residual_energy;
use crate::silk::{MAX_LPC_ORDER, MAX_NB_SUBFR};
use alloc::vec::Vec;
#[test]
fn computes_residuals_for_four_subframes() {
const LPC_ORDER: usize = 10;
const SUBFR: usize = 40;
const NB_SUBFR: usize = MAX_NB_SUBFR;
let mut nrgs = [0; MAX_NB_SUBFR];
let mut nrgs_q = [0; MAX_NB_SUBFR];
let input = test_input(NB_SUBFR, LPC_ORDER, SUBFR, -16_000, 1_103_515_245, 12_345);
let coeffs = test_coeffs(1234, 1);
let gains = [65_536, 98_304, 147_456, 229_376];
residual_energy(
&mut nrgs,
&mut nrgs_q,
&input,
&coeffs,
&gains,
SUBFR,
NB_SUBFR,
LPC_ORDER,
0,
);
assert_eq!(
nrgs[..NB_SUBFR],
[123_386_601, 283_447_072, 85_303_613, 347_417_940]
);
assert_eq!(nrgs_q[..NB_SUBFR], [-40, -40, -43, -42]);
}
#[test]
fn handles_two_subframe_mode() {
const LPC_ORDER: usize = 12;
const SUBFR: usize = 20;
const NB_SUBFR: usize = MAX_NB_SUBFR / 2;
let mut nrgs = [0; MAX_NB_SUBFR];
let mut nrgs_q = [0; MAX_NB_SUBFR];
let input = test_input(
NB_SUBFR,
LPC_ORDER,
SUBFR,
123_456_789,
1_664_525,
1_013_904_223,
);
let coeffs = test_coeffs(321, 2);
let gains = [65_536, 131_072, 98_304, 81_920];
residual_energy(
&mut nrgs,
&mut nrgs_q,
&input,
&coeffs,
&gains,
SUBFR,
NB_SUBFR,
LPC_ORDER,
0,
);
assert_eq!(nrgs[..NB_SUBFR], [67_666_475, 128_629_840]);
assert_eq!(nrgs_q[..NB_SUBFR], [-40, -41]);
}
fn test_input(
nb_subfr: usize,
lpc_order: usize,
subfr_length: usize,
mut seed: i64,
mult: i64,
incr: i64,
) -> Vec<i16> {
let total = nb_subfr * (lpc_order + subfr_length);
let mut out = Vec::with_capacity(total);
for _ in 0..total {
seed = (seed.wrapping_mul(mult).wrapping_add(incr)) & 0x7FFF_FFFF;
let sample = ((seed >> 15) & 0xFFFF) as i64 - 32_768;
out.push(sample as i16);
}
out
}
fn test_coeffs(scale: i32, stride: i32) -> [[i16; MAX_LPC_ORDER]; super::HALF_MAX_SUBFR] {
let mut coeffs = [[0i16; MAX_LPC_ORDER]; super::HALF_MAX_SUBFR];
for (row_idx, row) in coeffs.iter_mut().enumerate() {
for (k, coeff) in row.iter_mut().enumerate() {
let raw = ((row_idx as i32 + stride) * (k as i32 + 2) * scale) & 0x3FFF;
*coeff = if raw & 0x2000 != 0 {
(raw as i32 - 0x4000) as i16
} else {
raw as i16
};
}
}
coeffs
}
}