use anyhow::Result;
use ndarray::Array2;
use rustfft::{FftPlanner, num_complex::Complex};
pub fn apply_fir_zero_phase(data: &mut Array2<f32>, h: &[f32]) -> Result<()> {
let n_ch = data.nrows();
for ch in 0..n_ch {
let row: Vec<f32> = data.row(ch).to_vec();
let filtered = filter_1d(&row, h)?;
data.row_mut(ch).assign(&ndarray::ArrayView1::from(&filtered));
}
Ok(())
}
pub fn filter_1d(x: &[f32], h: &[f32]) -> Result<Vec<f32>> {
let n_x = x.len();
let n_h = h.len();
if n_x == 0 {
return Ok(vec![]);
}
let shift = (n_h - 1) / 2;
let n_edge = n_h - 1;
let x_ext = reflect_limited_pad(x, n_edge, n_edge);
let n_ext = x_ext.len();
let n_fft = choose_fft_len(n_h, n_ext);
let h_fft = fft_of_h(h, n_fft);
let n_seg = n_fft - n_h + 1;
let n_segments = n_ext.div_ceil(n_seg);
let mut x_filtered = vec![0.0_f32; n_ext];
let mut planner: FftPlanner<f32> = FftPlanner::new();
let fft_fwd = planner.plan_fft_forward(n_fft);
let fft_inv = planner.plan_fft_inverse(n_fft);
let inv_scale = 1.0 / n_fft as f32;
for seg_idx in 0..n_segments {
let start = seg_idx * n_seg;
let stop = (start + n_seg).min(n_ext);
let mut buf: Vec<Complex<f32>> = x_ext[start..stop]
.iter()
.map(|&v| Complex { re: v, im: 0.0 })
.chain(std::iter::repeat(Complex::default()))
.take(n_fft)
.collect();
fft_fwd.process(&mut buf);
for (b, &hf) in buf.iter_mut().zip(h_fft.iter()) {
*b *= hf;
}
fft_inv.process(&mut buf);
let out_start = start.saturating_sub(shift);
let out_end = (out_start + n_fft).min(n_ext);
let prod_start = shift.saturating_sub(start);
for (o, p) in (out_start..out_end).zip(prod_start..) {
if p < buf.len() {
x_filtered[o] += buf[p].re * inv_scale;
}
}
}
let result: Vec<f32> = x_filtered[n_edge..n_edge + n_x].to_vec();
Ok(result)
}
fn reflect_limited_pad(x: &[f32], n_l: usize, n_r: usize) -> Vec<f32> {
let n = x.len();
let actual_l = n_l.min(n - 1);
let actual_r = n_r.min(n - 1);
let mut out = Vec::with_capacity(actual_l + n + actual_r);
for i in (1..=actual_l).rev() {
out.push(2.0 * x[0] - x[i]);
}
if actual_l < n_l {
let mut prefix = vec![0.0_f32; n_l - actual_l];
prefix.append(&mut out);
out = prefix;
}
out.extend_from_slice(x);
let last = x[n - 1];
for i in 1..=actual_r {
let idx = (n - 1).saturating_sub(i);
out.push(2.0 * last - x[idx]);
}
if actual_r < n_r {
out.extend(std::iter::repeat_n(0.0_f32, n_r - actual_r));
}
out
}
fn choose_fft_len(n_h: usize, n_x: usize) -> usize {
let min_fft = 2 * n_h - 1;
let max_pow = (n_x as f64).log2().ceil() as u32 + 1;
let min_pow = (min_fft as f64).log2().ceil() as u32;
let mut best_n = 1_usize << max_pow;
let mut best_cost = f64::INFINITY;
for pow in min_pow..=max_pow {
let n = 1_usize << pow;
if n < min_fft { continue; }
let n_seg = (n - n_h + 1) as f64;
let cost = (n_x as f64 / n_seg).ceil() * n as f64 * (pow as f64 + 1.0)
+ 4e-5 * n as f64 * n_x as f64;
if cost < best_cost {
best_cost = cost;
best_n = n;
}
}
best_n
}
fn fft_of_h(h: &[f32], n_fft: usize) -> Vec<Complex<f32>> {
let mut buf: Vec<Complex<f32>> = h
.iter()
.map(|&v| Complex { re: v, im: 0.0 })
.chain(std::iter::repeat(Complex::default()))
.take(n_fft)
.collect();
let mut planner: FftPlanner<f32> = FftPlanner::new();
planner.plan_fft_forward(n_fft).process(&mut buf);
buf
}
#[cfg(test)]
mod tests {
use super::*;
use crate::filter::design::design_highpass;
#[test]
fn filter_preserves_length() {
let x: Vec<f32> = (0..1024).map(|i| (i as f32 / 1024.0).sin()).collect();
let h = design_highpass(0.5, 256.0);
let y = filter_1d(&x, &h).unwrap();
assert_eq!(y.len(), x.len());
}
#[test]
fn filter_removes_dc() {
let x = vec![1.0_f32; 4096];
let h = design_highpass(0.5, 256.0);
let y = filter_1d(&x, &h).unwrap();
let n_h = h.len();
let interior = &y[n_h..y.len() - n_h];
let max_val: f32 = interior.iter().map(|v| v.abs()).fold(0.0_f32, f32::max);
assert!(max_val < 1e-3, "DC not removed: max={max_val}");
}
#[test]
fn reflect_limited_left_pad() {
let x = [1.0_f32, 2.0, 3.0, 4.0, 5.0];
let padded = reflect_limited_pad(&x, 3, 0);
assert_eq!(&padded[..3], &[-2.0_f32, -1.0, 0.0]);
assert_eq!(&padded[3..], &x[..]);
}
}