use super::codebook::L_SUBFR;
use super::gain_tables::{QUA_GAIN_6B, QUA_GAIN_7B};
use super::math::{dot_product12, isqrt_n, log2, median5, pow2};
use crate::fixed_point::arith::{extract_h, extract_l, mult, round, sub};
use crate::fixed_point::arith32::{l_deposit_h, l_mac, l_mult};
use crate::fixed_point::oper32::{l_extract, mpy_32_16};
use crate::fixed_point::shift::{l_shl, l_shr};
use crate::fixed_point::types::{DspContext, Word16, Word32};
const PRED_ORDER: usize = 4;
const MEAN_ENER: i16 = 30;
const L_LTPHIST: usize = 5;
const PRED: [Word16; PRED_ORDER] = [Word16(4096), Word16(3277), Word16(2458), Word16(1638)];
const PDOWN_UNUSABLE: [Word16; 7] = [
Word16(32767),
Word16(31130),
Word16(29491),
Word16(24576),
Word16(7537),
Word16(1638),
Word16(328),
];
const CDOWN_UNUSABLE: [Word16; 7] = [
Word16(32767),
Word16(16384),
Word16(8192),
Word16(8192),
Word16(8192),
Word16(4915),
Word16(3277),
];
const PDOWN_USABLE: [Word16; 7] = [
Word16(32767),
Word16(32113),
Word16(31457),
Word16(24576),
Word16(7537),
Word16(1638),
Word16(328),
];
const CDOWN_USABLE: [Word16; 7] = [
Word16(32767),
Word16(32113),
Word16(32113),
Word16(32113),
Word16(32113),
Word16(32113),
Word16(22938),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Gains {
pub pitch: Word16,
pub code: Word32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameQuality {
Good,
Bad,
Unusable,
}
#[derive(Debug, Clone)]
pub struct GainDecoder {
past_energy: [Word16; PRED_ORDER],
past_pitch: Word16,
past_code: Word16,
prev_code: Word16,
pitch_history: [Word16; L_LTPHIST],
code_history: [Word16; L_LTPHIST],
}
impl Default for GainDecoder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameContext {
pub quality: FrameQuality,
pub erasure_state: usize,
pub vad_history: i16,
pub previous_frame_bad: bool,
}
impl FrameContext {
#[must_use]
pub const fn good() -> Self {
Self {
quality: FrameQuality::Good,
erasure_state: 0,
vad_history: 0,
previous_frame_bad: false,
}
}
}
impl GainDecoder {
#[must_use]
pub const fn new() -> Self {
Self {
past_energy: [Word16(-14336); PRED_ORDER],
past_pitch: Word16(0),
past_code: Word16(0),
prev_code: Word16(0),
pitch_history: [Word16(0); L_LTPHIST],
code_history: [Word16(0); L_LTPHIST],
}
}
#[must_use]
pub fn decode(
&mut self,
index: u16,
bits: usize,
code: &[Word16; L_SUBFR],
frame: FrameContext,
) -> Gains {
let mut ctx = DspContext::default();
let (energy, exp) = dot_product12(&mut ctx, code, code);
let (inv_norm, exp) = isqrt_n(&mut ctx, (energy, exp - 24));
let gcode_inov = extract_h(l_shl(&mut ctx, inv_norm, exp - 3));
if frame.quality == FrameQuality::Good {
self.decode_good(&mut ctx, index, bits, gcode_inov, frame.previous_frame_bad)
} else {
self.conceal(
&mut ctx,
frame.quality,
frame.erasure_state,
frame.vad_history,
gcode_inov,
)
}
}
fn decode_good(
&mut self,
ctx: &mut DspContext,
index: u16,
bits: usize,
gcode_inov: Word16,
previous_frame_bad: bool,
) -> Gains {
let mut acc = l_shl(ctx, l_deposit_h(Word16(MEAN_ENER)), 8);
for (coeff, past) in PRED.iter().zip(self.past_energy.iter()) {
acc = l_mac(ctx, acc, *coeff, *past);
}
let gcode0 = extract_h(acc);
let scaled = l_mult(ctx, gcode0, Word16(5443));
let acc = l_shr(ctx, scaled, 8);
let (mut exp_gcode0, frac) = l_extract(acc);
let gcode0 = extract_l(pow2(ctx, 14, frac));
exp_gcode0 = Word16(exp_gcode0.0 - 14);
let table: &[i16] = if bits == 6 {
&QUA_GAIN_6B
} else {
&QUA_GAIN_7B
};
let entry = index as usize * 2;
let pitch = Word16(table[entry]);
let g_code = Word16(table[entry + 1]);
let acc = l_mult(ctx, g_code, gcode0);
let mut code_gain = l_shl(ctx, acc, exp_gcode0.0 + 4);
if previous_frame_bad {
let ceiling = l_mult(ctx, self.prev_code, Word16(5120));
if code_gain.0 > ceiling.0 && code_gain.0 > 6_553_600 {
code_gain = ceiling;
}
}
let raised = l_shl(ctx, code_gain, 3);
self.past_code = round(ctx, raised);
self.past_pitch = pitch;
self.prev_code = self.past_code;
self.push_history(self.past_pitch, self.past_code);
let (hi, lo) = l_extract(code_gain);
let code = l_shl(ctx, mpy_32_16(hi, lo, gcode_inov), 3);
let (exp, frac) = log2(ctx, Word32(i32::from(g_code.0)));
let scaled = mpy_32_16(Word16(exp - 11), frac, Word16(24660));
let energy = extract_l(l_shr(ctx, scaled, 3));
self.push_energy(energy);
Gains { pitch, code }
}
fn conceal(
&mut self,
ctx: &mut DspContext,
quality: FrameQuality,
erasure_state: usize,
vad_history: i16,
gcode_inov: Word16,
) -> Gains {
let state = erasure_state.min(6);
let unusable = quality == FrameQuality::Unusable;
let mut pitch = median5(&self.pitch_history);
if pitch.0 > 15565 {
pitch = Word16(15565);
}
self.past_pitch = pitch;
let attenuation = if unusable {
PDOWN_UNUSABLE[state]
} else {
PDOWN_USABLE[state]
};
let pitch_out = mult(ctx, attenuation, self.past_pitch);
let median_code = median5(&self.code_history);
self.past_code = if vad_history > 2 {
median_code
} else if unusable {
mult(ctx, CDOWN_UNUSABLE[state], median_code)
} else {
mult(ctx, CDOWN_USABLE[state], median_code)
};
let mut acc = Word32(0);
for past in &self.past_energy {
acc = l_mac(ctx, acc, *past, Word16(8192));
}
let mut energy = extract_h(acc);
energy = sub(ctx, energy, Word16(3072));
if energy.0 < -14336 {
energy = Word16(-14336);
}
self.push_energy(energy);
self.push_history(self.past_pitch, self.past_code);
let code = l_mult(ctx, self.past_code, gcode_inov);
Gains {
pitch: pitch_out,
code,
}
}
fn push_energy(&mut self, energy: Word16) {
self.past_energy.rotate_right(1);
self.past_energy[0] = energy;
}
fn push_history(&mut self, pitch: Word16, code: Word16) {
self.pitch_history.rotate_left(1);
self.pitch_history[L_LTPHIST - 1] = pitch;
self.code_history.rotate_left(1);
self.code_history[L_LTPHIST - 1] = code;
}
}
#[cfg(test)]
mod tests {
use super::super::codebook;
use super::super::lp::isp_to_lp::tests_support::{block_has, block_row_i32, has_block};
use super::super::params::FrameParams;
use super::*;
use crate::codecs::amr::mode::{AmrMode, AmrVariant};
use crate::codecs::amr::storage;
fn mode_for(index: usize) -> AmrMode {
AmrMode::new(AmrVariant::WideBand, u8::try_from(index).expect("index")).expect("mode")
}
fn fixture(mode_index: usize) -> &'static [u8] {
const FILES: [&[u8]; 9] = [
include_bytes!("../testdata/amrwb_mode0.amr"),
include_bytes!("../testdata/amrwb_mode1.amr"),
include_bytes!("../testdata/amrwb_mode2.amr"),
include_bytes!("../testdata/amrwb_mode3.amr"),
include_bytes!("../testdata/amrwb_mode4.amr"),
include_bytes!("../testdata/amrwb_mode5.amr"),
include_bytes!("../testdata/amrwb_mode6.amr"),
include_bytes!("../testdata/amrwb_mode7.amr"),
include_bytes!("../testdata/amrwb_mode8.amr"),
];
FILES[mode_index]
}
const FRAME_BITS: [usize; 9] = [132, 177, 253, 285, 317, 365, 397, 461, 477];
#[test]
fn gains_are_bit_exact_against_ts26173() {
let mut checked = 0;
for (mode_index, &frame_bits) in FRAME_BITS.iter().enumerate() {
let block = format!("bitstream{mode_index}");
assert!(has_block(&block), "fixture block {block} missing");
let (_, frames) = storage::read(fixture(mode_index)).expect("fixture parses");
let mode = mode_for(mode_index);
let mut dec = GainDecoder::new();
let gain_bits = if frame_bits <= 177 { 6 } else { 7 };
for f in 0.. {
if !block_has(&block, &format!("meta{f}")) {
break;
}
let frame = frames.get(f).expect("frame");
let params = FrameParams::parse(mode, &frame.data).expect("parses");
for (sf, sub) in params.subframes.iter().enumerate() {
let code = codebook::decode(&sub.pulses, frame_bits).expect("code");
let code: [Word16; L_SUBFR] = code.map(Word16);
let got = dec.decode(sub.gain_index, gain_bits, &code, FrameContext::good());
let want = block_row_i32(&block, &format!("gain{f}_{sf}"));
assert_eq!(
i32::from(got.pitch.0),
want[0],
"{block} frame {f} subframe {sf}: pitch gain"
);
assert_eq!(
got.code.0, want[1],
"{block} frame {f} subframe {sf}: code gain"
);
checked += 1;
}
}
}
assert!(checked >= 72, "only {checked} subframes checked");
}
#[test]
fn a_reset_decoder_does_not_start_loud() {
let quiet = GainDecoder::new();
assert!(
quiet.past_energy.iter().all(|e| e.0 == -14336),
"predictor did not start at -14 dB"
);
}
#[test]
fn sustained_erasure_fades_toward_silence() {
let code = [Word16(512); L_SUBFR];
let mut dec = GainDecoder::new();
for _ in 0..8 {
let _ = dec.decode(20, 7, &code, FrameContext::good());
}
let mut previous = i64::MAX;
for state in 1..7 {
let g = dec.decode(
0,
7,
&code,
FrameContext {
quality: FrameQuality::Unusable,
erasure_state: state,
..FrameContext::good()
},
);
let level = i64::from(g.pitch.0);
assert!(
level <= previous,
"erasure state {state} gave pitch gain {level}, above the previous {previous}"
);
previous = level;
}
assert!(previous < 1000, "sustained erasure held at {previous}");
}
#[test]
fn a_concealed_pitch_gain_cannot_self_oscillate() {
let code = [Word16(512); L_SUBFR];
let mut dec = GainDecoder::new();
for _ in 0..8 {
let _ = dec.decode(63, 7, &code, FrameContext::good());
}
for state in 0..7 {
let g = dec.decode(
0,
7,
&code,
FrameContext {
quality: FrameQuality::Unusable,
erasure_state: state,
..FrameContext::good()
},
);
assert!(
g.pitch.0 < 16384,
"erasure state {state}: pitch gain {} would self-oscillate",
g.pitch.0
);
}
}
#[test]
fn the_energy_normalisation_offsets_pulse_placement() {
let sparse = {
let mut c = [Word16(0); L_SUBFR];
c[0] = Word16(512);
c
};
let dense = [Word16(512); L_SUBFR];
let mut a = GainDecoder::new();
let mut b = GainDecoder::new();
let g_sparse = a.decode(30, 7, &sparse, FrameContext::good());
let g_dense = b.decode(30, 7, &dense, FrameContext::good());
assert!(
g_sparse.code.0 > g_dense.code.0,
"the sparser vector should get the larger gain: {} against {}",
g_sparse.code.0,
g_dense.code.0
);
assert_eq!(g_sparse.pitch, g_dense.pitch);
}
}