use alloc::vec;
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use num_traits::Float;
pub fn design_lowpass(ntaps: usize, fc_norm: f32) -> Vec<f32> {
let mut h = vec![0.0f32; ntaps];
let m = (ntaps - 1) as f32;
let mut sum = 0.0f32;
for (k, tap) in h.iter_mut().enumerate() {
let x = k as f32 - m / 2.0;
let sinc = if x.abs() < 1e-6 {
2.0 * fc_norm
} else {
(2.0 * core::f32::consts::PI * fc_norm * x).sin() / (core::f32::consts::PI * x)
};
let w = 0.42 - 0.5 * (2.0 * core::f32::consts::PI * k as f32 / m).cos()
+ 0.08 * (4.0 * core::f32::consts::PI * k as f32 / m).cos();
*tap = sinc * w;
sum += *tap;
}
for tap in h.iter_mut() {
*tap /= sum;
}
h
}
pub struct FirStage {
taps_rev: Vec<f32>,
hist_i: Vec<f32>,
hist_q: Vec<f32>,
hist_len: usize,
win_start: usize,
to_next_out: usize,
decim: usize,
}
impl FirStage {
pub fn new(ntaps: usize, decim: usize, fc_norm: f32, hist_margin: usize) -> Self {
assert!(ntaps % 2 == 1, "ntaps must be odd for linear phase");
let designed = design_lowpass(ntaps, fc_norm);
let mut taps_rev = vec![0.0f32; ntaps];
let last = ntaps - 1;
for k in 0..ntaps {
taps_rev[k] = designed[last - k];
}
let hist_cap = ntaps + hist_margin;
let mut hist_i = vec![0.0f32; hist_cap];
let mut hist_q = vec![0.0f32; hist_cap];
hist_i[..ntaps].fill(0.0);
hist_q[..ntaps].fill(0.0);
let group_delay = (ntaps - 1) / 2;
Self {
taps_rev,
hist_i,
hist_q,
hist_len: ntaps,
win_start: 0,
to_next_out: group_delay + 1,
decim,
}
}
pub fn ntaps(&self) -> usize {
self.taps_rev.len()
}
pub fn group_delay(&self) -> usize {
(self.ntaps() - 1) / 2
}
pub fn push_one(&mut self, i: f32, q: f32) -> Option<(f32, f32)> {
self.hist_i[self.hist_len] = i;
self.hist_q[self.hist_len] = q;
self.hist_len += 1;
self.win_start += 1;
let mut out = None;
self.to_next_out -= 1;
if self.to_next_out == 0 {
self.to_next_out = self.decim;
out = Some(self.dot());
}
if self.hist_len == self.hist_i.len() {
self.compact();
}
out
}
pub fn push_block(
&mut self,
xi: &[f32],
xq: &[f32],
out_i: &mut Vec<f32>,
out_q: &mut Vec<f32>,
) {
assert_eq!(xi.len(), xq.len(), "I/Q blocks must be the same length");
for (&i, &q) in xi.iter().zip(xq.iter()) {
if let Some((oi, oq)) = self.push_one(i, q) {
out_i.push(oi);
out_q.push(oq);
}
}
}
fn compact(&mut self) {
let keep = self.win_start;
let ntaps = self.ntaps();
self.hist_i.copy_within(keep..self.hist_len, 0);
self.hist_q.copy_within(keep..self.hist_len, 0);
self.hist_len = ntaps;
self.win_start = 0;
}
fn dot(&self) -> (f32, f32) {
let ntaps = self.ntaps();
let a = self.win_start;
let hi = &self.hist_i[a..a + ntaps];
let hq = &self.hist_q[a..a + ntaps];
let h = &self.taps_rev[..];
(
super::dotprod::dot_f32(h, hi),
super::dotprod::dot_f32(h, hq),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_block_matches_repeated_push_one() {
let audio: Vec<f32> = (0..500).map(|k| (k as f32 * 0.037).sin()).collect();
let mut one = FirStage::new(31, 4, 0.1, 32);
let mut oi = Vec::new();
let mut oq = Vec::new();
for &s in &audio {
if let Some((i, q)) = one.push_one(s, -s) {
oi.push(i);
oq.push(q);
}
}
let mut block = FirStage::new(31, 4, 0.1, 32);
let neg: Vec<f32> = audio.iter().map(|&s| -s).collect();
let mut bi = Vec::new();
let mut bq = Vec::new();
block.push_block(&audio, &neg, &mut bi, &mut bq);
assert_eq!(oi, bi);
assert_eq!(oq, bq);
}
}