use super::autocorr::LP_ORDER;
use super::isf_tables::{ACOS_SLOPE, COS_TABLE};
use super::isp_to_lp::isp_to_lp;
use crate::fixed_point::arith::{add, extract_l, round, sub};
use crate::fixed_point::arith32::{l_mac, l_mult};
use crate::fixed_point::shift::{l_shl, l_shr, shl, shr};
use crate::fixed_point::types::{DspContext, Word16};
pub const NB_SUBFR: usize = 4;
pub const INTERPOL_FRAC: [Word16; NB_SUBFR] =
[Word16(14746), Word16(26214), Word16(31457), Word16(32767)];
#[must_use]
pub fn isp_to_isf(isp: &[Word16; LP_ORDER]) -> [Word16; LP_ORDER] {
let mut ctx = DspContext::default();
let mut isf = [Word16(0); LP_ORDER];
let mut ind = 127usize;
for i in (0..LP_ORDER).rev() {
if i >= LP_ORDER - 2 {
ind = 127;
}
while Word16(COS_TABLE[ind]).0 < isp[i].0 {
ind -= 1;
}
let delta = sub(&mut ctx, isp[i], Word16(COS_TABLE[ind]));
let interp = l_mult(&mut ctx, delta, Word16(ACOS_SLOPE[ind]));
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let base = shl(&mut ctx, Word16(ind as i16), 7);
let scaled = l_shl(&mut ctx, interp, 4);
let frac = round(&mut ctx, scaled);
isf[i] = add(&mut ctx, frac, base);
}
isf[LP_ORDER - 1] = shr(&mut ctx, isf[LP_ORDER - 1], 1);
isf
}
pub fn isf_to_isp_in_place(ctx: &mut DspContext, isf: &mut [Word16]) {
let m = isf.len();
isf[m - 1] = shl(ctx, isf[m - 1], 1);
for slot in isf.iter_mut() {
let ind =
usize::try_from(shr(ctx, *slot, 7).0).expect("ISFs are non-negative by construction");
let offset = Word16(slot.0 & 0x007f);
let step = sub(ctx, Word16(COS_TABLE[ind + 1]), Word16(COS_TABLE[ind]));
let interp = l_mult(ctx, step, offset);
let shifted = l_shr(ctx, interp, 8);
*slot = add(ctx, Word16(COS_TABLE[ind]), extract_l(shifted));
}
}
#[must_use]
pub fn isf_to_isp(isf: &[Word16; LP_ORDER]) -> [Word16; LP_ORDER] {
let mut ctx = DspContext::default();
let mut isp = [Word16(0); LP_ORDER];
isp[..LP_ORDER - 1].copy_from_slice(&isf[..LP_ORDER - 1]);
isp[LP_ORDER - 1] = shl(&mut ctx, isf[LP_ORDER - 1], 1);
for slot in &mut isp {
let ind = usize::try_from(shr(&mut ctx, *slot, 7).0)
.expect("ISFs are non-negative by construction");
let offset = Word16(slot.0 & 0x007f);
let step = sub(&mut ctx, Word16(COS_TABLE[ind + 1]), Word16(COS_TABLE[ind]));
let interp = l_mult(&mut ctx, step, offset);
let shifted = l_shr(&mut ctx, interp, 8);
*slot = add(&mut ctx, Word16(COS_TABLE[ind]), extract_l(shifted));
}
isp
}
#[must_use]
pub fn interpolate_isp(
isp_old: &[Word16; LP_ORDER],
isp_new: &[Word16; LP_ORDER],
) -> [[Word16; LP_ORDER + 1]; NB_SUBFR] {
let mut ctx = DspContext::default();
let mut az = [[Word16(0); LP_ORDER + 1]; NB_SUBFR];
for (k, frame) in az.iter_mut().enumerate().take(NB_SUBFR - 1) {
let fac_new = INTERPOL_FRAC[k];
let complement = sub(&mut ctx, Word16(32767), fac_new);
let fac_old = add(&mut ctx, complement, Word16(1));
let mut isp = [Word16(0); LP_ORDER];
for (i, slot) in isp.iter_mut().enumerate() {
let acc = l_mult(&mut ctx, isp_old[i], fac_old);
let acc = l_mac(&mut ctx, acc, isp_new[i], fac_new);
*slot = round(&mut ctx, acc);
}
*frame = isp_to_lp(&isp);
}
az[NB_SUBFR - 1] = isp_to_lp(isp_new);
az
}
#[cfg(test)]
mod tests {
use super::super::isp_to_lp::tests_support::{case_count, row};
use super::*;
fn vector(case: usize, label: &str) -> [Word16; LP_ORDER] {
let values = row(case, label);
assert_eq!(values.len(), LP_ORDER, "case {case}: {label} length");
let mut out = [Word16(0); LP_ORDER];
for (slot, &v) in out.iter_mut().zip(values.iter()) {
*slot = Word16(v);
}
out
}
#[test]
fn isp_to_isf_is_bit_exact_against_ts26173() {
for case in 0..case_count() {
let got = isp_to_isf(&vector(case, "isp"));
let want = row(case, "isf");
for i in 0..LP_ORDER {
assert_eq!(
got[i].0, want[i],
"case {case}: isf[{i}] = {} but the reference gives {}",
got[i].0, want[i]
);
}
}
}
#[test]
fn isf_to_isp_is_bit_exact_against_ts26173() {
for case in 0..case_count() {
let got = isf_to_isp(&vector(case, "isf"));
let want = row(case, "isp_rt");
for i in 0..LP_ORDER {
assert_eq!(
got[i].0, want[i],
"case {case}: isp[{i}] = {} but the reference gives {}",
got[i].0, want[i]
);
}
}
}
#[test]
fn interpolation_is_bit_exact_against_ts26173() {
for case in 0..case_count() {
let old = reset_state_isp();
let got = interpolate_isp(&old, &vector(case, "isp"));
for (k, subframe) in got.iter().enumerate() {
let want = row(case, &format!("az_int{k}"));
assert_eq!(want.len(), LP_ORDER + 1, "case {case}: az_int{k} length");
for i in 0..=LP_ORDER {
assert_eq!(
subframe[i].0, want[i],
"case {case} subframe {k}: a[{i}] = {} but the reference gives {}",
subframe[i].0, want[i]
);
}
}
}
}
fn reset_state_isp() -> [Word16; LP_ORDER] {
let mut isp = [Word16(0); LP_ORDER];
for (i, slot) in isp.iter_mut().enumerate() {
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
let v = (f64::cos(std::f64::consts::PI * (i + 1) as f64 / (LP_ORDER + 1) as f64)
* 32767.0) as i16;
*slot = Word16(v);
}
isp
}
#[test]
fn isfs_are_ordered_and_within_range() {
for case in 0..case_count() {
let isf = isp_to_isf(&vector(case, "isp"));
for i in 1..LP_ORDER - 1 {
assert!(
isf[i].0 > isf[i - 1].0,
"case {case}: isf[{i}] = {} does not exceed isf[{}] = {}",
isf[i].0,
i - 1,
isf[i - 1].0
);
}
assert!(isf[0].0 >= 0, "case {case}: first ISF is negative");
}
}
#[test]
fn the_last_subframe_skips_the_blend() {
for case in 0..case_count() {
let new = vector(case, "isp");
let az = interpolate_isp(&reset_state_isp(), &new);
assert_eq!(az[NB_SUBFR - 1], isp_to_lp(&new), "case {case}");
}
}
#[test]
fn interpolation_moves_monotonically_toward_the_new_frame() {
for case in 0..case_count() {
let old = reset_state_isp();
let new = vector(case, "isp");
let az = interpolate_isp(&old, &new);
let target = isp_to_lp(&new);
let distance = |a: &[Word16; LP_ORDER + 1]| -> i64 {
a.iter()
.zip(target.iter())
.map(|(x, y)| i64::from(x.0 - y.0).pow(2))
.sum()
};
for k in 1..NB_SUBFR {
assert!(
distance(&az[k]) <= distance(&az[k - 1]),
"case {case}: subframe {k} is further from the new frame than {}",
k - 1
);
}
}
}
}