use super::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 super::super::lsp::{lsf_to_lsp, reorder_lsf, M};
use crate::fixed_point::arith::{add, mult, negate, round, sub};
use crate::fixed_point::arith32::{l_mac, l_mult, l_sub};
use crate::fixed_point::shift::{l_shl, shl};
use crate::fixed_point::types::{DspContext, Word16, Word32, MAX_32};
const LSF_GAP: Word16 = Word16(205);
const LSP_PRED_FAC_MR122: Word16 = Word16(21299);
const MR475: u8 = 0;
const MR515: u8 = 1;
const MR795: u8 = 5;
const LSF_NYQUIST: Word16 = Word16(16384);
const WEIGHT_KNEE: Word16 = Word16(1843);
const WEIGHT_AT_ZERO: Word16 = Word16(3427);
const WEIGHT_SLOPE_NEAR: Word16 = Word16(28160);
const WEIGHT_SLOPE_FAR: Word16 = Word16(6242);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stride {
Every,
EveryOther,
}
impl Stride {
const fn step(self) -> usize {
match self {
Self::Every => 3,
Self::EveryOther => 6,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Quantised {
pub indices: [u16; 3],
pub lsp: [Word16; M],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SidQuantised {
pub seed_index: u16,
pub indices: [u16; 3],
pub lsp: [Word16; M],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QuantisedPair {
pub indices: [u16; 5],
pub mid: [Word16; M],
pub new: [Word16; M],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct LsfQuantiser {
past_rq: [Word16; M],
}
impl LsfQuantiser {
#[must_use]
pub const fn new() -> Self {
Self {
past_rq: [Word16(0); M],
}
}
#[must_use]
pub const fn prediction_memory(&self) -> &[Word16; M] {
&self.past_rq
}
pub fn quantise(
&mut self,
ctx: &mut DspContext,
mode_index: u8,
lsp: &[Word16; M],
) -> Quantised {
assert!(
mode_index < 7,
"12.2 kbit/s quantises through quantise_pair"
);
let lsf = lsp_to_lsf(ctx, lsp);
let weights = lsf_weights(ctx, &lsf);
let mut predicted = [Word16(0); M];
let mut residual = [Word16(0); M];
for i in 0..M {
let carried = mult(ctx, self.past_rq[i], Word16(PRED_FAC_3[i]));
predicted[i] = add(ctx, Word16(MEAN_LSF_3[i]), carried);
residual[i] = sub(ctx, lsf[i], predicted[i]);
}
let (first, first_size, second_size, third, third_size) = match mode_index {
MR475 | MR515 => (&DICO1_LSF_3[..], 256, 256, &MR515_3_LSF[..], 128),
MR795 => (&MR795_1_LSF[..], 512, 512, &DICO3_LSF_3[..], 512),
_ => (&DICO1_LSF_3[..], 256, 512, &DICO3_LSF_3[..], 512),
};
let second_stride = if matches!(mode_index, MR475 | MR515) {
Stride::EveryOther
} else {
Stride::Every
};
let mut split0 = [residual[0], residual[1], residual[2]];
let mut split1 = [residual[3], residual[4], residual[5]];
let mut split2 = [residual[6], residual[7], residual[8], residual[9]];
let indices = [
search_split3(
ctx,
&mut split0,
first,
&[weights[0], weights[1], weights[2]],
first_size,
Stride::Every,
),
search_split3(
ctx,
&mut split1,
&DICO2_LSF_3,
&[weights[3], weights[4], weights[5]],
second_size,
second_stride,
),
search_split4(
ctx,
&mut split2,
third,
&[weights[6], weights[7], weights[8], weights[9]],
third_size,
),
];
residual[0..3].copy_from_slice(&split0);
residual[3..6].copy_from_slice(&split1);
residual[6..10].copy_from_slice(&split2);
let mut quantised = [Word16(0); M];
for i in 0..M {
quantised[i] = add(ctx, residual[i], predicted[i]);
self.past_rq[i] = residual[i];
}
reorder_lsf(ctx, &mut quantised, LSF_GAP);
Quantised {
indices,
lsp: lsf_to_lsp(ctx, &quantised),
}
}
pub fn quantise_sid(&mut self, ctx: &mut DspContext, lsp: &[Word16; M]) -> SidQuantised {
let lsf = lsp_to_lsf(ctx, lsp);
let weights = lsf_weights(ctx, &lsf);
let mut seed_index = 0u16;
let mut best_error = Word32(i32::MAX);
let mut predicted = [Word16(0); M];
let mut residual = [Word16(0); M];
for j in 0..PAST_RQ_INIT.len() / M {
let mut error = Word32(0);
let mut candidate_p = [Word16(0); M];
let mut candidate_r = [Word16(0); M];
for i in 0..M {
candidate_p[i] = add(ctx, Word16(MEAN_LSF_3[i]), Word16(PAST_RQ_INIT[j * M + i]));
candidate_r[i] = sub(ctx, lsf[i], candidate_p[i]);
error = l_mac(ctx, error, candidate_r[i], candidate_r[i]);
}
if l_sub(ctx, error, best_error).0 < 0 {
best_error = error;
residual = candidate_r;
predicted = candidate_p;
for i in 0..M {
self.past_rq[i] = Word16(PAST_RQ_INIT[j * M + i]);
}
seed_index = u16::try_from(j).expect("eight seeds");
}
}
let mut split0 = [residual[0], residual[1], residual[2]];
let mut split1 = [residual[3], residual[4], residual[5]];
let mut split2 = [residual[6], residual[7], residual[8], residual[9]];
let indices = [
search_split3(
ctx,
&mut split0,
&DICO1_LSF_3,
&[weights[0], weights[1], weights[2]],
256,
Stride::Every,
),
search_split3(
ctx,
&mut split1,
&DICO2_LSF_3,
&[weights[3], weights[4], weights[5]],
512,
Stride::Every,
),
search_split4(
ctx,
&mut split2,
&DICO3_LSF_3,
&[weights[6], weights[7], weights[8], weights[9]],
512,
),
];
residual[0..3].copy_from_slice(&split0);
residual[3..6].copy_from_slice(&split1);
residual[6..10].copy_from_slice(&split2);
let mut quantised = [Word16(0); M];
for i in 0..M {
quantised[i] = add(ctx, residual[i], predicted[i]);
self.past_rq[i] = residual[i];
}
reorder_lsf(ctx, &mut quantised, LSF_GAP);
SidQuantised {
seed_index,
indices,
lsp: lsf_to_lsp(ctx, &quantised),
}
}
pub fn quantise_pair(
&mut self,
ctx: &mut DspContext,
mid: &[Word16; M],
new: &[Word16; M],
) -> QuantisedPair {
let lsf1 = lsp_to_lsf(ctx, mid);
let lsf2 = lsp_to_lsf(ctx, new);
let wf1 = lsf_weights(ctx, &lsf1);
let wf2 = lsf_weights(ctx, &lsf2);
let mut predicted = [Word16(0); M];
let mut r1 = [Word16(0); M];
let mut r2 = [Word16(0); M];
for i in 0..M {
let carried = mult(ctx, self.past_rq[i], LSP_PRED_FAC_MR122);
predicted[i] = add(ctx, Word16(MEAN_LSF_5[i]), carried);
r1[i] = sub(ctx, lsf1[i], predicted[i]);
r2[i] = sub(ctx, lsf2[i], predicted[i]);
}
let mut indices = [0u16; 5];
for (k, slot) in indices.iter_mut().enumerate() {
let a = 2 * k;
let mut pair1 = [r1[a], r1[a + 1]];
let mut pair2 = [r2[a], r2[a + 1]];
let w1 = [wf1[a], wf1[a + 1]];
let w2 = [wf2[a], wf2[a + 1]];
*slot = if k == 2 {
search_matrix_signed(ctx, &mut pair1, &mut pair2, &DICO3_LSF_5, &w1, &w2, 256)
} else {
let (book, size): (&[i16], usize) = match k {
0 => (&DICO1_LSF_5, 128),
1 => (&DICO2_LSF_5, 256),
3 => (&DICO4_LSF_5, 256),
_ => (&DICO5_LSF_5, 64),
};
search_matrix(ctx, &mut pair1, &mut pair2, book, &w1, &w2, size)
};
r1[a] = pair1[0];
r1[a + 1] = pair1[1];
r2[a] = pair2[0];
r2[a + 1] = pair2[1];
}
let mut q1 = [Word16(0); M];
let mut q2 = [Word16(0); M];
for i in 0..M {
q1[i] = add(ctx, r1[i], predicted[i]);
q2[i] = add(ctx, r2[i], predicted[i]);
self.past_rq[i] = r2[i];
}
reorder_lsf(ctx, &mut q1, LSF_GAP);
reorder_lsf(ctx, &mut q2, LSF_GAP);
QuantisedPair {
indices,
mid: lsf_to_lsp(ctx, &q1),
new: lsf_to_lsp(ctx, &q2),
}
}
}
#[must_use]
pub fn lsp_to_lsf(ctx: &mut DspContext, lsp: &[Word16; M]) -> [Word16; M] {
let mut lsf = [Word16(0); M];
let mut ind = COS_TABLE.len() - 2;
for i in (0..M).rev() {
while ind > 0 && sub(ctx, Word16(COS_TABLE[ind]), lsp[i]).0 < 0 {
ind -= 1;
}
let step = sub(ctx, lsp[i], Word16(COS_TABLE[ind]));
let scaled = l_mult(
ctx,
step,
Word16(super::super::decoder_tables::ACOS_SLOPE[ind]),
);
let promoted = l_shl(ctx, scaled, 3);
let fine = round(ctx, promoted);
let coarse = shl(
ctx,
Word16(i16::try_from(ind).expect("cosine table index fits in i16")),
8,
);
lsf[i] = add(ctx, fine, coarse);
}
lsf
}
#[must_use]
pub fn lsf_weights(ctx: &mut DspContext, lsf: &[Word16; M]) -> [Word16; M] {
let mut wf = [Word16(0); M];
wf[0] = lsf[1];
for i in 1..M - 1 {
wf[i] = sub(ctx, lsf[i + 1], lsf[i - 1]);
}
wf[M - 1] = sub(ctx, LSF_NYQUIST, lsf[M - 2]);
for slot in &mut wf {
let past_knee = sub(ctx, *slot, WEIGHT_KNEE);
*slot = if past_knee.0 < 0 {
let drop = mult(ctx, *slot, WEIGHT_SLOPE_NEAR);
sub(ctx, WEIGHT_AT_ZERO, drop)
} else {
let drop = mult(ctx, past_knee, WEIGHT_SLOPE_FAR);
sub(ctx, WEIGHT_KNEE, drop)
};
*slot = shl(ctx, *slot, 3);
}
wf
}
fn weighted_distance(
ctx: &mut DspContext,
residual: &[Word16],
codeword: &[i16],
weights: &[Word16],
) -> Word32 {
let mut dist = Word32(0);
for k in 0..residual.len() {
let err = sub(ctx, residual[k], Word16(codeword[k]));
let term = mult(ctx, weights[k], err);
dist = if k == 0 {
l_mult(ctx, term, term)
} else {
l_mac(ctx, dist, term, term)
};
}
dist
}
pub fn search_split3(
ctx: &mut DspContext,
residual: &mut [Word16; 3],
book: &[i16],
weights: &[Word16; 3],
size: usize,
stride: Stride,
) -> u16 {
let step = stride.step();
assert!(
book.len() >= (size - 1) * step + 3,
"codebook holds fewer than {size} candidates at stride {step}"
);
let mut dist_min = Word32(MAX_32);
let mut index = 0usize;
for i in 0..size {
let base = i * step;
let dist = weighted_distance(ctx, residual, &book[base..base + 3], weights);
if l_sub(ctx, dist, dist_min).0 < 0 {
dist_min = dist;
index = i;
}
}
let base = index * step;
residual.copy_from_slice(&[
Word16(book[base]),
Word16(book[base + 1]),
Word16(book[base + 2]),
]);
u16::try_from(index).expect("split index fits in 16 bits")
}
pub fn search_split4(
ctx: &mut DspContext,
residual: &mut [Word16; 4],
book: &[i16],
weights: &[Word16; 4],
size: usize,
) -> u16 {
assert!(
book.len() >= size * 4,
"codebook holds fewer than {size} candidates"
);
let mut dist_min = Word32(MAX_32);
let mut index = 0usize;
for i in 0..size {
let base = i * 4;
let dist = weighted_distance(ctx, residual, &book[base..base + 4], weights);
if l_sub(ctx, dist, dist_min).0 < 0 {
dist_min = dist;
index = i;
}
}
let base = index * 4;
for (k, slot) in residual.iter_mut().enumerate() {
*slot = Word16(book[base + k]);
}
u16::try_from(index).expect("split index fits in 16 bits")
}
fn matrix_distance(
ctx: &mut DspContext,
r1: [Word16; 2],
r2: [Word16; 2],
codeword: &[i16],
w1: [Word16; 2],
w2: [Word16; 2],
invert: bool,
) -> Word32 {
let mut dist = Word32(0);
for k in 0..4 {
let (value, weight) = if k < 2 {
(r1[k], w1[k])
} else {
(r2[k - 2], w2[k - 2])
};
let err = if invert {
add(ctx, value, Word16(codeword[k]))
} else {
sub(ctx, value, Word16(codeword[k]))
};
let term = mult(ctx, weight, err);
dist = if k == 0 {
l_mult(ctx, term, term)
} else {
l_mac(ctx, dist, term, term)
};
}
dist
}
pub fn search_matrix(
ctx: &mut DspContext,
r1: &mut [Word16; 2],
r2: &mut [Word16; 2],
book: &[i16],
w1: &[Word16; 2],
w2: &[Word16; 2],
size: usize,
) -> u16 {
assert!(
book.len() >= size * 4,
"codebook holds fewer than {size} candidates"
);
let mut dist_min = Word32(MAX_32);
let mut index = 0usize;
for i in 0..size {
let base = i * 4;
let dist = matrix_distance(ctx, *r1, *r2, &book[base..base + 4], *w1, *w2, false);
if l_sub(ctx, dist, dist_min).0 < 0 {
dist_min = dist;
index = i;
}
}
let base = index * 4;
r1[0] = Word16(book[base]);
r1[1] = Word16(book[base + 1]);
r2[0] = Word16(book[base + 2]);
r2[1] = Word16(book[base + 3]);
u16::try_from(index).expect("matrix index fits in 16 bits")
}
pub fn search_matrix_signed(
ctx: &mut DspContext,
r1: &mut [Word16; 2],
r2: &mut [Word16; 2],
book: &[i16],
w1: &[Word16; 2],
w2: &[Word16; 2],
size: usize,
) -> u16 {
assert!(
book.len() >= size * 4,
"codebook holds fewer than {size} candidates"
);
let mut dist_min = Word32(MAX_32);
let mut index = 0usize;
let mut negative = false;
for i in 0..size {
let base = i * 4;
let word = &book[base..base + 4];
let positive = matrix_distance(ctx, *r1, *r2, word, *w1, *w2, false);
if l_sub(ctx, positive, dist_min).0 < 0 {
dist_min = positive;
index = i;
negative = false;
}
let inverted = matrix_distance(ctx, *r1, *r2, word, *w1, *w2, true);
if l_sub(ctx, inverted, dist_min).0 < 0 {
dist_min = inverted;
index = i;
negative = true;
}
}
let base = index * 4;
let read = |ctx: &mut DspContext, at: usize| {
let v = Word16(book[at]);
if negative {
negate(ctx, v)
} else {
v
}
};
r1[0] = read(ctx, base);
r1[1] = read(ctx, base + 1);
r2[0] = read(ctx, base + 2);
r2[1] = read(ctx, base + 3);
let index = u16::try_from(index).expect("matrix index fits in 16 bits");
index * 2 + u16::from(negative)
}
#[cfg(test)]
mod tests {
use super::super::super::bitstream::parse;
use super::super::super::lsp::LsfDecoder;
use super::*;
const TRACE: &str = include_str!("../../testdata/nb_enc_trace.txt");
const MODE4: &[u8] = include_bytes!("../../testdata/amrnb_enc_mode4.amr");
const MR74: u8 = 4;
fn trace(frame: usize, subframe: i32, name: &str) -> Vec<i16> {
let want = format!("T {frame} {subframe} {name} ");
for line in TRACE.lines() {
if let Some(rest) = line.strip_prefix(&want) {
return rest
.split_whitespace()
.map(|v| v.parse().expect("trace value fits in i16"))
.collect();
}
}
panic!("the committed trace has no row {want:?}");
}
fn trace_lsp(frame: usize) -> [Word16; M] {
let v = trace(frame, 0, "lsp_new");
assert_eq!(v.len(), M, "lsp_new is ten coefficients");
let mut out = [Word16(0); M];
for (slot, value) in out.iter_mut().zip(v) {
*slot = Word16(value);
}
out
}
fn reference_parameters(frame: usize) -> Vec<u16> {
const PAYLOAD: usize = 19; let offset = 6 + frame * (1 + PAYLOAD);
let toc = MODE4[offset];
assert_eq!((toc >> 3) & 0x0f, MR74, "frame {frame}: ToC mode");
parse(MR74, &MODE4[offset + 1..offset + 1 + PAYLOAD]).expect("frame parses")
}
#[test]
fn three_split_indices_match_the_reference_bitstream() {
let mut ctx = DspContext::default();
let mut quantiser = LsfQuantiser::new();
let mut compared = 0;
for frame in 0..3 {
let got = quantiser
.quantise(&mut ctx, MR74, &trace_lsp(frame))
.indices;
let want = reference_parameters(frame);
for (split, &index) in got.iter().enumerate() {
assert_eq!(
index, want[split],
"frame {frame} split {split}: chose {index}, TS 26.073 chose {}",
want[split]
);
compared += 1;
}
}
assert_eq!(compared, 9, "three frames of three indices");
}
#[test]
fn three_split_reconstruction_matches_the_bit_exact_decoder() {
let mut ctx = DspContext::default();
let mut quantiser = LsfQuantiser::new();
let mut decoder = LsfDecoder::at_reset();
let mut compared = 0;
for frame in 0..3 {
let encoded = quantiser.quantise(&mut ctx, MR74, &trace_lsp(frame));
let rebuilt = decoder.decode(MR74, &encoded.indices, false);
for (i, (&mine, &theirs)) in encoded.lsp.iter().zip(rebuilt.iter()).enumerate() {
assert_eq!(mine.0, theirs.0, "frame {frame} coefficient {i}");
compared += 1;
}
}
assert_eq!(compared, 30, "three frames of ten coefficients");
}
#[test]
fn the_prediction_memory_carries_the_quantised_residual_across_frames() {
let mut ctx = DspContext::default();
let mut fresh = LsfQuantiser::new();
assert_eq!(fresh.prediction_memory(), &[Word16(0); M]);
let cold = fresh.quantise(&mut ctx, MR74, &trace_lsp(1)).indices;
let mut warm = LsfQuantiser::new();
let _ = warm.quantise(&mut ctx, MR74, &trace_lsp(0));
assert_ne!(
warm.prediction_memory(),
&[Word16(0); M],
"the memory did not advance"
);
let hot = warm.quantise(&mut ctx, MR74, &trace_lsp(1)).indices;
assert_ne!(
cold, hot,
"the prediction had no effect on the chosen indices"
);
assert_eq!(hot.to_vec(), reference_parameters(1)[..3].to_vec());
}
#[test]
fn every_rates_codebook_triple_round_trips_through_the_decoder() {
let mut compared = 0;
for mode_index in 0..7u8 {
let mut ctx = DspContext::default();
let mut quantiser = LsfQuantiser::new();
let mut decoder = LsfDecoder::at_reset();
for frame in 0..3 {
let encoded = quantiser.quantise(&mut ctx, mode_index, &trace_lsp(frame));
let rebuilt = decoder.decode(mode_index, &encoded.indices, false);
assert_eq!(encoded.lsp, rebuilt, "mode {mode_index} frame {frame}");
let widths: [u16; 3] = match mode_index {
0 | 1 => [256, 256, 128],
5 => [512, 512, 512],
_ => [256, 512, 512],
};
for (split, (&index, &size)) in
encoded.indices.iter().zip(widths.iter()).enumerate()
{
assert!(
index < size,
"mode {mode_index} split {split}: {index} >= {size}"
);
}
compared += 3;
}
}
assert_eq!(compared, 63, "seven rates of three frames of three indices");
}
#[test]
fn five_split_round_trips_through_the_bit_exact_decoder() {
let mut ctx = DspContext::default();
let mut quantiser = LsfQuantiser::new();
let mut decoder = LsfDecoder::at_reset();
let mut compared = 0;
for frame in 0..3 {
let mid = trace_lsp(frame);
let new = trace_lsp((frame + 1) % 3);
let encoded = quantiser.quantise_pair(&mut ctx, &mid, &new);
let (rebuilt_mid, rebuilt_new) = decoder.decode_pair(&encoded.indices, false);
for i in 0..M {
assert_eq!(encoded.mid[i].0, rebuilt_mid[i].0, "frame {frame} mid {i}");
assert_eq!(encoded.new[i].0, rebuilt_new[i].0, "frame {frame} new {i}");
compared += 2;
}
}
assert_eq!(compared, 60, "three frames of two ten-coefficient sets");
}
#[test]
fn the_five_split_signed_submatrix_uses_its_sign_bit() {
let mut ctx = DspContext::default();
let mut quantiser = LsfQuantiser::new();
let mut odd = 0;
for frame in 0..3 {
let q =
quantiser.quantise_pair(&mut ctx, &trace_lsp(frame), &trace_lsp((frame + 1) % 3));
assert!(q.indices[2] < 512, "signed index is nine bits");
odd += usize::from(q.indices[2] % 2 == 1);
}
assert!(odd > 0, "the negative hypothesis never won in three frames");
}
#[test]
fn the_weights_switch_segments_at_the_knee() {
let mut ctx = DspContext::default();
let mut lsf = [Word16(0); M];
lsf[1] = Word16(1842);
let steep = lsf_weights(&mut ctx, &lsf)[0];
let drop = mult(&mut ctx, Word16(1842), WEIGHT_SLOPE_NEAR);
let expected = sub(&mut ctx, WEIGHT_AT_ZERO, drop);
assert_eq!(steep.0, shl(&mut ctx, expected, 3).0);
lsf[1] = Word16(1843);
let shallow = lsf_weights(&mut ctx, &lsf)[0];
assert_eq!(
shallow.0,
shl(&mut ctx, WEIGHT_KNEE, 3).0,
"at the knee the correction is zero"
);
assert!(shallow.0 < steep.0, "the weight decreases with spacing");
let mut wide = [Word16(0); M];
wide[8] = Word16(16384 - 1843);
assert_eq!(
lsf_weights(&mut ctx, &wide)[9].0,
shl(&mut ctx, WEIGHT_KNEE, 3).0
);
}
#[test]
fn lsp_to_lsf_inverts_the_decoders_conversion_closely() {
let mut ctx = DspContext::default();
let mut compared = 0;
for frame in 0..3 {
let lsp = trace_lsp(frame);
let lsf = lsp_to_lsf(&mut ctx, &lsp);
for i in 1..M {
assert!(
lsf[i].0 > lsf[i - 1].0,
"frame {frame}: lsf {i} out of order"
);
}
assert!(lsf[M - 1].0 < 16384, "frame {frame}: lsf above Nyquist");
let back = lsf_to_lsp(&mut ctx, &lsf);
for i in 0..M {
assert!(
(i32::from(back[i].0) - i32::from(lsp[i].0)).abs() <= 4,
"frame {frame} coefficient {i}: {} round-tripped to {}",
lsp[i].0,
back[i].0
);
compared += 1;
}
}
assert_eq!(compared, 30);
}
#[test]
fn split_searches_break_ties_toward_the_lowest_index() {
let mut ctx = DspContext::default();
let weights = [Word16(8192); 3];
let book3: [i16; 12] = [
100, 0, 0, 100, 0, 0, 0, 0, 0, 7, 7, 7,
];
let mut residual = [Word16(0); 3];
assert_eq!(
search_split3(&mut ctx, &mut residual, &book3, &weights, 2, Stride::Every),
0,
"the tie must go to the lower index"
);
assert_eq!(
residual,
[Word16(100), Word16(0), Word16(0)],
"the codevector is written back"
);
let mut residual = [Word16(0); 3];
assert_eq!(
search_split3(&mut ctx, &mut residual, &book3, &weights, 3, Stride::Every),
2,
"an exact match still wins"
);
assert_eq!(residual, [Word16(0); 3]);
let book4: [i16; 12] = [50, 0, 0, 0, 50, 0, 0, 0, 1, 1, 1, 1];
let mut residual4 = [Word16(0); 4];
assert_eq!(
search_split4(&mut ctx, &mut residual4, &book4, &[Word16(8192); 4], 2),
0
);
assert_eq!(residual4, [Word16(50), Word16(0), Word16(0), Word16(0)]);
}
#[test]
fn the_flooring_weight_makes_equal_errors_score_differently() {
let mut ctx = DspContext::default();
let weights = [Word16(8192); 3];
assert_eq!(mult(&mut ctx, Word16(8192), Word16(-50)).0, -13);
assert_eq!(mult(&mut ctx, Word16(8192), Word16(50)).0, 12);
let book: [i16; 6] = [-50, 0, 0, 50, 0, 0];
let mut residual = [Word16(0); 3];
assert_eq!(
search_split3(&mut ctx, &mut residual, &book, &weights, 2, Stride::Every),
0,
"the error that floors to the smaller magnitude wins"
);
}
#[test]
fn the_half_stride_search_skips_the_odd_codevectors() {
let mut ctx = DspContext::default();
let weights = [Word16(8192); 3];
let book: [i16; 12] = [
500, 500, 500, 0, 0, 0, 9, 9, 9, 0, 0, 0, ];
let mut residual = [Word16(0); 3];
let index = search_split3(
&mut ctx,
&mut residual,
&book,
&weights,
2,
Stride::EveryOther,
);
assert_eq!(index, 1, "codevector 2 is candidate 1 at this stride");
assert_eq!(
residual,
[Word16(9), Word16(9), Word16(9)],
"read back from word 6*index"
);
let mut residual = [Word16(0); 3];
assert_eq!(
search_split3(&mut ctx, &mut residual, &book, &weights, 4, Stride::Every),
1
);
}
#[test]
fn a_saturating_distance_cannot_displace_an_earlier_candidate() {
let mut ctx = DspContext::default();
let weights = [Word16(32767); 4];
let book: [i16; 8] = [
-32768, -32768, -32768, -32768, -32000, -32000, -32000, -32000, ];
let mut residual = [Word16(32767); 4];
let index = search_split4(&mut ctx, &mut residual, &book, &weights, 2);
assert_eq!(index, 0, "saturation makes the two compare equal");
}
#[test]
fn the_signed_matrix_search_prefers_positive_then_earlier() {
let mut ctx = DspContext::default();
let w = [Word16(8192); 2];
let book: [i16; 8] = [0, 0, 0, 0, 1, 1, 1, 1];
let mut r1 = [Word16(0); 2];
let mut r2 = [Word16(0); 2];
assert_eq!(
search_matrix_signed(&mut ctx, &mut r1, &mut r2, &book, &w, &w, 2),
0,
"+cb ties with -cb: positive wins"
);
let book: [i16; 8] = [100, 100, 100, 100, -100, -100, -100, -100];
let mut r1 = [Word16(-100); 2];
let mut r2 = [Word16(-100); 2];
let index = search_matrix_signed(&mut ctx, &mut r1, &mut r2, &book, &w, &w, 2);
assert_eq!(index, 1, "index 0 with the sign bit, i.e. 2*0 + 1");
assert_eq!(
r1,
[Word16(-100); 2],
"the codevector is written back negated"
);
assert_eq!(r2, [Word16(-100); 2]);
}
#[test]
fn the_matrix_search_interleaves_the_two_residuals() {
let mut ctx = DspContext::default();
let w = [Word16(8192); 2];
let book: [i16; 8] = [1, 2, 3, 4, 1, 3, 2, 4];
let mut r1 = [Word16(1), Word16(2)];
let mut r2 = [Word16(3), Word16(4)];
assert_eq!(
search_matrix(&mut ctx, &mut r1, &mut r2, &book, &w, &w, 2),
0,
"candidate 0 matches exactly under the interleaved order"
);
}
}