use alloc::vec;
use alloc::vec::Vec;
use core::iter;
use num_complex::Complex;
#[cfg(not(feature = "std"))]
use num_traits::Float;
use crate::core::fft::default_planner;
#[derive(Clone, Copy, Debug)]
pub struct DownsampleCfg {
pub input_rate: u32,
pub fft1_size: usize,
pub fft2_size: usize,
pub tone_spacing_hz: f32,
pub leading_pad_tones: f32,
pub trailing_pad_tones: f32,
pub ntones: u32,
pub edge_taper_bins: usize,
}
impl DownsampleCfg {
#[inline]
fn bin_hz(&self) -> f32 {
self.input_rate as f32 / self.fft1_size as f32
}
}
#[inline]
pub fn build_fft_cache(audio: &[i16], cfg: &DownsampleCfg) -> Vec<Complex<f32>> {
let mut x: Vec<Complex<f32>> = audio
.iter()
.map(|&s| Complex::new(s as f32, 0.0))
.chain(iter::repeat(Complex::new(0.0, 0.0)))
.take(cfg.fft1_size)
.collect();
let mut planner = default_planner();
planner.plan_forward(cfg.fft1_size).process(&mut x);
x
}
#[inline]
pub fn downsample(
audio: &[i16],
f0: f32,
cfg: &DownsampleCfg,
) -> (Vec<Complex<f32>>, Vec<Complex<f32>>) {
let cache = build_fft_cache(audio, cfg);
let out = downsample_cached(&cache, f0, cfg);
(out, cache)
}
#[inline]
pub fn downsample_cached(
fft_cache: &[Complex<f32>],
f0: f32,
cfg: &DownsampleCfg,
) -> Vec<Complex<f32>> {
debug_assert_eq!(fft_cache.len(), cfg.fft1_size);
let mut planner = default_planner();
let df = cfg.bin_hz();
let baud = cfg.tone_spacing_hz;
let i0 = (f0 / df).round() as usize;
let ft = f0 + (cfg.ntones as f32 - 1.0 + cfg.trailing_pad_tones) * baud;
let fb = f0 - cfg.leading_pad_tones * baud;
let it = ((ft / df).round() as usize).min(cfg.fft1_size / 2);
let ib = ((fb / df).round() as usize).max(1);
let k = it.saturating_sub(ib) + 1;
let mut c1 = vec![Complex::new(0.0f32, 0.0); cfg.fft2_size];
for (dst, src) in c1[..k.min(cfg.fft2_size)]
.iter_mut()
.zip(fft_cache[ib..=it].iter())
{
*dst = *src;
}
let et = cfg.edge_taper_bins;
if et > 1 {
let n = et - 1;
let taper: Vec<f32> = (0..et)
.map(|i| 0.5 * (1.0 + (i as f32 * core::f32::consts::PI / n as f32).cos()))
.collect();
for i in 0..et.min(k) {
c1[i] *= taper[n - i];
}
if k > et {
for i in 0..et {
c1[k - et + i] *= taper[i];
}
}
}
let shift = i0.saturating_sub(ib) % cfg.fft2_size;
c1.rotate_left(shift);
planner.plan_inverse(cfg.fft2_size).process(&mut c1);
let fac = 1.0 / ((cfg.fft1_size as f32) * (cfg.fft2_size as f32)).sqrt();
for s in c1.iter_mut() {
*s *= fac;
}
c1
}