use alloc::vec::Vec;
use num_complex::Complex;
#[cfg(not(feature = "std"))]
use num_traits::Float;
use super::fir_decimate::FirStage;
use super::polyphase::PolyphaseResampler;
const RENORM_PERIOD: u32 = 4096;
pub(crate) struct Mixer {
step: Complex<f32>,
cur: Complex<f32>,
since_renorm: u32,
}
impl Mixer {
pub(crate) fn new(center_hz: f32, sample_rate_hz: f32) -> Self {
let dphi = -2.0 * core::f32::consts::PI * center_hz / sample_rate_hz;
Self {
step: Complex::new(dphi.cos(), dphi.sin()),
cur: Complex::new(1.0, 0.0),
since_renorm: 0,
}
}
#[inline]
fn advance(&mut self) {
self.cur *= self.step;
self.since_renorm += 1;
if self.since_renorm >= RENORM_PERIOD {
let mag = (self.cur.re * self.cur.re + self.cur.im * self.cur.im).sqrt();
if mag > 0.0 {
self.cur.re /= mag;
self.cur.im /= mag;
}
self.since_renorm = 0;
}
}
#[inline]
pub(crate) fn mix(&mut self, x: f32) -> (f32, f32) {
let out = (x * self.cur.re, x * self.cur.im);
self.advance();
out
}
#[inline]
pub(crate) fn mix_complex(&mut self, i: f32, q: f32) -> (f32, f32) {
let out = (
i * self.cur.re - q * self.cur.im,
i * self.cur.im + q * self.cur.re,
);
self.advance();
out
}
}
pub type IntStageSpec = (usize, usize, f32);
#[derive(Clone)]
pub struct DdcCascadeConfig {
pub center_hz: f32,
pub input_rate_hz: f32,
pub int_stages: Vec<IntStageSpec>,
pub resampler: (u32, u32, usize),
pub hist_margin: usize,
}
pub struct StreamingComplexDdc {
mixer: Mixer,
int_stages: Vec<FirStage>,
resampler: PolyphaseResampler,
flush_zeros: usize,
}
impl StreamingComplexDdc {
pub fn new(cfg: &DdcCascadeConfig) -> Self {
let mixer = Mixer::new(cfg.center_hz, cfg.input_rate_hz);
let int_stages = cfg
.int_stages
.iter()
.map(|&(ntaps, decim, fc_norm)| FirStage::new(ntaps, decim, fc_norm, cfg.hist_margin))
.collect();
let (l, m, ntaps) = cfg.resampler;
let resampler = PolyphaseResampler::new(l, m, ntaps, cfg.hist_margin);
let mut scale = 1usize;
let mut delay = 0usize;
for &(stage_ntaps, decim, _) in &cfg.int_stages {
delay += ((stage_ntaps - 1) / 2) * scale;
scale *= decim;
}
delay += (((ntaps - 1) / 2) / l as usize) * scale;
Self {
mixer,
int_stages,
resampler,
flush_zeros: delay + 1,
}
}
pub fn group_delay_input_samples(&self) -> usize {
self.flush_zeros.saturating_sub(1)
}
pub fn push_one(&mut self, x: f32, out_i: &mut Vec<f32>, out_q: &mut Vec<f32>) {
let (mut i, mut q) = self.mixer.mix(x);
for stage in &mut self.int_stages {
match stage.push_one(i, q) {
Some((si, sq)) => {
i = si;
q = sq;
}
None => return,
}
}
self.resampler.push(i, q, out_i, out_q);
}
pub fn push_i16(&mut self, audio: &[i16], out_i: &mut Vec<f32>, out_q: &mut Vec<f32>) {
for &s in audio {
self.push_one(s as f32, out_i, out_q);
}
}
pub fn flush(&mut self, out_i: &mut Vec<f32>, out_q: &mut Vec<f32>) {
for _ in 0..self.flush_zeros {
self.push_one(0.0, out_i, out_q);
}
}
}
pub struct StreamingComplexRecenter {
mixer: Mixer,
resampler: PolyphaseResampler,
flush_zeros: usize,
}
impl StreamingComplexRecenter {
pub fn new(
center_hz: f32,
input_rate_hz: f32,
l: u32,
m: u32,
ntaps: usize,
hist_margin: usize,
) -> Self {
let mixer = Mixer::new(center_hz, input_rate_hz);
let resampler = PolyphaseResampler::new(l, m, ntaps, hist_margin);
let delay = ((ntaps - 1) / 2) / l as usize;
Self {
mixer,
resampler,
flush_zeros: delay + 1,
}
}
pub fn group_delay_input_samples(&self) -> usize {
self.flush_zeros.saturating_sub(1)
}
pub fn group_delay_output_samples(&self) -> usize {
self.resampler.group_delay_output()
}
pub fn push_one(&mut self, i: f32, q: f32, out_i: &mut Vec<f32>, out_q: &mut Vec<f32>) {
let (mi, mq) = self.mixer.mix_complex(i, q);
self.resampler.push(mi, mq, out_i, out_q);
}
pub fn push(&mut self, xi: &[f32], xq: &[f32], out_i: &mut Vec<f32>, out_q: &mut Vec<f32>) {
for (&i, &q) in xi.iter().zip(xq.iter()) {
self.push_one(i, q, out_i, out_q);
}
}
pub fn flush(&mut self, out_i: &mut Vec<f32>, out_q: &mut Vec<f32>) {
for _ in 0..self.flush_zeros {
self.push_one(0.0, 0.0, out_i, out_q);
}
}
}
pub fn ddc_block(audio: &[i16], cfg: &DdcCascadeConfig) -> (Vec<f32>, Vec<f32>) {
let mut ddc = StreamingComplexDdc::new(cfg);
let mut out_i = Vec::new();
let mut out_q = Vec::new();
ddc.push_i16(audio, &mut out_i, &mut out_q);
ddc.flush(&mut out_i, &mut out_q);
(out_i, out_q)
}
#[cfg(test)]
mod tests {
use super::*;
fn tone(freq_hz: f32, fs_hz: f32, amp: f32, n: usize) -> Vec<i16> {
let w = 2.0 * core::f64::consts::PI * freq_hz as f64 / fs_hz as f64;
(0..n)
.map(|k| (amp as f64 * 32767.0 * (w * k as f64).cos()) as i16)
.collect()
}
#[test]
fn centre_tone_lands_near_dc() {
let fs_in = 12_000.0f32;
let center = 1500.0f32;
let cfg = DdcCascadeConfig {
center_hz: center,
input_rate_hz: fs_in,
int_stages: Vec::new(),
resampler: (64, 243, 4001), hist_margin: 512,
};
let n = 96_000;
let audio = tone(center, fs_in, 0.5, n);
let (i, q) = ddc_block(&audio, &cfg);
let settle = i.len() / 4;
let span = settle..(i.len() - settle);
let (mut si, mut sq, mut cnt) = (0.0f64, 0.0f64, 0usize);
for k in span {
si += i[k] as f64;
sq += q[k] as f64;
cnt += 1;
}
let (mi, mq) = (si / cnt as f64, sq / cnt as f64);
let mag = (mi * mi + mq * mq).sqrt();
assert!(mag > 0.05, "centre tone magnitude too small: {mag}");
assert!(mi.abs() > mag * 0.3 || mq.abs() > mag * 0.3);
}
#[test]
fn offset_tone_rotates_at_the_offset() {
let fs_in = 12_000.0f32;
let center = 1500.0f32;
let offset = 40.0f32;
let cfg = DdcCascadeConfig {
center_hz: center,
input_rate_hz: fs_in,
int_stages: Vec::new(),
resampler: (64, 243, 4001),
hist_margin: 512,
};
let n = 96_000;
let audio = tone(center + offset, fs_in, 0.5, n);
let (i, q) = ddc_block(&audio, &cfg);
let fs_out = fs_in * 64.0 / 243.0;
let settle = i.len() / 4;
let a = settle;
let b = i.len() - settle;
let ph = |k: usize| q[k].atan2(i[k]);
let mut d = ph(b) - ph(a);
let expect_total = 2.0 * core::f32::consts::PI * offset * (b - a) as f32 / fs_out;
while d - expect_total > core::f32::consts::PI {
d -= 2.0 * core::f32::consts::PI;
}
while expect_total - d > core::f32::consts::PI {
d += 2.0 * core::f32::consts::PI;
}
let measured_hz = d / (2.0 * core::f32::consts::PI) * fs_out / (b - a) as f32;
assert!(
(measured_hz - offset).abs() < 1.0,
"measured {measured_hz} Hz, expected {offset}"
);
}
}