mod input_pruned;
mod output_pruned;
mod plan;
pub use input_pruned::fft_pruned_input;
pub use output_pruned::fft_pruned_output;
pub use plan::{PrunedPlan, PruningMode};
use crate::kernel::{Complex, Float};
pub fn goertzel<T: Float>(input: &[Complex<T>], freq_idx: usize) -> Complex<T> {
let n = input.len();
if n == 0 {
return Complex::<T>::zero();
}
let two_pi = <T as Float>::PI + <T as Float>::PI;
let omega = two_pi * T::from_usize(freq_idx) / T::from_usize(n);
let (sin_omega, cos_omega) = Float::sin_cos(omega);
let coeff = cos_omega + cos_omega;
let mut s0 = T::ZERO;
let mut s1 = T::ZERO;
for sample in input.iter() {
let s2 = sample.re + coeff * s1 - s0;
s0 = s1;
s1 = s2;
}
let re = cos_omega * s1 - s0;
let im = sin_omega * s1;
s0 = T::ZERO;
s1 = T::ZERO;
for sample in input.iter() {
let s2 = sample.im + coeff * s1 - s0;
s0 = s1;
s1 = s2;
}
let re_im = cos_omega * s1 - s0;
let im_im = sin_omega * s1;
let re_from_im = T::ZERO - im_im;
let im_from_im = re_im;
Complex::new(re + re_from_im, im + im_from_im)
}
pub fn goertzel_multi<T: Float>(input: &[Complex<T>], freq_indices: &[usize]) -> Vec<Complex<T>> {
freq_indices.iter().map(|&k| goertzel(input, k)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::{Direction, Flags, Plan};
#[test]
fn test_goertzel_single_frequency() {
let n = 256;
let freq = 10;
let two_pi = core::f64::consts::PI * 2.0;
let input: Vec<Complex<f64>> = (0..n)
.map(|i| {
let angle = two_pi * (freq as f64) * f64::from(i) / f64::from(n);
Complex::new(angle.cos(), angle.sin())
})
.collect();
let result = goertzel(&input, freq);
let magnitude = (result.re * result.re + result.im * result.im).sqrt();
assert!(
magnitude > f64::from(n) * 0.9,
"Expected magnitude ~{n}, got {magnitude}"
);
}
#[test]
fn test_goertzel_vs_fft() {
let n = 64;
let input: Vec<Complex<f64>> = (0..n)
.map(|i| Complex::new((i as f64) / (n as f64), 0.0))
.collect();
let plan = Plan::dft_1d(n, Direction::Forward, Flags::ESTIMATE).unwrap();
let mut fft_output = vec![Complex::new(0.0_f64, 0.0); n];
plan.execute(&input, &mut fft_output);
for freq in [0, 5, 10, 20, 31] {
let goertzel_result = goertzel(&input, freq);
let fft_result = fft_output[freq];
let diff_re = (goertzel_result.re - fft_result.re).abs();
let diff_im = (goertzel_result.im - fft_result.im).abs();
assert!(
diff_re < 1e-10,
"Real mismatch at freq {}: {} vs {}",
freq,
goertzel_result.re,
fft_result.re
);
assert!(
diff_im < 1e-10,
"Imag mismatch at freq {}: {} vs {}",
freq,
goertzel_result.im,
fft_result.im
);
}
}
#[test]
fn test_goertzel_multi() {
let n = 128;
let input: Vec<Complex<f64>> = vec![Complex::new(1.0, 0.0); n];
let freq_indices = vec![0, 5, 10, 50];
let results = goertzel_multi(&input, &freq_indices);
assert_eq!(results.len(), 4);
let dc_mag = (results[0].re * results[0].re + results[0].im * results[0].im).sqrt();
assert!((dc_mag - n as f64).abs() < 1e-10);
}
}