use super::codebook::L_SUBFR;
use super::gain_tables::FIR_UP;
use super::ltp::{HISTORY, L_INTERPOL, PIT_MAX};
use crate::fixed_point::arith::{abs_s, add, round, sub};
use crate::fixed_point::arith32::{l_deposit_h, l_mac, l_mult};
use crate::fixed_point::shift::{l_shl, norm_s};
use crate::fixed_point::types::{DspContext, Word16, Word32};
const Q_MAX: i16 = 8;
pub const L_SUBFR16K: usize = 80;
const NB_COEF_UP: usize = 12;
const FAC5: usize = 5;
const FAC4: usize = 4;
#[derive(Debug, Clone)]
pub struct Excitation {
buffer: Vec<Word16>,
q_subfr: [Word16; 4],
q_old: i16,
}
impl Default for Excitation {
fn default() -> Self {
Self::new()
}
}
impl Excitation {
#[must_use]
pub fn new() -> Self {
Self {
buffer: vec![Word16(0); HISTORY + L_SUBFR + 1],
q_subfr: [Word16(Q_MAX); 4],
q_old: Q_MAX,
}
}
#[must_use]
pub fn history(&self) -> &[Word16] {
&self.buffer[..HISTORY]
}
#[must_use]
pub fn buffer_mut(&mut self) -> (&mut [Word16], usize) {
(&mut self.buffer, HISTORY)
}
#[must_use]
pub const fn q_new(&self) -> i16 {
self.q_old
}
fn choose_scaling(&self, ctx: &mut DspContext, gain_code: Word32) -> (i16, Word16) {
let mut limit = self.q_subfr.iter().map(|q| q.0).min().unwrap_or(0);
limit = limit.min(Q_MAX);
let mut q_new = 0i16;
let mut scaled = gain_code;
while scaled.0 < 0x0800_0000 && q_new < limit {
scaled = l_shl(ctx, scaled, 1);
q_new += 1;
}
(q_new, round(ctx, scaled))
}
fn rescale(&mut self, ctx: &mut DspContext, shift: i16) {
if shift == 0 {
return;
}
for sample in &mut self.buffer {
let widened = l_shl(ctx, l_deposit_h(*sample), shift);
*sample = round(ctx, widened);
}
}
pub fn rescale_to(&mut self, gain_code: Word32) -> (i16, Word16) {
let mut ctx = DspContext::default();
let (q_new, gain_word) = self.choose_scaling(&mut ctx, gain_code);
let shift = q_new - self.q_old;
self.rescale(&mut ctx, shift);
self.q_old = q_new;
(q_new, gain_word)
}
pub fn build(
&mut self,
code: &[Word16; L_SUBFR],
gain_pitch: Word16,
gain_code: Word16,
q_new: i16,
) -> [Word16; L_SUBFR] {
let mut ctx = DspContext::default();
let offset = HISTORY;
let mut out = [Word16(0); L_SUBFR];
for i in 0..L_SUBFR {
let mut acc = l_mult(&mut ctx, code[i], gain_code);
acc = l_shl(&mut ctx, acc, 5);
acc = l_mac(&mut ctx, acc, self.buffer[offset + i], gain_pitch);
let acc = l_shl(&mut ctx, acc, 1);
let sample = round(&mut ctx, acc);
self.buffer[offset + i] = sample;
out[i] = sample;
}
let mut max = Word16(1);
for sample in &out {
let magnitude = abs_s(&mut ctx, *sample);
if magnitude.0 > max.0 {
max = magnitude;
}
}
let raised = add(&mut ctx, Word16(norm_s(max)), Word16(q_new));
let headroom = sub(&mut ctx, raised, Word16(1));
self.q_subfr.rotate_right(1);
self.q_subfr[0] = headroom;
out
}
pub fn assemble(
&mut self,
code: &[Word16; L_SUBFR],
gain_pitch: Word16,
gain_code: Word32,
) -> ([Word16; L_SUBFR], i16) {
let mut ctx = DspContext::default();
let (q_new, gain_code) = self.choose_scaling(&mut ctx, gain_code);
let shift = q_new - self.q_old;
self.rescale(&mut ctx, shift);
self.q_old = q_new;
let offset = HISTORY;
let mut out = [Word16(0); L_SUBFR];
for i in 0..L_SUBFR {
let mut acc = l_mult(&mut ctx, code[i], gain_code);
acc = l_shl(&mut ctx, acc, 5);
acc = l_mac(&mut ctx, acc, self.buffer[offset + i], gain_pitch);
let acc = l_shl(&mut ctx, acc, 1);
let sample = round(&mut ctx, acc);
self.buffer[offset + i] = sample;
out[i] = sample;
}
let mut max = Word16(1);
for sample in &out {
let magnitude = abs_s(&mut ctx, *sample);
if magnitude.0 > max.0 {
max = magnitude;
}
}
let raised = add(&mut ctx, Word16(norm_s(max)), Word16(q_new));
let headroom = sub(&mut ctx, raised, Word16(1));
self.q_subfr.rotate_right(1);
self.q_subfr[0] = headroom;
(out, q_new)
}
pub fn advance(&mut self) {
self.buffer.copy_within(L_SUBFR..HISTORY + L_SUBFR, 0);
self.buffer[HISTORY..].fill(Word16(0));
}
#[must_use]
pub const fn scaling_history(&self) -> &[Word16; 4] {
&self.q_subfr
}
}
#[derive(Debug, Clone)]
pub struct Upsampler {
memory: [Word16; 2 * NB_COEF_UP],
}
impl Default for Upsampler {
fn default() -> Self {
Self::new()
}
}
impl Upsampler {
#[must_use]
pub const fn new() -> Self {
Self {
memory: [Word16(0); 2 * NB_COEF_UP],
}
}
#[must_use]
pub fn process(&mut self, input: &[Word16; L_SUBFR]) -> [Word16; L_SUBFR16K] {
let mut ctx = DspContext::default();
let mut signal = [Word16(0); L_SUBFR + 2 * NB_COEF_UP];
signal[..2 * NB_COEF_UP].copy_from_slice(&self.memory);
signal[2 * NB_COEF_UP..].copy_from_slice(input);
let mut out = [Word16(0); L_SUBFR16K];
let mut pos = 0usize;
for slot in &mut out {
let i = pos / FAC5;
let frac = pos % FAC5;
*slot = interpolate(&mut ctx, &signal[i + 1..], frac);
pos += FAC4;
}
self.memory.copy_from_slice(&signal[L_SUBFR..]);
out
}
}
fn interpolate(ctx: &mut DspContext, x: &[Word16], frac: usize) -> Word16 {
let mut sum = Word32(0);
let mut k = FAC5 - 1 - frac;
for &sample in x.iter().take(2 * NB_COEF_UP) {
sum = l_mac(ctx, sum, sample, Word16(FIR_UP[k]));
k += FAC5;
}
let scaled = l_shl(ctx, sum, 1);
round(ctx, scaled)
}
pub const EXCITATION_HISTORY: usize = PIT_MAX + L_INTERPOL;
#[cfg(test)]
mod tests {
use super::super::lp::isp_to_lp::tests_support::{block_row, block_row_i32, has_block};
use super::*;
fn adaptive(sf: usize) -> [Word16; L_SUBFR] {
let mut v = [Word16(0); L_SUBFR];
for (n, slot) in v.iter_mut().enumerate() {
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
let t = (sf * L_SUBFR + n) as f64;
#[allow(clippy::cast_possible_truncation)]
{
*slot = Word16((2000.0 * (2.0 * std::f64::consts::PI * t / 37.0).sin()) as i16);
}
}
v
}
fn innovation(sf: usize) -> [Word16; L_SUBFR] {
let mut code = [Word16(0); L_SUBFR];
for (n, slot) in code.iter_mut().enumerate() {
if n % 11 == sf % 11 {
*slot = Word16(if n % 2 == 1 { -512 } else { 512 });
}
}
code
}
fn expect(label: &str, got: &[Word16], sf: usize) {
let want = block_row("excasm", &format!("{label}{sf}"));
assert_eq!(want.len(), got.len(), "{label}{sf}: length");
for (i, (&g, &w)) in got.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.0, w,
"{label}{sf}: sample {i} = {} but the reference gives {w}",
g.0
);
}
}
#[test]
fn excitation_assembly_and_upsampling_are_bit_exact_against_ts26173() {
assert!(has_block("excasm"), "fixture block excasm missing");
let mut exc = Excitation::new();
let mut up = Upsampler::new();
for sf in 0..4 {
let meta = block_row_i32("excasm", &format!("meta{sf}"));
let gain_pitch = Word16(i16::try_from(meta[0]).expect("pitch gain is a Word16"));
let gain_code = Word32(meta[2]);
let (buffer, offset) = exc.buffer_mut();
buffer[offset..offset + L_SUBFR].copy_from_slice(&adaptive(sf));
let code = innovation(sf);
expect("code", &code, sf);
let (assembled, _) = exc.assemble(&code, gain_pitch, gain_code);
expect("exc", &assembled, sf);
expect("q", exc.scaling_history(), sf);
let upsampled = up.process(&assembled);
expect("up", &upsampled, sf);
exc.advance();
}
}
#[test]
fn rescaling_moves_the_history_with_the_present() {
let mut exc = Excitation::new();
for _ in 0..4 {
let (buffer, offset) = exc.buffer_mut();
buffer[offset..offset + L_SUBFR].fill(Word16(100));
exc.assemble(&[Word16(0); L_SUBFR], Word16(4096), Word32(1 << 20));
exc.advance();
}
assert!(
exc.scaling_history().iter().all(|q| q.0 > 0),
"four quiet subframes left no headroom to shift into"
);
{
let (buffer, _) = exc.buffer_mut();
buffer.fill(Word16(1000));
}
let before = exc.history()[0];
exc.assemble(&[Word16(0); L_SUBFR], Word16(0), Word32(1));
let after = exc.history()[0];
assert_ne!(
before.0, after.0,
"the history was left at the old scale when the shift moved"
);
}
#[test]
fn the_shift_never_exceeds_what_the_synthesis_filter_absorbs() {
let mut exc = Excitation::new();
for sf in 0..20 {
let (buffer, offset) = exc.buffer_mut();
buffer[offset..offset + L_SUBFR].copy_from_slice(&adaptive(sf));
let (_, q) = exc.assemble(&innovation(sf), Word16(8000), Word32(1));
assert!(q <= Q_MAX, "subframe {sf} chose a shift of {q}");
exc.advance();
}
}
#[test]
fn upsampling_produces_five_samples_for_every_four() {
let mut up = Upsampler::new();
let input = [Word16(0); L_SUBFR];
assert_eq!(up.process(&input).len() * FAC4, L_SUBFR * FAC5);
}
#[test]
fn upsampling_preserves_a_tone_in_the_passband() {
let mut up = Upsampler::new();
let mut peak = 0i32;
for block in 0..6 {
let mut input = [Word16(0); L_SUBFR];
for (n, slot) in input.iter_mut().enumerate() {
#[allow(clippy::cast_precision_loss)]
let t = (block * L_SUBFR + n) as f64;
#[allow(clippy::cast_possible_truncation)]
{
*slot = Word16(
(8000.0 * (2.0 * std::f64::consts::PI * 1000.0 * t / 12800.0).sin()) as i16,
);
}
}
let out = up.process(&input);
if block >= 2 {
peak = peak.max(out.iter().map(|s| i32::from(s.0).abs()).max().unwrap_or(0));
}
}
assert!(
(7000..=9000).contains(&peak),
"a 1 kHz tone came out at {peak} against an input of 8000"
);
}
#[test]
fn the_upsampler_carries_state_across_subframes() {
let input = [Word16(4000); L_SUBFR];
let mut continuous = Upsampler::new();
let _ = continuous.process(&input);
let carried = continuous.process(&input);
let mut fresh = Upsampler::new();
let isolated = fresh.process(&input);
assert_ne!(
carried, isolated,
"the resampler produced the same block with and without history"
);
}
}