use super::decoder_tables::{
COS_TABLE, DICO1_LSF_3, DICO1_LSF_5, DICO2_LSF_3, DICO2_LSF_5, DICO3_LSF_3, DICO3_LSF_5,
DICO4_LSF_5, DICO5_LSF_5, MEAN_LSF_3, MEAN_LSF_5, MR515_3_LSF, MR795_1_LSF, PAST_RQ_INIT,
PRED_FAC_3,
};
use crate::fixed_point::arith::{add, extract_l, mult, negate, sub};
use crate::fixed_point::arith32::{l_add, l_msu, l_mult, l_sub};
use crate::fixed_point::oper32::{l_extract, mpy_32_16};
use crate::fixed_point::shift::{l_shl, l_shr, l_shr_r, shr};
use crate::fixed_point::types::{DspContext, Word16, Word32};
pub const M: usize = 10;
pub const MP1: usize = M + 1;
pub const NB_SUBFR: usize = 4;
pub const AZ_SIZE: usize = NB_SUBFR * MP1;
const LSF_GAP: Word16 = Word16(205);
const ALPHA_3: Word16 = Word16(29491);
const ONE_ALPHA_3: Word16 = Word16(3277);
const ALPHA_5: Word16 = Word16(31128);
const ONE_ALPHA_5: Word16 = Word16(1639);
const LSP_PRED_FAC_MR122: Word16 = Word16(21299);
const SPLITS_5: [(&[i16], usize); 5] = [
(&DICO1_LSF_5, 0),
(&DICO2_LSF_5, 2),
(&DICO3_LSF_5, 4),
(&DICO4_LSF_5, 6),
(&DICO5_LSF_5, 8),
];
const SIGNED_SPLIT_5: usize = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Books {
Standard,
Reduced,
Wide,
}
impl Books {
const fn for_mode(mode_index: u8) -> Self {
match mode_index {
0 | 1 => Self::Reduced,
5 => Self::Wide,
_ => Self::Standard,
}
}
const fn first(self) -> &'static [i16] {
match self {
Self::Wide => &MR795_1_LSF,
_ => &DICO1_LSF_3,
}
}
const fn third(self) -> &'static [i16] {
match self {
Self::Reduced => &MR515_3_LSF,
_ => &DICO3_LSF_3,
}
}
}
#[derive(Debug, Clone)]
pub struct LsfDecoder {
past_residual: [Word16; M],
past_lsf: [Word16; M],
}
impl Default for LsfDecoder {
fn default() -> Self {
Self::new()
}
}
impl LsfDecoder {
#[must_use]
pub fn new() -> Self {
let mut past_residual = [Word16(0); M];
for (slot, &v) in past_residual.iter_mut().zip(PAST_RQ_INIT.iter()) {
*slot = Word16(v);
}
Self {
past_residual,
past_lsf: MEAN_LSF_3.map(Word16),
}
}
#[must_use]
pub fn at_reset() -> Self {
Self {
past_residual: [Word16(0); M],
past_lsf: MEAN_LSF_5.map(Word16),
}
}
pub fn seed_predictor(&mut self, index: u16) {
assert!(index < 8, "past_rq_init holds eight vectors, not {index}");
let base = usize::from(index) * M;
for (slot, &v) in self
.past_residual
.iter_mut()
.zip(&PAST_RQ_INIT[base..base + M])
{
*slot = Word16(v);
}
}
pub const fn clear_predictor(&mut self) {
self.past_residual = [Word16(0); M];
}
pub const fn set_last_lsf(&mut self, lsf: [Word16; M]) {
self.past_lsf = lsf;
}
pub fn decode_sid(&mut self, indices: &[u16]) -> [Word16; M] {
assert!(indices.len() >= 3, "a SID spectrum is three indices");
let mut ctx = DspContext::default();
let books = Books::Standard;
let mut residual = [Word16(0); M];
let base = usize::from(indices[0]) * 3;
for (i, slot) in residual[0..3].iter_mut().enumerate() {
*slot = Word16(books.first()[base + i]);
}
let base = usize::from(indices[1]) * 3;
for (i, slot) in residual[3..6].iter_mut().enumerate() {
*slot = Word16(DICO2_LSF_3[base + i]);
}
let base = usize::from(indices[2]) * 4;
for (i, slot) in residual[6..10].iter_mut().enumerate() {
*slot = Word16(books.third()[base + i]);
}
let mut lsf = [Word16(0); M];
for i in 0..M {
let anchor = add(&mut ctx, Word16(MEAN_LSF_3[i]), self.past_residual[i]);
lsf[i] = add(&mut ctx, residual[i], anchor);
self.past_residual[i] = residual[i];
}
reorder_lsf(&mut ctx, &mut lsf, LSF_GAP);
self.past_lsf = lsf;
lsf_to_lsp(&mut ctx, &lsf)
}
pub fn decode(&mut self, mode_index: u8, indices: &[u16], bad_frame: bool) -> [Word16; M] {
assert!(mode_index < 7, "12.2 kbit/s decodes through decode_pair");
let mut ctx = DspContext::default();
let mut lsf = if bad_frame {
self.conceal(&mut ctx)
} else {
assert!(
indices.len() >= 3,
"the 3-split quantiser needs three indices"
);
self.decode_good(&mut ctx, mode_index, indices)
};
reorder_lsf(&mut ctx, &mut lsf, LSF_GAP);
self.past_lsf = lsf;
lsf_to_lsp(&mut ctx, &lsf)
}
#[must_use]
pub const fn last_lsf(&self) -> &[Word16; M] {
&self.past_lsf
}
fn decode_good(
&mut self,
ctx: &mut DspContext,
mode_index: u8,
indices: &[u16],
) -> [Word16; M] {
let books = Books::for_mode(mode_index);
let mut residual = [Word16(0); M];
let base = usize::from(indices[0]) * 3;
for (i, slot) in residual[0..3].iter_mut().enumerate() {
*slot = Word16(books.first()[base + i]);
}
let index = if books == Books::Reduced {
usize::from(indices[1]) * 2
} else {
usize::from(indices[1])
};
let base = index * 3;
for (i, slot) in residual[3..6].iter_mut().enumerate() {
*slot = Word16(DICO2_LSF_3[base + i]);
}
let base = usize::from(indices[2]) * 4;
for (i, slot) in residual[6..10].iter_mut().enumerate() {
*slot = Word16(books.third()[base + i]);
}
let mut lsf = [Word16(0); M];
for i in 0..M {
let predicted = mult(ctx, self.past_residual[i], Word16(PRED_FAC_3[i]));
let anchor = add(ctx, Word16(MEAN_LSF_3[i]), predicted);
lsf[i] = add(ctx, residual[i], anchor);
self.past_residual[i] = residual[i];
}
lsf
}
fn conceal(&mut self, ctx: &mut DspContext) -> [Word16; M] {
let mut lsf = [Word16(0); M];
for i in 0..M {
let held = mult(ctx, self.past_lsf[i], ALPHA_3);
let pulled = mult(ctx, Word16(MEAN_LSF_3[i]), ONE_ALPHA_3);
lsf[i] = add(ctx, held, pulled);
}
for i in 0..M {
let predicted = mult(ctx, self.past_residual[i], Word16(PRED_FAC_3[i]));
let anchor = add(ctx, Word16(MEAN_LSF_3[i]), predicted);
self.past_residual[i] = sub(ctx, lsf[i], anchor);
}
lsf
}
pub fn decode_pair(&mut self, indices: &[u16], bad_frame: bool) -> ([Word16; M], [Word16; M]) {
let mut ctx = DspContext::default();
let (mut lsf_mid, mut lsf_new) = if bad_frame {
self.conceal_pair(&mut ctx)
} else {
assert!(
indices.len() >= 5,
"the 5-split quantiser needs five indices"
);
self.decode_pair_good(&mut ctx, indices)
};
reorder_lsf(&mut ctx, &mut lsf_mid, LSF_GAP);
reorder_lsf(&mut ctx, &mut lsf_new, LSF_GAP);
self.past_lsf = lsf_new;
(
lsf_to_lsp(&mut ctx, &lsf_mid),
lsf_to_lsp(&mut ctx, &lsf_new),
)
}
fn decode_pair_good(
&mut self,
ctx: &mut DspContext,
indices: &[u16],
) -> ([Word16; M], [Word16; M]) {
let mut residual_mid = [Word16(0); M];
let mut residual_new = [Word16(0); M];
for (split, &(book, first)) in SPLITS_5.iter().enumerate() {
let (index, flip) = if split == SIGNED_SPLIT_5 {
(usize::from(indices[split] >> 1), indices[split] & 1 != 0)
} else {
(usize::from(indices[split]), false)
};
let base = index * 4;
assert!(
base + 4 <= book.len(),
"5-split index {index} overruns codebook {split}"
);
for k in 0..2 {
let (mid, new) = (Word16(book[base + k]), Word16(book[base + 2 + k]));
residual_mid[first + k] = if flip { negate(ctx, mid) } else { mid };
residual_new[first + k] = if flip { negate(ctx, new) } else { new };
}
}
let mut lsf_mid = [Word16(0); M];
let mut lsf_new = [Word16(0); M];
for i in 0..M {
let predicted = mult(ctx, self.past_residual[i], LSP_PRED_FAC_MR122);
let anchor = add(ctx, Word16(MEAN_LSF_5[i]), predicted);
lsf_mid[i] = add(ctx, residual_mid[i], anchor);
lsf_new[i] = add(ctx, residual_new[i], anchor);
self.past_residual[i] = residual_new[i];
}
(lsf_mid, lsf_new)
}
fn conceal_pair(&mut self, ctx: &mut DspContext) -> ([Word16; M], [Word16; M]) {
let mut lsf = [Word16(0); M];
for i in 0..M {
let held = mult(ctx, self.past_lsf[i], ALPHA_5);
let pulled = mult(ctx, Word16(MEAN_LSF_5[i]), ONE_ALPHA_5);
lsf[i] = add(ctx, held, pulled);
}
for i in 0..M {
let predicted = mult(ctx, self.past_residual[i], LSP_PRED_FAC_MR122);
let anchor = add(ctx, Word16(MEAN_LSF_5[i]), predicted);
self.past_residual[i] = sub(ctx, lsf[i], anchor);
}
(lsf, lsf)
}
}
pub fn reorder_lsf(ctx: &mut DspContext, lsf: &mut [Word16; M], min_dist: Word16) {
let mut floor = min_dist;
for slot in lsf.iter_mut() {
if slot.0 < floor.0 {
*slot = floor;
}
floor = add(ctx, *slot, min_dist);
}
}
#[must_use]
pub fn lsf_to_lsp(ctx: &mut DspContext, lsf: &[Word16; M]) -> [Word16; M] {
let mut lsp = [Word16(0); M];
for (i, slot) in lsp.iter_mut().enumerate() {
let ind = usize::try_from(shr(ctx, lsf[i], 8).0).expect("LSFs are non-negative");
let offset = Word16(lsf[i].0 & 0x00ff);
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, 9);
*slot = add(ctx, Word16(COS_TABLE[ind]), extract_l(shifted));
}
lsp
}
fn lsp_polynomial(ctx: &mut DspContext, lsp: &[Word16], f: &mut [Word32; 6]) {
f[0] = l_mult(ctx, Word16(4096), Word16(2048));
f[1] = l_msu(ctx, Word32(0), lsp[0], Word16(512));
for i in 2..=5 {
f[i] = f[i - 2];
let q = lsp[(i - 1) * 2];
for k in (2..=i).rev() {
let (hi, lo) = l_extract(f[k - 1]);
let term = l_shl(ctx, mpy_32_16(hi, lo, q), 1);
f[k] = l_add(ctx, f[k], f[k - 2]);
f[k] = l_sub(ctx, f[k], term);
}
f[1] = l_msu(ctx, f[1], q, Word16(512));
}
}
#[must_use]
pub fn lsp_to_lp(ctx: &mut DspContext, lsp: &[Word16; M]) -> [Word16; MP1] {
let mut f1 = [Word32(0); 6];
let mut f2 = [Word32(0); 6];
lsp_polynomial(ctx, &lsp[0..], &mut f1);
lsp_polynomial(ctx, &lsp[1..], &mut f2);
for i in (1..=5).rev() {
f1[i] = l_add(ctx, f1[i], f1[i - 1]);
f2[i] = l_sub(ctx, f2[i], f2[i - 1]);
}
let mut a = [Word16(0); MP1];
a[0] = Word16(4096);
for i in 1..=5 {
let j = M - i + 1;
let sum = l_add(ctx, f1[i], f2[i]);
a[i] = extract_l(l_shr_r(ctx, sum, 13));
let diff = l_sub(ctx, f1[i], f2[i]);
a[j] = extract_l(l_shr_r(ctx, diff, 13));
}
a
}
#[must_use]
pub fn interpolate_lsp(
ctx: &mut DspContext,
lsp_old: &[Word16; M],
lsp_new: &[Word16; M],
) -> [Word16; AZ_SIZE] {
let mut az = [Word16(0); AZ_SIZE];
let mut lsp = [Word16(0); M];
for i in 0..M {
let quarter_new = shr(ctx, lsp_new[i], 2);
let quarter_old = shr(ctx, lsp_old[i], 2);
let three_quarter_old = sub(ctx, lsp_old[i], quarter_old);
lsp[i] = add(ctx, quarter_new, three_quarter_old);
}
az[0..MP1].copy_from_slice(&lsp_to_lp(ctx, &lsp));
for i in 0..M {
let half_old = shr(ctx, lsp_old[i], 1);
let half_new = shr(ctx, lsp_new[i], 1);
lsp[i] = add(ctx, half_old, half_new);
}
az[MP1..2 * MP1].copy_from_slice(&lsp_to_lp(ctx, &lsp));
for i in 0..M {
let quarter_old = shr(ctx, lsp_old[i], 2);
let quarter_new_drop = shr(ctx, lsp_new[i], 2);
let three_quarter_new = sub(ctx, lsp_new[i], quarter_new_drop);
lsp[i] = add(ctx, quarter_old, three_quarter_new);
}
az[2 * MP1..3 * MP1].copy_from_slice(&lsp_to_lp(ctx, &lsp));
az[3 * MP1..].copy_from_slice(&lsp_to_lp(ctx, lsp_new));
az
}
#[must_use]
pub fn interpolate_lsp_mid(
ctx: &mut DspContext,
lsp_old: &[Word16; M],
lsp_mid: &[Word16; M],
lsp_new: &[Word16; M],
) -> [Word16; AZ_SIZE] {
let mut az = [Word16(0); AZ_SIZE];
let mut lsp = [Word16(0); M];
for i in 0..M {
let half_mid = shr(ctx, lsp_mid[i], 1);
let half_old = shr(ctx, lsp_old[i], 1);
lsp[i] = add(ctx, half_mid, half_old);
}
az[0..MP1].copy_from_slice(&lsp_to_lp(ctx, &lsp));
az[MP1..2 * MP1].copy_from_slice(&lsp_to_lp(ctx, lsp_mid));
for i in 0..M {
let half_mid = shr(ctx, lsp_mid[i], 1);
let half_new = shr(ctx, lsp_new[i], 1);
lsp[i] = add(ctx, half_mid, half_new);
}
az[2 * MP1..3 * MP1].copy_from_slice(&lsp_to_lp(ctx, &lsp));
az[3 * MP1..].copy_from_slice(&lsp_to_lp(ctx, lsp_new));
az
}
#[must_use]
pub fn initial_lsp() -> [Word16; M] {
super::decoder_tables::LSP_INIT.map(Word16)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codecs::amr::nb::vectors;
use std::collections::HashSet;
const STAGES: &str = include_str!("../testdata/stages_nb.txt");
fn block_rows(block: &str) -> impl Iterator<Item = &'static str> + '_ {
STAGES
.lines()
.skip_while(move |l| l.trim_end() != block)
.skip(1)
.take_while(|l| l.starts_with(' '))
}
fn row(block: &str, label: &str) -> Vec<i16> {
for line in block_rows(block) {
let mut parts = line.split_whitespace();
if parts.next() == Some(label) {
return parts.map(|v| v.parse().expect("integer")).collect();
}
}
panic!("block {block:?} has no row {label:?}");
}
fn has_row(block: &str, label: &str) -> bool {
block_rows(block).any(|l| l.split_whitespace().next() == Some(label))
}
fn agrees(what: &str, frame: usize, got: &[Word16], want: &[i16]) {
assert_eq!(got.len(), want.len(), "plsf5 frame {frame}: {what} length");
for (i, (&g, &w)) in got.iter().zip(want).enumerate() {
assert_eq!(
g.0, w,
"plsf5 frame {frame}: {what}[{i}] = {} but the reference gives {w}",
g.0
);
}
}
fn replay_five_split() -> usize {
let rows = vectors::rows("plsf5");
assert_eq!(rows[0].label, "seed", "plsf5 starts with the oracle's seed");
let groups = &rows[1..];
assert_eq!(groups.len() % 4, 0, "plsf5 rows come in groups of four");
let mut dec = LsfDecoder::at_reset();
let mut lsp_old = initial_lsp();
let mut ctx = DspContext::default();
let mut erased = Vec::new();
for (f, group) in groups.chunks_exact(4).enumerate() {
assert_eq!(group[0].label, "frame", "plsf5 group {f}: no frame row");
assert_eq!(group[1].label, "lsp1", "plsf5 group {f}: no lsp1 row");
assert_eq!(group[2].label, "lsp2", "plsf5 group {f}: no lsp2 row");
assert_eq!(group[3].label, "az", "plsf5 group {f}: no az row");
let head = group[0].ints();
assert_eq!(head.len(), 6, "plsf5 frame {f}: bfi and five indices");
let bad = head[0] != 0;
if bad {
erased.push(f);
}
let indices: Vec<u16> = head[1..]
.iter()
.map(|&v| u16::try_from(v).expect("index is a non-negative field"))
.collect();
let (lsp_mid, lsp_new) = dec.decode_pair(&indices, bad);
agrees("lsp1", f, &lsp_mid, &group[1].i16s());
agrees("lsp2", f, &lsp_new, &group[2].i16s());
let az = interpolate_lsp_mid(&mut ctx, &lsp_old, &lsp_mid, &lsp_new);
let want_az = group[3].i16s();
assert_eq!(want_az.len(), AZ_SIZE, "plsf5 frame {f}: az length");
agrees("az", f, &az, &want_az);
lsp_old = lsp_new;
}
assert_eq!(erased, vec![6], "plsf5 covers exactly one erased frame");
groups.len() / 4
}
#[test]
fn the_five_split_quantiser_is_bit_exact_against_ts26073() {
assert_eq!(replay_five_split(), 8, "plsf5 carries eight frames");
}
#[test]
fn the_spectral_path_is_bit_exact_against_ts26073() {
let mut checked = 0;
for mode_index in 0..7u8 {
let block = format!("nb{mode_index}");
let mut dec = LsfDecoder::new();
let mut lsp_old = initial_lsp();
let mut ctx = DspContext::default();
for f in 0.. {
if !has_row(&block, &format!("prm{f}")) {
break;
}
let prm = row(&block, &format!("prm{f}"));
let indices: Vec<u16> = prm[..3]
.iter()
.map(|&v| u16::try_from(v).expect("index"))
.collect();
let lsp_new = dec.decode(mode_index, &indices, false);
let want_lsp = row(&block, &format!("lsp{f}"));
for (i, (&g, &w)) in lsp_new.iter().zip(want_lsp.iter()).enumerate() {
assert_eq!(
g.0, w,
"{block} frame {f}: lsp[{i}] = {} but the reference gives {w}",
g.0
);
}
let az = interpolate_lsp(&mut ctx, &lsp_old, &lsp_new);
let want_az = row(&block, &format!("az{f}"));
assert_eq!(want_az.len(), AZ_SIZE, "{block} frame {f}: az length");
for (i, (&g, &w)) in az.iter().zip(want_az.iter()).enumerate() {
assert_eq!(
g.0, w,
"{block} frame {f}: a[{i}] = {} but the reference gives {w}",
g.0
);
}
lsp_old = lsp_new;
checked += 1;
}
}
assert!(checked >= 14, "only {checked} three-split frames checked");
assert!(
!has_row("nb7", "lsp0"),
"nb7 now carries its own spectral rows"
);
let five_split = replay_five_split();
assert_eq!(five_split, 8, "plsf5 carries eight frames");
}
#[test]
fn lsps_stay_ordered_and_the_filter_stays_stable() {
for mode_index in 0..7u8 {
let block = format!("nb{mode_index}");
let mut dec = LsfDecoder::new();
for f in 0..3 {
if !has_row(&block, &format!("prm{f}")) {
break;
}
let prm = row(&block, &format!("prm{f}"));
let indices: Vec<u16> = prm[..3]
.iter()
.map(|&v| u16::try_from(v).expect("index"))
.collect();
let lsp = dec.decode(mode_index, &indices, false);
ordered(mode_index, f, "lsp", &lsp);
}
}
let mut dec = LsfDecoder::at_reset();
for f in 0..3 {
if !has_row("nb7", &format!("prm{f}")) {
break;
}
let prm = row("nb7", &format!("prm{f}"));
let indices: Vec<u16> = prm[..5]
.iter()
.map(|&v| u16::try_from(v).expect("index"))
.collect();
let (mid, new) = dec.decode_pair(&indices, false);
ordered(7, f, "mid", &mid);
ordered(7, f, "new", &new);
}
}
fn ordered(mode_index: u8, frame: usize, what: &str, lsp: &[Word16; M]) {
for i in 1..M {
assert!(
lsp[i].0 < lsp[i - 1].0,
"mode {mode_index} frame {frame}: {what}[{i}] is not below {what}[{}]",
i - 1
);
}
}
#[test]
fn the_five_splits_cover_every_coefficient_exactly_once() {
let mut covered = [0u8; M];
let sizes = [128, 256, 256, 256, 64];
for (split, (&(book, first), entries)) in SPLITS_5.iter().zip(sizes).enumerate() {
assert_eq!(
book.len(),
entries * 4,
"split {split} is not {entries} stride-4 entries"
);
covered[first] += 1;
covered[first + 1] += 1;
}
assert_eq!(covered, [1u8; M], "the five splits are not a partition");
}
#[test]
fn the_third_splits_sign_bit_buys_a_second_codebook() {
let mut seen = HashSet::new();
for entry in DICO3_LSF_5.chunks_exact(4) {
let quad = [entry[0], entry[1], entry[2], entry[3]];
assert!(
seen.insert(quad),
"dico3 entry {quad:?} repeats, or negates an earlier one"
);
let flipped = quad.map(|v| -v);
assert!(seen.insert(flipped), "dico3 already contains {flipped:?}");
}
assert_eq!(seen.len(), 512, "the effective codebook is not 512 wide");
}
#[test]
fn the_two_quantisers_share_no_constants() {
assert_ne!(
MEAN_LSF_3, MEAN_LSF_5,
"the two long-term means are the same"
);
assert_ne!(
ALPHA_3.0, ALPHA_5.0,
"the two concealment weights are the same"
);
assert!(
PRED_FAC_3.iter().any(|&f| f != LSP_PRED_FAC_MR122.0),
"the per-coefficient predictor collapsed onto the 12.2 scalar"
);
assert_eq!(i32::from(ALPHA_3.0) + i32::from(ONE_ALPHA_3.0), 32768);
assert_eq!(i32::from(ALPHA_5.0) + i32::from(ONE_ALPHA_5.0), 32767);
}
#[test]
fn a_frozen_spectrum_survives_interpolation_unchanged() {
let mut ctx = DspContext::default();
let lsp = initial_lsp();
assert!(
lsp.iter().all(|v| v.0 % 2 == 0),
"the reset LSPs are not all even"
);
let az = interpolate_lsp_mid(&mut ctx, &lsp, &lsp, &lsp);
let want = lsp_to_lp(&mut ctx, &lsp);
for sf in 0..NB_SUBFR {
assert_eq!(
az[sf * MP1..(sf + 1) * MP1]
.iter()
.map(|w| w.0)
.collect::<Vec<_>>(),
want.iter().map(|w| w.0).collect::<Vec<_>>(),
"subframe {sf} does not reproduce a frozen spectrum"
);
}
}
#[test]
fn the_transmitted_sets_reach_their_subframes_unmodified() {
let mut dec = LsfDecoder::at_reset();
let (mid, new) = dec.decode_pair(&[29, 44, 274, 202, 5], false);
let old = initial_lsp();
let mut ctx = DspContext::default();
let az = interpolate_lsp_mid(&mut ctx, &old, &mid, &new);
assert_eq!(az[MP1..2 * MP1], lsp_to_lp(&mut ctx, &mid), "subframe 2");
assert_eq!(az[3 * MP1..], lsp_to_lp(&mut ctx, &new), "subframe 4");
}
#[test]
fn the_two_interpolators_are_not_interchangeable() {
let mut dec = LsfDecoder::at_reset();
let (_, new) = dec.decode_pair(&[77, 37, 423, 36, 7], false);
let old = initial_lsp();
let mut ctx = DspContext::default();
let both = interpolate_lsp_mid(&mut ctx, &old, &new, &new);
let across = interpolate_lsp(&mut ctx, &old, &new);
assert_ne!(
both.map(|w| w.0),
across.map(|w| w.0),
"the two interpolation schemes agree, which means one is wrong"
);
}
#[test]
fn a_sustained_12k2_erasure_holds_one_spectrum_and_converges() {
let mut dec = LsfDecoder::at_reset();
let indices = [29u16, 44, 274, 202, 5];
for _ in 0..4 {
dec.decode_pair(&indices, false);
}
let (mut previous, first_new) = dec.decode_pair(&indices, true);
assert_eq!(previous, first_new, "an erased frame produced two spectra");
let mut first_gap = 0i32;
for n in 0..12 {
let (mid, new) = dec.decode_pair(&indices, true);
assert_eq!(mid, new, "erasure {n} produced two spectra");
let gap: i32 = (0..M)
.map(|i| (i32::from(new[i].0) - i32::from(previous[i].0)).abs())
.sum();
if n == 0 {
first_gap = gap;
} else if n == 11 {
assert!(
gap <= first_gap,
"erasure {n} moved {gap}, not below {first_gap}"
);
}
previous = new;
}
}
#[test]
fn the_leading_coefficient_is_unity_in_q12() {
let mut ctx = DspContext::default();
let a = lsp_to_lp(&mut ctx, &initial_lsp());
assert_eq!(a[0].0, 4096);
}
#[test]
fn this_is_not_the_wideband_conversion() {
let mut ctx = DspContext::default();
let lsp = initial_lsp();
let nb = lsp_to_lp(&mut ctx, &lsp);
let mut wide = [Word16(0); 11];
let mut wb_input = [Word16(0); 10];
wb_input.copy_from_slice(&lsp);
crate::codecs::amr::wb::lp::isp_to_lp::isp_to_lp_order(&wb_input, &mut wide);
assert_ne!(
nb.map(|w| w.0),
wide.map(|w| w.0),
"the narrowband and wideband conversions agree, which means one is wrong"
);
}
#[test]
fn concealment_pulls_toward_the_long_term_mean() {
let mut dec = LsfDecoder::new();
let indices = [10u16, 20, 30];
for _ in 0..4 {
dec.decode(4, &indices, false);
}
let mut previous = dec.decode(4, &indices, true);
let mut first_gap = 0i32;
for n in 0..12 {
let next = dec.decode(4, &indices, true);
let gap: i32 = (0..M)
.map(|i| (i32::from(next[i].0) - i32::from(previous[i].0)).abs())
.sum();
if n == 0 {
first_gap = gap;
} else if n == 11 {
assert!(
gap <= first_gap,
"erasure {n} moved {gap}, not below {first_gap}"
);
}
previous = next;
}
}
}