use super::super::decoder_tables::{CORR_WEIGHT, INTER_6_SEARCH, QUA_GAIN_PITCH};
use super::super::lag::{Excitation, LagResolution, LagWindow, PitchLag};
use super::super::lsp::M;
use super::super::math::inv_sqrt;
use super::super::{L_FRAME, L_INTERPOL, L_SUBFR, PIT_MAX, PIT_MIN, PIT_MIN_MR122};
use super::vad::VoiceActivityDetector;
use crate::fixed_point::arith::{abs_s, add, extract_h, extract_l, mult, round, sub};
use crate::fixed_point::arith32::{l_abs, l_mac, l_msu, l_mult, l_sub};
use crate::fixed_point::div::div_s;
use crate::fixed_point::oper32::{l_extract, mpy_32, mpy_32_16};
use crate::fixed_point::shift::{l_shl, l_shr, norm_l, shl, shr};
use crate::fixed_point::types::{DspContext, Word16, Word32, MAX_16, MAX_32, MIN_32};
pub const L_FRAME_BY2: usize = L_FRAME / 2;
pub const L_INTER_SRCH: i16 = 4;
pub const GP_CLIP: Word16 = Word16(15565);
pub const N_FRAME: usize = 7;
pub const EXC_ORIGIN: usize = PIT_MAX as usize + L_INTERPOL;
const THRESHOLD: Word16 = Word16(27853);
const OPEN_LOOP_QUIET: Word32 = Word32(1_048_576);
const NORM_CORR_LOUD: Word32 = Word32(67_108_864);
const UP_SAMP_MAX: i16 = 6;
const INTERP_TAPS: usize = L_INTER_SRCH as usize;
const MR475: u8 = 0;
const MR515: u8 = 1;
const MR59: u8 = 2;
const MR67: u8 = 3;
const MR795: u8 = 5;
const MR102: u8 = 6;
const MR122: u8 = 7;
const SCALED_LEN: usize = PIT_MAX as usize + L_FRAME;
struct Scaled {
samples: [Word16; SCALED_LEN],
origin: usize,
factor: i16,
}
impl Scaled {
fn at(&self, offset: i16) -> Word16 {
let index = isize::try_from(self.origin).expect("origin fits") + isize::from(offset);
self.samples[usize::try_from(index).expect("open-loop read stays inside the window")]
}
}
fn scale_for_correlation(
ctx: &mut DspContext,
signal: &[Word16],
origin: usize,
l_frame: usize,
) -> Scaled {
let mut energy = Word32(0);
for i in 0..PIT_MAX as usize + l_frame {
let s = signal[origin - PIT_MAX as usize + i];
energy = l_mac(ctx, energy, s, s);
}
let factor = if l_sub(ctx, energy, Word32(MAX_32)).0 == 0 {
3
} else if l_sub(ctx, energy, OPEN_LOOP_QUIET).0 < 0 {
-3
} else {
0
};
let mut samples = [Word16(0); SCALED_LEN];
for i in 0..PIT_MAX as usize + l_frame {
samples[i] = shr(ctx, signal[origin - PIT_MAX as usize + i], factor);
}
Scaled {
samples,
origin: PIT_MAX as usize,
factor,
}
}
fn correlations(
ctx: &mut DspContext,
scaled: &Scaled,
l_frame: usize,
pit_min: i16,
) -> [Word32; PIT_MAX as usize + 1] {
let mut corr = [Word32(0); PIT_MAX as usize + 1];
for lag in (pit_min..=PIT_MAX).rev() {
let mut acc = Word32(0);
for j in 0..l_frame {
let j = i16::try_from(j).expect("frame length fits in i16");
acc = l_mac(ctx, acc, scaled.at(j), scaled.at(j - lag));
}
corr[usize::try_from(lag).expect("lag is positive")] = acc;
}
corr
}
#[must_use]
pub fn peak_lag(
ctx: &mut DspContext,
corr: &[Word32; PIT_MAX as usize + 1],
lag_max: i16,
lag_min: i16,
) -> i16 {
let mut best = Word32(MIN_32);
let mut chosen = lag_max;
for lag in (lag_min..=lag_max).rev() {
let value = corr[usize::try_from(lag).expect("lag is positive")];
if l_sub(ctx, value, best).0 >= 0 {
best = value;
chosen = lag;
}
}
chosen
}
#[allow(clippy::too_many_arguments)]
fn section_peak(
ctx: &mut DspContext,
corr: &[Word32; PIT_MAX as usize + 1],
scaled: &Scaled,
efr_scaling: bool,
l_frame: usize,
lag_max: i16,
lag_min: i16,
vad: Option<&mut VoiceActivityDetector>,
) -> (i16, Word16) {
let chosen = peak_lag(ctx, corr, lag_max, lag_min);
let best = corr[usize::try_from(chosen).expect("lag is positive")];
let mut energy = Word32(0);
for i in 0..l_frame {
let s = scaled.at(i16::try_from(i).expect("frame length fits in i16") - chosen);
energy = l_mac(ctx, energy, s, s);
}
if let Some(vad) = vad {
vad.observe_tone(ctx, best, energy);
}
let mut inverse = inv_sqrt(ctx, energy);
if efr_scaling {
inverse = l_shl(ctx, inverse, 1);
}
let (best_hi, best_lo) = l_extract(best);
let (energy_hi, energy_lo) = l_extract(inverse);
let mut product = mpy_32(best_hi, best_lo, energy_hi, energy_lo);
let normalised = if efr_scaling {
product = l_shr(ctx, product, scaled.factor);
extract_h(l_shl(ctx, product, 15))
} else {
extract_l(product)
};
(chosen, normalised)
}
#[must_use]
pub fn arbitrate_sections(
ctx: &mut DspContext,
long: (i16, Word16),
middle: (i16, Word16),
short: (i16, Word16),
) -> i16 {
let (mut lag, mut best) = long;
let handicapped = mult(ctx, best, THRESHOLD);
if sub(ctx, handicapped, middle.1).0 < 0 {
best = middle.1;
lag = middle.0;
}
let handicapped = mult(ctx, best, THRESHOLD);
if sub(ctx, handicapped, short.1).0 < 0 {
lag = short.0;
}
lag
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn open_loop_lag(
ctx: &mut DspContext,
mode_index: u8,
signal: &[Word16],
origin: usize,
l_frame: usize,
mut vad: Option<&mut VoiceActivityDetector>,
second_half: bool,
) -> i16 {
let pit_min = if mode_index == MR122 {
PIT_MIN_MR122
} else {
PIT_MIN
};
if let Some(vad) = vad.as_deref_mut() {
vad.shift_tone_register(ctx, matches!(mode_index, MR475 | MR515));
}
let scaled = scale_for_correlation(ctx, signal, origin, l_frame);
let corr = correlations(ctx, &scaled, l_frame, pit_min);
let efr_scaling = mode_index == MR122;
let quarter = shl(ctx, Word16(pit_min), 2).0;
let half = shl(ctx, Word16(pit_min), 1).0;
let long = section_peak(
ctx,
&corr,
&scaled,
efr_scaling,
l_frame,
PIT_MAX,
quarter,
vad.as_deref_mut(),
);
let middle = section_peak(
ctx,
&corr,
&scaled,
efr_scaling,
l_frame,
quarter - 1,
half,
vad.as_deref_mut(),
);
let short = section_peak(
ctx,
&corr,
&scaled,
efr_scaling,
l_frame,
half - 1,
pit_min,
vad.as_deref_mut(),
);
if let Some(vad) = vad {
if second_half {
let correlation = high_pass_correlation(ctx, &corr, &scaled, l_frame, PIT_MAX, pit_min);
vad.observe_correlation(correlation);
}
}
arbitrate_sections(ctx, long, middle, short)
}
fn high_pass_correlation(
ctx: &mut DspContext,
corr: &[Word32; PIT_MAX as usize + 1],
scaled: &Scaled,
l_frame: usize,
lag_max: i16,
lag_min: i16,
) -> Word16 {
let mut max = Word32(MIN_32);
for lag in (lag_min + 1..lag_max).rev() {
let at = |l: i16| corr[usize::try_from(l).expect("lag is positive")];
let doubled = l_shl(ctx, at(lag), 1);
let above = l_sub(ctx, doubled, at(lag + 1));
let t = l_sub(ctx, above, at(lag - 1));
let t = l_abs(ctx, t);
if l_sub(ctx, t, max).0 >= 0 {
max = t;
}
}
let mut energy = Word32(0);
let mut lagged = Word32(0);
for i in 0..l_frame {
let i = i16::try_from(i).expect("frame length fits in i16");
let here = scaled.at(i);
energy = l_mac(ctx, energy, here, here);
lagged = l_mac(ctx, lagged, here, scaled.at(i - 1));
}
let doubled_energy = l_shl(ctx, energy, 1);
let doubled_lagged = l_shl(ctx, lagged, 1);
let difference = l_sub(ctx, doubled_energy, doubled_lagged);
let denominator = l_abs(ctx, difference);
let shift_num = sub(ctx, Word16(norm_l(max)), Word16(1));
let numerator = extract_h(l_shl(ctx, max, shift_num.0));
let shift_den = norm_l(denominator);
let scaled_den = extract_h(l_shl(ctx, denominator, shift_den));
let quotient = if scaled_den.0 == 0 {
Word16(0)
} else {
div_s(numerator, scaled_den)
};
let shift = sub(ctx, shift_num, Word16(shift_den));
if shift.0 >= 0 {
shr(ctx, quotient, shift.0)
} else {
shl(ctx, quotient, -shift.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WeightedOpenLoop {
old_t0_med: Word16,
ada_w: Word16,
weighting_armed: bool,
}
impl Default for WeightedOpenLoop {
fn default() -> Self {
Self::new()
}
}
impl WeightedOpenLoop {
#[must_use]
pub const fn new() -> Self {
Self {
old_t0_med: Word16(40),
ada_w: Word16(0),
weighting_armed: false,
}
}
#[allow(clippy::too_many_arguments)]
pub fn search(
&mut self,
ctx: &mut DspContext,
signal: &[Word16],
origin: usize,
l_frame: usize,
old_lags: &mut [Word16; 5],
voiced: &mut bool,
mut vad: Option<&mut VoiceActivityDetector>,
second_half: bool,
) -> i16 {
let scaled = scale_for_correlation(ctx, signal, origin, l_frame);
let corr = correlations(ctx, &scaled, l_frame, PIT_MIN);
let chosen = self.weighted_peak(ctx, &corr, &scaled, l_frame, voiced, vad.as_deref_mut());
if let Some(vad) = vad {
if second_half {
let correlation =
high_pass_correlation(ctx, &corr, &scaled, l_frame, PIT_MAX, PIT_MIN);
vad.observe_correlation(correlation);
}
}
if *voiced {
old_lags.copy_within(0..4, 1);
old_lags[0] = Word16(chosen);
self.old_t0_med = median_of_five(ctx, old_lags);
self.ada_w = Word16(MAX_16);
} else {
self.old_t0_med = Word16(chosen);
self.ada_w = mult(ctx, self.ada_w, Word16(29491));
}
self.weighting_armed = sub(ctx, self.ada_w, Word16(9830)).0 >= 0;
chosen
}
fn weighted_peak(
self,
ctx: &mut DspContext,
corr: &[Word32; PIT_MAX as usize + 1],
scaled: &Scaled,
l_frame: usize,
voiced: &mut bool,
vad: Option<&mut VoiceActivityDetector>,
) -> i16 {
let mut fixed = CORR_WEIGHT.len() - 1;
let mut near = usize::try_from(123 + i32::from(PIT_MAX) - i32::from(self.old_t0_med.0))
.ok()
.filter(|start| *start < CORR_WEIGHT.len())
.expect("the lag history stayed inside the correlation weighting table");
let mut best = Word32(MIN_32);
let mut chosen = PIT_MAX;
for lag in (PIT_MIN..=PIT_MAX).rev() {
let raw = corr[usize::try_from(lag).expect("lag is positive")];
let (hi, lo) = l_extract(raw);
let mut weighted = mpy_32_16(hi, lo, Word16(CORR_WEIGHT[fixed]));
fixed = fixed.saturating_sub(1);
if self.weighting_armed {
let (hi, lo) = l_extract(weighted);
weighted = mpy_32_16(hi, lo, Word16(CORR_WEIGHT[near]));
near = near.saturating_sub(1);
}
if l_sub(ctx, weighted, best).0 >= 0 {
best = weighted;
chosen = lag;
}
}
let mut cross = Word32(0);
let mut delayed = Word32(0);
for j in 0..l_frame {
let j = i16::try_from(j).expect("frame length fits in i16");
let here = scaled.at(j);
let there = scaled.at(j - chosen);
cross = l_mac(ctx, cross, here, there);
delayed = l_mac(ctx, delayed, there, there);
}
if let Some(vad) = vad {
vad.shift_tone_register(ctx, false);
vad.observe_tone(ctx, cross, delayed);
}
let rounded = round(ctx, delayed);
let excess = l_msu(ctx, cross, rounded, Word16(13107));
*voiced = round(ctx, excess).0 > 0;
chosen
}
}
#[must_use]
pub fn median_of_five(ctx: &mut DspContext, values: &[Word16; 5]) -> Word16 {
let mut remaining = *values;
let mut rank = [0usize; 5];
let mut chosen = 0usize;
for slot in &mut rank {
let mut largest = Word16(-32767);
for (j, &candidate) in remaining.iter().enumerate() {
if sub(ctx, candidate, largest).0 >= 0 {
largest = candidate;
chosen = j;
}
}
remaining[chosen] = Word16(MIN_16_VALUE);
*slot = chosen;
}
values[rank[5 / 2]]
}
const MIN_16_VALUE: i16 = -32768;
#[allow(clippy::too_many_arguments)]
pub fn open_loop_lags(
ctx: &mut DspContext,
mode_index: u8,
weighted: &mut WeightedOpenLoop,
wsp: &[Word16],
origin: usize,
old_lags: &mut [Word16; 5],
voiced: &mut [bool; 2],
mut vad: Option<&mut VoiceActivityDetector>,
) -> [i16; 2] {
if mode_index != MR102 {
voiced[0] = false;
voiced[1] = false;
}
if mode_index == MR475 || mode_index == MR515 {
let lag = open_loop_lag(ctx, mode_index, wsp, origin, L_FRAME, vad, true);
return [lag, lag];
}
let mut lags = [0i16; 2];
for (half, slot) in lags.iter_mut().enumerate() {
let at = origin + half * L_FRAME_BY2;
*slot = if mode_index == MR102 {
let mut flag = voiced[half];
let lag = weighted.search(
ctx,
wsp,
at,
L_FRAME_BY2,
old_lags,
&mut flag,
vad.as_deref_mut(),
half == 1,
);
voiced[half] = flag;
lag
} else {
open_loop_lag(
ctx,
mode_index,
wsp,
at,
L_FRAME_BY2,
vad.as_deref_mut(),
half == 1,
)
};
}
lags
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ModeParams {
pub max_frac_lag: i16,
pub one_third: bool,
pub first_frac: i16,
pub last_frac: i16,
pub delta_int_low: i16,
pub delta_int_range: i16,
pub delta_frc_low: i16,
pub delta_frc_range: i16,
pub pit_min: i16,
}
#[must_use]
pub const fn mode_params(mode_index: u8) -> ModeParams {
assert!(
mode_index <= MR122,
"no closed-loop parameters for this mode"
);
let (max_frac_lag, one_third, first_frac, last_frac, pit_min) = match mode_index {
MR122 => (94, false, -3, 3, PIT_MIN_MR122),
_ => (84, true, -2, 2, PIT_MIN),
};
let (delta_int_low, delta_int_range) = match mode_index {
MR475 | MR515 => (5, 10),
_ => (3, 6),
};
let (delta_frc_low, delta_frc_range) = match mode_index {
MR795 => (10, 19),
_ => (5, 9),
};
ModeParams {
max_frac_lag,
one_third,
first_frac,
last_frac,
delta_int_low,
delta_int_range,
delta_frc_low,
delta_frc_range,
pit_min,
}
}
#[must_use]
pub fn lag_window(
ctx: &mut DspContext,
lag: i16,
delta_low: i16,
delta_range: i16,
pit_min: i16,
) -> LagWindow {
let mut min = sub(ctx, Word16(lag), Word16(delta_low));
if sub(ctx, min, Word16(pit_min)).0 < 0 {
min = Word16(pit_min);
}
let mut max = add(ctx, min, Word16(delta_range));
if sub(ctx, max, Word16(PIT_MAX)).0 > 0 {
max = Word16(PIT_MAX);
min = sub(ctx, max, Word16(delta_range));
}
LagWindow { min, max }
}
#[must_use]
pub fn convolve(ctx: &mut DspContext, x: &[Word16], h: &[Word16]) -> [Word16; L_SUBFR] {
assert!(
x.len() >= L_SUBFR && h.len() >= L_SUBFR,
"convolve needs a full subframe"
);
let mut y = [Word16(0); L_SUBFR];
for n in 0..L_SUBFR {
let mut s = Word32(0);
for i in 0..=n {
s = l_mac(ctx, s, x[i], h[n - i]);
}
y[n] = extract_h(l_shl(ctx, s, 3));
}
y
}
#[derive(Debug, Clone, Copy)]
pub struct Correlations {
first: i16,
last: i16,
values: [Word16; 40],
}
impl Correlations {
#[must_use]
pub fn at(&self, lag: i16) -> Word16 {
assert!(
lag >= self.first && lag <= self.last,
"lag {lag} is outside the correlated window {}..={}",
self.first,
self.last
);
self.values[usize::try_from(lag - self.first).expect("in-window offset")]
}
}
#[must_use]
pub fn normalised_correlation(
ctx: &mut DspContext,
exc: &[Word16],
origin: usize,
xn: &[Word16],
h: &[Word16],
t_min: i16,
t_max: i16,
) -> Correlations {
let width = usize::try_from(t_max - t_min + 1).expect("a non-empty window");
assert!(
width <= 40,
"the closed-loop window is at most 40 lags wide"
);
assert!(
xn.len() >= L_SUBFR && h.len() >= L_SUBFR,
"the target and impulse response are one subframe each"
);
let mut back = usize::try_from(i32::from(t_min)).expect("delays are positive");
let start = origin
.checked_sub(back)
.expect("excitation history is too short");
let mut filtered = convolve(ctx, &exc[start..start + L_SUBFR], h);
let mut quartered = [Word16(0); L_SUBFR];
for (slot, &value) in quartered.iter_mut().zip(filtered.iter()) {
*slot = shr(ctx, value, 2);
}
let mut energy = Word32(0);
for &value in &filtered {
energy = l_mac(ctx, energy, value, value);
}
let loud = l_sub(ctx, energy, NORM_CORR_LOUD).0 > 0;
if loud {
filtered = quartered;
}
let h_fac = if loud { 1 } else { 3 };
let scaling = if loud { 2 } else { 0 };
let mut values = [Word16(0); 40];
for lag in t_min..=t_max {
let mut energy = Word32(0);
for &value in &filtered {
energy = l_mac(ctx, energy, value, value);
}
let (norm_hi, norm_lo) = l_extract(inv_sqrt(ctx, energy));
let mut cross = Word32(0);
for (j, &value) in filtered.iter().enumerate() {
cross = l_mac(ctx, cross, xn[j], value);
}
let (cross_hi, cross_lo) = l_extract(cross);
let s = mpy_32(cross_hi, cross_lo, norm_hi, norm_lo);
values[usize::try_from(lag - t_min).expect("in-window offset")] =
extract_h(l_shl(ctx, s, 16));
if lag != t_max {
back += 1;
let tap = origin - back;
for j in (1..L_SUBFR).rev() {
let product = l_mult(ctx, exc[tap], h[j]);
let term = extract_h(l_shl(ctx, product, h_fac));
filtered[j] = add(ctx, term, filtered[j - 1]);
}
filtered[0] = shr(ctx, exc[tap], scaling);
}
}
Correlations {
first: t_min,
last: t_max,
values,
}
}
#[must_use]
pub fn interpolate(
ctx: &mut DspContext,
corr: &Correlations,
lag: i16,
frac: i16,
one_third: bool,
) -> Word16 {
let mut phase = if one_third {
shl(ctx, Word16(frac), 1).0
} else {
frac
};
let mut centre = lag;
if phase < 0 {
phase += UP_SAMP_MAX;
centre -= 1;
}
let phase = usize::try_from(phase).expect("the phase is folded non-negative");
let mirror = usize::try_from(UP_SAMP_MAX).expect("six is positive") - phase;
let mut s = Word32(0);
for i in 0..INTERP_TAPS {
let k = i * usize::try_from(UP_SAMP_MAX).expect("six is positive");
let step = i16::try_from(i).expect("four taps fit in i16");
s = l_mac(
ctx,
s,
corr.at(centre - step),
Word16(INTER_6_SEARCH[phase + k]),
);
s = l_mac(
ctx,
s,
corr.at(centre + 1 + step),
Word16(INTER_6_SEARCH[mirror + k]),
);
}
round(ctx, s)
}
#[must_use]
pub fn search_fraction(
ctx: &mut DspContext,
lag: i16,
frac: i16,
last_frac: i16,
corr: &Correlations,
one_third: bool,
) -> (i16, i16) {
let mut lag = lag;
let mut frac = frac;
let mut best = interpolate(ctx, corr, lag, frac, one_third);
let mut candidate = frac + 1;
while candidate <= last_frac {
let value = interpolate(ctx, corr, lag, candidate, one_third);
if sub(ctx, value, best).0 > 0 {
best = value;
frac = candidate;
}
candidate += 1;
}
if one_third {
if frac == -2 {
frac = 1;
lag = sub(ctx, Word16(lag), Word16(1)).0;
}
if frac == 2 {
frac = -1;
lag = add(ctx, Word16(lag), Word16(1)).0;
}
} else if frac == -3 {
frac = 3;
lag = sub(ctx, Word16(lag), Word16(1)).0;
}
(lag, frac)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClosedLoopResult {
pub lag: PitchLag,
pub index: u16,
pub window: LagWindow,
pub delta: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ClosedLoopPitch {
previous_lag: Word16,
}
impl ClosedLoopPitch {
#[must_use]
pub const fn new() -> Self {
Self {
previous_lag: Word16(0),
}
}
#[must_use]
pub const fn previous_lag(&self) -> Word16 {
self.previous_lag
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
pub fn search(
&mut self,
ctx: &mut DspContext,
mode_index: u8,
open_loop: [i16; 2],
exc: &[Word16],
origin: usize,
xn: &[Word16],
h1: &[Word16],
subframe: usize,
) -> ClosedLoopResult {
let parm = mode_params(mode_index);
let four_bit = matches!(mode_index, MR475 | MR515 | MR59 | MR67);
let even = subframe.is_multiple_of(2);
let delta = !even || (subframe == 2 && matches!(mode_index, MR475 | MR515));
let window = if delta {
lag_window(
ctx,
self.previous_lag.0,
parm.delta_frc_low,
parm.delta_frc_range,
parm.pit_min,
)
} else {
lag_window(
ctx,
open_loop[subframe / 2],
parm.delta_int_low,
parm.delta_int_range,
parm.pit_min,
)
};
let t_min = sub(ctx, window.min, Word16(L_INTER_SRCH)).0;
let t_max = add(ctx, window.max, Word16(L_INTER_SRCH)).0;
let corr = normalised_correlation(ctx, exc, origin, xn, h1, t_min, t_max);
let mut best = corr.at(window.min.0);
let mut lag = window.min.0;
for candidate in window.min.0 + 1..=window.max.0 {
if sub(ctx, corr.at(candidate), best).0 >= 0 {
best = corr.at(candidate);
lag = candidate;
}
}
let mut frac = parm.first_frac;
let mut last_frac = parm.last_frac;
if !delta && sub(ctx, Word16(lag), Word16(parm.max_frac_lag)).0 > 0 {
frac = 0;
} else if delta && four_bit {
let anchor = four_bit_anchor(ctx, self.previous_lag.0, window);
if lag == anchor || lag == anchor - 1 {
let (l, f) = search_fraction(ctx, lag, frac, last_frac, &corr, parm.one_third);
lag = l;
frac = f;
} else if lag == anchor - 2 {
frac = 0;
let (l, f) = search_fraction(ctx, lag, frac, last_frac, &corr, parm.one_third);
lag = l;
frac = f;
} else if lag == anchor + 1 {
last_frac = 0;
let (l, f) = search_fraction(ctx, lag, frac, last_frac, &corr, parm.one_third);
lag = l;
frac = f;
} else {
frac = 0;
}
} else {
let (l, f) = search_fraction(ctx, lag, frac, last_frac, &corr, parm.one_third);
lag = l;
frac = f;
}
let index = if parm.one_third {
encode_lag_1_3(ctx, lag, frac, self.previous_lag.0, window, delta, four_bit)
} else {
encode_lag_1_6(ctx, lag, frac, window.min.0, delta)
};
self.previous_lag = Word16(lag);
ClosedLoopResult {
lag: PitchLag {
integer: Word16(lag),
frac: Word16(frac),
resolution: if parm.one_third {
LagResolution::OneThird
} else {
LagResolution::OneSixth
},
},
index,
window,
delta,
}
}
}
fn four_bit_anchor(ctx: &mut DspContext, previous_lag: i16, window: LagWindow) -> i16 {
let mut anchor = Word16(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));
}
anchor.0
}
fn triple(ctx: &mut DspContext, v: Word16) -> Word16 {
let doubled = add(ctx, v, v);
add(ctx, doubled, v)
}
#[must_use]
pub fn encode_lag_1_3(
ctx: &mut DspContext,
lag: i16,
frac: i16,
previous_lag: i16,
window: LagWindow,
delta: bool,
four_bit: bool,
) -> u16 {
let lag = Word16(lag);
let frac = Word16(frac);
let index = if !delta {
if sub(ctx, lag, Word16(85)).0 <= 0 {
let tripled = triple(ctx, lag);
let biased = sub(ctx, tripled, Word16(58));
add(ctx, biased, frac)
} else {
add(ctx, lag, Word16(112))
}
} else if four_bit {
let anchor = Word16(four_bit_anchor(ctx, previous_lag, window));
let tripled = triple(ctx, lag);
let uplag = add(ctx, tripled, frac);
let two_below = sub(ctx, anchor, Word16(2));
let below = triple(ctx, two_below);
if sub(ctx, below, uplag).0 >= 0 {
let offset = sub(ctx, lag, anchor);
add(ctx, offset, Word16(5))
} else {
let one_above = add(ctx, anchor, Word16(1));
let above = triple(ctx, one_above);
if sub(ctx, above, uplag).0 > 0 {
let offset = sub(ctx, uplag, below);
add(ctx, offset, Word16(3))
} else {
let offset = sub(ctx, lag, anchor);
add(ctx, offset, Word16(11))
}
}
} else {
let offset = sub(ctx, lag, window.min);
let steps = triple(ctx, offset);
let biased = add(ctx, steps, Word16(2));
add(ctx, biased, frac)
};
u16::try_from(index.0).expect("a pitch index is non-negative")
}
#[must_use]
pub fn encode_lag_1_6(ctx: &mut DspContext, lag: i16, frac: i16, t0_min: i16, delta: bool) -> u16 {
let lag = Word16(lag);
let frac = Word16(frac);
let index = if delta {
let offset = sub(ctx, lag, Word16(t0_min));
let tripled = triple(ctx, offset);
let sixfold = add(ctx, tripled, tripled);
let biased = add(ctx, sixfold, Word16(3));
add(ctx, biased, frac)
} else if sub(ctx, lag, Word16(94)).0 <= 0 {
let tripled = triple(ctx, lag);
let sixfold = add(ctx, tripled, tripled);
let biased = sub(ctx, sixfold, Word16(105));
add(ctx, biased, frac)
} else {
add(ctx, lag, Word16(368))
};
u16::try_from(index.0).expect("a pitch index is non-negative")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct GainCoefficients {
pub yy: Word16,
pub exp_yy: Word16,
pub xy: Word16,
pub exp_xy: Word16,
}
#[must_use]
pub fn pitch_gain(
ctx: &mut DspContext,
mode_index: u8,
xn: &[Word16],
y1: &[Word16],
) -> (Word16, GainCoefficients) {
assert!(
xn.len() >= L_SUBFR && y1.len() >= L_SUBFR,
"a full subframe"
);
let mut quartered = [Word16(0); L_SUBFR];
for (slot, &value) in quartered.iter_mut().zip(y1.iter()) {
*slot = shr(ctx, value, 2);
}
ctx.overflow = false;
let mut s = Word32(1);
for &value in &y1[..L_SUBFR] {
s = l_mac(ctx, s, value, value);
}
let (energy, energy_shift) = if ctx.overflow {
let mut s = Word32(1);
for &value in &quartered {
s = l_mac(ctx, s, value, value);
}
let exp = norm_l(s);
let normalised = l_shl(ctx, s, exp);
(round(ctx, normalised), sub(ctx, Word16(exp), Word16(4)))
} else {
let exp = norm_l(s);
let normalised = l_shl(ctx, s, exp);
(round(ctx, normalised), Word16(exp))
};
ctx.overflow = false;
let mut s = Word32(1);
for (i, &value) in y1[..L_SUBFR].iter().enumerate() {
s = l_mac(ctx, s, xn[i], value);
}
let (cross, cross_shift) = if ctx.overflow {
let mut s = Word32(1);
for (i, &value) in quartered.iter().enumerate() {
s = l_mac(ctx, s, xn[i], value);
}
let exp = norm_l(s);
let normalised = l_shl(ctx, s, exp);
(round(ctx, normalised), sub(ctx, Word16(exp), Word16(2)))
} else {
let exp = norm_l(s);
let normalised = l_shl(ctx, s, exp);
(round(ctx, normalised), Word16(exp))
};
let coefficients = GainCoefficients {
yy: energy,
exp_yy: sub(ctx, Word16(15), energy_shift),
xy: cross,
exp_xy: sub(ctx, Word16(15), cross_shift),
};
if sub(ctx, cross, Word16(4)).0 < 0 {
return (Word16(0), coefficients);
}
let numerator = shr(ctx, cross, 1);
let mut gain = crate::fixed_point::div::div_s(numerator, energy);
let denormalise = sub(ctx, cross_shift, energy_shift).0;
gain = shr(ctx, gain, denormalise);
if sub(ctx, gain, Word16(19661)).0 > 0 {
gain = Word16(19661);
}
if mode_index == MR122 {
gain = Word16(gain.0 & !0x0003);
}
(gain, coefficients)
}
#[must_use]
pub fn nearest_pitch_gain(ctx: &mut DspContext, gain_limit: Word16, gain: Word16) -> u16 {
let first = sub(ctx, gain, Word16(QUA_GAIN_PITCH[0]));
let mut smallest = abs_s(ctx, first);
let mut index = 0usize;
for (i, &candidate) in QUA_GAIN_PITCH.iter().enumerate().skip(1) {
if sub(ctx, Word16(candidate), gain_limit).0 > 0 {
continue;
}
let difference = sub(ctx, gain, Word16(candidate));
let error = abs_s(ctx, difference);
if sub(ctx, error, smallest).0 < 0 {
smallest = error;
index = i;
}
}
u16::try_from(index).expect("a gain index fits in 16 bits")
}
#[must_use]
pub fn quantise_pitch_gain_12k2(
ctx: &mut DspContext,
gain_limit: Word16,
gain: Word16,
) -> (u16, Word16) {
let index = nearest_pitch_gain(ctx, gain_limit, gain);
let entry = QUA_GAIN_PITCH[usize::from(index)];
(index, Word16(entry & !0x0003))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ToneStability {
count: Word16,
gains: [Word16; N_FRAME],
}
impl ToneStability {
#[must_use]
pub const fn new() -> Self {
Self {
count: Word16(0),
gains: [Word16(0); N_FRAME],
}
}
pub fn check_lsp(&mut self, ctx: &mut DspContext, lsp: &[Word16; M]) -> bool {
let mut upper = Word16(MAX_16);
for i in 3..M - 2 {
let gap = sub(ctx, lsp[i], lsp[i + 1]);
if sub(ctx, gap, upper).0 < 0 {
upper = gap;
}
}
let mut lower = Word16(MAX_16);
for i in 1..3 {
let gap = sub(ctx, lsp[i], lsp[i + 1]);
if sub(ctx, gap, lower).0 < 0 {
lower = gap;
}
}
let threshold = if sub(ctx, lsp[1], Word16(32000)).0 > 0 {
Word16(600)
} else if sub(ctx, lsp[1], Word16(30500)).0 > 0 {
Word16(800)
} else {
Word16(1100)
};
if sub(ctx, upper, Word16(1500)).0 < 0 || sub(ctx, lower, threshold).0 < 0 {
self.count = add(ctx, self.count, Word16(1));
} else {
self.count = Word16(0);
}
if sub(ctx, self.count, Word16(12)).0 >= 0 {
self.count = Word16(12);
true
} else {
false
}
}
#[must_use]
pub fn clipping(&self, ctx: &mut DspContext, gain_pitch: Word16) -> bool {
let mut sum = shr(ctx, gain_pitch, 3);
for &past in &self.gains {
sum = add(ctx, sum, past);
}
sub(ctx, sum, GP_CLIP).0 > 0
}
pub fn update(&mut self, ctx: &mut DspContext, gain_pitch: Word16) {
self.gains.copy_within(1.., 0);
self.gains[N_FRAME - 1] = shr(ctx, gain_pitch, 3);
}
#[must_use]
pub const fn history(&self) -> &[Word16; N_FRAME] {
&self.gains
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LtpResult {
pub pitch: ClosedLoopResult,
pub gain_pitch: Word16,
pub gain_limit: Word16,
pub gain_coefficients: GainCoefficients,
pub gain_index: Option<u16>,
}
pub fn update_target(ctx: &mut DspContext, target: &mut [Word16], source: &[Word16], gain: Word16) {
assert!(
target.len() >= L_SUBFR && source.len() >= L_SUBFR,
"a full subframe"
);
for i in 0..L_SUBFR {
let product = l_mult(ctx, source[i], gain);
let scaled = extract_h(l_shl(ctx, product, 1));
target[i] = sub(ctx, target[i], scaled);
}
}
#[allow(clippy::too_many_arguments)]
pub fn closed_loop_ltp(
ctx: &mut DspContext,
mode_index: u8,
pitch: &mut ClosedLoopPitch,
tone: &mut ToneStability,
open_loop: [i16; 2],
subframe: usize,
excitation: &mut Excitation,
xn: &[Word16; L_SUBFR],
h1: &[Word16; L_SUBFR],
resonant: bool,
res2: &mut [Word16; L_SUBFR],
xn2: &mut [Word16; L_SUBFR],
y1: &mut [Word16; L_SUBFR],
) -> LtpResult {
let found = pitch.search(
ctx,
mode_index,
open_loop,
excitation.all(),
EXC_ORIGIN,
xn,
h1,
subframe,
);
excitation.predict(ctx, found.lag);
let adaptive: [Word16; L_SUBFR] = excitation
.subframe()
.try_into()
.expect("the excitation subframe is L_SUBFR long");
*y1 = convolve(ctx, &adaptive, h1);
let (mut gain_pitch, gain_coefficients) = pitch_gain(ctx, mode_index, xn, y1);
let mut gain_limit = Word16(MAX_16);
let clipped = resonant && sub(ctx, gain_pitch, GP_CLIP).0 > 0 && tone.clipping(ctx, gain_pitch);
let mut gain_index = None;
if matches!(mode_index, MR475 | MR515) {
if sub(ctx, gain_pitch, Word16(13926)).0 > 0 {
gain_pitch = Word16(13926);
}
if clipped {
gain_limit = GP_CLIP;
}
} else {
if clipped {
gain_limit = GP_CLIP;
gain_pitch = GP_CLIP;
}
if mode_index == MR122 {
let (index, quantised) = quantise_pitch_gain_12k2(ctx, gain_limit, gain_pitch);
gain_pitch = quantised;
gain_index = Some(index);
}
}
xn2.copy_from_slice(xn);
update_target(ctx, xn2, y1, gain_pitch);
update_target(ctx, res2, &adaptive, gain_pitch);
LtpResult {
pitch: found,
gain_pitch,
gain_limit,
gain_coefficients,
gain_index,
}
}
#[cfg(test)]
mod tests {
use super::super::super::bitstream::parse;
use super::super::super::lag::{
absolute_lag, delta_coding, delta_lag_1_3, delta_lag_1_6, delta_window,
};
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;
const FRAMES: usize = 3;
const SUBFRAMES: usize = 4;
const LAG_PARAM: [usize; SUBFRAMES] = [3, 7, 11, 15];
fn trace(frame: usize, subframe: usize, 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 vector(frame: usize, subframe: usize, name: &str) -> [Word16; L_SUBFR] {
let v = trace(frame, subframe, name);
assert_eq!(v.len(), L_SUBFR, "{name} is one subframe long");
let mut out = [Word16(0); L_SUBFR];
for (slot, value) in out.iter_mut().zip(v) {
*slot = Word16(value);
}
out
}
fn scalar(frame: usize, subframe: usize, name: &str) -> i16 {
let v = trace(frame, subframe, name);
assert_eq!(v.len(), 1, "{name} is a scalar");
v[0]
}
fn code(index: u16) -> Word16 {
Word16(i16::try_from(index).expect("a pitch index fits in i16"))
}
fn reference_parameters(frame: usize) -> Vec<u16> {
const PAYLOAD: usize = 19;
let offset = 6 + frame * (1 + PAYLOAD);
assert_eq!((MODE4[offset] >> 3) & 0x0f, MR74, "frame {frame}: ToC mode");
parse(MR74, &MODE4[offset + 1..offset + 1 + PAYLOAD]).expect("frame parses")
}
struct Replay {
excitation: Excitation,
ctx: DspContext,
}
impl Replay {
fn new() -> Self {
Self {
excitation: Excitation::new(),
ctx: DspContext::default(),
}
}
fn open_subframe(&mut self, frame: usize, subframe: usize) {
let res = vector(frame, subframe, "res");
self.excitation.subframe_mut().copy_from_slice(&res);
}
fn close_subframe(&mut self, frame: usize, subframe: usize) {
let lag = PitchLag {
integer: Word16(scalar(frame, subframe, "T0")),
frac: Word16(scalar(frame, subframe, "T0_frac")),
resolution: LagResolution::OneThird,
};
self.excitation.predict(&mut self.ctx, lag);
assert_eq!(
self.excitation.subframe(),
vector(frame, subframe, "adapt"),
"frame {frame} subframe {subframe}: replayed adaptive codevector"
);
let code = vector(frame, subframe, "code");
let gain_pitch = Word16(scalar(frame, subframe, "gain_pit"));
let gain_code = Word16(scalar(frame, subframe, "gain_code"));
for (i, &pulse) in code.iter().enumerate() {
let mut acc = l_mult(&mut self.ctx, self.excitation.subframe()[i], gain_pitch);
acc = l_mac(&mut self.ctx, acc, pulse, gain_code);
acc = l_shl(&mut self.ctx, acc, 1);
let total = round(&mut self.ctx, acc);
self.excitation.subframe_mut()[i] = total;
}
self.excitation.advance();
}
}
#[test]
fn the_replayed_excitation_reproduces_the_traced_codevector() {
let mut replay = Replay::new();
let mut compared = 0;
for frame in 0..FRAMES {
for subframe in 0..SUBFRAMES {
replay.open_subframe(frame, subframe);
replay.close_subframe(frame, subframe);
compared += 1;
}
}
assert_eq!(compared, 12, "three frames of four subframes");
}
#[test]
fn convolve_matches_the_trace() {
let mut ctx = DspContext::default();
let mut compared = 0;
for frame in 0..FRAMES {
for subframe in 0..SUBFRAMES {
let got = convolve(
&mut ctx,
&vector(frame, subframe, "adapt"),
&vector(frame, subframe, "h1"),
);
assert_eq!(
got,
vector(frame, subframe, "y1"),
"frame {frame} subframe {subframe}: filtered adaptive codevector"
);
compared += 1;
}
}
assert_eq!(compared, 12);
}
#[test]
fn the_pitch_gain_matches_the_trace() {
let mut ctx = DspContext::default();
let mut compared = 0;
for frame in 0..FRAMES {
for subframe in 0..SUBFRAMES {
let (gain, _) = pitch_gain(
&mut ctx,
MR74,
&vector(frame, subframe, "xn"),
&vector(frame, subframe, "y1"),
);
assert_eq!(
gain.0,
scalar(frame, subframe, "gain_pit_ol"),
"frame {frame} subframe {subframe}"
);
compared += 1;
}
}
assert_eq!(compared, 12);
}
#[test]
fn the_target_update_matches_the_trace() {
let mut ctx = DspContext::default();
let mut compared = 0;
for frame in 0..FRAMES {
for subframe in 0..SUBFRAMES {
let mut xn2 = vector(frame, subframe, "xn");
update_target(
&mut ctx,
&mut xn2,
&vector(frame, subframe, "y1"),
Word16(scalar(frame, subframe, "gain_pit_ol")),
);
assert_eq!(
xn2,
vector(frame, subframe, "xn2"),
"frame {frame} subframe {subframe}"
);
compared += 1;
}
}
assert_eq!(compared, 12);
}
#[test]
fn the_closed_loop_search_matches_the_trace_on_delta_subframes() {
let mut replay = Replay::new();
let mut pitch = ClosedLoopPitch::new();
let mut ctx = DspContext::default();
let mut compared = 0;
for frame in 0..FRAMES {
for (subframe, &slot) in LAG_PARAM.iter().enumerate() {
replay.open_subframe(frame, subframe);
if !subframe.is_multiple_of(2) {
let found = pitch.search(
&mut ctx,
MR74,
[0, 0], replay.excitation.all(),
EXC_ORIGIN,
&vector(frame, subframe, "xn"),
&vector(frame, subframe, "h1"),
subframe,
);
assert!(found.delta, "subframe {subframe} is delta-coded at 7.40");
assert_eq!(
(found.lag.integer.0, found.lag.frac.0),
(
scalar(frame, subframe, "T0"),
scalar(frame, subframe, "T0_frac")
),
"frame {frame} subframe {subframe}: window {}..={}",
found.window.min.0,
found.window.max.0
);
assert_eq!(
u32::from(found.index),
u32::from(reference_parameters(frame)[slot]),
"frame {frame} subframe {subframe}: transmitted lag index"
);
compared += 1;
}
pitch.previous_lag = Word16(scalar(frame, subframe, "T0"));
replay.close_subframe(frame, subframe);
}
}
assert_eq!(compared, 6, "two delta subframes in each of three frames");
}
#[test]
fn some_open_loop_lag_reproduces_each_full_search_subframe() {
let mut replay = Replay::new();
let mut ctx = DspContext::default();
let mut compared = 0;
for frame in 0..FRAMES {
for subframe in 0..SUBFRAMES {
replay.open_subframe(frame, subframe);
if subframe % 2 == 0 {
let want = (
scalar(frame, subframe, "T0"),
scalar(frame, subframe, "T0_frac"),
);
let consistent: Vec<i16> = (PIT_MIN..=PIT_MAX)
.filter(|&candidate| {
let mut probe = ClosedLoopPitch::new();
let found = probe.search(
&mut ctx,
MR74,
[candidate, candidate],
replay.excitation.all(),
EXC_ORIGIN,
&vector(frame, subframe, "xn"),
&vector(frame, subframe, "h1"),
subframe,
);
assert!(!found.delta, "subframe {subframe} is a full search");
(found.lag.integer.0, found.lag.frac.0) == want
})
.collect();
assert!(
!consistent.is_empty(),
"frame {frame} subframe {subframe}: no open-loop lag reproduces \
T0={} frac={}",
want.0,
want.1
);
if frame == 0 && subframe == 0 {
assert_eq!(
consistent,
vec![27],
"the first subframe pins the open-loop lag exactly"
);
}
compared += 1;
}
replay.close_subframe(frame, subframe);
}
}
assert_eq!(
compared, 6,
"two full-search subframes in each of three frames"
);
}
#[test]
fn every_lag_index_matches_the_reference_bitstream() {
let mut ctx = DspContext::default();
let mut previous = Word16(0);
let mut compared = 0;
for frame in 0..FRAMES {
let params = reference_parameters(frame);
for (subframe, &slot) in LAG_PARAM.iter().enumerate() {
let lag = scalar(frame, subframe, "T0");
let frac = scalar(frame, subframe, "T0_frac");
let delta = !subframe.is_multiple_of(2);
let window = lag_window(&mut ctx, previous.0, 5, 9, PIT_MIN);
let index = encode_lag_1_3(&mut ctx, lag, frac, previous.0, window, delta, false);
assert_eq!(
u32::from(index),
u32::from(params[slot]),
"frame {frame} subframe {subframe}"
);
previous = Word16(lag);
compared += 1;
}
}
assert_eq!(compared, 12);
}
#[test]
fn lag_encoding_round_trips_through_the_decoder() {
let mut ctx = DspContext::default();
let mut compared = 0;
for lag in PIT_MIN..=PIT_MAX {
let fracs: &[i16] = if lag <= 84 { &[-1, 0, 1] } else { &[0] };
for &frac in fracs {
let window = LagWindow {
min: Word16(PIT_MIN),
max: Word16(PIT_MAX),
};
let index = encode_lag_1_3(&mut ctx, lag, frac, 0, window, false, false);
let back = absolute_lag(&mut ctx, code(index), LagResolution::OneThird);
assert_eq!((back.integer.0, back.frac.0), (lag, frac), "absolute 1/3");
compared += 1;
}
}
for previous in PIT_MIN..=PIT_MAX {
let uniform = delta_window(&mut ctx, MR74, Word16(previous));
for lag in uniform.min.0..=uniform.max.0 {
for frac in -1..=1 {
let index = encode_lag_1_3(&mut ctx, lag, frac, previous, uniform, true, false);
let back = delta_lag_1_3(
&mut ctx,
code(index),
uniform,
delta_coding(MR74, Word16(previous)),
);
assert_eq!(
(back.integer.0, back.frac.0),
(lag, frac),
"delta 1/3, 5 bit"
);
compared += 1;
}
}
let anchored = delta_window(&mut ctx, MR67, Word16(previous));
let anchor = four_bit_anchor(&mut ctx, previous, anchored);
for index in 0..16u16 {
let decoded = delta_lag_1_3(
&mut ctx,
code(index),
anchored,
delta_coding(MR67, Word16(previous)),
);
let again = encode_lag_1_3(
&mut ctx,
decoded.integer.0,
decoded.frac.0,
previous,
anchored,
true,
true,
);
assert_eq!(
again, index,
"delta 1/3, 4 bit: previous {previous}, anchor {anchor}, lag {} frac {}",
decoded.integer.0, decoded.frac.0
);
compared += 1;
}
}
for lag in PIT_MIN_MR122..=PIT_MAX {
let fracs: &[i16] = if lag <= 94 {
&[-2, -1, 0, 1, 2, 3]
} else {
&[0]
};
for &frac in fracs {
let index = encode_lag_1_6(&mut ctx, lag, frac, 0, false);
let back = absolute_lag(&mut ctx, code(index), LagResolution::OneSixth);
assert_eq!((back.integer.0, back.frac.0), (lag, frac), "absolute 1/6");
compared += 1;
}
}
for previous in PIT_MIN_MR122..=PIT_MAX {
let window = lag_window(&mut ctx, previous, 5, 9, PIT_MIN_MR122);
for lag in window.min.0..=window.max.0 {
for frac in -2..=3 {
let index = encode_lag_1_6(&mut ctx, lag, frac, window.min.0, true);
assert!(index < 61, "only 61 of the 64 relative codes are used");
let back = delta_lag_1_6(&mut ctx, code(index), Word16(previous));
assert_eq!((back.integer.0, back.frac.0), (lag, frac), "delta 1/6");
compared += 1;
}
}
}
assert!(compared > 9000, "only {compared} lag codes round-tripped");
}
#[test]
fn the_open_loop_peak_takes_the_smallest_lag_on_a_tie() {
let mut ctx = DspContext::default();
let mut corr = [Word32(0); PIT_MAX as usize + 1];
corr[40] = Word32(1000);
corr[50] = Word32(1000);
corr[60] = Word32(1000);
assert_eq!(peak_lag(&mut ctx, &corr, 79, 40), 40);
assert_eq!(peak_lag(&mut ctx, &corr, 79, 41), 50);
}
#[test]
fn an_all_minimum_section_returns_its_lowest_lag() {
let mut ctx = DspContext::default();
let corr = [Word32(MIN_32); PIT_MAX as usize + 1];
assert_eq!(peak_lag(&mut ctx, &corr, 79, 40), 40);
}
#[test]
fn the_section_arbitration_keeps_the_incumbent_on_a_tie() {
let mut ctx = DspContext::default();
let long = (100i16, Word16(1000));
let handicapped = mult(&mut ctx, Word16(1000), THRESHOLD);
assert_eq!(
arbitrate_sections(&mut ctx, long, (60, handicapped), (30, Word16(0))),
100,
"equalling the handicapped incumbent is not enough"
);
assert_eq!(
arbitrate_sections(
&mut ctx,
long,
(60, Word16(handicapped.0 + 1)),
(30, Word16(0))
),
60,
"one more than the handicap takes it"
);
assert_eq!(
arbitrate_sections(
&mut ctx,
long,
(60, Word16(handicapped.0 + 1)),
(30, Word16(handicapped.0 + 2))
),
30,
"section three is measured against the new incumbent"
);
}
#[test]
fn the_arbitration_handicap_floors_rather_than_rounds() {
let mut ctx = DspContext::default();
let negative = Word16(-1000);
let handicapped = mult(&mut ctx, negative, THRESHOLD);
assert_eq!(handicapped.0, -851, "floor(-1000 * 27853 / 32768)");
assert_eq!(-1000i32 * 27853 / 32768, -850);
}
#[test]
fn the_open_loop_finds_a_planted_period() {
const PERIOD: usize = 45;
let mut ctx = DspContext::default();
let origin = PIT_MAX as usize;
let mut signal = vec![Word16(0); origin + L_FRAME];
for (i, slot) in signal.iter_mut().enumerate() {
*slot = Word16(match i % PERIOD {
0 => 8000,
1 => -6000,
2 => 3000,
_ => 0,
});
}
assert_eq!(
open_loop_lag(&mut ctx, MR74, &signal, origin, L_FRAME_BY2, None, false),
i16::try_from(PERIOD).expect("period fits"),
);
}
#[test]
fn the_two_lowest_rates_search_once_for_the_whole_frame() {
let mut ctx = DspContext::default();
let origin = PIT_MAX as usize;
let mut signal = vec![Word16(0); origin + L_FRAME];
for (i, slot) in signal.iter_mut().enumerate() {
let period = if i < origin + L_FRAME_BY2 { 45 } else { 30 };
*slot = Word16(match i % period {
0 => 8000,
1 => -6000,
2 => 3000,
_ => 0,
});
}
let mut weighted = WeightedOpenLoop::new();
let mut old_lags = [Word16(40); 5];
let mut voiced = [false; 2];
let split = open_loop_lags(
&mut ctx,
MR74,
&mut weighted,
&signal,
origin,
&mut old_lags,
&mut voiced,
None,
);
assert_ne!(split[0], split[1], "each half is searched on its own");
let single = open_loop_lags(
&mut ctx,
MR475,
&mut weighted,
&signal,
origin,
&mut old_lags,
&mut voiced,
None,
);
assert_eq!(single[0], single[1], "one search, copied into both halves");
}
#[test]
fn twelve_two_reaches_a_shorter_lag_than_the_other_rates() {
let mut ctx = DspContext::default();
let origin = PIT_MAX as usize;
let mut signal = vec![Word16(0); origin + L_FRAME];
for (i, slot) in signal.iter_mut().enumerate() {
*slot = Word16(match i % 19 {
0 => 8000,
1 => -6000,
_ => 0,
});
}
assert_eq!(
open_loop_lag(&mut ctx, MR122, &signal, origin, L_FRAME_BY2, None, false),
19
);
assert_eq!(mode_params(MR122).pit_min, 18);
assert_eq!(mode_params(MR74).pit_min, 20);
}
#[test]
fn the_fractional_search_keeps_its_starting_point_on_a_tie() {
let mut ctx = DspContext::default();
let flat = Correlations {
first: 20,
last: 40,
values: [Word16(0); 40],
};
assert_eq!(search_fraction(&mut ctx, 30, -2, 2, &flat, true), (29, 1));
assert_eq!(search_fraction(&mut ctx, 30, 2, 2, &flat, true), (31, -1));
assert_eq!(search_fraction(&mut ctx, 30, -3, 3, &flat, false), (29, 3));
assert_eq!(search_fraction(&mut ctx, 30, 2, 3, &flat, false), (30, 2));
let level = Correlations {
first: 20,
last: 40,
values: [Word16(1234); 40],
};
assert_eq!(search_fraction(&mut ctx, 30, -2, 2, &level, true), (30, 0));
}
#[test]
fn the_window_clamp_preserves_its_width_at_the_top() {
let mut ctx = DspContext::default();
let high = lag_window(&mut ctx, 143, 5, 9, PIT_MIN);
assert_eq!((high.min.0, high.max.0), (134, 143));
let low = lag_window(&mut ctx, 20, 5, 9, PIT_MIN);
assert_eq!((low.min.0, low.max.0), (20, 29));
let deep = lag_window(&mut ctx, 20, 5, 9, PIT_MIN_MR122);
assert_eq!((deep.min.0, deep.max.0), (18, 27));
}
#[test]
fn the_four_bit_anchor_applies_both_clamps_in_order() {
let mut ctx = DspContext::default();
let window = LagWindow {
min: Word16(30),
max: Word16(39),
};
assert_eq!(four_bit_anchor(&mut ctx, 100, window), 35);
assert_eq!(four_bit_anchor(&mut ctx, 20, window), 35);
let wide = LagWindow {
min: Word16(30),
max: Word16(49),
};
assert_eq!(four_bit_anchor(&mut ctx, 100, wide), 45);
}
#[test]
fn the_gain_search_skips_entries_above_the_limit_but_never_entry_zero() {
let mut ctx = DspContext::default();
assert_eq!(nearest_pitch_gain(&mut ctx, Word16(0), Word16(16384)), 0);
let free = nearest_pitch_gain(&mut ctx, Word16(MAX_16), Word16(16384));
assert!(free > 0, "the codebook is not degenerate");
let capped = nearest_pitch_gain(&mut ctx, GP_CLIP, Word16(MAX_16));
assert!(
QUA_GAIN_PITCH[usize::from(capped)] <= GP_CLIP.0,
"the chosen gain respects the limit"
);
}
#[test]
fn twelve_two_clears_the_two_low_bits_of_its_pitch_gain() {
let mut ctx = DspContext::default();
let (index, gain) = quantise_pitch_gain_12k2(&mut ctx, Word16(MAX_16), Word16(12345));
assert_eq!(gain.0, QUA_GAIN_PITCH[usize::from(index)] & !0x0003);
assert_eq!(gain.0 & 0x0003, 0);
let flat = [Word16(1000); L_SUBFR];
let (masked, _) = pitch_gain(&mut ctx, MR122, &flat, &flat);
assert_eq!(masked.0 & 0x0003, 0);
}
#[test]
fn the_resonance_flag_needs_twelve_consecutive_frames() {
let mut ctx = DspContext::default();
let mut tone = ToneStability::new();
let mut resonant = [Word16(0); M];
for (i, slot) in resonant.iter_mut().enumerate() {
*slot = Word16(30000 - 100 * i16::try_from(i).expect("ten fits"));
}
for frame in 0..11 {
assert!(
!tone.check_lsp(&mut ctx, &resonant),
"frame {frame} fired early"
);
}
assert!(
tone.check_lsp(&mut ctx, &resonant),
"the twelfth frame sets it"
);
assert!(tone.check_lsp(&mut ctx, &resonant), "and it stays set");
let mut spread = [Word16(0); M];
for (i, slot) in spread.iter_mut().enumerate() {
let step = 6000 * i32::try_from(i).expect("ten fits");
*slot = Word16(i16::try_from(30000 - step).expect("stays inside a Word16"));
}
assert!(!tone.check_lsp(&mut ctx, &spread));
for _ in 0..11 {
assert!(!tone.check_lsp(&mut ctx, &resonant), "the count restarted");
}
assert!(tone.check_lsp(&mut ctx, &resonant));
}
#[test]
fn gain_clipping_averages_the_last_eight_subframes() {
let mut ctx = DspContext::default();
let mut tone = ToneStability::new();
assert!(!tone.clipping(&mut ctx, Word16(MAX_16)));
for _ in 0..N_FRAME {
tone.update(&mut ctx, Word16(19661));
}
assert_eq!(tone.history()[0].0, 19661 >> 3);
assert!(tone.clipping(&mut ctx, Word16(19661)));
for _ in 0..N_FRAME {
tone.update(&mut ctx, Word16(0));
}
assert_eq!(tone.history(), &[Word16(0); N_FRAME]);
assert!(!tone.clipping(&mut ctx, Word16(19661)));
}
#[test]
fn the_median_takes_the_middle_of_five() {
let mut ctx = DspContext::default();
assert_eq!(
median_of_five(
&mut ctx,
&[Word16(10), Word16(50), Word16(20), Word16(40), Word16(30)]
)
.0,
30
);
assert_eq!(median_of_five(&mut ctx, &[Word16(7); 5]).0, 7);
}
#[test]
fn clipping_forces_the_gain_down_at_every_rate_but_the_two_lowest() {
let run = |mode_index: u8, resonant: bool| {
let mut ctx = DspContext::default();
let mut pitch = ClosedLoopPitch::new();
let mut tone = ToneStability::new();
for _ in 0..N_FRAME {
tone.update(&mut ctx, Word16(19661));
}
let mut excitation = Excitation::new();
for (i, slot) in excitation.all_mut().iter_mut().enumerate() {
*slot = Word16(if i % 40 == 0 { 8000 } else { 0 });
}
let xn: [Word16; L_SUBFR] = excitation.all()[EXC_ORIGIN - 40..EXC_ORIGIN]
.try_into()
.expect("one subframe");
let mut h1 = [Word16(0); L_SUBFR];
h1[0] = Word16(4096);
let mut res2 = xn;
let mut xn2 = [Word16(0); L_SUBFR];
let mut y1 = [Word16(0); L_SUBFR];
closed_loop_ltp(
&mut ctx,
mode_index,
&mut pitch,
&mut tone,
[40, 40],
0,
&mut excitation,
&xn,
&h1,
resonant,
&mut res2,
&mut xn2,
&mut y1,
)
};
let quiet = run(MR74, false);
assert_eq!(quiet.gain_limit.0, MAX_16, "no limit reported");
assert!(
quiet.gain_pitch.0 > GP_CLIP.0,
"the raw gain is above the clip"
);
let clipped = run(MR74, true);
assert_eq!(clipped.gain_limit, GP_CLIP);
assert_eq!(clipped.gain_pitch, GP_CLIP, "7.40 forces the gain down");
let low = run(MR475, true);
assert_eq!(low.gain_limit, GP_CLIP, "4.75 reports the limit");
assert_eq!(
low.gain_pitch.0, 13926,
"and caps at 0.85 rather than at GP_CLIP"
);
let fine = run(MR122, false);
let index = fine
.gain_index
.expect("12.2 quantises its pitch gain in cl_ltp");
assert_eq!(
fine.gain_pitch.0,
QUA_GAIN_PITCH[usize::from(index)] & !0x0003
);
assert!(run(MR74, false).gain_index.is_none(), "no other rate does");
}
#[test]
fn the_weighted_open_loop_updates_its_lag_history() {
let mut ctx = DspContext::default();
let origin = PIT_MAX as usize;
let mut signal = vec![Word16(0); origin + L_FRAME];
for (i, slot) in signal.iter_mut().enumerate() {
*slot = Word16(match i % 45 {
0 => 8000,
1 => -6000,
2 => 3000,
_ => 0,
});
}
let mut weighted = WeightedOpenLoop::new();
let mut old_lags = [Word16(40); 5];
let mut voiced = [false; 2];
let first = open_loop_lags(
&mut ctx,
MR102,
&mut weighted,
&signal,
origin,
&mut old_lags,
&mut voiced,
None,
);
assert_eq!(first[0], 45, "the planted period survives the weighting");
assert!(voiced[0] && voiced[1], "a periodic signal is voiced");
assert_eq!(old_lags[0].0, 45, "the lag joined the history");
let second = open_loop_lags(
&mut ctx,
MR102,
&mut weighted,
&signal,
origin,
&mut old_lags,
&mut voiced,
None,
);
assert_eq!(second[0], 45);
let quiet = vec![Word16(0); origin + L_FRAME];
let _ = open_loop_lags(
&mut ctx,
MR102,
&mut weighted,
&quiet,
origin,
&mut old_lags,
&mut voiced,
None,
);
assert!(!voiced[0], "silence is not voiced");
let mut other = [true; 2];
let _ = open_loop_lags(
&mut ctx,
MR74,
&mut weighted,
&signal,
origin,
&mut old_lags,
&mut other,
None,
);
assert_eq!(other, [false; 2]);
}
#[test]
fn the_correlation_weighting_cursors_stay_in_bounds_at_both_extremes() {
for old in [PIT_MIN, PIT_MAX] {
let start = 123 + i32::from(PIT_MAX) - i32::from(old);
let start = usize::try_from(start).expect("the start cursor is non-negative");
assert!(start < CORR_WEIGHT.len(), "start {start}");
assert!(
start >= usize::try_from(PIT_MAX - PIT_MIN).expect("the lag span is positive"),
"the cursor would step below zero from {start}"
);
}
}
#[test]
fn the_closed_loop_integer_scan_takes_the_largest_lag_on_a_tie() {
let mut ctx = DspContext::default();
let mut pitch = ClosedLoopPitch::new();
let excitation = Excitation::new();
let xn = [Word16(1000); L_SUBFR];
let h1 = [Word16(4096); L_SUBFR];
let found = pitch.search(
&mut ctx,
MR74,
[50, 50],
excitation.all(),
EXC_ORIGIN,
&xn,
&h1,
0,
);
let window = lag_window(&mut ctx, 50, 3, 6, PIT_MIN);
assert_eq!(
(found.lag.integer.0, found.lag.frac.0),
(window.max.0 - 1, 1),
"top of the window, then the fractional normalisation"
);
}
}