use super::super::decoder_tables::{
GAIN_HIGHRATES, GAIN_LOWRATES, GAIN_MR475, QUA_GAIN_CODE, QUA_GAIN_PITCH,
};
use super::super::gain::{CodeGainPredictor, GainPrediction, SubframeGains};
use super::super::math::{log2, pow2, sqrt_l_exp};
use super::super::L_SUBFR;
use crate::fixed_point::arith::{abs_s, add, extract_h, extract_l, mult, negate, round, sub};
use crate::fixed_point::arith32::{l_deposit_l, l_mac, l_mult, l_sub};
use crate::fixed_point::div::div_s;
use crate::fixed_point::oper32::{l_comp, l_extract, mpy_32_16};
use crate::fixed_point::shift::{l_shl, l_shr, norm_l, shl, shr, shr_r};
use crate::fixed_point::types::{DspContext, Word16, Word32, MAX_32};
const MR475_VQ_SIZE: usize = 256;
const VQ_SIZE_HIGHRATES: usize = 128;
const VQ_SIZE_LOWRATES: usize = 64;
const NB_QUA_CODE: usize = 32;
const NB_QUA_PITCH: usize = 16;
const JOINT_ENTRY: usize = 4;
const CODE_ENTRY: usize = 3;
const COEFFS: usize = 5;
const DB_PER_OCTAVE: Word16 = Word16(24660);
const MIN_QUA_ENER: Word16 = Word16(-5443);
const MIN_QUA_ENER_MR122: Word16 = Word16(-32768);
const MAX_QUA_ENER: Word16 = Word16(3037);
const MAX_QUA_ENER_MR122: Word16 = Word16(18284);
const LTP_GAIN_THR1: Word16 = Word16(2721);
const LTP_GAIN_THR2: Word16 = Word16(5443);
const LTPG_MEM_SIZE: usize = 5;
#[derive(Clone, Copy, Debug)]
pub struct SubframeSignals<'a> {
pub residual: &'a [Word16; L_SUBFR],
pub adaptive: &'a [Word16; L_SUBFR],
pub code: &'a [Word16; L_SUBFR],
pub pitch_target: &'a [Word16; L_SUBFR],
pub code_target: &'a [Word16; L_SUBFR],
pub filtered_adaptive: &'a [Word16; L_SUBFR],
pub filtered_code: &'a [Word16; L_SUBFR],
pub pitch_correlations: [Word16; 4],
pub gain_pit: Word16,
pub gp_limit: Word16,
pub even_subframe: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GainParams {
Reserve,
Index(u16),
Pair(u16),
PitchAndCode(u16, u16),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GainDecision {
pub gains: SubframeGains,
pub previous: Option<SubframeGains>,
pub params: GainParams,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct Coefficients {
fraction: [Word16; COEFFS],
exponent: [Word16; COEFFS],
}
#[derive(Clone, Copy, Debug)]
struct PendingPair {
predicted: GainPrediction,
target_energy: (Word16, Word16),
coefficients: Coefficients,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
struct GainAdaptor {
onset: Word16,
prev_alpha: Word16,
prev_gain_code: Word16,
ltpg: [Word16; LTPG_MEM_SIZE],
}
#[derive(Clone, Copy, Debug)]
pub struct GainQuantiser {
predictor: CodeGainPredictor,
unquantised: CodeGainPredictor,
pending: Option<PendingPair>,
adaptor: GainAdaptor,
}
impl GainQuantiser {
pub const fn reseed_predictors(&mut self, db: Word16, log2: Word16) {
self.predictor.seed_directly(db, log2);
}
}
impl Default for GainQuantiser {
fn default() -> Self {
Self::new()
}
}
impl GainQuantiser {
#[must_use]
pub const fn new() -> Self {
Self {
predictor: CodeGainPredictor::new(),
unquantised: CodeGainPredictor::new(),
pending: None,
adaptor: GainAdaptor {
onset: Word16(0),
prev_alpha: Word16(0),
prev_gain_code: Word16(0),
ltpg: [Word16(0); LTPG_MEM_SIZE],
},
}
}
pub fn quantise(
&mut self,
ctx: &mut DspContext,
mode_index: u8,
signals: &SubframeSignals<'_>,
) -> GainDecision {
if mode_index == 0 {
return self.quantise_pair(ctx, signals);
}
let predicted = self.predictor.predict(ctx, mode_index, signals.code);
if mode_index >= 7 {
let optimum = optimum_code_gain(ctx, signals.code_target, signals.filtered_code);
let (index, gain_code, energies) = quantise_code_gain(ctx, predicted, optimum);
self.predictor.push(energies.0, energies.1);
return GainDecision {
gains: SubframeGains {
pitch: signals.gain_pit,
code: gain_code,
},
previous: None,
params: GainParams::Index(index),
};
}
let filtered = filtered_energies(ctx, mode_index, signals);
if mode_index == 5 {
let (gains, energies, params) =
self.quantise_adaptive(ctx, signals, predicted, &filtered);
self.predictor.push(energies.0, energies.1);
return GainDecision {
gains,
previous: None,
params,
};
}
let (index, gains, energies) = quantise_joint(
ctx,
mode_index,
predicted,
&filtered.coefficients,
signals.gp_limit,
);
self.predictor.push(energies.0, energies.1);
GainDecision {
gains,
previous: None,
params: GainParams::Index(index),
}
}
fn quantise_pair(
&mut self,
ctx: &mut DspContext,
signals: &SubframeSignals<'_>,
) -> GainDecision {
if signals.even_subframe {
self.unquantised = self.predictor;
let predicted = self.unquantised.predict(ctx, 0, signals.code);
let filtered = filtered_energies(ctx, 0, signals);
let (optimum_fraction, optimum_exponent) = filtered
.optimum_code_gain
.expect("4.75 kbit/s always computes the optimum code gain");
let shift = add(ctx, optimum_exponent, Word16(1));
let gain_code = shl(ctx, optimum_fraction, shift.0);
self.pending = Some(PendingPair {
predicted,
target_energy: target_energy(ctx, signals.pitch_target),
coefficients: filtered.coefficients,
});
update_unquantised_predictor(
ctx,
&mut self.unquantised,
predicted,
(optimum_fraction, optimum_exponent),
);
return GainDecision {
gains: SubframeGains {
pitch: signals.gain_pit,
code: gain_code,
},
previous: None,
params: GainParams::Reserve,
};
}
let pending = self
.pending
.take()
.expect("4.75 kbit/s codes subframes in pairs; the even one must run first");
let predicted = self.unquantised.predict(ctx, 0, signals.code);
let filtered = filtered_energies(ctx, 0, signals);
let odd_energy = target_energy(ctx, signals.pitch_target);
let (index, even, odd) = quantise_pair_jointly(
ctx,
&mut self.predictor,
&pending,
(predicted, &filtered.coefficients, odd_energy),
signals.code,
signals.gp_limit,
);
GainDecision {
gains: odd,
previous: Some(even),
params: GainParams::Pair(index),
}
}
fn quantise_adaptive(
&mut self,
ctx: &mut DspContext,
signals: &SubframeSignals<'_>,
predicted: GainPrediction,
filtered: &FilteredEnergies,
) -> (SubframeGains, (Word16, Word16), GainParams) {
let mut pitch = signals.gain_pit;
let (_, candidates) = quantise_pitch_gain(ctx, 5, signals.gp_limit, &mut pitch);
let candidates = candidates.expect("7.95 kbit/s asks for the three candidates");
let gcode0 = extract_l(pow2(ctx, Word16(14), predicted.fraction));
let pre = prequantise_code_gain(
ctx,
predicted.exponent,
gcode0,
&candidates,
&filtered.coefficients,
);
pitch = pre.pitch;
let pitch_index = pre.pitch_index;
let mut code = pre.code;
let mut code_index = pre.code_index;
let mut energies = pre.energies;
let unfiltered =
unfiltered_energies(ctx, signals.residual, signals.adaptive, signals.code, pitch);
let alpha = self.adaptor.adapt(ctx, unfiltered.ltp_gain, code);
if unfiltered.fraction[0].0 != 0 && alpha.0 > 0 {
let mut fraction = unfiltered.fraction;
let mut exponent = unfiltered.exponent;
let (code_exp, code_frac) = predicted
.innovation_energy
.expect("7.95 kbit/s asks gc_pred for the innovation energy");
fraction[3] = code_frac;
exponent[3] = code_exp;
let (optimum_fraction, optimum_exponent) = filtered
.optimum_code_gain
.expect("7.95 kbit/s always computes the optimum code gain");
let scale = sub(ctx, optimum_exponent, predicted.exponent);
let scale = add(ctx, scale, Word16(10));
let optimum = shl(ctx, optimum_fraction, scale.0);
let modified = requantise_code_gain(
ctx,
&AdaptedCriterion {
gain_pit: pitch,
exp_gcode0: predicted.exponent,
gcode0,
fraction,
exponent,
alpha,
optimum,
},
code,
);
code_index = modified.0;
code = modified.1;
energies = modified.2;
}
(
SubframeGains { pitch, code },
energies,
GainParams::PitchAndCode(pitch_index, code_index),
)
}
}
struct FilteredEnergies {
coefficients: Coefficients,
optimum_code_gain: Option<(Word16, Word16)>,
}
fn filtered_energies(
ctx: &mut DspContext,
mode_index: u8,
signals: &SubframeSignals<'_>,
) -> FilteredEnergies {
let wants_optimum = mode_index == 0 || mode_index == 5;
let seed = if wants_optimum { Word32(0) } else { Word32(1) };
let mut y2 = [Word16(0); L_SUBFR];
for (out, &value) in y2.iter_mut().zip(signals.filtered_code.iter()) {
*out = shr(ctx, value, 3);
}
let mut coefficients = Coefficients::default();
coefficients.fraction[0] = signals.pitch_correlations[0];
coefficients.exponent[0] = signals.pitch_correlations[1];
coefficients.fraction[1] = negate(ctx, signals.pitch_correlations[2]);
coefficients.exponent[1] = add(ctx, signals.pitch_correlations[3], Word16(1));
let (fraction, shift) = normalised_dot(ctx, seed, &y2, &y2);
coefficients.fraction[2] = fraction;
coefficients.exponent[2] = sub(ctx, Word16(-3), Word16(shift));
let (fraction, shift) = normalised_dot(ctx, seed, signals.pitch_target, &y2);
coefficients.fraction[3] = negate(ctx, fraction);
coefficients.exponent[3] = sub(ctx, Word16(7), Word16(shift));
let (fraction, shift) = normalised_dot(ctx, seed, signals.filtered_adaptive, &y2);
coefficients.fraction[4] = fraction;
coefficients.exponent[4] = sub(ctx, Word16(7), Word16(shift));
let optimum_code_gain = wants_optimum.then(|| {
let (fraction, shift) = normalised_dot(ctx, seed, signals.code_target, &y2);
let exponent = sub(ctx, Word16(6), Word16(shift));
if fraction.0 <= 0 {
(Word16(0), Word16(0))
} else {
let half = shr(ctx, fraction, 1);
let ratio = div_s(half, coefficients.fraction[2]);
let exponent = sub(ctx, exponent, coefficients.exponent[2]);
(ratio, sub(ctx, exponent, Word16(14)))
}
});
FilteredEnergies {
coefficients,
optimum_code_gain,
}
}
fn normalised_dot(
ctx: &mut DspContext,
seed: Word32,
a: &[Word16; L_SUBFR],
b: &[Word16; L_SUBFR],
) -> (Word16, i16) {
let mut s = seed;
for (&x, &y) in a.iter().zip(b.iter()) {
s = l_mac(ctx, s, x, y);
}
let shift = norm_l(s);
let scaled = l_shl(ctx, s, shift);
(extract_h(scaled), shift)
}
fn target_energy(ctx: &mut DspContext, xn: &[Word16; L_SUBFR]) -> (Word16, Word16) {
let (fraction, shift) = normalised_dot(ctx, Word32(0), xn, xn);
(sub(ctx, Word16(16), Word16(shift)), fraction)
}
struct UnfilteredEnergies {
fraction: [Word16; 4],
exponent: [Word16; 4],
ltp_gain: Word16,
}
fn unfiltered_energies(
ctx: &mut DspContext,
res: &[Word16; L_SUBFR],
exc: &[Word16; L_SUBFR],
code: &[Word16; L_SUBFR],
gain_pit: Word16,
) -> UnfilteredEnergies {
let mut fraction = [Word16(0); 4];
let mut exponent = [Word16(0); 4];
let mut s = Word32(0);
for &x in res {
s = l_mac(ctx, s, x, x);
}
if l_sub(ctx, s, Word32(400)).0 < 0 {
fraction[0] = Word16(0);
exponent[0] = Word16(-15);
} else {
let shift = norm_l(s);
let scaled = l_shl(ctx, s, shift);
fraction[0] = extract_h(scaled);
exponent[0] = sub(ctx, Word16(15), Word16(shift));
}
let (value, shift) = normalised_dot(ctx, Word32(0), exc, exc);
fraction[1] = value;
exponent[1] = sub(ctx, Word16(15), Word16(shift));
let (value, shift) = normalised_dot(ctx, Word32(0), exc, code);
fraction[2] = value;
exponent[2] = sub(ctx, Word16(2), Word16(shift));
let mut s = Word32(0);
for i in 0..L_SUBFR {
let product = l_mult(ctx, exc[i], gain_pit);
let scaled = l_shl(ctx, product, 1);
let contribution = round(ctx, scaled);
let residual = sub(ctx, res[i], contribution);
s = l_mac(ctx, s, residual, residual);
}
let shift = norm_l(s);
let scaled = l_shl(ctx, s, shift);
let ltp_residual = extract_h(scaled);
let ltp_exponent = sub(ctx, Word16(15), Word16(shift));
fraction[3] = ltp_residual;
exponent[3] = ltp_exponent;
let ltp_gain = if ltp_residual.0 > 0 && fraction[0].0 != 0 {
let half = shr(ctx, fraction[0], 1);
let ratio = div_s(half, ltp_residual);
let shift = sub(ctx, ltp_exponent, exponent[0]);
let wide = Word32(i32::from(ratio.0) << 16);
let shift = add(ctx, shift, Word16(3));
let wide = l_shr(ctx, wide, shift.0);
let (exp, frac) = log2(ctx, wide);
let exp = sub(ctx, exp, Word16(27));
let combined = l_comp(exp, frac);
let scaled = l_shl(ctx, combined, 13);
round(ctx, scaled)
} else {
Word16(0)
};
UnfilteredEnergies {
fraction,
exponent,
ltp_gain,
}
}
fn mac_32_16(ctx: &mut DspContext, acc: Word32, hi: Word16, lo: Word16, n: Word16) -> Word32 {
let acc = l_mac(ctx, acc, hi, n);
let partial = mult(ctx, lo, n);
l_mac(ctx, acc, partial, Word16(1))
}
fn mac_32(
ctx: &mut DspContext,
acc: Word32,
hi1: Word16,
lo1: Word16,
hi2: Word16,
lo2: Word16,
) -> Word32 {
let acc = l_mac(ctx, acc, hi1, hi2);
let cross = mult(ctx, hi1, lo2);
let acc = l_mac(ctx, acc, cross, Word16(1));
let cross = mult(ctx, lo1, hi2);
l_mac(ctx, acc, cross, Word16(1))
}
fn align_coefficients(
ctx: &mut DspContext,
fraction: &[Word16],
exp_max: &[Word16],
) -> (Vec<Word16>, Vec<Word16>) {
let mut top = exp_max[0];
for &candidate in &exp_max[1..] {
if sub(ctx, candidate, top).0 > 0 {
top = candidate;
}
}
let top = add(ctx, top, Word16(1));
let mut hi = Vec::with_capacity(fraction.len());
let mut lo = Vec::with_capacity(fraction.len());
for (&value, &exponent) in fraction.iter().zip(exp_max.iter()) {
let shift = sub(ctx, top, exponent);
let wide = Word32(i32::from(value.0) << 16);
let wide = l_shr(ctx, wide, shift.0);
let (h, l) = l_extract(wide);
hi.push(h);
lo.push(l);
}
(hi, lo)
}
fn scaling_exponents(
ctx: &mut DspContext,
exponent: &[Word16; COEFFS],
bias: Word16,
) -> [Word16; COEFFS] {
let doubled = shl(ctx, bias, 1);
let squared = add(ctx, Word16(15), doubled);
let incremented = add(ctx, Word16(1), bias);
[
sub(ctx, exponent[0], Word16(13)),
sub(ctx, exponent[1], Word16(14)),
add(ctx, exponent[2], squared),
add(ctx, exponent[3], bias),
add(ctx, exponent[4], incremented),
]
}
fn quantise_joint(
ctx: &mut DspContext,
mode_index: u8,
predicted: GainPrediction,
coefficients: &Coefficients,
gp_limit: Word16,
) -> (u16, SubframeGains, (Word16, Word16)) {
let (table, entries) = match mode_index {
3 | 4 | 6 => (&GAIN_HIGHRATES[..], VQ_SIZE_HIGHRATES),
_ => (&GAIN_LOWRATES[..], VQ_SIZE_LOWRATES),
};
let gcode0 = extract_l(pow2(ctx, Word16(14), predicted.fraction));
let bias = sub(ctx, predicted.exponent, Word16(11));
let exp_max = scaling_exponents(ctx, &coefficients.exponent, bias);
let (hi, lo) = align_coefficients(ctx, &coefficients.fraction, &exp_max);
let mut best = Word32(MAX_32);
let mut index = 0usize;
for candidate in 0..entries {
let entry = &table[candidate * JOINT_ENTRY..];
let pitch = Word16(entry[0]);
if sub(ctx, pitch, gp_limit).0 > 0 {
continue;
}
let code = mult(ctx, Word16(entry[1]), gcode0);
let pitch_squared = mult(ctx, pitch, pitch);
let code_squared = mult(ctx, code, code);
let cross = mult(ctx, code, pitch);
let mut acc = mpy_32_16(hi[0], lo[0], pitch_squared);
for (term, factor) in [pitch, code_squared, code, cross].into_iter().enumerate() {
let contribution = mpy_32_16(hi[term + 1], lo[term + 1], factor);
acc = Word32(acc.0.saturating_add(contribution.0));
}
if l_sub(ctx, acc, best).0 < 0 {
best = acc;
index = candidate;
}
}
let entry = &table[index * JOINT_ENTRY..];
let gain_pit = Word16(entry[0]);
let code = read_back_code_gain(ctx, Word16(entry[1]), gcode0, predicted.exponent, 10);
(
u16::try_from(index).expect("a VQ index is at most 255"),
SubframeGains {
pitch: gain_pit,
code,
},
(Word16(entry[2]), Word16(entry[3])),
)
}
fn read_back_code_gain(
ctx: &mut DspContext,
g_fac: Word16,
gcode0: Word16,
exp_gcode0: Word16,
shift: i16,
) -> Word16 {
let product = l_mult(ctx, g_fac, gcode0);
let amount = sub(ctx, Word16(shift), exp_gcode0);
let scaled = l_shr(ctx, product, amount.0);
extract_h(scaled)
}
fn optimum_code_gain(
ctx: &mut DspContext,
xn2: &[Word16; L_SUBFR],
y2: &[Word16; L_SUBFR],
) -> Word16 {
let mut scaled = [Word16(0); L_SUBFR];
for (out, &value) in scaled.iter_mut().zip(y2.iter()) {
*out = shr(ctx, value, 1);
}
let mut s = Word32(1);
for (&x, &y) in xn2.iter().zip(scaled.iter()) {
s = l_mac(ctx, s, x, y);
}
let cross_shift = norm_l(s);
let normalised = l_shl(ctx, s, cross_shift);
let cross = extract_h(normalised);
if cross.0 <= 0 {
return Word16(0);
}
let mut s = Word32(0);
for &y in &scaled {
s = l_mac(ctx, s, y, y);
}
let energy_shift = norm_l(s);
let normalised = l_shl(ctx, s, energy_shift);
let energy = extract_h(normalised);
let cross = shr(ctx, cross, 1);
let gain = div_s(cross, energy);
let denorm = sub(ctx, Word16(cross_shift + 5), Word16(energy_shift));
let truncated = shr(ctx, gain, denorm.0);
shl(ctx, truncated, 1)
}
fn quantise_code_gain(
ctx: &mut DspContext,
predicted: GainPrediction,
gain: Word16,
) -> (u16, Word16, (Word16, Word16)) {
let target = shr(ctx, gain, 1); let gcode0 = extract_l(pow2(ctx, predicted.exponent, predicted.fraction));
let gcode0 = shl(ctx, gcode0, 4);
let first = mult(ctx, gcode0, Word16(QUA_GAIN_CODE[0]));
let difference = sub(ctx, target, first);
let mut smallest = abs_s(ctx, difference);
let mut index = 0usize;
for candidate in 1..NB_QUA_CODE {
let scaled = mult(ctx, gcode0, Word16(QUA_GAIN_CODE[candidate * CODE_ENTRY]));
let difference = sub(ctx, target, scaled);
let error = abs_s(ctx, difference);
if sub(ctx, error, smallest).0 < 0 {
smallest = error;
index = candidate;
}
}
let entry = &QUA_GAIN_CODE[index * CODE_ENTRY..];
let scaled = mult(ctx, gcode0, Word16(entry[0]));
let gain = shl(ctx, scaled, 1);
(
u16::try_from(index).expect("a scalar index is at most 31"),
gain,
(Word16(entry[1]), Word16(entry[2])),
)
}
#[must_use]
pub fn quantise_pitch_gain_mr122(
ctx: &mut DspContext,
gp_limit: Word16,
gain: Word16,
) -> (u16, Word16) {
let mut quantised = gain;
let (index, _) = quantise_pitch_gain(ctx, 7, gp_limit, &mut quantised);
(index, quantised)
}
fn quantise_pitch_gain(
ctx: &mut DspContext,
mode_index: u8,
gp_limit: Word16,
gain: &mut Word16,
) -> (u16, Option<[(u16, Word16); 3]>) {
let difference = sub(ctx, *gain, Word16(QUA_GAIN_PITCH[0]));
let mut smallest = abs_s(ctx, difference);
let mut index = 0usize;
for (candidate, &tabulated) in QUA_GAIN_PITCH.iter().enumerate().skip(1) {
let value = Word16(tabulated);
if sub(ctx, value, gp_limit).0 > 0 {
continue;
}
let difference = sub(ctx, *gain, value);
let error = abs_s(ctx, difference);
if sub(ctx, error, smallest).0 < 0 {
smallest = error;
index = candidate;
}
}
let candidates = (mode_index == 5).then(|| {
let first = if index == 0 {
0
} else if index == NB_QUA_PITCH - 1
|| sub(ctx, Word16(QUA_GAIN_PITCH[index + 1]), gp_limit).0 > 0
{
index - 2
} else {
index - 1
};
let mut out = [(0u16, Word16(0)); 3];
for (offset, slot) in out.iter_mut().enumerate() {
let at = first + offset;
*slot = (
u16::try_from(at).expect("a pitch index is at most 15"),
Word16(QUA_GAIN_PITCH[at]),
);
}
out
});
*gain = if mode_index >= 7 {
Word16(QUA_GAIN_PITCH[index] & !3)
} else {
Word16(QUA_GAIN_PITCH[index])
};
(
u16::try_from(index).expect("a pitch index is at most 15"),
candidates,
)
}
fn update_unquantised_predictor(
ctx: &mut DspContext,
predictor: &mut CodeGainPredictor,
predicted: GainPrediction,
optimum: (Word16, Word16),
) {
let (mut fraction, mut exponent) = optimum;
if fraction.0 <= 0 {
predictor.push(MIN_QUA_ENER_MR122, MIN_QUA_ENER);
return;
}
let denominator = extract_l(pow2(ctx, Word16(14), predicted.fraction));
if sub(ctx, fraction, denominator).0 >= 0 {
fraction = shr(ctx, fraction, 1);
exponent = add(ctx, exponent, Word16(1));
}
let ratio = div_s(fraction, denominator);
let offset = sub(ctx, exponent, predicted.exponent);
let offset = sub(ctx, offset, Word16(1));
let (exp, frac) = log2(ctx, l_deposit_l(ratio));
let exp = add(ctx, exp, offset);
let rounded = shr_r(ctx, frac, 5);
let whole = shl(ctx, exp, 10);
let octaves = add(ctx, rounded, whole);
let (log2_energy, db_energy) = if sub(ctx, octaves, MIN_QUA_ENER_MR122).0 < 0 {
(MIN_QUA_ENER_MR122, MIN_QUA_ENER)
} else if sub(ctx, octaves, MAX_QUA_ENER_MR122).0 > 0 {
(MAX_QUA_ENER_MR122, MAX_QUA_ENER)
} else {
let product = mpy_32_16(exp, frac, DB_PER_OCTAVE);
let scaled = l_shl(ctx, product, 13);
(octaves, round(ctx, scaled))
};
predictor.push(log2_energy, db_energy);
}
fn store_pair_half(
ctx: &mut DspContext,
predictor: &mut CodeGainPredictor,
entry: &[i16],
gcode0: Word16,
exp_gcode0: Word16,
) -> SubframeGains {
let pitch = Word16(entry[0]);
let g_fac = Word16(entry[1]);
let code = read_back_code_gain(ctx, g_fac, gcode0, exp_gcode0, 10);
let (exp, frac) = log2(ctx, l_deposit_l(g_fac));
let exp = sub(ctx, exp, Word16(12));
let rounded = shr_r(ctx, frac, 5);
let whole = shl(ctx, exp, 10);
let octaves = add(ctx, rounded, whole);
let product = mpy_32_16(exp, frac, DB_PER_OCTAVE);
let scaled = l_shl(ctx, product, 13);
let decibels = round(ctx, scaled);
predictor.push(octaves, decibels);
SubframeGains { pitch, code }
}
fn quantise_pair_jointly(
ctx: &mut DspContext,
predictor: &mut CodeGainPredictor,
even: &PendingPair,
odd: (GainPrediction, &Coefficients, (Word16, Word16)),
code: &[Word16; L_SUBFR],
gp_limit: Word16,
) -> (u16, SubframeGains, SubframeGains) {
let (odd_predicted, odd_coefficients, odd_energy) = odd;
let even_gcode0 = extract_l(pow2(ctx, Word16(14), even.predicted.fraction));
let odd_gcode0 = extract_l(pow2(ctx, Word16(14), odd_predicted.fraction));
let even_bias = sub(ctx, even.predicted.exponent, Word16(11));
let odd_bias = sub(ctx, odd_predicted.exponent, Word16(11));
let mut exp_max = [Word16(0); 2 * COEFFS];
exp_max[..COEFFS].copy_from_slice(&scaling_exponents(
ctx,
&even.coefficients.exponent,
even_bias,
));
exp_max[COEFFS..].copy_from_slice(&scaling_exponents(
ctx,
&odd_coefficients.exponent,
odd_bias,
));
let (even_exp, mut even_frac) = even.target_energy;
let (odd_exp, mut odd_frac) = odd_energy;
let difference = even_exp.0 - odd_exp.0;
if difference > 0 {
odd_frac = shr(ctx, odd_frac, difference);
} else {
even_frac = shl(ctx, even_frac, difference);
}
let mut tilt = 0i16;
let half_odd = shr_r(ctx, odd_frac, 1);
if sub(ctx, half_odd, even_frac).0 > 0 {
tilt = 1;
} else {
let raised = add(ctx, even_frac, Word16(3));
let quarter_even = shr(ctx, raised, 2);
if sub(ctx, quarter_even, odd_frac).0 > 0 {
tilt = -1;
}
}
for slot in &mut exp_max[..COEFFS] {
*slot = add(ctx, *slot, Word16(tilt));
}
let mut fraction = [Word16(0); 2 * COEFFS];
fraction[..COEFFS].copy_from_slice(&even.coefficients.fraction);
fraction[COEFFS..].copy_from_slice(&odd_coefficients.fraction);
let (hi, lo) = align_coefficients(ctx, &fraction, &exp_max);
let mut best = Word32(MAX_32);
let mut index = 0usize;
for candidate in 0..MR475_VQ_SIZE {
let entry = &GAIN_MR475[candidate * JOINT_ENTRY..candidate * JOINT_ENTRY + JOINT_ENTRY];
let even_pitch = Word16(entry[0]);
let even_code = mult(ctx, Word16(entry[1]), even_gcode0);
let mut acc = {
let g2_pitch = mult(ctx, even_pitch, even_pitch);
let g2_code = mult(ctx, even_code, even_code);
let g_pit_cod = mult(ctx, even_code, even_pitch);
let mut acc = mpy_32_16(hi[0], lo[0], g2_pitch);
acc = mac_32_16(ctx, acc, hi[1], lo[1], even_pitch);
acc = mac_32_16(ctx, acc, hi[2], lo[2], g2_code);
acc = mac_32_16(ctx, acc, hi[3], lo[3], even_code);
mac_32_16(ctx, acc, hi[4], lo[4], g_pit_cod)
};
let even_over = sub(ctx, even_pitch, gp_limit);
let odd_pitch = Word16(entry[2]);
if even_over.0 > 0 || sub(ctx, odd_pitch, gp_limit).0 > 0 {
continue;
}
let odd_code = mult(ctx, Word16(entry[3]), odd_gcode0);
let g2_pitch = mult(ctx, odd_pitch, odd_pitch);
let g2_code = mult(ctx, odd_code, odd_code);
let g_pit_cod = mult(ctx, odd_code, odd_pitch);
acc = mac_32_16(ctx, acc, hi[5], lo[5], g2_pitch);
acc = mac_32_16(ctx, acc, hi[6], lo[6], odd_pitch);
acc = mac_32_16(ctx, acc, hi[7], lo[7], g2_code);
acc = mac_32_16(ctx, acc, hi[8], lo[8], odd_code);
acc = mac_32_16(ctx, acc, hi[9], lo[9], g_pit_cod);
if l_sub(ctx, acc, best).0 < 0 {
best = acc;
index = candidate;
}
}
let base = index * JOINT_ENTRY;
let even_gains = store_pair_half(
ctx,
predictor,
&GAIN_MR475[base..base + 2],
even_gcode0,
even.predicted.exponent,
);
let odd_predicted = predictor.predict(ctx, 0, code);
let odd_gcode0 = extract_l(pow2(ctx, Word16(14), odd_predicted.fraction));
let odd_gains = store_pair_half(
ctx,
predictor,
&GAIN_MR475[base + 2..base + 4],
odd_gcode0,
odd_predicted.exponent,
);
(
u16::try_from(index).expect("a 4.75 index is at most 255"),
even_gains,
odd_gains,
)
}
struct PreQuantised {
pitch: Word16,
pitch_index: u16,
code: Word16,
code_index: u16,
energies: (Word16, Word16),
}
fn prequantise_code_gain(
ctx: &mut DspContext,
exp_gcode0: Word16,
gcode0: Word16,
candidates: &[(u16, Word16); 3],
coefficients: &Coefficients,
) -> PreQuantised {
let bias = sub(ctx, exp_gcode0, Word16(10));
let exp_max = scaling_exponents(ctx, &coefficients.exponent, bias);
let (hi, lo) = align_coefficients(ctx, &coefficients.fraction, &exp_max);
let mut best = Word32(MAX_32);
let mut code_index = 0usize;
let mut pitch_slot = 0usize;
for (slot, &(_, g_pitch)) in candidates.iter().enumerate() {
let g2_pitch = mult(ctx, g_pitch, g_pitch);
let seed = mpy_32_16(hi[0], lo[0], g2_pitch);
let seed = mac_32_16(ctx, seed, hi[1], lo[1], g_pitch);
for candidate in 0..NB_QUA_CODE {
let g_code = mult(ctx, Word16(QUA_GAIN_CODE[candidate * CODE_ENTRY]), gcode0);
let squared = l_mult(ctx, g_code, g_code);
let (g2_hi, g2_lo) = l_extract(squared);
let crossed = l_mult(ctx, g_code, g_pitch);
let (cross_hi, cross_lo) = l_extract(crossed);
let mut acc = mac_32(ctx, seed, hi[2], lo[2], g2_hi, g2_lo);
acc = mac_32_16(ctx, acc, hi[3], lo[3], g_code);
acc = mac_32(ctx, acc, hi[4], lo[4], cross_hi, cross_lo);
if l_sub(ctx, acc, best).0 < 0 {
best = acc;
code_index = candidate;
pitch_slot = slot;
}
}
}
let entry = &QUA_GAIN_CODE[code_index * CODE_ENTRY..];
let code = read_back_code_gain(ctx, Word16(entry[0]), gcode0, exp_gcode0, 9);
let (pitch_index, pitch) = candidates[pitch_slot];
PreQuantised {
pitch,
pitch_index,
code,
code_index: u16::try_from(code_index).expect("a scalar index is at most 31"),
energies: (Word16(entry[1]), Word16(entry[2])),
}
}
struct AdaptedCriterion {
gain_pit: Word16,
exp_gcode0: Word16,
gcode0: Word16,
fraction: [Word16; 4],
exponent: [Word16; 4],
alpha: Word16,
optimum: Word16,
}
fn requantise_code_gain(
ctx: &mut DspContext,
input: &AdaptedCriterion,
pre_quantised: Word16,
) -> (u16, Word16, (Word16, Word16)) {
let limit_shift = sub(ctx, Word16(10), input.exp_gcode0);
let ceiling = shl(ctx, pre_quantised, limit_shift.0);
let g2_pitch = mult(ctx, input.gain_pit, input.gain_pit);
let complement = sub(ctx, Word16(32767), input.alpha);
let one_alpha = add(ctx, complement, Word16(1));
let mut coefficient = [Word16(0); COEFFS];
let mut coefficient_lo = [Word16(0); COEFFS];
let mut exponent = [Word16(0); COEFFS];
let weighted = l_mult(ctx, input.alpha, input.fraction[1]);
let weighted = extract_h(l_shl(ctx, weighted, 1));
let mut ltp_term = l_mult(ctx, weighted, g2_pitch);
exponent[1] = sub(ctx, input.exponent[1], Word16(15));
let weighted = l_mult(ctx, input.alpha, input.fraction[2]);
let weighted = extract_h(l_shl(ctx, weighted, 1));
coefficient[2] = mult(ctx, weighted, input.gain_pit);
let shift = sub(ctx, input.exp_gcode0, Word16(10));
exponent[2] = add(ctx, input.exponent[2], shift);
let weighted = l_mult(ctx, input.alpha, input.fraction[3]);
coefficient[3] = extract_h(l_shl(ctx, weighted, 1));
let doubled = shl(ctx, input.exp_gcode0, 1);
let shift = sub(ctx, doubled, Word16(7));
exponent[3] = add(ctx, input.exponent[3], shift);
coefficient[4] = mult(ctx, one_alpha, input.fraction[3]);
exponent[4] = add(ctx, exponent[3], Word16(1));
let weighted = l_mult(ctx, input.alpha, input.fraction[0]);
let (mut residual_root, root_exp) = sqrt_l_exp(ctx, weighted);
let root_exp = add(ctx, Word16(root_exp), Word16(47));
exponent[0] = sub(ctx, input.exponent[0], root_exp);
let mut top = add(ctx, exponent[0], Word16(31));
for &candidate in &exponent[1..] {
if sub(ctx, candidate, top).0 > 0 {
top = candidate;
}
}
let shift = sub(ctx, top, exponent[1]);
ltp_term = l_shr(ctx, ltp_term, shift.0);
for i in 2..COEFFS {
let shift = sub(ctx, top, exponent[i]);
let wide = Word32(i32::from(coefficient[i].0) << 16);
let wide = l_shr(ctx, wide, shift.0);
let (h, l) = l_extract(wide);
coefficient[i] = h;
coefficient_lo[i] = l;
}
let rebased = sub(ctx, top, Word16(31));
let difference = sub(ctx, rebased, exponent[0]);
let halved = shr(ctx, difference, 1);
residual_root = l_shr(ctx, residual_root, halved.0);
if difference.0 & 1 != 0 {
let (h, l) = l_extract(residual_root);
residual_root = mpy_32_16(h, l, Word16(23170));
}
let mut best = Word32(MAX_32);
let mut index = 0usize;
for candidate in 0..NB_QUA_CODE {
let g_code = mult(
ctx,
Word16(QUA_GAIN_CODE[candidate * CODE_ENTRY]),
input.gcode0,
);
if sub(ctx, g_code, ceiling).0 >= 0 {
break;
}
let squared = l_mult(ctx, g_code, g_code);
let (g2_hi, g2_lo) = l_extract(squared);
let error = sub(ctx, g_code, input.optimum);
let error_squared = l_mult(ctx, error, error);
let (d2_hi, d2_lo) = l_extract(error_squared);
let mut acc = mac_32_16(ctx, ltp_term, coefficient[2], coefficient_lo[2], g_code);
acc = mac_32(ctx, acc, coefficient[3], coefficient_lo[3], g2_hi, g2_lo);
let (root, exp) = sqrt_l_exp(ctx, acc);
let halved = shr(ctx, Word16(exp), 1);
let root = l_shr(ctx, root, halved.0);
let gap = l_sub(ctx, root, residual_root);
let gap = round(ctx, gap);
let mut distance = l_mult(ctx, gap, gap);
distance = mac_32(
ctx,
distance,
coefficient[4],
coefficient_lo[4],
d2_hi,
d2_lo,
);
if l_sub(ctx, distance, best).0 < 0 {
best = distance;
index = candidate;
}
}
let entry = &QUA_GAIN_CODE[index * CODE_ENTRY..];
let code = read_back_code_gain(ctx, Word16(entry[0]), input.gcode0, input.exp_gcode0, 9);
(
u16::try_from(index).expect("a scalar index is at most 31"),
code,
(Word16(entry[1]), Word16(entry[2])),
)
}
impl GainAdaptor {
fn adapt(&mut self, ctx: &mut DspContext, ltp_gain: Word16, gain_code: Word16) -> Word16 {
let mut level = if sub(ctx, ltp_gain, LTP_GAIN_THR1).0 <= 0 {
0
} else if sub(ctx, ltp_gain, LTP_GAIN_THR2).0 <= 0 {
1
} else {
2
};
let half = shr_r(ctx, gain_code, 1);
if sub(ctx, half, self.prev_gain_code).0 > 0 && sub(ctx, gain_code, Word16(200)).0 > 0 {
self.onset = Word16(8);
} else if self.onset.0 != 0 {
self.onset = sub(ctx, self.onset, Word16(1));
}
if self.onset.0 != 0 && level < 2 {
level += 1;
}
self.ltpg[0] = ltp_gain;
let filtered = median5(ctx, &self.ltpg);
let mut alpha = if level == 0 {
if sub(ctx, filtered, Word16(5443)).0 > 0 {
Word16(0)
} else if filtered.0 < 0 {
Word16(16384)
} else {
let scaled = shl(ctx, filtered, 2);
let slope = mult(ctx, Word16(24660), scaled);
sub(ctx, Word16(16384), slope)
}
} else {
Word16(0)
};
if self.prev_alpha.0 == 0 {
alpha = shr(ctx, alpha, 1);
}
self.prev_alpha = alpha;
self.prev_gain_code = gain_code;
self.ltpg.copy_within(0..LTPG_MEM_SIZE - 1, 1);
alpha
}
}
fn median5(ctx: &mut DspContext, values: &[Word16; LTPG_MEM_SIZE]) -> Word16 {
let mut remaining = *values;
let mut order = [0usize; LTPG_MEM_SIZE];
let mut index = 0usize;
for slot in &mut order {
let mut max = Word16(-32767);
for (j, &value) in remaining.iter().enumerate() {
if sub(ctx, value, max).0 >= 0 {
max = value;
index = j;
}
}
remaining[index] = Word16(i16::MIN);
*slot = index;
}
values[order[LTPG_MEM_SIZE / 2]]
}
#[cfg(test)]
mod tests {
use super::super::super::bitstream::parse;
use super::*;
use std::collections::HashMap;
use std::sync::OnceLock;
const TRACE_FRAMES: usize = 3;
const SUBFRAMES: usize = 4;
const TRACE_MODE: u8 = 4;
const TRACE_SUBFRAMES: usize = TRACE_FRAMES * SUBFRAMES;
const GP_CLIP: i16 = 15565;
type TraceRows = HashMap<(i32, i32, String), Vec<i32>>;
fn trace() -> &'static TraceRows {
static ROWS: OnceLock<TraceRows> = OnceLock::new();
ROWS.get_or_init(|| {
let text = include_str!("../../testdata/nb_enc_trace.txt");
let mut rows = TraceRows::new();
for line in text.lines() {
let mut field = line.split_whitespace();
if field.next() != Some("T") {
continue;
}
let frame: i32 = field.next().expect("frame").parse().expect("frame");
let subframe: i32 = field.next().expect("subframe").parse().expect("subframe");
let name = field.next().expect("name").to_owned();
let values = field.map(|v| v.parse().expect("value")).collect();
rows.insert((frame, subframe, name), values);
}
assert!(!rows.is_empty(), "the encoder trace parsed to nothing");
rows
})
}
fn row(frame: usize, subframe: usize, name: &str) -> Vec<Word16> {
let key = (
i32::try_from(frame).expect("frame"),
i32::try_from(subframe).expect("subframe"),
name.to_owned(),
);
trace()
.get(&key)
.unwrap_or_else(|| panic!("trace row {name} missing at frame {frame} sf {subframe}"))
.iter()
.map(|&v| Word16(i16::try_from(v).expect("a trace row holds Word16 values")))
.collect()
}
fn vector(frame: usize, subframe: usize, name: &str) -> [Word16; L_SUBFR] {
let values = row(frame, subframe, name);
assert_eq!(values.len(), L_SUBFR, "{name} is not a subframe vector");
let mut out = [Word16(0); L_SUBFR];
out.copy_from_slice(&values);
out
}
fn scalar(frame: usize, subframe: usize, name: &str) -> Word16 {
let values = row(frame, subframe, name);
assert_eq!(values.len(), 1, "{name} is not a scalar row");
values[0]
}
fn ctx() -> DspContext {
DspContext::default()
}
fn pitch_correlations(
ctx: &mut DspContext,
xn: &[Word16; L_SUBFR],
y1: &[Word16; L_SUBFR],
) -> [Word16; 4] {
let mut scaled = [Word16(0); L_SUBFR];
for (out, &value) in scaled.iter_mut().zip(y1.iter()) {
*out = shr(ctx, value, 2);
}
ctx.overflow = false;
let mut s = Word32(1);
for &y in y1 {
s = l_mac(ctx, s, y, y);
}
let (energy, energy_shift) = if ctx.overflow {
let mut s = Word32(1);
for &y in &scaled {
s = l_mac(ctx, s, y, y);
}
let exp = norm_l(s);
let normalised = l_shl(ctx, s, exp);
(round(ctx, normalised), exp - 4)
} else {
let exp = norm_l(s);
let normalised = l_shl(ctx, s, exp);
(round(ctx, normalised), exp)
};
ctx.overflow = false;
let mut s = Word32(1);
for (&x, &y) in xn.iter().zip(y1.iter()) {
s = l_mac(ctx, s, x, y);
}
let (cross, cross_shift) = if ctx.overflow {
let mut s = Word32(1);
for (&x, &y) in xn.iter().zip(scaled.iter()) {
s = l_mac(ctx, s, x, y);
}
let exp = norm_l(s);
let normalised = l_shl(ctx, s, exp);
(round(ctx, normalised), exp - 2)
} else {
let exp = norm_l(s);
let normalised = l_shl(ctx, s, exp);
(round(ctx, normalised), exp)
};
[
energy,
sub(ctx, Word16(15), Word16(energy_shift)),
cross,
sub(ctx, Word16(15), Word16(cross_shift)),
]
}
fn run(ctx: &mut DspContext) -> Vec<(usize, usize, GainDecision)> {
let mut quantiser = GainQuantiser::new();
let mut out = Vec::new();
for frame in 0..TRACE_FRAMES {
for subframe in 0..SUBFRAMES {
let residual = vector(frame, subframe, "res");
let adaptive = vector(frame, subframe, "adapt");
let code = vector(frame, subframe, "code");
let pitch_target = vector(frame, subframe, "xn");
let code_target = vector(frame, subframe, "xn2");
let filtered_adaptive = vector(frame, subframe, "y1");
let filtered_code = vector(frame, subframe, "y2");
let gain_pit = scalar(frame, subframe, "gain_pit_ol");
let gp_limit = if gain_pit.0 == GP_CLIP {
Word16(GP_CLIP)
} else {
Word16(i16::MAX)
};
let signals = SubframeSignals {
residual: &residual,
adaptive: &adaptive,
code: &code,
pitch_target: &pitch_target,
code_target: &code_target,
filtered_adaptive: &filtered_adaptive,
filtered_code: &filtered_code,
pitch_correlations: pitch_correlations(ctx, &pitch_target, &filtered_adaptive),
gain_pit,
gp_limit,
even_subframe: subframe % 2 == 0,
};
let decision = quantiser.quantise(ctx, TRACE_MODE, &signals);
out.push((frame, subframe, decision));
}
}
assert_eq!(
out.len(),
TRACE_SUBFRAMES,
"the harness produced nothing to compare"
);
out
}
#[test]
fn gains_are_bit_exact_against_the_74_trace() {
let mut c = ctx();
let run = run(&mut c);
let mut compared = 0usize;
for (frame, subframe, decision) in &run {
assert_eq!(
decision.gains.pitch,
scalar(*frame, *subframe, "gain_pit"),
"gain_pit differs at frame {frame} subframe {subframe}"
);
assert_eq!(
decision.gains.code,
scalar(*frame, *subframe, "gain_code"),
"gain_code differs at frame {frame} subframe {subframe}"
);
compared += 1;
}
assert_eq!(
compared, TRACE_SUBFRAMES,
"compared {compared} subframes, expected {TRACE_SUBFRAMES}"
);
}
#[test]
fn chosen_gain_index_matches_the_reference_bitstream() {
const HEADER: usize = 6;
const FRAME_BYTES: usize = 20;
let stream = include_bytes!("../../testdata/amrnb_enc_mode4.amr");
let mut c = ctx();
let run = run(&mut c);
let mut compared = 0usize;
for (frame, subframe, decision) in &run {
let start = HEADER + frame * FRAME_BYTES + 1;
let params =
parse(TRACE_MODE, &stream[start..start + FRAME_BYTES - 1]).expect("frame parses");
let want = params[6 + 4 * subframe];
let GainParams::Index(got) = decision.params else {
panic!("7.40 kbit/s emits exactly one gain word per subframe");
};
assert_eq!(
got, want,
"gain index differs at frame {frame} subframe {subframe}"
);
compared += 1;
}
assert_eq!(
compared, TRACE_SUBFRAMES,
"compared {compared} subframes, expected {TRACE_SUBFRAMES}"
);
}
#[test]
fn the_pitch_gain_limit_is_respected() {
let mut c = ctx();
let predicted = GainPrediction {
exponent: Word16(0),
fraction: Word16(0),
innovation_energy: None,
};
let coefficients = Coefficients {
fraction: [Word16(0), Word16(-32000), Word16(0), Word16(0), Word16(0)],
exponent: [Word16(0); COEFFS],
};
let (unlimited, gains, _) = quantise_joint(
&mut c,
TRACE_MODE,
predicted,
&coefficients,
Word16(i16::MAX),
);
assert!(
gains.pitch.0 > GP_CLIP,
"without a limit this fixture should pick a high pitch gain, got {}",
gains.pitch.0
);
let (limited, gains, _) = quantise_joint(
&mut c,
TRACE_MODE,
predicted,
&coefficients,
Word16(GP_CLIP),
);
assert!(
gains.pitch.0 <= GP_CLIP,
"an entry above gp_limit was chosen: {}",
gains.pitch.0
);
assert_ne!(
unlimited, limited,
"the limit must actually change the decision in this fixture"
);
}
#[test]
fn entry_zero_of_the_pitch_table_is_always_reachable() {
let mut c = ctx();
let mut gain = Word16(0);
let (index, _) = quantise_pitch_gain(&mut c, 7, Word16(GP_CLIP), &mut gain);
assert_eq!(index, 0);
assert_eq!(gain.0, 0);
}
#[test]
fn only_122_masks_the_quantised_pitch_gain() {
let mut c = ctx();
let (index, gain) = quantise_pitch_gain_mr122(&mut c, Word16(i16::MAX), Word16(15565));
assert_eq!(index, 10);
assert_eq!(gain.0, 15564, "12.2 kbit/s must clear the two LSBs");
let mut gain = Word16(15565);
quantise_pitch_gain(&mut c, 5, Word16(i16::MAX), &mut gain);
assert_eq!(gain.0, 15565, "7.95 kbit/s must not");
}
#[test]
fn the_122_pitch_gain_index_matches_the_reference_bitstream() {
const HEADER: usize = 6;
const FRAME_BYTES: usize = 32;
const MODE: u8 = 7;
let stream = include_bytes!("../../testdata/amrnb_enc_mode7.amr");
let mut c = ctx();
let mut compared = 0usize;
for frame in 0..TRACE_FRAMES {
let start = HEADER + frame * FRAME_BYTES + 1;
let params =
parse(MODE, &stream[start..start + FRAME_BYTES - 1]).expect("frame parses");
for subframe in 0..SUBFRAMES {
let index = params[6 + 13 * subframe];
let tabulated = Word16(QUA_GAIN_PITCH[usize::from(index)]);
let (got, quantised) =
quantise_pitch_gain_mr122(&mut c, Word16(i16::MAX), tabulated);
assert_eq!(
got, index,
"the reference's own quantised gain must re-quantise to \
its own index, frame {frame} subframe {subframe}"
);
assert_eq!(quantised.0, tabulated.0 & !3);
compared += 1;
}
}
assert_eq!(
compared, TRACE_SUBFRAMES,
"compared {compared} subframes, expected {TRACE_SUBFRAMES}"
);
}
#[test]
fn the_scalar_code_gain_search_breaks_ties_low() {
let mut c = ctx();
let predicted = GainPrediction {
exponent: Word16(-20),
fraction: Word16(0),
innovation_energy: None,
};
let gcode0 = extract_l(pow2(&mut c, predicted.exponent, predicted.fraction));
assert_eq!(gcode0.0, 0, "the fixture needs every candidate to tie");
let (index, _, _) = quantise_code_gain(&mut c, predicted, Word16(1234));
assert_eq!(index, 0, "a tie must keep the lower code-gain index");
}
#[test]
fn the_median_selects_the_last_of_equal_values() {
let mut c = ctx();
assert_eq!(
median5(
&mut c,
&[Word16(1), Word16(5), Word16(3), Word16(2), Word16(4)]
)
.0,
3,
"the median of 1..5 is 3 however it is ordered"
);
assert_eq!(median5(&mut c, &[Word16(7); LTPG_MEM_SIZE]).0, 7);
}
#[test]
fn the_two_accumulation_forms_are_not_interchangeable() {
let mut c = ctx();
let acc = Word32(i32::MAX - 1000);
let (hi, lo, n) = (Word16(32767), Word16(-32768), Word16(32767));
let stepwise = mac_32_16(&mut c, acc, hi, lo, n);
let contribution = mpy_32_16(hi, lo, n);
let once = Word32(acc.0.saturating_add(contribution.0));
assert_eq!(stepwise.0, 2_147_418_113);
assert_eq!(once.0, i32::MAX);
assert_ne!(
stepwise.0, once.0,
"the two accumulation forms must not be collapsed into one"
);
}
}