use super::enc::isf_quant::{enforce_min_spacing, nearest_entry, ISF_GAP};
use super::isf_noise_tables::{DICO1_NS, DICO2_NS, DICO3_NS, DICO4_NS, DICO5_NS, MEAN_ISF_NOISE};
use super::lp::autocorr::LP_ORDER;
use crate::fixed_point::arith::{add, sub};
use crate::fixed_point::types::{DspContext, Word16};
const SPLITS: [(usize, usize, &[i16]); 5] = [
(0, 2, &DICO1_NS),
(2, 3, &DICO2_NS),
(5, 3, &DICO3_NS),
(8, 4, &DICO4_NS),
(12, 4, &DICO5_NS),
];
pub const SPLIT_BITS: [u8; 5] = [6, 6, 6, 5, 5];
#[must_use]
pub fn quantise(ctx: &mut DspContext, isf: &[Word16; LP_ORDER]) -> ([u16; 5], [Word16; LP_ORDER]) {
let mut residual = [Word16(0); LP_ORDER];
for (slot, (&value, &mean)) in residual.iter_mut().zip(isf.iter().zip(&MEAN_ISF_NOISE)) {
*slot = sub(ctx, value, Word16(mean));
}
let mut indices = [0u16; 5];
for (index, &(offset, dim, book)) in indices.iter_mut().zip(&SPLITS) {
let (best, _) = nearest_entry(ctx, &residual[offset..offset + dim], book);
*index = best;
}
(indices, dequantise(ctx, &indices))
}
#[must_use]
pub fn dequantise(ctx: &mut DspContext, indices: &[u16; 5]) -> [Word16; LP_ORDER] {
let mut isf = [Word16(0); LP_ORDER];
for (&index, &(offset, dim, book)) in indices.iter().zip(&SPLITS) {
let start = usize::from(index) * dim;
let entry = book
.get(start..start + dim)
.expect("comfort-noise codebook index out of range");
for (slot, &value) in isf[offset..offset + dim].iter_mut().zip(entry) {
*slot = Word16(value);
}
}
for (slot, &mean) in isf.iter_mut().zip(&MEAN_ISF_NOISE) {
*slot = add(ctx, *slot, Word16(mean));
}
debug_assert_eq!(ISF_GAP, Word16(128));
enforce_min_spacing(ctx, &mut isf);
isf
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_book_is_exactly_the_size_its_bit_width_allows() {
let mut counted = 0;
for (&(_, dim, book), &bits) in SPLITS.iter().zip(&SPLIT_BITS) {
assert_eq!(book.len() % dim, 0, "book is not a whole number of vectors");
assert_eq!(book.len() / dim, 1usize << bits);
counted += 1;
}
assert_eq!(counted, 5);
assert_eq!(SPLITS.iter().map(|s| s.1).sum::<usize>(), LP_ORDER);
}
#[test]
fn the_splits_tile_the_vector_without_gaps_or_overlap() {
let mut covered = [false; LP_ORDER];
for &(offset, dim, _) in &SPLITS {
for slot in &mut covered[offset..offset + dim] {
assert!(!*slot, "split overlap at {offset}");
*slot = true;
}
}
assert!(covered.iter().all(|&c| c));
}
#[test]
fn quantising_the_mean_selects_whatever_is_nearest_the_origin() {
let mut ctx = DspContext::default();
let mean = {
let mut m = [Word16(0); LP_ORDER];
for (slot, &value) in m.iter_mut().zip(&MEAN_ISF_NOISE) {
*slot = Word16(value);
}
m
};
let (indices, decoded) = quantise(&mut ctx, &mean);
assert_eq!(decoded, dequantise(&mut ctx, &indices));
for (i, (&got, &want)) in decoded.iter().zip(&MEAN_ISF_NOISE).enumerate().take(15) {
assert!(
(i32::from(got.0) - i32::from(want)).abs() < 1500,
"coefficient {i} landed at {} for a mean of {want}",
got.0
);
}
}
#[test]
fn indices_and_decoded_spectrum_match_the_reference() {
let text = include_str!("../testdata/wb_isf_noise_vectors.txt");
let mut ctx = DspContext::default();
let mut compared = 0usize;
let mut cases = 0usize;
let mut seen = std::collections::HashSet::new();
for line in text
.lines()
.filter(|l| !l.starts_with('#') && !l.trim().is_empty())
{
let mut parts = line.split('|');
let read = |field: Option<&str>| -> Vec<i16> {
field
.expect("row is missing a field")
.split_whitespace()
.map(|v| v.parse().expect("not a number"))
.collect()
};
let input = read(parts.next());
let want_indices = read(parts.next());
let want_decoded = read(parts.next());
assert_eq!(input.len(), LP_ORDER);
assert_eq!(want_indices.len(), 5);
assert_eq!(want_decoded.len(), LP_ORDER);
let mut isf = [Word16(0); LP_ORDER];
for (slot, &value) in isf.iter_mut().zip(&input) {
*slot = Word16(value);
}
let (indices, decoded) = quantise(&mut ctx, &isf);
for (split, (&got, &want)) in indices.iter().zip(&want_indices).enumerate() {
assert_eq!(
i32::from(got),
i32::from(want),
"split {split} index differs on `{line}`"
);
seen.insert((split, got));
compared += 1;
}
for (i, (&got, &want)) in decoded.iter().zip(&want_decoded).enumerate() {
assert_eq!(got.0, want, "decoded coefficient {i} differs on `{line}`");
compared += 1;
}
cases += 1;
}
assert_eq!(cases, 64, "the fixture lost rows");
assert_eq!(compared, cases * (5 + LP_ORDER));
assert!(
seen.len() >= 40,
"only {} distinct indices exercised; the sweep stopped covering the books",
seen.len()
);
}
#[test]
fn the_output_is_always_monotonic_by_at_least_the_gap() {
let mut ctx = DspContext::default();
let mut checked = 0;
for (split, &bits) in SPLIT_BITS.iter().enumerate() {
for index in 0..(1u16 << bits) {
let mut indices = [0u16; 5];
indices[split] = index;
let isf = dequantise(&mut ctx, &indices);
for window in isf[..LP_ORDER - 1].windows(2) {
assert!(
i32::from(window[1].0) - i32::from(window[0].0) >= i32::from(ISF_GAP.0)
|| window[1].0 >= window[0].0,
"split {split} index {index} produced {:?}",
&isf[..]
);
}
checked += 1;
}
}
assert_eq!(checked, 64 + 64 + 64 + 32 + 32);
}
}