use super::cn::{a_refl, build_cn_code, pseudonoise, PN_INITIAL_SEED};
use super::decoder_tables::{LSP_INIT, MEAN_LSF_3};
use super::enc::dtx::DTX_HIST_SIZE;
use super::enc::lsp_quant::lsp_to_lsf;
use super::gain::CodeGainPredictor;
use super::lsp::{lsf_to_lsp, lsp_to_lp, reorder_lsf, LsfDecoder, M, MP1};
use super::math::{log2, pow2};
use super::synthesis::synthesis_filter;
use super::L_FRAME;
use crate::fixed_point::arith::{abs_s, add, extract_h, extract_l, mult, sub};
use crate::fixed_point::arith32::{l_add, l_deposit_h, l_deposit_l, l_mac, l_mult, l_sub};
use crate::fixed_point::div::div_s;
use crate::fixed_point::shift::{l_shl, l_shr, shl, shr};
use crate::fixed_point::types::{DspContext, Word16, Word32};
const L_SUBFR: usize = 40;
const LSF_GAP: Word16 = Word16(205);
const DTX_HANG_CONST: Word16 = Word16(7);
const DTX_ELAPSED_FRAMES_THRESH: Word16 = Word16(24 + 7 - 1);
pub const DTX_MAX_EMPTY_THRESH: Word16 = Word16(50);
const LSF_HIST_MEAN_SCALE: [Word16; M] = [
Word16(20000),
Word16(20000),
Word16(20000),
Word16(20000),
Word16(20000),
Word16(18000),
Word16(16384),
Word16(8192),
Word16(0),
Word16(0),
];
const DTX_LOG_EN_ADJUST: [Word16; 9] = [
Word16(-1023),
Word16(-878),
Word16(-732),
Word16(-586),
Word16(-440),
Word16(-294),
Word16(-148),
Word16(0),
Word16(0),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RxFrameType {
SpeechGood,
SpeechDegraded,
SpeechBad,
SidFirst,
SidUpdate,
SidBad,
NoData,
Onset,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DtxState {
Speech,
Dtx,
DtxMute,
}
pub struct ComfortNoise {
pub synth: [Word16; L_FRAME],
pub a_t: [Word16; 4 * MP1],
pub lsf: [Word16; M],
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub struct DtxDecoder {
since_last_sid: Word16,
true_sid_period_inv: Word16,
log_en: Word16,
old_log_en: Word16,
pn_seed_rx: Word32,
lsp: [Word16; M],
lsp_old: [Word16; M],
lsf_hist: [Word16; M * DTX_HIST_SIZE],
lsf_hist_ptr: usize,
lsf_hist_mean: [Word16; M * DTX_HIST_SIZE],
log_pg_mean: Word16,
log_en_hist: [Word16; DTX_HIST_SIZE],
log_en_hist_ptr: usize,
log_en_adjust: Word16,
dtx_hangover_count: Word16,
dec_ana_elapsed_count: Word16,
sid_frame: bool,
valid_data: bool,
dtx_hangover_added: bool,
global_state: DtxState,
data_updated: bool,
}
impl Default for DtxDecoder {
fn default() -> Self {
Self::new()
}
}
impl DtxDecoder {
#[must_use]
pub fn new() -> Self {
let mean = MEAN_LSF_3.map(Word16);
let mut lsf_hist = [Word16(0); M * DTX_HIST_SIZE];
for frame in 0..DTX_HIST_SIZE {
lsf_hist[frame * M..(frame + 1) * M].copy_from_slice(&mean);
}
Self {
since_last_sid: Word16(0),
true_sid_period_inv: Word16(1 << 13),
log_en: Word16(3500),
old_log_en: Word16(3500),
pn_seed_rx: PN_INITIAL_SEED,
lsp: LSP_INIT.map(Word16),
lsp_old: LSP_INIT.map(Word16),
lsf_hist,
lsf_hist_ptr: 0,
lsf_hist_mean: [Word16(0); M * DTX_HIST_SIZE],
log_pg_mean: Word16(0),
log_en_hist: [Word16(3500); DTX_HIST_SIZE],
log_en_hist_ptr: 0,
log_en_adjust: Word16(0),
dtx_hangover_count: DTX_HANG_CONST,
dec_ana_elapsed_count: Word16(32767),
sid_frame: false,
valid_data: false,
dtx_hangover_added: false,
global_state: DtxState::Dtx,
data_updated: false,
}
}
#[must_use]
pub const fn global_state(&self) -> DtxState {
self.global_state
}
pub const fn commit(&mut self, state: DtxState) {
self.global_state = state;
}
pub fn receive(&mut self, ctx: &mut DspContext, frame_type: RxFrameType) -> DtxState {
use RxFrameType as F;
let in_dtx = matches!(self.global_state, DtxState::Dtx | DtxState::DtxMute);
let quiet_continuation =
in_dtx && matches!(frame_type, F::NoData | F::SpeechBad | F::Onset);
let new_state =
if matches!(frame_type, F::SidFirst | F::SidUpdate | F::SidBad) || quiet_continuation {
let sticky = self.global_state == DtxState::DtxMute
&& matches!(frame_type, F::SidBad | F::SidFirst | F::Onset | F::NoData);
let mut state = if sticky {
DtxState::DtxMute
} else {
DtxState::Dtx
};
self.since_last_sid = add(ctx, self.since_last_sid, Word16(1));
if frame_type != F::SidUpdate
&& sub(ctx, self.since_last_sid, DTX_MAX_EMPTY_THRESH).0 > 0
{
state = DtxState::DtxMute;
}
state
} else {
self.since_last_sid = Word16(0);
DtxState::Speech
};
if !self.data_updated && frame_type == F::SidUpdate {
self.dec_ana_elapsed_count = Word16(0);
}
self.dec_ana_elapsed_count = add(ctx, self.dec_ana_elapsed_count, Word16(1));
self.dtx_hangover_added = false;
let enc_speaking = !matches!(
frame_type,
F::SidFirst | F::SidUpdate | F::SidBad | F::Onset | F::NoData
) || (frame_type == F::NoData && new_state == DtxState::Speech);
if enc_speaking {
self.dtx_hangover_count = DTX_HANG_CONST;
} else if sub(ctx, self.dec_ana_elapsed_count, DTX_ELAPSED_FRAMES_THRESH).0 > 0 {
self.dtx_hangover_added = true;
self.dec_ana_elapsed_count = Word16(0);
self.dtx_hangover_count = Word16(0);
} else if self.dtx_hangover_count.0 == 0 {
self.dec_ana_elapsed_count = Word16(0);
} else {
self.dtx_hangover_count = sub(ctx, self.dtx_hangover_count, Word16(1));
}
if new_state != DtxState::Speech {
self.sid_frame = false;
self.valid_data = false;
match frame_type {
F::SidFirst => self.sid_frame = true,
F::SidUpdate => {
self.sid_frame = true;
self.valid_data = true;
}
F::SidBad => {
self.sid_frame = true;
self.dtx_hangover_added = false;
}
_ => {}
}
}
new_state
}
pub fn observe_speech(&mut self, ctx: &mut DspContext, lsf: &[Word16; M], frame: &[Word16]) {
self.lsf_hist_ptr = (self.lsf_hist_ptr + M) % (M * DTX_HIST_SIZE);
self.lsf_hist[self.lsf_hist_ptr..self.lsf_hist_ptr + M].copy_from_slice(lsf);
let mut frame_energy = Word32(0);
for &sample in frame.iter().take(L_FRAME) {
frame_energy = l_mac(ctx, frame_energy, sample, sample);
}
let (exponent, mantissa) = log2(ctx, frame_energy);
let mut log_en = shl(ctx, exponent, 10);
let fraction = shr(ctx, mantissa, 15 - 10);
log_en = add(ctx, log_en, fraction);
log_en = sub(ctx, log_en, Word16(7497 + 1024));
self.log_en_hist_ptr = (self.log_en_hist_ptr + 1) % DTX_HIST_SIZE;
self.log_en_hist[self.log_en_hist_ptr] = log_en;
}
#[allow(
clippy::too_many_lines,
clippy::similar_names,
clippy::too_many_arguments
)]
pub fn comfort_noise(
&mut self,
ctx: &mut DspContext,
new_state: DtxState,
mode: usize,
parm: &[u16],
mem_syn: &mut [Word16; M],
lsf_state: &mut LsfDecoder,
predictor: &mut CodeGainPredictor,
) -> ComfortNoise {
assert!(mode <= 8, "mode {mode} has no level adjustment");
if self.dtx_hangover_added && self.sid_frame {
self.backward_analysis(ctx, mode);
}
if self.sid_frame {
self.lsp_old = self.lsp;
self.old_log_en = self.log_en;
if self.valid_data {
self.absorb_sid(ctx, parm);
}
predictor.reseed_from_sid(ctx, self.log_en);
}
let held = mult(ctx, self.log_en_adjust, Word16(29491));
let raised = shl(ctx, DTX_LOG_EN_ADJUST[mode], 5);
let scaled = mult(ctx, raised, Word16(3277));
let fresh = shr(ctx, scaled, 5);
self.log_en_adjust = add(ctx, held, fresh);
let (lsp_int, l_log_en_int) = self.interpolate(ctx);
let (lsf_int, lsf_int_variab) = self.apply_variability(ctx, &lsp_int);
lsf_state.set_last_lsf(lsf_int);
let lsp_int = lsf_to_lsp(ctx, &lsf_int);
let lsp_int_variab = lsf_to_lsp(ctx, &lsf_int_variab);
let acoeff = lsp_to_lp(ctx, &lsp_int);
let acoeff_variab = lsp_to_lp(ctx, &lsp_int_variab);
let mut a_t = [Word16(0); 4 * MP1];
for sf in 0..4 {
a_t[sf * MP1..(sf + 1) * MP1].copy_from_slice(&acoeff);
}
let level = self.level(ctx, &acoeff, l_log_en_int);
let mut synth = [Word16(0); L_FRAME];
for sf in 0..4 {
let mut ex = build_cn_code(ctx, &mut self.pn_seed_rx);
for sample in &mut ex {
*sample = mult(ctx, level, *sample);
}
*mem_syn = synthesis_filter(
ctx,
&acoeff_variab,
&ex,
&mut synth[sf * L_SUBFR..(sf + 1) * L_SUBFR],
mem_syn,
);
}
if new_state == DtxState::DtxMute {
self.mute(ctx);
}
if self.sid_frame && (self.valid_data || self.dtx_hangover_added) {
self.since_last_sid = Word16(0);
self.data_updated = true;
}
ComfortNoise {
synth,
a_t,
lsf: lsf_int,
}
}
fn backward_analysis(&mut self, ctx: &mut DspContext, mode: usize) {
self.log_en_adjust = DTX_LOG_EN_ADJUST[mode];
let ptr = (self.lsf_hist_ptr + M) % (M * DTX_HIST_SIZE);
let newest: [Word16; M] = self.lsf_hist[self.lsf_hist_ptr..self.lsf_hist_ptr + M]
.try_into()
.expect("M wide");
self.lsf_hist[ptr..ptr + M].copy_from_slice(&newest);
let ptr = (self.log_en_hist_ptr + 1) % DTX_HIST_SIZE;
self.log_en_hist[ptr] = self.log_en_hist[self.log_en_hist_ptr];
self.log_en = Word16(0);
let mut l_lsf = [Word32(0); M];
for i in 0..DTX_HIST_SIZE {
let eighth = shr(ctx, self.log_en_hist[i], 3);
self.log_en = add(ctx, self.log_en, eighth);
let frame = &self.lsf_hist[i * M..(i + 1) * M];
for (acc, &coefficient) in l_lsf.iter_mut().zip(frame.iter()) {
*acc = l_add(ctx, *acc, l_deposit_l(coefficient));
}
}
let mut lsf = [Word16(0); M];
for (slot, &acc) in lsf.iter_mut().zip(l_lsf.iter()) {
*slot = extract_l(l_shr(ctx, acc, 3));
}
self.lsp = lsf_to_lsp(ctx, &lsf);
self.log_en = sub(ctx, self.log_en, self.log_en_adjust);
self.compute_variability_vectors(ctx);
}
fn compute_variability_vectors(&mut self, ctx: &mut DspContext) {
self.lsf_hist_mean = self.lsf_hist;
for (i, &scale) in LSF_HIST_MEAN_SCALE.iter().enumerate() {
let mut l_mean = Word32(0);
for j in 0..DTX_HIST_SIZE {
l_mean = l_add(ctx, l_mean, l_deposit_l(self.lsf_hist_mean[i + j * M]));
}
let mean = extract_l(l_shr(ctx, l_mean, 3));
for j in 0..DTX_HIST_SIZE {
let at = i + j * M;
let mut v = sub(ctx, self.lsf_hist_mean[at], mean);
v = mult(ctx, v, scale);
let negative = v.0 < 0;
v = abs_s(ctx, v);
if sub(ctx, v, Word16(655)).0 > 0 {
let excess = sub(ctx, v, Word16(655));
let compressed = shr(ctx, excess, 2);
v = add(ctx, Word16(655), compressed);
}
if sub(ctx, v, Word16(1310)).0 > 0 {
v = Word16(1310);
}
self.lsf_hist_mean[at] = if negative { Word16(-v.0) } else { v };
}
}
}
fn absorb_sid(&mut self, ctx: &mut DspContext, parm: &[u16]) {
assert!(parm.len() >= 5, "a SID frame carries five parameters");
let mut span = self.since_last_sid;
self.since_last_sid = Word16(0);
if sub(ctx, span, Word16(32)).0 > 0 {
span = Word16(32);
}
self.true_sid_period_inv = if sub(ctx, span, Word16(2)).0 >= 0 {
let scaled = shl(ctx, span, 10);
div_s(Word16(1 << 10), scaled)
} else {
Word16(1 << 14)
};
let lsf_state_seed = parm[0];
let mut lsf_state = LsfDecoder::at_reset();
lsf_state.seed_predictor(lsf_state_seed);
self.lsp = lsf_state.decode_sid(&parm[1..4]);
let log_en_index = Word16(i16::try_from(parm[4]).expect("six bits"));
self.log_en = shl(ctx, log_en_index, 11 - 2);
self.log_en = sub(ctx, self.log_en, Word16(2560 * 2));
if log_en_index.0 == 0 {
self.log_en = Word16(i16::MIN);
}
if !self.data_updated || self.global_state == DtxState::Speech {
self.lsp_old = self.lsp;
self.old_log_en = self.log_en;
}
}
fn interpolate(&self, ctx: &mut DspContext) -> ([Word16; M], Word32) {
let elapsed = add(ctx, Word16(1), self.since_last_sid);
let mut int_fac = shl(ctx, elapsed, 10);
int_fac = mult(ctx, int_fac, self.true_sid_period_inv);
if sub(ctx, int_fac, Word16(1024)).0 > 0 {
int_fac = Word16(1024);
}
int_fac = shl(ctx, int_fac, 4);
let mut l_log_en_int = l_mult(ctx, int_fac, self.log_en);
let mut lsp_int = [Word16(0); M];
for (slot, &lsp) in lsp_int.iter_mut().zip(self.lsp.iter()) {
*slot = mult(ctx, int_fac, lsp);
}
let int_fac = sub(ctx, Word16(16384), int_fac);
l_log_en_int = l_mac(ctx, l_log_en_int, int_fac, self.old_log_en);
for (slot, &old_lsp) in lsp_int.iter_mut().zip(self.lsp_old.iter()) {
let old = mult(ctx, int_fac, old_lsp);
*slot = add(ctx, *slot, old);
*slot = shl(ctx, *slot, 1);
}
(lsp_int, l_log_en_int)
}
#[allow(clippy::similar_names)]
fn apply_variability(
&mut self,
ctx: &mut DspContext,
lsp_int: &[Word16; M],
) -> ([Word16; M], [Word16; M]) {
let mut factor = sub(ctx, self.log_pg_mean, Word16(2457));
let scaled = mult(ctx, factor, Word16(9830));
factor = sub(ctx, Word16(4096), scaled);
if sub(ctx, factor, Word16(4096)).0 > 0 {
factor = Word16(4096);
}
if factor.0 < 0 {
factor = Word16(0);
}
factor = shl(ctx, factor, 3);
let index = pseudonoise(&mut self.pn_seed_rx, 3);
let index = usize::try_from(index.0).expect("three bits");
let mut lsf_int = lsp_to_lsf(ctx, lsp_int);
let mut lsf_int_variab = lsf_int;
let deviation = &self.lsf_hist_mean[index * M..(index + 1) * M];
for (slot, &d) in lsf_int_variab.iter_mut().zip(deviation.iter()) {
let offset = mult(ctx, factor, d);
*slot = add(ctx, *slot, offset);
}
reorder_lsf(ctx, &mut lsf_int, LSF_GAP);
reorder_lsf(ctx, &mut lsf_int_variab, LSF_GAP);
(lsf_int, lsf_int_variab)
}
fn level(
&mut self,
ctx: &mut DspContext,
acoeff: &[Word16; MP1],
mut l_log_en: Word32,
) -> Word16 {
let refl = a_refl(ctx, &acoeff[1..]);
let mut pred_err = Word16(i16::MAX);
for &k in &refl {
let k2 = mult(ctx, k, k);
let residual = sub(ctx, Word16(i16::MAX), k2);
pred_err = mult(ctx, pred_err, residual);
}
let (log_pg_e, log_pg_m) = log2(ctx, l_deposit_l(pred_err));
let exponent = sub(ctx, log_pg_e, Word16(15));
let mut log_pg = shl(ctx, exponent, 12);
let mantissa = shr(ctx, log_pg_m, 15 - 12);
let whole = add(ctx, log_pg, mantissa);
let negated = sub(ctx, Word16(0), whole);
log_pg = shr(ctx, negated, 1);
let held = mult(ctx, Word16(29491), self.log_pg_mean);
let fresh = mult(ctx, Word16(3277), log_pg);
self.log_pg_mean = add(ctx, held, fresh);
l_log_en = l_shr(ctx, l_log_en, 10);
l_log_en = l_add(ctx, l_log_en, Word32(4 * 65536));
let gain_term = l_shl(ctx, l_deposit_l(log_pg), 4);
l_log_en = l_sub(ctx, l_log_en, gain_term);
let adjust_term = l_shl(ctx, l_deposit_l(self.log_en_adjust), 5);
l_log_en = l_add(ctx, l_log_en, adjust_term);
let exponent = extract_h(l_log_en);
let remainder = l_sub(ctx, l_log_en, l_deposit_h(exponent));
let mantissa = extract_l(l_shr(ctx, remainder, 1));
extract_l(pow2(ctx, exponent, mantissa))
}
fn mute(&mut self, ctx: &mut DspContext) {
let mut span = self.since_last_sid;
if sub(ctx, span, Word16(32)).0 > 0 {
span = Word16(32);
}
if span.0 <= 0 {
span = Word16(8);
}
let scaled = shl(ctx, span, 10);
self.true_sid_period_inv = div_s(Word16(1 << 10), scaled);
self.since_last_sid = Word16(0);
self.lsp_old = self.lsp;
self.old_log_en = self.log_en;
self.log_en = sub(ctx, self.log_en, Word16(256));
}
}
#[cfg(test)]
mod tests {
use super::*;
fn drive(types: &[RxFrameType]) -> Vec<DtxState> {
let mut ctx = DspContext::default();
let mut dtx = DtxDecoder::new();
let mut mem_syn = [Word16(0); M];
let mut lsf_state = LsfDecoder::at_reset();
let mut predictor = CodeGainPredictor::new();
let parm = [0u16, 100, 200, 300, 30];
types
.iter()
.map(|&t| {
let state = dtx.receive(&mut ctx, t);
if state != DtxState::Speech {
dtx.comfort_noise(
&mut ctx,
state,
8,
&parm,
&mut mem_syn,
&mut lsf_state,
&mut predictor,
);
}
dtx.commit(state);
state
})
.collect()
}
#[test]
fn the_state_table_matches_the_specification_entry_for_entry() {
use DtxState::{Dtx, DtxMute, Speech};
use RxFrameType as F;
let table = [
(F::SpeechGood, Speech, Speech, Speech),
(F::SpeechDegraded, Speech, Speech, Speech),
(F::SpeechBad, Speech, Dtx, Dtx),
(F::SidFirst, Dtx, Dtx, DtxMute),
(F::SidUpdate, Dtx, Dtx, Dtx),
(F::SidBad, Dtx, Dtx, DtxMute),
(F::NoData, Speech, Dtx, DtxMute),
(F::Onset, Speech, Dtx, DtxMute),
];
let mut checked = 0;
for (incoming, from_speech, from_dtx, from_mute) in table {
for (start, want) in [(Speech, from_speech), (Dtx, from_dtx), (DtxMute, from_mute)] {
let mut ctx = DspContext::default();
let mut dtx = DtxDecoder::new();
dtx.commit(start);
let got = dtx.receive(&mut ctx, incoming);
assert_eq!(got, want, "{incoming:?} from {start:?}");
checked += 1;
}
}
assert_eq!(checked, 24, "the table has 24 entries");
let mut stream = vec![F::SidFirst];
stream.extend(std::iter::repeat_n(F::NoData, 55));
stream.push(F::SpeechBad);
let states = drive(&stream);
assert_eq!(
states[55], DtxMute,
"the stream must be muting first, or this proves nothing"
);
assert_eq!(*states.last().expect("non-empty"), Dtx);
}
#[test]
fn a_long_silence_mutes_and_a_sid_update_does_not() {
let mut quiet = vec![RxFrameType::SidFirst];
quiet.extend(std::iter::repeat_n(RxFrameType::NoData, 60));
let states = drive(&quiet);
let first_mute = states.iter().position(|&s| s == DtxState::DtxMute);
assert_eq!(first_mute, Some(51), "muting did not begin where expected");
assert!(states[..51].iter().all(|&s| s == DtxState::Dtx));
let mut rescued = quiet.clone();
rescued[45] = RxFrameType::SidUpdate;
let states = drive(&rescued);
assert!(
states.iter().all(|&s| s != DtxState::DtxMute),
"an update partway through should have prevented muting"
);
}
#[test]
fn an_update_arriving_after_the_threshold_still_returns_plain_dtx() {
let mut stream = vec![RxFrameType::SidFirst];
stream.extend(std::iter::repeat_n(RxFrameType::NoData, 55));
stream.push(RxFrameType::SidUpdate);
let states = drive(&stream);
assert_eq!(*states.last().expect("non-empty"), DtxState::Dtx);
assert_eq!(
states[55],
DtxState::DtxMute,
"it should have been muting before"
);
}
#[test]
fn speech_clears_the_staleness_counter() {
let mut stream = vec![RxFrameType::SidFirst];
stream.extend(std::iter::repeat_n(RxFrameType::NoData, 40));
stream.push(RxFrameType::SpeechGood);
stream.push(RxFrameType::SidFirst);
stream.extend(std::iter::repeat_n(RxFrameType::NoData, 45));
let states = drive(&stream);
assert!(
states.iter().all(|&s| s != DtxState::DtxMute),
"the speech frame should have restarted the clock"
);
}
#[test]
fn the_first_silence_of_a_stream_triggers_backward_analysis_at_once() {
let mut ctx = DspContext::default();
let mut dtx = DtxDecoder::new();
dtx.commit(DtxState::Speech);
let state = dtx.receive(&mut ctx, RxFrameType::SidFirst);
assert_eq!(state, DtxState::Dtx);
assert!(
dtx.dtx_hangover_added,
"hangover was not detected on the first SID"
);
assert!(dtx.sid_frame);
assert!(!dtx.valid_data, "a SID_FIRST carries no parameters");
let mut dtx = DtxDecoder::new();
dtx.dec_ana_elapsed_count = Word16(0);
dtx.commit(DtxState::Speech);
dtx.receive(&mut ctx, RxFrameType::SidFirst);
assert!(
!dtx.dtx_hangover_added,
"the test is vacuous if this also fires"
);
}
#[test]
fn a_damaged_sid_does_not_run_backward_analysis() {
let mut ctx = DspContext::default();
let mut dtx = DtxDecoder::new();
dtx.commit(DtxState::Speech);
dtx.receive(&mut ctx, RxFrameType::SidBad);
assert!(dtx.sid_frame, "it is still a SID frame");
assert!(!dtx.valid_data);
assert!(
!dtx.dtx_hangover_added,
"damaged bits must not drive the analysis"
);
}
}