use alloc::vec;
use alloc::vec::Vec;
const COEFS_1_3: [i16; 20] = [
16102, -15162, -13, 0, 20, 26, 5, -31, -43, -4, 65, 90, 7, -157, -248, -44, 593, 1583, 2612, 3271,
];
const COEFS_1_4: [i16; 20] = [
22500, -15099, 3, -14, -20, -15, 2, 25, 37, 25, -16, -71, -107, -79, 50, 292, 623, 982, 1288, 1464,
];
const COEFS_1_6: [i16; 20] = [
27540, -15257, 17, 12, 8, 1, -10, -22, -30, -32, -22, 3, 44, 100, 168, 243, 317, 381, 429, 455,
];
const ORD: usize = 36;
const INPUT_DELAY_48: [usize; 3] = [18, 10, 12];
#[derive(Debug, Clone)]
pub(crate) struct EncDownsampler {
s_iir: [f32; 2],
s_fir: [f32; ORD],
delay_buf: [f32; 48],
input_delay: usize,
fs_in_khz: usize,
fs_out_khz: usize,
decim: usize,
a0: f32,
a1: f32,
full_coef: [f32; ORD],
in_scale: f32,
}
impl EncDownsampler {
pub(crate) fn new(fs_out_khz: usize) -> Self {
let (coefs, decim) = match fs_out_khz {
16 => (&COEFS_1_3, 3),
12 => (&COEFS_1_4, 4),
8 => (&COEFS_1_6, 6),
_ => panic!("encoder internal rate must be 8/12/16 kHz, got {fs_out_khz}"),
};
let fir = &coefs[2..];
let mut full_coef = [0.0f32; ORD];
for (t, fc) in full_coef.iter_mut().enumerate() {
*fc = f32::from(if t < 18 { fir[t] } else { fir[ORD - 1 - t] });
}
let input_delay = INPUT_DELAY_48[match fs_out_khz {
8 => 0,
12 => 1,
_ => 2,
}];
Self {
s_iir: [0.0; 2],
s_fir: [0.0; ORD],
delay_buf: [0.0; 48],
input_delay,
fs_in_khz: 48,
fs_out_khz,
decim,
a0: f32::from(coefs[0]) / 16384.0,
a1: f32::from(coefs[1]) / 16384.0,
full_coef,
in_scale: 32768.0,
}
}
pub(crate) fn process(&mut self, out: &mut [i16], input: &[f32]) {
let in_len = input.len();
debug_assert!(in_len >= self.fs_in_khz);
debug_assert_eq!(out.len(), in_len * self.fs_out_khz / self.fs_in_khz);
let n_samples = self.fs_in_khz - self.input_delay;
let mut head = [0.0f32; 48];
head[..self.input_delay].copy_from_slice(&self.delay_buf[..self.input_delay]);
head[self.input_delay..self.fs_in_khz].copy_from_slice(&input[..n_samples]);
let tail = &input[n_samples..in_len - self.input_delay];
let (out_head, out_tail) = out.split_at_mut(self.fs_out_khz);
self.down_fir(out_head, &head[..self.fs_in_khz]);
self.down_fir(out_tail, tail);
self.delay_buf[..self.input_delay].copy_from_slice(&input[in_len - self.input_delay..]);
}
fn down_fir(&mut self, out: &mut [i16], input: &[f32]) {
let n = input.len();
let mut buf = vec![0.0f32; ORD + n];
buf[..ORD].copy_from_slice(&self.s_fir);
{
let s = &mut self.s_iir;
let scale = self.in_scale;
for (o, &x) in buf[ORD..].iter_mut().zip(input.iter()) {
let v = s[0] + x * scale;
*o = v;
s[0] = s[1] + v * self.a0;
s[1] = v * self.a1;
}
}
let coef = &self.full_coef;
for (m, o) in out.iter_mut().enumerate() {
let base = m * self.decim;
let acc = crate::simd::dot(coef, &buf[base..base + ORD]);
let v = acc * (1.0 / 16384.0);
*o = (v + 0.5f32.copysign(v)) as i16;
}
self.s_fir.copy_from_slice(&buf[n..n + ORD]);
}
}
pub(crate) fn resample_48k(ds: &mut EncDownsampler, input: &[f32]) -> Vec<i16> {
let out_len = input.len() * ds.fs_out_khz / 48;
let mut out = vec![0i16; out_len];
ds.process(&mut out, input);
out
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use crate::silk::resampler::Resampler;
#[test]
fn matches_fixed_point_within_tolerance() {
for &(out_khz, _ratio) in &[(16usize, 3usize), (12, 4), (8, 6)] {
let frames = 6;
let n = 960 * frames;
let pcm: Vec<f32> = (0..n)
.map(|i| {
let t = i as f32;
0.25 * (t * 0.04).sin() + 0.09 * (t * 0.21).sin() + 0.045 * (t * 0.011).cos()
})
.collect();
let mut fixed = Resampler::new_enc(48_000, (out_khz * 1000) as i32);
let mut flt = EncDownsampler::new(out_khz);
let mut max_diff = 0i32;
for f in 0..frames {
let chunk = &pcm[f * 960..f * 960 + 960];
let in16: Vec<i16> = chunk
.iter()
.map(|&v| (v * 32768.0).round().clamp(-32768.0, 32767.0) as i16)
.collect();
let mut a = vec![0i16; 960 * out_khz / 48];
fixed.process(&mut a, &in16);
let b = resample_48k(&mut flt, chunk);
for (x, y) in a.iter().zip(b.iter()) {
max_diff = max_diff.max((i32::from(*x) - i32::from(*y)).abs());
}
}
assert!(max_diff <= 4, "{out_khz}kHz: max LSB diff {max_diff} vs fixed-point");
}
}
}