#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::unreadable_literal
)]
use super::super::synthesis::{HighPass50, PREEMPH_FAC};
use crate::fixed_point::arith::{add, extract_h, mult, round, sub};
use crate::fixed_point::arith32::{l_abs, l_mac, l_msu, l_mult};
use crate::fixed_point::shift::{l_shl, norm_s, shr};
use crate::fixed_point::types::{DspContext, Word16, Word32};
pub const L_FRAME16K: usize = 320;
pub const L_FRAME: usize = 256;
pub const L_FILT16K: usize = 15;
pub const L_FILT: usize = 12;
pub const L_TOTAL: usize = 384;
pub const NEW_SPEECH: usize = L_TOTAL - L_FRAME - L_FILT;
const Q_MAX: i16 = 8;
const NB_COEF_DOWN: usize = 15;
const DOWN_FAC: Word16 = Word16(26215);
const FAC4: i16 = 4;
const FAC5: i16 = 5;
const FIR_DOWN: [Word16; 4 * NB_COEF_DOWN * 2] = {
const fn w(v: i16) -> Word16 {
Word16(v)
}
[
w(-1),
w(-3),
w(-6),
w(-5),
w(0),
w(9),
w(19),
w(24),
w(18),
w(0),
w(-26),
w(-50),
w(-58),
w(-41),
w(0),
w(54),
w(99),
w(111),
w(77),
w(0),
w(-95),
w(-170),
w(-188),
w(-128),
w(0),
w(153),
w(270),
w(294),
w(198),
w(0),
w(-233),
w(-408),
w(-441),
w(-295),
w(0),
w(344),
w(601),
w(649),
w(434),
w(0),
w(-507),
w(-888),
w(-964),
w(-647),
w(0),
w(770),
w(1366),
w(1505),
w(1030),
w(0),
w(-1293),
w(-2379),
w(-2746),
w(-1997),
w(0),
w(3034),
w(6575),
w(9894),
w(12254),
w(13107),
w(12254),
w(9894),
w(6575),
w(3034),
w(0),
w(-1997),
w(-2746),
w(-2379),
w(-1293),
w(0),
w(1030),
w(1505),
w(1366),
w(770),
w(0),
w(-647),
w(-964),
w(-888),
w(-507),
w(0),
w(434),
w(649),
w(601),
w(344),
w(0),
w(-295),
w(-441),
w(-408),
w(-233),
w(0),
w(198),
w(294),
w(270),
w(153),
w(0),
w(-128),
w(-188),
w(-170),
w(-95),
w(0),
w(77),
w(111),
w(99),
w(54),
w(0),
w(-41),
w(-58),
w(-50),
w(-26),
w(0),
w(18),
w(24),
w(19),
w(9),
w(0),
w(-5),
w(-6),
w(-3),
w(-1),
w(0),
]
};
pub fn restrict_to_14_bit(frame: &mut [Word16]) {
for sample in frame.iter_mut() {
sample.0 &= -4; }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Scaling {
pub q_new: i16,
pub exp: i16,
}
#[derive(Clone, Debug)]
pub struct Decimator {
memory: [Word16; 2 * NB_COEF_DOWN],
}
impl Default for Decimator {
fn default() -> Self {
Self::new()
}
}
impl Decimator {
#[must_use]
pub const fn new() -> Self {
Self {
memory: [Word16(0); 2 * NB_COEF_DOWN],
}
}
pub fn decimate(&mut self, sig16k: &[Word16], out: &mut [Word16]) {
let lg = sig16k.len();
assert!(lg <= L_FRAME16K, "decimator input is at most one frame");
let mut signal = [Word16(0); 2 * NB_COEF_DOWN + L_FRAME16K];
signal[..2 * NB_COEF_DOWN].copy_from_slice(&self.memory);
signal[2 * NB_COEF_DOWN..2 * NB_COEF_DOWN + lg].copy_from_slice(sig16k);
let mut ctx = DspContext::default();
let lg_down = mult(&mut ctx, Word16(lg as i16), DOWN_FAC).0 as usize;
assert_eq!(
out.len(),
lg_down,
"output length must be floor(4/5 · input)"
);
down_sample(&signal, out);
self.memory
.copy_from_slice(&signal[lg..lg + 2 * NB_COEF_DOWN]);
}
}
fn down_sample(signal: &[Word16], out: &mut [Word16]) {
let mut ctx = DspContext::default();
let mut pos = Word16(0);
for slot in out.iter_mut() {
let i = shr(&mut ctx, pos, 2).0 as usize;
let frac = pos.0 & 3;
*slot = interpolate(&signal[i + 1..], frac);
pos = add(&mut ctx, pos, Word16(FAC5));
}
}
fn interpolate(x: &[Word16], frac: i16) -> Word16 {
let mut ctx = DspContext::default();
let mut sum = Word32(0);
let mut k = (FAC4 - 1 - frac) as usize;
for &sample in x.iter().take(2 * NB_COEF_DOWN) {
sum = l_mac(&mut ctx, sum, sample, FIR_DOWN[k]);
k += FAC4 as usize;
}
let sum = l_shl(&mut ctx, sum, 1);
round(&mut ctx, sum)
}
#[derive(Clone, Debug)]
pub struct Preprocessor {
decimator: Decimator,
highpass: HighPass50,
preemph_memory: Word16,
q_old: i16,
q_max: [i16; 2],
}
impl Default for Preprocessor {
fn default() -> Self {
Self::new()
}
}
impl Preprocessor {
#[must_use]
pub const fn new() -> Self {
Self {
decimator: Decimator::new(),
highpass: HighPass50::new(),
preemph_memory: Word16(0),
q_old: 15,
q_max: [15, 15],
}
}
pub fn band_limit(&mut self, speech16k: &[Word16; L_FRAME16K], new_speech: &mut [Word16]) {
assert_eq!(
new_speech.len(),
L_FRAME + L_FILT,
"new_speech spans the frame plus its tail approximation"
);
let (frame, tail) = new_speech.split_at_mut(L_FRAME);
self.decimator.decimate(speech16k, frame);
let mut tail_decimator = self.decimator.clone();
tail_decimator.decimate(&[Word16(0); L_FILT16K], tail);
self.highpass.filter(frame);
let mut tail_highpass = self.highpass.clone();
tail_highpass.filter(tail);
}
pub fn preemphasise(&mut self, new_speech: &mut [Word16]) -> Scaling {
assert_eq!(
new_speech.len(),
L_FRAME + L_FILT,
"new_speech spans the frame plus its tail approximation"
);
let mut ctx = DspContext::default();
let mu = shr(&mut ctx, PREEMPH_FAC, 1);
let shift = self.headroom(&mut ctx, new_speech, mu);
let q_new = shift.min(self.q_max[0]).min(self.q_max[1]);
let exp = sub(&mut ctx, Word16(q_new), Word16(self.q_old)).0;
self.q_old = q_new;
self.q_max[1] = self.q_max[0];
self.q_max[0] = shift;
let memory = new_speech[L_FRAME - 1];
for i in (1..L_FRAME + L_FILT).rev() {
let mut acc = l_mult(&mut ctx, new_speech[i], Word16(16384));
acc = l_msu(&mut ctx, acc, new_speech[i - 1], mu);
acc = l_shl(&mut ctx, acc, q_new);
new_speech[i] = round(&mut ctx, acc);
}
let mut acc = l_mult(&mut ctx, new_speech[0], Word16(16384));
acc = l_msu(&mut ctx, acc, self.preemph_memory, mu);
acc = l_shl(&mut ctx, acc, q_new);
new_speech[0] = round(&mut ctx, acc);
self.preemph_memory = memory;
Scaling { q_new, exp }
}
fn headroom(&self, ctx: &mut DspContext, new_speech: &[Word16], mu: Word16) -> i16 {
let mut acc = l_mult(ctx, new_speech[0], Word16(16384));
acc = l_msu(ctx, acc, self.preemph_memory, mu);
let mut peak = l_abs(ctx, acc);
for i in 1..L_FRAME + L_FILT {
let mut acc = l_mult(ctx, new_speech[i], Word16(16384));
acc = l_msu(ctx, acc, new_speech[i - 1], mu);
let magnitude = l_abs(ctx, acc);
if magnitude.0 > peak.0 {
peak = magnitude;
}
}
let top = extract_h(peak);
if top.0 == 0 {
Q_MAX
} else {
sub(ctx, Word16(norm_s(top)), Word16(1)).0.clamp(0, Q_MAX)
}
}
}
#[cfg(test)]
pub(crate) mod trace_support {
use crate::fixed_point::types::Word16;
use super::L_FRAME16K;
pub const TRACE: &str = include_str!("../../testdata/wb_enc_trace.txt");
const INPUT: &[u8] = include_bytes!("../../testdata/amrwb_enc_input.pcm");
pub fn frames() -> usize {
let mut highest = None;
for line in TRACE.lines() {
let mut parts = line.split_whitespace();
if parts.next() != Some("T") {
continue;
}
let frame: usize = parts.next().expect("frame index").parse().expect("integer");
highest = Some(highest.map_or(frame, |h: usize| h.max(frame)));
}
highest.expect("trace carries at least one frame") + 1
}
pub fn row(frame: usize, subframe: i32, name: &str) -> Vec<i32> {
for line in TRACE.lines() {
let mut parts = line.split_whitespace();
if parts.next() != Some("T") {
continue;
}
let f: usize = parts.next().expect("frame index").parse().expect("integer");
let s: i32 = parts.next().expect("subframe").parse().expect("integer");
if f != frame || s != subframe || parts.next() != Some(name) {
continue;
}
return parts.map(|v| v.parse().expect("integer")).collect();
}
panic!("trace has no row {name:?} for frame {frame} subframe {subframe}");
}
pub fn words(frame: usize, name: &str, expected: usize) -> Vec<Word16> {
let values = row(frame, -1, name);
assert_eq!(values.len(), expected, "frame {frame}: {name} length");
values
.into_iter()
.map(|v| Word16(i16::try_from(v).expect("row holds Word16 values")))
.collect()
}
pub fn scalar(frame: usize, name: &str) -> i32 {
let values = row(frame, -1, name);
assert_eq!(values.len(), 1, "frame {frame}: {name} is a scalar");
values[0]
}
pub fn input_frame(frame: usize) -> [Word16; L_FRAME16K] {
let mut samples = [Word16(0); L_FRAME16K];
let base = frame * L_FRAME16K * 2;
for (n, slot) in samples.iter_mut().enumerate() {
let lo = u16::from(INPUT[base + n * 2]);
let hi = u16::from(INPUT[base + n * 2 + 1]);
*slot = Word16((lo | (hi << 8)) as i16);
}
samples
}
}
#[cfg(test)]
mod tests {
use super::trace_support::{frames, input_frame, scalar, words};
use super::*;
fn replay(mut check: impl FnMut(usize, &[Word16], &[Word16], Scaling)) -> usize {
let mut pre = Preprocessor::new();
let total = frames();
for frame in 0..total {
let mut input = input_frame(frame);
restrict_to_14_bit(&mut input);
let mut new_speech = [Word16(0); L_FRAME + L_FILT];
pre.band_limit(&input, &mut new_speech);
let band_limited = new_speech;
let scaling = pre.preemphasise(&mut new_speech);
check(frame, &band_limited, &new_speech, scaling);
}
total
}
#[test]
fn input_is_restricted_to_fourteen_bits_before_the_coder_sees_it() {
let mut compared = 0usize;
for frame in 0..frames() {
let mut got = input_frame(frame);
restrict_to_14_bit(&mut got);
let want = words(frame, "speech16k", L_FRAME16K);
for (i, &sample) in got.iter().enumerate() {
assert_eq!(
sample.0, want[i].0,
"frame {frame}: input sample {i} differs from what coder() received"
);
compared += 1;
}
}
assert_eq!(compared, 3 * L_FRAME16K, "960 input samples compared");
}
#[test]
fn decimation_and_highpass_are_bit_exact_against_ts26173() {
let mut compared = 0usize;
let count = replay(|frame, decimated, _, _| {
let want = words(frame, "decimated", L_FRAME);
for (i, &got) in decimated.iter().take(L_FRAME).enumerate() {
assert_eq!(
got.0, want[i].0,
"frame {frame}: decimated[{i}] differs from the reference"
);
compared += 1;
}
});
assert_eq!(count, 3, "the committed trace covers three frames");
assert_eq!(compared, 3 * L_FRAME, "768 samples compared");
}
#[test]
fn preemphasised_frame_is_bit_exact_against_ts26173() {
let mut compared = 0usize;
let count = replay(|frame, _, preemphasised, _| {
let window = words(frame, "window", L_TOTAL);
for (i, &got) in preemphasised.iter().enumerate() {
assert_eq!(
got.0,
window[NEW_SPEECH + i].0,
"frame {frame}: preemphasised sample {i} differs from the reference"
);
compared += 1;
}
});
assert_eq!(count, 3, "the committed trace covers three frames");
assert_eq!(compared, 3 * (L_FRAME + L_FILT), "804 samples compared");
}
#[test]
fn adaptive_scaling_is_bit_exact_against_ts26173() {
let mut seen = Vec::new();
let count = replay(|frame, _, _, scaling| {
assert_eq!(
i32::from(scaling.q_new),
scalar(frame, "Q_new"),
"frame {frame}: Q_new differs from the reference"
);
seen.push(scaling.q_new);
});
assert_eq!(count, 3, "the committed trace covers three frames");
assert_eq!(seen, vec![3, 2, 2], "Q_new must vary across the trace");
}
#[test]
fn exp_tracks_the_change_in_scaling_across_frames() {
let mut previous = 15i16;
let count = replay(|frame, _, _, scaling| {
let want = i16::try_from(scalar(frame, "Q_new")).expect("Q_new is small") - previous;
assert_eq!(scaling.exp, want, "frame {frame}: exp");
previous = scaling.q_new;
});
assert_eq!(count, 3, "the committed trace covers three frames");
}
#[test]
fn fir_phases_have_unity_dc_gain() {
let want = [16383, 16384, 16383, 16383];
for (column, &expected) in want.iter().enumerate() {
let sum: i32 = FIR_DOWN
.iter()
.skip(column)
.step_by(4)
.map(|c| i32::from(c.0))
.sum();
assert_eq!(sum, expected, "phase column {column} sums wrong");
}
assert_eq!(FIR_DOWN.len(), 120);
}
#[test]
fn output_length_is_four_fifths_of_the_input() {
let mut ctx = DspContext::default();
assert_eq!(mult(&mut ctx, Word16(320), DOWN_FAC).0, 256);
assert_eq!(mult(&mut ctx, Word16(15), DOWN_FAC).0, 12);
assert_eq!(mult(&mut ctx, Word16(320), Word16(26214)).0, 255);
}
#[test]
fn the_tail_filters_do_not_disturb_the_carried_state() {
let input = input_frame(0);
let mut with_tail = Preprocessor::new();
let mut first = [Word16(0); L_FRAME + L_FILT];
with_tail.band_limit(&input, &mut first);
let mut second = [Word16(0); L_FRAME + L_FILT];
with_tail.band_limit(&input, &mut second);
let mut plain_decimator = Decimator::new();
let mut plain_highpass = HighPass50::new();
let mut reference = [Word16(0); L_FRAME];
plain_decimator.decimate(&input, &mut reference);
plain_highpass.filter(&mut reference);
plain_decimator.decimate(&input, &mut reference);
plain_highpass.filter(&mut reference);
for i in 0..L_FRAME {
assert_eq!(
second[i].0, reference[i].0,
"sample {i}: the tail call leaked into the carried state"
);
}
}
#[test]
fn silence_takes_the_maximum_scaling() {
let mut pre = Preprocessor::new();
let mut new_speech = [Word16(0); L_FRAME + L_FILT];
pre.band_limit(&[Word16(0); L_FRAME16K], &mut new_speech);
let scaling = pre.preemphasise(&mut new_speech);
assert_eq!(scaling.q_new, Q_MAX);
assert_eq!(scaling.exp, Q_MAX - 15);
}
}