use crate::core::{Block, WorkReport};
use num_complex::Complex32 as C32;
#[derive(Debug, Clone)]
pub struct FirLowpass {
taps: Vec<f32>,
delay: Vec<f32>,
idx: usize,
}
impl FirLowpass {
pub fn design(fs: f32, pass_hz: f32, trans_hz: f32) -> Self {
let pass_hz = pass_hz.max(10.0);
let trans_hz = trans_hz.max(pass_hz * 0.2);
let ntaps = ((fs / trans_hz).ceil() as usize).max(31) | 1; let mut taps = vec![0.0f32; ntaps];
let fc = pass_hz / fs;
let m0 = ntaps as isize / 2;
for (n, tap) in taps.iter_mut().enumerate() {
let m = n as isize - m0;
let sinc = if m == 0 {
2.0 * fc
} else {
let x = core::f32::consts::PI * m as f32;
(2.0 * fc) * (2.0 * core::f32::consts::PI * fc * m as f32).sin() / x
};
let w =
0.5 - 0.5 * (2.0 * core::f32::consts::PI * n as f32 / (ntaps as f32 - 1.0)).cos();
*tap = sinc * w;
}
let s: f32 = taps.iter().sum();
for t in &mut taps {
*t /= s;
}
Self {
taps,
delay: vec![0.0; ntaps],
idx: 0,
}
}
#[inline]
pub fn process(&mut self, input: &[f32], output: &mut [f32]) {
let n = input.len().min(output.len());
for i in 0..n {
self.delay[self.idx] = input[i];
output[i] = self.dot();
self.idx = (self.idx + 1) % self.delay.len();
}
}
#[inline(always)]
fn dot(&self) -> f32 {
let len = self.taps.len();
let d = &self.delay;
let mut acc = 0.0f32;
for (t_idx, &tap) in self.taps.iter().enumerate() {
let d_idx = (self.idx + len - 1 - t_idx) % len;
acc += d[d_idx] * tap;
}
acc
}
}
fn kaiser_beta(a_db: f32) -> f32 {
if a_db > 50.0 {
0.1102 * (a_db - 8.7)
} else if a_db >= 21.0 {
0.5842 * (a_db - 21.0).powf(0.4) + 0.07886 * (a_db - 21.0)
} else {
0.0
}
}
fn bessel_i0(x: f32) -> f32 {
let half = 0.5 * x;
let mut term = 1.0f32;
let mut sum = 1.0f32;
for k in 1..=40u32 {
term *= half / k as f32;
let t = term * term;
sum += t;
if t < 1e-12 * sum {
break;
}
}
sum
}
pub fn kaiser_lowpass_taps(num_taps: usize, cutoff_norm: f32, stopband_db: f32) -> Vec<f32> {
let m = num_taps.max(3) | 1;
let mid = (m / 2) as f32;
let fc = cutoff_norm.clamp(1e-4, 0.499_9);
let beta = kaiser_beta(stopband_db);
let i0_beta = bessel_i0(beta);
let mut taps = vec![0.0f32; m];
for (n, tap) in taps.iter_mut().enumerate() {
let d = n as f32 - mid;
let ideal = if d == 0.0 {
2.0 * fc
} else {
(core::f32::consts::TAU * fc * d).sin() / (core::f32::consts::PI * d)
};
let r = d / mid;
let w = bessel_i0(beta * (1.0 - r * r).max(0.0).sqrt()) / i0_beta;
*tap = ideal * w;
}
let s: f32 = taps.iter().sum();
if s.abs() > f32::EPSILON {
for t in &mut taps {
*t /= s;
}
}
taps
}
pub fn kaiser_transition_norm(num_taps: usize, stopband_db: f32) -> f32 {
let m = (num_taps.max(3) | 1) as f32;
(stopband_db.max(21.0) - 8.0) / (14.36 * m)
}
pub fn kaiser_num_taps(transition_norm: f32, stopband_db: f32) -> usize {
let m = ((stopband_db.max(21.0) - 8.0) / (14.36 * transition_norm.max(1e-4))).ceil();
(m.max(3.0) as usize) | 1
}
#[derive(Debug, Clone)]
pub struct FirLowpassIq {
taps: Vec<f32>,
delay: Vec<C32>,
idx: usize,
}
impl FirLowpassIq {
pub fn design(num_taps: usize, cutoff_norm: f32, stopband_db: f32) -> Self {
Self::from_taps(kaiser_lowpass_taps(num_taps, cutoff_norm, stopband_db))
}
pub fn from_taps(taps: Vec<f32>) -> Self {
let mut taps = taps;
if taps.is_empty() {
taps.push(1.0);
}
let len = taps.len();
Self {
taps,
delay: vec![C32::default(); len],
idx: 0,
}
}
pub fn taps(&self) -> &[f32] {
&self.taps
}
pub fn num_taps(&self) -> usize {
self.taps.len()
}
pub fn group_delay(&self) -> usize {
(self.taps.len() - 1) / 2
}
pub fn reset(&mut self) {
self.delay.fill(C32::default());
self.idx = 0;
}
#[inline(always)]
pub fn push(&mut self, s: C32) -> C32 {
let len = self.taps.len();
self.delay[self.idx] = s;
let (mut re, mut im) = (0.0f32, 0.0f32);
for (j, &t) in self.taps[..=self.idx].iter().enumerate() {
let d = self.delay[self.idx - j];
re = d.re.mul_add(t, re);
im = d.im.mul_add(t, im);
}
for (k, &t) in self.taps[self.idx + 1..].iter().enumerate() {
let d = self.delay[len - 1 - k];
re = d.re.mul_add(t, re);
im = d.im.mul_add(t, im);
}
self.idx = if self.idx + 1 == len { 0 } else { self.idx + 1 };
C32::new(re, im)
}
pub fn filter_aligned(&mut self, io: &mut [C32]) {
let d = self.group_delay();
let n = io.len();
self.reset();
for i in 0..d {
let x = io.get(i).copied().unwrap_or_default();
self.push(x);
}
for i in 0..n {
let x = if i + d < n { io[i + d] } else { C32::default() };
io[i] = self.push(x);
}
}
}
impl Block for FirLowpassIq {
type In = C32;
type Out = C32;
fn process(&mut self, input: &[C32], output: &mut [C32]) -> WorkReport {
let n = input.len().min(output.len());
for i in 0..n {
output[i] = self.push(input[i]);
}
WorkReport {
in_read: n,
out_written: n,
}
}
}
#[derive(Debug, Clone)]
pub struct HalfCosineMf {
taps: Vec<f32>,
delay_re: Vec<f32>,
delay_im: Vec<f32>,
idx: usize,
}
impl HalfCosineMf {
pub fn new(sps: usize) -> Self {
let hann: Vec<f32> = if sps <= 1 {
vec![1.0f32; sps.max(1)]
} else {
let denom = (sps - 1) as f32;
(0..sps)
.map(|i| 0.5 - 0.5 * (core::f32::consts::PI * i as f32 / denom).cos())
.collect()
};
let energy: f32 = hann.iter().map(|&h| h * h).sum();
let scale = if energy > 0.0 {
energy.sqrt().recip()
} else {
1.0
};
let taps: Vec<f32> = hann.iter().map(|&h| h * scale).collect();
let len = taps.len();
Self {
taps,
delay_re: vec![0.0f32; len],
delay_im: vec![0.0f32; len],
idx: 0,
}
}
#[inline(always)]
pub fn push(&mut self, s: C32) -> C32 {
let len = self.taps.len();
self.delay_re[self.idx] = s.re;
self.delay_im[self.idx] = s.im;
let mut re = 0.0f32;
let mut im = 0.0f32;
for t_idx in 0..len {
let d_idx = (self.idx + len - t_idx) % len;
let w = self.taps[t_idx];
re += self.delay_re[d_idx] * w;
im += self.delay_im[d_idx] * w;
}
self.idx = (self.idx + 1) % len;
C32::new(re, im)
}
pub fn reset(&mut self) {
self.delay_re.fill(0.0);
self.delay_im.fill(0.0);
self.idx = 0;
}
}