use super::decoder_tables::{PH_IMP_LOW, PH_IMP_LOW_MR795, PH_IMP_MID, PH_IMP_MID_MR795};
use super::lsp::{M, MP1};
use super::math::inv_sqrt;
use super::L_SUBFR;
use crate::fixed_point::arith::{add, extract_h, extract_l, mult, round, sub};
use crate::fixed_point::arith32::{l_deposit_l, l_mac, l_msu, l_mult, l_sub};
use crate::fixed_point::div::div_s;
use crate::fixed_point::shift::{l_shl, l_shr, norm_l, norm_s, shl, shr};
use crate::fixed_point::types::{DspContext, Word16, Word32, MAX_32};
const SYN_SCRATCH: usize = 80;
const MR74: u8 = 4;
const MR795: u8 = 5;
const MR102: u8 = 6;
const MR122: u8 = 7;
const PHD_GAIN_MEM: usize = 5;
const PHD_THR1_LTP: Word16 = Word16(9830);
const PHD_THR2_LTP: Word16 = Word16(14746);
const ON_FACT_PLUS1: Word16 = Word16(16384);
const ON_LENGTH: Word16 = Word16(2);
pub const EXC_ENERGY_HIST: usize = 9;
fn synthesis_recursion(
ctx: &mut DspContext,
a: &[Word16],
x: &[Word16],
mem: &[Word16; M],
) -> [Word16; SYN_SCRATCH] {
let lg = x.len();
assert!(
lg + M <= SYN_SCRATCH,
"synthesis filter length {lg} exceeds the reference's fixed scratch"
);
assert!(a.len() >= MP1, "synthesis filter needs a[0..=M]");
let mut tmp = [Word16(0); SYN_SCRATCH];
tmp[..M].copy_from_slice(mem);
for i in 0..lg {
let mut s = l_mult(ctx, x[i], a[0]);
for j in 1..=M {
s = l_msu(ctx, s, a[j], tmp[M + i - j]);
}
s = l_shl(ctx, s, 3);
tmp[M + i] = round(ctx, s);
}
tmp
}
fn commit(tmp: &[Word16; SYN_SCRATCH], out: &mut [Word16]) -> [Word16; M] {
let lg = out.len();
out.copy_from_slice(&tmp[M..M + lg]);
let mut updated = [Word16(0); M];
updated.copy_from_slice(&out[lg - M..]);
updated
}
pub fn synthesis_filter(
ctx: &mut DspContext,
a: &[Word16],
excitation: &[Word16],
out: &mut [Word16],
mem: &[Word16; M],
) -> [Word16; M] {
assert_eq!(
out.len(),
excitation.len(),
"synthesis in/out length mismatch"
);
assert!(out.len() >= M, "a subframe shorter than the filter memory");
let tmp = synthesis_recursion(ctx, a, excitation, mem);
commit(&tmp, out)
}
pub fn synthesis_filter_in_place(
ctx: &mut DspContext,
a: &[Word16],
signal: &mut [Word16],
mem: &[Word16; M],
) -> [Word16; M] {
assert!(
signal.len() >= M,
"a subframe shorter than the filter memory"
);
let tmp = synthesis_recursion(ctx, a, signal, mem);
commit(&tmp, signal)
}
pub fn lp_residual(ctx: &mut DspContext, a: &[Word16], signal: &[Word16], residual: &mut [Word16]) {
assert_eq!(
signal.len(),
residual.len() + M,
"Residu needs exactly M samples of history before the window"
);
assert!(a.len() >= MP1, "Residu needs a[0..=M]");
for (i, slot) in residual.iter_mut().enumerate() {
let mut s = l_mult(ctx, signal[M + i], a[0]);
for j in 1..=M {
s = l_mac(ctx, s, a[j], signal[M + i - j]);
}
s = l_shl(ctx, s, 3);
*slot = round(ctx, s);
}
}
#[must_use]
pub fn expand_bandwidth(ctx: &mut DspContext, a: &[Word16], factors: &[i16]) -> [Word16; MP1] {
assert!(a.len() >= MP1, "Weight_Ai needs a[0..=M]");
assert!(factors.len() >= M, "Weight_Ai needs M expansion factors");
let mut expanded = [Word16(0); MP1];
expanded[0] = a[0];
for i in 1..=M {
let scaled = l_mult(ctx, a[i], Word16(factors[i - 1]));
expanded[i] = round(ctx, scaled);
}
expanded
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Preemphasis {
previous: Word16,
}
impl Preemphasis {
#[must_use]
pub const fn new() -> Self {
Self {
previous: Word16(0),
}
}
pub fn filter(&mut self, ctx: &mut DspContext, signal: &mut [Word16], coefficient: Word16) {
let len = signal.len();
assert!(len > 0, "pre-emphasis of an empty block");
let carried = signal[len - 1];
for k in (1..len).rev() {
let feedback = mult(ctx, coefficient, signal[k - 1]);
signal[k] = sub(ctx, signal[k], feedback);
}
let feedback = mult(ctx, coefficient, self.previous);
signal[0] = sub(ctx, signal[0], feedback);
self.previous = carried;
}
#[must_use]
pub const fn memory(self) -> Word16 {
self.previous
}
}
fn block_energy(ctx: &mut DspContext, x: &[Word16]) -> Word32 {
let saved_overflow = ctx.overflow;
let mut s = l_mult(ctx, x[0], x[0]);
for &v in &x[1..] {
s = l_mac(ctx, s, v, v);
}
if l_sub(ctx, s, Word32(MAX_32)).0 == 0 {
ctx.overflow = saved_overflow;
let mut t = shr(ctx, x[0], 2);
let mut s = l_mult(ctx, t, t);
for &v in &x[1..] {
t = shr(ctx, v, 2);
s = l_mac(ctx, s, t, t);
}
s
} else {
l_shr(ctx, s, 4)
}
}
fn energy_ratio_root(
ctx: &mut DspContext,
numerator: Word16,
denominator: Word16,
exp: i16,
) -> Word16 {
let mut s = l_deposit_l(div_s(numerator, denominator));
s = l_shl(ctx, s, 7);
s = l_shr(ctx, s, exp);
let s = inv_sqrt(ctx, s);
let positioned = l_shl(ctx, s, 9);
round(ctx, positioned)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AdaptiveGain {
past_gain: Word16,
}
impl Default for AdaptiveGain {
fn default() -> Self {
Self::new()
}
}
impl AdaptiveGain {
#[must_use]
pub const fn new() -> Self {
Self {
past_gain: Word16(4096),
}
}
#[must_use]
pub const fn past_gain(self) -> Word16 {
self.past_gain
}
pub fn scale(
&mut self,
ctx: &mut DspContext,
reference: &[Word16],
signal: &mut [Word16],
factor: Word16,
) {
assert_eq!(reference.len(), signal.len(), "AGC block length mismatch");
assert!(!signal.is_empty(), "AGC of an empty block");
let energy_out = block_energy(ctx, signal);
if energy_out.0 == 0 {
self.past_gain = Word16(0);
return;
}
let mut exp = sub(ctx, Word16(norm_l(energy_out)), Word16(1));
let positioned = l_shl(ctx, energy_out, exp.0);
let normalised_out = round(ctx, positioned);
let energy_in = block_energy(ctx, reference);
let step = if energy_in.0 == 0 {
Word16(0)
} else {
let shift = norm_l(energy_in);
let positioned = l_shl(ctx, energy_in, shift);
let normalised_in = round(ctx, positioned);
exp = sub(ctx, exp, Word16(shift));
let root = energy_ratio_root(ctx, normalised_out, normalised_in, exp.0);
let complement = sub(ctx, Word16(32767), factor);
mult(ctx, root, complement)
};
let mut gain = self.past_gain;
for slot in signal.iter_mut() {
gain = mult(ctx, gain, factor);
gain = add(ctx, gain, step);
let product = l_mult(ctx, *slot, gain);
*slot = extract_h(l_shl(ctx, product, 3));
}
self.past_gain = gain;
}
}
pub fn match_energy(ctx: &mut DspContext, reference: &[Word16], signal: &mut [Word16]) {
assert_eq!(reference.len(), signal.len(), "agc2 block length mismatch");
assert!(!signal.is_empty(), "agc2 of an empty block");
let energy_out = block_energy(ctx, signal);
if energy_out.0 == 0 {
return;
}
let mut exp = sub(ctx, Word16(norm_l(energy_out)), Word16(1));
let positioned = l_shl(ctx, energy_out, exp.0);
let normalised_out = round(ctx, positioned);
let energy_in = block_energy(ctx, reference);
let gain = if energy_in.0 == 0 {
Word16(0)
} else {
let shift = norm_l(energy_in);
let positioned = l_shl(ctx, energy_in, shift);
let normalised_in = round(ctx, positioned);
exp = sub(ctx, exp, Word16(shift));
energy_ratio_root(ctx, normalised_out, normalised_in, exp.0)
};
for slot in signal.iter_mut() {
let product = l_mult(ctx, *slot, gain);
*slot = extract_h(l_shl(ctx, product, 3));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PhaseDispersion {
gain_memory: [Word16; PHD_GAIN_MEM],
previous_level: Word16,
previous_cb_gain: Word16,
locked: bool,
onset: Word16,
}
impl PhaseDispersion {
#[must_use]
pub const fn new() -> Self {
Self {
gain_memory: [Word16(0); PHD_GAIN_MEM],
previous_level: Word16(0),
previous_cb_gain: Word16(0),
locked: false,
onset: Word16(0),
}
}
pub const fn lock(&mut self) {
self.locked = true;
}
pub const fn release(&mut self) {
self.locked = false;
}
fn choose_level(&mut self, ctx: &mut DspContext, cb_gain: Word16, ltp_gain: Word16) -> Word16 {
for i in (1..PHD_GAIN_MEM).rev() {
self.gain_memory[i] = self.gain_memory[i - 1];
}
self.gain_memory[0] = ltp_gain;
let mut level = if sub(ctx, ltp_gain, PHD_THR2_LTP).0 < 0 {
if sub(ctx, ltp_gain, PHD_THR1_LTP).0 > 0 {
Word16(1)
} else {
Word16(0)
}
} else {
Word16(2)
};
let doubled = l_mult(ctx, self.previous_cb_gain, ON_FACT_PLUS1);
let positioned = l_shl(ctx, doubled, 2);
let onset_threshold = round(ctx, positioned);
if sub(ctx, cb_gain, onset_threshold).0 > 0 {
self.onset = ON_LENGTH;
} else if self.onset.0 > 0 {
self.onset = sub(ctx, self.onset, Word16(1));
}
if self.onset.0 == 0 {
let mut weak = Word16(0);
for &gain in &self.gain_memory {
if sub(ctx, gain, PHD_THR1_LTP).0 < 0 {
weak = add(ctx, weak, Word16(1));
}
}
if sub(ctx, weak, Word16(2)).0 > 0 {
level = Word16(0);
}
}
let one_step_up = add(ctx, self.previous_level, Word16(1));
if sub(ctx, level, one_step_up).0 > 0 && self.onset.0 == 0 {
level = sub(ctx, level, Word16(1));
}
if sub(ctx, level, Word16(2)).0 < 0 && self.onset.0 > 0 {
level = add(ctx, level, Word16(1));
}
if sub(ctx, cb_gain, Word16(10)).0 < 0 {
level = Word16(2);
}
if self.locked {
level = Word16(0);
}
self.previous_level = level;
self.previous_cb_gain = cb_gain;
level
}
pub fn apply(
&mut self,
ctx: &mut DspContext,
mode_index: u8,
excitation: &mut [Word16],
innovation: &mut [Word16],
gains: ExcitationGains,
) {
assert_eq!(
excitation.len(),
L_SUBFR,
"phase dispersion takes a subframe"
);
assert_eq!(
innovation.len(),
L_SUBFR,
"phase dispersion takes a subframe"
);
assert!(
mode_index <= MR122,
"mode {mode_index} is not a speech mode"
);
let level = self.choose_level(ctx, gains.codebook, gains.pitch);
let disperse =
mode_index != MR122 && mode_index != MR102 && mode_index != MR74 && level.0 < 2;
if disperse {
disperse_innovation(ctx, mode_index, level, innovation);
}
for i in 0..L_SUBFR {
let mut acc = l_mult(ctx, excitation[i], gains.pitch_factor);
acc = l_mac(ctx, acc, innovation[i], gains.codebook);
acc = l_shl(ctx, acc, gains.shift);
excitation[i] = round(ctx, acc);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExcitationGains {
pub codebook: Word16,
pub pitch: Word16,
pub pitch_factor: Word16,
pub shift: i16,
}
fn disperse_innovation(
ctx: &mut DspContext,
mode_index: u8,
level: Word16,
innovation: &mut [Word16],
) {
let mut original = [Word16(0); L_SUBFR];
let mut positions = [0usize; L_SUBFR];
let mut pulses = 0usize;
for (i, slot) in innovation.iter_mut().enumerate() {
if slot.0 != 0 {
positions[pulses] = i;
pulses += 1;
}
original[i] = *slot;
*slot = Word16(0);
}
let response: &[i16; L_SUBFR] = if mode_index == MR795 {
if level.0 == 0 {
&PH_IMP_LOW_MR795
} else {
&PH_IMP_MID_MR795
}
} else if level.0 == 0 {
&PH_IMP_LOW
} else {
&PH_IMP_MID
};
for &position in &positions[..pulses] {
let amplitude = original[position];
let mut tap = 0usize;
for slot in &mut innovation[position..] {
let contribution = mult(ctx, amplitude, Word16(response[tap]));
tap += 1;
*slot = add(ctx, *slot, contribution);
}
for slot in &mut innovation[..position] {
let contribution = mult(ctx, amplitude, Word16(response[tap]));
tap += 1;
*slot = add(ctx, *slot, contribution);
}
}
}
#[must_use]
pub fn median_of_nine(ctx: &mut DspContext, values: &[Word16; EXC_ENERGY_HIST]) -> Word16 {
let mut remaining = *values;
let mut rank = [0usize; EXC_ENERGY_HIST];
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(-32768);
*slot = chosen;
}
values[rank[EXC_ENERGY_HIST / 2]]
}
pub fn control_excitation(
ctx: &mut DspContext,
excitation: &mut [Word16],
energy: Word16,
history: &[Word16; EXC_ENERGY_HIST],
hangover: Word16,
prev_bfi: bool,
careful: bool,
) {
assert_eq!(excitation.len(), L_SUBFR, "Ex_ctrl takes a subframe");
let mut target = median_of_nine(ctx, history);
let recent_sum = add(ctx, history[7], history[8]);
let mut previous = shr(ctx, recent_sum, 1);
if sub(ctx, history[8], previous).0 < 0 {
previous = history[8];
}
if !(sub(ctx, energy, target).0 < 0 && sub(ctx, energy, Word16(5)).0 > 0) {
return;
}
let mut ceiling = shl(ctx, previous, 2);
if sub(ctx, hangover, Word16(7)).0 < 0 || prev_bfi {
ceiling = sub(ctx, ceiling, previous);
}
if sub(ctx, target, ceiling).0 > 0 {
target = ceiling;
}
let exp = norm_s(energy);
let normalised = shl(ctx, energy, exp);
let reciprocal = div_s(Word16(16383), normalised);
let mut acc = l_mult(ctx, target, reciprocal);
let denormalise = sub(ctx, Word16(20), Word16(exp));
acc = l_shr(ctx, acc, denormalise.0);
if l_sub(ctx, acc, Word32(32767)).0 > 0 {
acc = Word32(32767);
}
let mut scale = extract_l(acc);
if careful && sub(ctx, scale, Word16(3072)).0 > 0 {
scale = Word16(3072);
}
for slot in excitation.iter_mut() {
let mut product = l_mult(ctx, scale, *slot);
product = l_shr(ctx, product, 11);
*slot = extract_l(product);
}
}
#[cfg(test)]
mod tests {
use super::super::lsp::AZ_SIZE;
use super::super::vectors::{next_noise, noise, rows, Row};
use super::*;
fn ctx() -> DspContext {
DspContext::default()
}
const PROBE_GAINS: ExcitationGains = ExcitationGains {
codebook: Word16(1000),
pitch: Word16(0),
pitch_factor: Word16(0),
shift: 1,
};
fn labelled(section: &str, label: &str) -> Row {
*rows(section)
.iter()
.find(|r| r.label == label)
.unwrap_or_else(|| panic!("{section} has no {label} row"))
}
fn oracle_az() -> Vec<Word16> {
let az = labelled("synfilt", "az").words();
assert_eq!(az.len(), AZ_SIZE, "the az row is a whole frame of filters");
az
}
#[test]
fn synthesis_filtering_is_bit_exact_against_ts26073() {
let az = oracle_az();
let rows = rows("synfilt");
let mut c = ctx();
let mut mem = [Word16(0); M];
let mut compared = 0;
let mut subframe = 0usize;
let mut pending: Option<Vec<Word16>> = None;
for row in &rows {
match row.label {
"exc" => pending = Some(row.words()),
"out" => {
let exc = pending.take().expect("an exc row precedes every out row");
let want = row.words();
assert_eq!(exc.len(), L_SUBFR);
let mut got = vec![Word16(0); L_SUBFR];
let a = &az[subframe * MP1..(subframe + 1) * MP1];
mem = synthesis_filter(&mut c, a, &exc, &mut got, &mem);
for (i, (&g, &w)) in got.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.0, w.0,
"synfilt subframe {subframe}: y[{i}] = {} but the reference gives {}",
g.0, w.0
);
}
subframe += 1;
compared += 1;
}
_ => {}
}
}
assert_eq!(
compared, 4,
"synfilt replays four subframes, compared {compared}"
);
}
#[test]
fn the_synthesis_inputs_come_from_the_oracles_own_generator() {
let seed = i16::try_from(labelled("synfilt", "seed").ints()[0]).expect("seed fits");
let drawn = noise(seed, 4 * L_SUBFR, 5);
let dumped: Vec<Word16> = rows("synfilt")
.iter()
.filter(|r| r.label == "exc")
.flat_map(Row::words)
.collect();
assert_eq!(dumped.len(), 4 * L_SUBFR);
assert_eq!(
drawn, dumped,
"the regenerated excitation is not the oracle's"
);
}
#[test]
fn agc_is_bit_exact_against_ts26073() {
let rows = rows("agc");
let seed = i16::try_from(labelled("agc", "seed").ints()[0]).expect("seed fits");
let cases = rows.iter().filter(|r| r.label == "out").count();
let drawn = noise(seed, cases * 2 * L_SUBFR, 4);
let mut c = ctx();
let mut agc = AdaptiveGain::new();
assert_eq!(
agc.past_gain().0,
4096,
"AGC resets to unity, not to silence"
);
let mut compared = 0usize;
let mut n = 0usize;
let mut produced: Option<Vec<Word16>> = None;
for row in &rows {
match row.label {
"out" => {
let base = n * 2 * L_SUBFR;
let reference = &drawn[base..base + L_SUBFR];
let mut signal = drawn[base + L_SUBFR..base + 2 * L_SUBFR].to_vec();
agc.scale(
&mut c,
reference,
&mut signal,
Word16(super::super::AGC_FAC),
);
let want = row.words();
for (i, (&g, &w)) in signal.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.0, w.0,
"agc case {n}: out[{i}] = {} but the reference gives {}",
g.0, w.0
);
}
produced = Some(signal);
n += 1;
compared += 1;
}
"mem" => {
assert!(produced.take().is_some(), "a mem row follows every out row");
let want = i16::try_from(row.ints()[0]).expect("gain fits");
assert_eq!(agc.past_gain().0, want, "agc case {}: carried gain", n - 1);
}
_ => {}
}
}
assert_eq!(compared, 6, "agc replays six blocks, compared {compared}");
}
#[test]
fn agc2_is_bit_exact_against_ts26073() {
let rows = rows("agc2");
let seed = i16::try_from(labelled("agc2", "seed").ints()[0]).expect("seed fits");
let cases = rows.iter().filter(|r| r.label == "out").count();
let drawn = noise(seed, cases * 2 * L_SUBFR, 4);
let mut c = ctx();
let mut compared = 0usize;
for (n, row) in rows.iter().filter(|r| r.label == "out").enumerate() {
let base = n * 2 * L_SUBFR;
let reference = &drawn[base..base + L_SUBFR];
let mut signal = drawn[base + L_SUBFR..base + 2 * L_SUBFR].to_vec();
match_energy(&mut c, reference, &mut signal);
let want = row.words();
for (i, (&g, &w)) in signal.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.0, w.0,
"agc2 case {n}: out[{i}] = {} but the reference gives {}",
g.0, w.0
);
}
compared += 1;
}
assert_eq!(compared, 6, "agc2 replays six blocks, compared {compared}");
}
#[test]
fn bandwidth_expansion_is_bit_exact_against_ts26073() {
use super::super::decoder_tables::{GAMMA3, GAMMA3_MR122, GAMMA4, GAMMA4_MR122};
let sets: [&[i16; M]; 4] = [&GAMMA3_MR122, &GAMMA3, &GAMMA4_MR122, &GAMMA4];
let az = oracle_az();
let mut c = ctx();
let mut compared = 0usize;
let mut which: Option<usize> = None;
for row in &rows("weightai") {
match row.label {
"case" => {
let index = usize::try_from(row.ints()[0]).expect("case index");
assert_eq!(index, compared, "weightai cases arrive in order");
which = Some(index);
}
"out" => {
let index = which.take().expect("a case row precedes every out row");
let got = expand_bandwidth(&mut c, &az[..MP1], sets[index]);
let want = row.words();
assert_eq!(want.len(), MP1);
for (i, (&g, &w)) in got.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.0, w.0,
"weightai case {index}: a[{i}] = {} but the reference gives {}",
g.0, w.0
);
}
compared += 1;
}
_ => {}
}
}
assert_eq!(
compared, 4,
"weightai sweeps four factor sets, compared {compared}"
);
}
#[test]
fn lp_inverse_filtering_is_bit_exact_against_ts26073() {
let az = oracle_az();
let rows = rows("residu");
let signal = labelled("residu", "in").words();
let want = rows
.iter()
.find(|r| r.label == "out")
.expect("residu has an out row")
.words();
assert_eq!(
signal.len(),
L_SUBFR + M,
"residu supplies M samples of history"
);
assert_eq!(want.len(), L_SUBFR);
let mut c = ctx();
let mut got = vec![Word16(0); L_SUBFR];
lp_residual(&mut c, &az[..MP1], &signal, &mut got);
for (i, (&g, &w)) in got.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.0, w.0,
"residu: r[{i}] = {} but the reference gives {}",
g.0, w.0
);
}
assert_eq!(got.len(), L_SUBFR, "compared a whole subframe");
}
#[test]
fn preemphasis_is_bit_exact_against_ts26073() {
let rows = rows("preemph");
let mut seed = i16::try_from(labelled("preemph", "seed").ints()[0]).expect("seed fits");
let mut c = ctx();
let mut filter = Preemphasis::new();
let mut compared = 0usize;
let mut block: Option<(Word16, Vec<Word16>)> = None;
for row in &rows {
match row.label {
"case" => {
let raw = next_noise(&mut seed);
let coefficient = Word16((raw >> 3) & 0x0FFF);
let want = i16::try_from(row.ints()[0]).expect("coefficient fits");
assert_eq!(
coefficient.0, want,
"preemph case {compared}: regenerated coefficient"
);
let signal = (0..L_SUBFR)
.map(|_| Word16(next_noise(&mut seed) >> 4))
.collect();
block = Some((coefficient, signal));
}
"out" => {
let (coefficient, mut signal) =
block.take().expect("a case row precedes every out row");
filter.filter(&mut c, &mut signal, coefficient);
let want = row.words();
for (i, (&g, &w)) in signal.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.0, w.0,
"preemph case {compared}: y[{i}] = {} but the reference gives {}",
g.0, w.0
);
}
compared += 1;
}
"mem" => {
let want = i16::try_from(row.ints()[0]).expect("memory fits");
assert_eq!(
filter.memory().0,
want,
"preemph case {}: carried sample",
compared - 1
);
}
_ => {}
}
}
assert_eq!(
compared, 4,
"preemph replays four blocks, compared {compared}"
);
}
#[test]
fn phase_dispersion_is_bit_exact_against_ts26073() {
let mut c = ctx();
let mut state = PhaseDispersion::new();
let mut mode_index = 0u8;
let mut sequences = 0usize;
let mut compared = 0usize;
let mut step: Option<(Word16, Word16, i16)> = None;
let mut excitation: Option<Vec<Word16>> = None;
let mut innovation: Option<Vec<Word16>> = None;
let mut levels_seen = [false; 3];
let mut dispersed = 0usize;
for row in &rows("phdisp") {
match row.label {
"seq" => {
let head = row.ints();
mode_index = u8::try_from(head[0]).expect("mode index");
state = PhaseDispersion::new();
sequences += 1;
}
"step" => {
let v = row.ints();
step = Some((
Word16(i16::try_from(v[0]).expect("cbGain fits")),
Word16(i16::try_from(v[1]).expect("ltpGain fits")),
i16::try_from(v[2]).expect("shift fits"),
));
}
"x" => excitation = Some(row.words()),
"inno" => innovation = Some(row.words()),
"out" => {
let (cb_gain, ltp_gain, shift) = step.take().expect("a step row precedes out");
let mut x = excitation.take().expect("an x row precedes out");
let mut inno = innovation.take().expect("an inno row precedes out");
let inno_before = inno.clone();
state.release();
state.apply(
&mut c,
mode_index,
&mut x,
&mut inno,
ExcitationGains {
codebook: cb_gain,
pitch: ltp_gain,
pitch_factor: ltp_gain,
shift,
},
);
let want = row.words();
for (i, (&g, &w)) in x.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.0,
w.0,
"phdisp mode {mode_index} case {}: x[{i}] = {} but the reference \
gives {}",
compared % 5,
g.0,
w.0
);
}
let level = usize::try_from(state.previous_level.0).expect("level is 0..=2");
levels_seen[level] = true;
if inno != inno_before {
dispersed += 1;
}
compared += 1;
}
_ => {}
}
}
assert_eq!(sequences, 8, "phdisp sweeps all eight rates");
assert_eq!(
compared, 40,
"phdisp replays five subframes per rate, compared {compared}"
);
assert_eq!(
levels_seen, [true; 3],
"the sweep did not reach all three dispersion levels"
);
assert!(
dispersed >= 8,
"only {dispersed} of {compared} cases actually dispersed anything"
);
}
#[test]
fn excitation_control_is_bit_exact_against_ts26073() {
let mut c = ctx();
let mut compared = 0usize;
let mut scaled = 0usize;
let mut excitation: Option<Vec<Word16>> = None;
let mut history: Option<Vec<Word16>> = None;
let mut step: Option<(Word16, Word16, bool, bool)> = None;
for row in &rows("exctrl") {
match row.label {
"exc" => excitation = Some(row.words()),
"hist" => history = Some(row.words()),
"step" => {
let v = row.ints();
step = Some((
Word16(i16::try_from(v[0]).expect("energy fits")),
Word16(i16::try_from(v[1]).expect("hangover fits")),
v[2] != 0,
v[3] != 0,
));
}
"out" => {
let mut exc = excitation.take().expect("an exc row precedes out");
let hist_row = history.take().expect("a hist row precedes out");
let (energy, hangover, prev_bfi, careful) =
step.take().expect("a step row precedes out");
let hist: [Word16; EXC_ENERGY_HIST] =
hist_row.try_into().expect("nine history entries");
let before = exc.clone();
control_excitation(
&mut c, &mut exc, energy, &hist, hangover, prev_bfi, careful,
);
let want = row.words();
for (i, (&g, &w)) in exc.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.0, w.0,
"exctrl case {compared}: exc[{i}] = {} but the reference gives {}",
g.0, w.0
);
}
if before != exc {
scaled += 1;
}
compared += 1;
}
_ => {}
}
}
assert_eq!(
compared, 8,
"exctrl replays eight subframes, compared {compared}"
);
assert!(
scaled >= 1,
"no exctrl case actually rescaled the excitation"
);
assert!(
scaled < compared,
"no exctrl case exercised the pass-through gate"
);
}
#[test]
fn the_two_synthesis_entry_points_agree() {
let az = oracle_az();
let exc = noise(4321, L_SUBFR, 5);
let mem = [Word16(7); M];
let mut c = ctx();
let mut separate = vec![Word16(0); L_SUBFR];
let mem_a = synthesis_filter(&mut c, &az[..MP1], &exc, &mut separate, &mem);
let mut c2 = ctx();
let mut aliased = exc.clone();
let mem_b = synthesis_filter_in_place(&mut c2, &az[..MP1], &mut aliased, &mem);
assert_eq!(
separate, aliased,
"in-place filtering diverged from the copying form"
);
assert_eq!(mem_a, mem_b);
assert_eq!(mem_a.to_vec(), separate[L_SUBFR - M..].to_vec());
}
#[test]
fn the_inverse_filter_undoes_the_synthesis_filter() {
let az = oracle_az();
let a = &az[..MP1];
let speech = noise(1379, L_SUBFR + M, 6);
let mut c = ctx();
let mut residual = vec![Word16(0); L_SUBFR];
lp_residual(&mut c, a, &speech, &mut residual);
let mut mem = [Word16(0); M];
mem.copy_from_slice(&speech[..M]);
let mut recovered = vec![Word16(0); L_SUBFR];
synthesis_filter(&mut c, a, &residual, &mut recovered, &mem);
for (i, (&r, &s)) in recovered.iter().zip(speech[M..].iter()).enumerate() {
let error = i32::from(r.0) - i32::from(s.0);
assert!(
error.abs() <= 4,
"round trip sample {i}: {} against {}, error {error}",
r.0,
s.0
);
}
}
#[test]
fn expansion_preserves_the_leading_coefficient_and_shrinks_the_rest() {
use super::super::decoder_tables::GAMMA3;
let az = oracle_az();
let mut c = ctx();
let expanded = expand_bandwidth(&mut c, &az[..MP1], &GAMMA3);
assert_eq!(expanded[0].0, 4096, "Weight_Ai multiplied a[0]");
for i in 1..=M {
assert!(
i32::from(expanded[i].0).abs() <= i32::from(az[i].0).abs(),
"coefficient {i} grew under bandwidth expansion"
);
}
}
#[test]
fn the_two_identical_factor_sets_really_are_identical() {
use super::super::decoder_tables::{GAMMA3_MR122, GAMMA4};
let az = oracle_az();
let mut c = ctx();
let a = expand_bandwidth(&mut c, &az[..MP1], &GAMMA3_MR122);
let b = expand_bandwidth(&mut c, &az[..MP1], &GAMMA4);
assert_eq!(a, b);
assert_ne!(GAMMA3_MR122, super::super::decoder_tables::GAMMA4_MR122);
}
#[test]
fn preemphasis_with_a_zero_coefficient_is_the_identity() {
let mut c = ctx();
let mut filter = Preemphasis::new();
let original = noise(999, L_SUBFR, 4);
let mut signal = original.clone();
filter.filter(&mut c, &mut signal, Word16(0));
assert_eq!(
signal, original,
"a zero coefficient must not alter the block"
);
assert_eq!(
filter.memory(),
original[L_SUBFR - 1],
"the carried sample is the last input, not the last output"
);
}
#[test]
fn preemphasis_carries_the_input_sample_not_the_filtered_one() {
let mut c = ctx();
let mut filter = Preemphasis::new();
let original = noise(2024, L_SUBFR, 4);
let mut signal = original.clone();
filter.filter(&mut c, &mut signal, Word16(16384));
assert_ne!(
signal[L_SUBFR - 1],
original[L_SUBFR - 1],
"the block was not filtered"
);
assert_eq!(filter.memory(), original[L_SUBFR - 1]);
}
#[test]
fn agc2_brings_the_two_energies_together() {
let mut c = ctx();
let reference = noise(5150, L_SUBFR, 3);
let mut signal: Vec<Word16> = noise(5900, L_SUBFR, 6)
.iter()
.map(|w| Word16(w.0))
.collect();
let energy = |v: &[Word16]| -> f64 { v.iter().map(|w| f64::from(w.0).powi(2)).sum() };
let target = energy(&reference);
let before = energy(&signal);
assert!(before > 0.0 && target > 0.0);
match_energy(&mut c, &reference, &mut signal);
let after = energy(&signal);
assert!(
(after / target).log2().abs() < (before / target).log2().abs(),
"agc2 moved the energy away from its target: {before} -> {after}, target {target}"
);
assert!(
(after / target).log2().abs() < 0.25,
"agc2 left the energies {} dB apart",
10.0 * (after / target).log10()
);
}
#[test]
fn phase_dispersion_of_a_single_pulse_rotates_the_impulse_response() {
for (mode_index, response) in [(0u8, &PH_IMP_LOW), (MR795, &PH_IMP_LOW_MR795)] {
for position in [0usize, 1, 17, 39] {
let mut c = ctx();
let mut state = PhaseDispersion::new();
state.lock();
let mut innovation = vec![Word16(0); L_SUBFR];
innovation[position] = Word16(16384);
let mut excitation = vec![Word16(0); L_SUBFR];
state.apply(
&mut c,
mode_index,
&mut excitation,
&mut innovation,
PROBE_GAINS,
);
for (i, got) in innovation.iter().enumerate() {
let tap = (i + L_SUBFR - position) % L_SUBFR;
let want = mult(&mut c, Word16(16384), Word16(response[tap]));
assert_eq!(
got.0, want.0,
"mode {mode_index}, pulse at {position}: innovation[{i}] is not tap {tap}"
);
}
}
}
}
#[test]
fn an_onset_holds_dispersion_one_step_below_maximum() {
let mut c = ctx();
let mut state = PhaseDispersion::new();
let mut levels = Vec::new();
for _ in 0..4 {
let mut excitation = vec![Word16(0); L_SUBFR];
let mut innovation = vec![Word16(0); L_SUBFR];
state.apply(&mut c, 0, &mut excitation, &mut innovation, PROBE_GAINS);
levels.push(state.previous_level.0);
}
assert_eq!(
levels,
vec![1, 1, 0, 0],
"onset hold did not run for two subframes"
);
}
#[test]
fn the_rates_that_never_disperse_leave_the_innovation_alone() {
for mode_index in [MR74, MR102, MR122] {
let mut c = ctx();
let mut state = PhaseDispersion::new();
let original = noise(777, L_SUBFR, 4);
let mut innovation = original.clone();
let mut excitation = vec![Word16(0); L_SUBFR];
state.apply(
&mut c,
mode_index,
&mut excitation,
&mut innovation,
PROBE_GAINS,
);
assert_eq!(
innovation, original,
"mode {mode_index} dispersed an innovation it must not touch"
);
}
}
#[test]
fn the_gain_history_is_a_shift_register_in_every_mode() {
for mode_index in 0..=MR122 {
let mut c = ctx();
let mut state = PhaseDispersion::new();
let gains = [
Word16(100),
Word16(200),
Word16(300),
Word16(400),
Word16(500),
];
for &g in &gains {
let mut excitation = vec![Word16(0); L_SUBFR];
let mut innovation = vec![Word16(0); L_SUBFR];
state.apply(
&mut c,
mode_index,
&mut excitation,
&mut innovation,
ExcitationGains {
pitch: g,
..PROBE_GAINS
},
);
}
let mut want: Vec<Word16> = gains.to_vec();
want.reverse();
assert_eq!(
state.gain_memory.to_vec(),
want,
"mode {mode_index} did not maintain the LTP gain history"
);
}
}
#[test]
fn locking_overrides_the_low_level_cutout() {
let mut c = ctx();
let mut state = PhaseDispersion::new();
state.lock();
let mut innovation = vec![Word16(0); L_SUBFR];
innovation[3] = Word16(8192);
let mut excitation = vec![Word16(0); L_SUBFR];
state.apply(
&mut c,
0,
&mut excitation,
&mut innovation,
ExcitationGains {
codebook: Word16(5),
pitch: Word16(16000),
..PROBE_GAINS
},
);
assert_eq!(
state.previous_level.0, 0,
"the lock did not force full dispersion"
);
assert_ne!(
innovation[0].0, 0,
"a locked disperser left the innovation alone"
);
}
#[test]
fn the_median_is_a_genuine_median() {
let mut c = ctx();
let mut seed = 4242i16;
for _ in 0..64 {
let mut values = [Word16(0); EXC_ENERGY_HIST];
for slot in &mut values {
*slot = Word16(next_noise(&mut seed) >> 4);
}
let got = median_of_nine(&mut c, &values);
let mut sorted: Vec<i16> = values.iter().map(|w| w.0).collect();
sorted.sort_unstable();
assert_eq!(got.0, sorted[4], "median of {sorted:?}");
}
}
#[test]
fn excitation_control_is_a_no_op_outside_its_window() {
let mut c = ctx();
let history = [Word16(100); EXC_ENERGY_HIST];
let original = noise(313, L_SUBFR, 5);
for energy in [Word16(0), Word16(5), Word16(100), Word16(1000)] {
let mut exc = original.clone();
control_excitation(&mut c, &mut exc, energy, &history, Word16(4), false, false);
assert_eq!(
exc, original,
"energy {} is outside the scaling window but the excitation moved",
energy.0
);
}
}
#[test]
fn the_careful_flag_caps_the_scale_factor_at_three() {
let history = [Word16(1000); EXC_ENERGY_HIST];
let original = noise(818, L_SUBFR, 8);
let mut careful = original.clone();
let mut c = ctx();
control_excitation(
&mut c,
&mut careful,
Word16(6),
&history,
Word16(9),
false,
true,
);
let mut free = original.clone();
control_excitation(
&mut c,
&mut free,
Word16(6),
&history,
Word16(9),
false,
false,
);
let peak = |v: &[Word16]| v.iter().map(|w| i32::from(w.0).abs()).max().unwrap_or(0);
assert!(
peak(&careful) < peak(&free),
"the careful flag did not restrain the gain"
);
assert!(
peak(&careful) <= 3 * peak(&original) + 1,
"the careful cap let the excitation past three times its input"
);
}
}