use super::decoder_tables::INTER_6_PRED;
use super::{L_INTERPOL, L_SUBFR, PIT_MAX, PIT_MIN, PIT_MIN_MR122};
use crate::fixed_point::arith::{add, mult, negate, round, sub};
use crate::fixed_point::arith32::l_mac;
use crate::fixed_point::shift::shl;
use crate::fixed_point::types::{DspContext, Word16, Word32};
const PHASE_COUNT: i16 = 6;
const UP_SAMP_MAX: usize = PHASE_COUNT as usize;
const TAPS_PER_SIDE: usize = L_INTERPOL - 1;
const FIR_SIZE: usize = UP_SAMP_MAX * TAPS_PER_SIDE + 1;
const _: () = assert!(
INTER_6_PRED.len() == FIR_SIZE,
"the generated interpolation filter is not the length this module indexes"
);
const HISTORY: usize = PIT_MAX as usize + L_INTERPOL;
const EXC_LEN: usize = HISTORY + L_SUBFR;
const MODE_MR795: u8 = 5;
const MAX_FOUR_BIT_DELTA_MODE: u8 = 3;
const MODE_MR122: u8 = 7;
const THIRD: Word16 = Word16(10923);
const SIXTH: Word16 = Word16(5462);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LagResolution {
OneThird,
OneSixth,
}
impl LagResolution {
#[must_use]
pub const fn for_mode(mode_index: u8) -> Self {
if mode_index == MODE_MR122 {
Self::OneSixth
} else {
Self::OneThird
}
}
#[must_use]
pub const fn units_per_sample(self) -> i32 {
match self {
Self::OneThird => 3,
Self::OneSixth => 6,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PitchLag {
pub integer: Word16,
pub frac: Word16,
pub resolution: LagResolution,
}
impl PitchLag {
#[must_use]
pub const fn integral(integer: Word16, resolution: LagResolution) -> Self {
Self {
integer,
frac: Word16(0),
resolution,
}
}
#[must_use]
pub const fn units(self) -> i32 {
self.integer.0 as i32 * self.resolution.units_per_sample() + self.frac.0 as i32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LagWindow {
pub min: Word16,
pub max: Word16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeltaCoding {
Uniform,
Anchored {
previous_lag: Word16,
},
}
#[must_use]
pub const fn is_delta_coded(mode_index: u8, subframe: usize) -> bool {
match subframe {
0 => false,
2 => mode_index == 0 || mode_index == 1,
_ => true,
}
}
#[must_use]
pub const fn delta_coding(mode_index: u8, previous_lag: Word16) -> DeltaCoding {
if mode_index <= MAX_FOUR_BIT_DELTA_MODE {
DeltaCoding::Anchored { previous_lag }
} else {
DeltaCoding::Uniform
}
}
#[must_use]
pub fn delta_window(ctx: &mut DspContext, mode_index: u8, previous_lag: Word16) -> LagWindow {
let (low, range) = if mode_index == MODE_MR795 {
(Word16(10), Word16(19))
} else {
(Word16(5), Word16(9))
};
let mut min = sub(ctx, previous_lag, low);
if sub(ctx, min, Word16(PIT_MIN)).0 < 0 {
min = Word16(PIT_MIN);
}
let mut max = add(ctx, min, range);
if sub(ctx, max, Word16(PIT_MAX)).0 > 0 {
max = Word16(PIT_MAX);
min = sub(ctx, max, range);
}
LagWindow { min, max }
}
#[must_use]
pub fn absolute_lag(ctx: &mut DspContext, index: Word16, resolution: LagResolution) -> PitchLag {
match resolution {
LagResolution::OneThird => {
if sub(ctx, index, Word16(197)).0 < 0 {
let biased = add(ctx, index, Word16(2));
let quotient = mult(ctx, biased, THIRD);
let integer = add(ctx, quotient, Word16(19));
let tripled = triple(ctx, integer);
let residue = sub(ctx, index, tripled);
let frac = add(ctx, residue, Word16(58));
PitchLag {
integer,
frac,
resolution,
}
} else {
PitchLag::integral(sub(ctx, index, Word16(112)), resolution)
}
}
LagResolution::OneSixth => {
if sub(ctx, index, Word16(463)).0 < 0 {
let biased = add(ctx, index, Word16(5));
let quotient = mult(ctx, biased, SIXTH);
let integer = add(ctx, quotient, Word16(17));
let tripled = triple(ctx, integer);
let sixfold = add(ctx, tripled, tripled);
let residue = sub(ctx, index, sixfold);
let frac = add(ctx, residue, Word16(105));
PitchLag {
integer,
frac,
resolution,
}
} else {
PitchLag::integral(sub(ctx, index, Word16(368)), resolution)
}
}
}
}
fn triple(ctx: &mut DspContext, v: Word16) -> Word16 {
let doubled = add(ctx, v, v);
add(ctx, doubled, v)
}
#[must_use]
pub fn delta_lag_1_3(
ctx: &mut DspContext,
index: Word16,
window: LagWindow,
coding: DeltaCoding,
) -> PitchLag {
let resolution = LagResolution::OneThird;
match coding {
DeltaCoding::Uniform => {
let biased = add(ctx, index, Word16(2));
let quotient = mult(ctx, biased, THIRD);
let step = sub(ctx, quotient, Word16(1));
let integer = add(ctx, step, window.min);
let tripled = triple(ctx, step);
let residue = sub(ctx, index, Word16(2));
let frac = sub(ctx, residue, tripled);
PitchLag {
integer,
frac,
resolution,
}
}
DeltaCoding::Anchored { previous_lag } => {
let mut anchor = previous_lag;
let above = sub(ctx, anchor, window.min);
if sub(ctx, above, Word16(5)).0 > 0 {
anchor = add(ctx, window.min, Word16(5));
}
let below = sub(ctx, window.max, anchor);
if sub(ctx, below, Word16(4)).0 > 0 {
anchor = sub(ctx, window.max, Word16(4));
}
if sub(ctx, index, Word16(4)).0 < 0 {
let base = sub(ctx, anchor, Word16(5));
PitchLag::integral(add(ctx, base, index), resolution)
} else if sub(ctx, index, Word16(12)).0 < 0 {
let biased = sub(ctx, index, Word16(5));
let quotient = mult(ctx, biased, THIRD);
let step = sub(ctx, quotient, Word16(1));
let integer = add(ctx, step, anchor);
let tripled = triple(ctx, step);
let residue = sub(ctx, index, Word16(9));
let frac = sub(ctx, residue, tripled);
PitchLag {
integer,
frac,
resolution,
}
} else {
let above_anchor = sub(ctx, index, Word16(12));
let base = add(ctx, above_anchor, anchor);
PitchLag::integral(add(ctx, base, Word16(1)), resolution)
}
}
}
}
#[must_use]
pub fn delta_lag_1_6(ctx: &mut DspContext, index: Word16, previous_lag: Word16) -> PitchLag {
let mut min = sub(ctx, previous_lag, Word16(5));
if sub(ctx, min, Word16(PIT_MIN_MR122)).0 < 0 {
min = Word16(PIT_MIN_MR122);
}
let max = add(ctx, min, Word16(9));
if sub(ctx, max, Word16(PIT_MAX)).0 > 0 {
min = sub(ctx, Word16(PIT_MAX), Word16(9));
}
let biased = add(ctx, index, Word16(5));
let quotient = mult(ctx, biased, SIXTH);
let step = sub(ctx, quotient, Word16(1));
let integer = add(ctx, step, min);
let tripled = triple(ctx, step);
let sixfold = add(ctx, tripled, tripled);
let residue = sub(ctx, index, Word16(3));
let frac = sub(ctx, residue, sixfold);
PitchLag {
integer,
frac,
resolution: LagResolution::OneSixth,
}
}
#[derive(Debug, Clone)]
pub struct Excitation {
samples: [Word16; EXC_LEN],
}
impl Default for Excitation {
fn default() -> Self {
Self::new()
}
}
impl Excitation {
#[must_use]
pub const fn new() -> Self {
Self {
samples: [Word16(0); EXC_LEN],
}
}
pub const fn reset(&mut self) {
self.samples = [Word16(0); EXC_LEN];
}
#[must_use]
pub fn subframe(&self) -> &[Word16] {
&self.samples[HISTORY..]
}
pub fn subframe_mut(&mut self) -> &mut [Word16] {
&mut self.samples[HISTORY..]
}
#[must_use]
pub const fn all(&self) -> &[Word16] {
&self.samples
}
pub const fn all_mut(&mut self) -> &mut [Word16] {
&mut self.samples
}
pub fn advance(&mut self) {
self.samples.copy_within(L_SUBFR.., 0);
}
pub fn predict(&mut self, ctx: &mut DspContext, lag: PitchLag) {
let mut phase = negate(ctx, lag.frac);
if lag.resolution == LagResolution::OneThird {
phase = shl(ctx, phase, 1);
}
let back = if phase.0 < 0 {
phase = add(ctx, phase, Word16(PHASE_COUNT));
1
} else {
0
};
let reach = usize::try_from(lag.integer.0)
.ok()
.and_then(|integer| integer.checked_add(back))
.filter(|reach| (TAPS_PER_SIDE..=HISTORY - TAPS_PER_SIDE + 1).contains(reach))
.unwrap_or_else(|| {
panic!("pitch lag {} is outside the decodable range", lag.integer.0)
});
let base = HISTORY - reach;
let lead = usize::try_from(phase.0).expect("interpolation phase is 0..=5");
for j in 0..L_SUBFR {
let trail =
usize::try_from(sub(ctx, Word16(PHASE_COUNT), phase).0).expect("phase complement");
let backward = base + j;
let forward = backward + 1;
let mut acc = Word32(0);
for i in 0..TAPS_PER_SIDE {
let k = i * UP_SAMP_MAX;
acc = l_mac(
ctx,
acc,
self.samples[backward - i],
Word16(INTER_6_PRED[lead + k]),
);
acc = l_mac(
ctx,
acc,
self.samples[forward + i],
Word16(INTER_6_PRED[trail + k]),
);
}
self.samples[HISTORY + j] = round(ctx, acc);
}
}
}
#[cfg(test)]
mod tests {
use super::super::decoder_tables::{INTER_6_PRED, INTER_6_SEARCH};
use super::super::vectors;
use super::{
absolute_lag, delta_coding, delta_lag_1_3, delta_lag_1_6, delta_window, is_delta_coded,
DeltaCoding, Excitation, LagResolution, LagWindow, PitchLag, EXC_LEN, FIR_SIZE, HISTORY,
MODE_MR795, TAPS_PER_SIDE, UP_SAMP_MAX,
};
use super::{L_INTERPOL, L_SUBFR, PIT_MAX, PIT_MIN};
use crate::fixed_point::types::{DspContext, Word16};
fn encode_lag_1_3(lag: PitchLag, window: LagWindow, coding: Option<DeltaCoding>) -> i32 {
let t0 = i32::from(lag.integer.0);
let frac = i32::from(lag.frac.0);
match coding {
None => {
if t0 <= 85 {
3 * t0 - 58 + frac
} else {
t0 + 112
}
}
Some(DeltaCoding::Uniform) => 3 * (t0 - i32::from(window.min.0)) + 2 + frac,
Some(DeltaCoding::Anchored { previous_lag }) => {
let min = i32::from(window.min.0);
let max = i32::from(window.max.0);
let mut anchor = i32::from(previous_lag.0);
if anchor - min > 5 {
anchor = min + 5;
}
if max - anchor > 4 {
anchor = max - 4;
}
let uplag = 3 * t0 + frac;
let low = 3 * (anchor - 2);
if low >= uplag {
t0 - anchor + 5
} else if 3 * (anchor + 1) > uplag {
uplag - low + 3
} else {
t0 - anchor + 11
}
}
}
}
fn encode_lag_1_6(lag: PitchLag, min: Option<i32>) -> i32 {
let t0 = i32::from(lag.integer.0);
let frac = i32::from(lag.frac.0);
min.map_or_else(
|| {
if t0 <= 94 {
6 * t0 - 105 + frac
} else {
t0 + 368
}
},
|min| 6 * (t0 - min) + 3 + frac,
)
}
fn window(min: i16, max: i16) -> LagWindow {
LagWindow {
min: Word16(min),
max: Word16(max),
}
}
#[test]
fn one_third_lag_decoding_is_bit_exact_against_ts26073() {
let rows = vectors::rows("lag3");
assert_eq!(rows.len() % 2, 0, "lag3 rows come in case/out pairs");
let mut ctx = DspContext::default();
let mut cases = 0;
let mut compared = 0;
for pair in rows.chunks_exact(2) {
assert_eq!(pair[0].label, "case");
assert_eq!(pair[1].label, "out");
let c = pair[0].i16s();
let (four_bit, delta, previous, min, max, count) =
(c[0] != 0, c[1] != 0, c[2], c[3], c[4], c[5]);
let out = pair[1].i16s();
let count = usize::try_from(count).expect("index count");
assert_eq!(out.len(), 2 * count, "case {cases}: out length");
let win = window(min, max);
let coding = if four_bit {
DeltaCoding::Anchored {
previous_lag: Word16(previous),
}
} else {
DeltaCoding::Uniform
};
for index in 0..count {
let i = i16::try_from(index).expect("index fits");
let got = if delta {
delta_lag_1_3(&mut ctx, Word16(i), win, coding)
} else {
absolute_lag(&mut ctx, Word16(i), LagResolution::OneThird)
};
assert_eq!(
(got.integer.0, got.frac.0),
(out[2 * index], out[2 * index + 1]),
"four_bit={four_bit} delta={delta} previous={previous} index={index}"
);
compared += 1;
}
cases += 1;
}
assert_eq!(cases, 20, "lag3 sweeps 20 parameter sets");
assert_eq!(compared, 2960, "lag3 covers 2960 indices");
}
#[test]
fn one_sixth_lag_decoding_is_bit_exact_against_ts26073() {
let rows = vectors::rows("lag6");
assert_eq!(rows.len() % 2, 0, "lag6 rows come in case/out pairs");
let mut ctx = DspContext::default();
let mut cases = 0;
let mut compared = 0;
for pair in rows.chunks_exact(2) {
assert_eq!(pair[0].label, "case");
assert_eq!(pair[1].label, "out");
let c = pair[0].i16s();
let (delta, count) = (c[0] != 0, usize::try_from(c[1]).expect("index count"));
let out = pair[1].i16s();
assert_eq!(out.len(), 2 * count, "case {cases}: out length");
for index in 0..count {
let i = i16::try_from(index).expect("index fits");
let got = if delta {
delta_lag_1_6(&mut ctx, Word16(i), Word16(60))
} else {
absolute_lag(&mut ctx, Word16(i), LagResolution::OneSixth)
};
assert_eq!(
(got.integer.0, got.frac.0),
(out[2 * index], out[2 * index + 1]),
"delta={delta} index={index}"
);
compared += 1;
}
cases += 1;
}
assert_eq!(cases, 2, "lag6 sweeps the absolute and delta paths");
assert_eq!(compared, 576, "lag6 covers 576 indices");
}
fn check_predlt(section: &str, resolution: LagResolution, cases_want: usize) {
let rows = vectors::rows(section);
assert_eq!(rows.len() % 2, 0, "{section} rows come in case/out pairs");
let mut ctx = DspContext::default();
let mut cases = 0;
let mut compared = 0;
for pair in rows.chunks_exact(2) {
assert_eq!(pair[0].label, "case");
assert_eq!(pair[1].label, "out");
let c = pair[0].i16s();
let (t0, frac, seed) = (c[0], c[1], c[2]);
let want = pair[1].i16s();
assert_eq!(want.len(), L_SUBFR, "{section}: 40 output samples");
let mut exc = Excitation::new();
let noise = vectors::noise(seed, EXC_LEN, 3);
exc.all_mut().copy_from_slice(&noise);
exc.predict(
&mut ctx,
PitchLag {
integer: Word16(t0),
frac: Word16(frac),
resolution,
},
);
for (j, (&got, &w)) in exc.subframe().iter().zip(want.iter()).enumerate() {
assert_eq!(got.0, w, "{section}: T0={t0} frac={frac} sample {j}");
compared += 1;
}
cases += 1;
}
assert_eq!(cases, cases_want, "{section} case count");
assert_eq!(compared, cases_want * L_SUBFR, "{section} sample count");
}
#[test]
fn one_third_adaptive_codebook_is_bit_exact_against_ts26073() {
check_predlt("predlt3", LagResolution::OneThird, 24);
}
#[test]
fn one_sixth_adaptive_codebook_is_bit_exact_against_ts26073() {
check_predlt("predlt6", LagResolution::OneSixth, 48);
}
#[test]
fn every_index_round_trips_through_the_encoders_mapping() {
let mut ctx = DspContext::default();
let mut checked = 0;
for index in 0..256i16 {
let lag = absolute_lag(&mut ctx, Word16(index), LagResolution::OneThird);
assert_eq!(
encode_lag_1_3(lag, window(0, 0), None),
i32::from(index),
"absolute 1/3 index {index}"
);
checked += 1;
}
for index in 0..512i16 {
let lag = absolute_lag(&mut ctx, Word16(index), LagResolution::OneSixth);
assert_eq!(
encode_lag_1_6(lag, None),
i32::from(index),
"absolute 1/6 index {index}"
);
checked += 1;
}
for previous in [PIT_MIN, 40, 90, PIT_MAX] {
for mode_index in [4u8, MODE_MR795, 6] {
let win = delta_window(&mut ctx, mode_index, Word16(previous));
for index in 0..64i16 {
let lag = delta_lag_1_3(&mut ctx, Word16(index), win, DeltaCoding::Uniform);
assert_eq!(
encode_lag_1_3(lag, win, Some(DeltaCoding::Uniform)),
i32::from(index),
"uniform delta mode {mode_index} previous {previous} index {index}"
);
checked += 1;
}
}
let win = delta_window(&mut ctx, 0, Word16(previous));
let coding = DeltaCoding::Anchored {
previous_lag: Word16(previous),
};
for index in 0..16i16 {
let lag = delta_lag_1_3(&mut ctx, Word16(index), win, coding);
assert_eq!(
encode_lag_1_3(lag, win, Some(coding)),
i32::from(index),
"anchored delta previous {previous} index {index}"
);
checked += 1;
}
for index in 0..64i16 {
let lag = delta_lag_1_6(&mut ctx, Word16(index), Word16(previous));
let min = i32::from(
delta_lag_1_6(&mut ctx, Word16(3), Word16(previous))
.integer
.0,
);
assert_eq!(
encode_lag_1_6(lag, Some(min)),
i32::from(index),
"delta 1/6 previous {previous} index {index}"
);
checked += 1;
}
}
assert_eq!(checked, 256 + 512 + 4 * (3 * 64 + 16 + 64));
}
#[test]
fn lag_decoding_is_strictly_monotone_in_the_index() {
let mut ctx = DspContext::default();
let mut previous = i32::MIN;
for index in 0..256i16 {
let lag = absolute_lag(&mut ctx, Word16(index), LagResolution::OneThird);
assert!(lag.units() > previous, "absolute 1/3 index {index}");
previous = lag.units();
}
let mut previous = i32::MIN;
for index in 0..512i16 {
let lag = absolute_lag(&mut ctx, Word16(index), LagResolution::OneSixth);
assert!(lag.units() > previous, "absolute 1/6 index {index}");
previous = lag.units();
}
let win = delta_window(&mut ctx, 0, Word16(60));
let coding = DeltaCoding::Anchored {
previous_lag: Word16(60),
};
let mut previous = i32::MIN;
for index in 0..16i16 {
let lag = delta_lag_1_3(&mut ctx, Word16(index), win, coding);
assert!(lag.units() > previous, "anchored index {index}");
previous = lag.units();
}
}
#[test]
fn the_four_bit_codes_span_exactly_the_search_window() {
let mut ctx = DspContext::default();
for previous in 1..=PIT_MAX {
let win = delta_window(&mut ctx, 0, Word16(previous));
let coding = delta_coding(0, Word16(previous));
let lowest = delta_lag_1_3(&mut ctx, Word16(0), win, coding);
let highest = delta_lag_1_3(&mut ctx, Word16(15), win, coding);
assert_eq!(lowest.integer, win.min, "previous {previous}");
assert_eq!(lowest.frac.0, 0);
assert_eq!(highest.integer, win.max, "previous {previous}");
assert_eq!(highest.frac.0, 0);
let middle = delta_lag_1_3(&mut ctx, Word16(9), win, coding);
assert_eq!(middle.integer.0, win.min.0 + 5, "previous {previous}");
assert_eq!(middle.frac.0, 0);
}
}
#[test]
fn the_delta_window_keeps_its_width_inside_the_lag_range() {
let mut ctx = DspContext::default();
for mode_index in 0..8u8 {
if mode_index == 7 {
continue; }
let width = if mode_index == MODE_MR795 { 19 } else { 9 };
for previous in 1..=200i16 {
let win = delta_window(&mut ctx, mode_index, Word16(previous));
assert_eq!(
win.max.0 - win.min.0,
width,
"mode {mode_index} previous {previous}"
);
assert!(
win.min.0 >= PIT_MIN,
"mode {mode_index} previous {previous}"
);
assert!(
win.max.0 <= PIT_MAX,
"mode {mode_index} previous {previous}"
);
}
}
}
#[test]
fn only_the_two_lowest_rates_delta_code_the_third_subframe() {
for mode_index in 0..8u8 {
assert!(!is_delta_coded(mode_index, 0));
assert!(is_delta_coded(mode_index, 1));
assert!(is_delta_coded(mode_index, 3));
assert_eq!(
is_delta_coded(mode_index, 2),
mode_index <= 1,
"mode {mode_index} subframe 2"
);
}
}
fn snapshot_filter(
frozen: &[Word16],
t0: i16,
frac: i16,
resolution: LagResolution,
) -> Vec<i16> {
let mut phase = i32::from(-frac);
if resolution == LagResolution::OneThird {
phase *= 2;
}
let back = if phase < 0 {
phase += 6;
1i32
} else {
0
};
let base = i32::try_from(HISTORY).expect("fits") - i32::from(t0) - back;
let tap = |p: i32, k: usize| -> i64 {
i64::from(INTER_6_PRED[usize::try_from(p).expect("phase") + k * 6])
};
(0..L_SUBFR)
.map(|j| {
let centre = base + i32::try_from(j).expect("fits");
let mut acc = 0i64;
for k in 0..TAPS_PER_SIDE {
let lo = usize::try_from(centre - i32::try_from(k).expect("fits"))
.expect("in range");
let hi = usize::try_from(centre + 1 + i32::try_from(k).expect("fits"))
.expect("in range");
acc += 2 * i64::from(frozen[lo].0) * tap(phase, k);
acc += 2 * i64::from(frozen[hi].0) * tap(6 - phase, k);
}
let acc = acc.clamp(i64::from(i32::MIN), i64::from(i32::MAX));
i16::try_from(((acc + 0x8000) >> 16).clamp(-32768, 32767)).expect("fits")
})
.collect()
}
#[test]
fn the_short_lag_recursion_is_observable() {
let mut ctx = DspContext::default();
let noise = vectors::noise(4321, EXC_LEN, 3);
for &(t0, frac, expect_same) in &[
(20i16, 0i16, false),
(20, -1, false),
(25, 1, false),
(39, 0, false),
(50, 0, true),
(50, 1, true),
(90, -1, true),
(PIT_MAX, 0, true),
] {
let lag = PitchLag {
integer: Word16(t0),
frac: Word16(frac),
resolution: LagResolution::OneThird,
};
let mut inplace = Excitation::new();
inplace.all_mut().copy_from_slice(&noise);
inplace.predict(&mut ctx, lag);
let frozen = snapshot_filter(&noise, t0, frac, LagResolution::OneThird);
let got: Vec<i16> = inplace.subframe().iter().map(|s| s.0).collect();
assert_eq!(
got == frozen,
expect_same,
"T0={t0} frac={frac}: in-place and snapshot filtering agreed={}",
got == frozen
);
}
}
#[test]
fn a_constant_history_comes_back_scaled_by_the_filters_dc_gain() {
let mut ctx = DspContext::default();
let mut exc = Excitation::new();
for s in exc.all_mut() {
*s = Word16(1000);
}
exc.predict(
&mut ctx,
PitchLag::integral(Word16(PIT_MAX), LagResolution::OneThird),
);
let dc_gain: i32 = (0..TAPS_PER_SIDE)
.map(|i| {
i32::from(INTER_6_PRED[i * UP_SAMP_MAX])
+ i32::from(INTER_6_PRED[6 + i * UP_SAMP_MAX])
})
.sum();
assert_eq!(dc_gain, 32723);
let want = i16::try_from((2 * 1000 * dc_gain + 0x8000) >> 16).expect("fits");
for (j, s) in exc.subframe().iter().enumerate() {
assert_eq!(s.0, want, "sample {j}");
}
}
#[test]
fn this_is_not_the_encoders_interpolation_filter() {
assert_eq!(FIR_SIZE, 61);
assert_eq!(INTER_6_SEARCH.len(), 25);
assert_ne!(INTER_6_PRED[0], INTER_6_SEARCH[0]);
assert_eq!(INTER_6_PRED[0], 29443);
assert_eq!(INTER_6_SEARCH[0], 29519);
}
#[test]
fn the_buffer_spans_the_whole_reachable_history() {
assert_eq!(
HISTORY,
usize::try_from(PIT_MAX).expect("positive") + L_INTERPOL
);
assert_eq!(EXC_LEN, HISTORY + L_SUBFR);
let mut ctx = DspContext::default();
let win = delta_window(&mut ctx, MODE_MR795, Word16(PIT_MAX));
let lag = delta_lag_1_3(&mut ctx, Word16(63), win, DeltaCoding::Uniform);
assert_eq!((lag.integer.0, lag.frac.0), (144, 1));
let mut exc = Excitation::new();
exc.all_mut()
.copy_from_slice(&vectors::noise(77, EXC_LEN, 3));
exc.predict(&mut ctx, lag);
}
#[test]
fn advancing_moves_the_subframe_into_the_history() {
let mut exc = Excitation::new();
for (i, s) in exc.all_mut().iter_mut().enumerate() {
*s = Word16(i16::try_from(i).expect("fits"));
}
let last = exc.subframe().to_vec();
exc.advance();
assert_eq!(&exc.all()[HISTORY - L_SUBFR..HISTORY], last.as_slice());
}
#[test]
fn resetting_clears_the_history() {
let mut exc = Excitation::new();
for s in exc.all_mut() {
*s = Word16(-5000);
}
exc.reset();
assert!(exc.all().iter().all(|s| s.0 == 0));
}
}