use std::f32::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WindowType {
Hamming,
Blackman,
Hann,
}
pub fn compute_window(n: usize, taps: usize, window_type: WindowType) -> f32 {
let normalized_n = n as f32 / (taps as f32 - 1.0);
match window_type {
WindowType::Hamming => {
0.54 - 0.46 * (2.0 * PI * normalized_n).cos()
}
WindowType::Blackman => {
0.42 - 0.5 * (2.0 * PI * normalized_n).cos() + 0.08 * (4.0 * PI * normalized_n).cos()
}
WindowType::Hann => {
0.5 - 0.5 * (2.0 * PI * normalized_n).cos()
}
}
}
pub fn compute_window_f64(n: usize, taps: usize, window_type: WindowType) -> f64 {
let normalized_n = n as f64 / (taps as f64 - 1.0);
let two_pi = 2.0 * std::f64::consts::PI;
match window_type {
WindowType::Hamming => 0.54 - 0.46 * (two_pi * normalized_n).cos(),
WindowType::Blackman => {
0.42 - 0.5 * (two_pi * normalized_n).cos() + 0.08 * (2.0 * two_pi * normalized_n).cos()
}
WindowType::Hann => 0.5 - 0.5 * (two_pi * normalized_n).cos(),
}
}
pub fn sinc(x: f64) -> f64 {
if x.abs() < 1e-12 {
1.0
} else {
let px = std::f64::consts::PI * x;
px.sin() / px
}
}
pub fn normalize_coeffs(coeffs: &mut [f32]) {
let sum: f32 = coeffs.iter().sum();
if sum != 0.0 {
for coeff in coeffs.iter_mut() {
*coeff /= sum;
}
}
}
pub fn design_fir_filter(cutoff: f32, taps: usize, window_type: WindowType) -> Vec<f32> {
assert!(taps > 0, "Number of taps must be greater than 0");
assert!(
cutoff > 0.0 && cutoff <= 0.5,
"Cutoff must be in range (0.0, 0.5]"
);
let mut fir = Vec::with_capacity(taps);
let mid = (taps / 2) as isize;
for n in 0..taps {
let x = n as isize - mid;
let h = 2.0 * cutoff as f64 * sinc(2.0 * cutoff as f64 * x as f64);
let window = compute_window(n, taps, window_type);
fir.push(h as f32 * window);
}
normalize_coeffs(&mut fir);
fir
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_hamming_window() {
assert_relative_eq!(
compute_window(0, 31, WindowType::Hamming),
0.08,
epsilon = 0.01
);
assert_relative_eq!(
compute_window(30, 31, WindowType::Hamming),
0.08,
epsilon = 0.01
);
assert_relative_eq!(
compute_window(15, 31, WindowType::Hamming),
1.0,
epsilon = 0.01
);
}
#[test]
fn test_blackman_window() {
assert_relative_eq!(
compute_window(0, 31, WindowType::Blackman),
0.0,
epsilon = 0.001
);
assert_relative_eq!(
compute_window(30, 31, WindowType::Blackman),
0.0,
epsilon = 0.001
);
assert_relative_eq!(
compute_window(15, 31, WindowType::Blackman),
1.0,
epsilon = 0.01
);
}
#[test]
fn test_hann_window() {
assert_relative_eq!(compute_window(0, 31, WindowType::Hann), 0.0, epsilon = 1e-6);
assert_relative_eq!(
compute_window(30, 31, WindowType::Hann),
0.0,
epsilon = 1e-6
);
assert_relative_eq!(
compute_window(15, 31, WindowType::Hann),
1.0,
epsilon = 0.01
);
}
#[test]
fn test_normalize_coeffs() {
let mut coeffs = vec![1.0, 2.0, 1.0];
normalize_coeffs(&mut coeffs);
assert_relative_eq!(coeffs.iter().sum::<f32>(), 1.0, epsilon = 1e-6);
}
#[test]
fn test_design_fir_filter_hamming() {
let filter = design_fir_filter(0.125, 31, WindowType::Hamming);
assert_eq!(filter.len(), 31);
let sum: f32 = filter.iter().sum();
assert_relative_eq!(sum, 1.0, epsilon = 1e-6);
}
#[test]
fn test_design_fir_filter_blackman() {
let filter = design_fir_filter(0.0625, 63, WindowType::Blackman);
assert_eq!(filter.len(), 63);
let sum: f32 = filter.iter().sum();
assert_relative_eq!(sum, 1.0, epsilon = 1e-6);
}
#[test]
#[should_panic(expected = "Cutoff must be in range")]
fn test_design_fir_invalid_cutoff_zero() {
design_fir_filter(0.0, 31, WindowType::Hamming);
}
#[test]
#[should_panic(expected = "Cutoff must be in range")]
fn test_design_fir_invalid_cutoff_too_high() {
design_fir_filter(0.6, 31, WindowType::Hamming);
}
#[test]
#[should_panic(expected = "Number of taps must be greater than 0")]
fn test_design_fir_zero_taps() {
design_fir_filter(0.125, 0, WindowType::Hamming);
}
#[test]
fn test_compute_window_f64_matches_f32() {
for &wtype in &[WindowType::Hamming, WindowType::Blackman, WindowType::Hann] {
for n in 0..31 {
let f32_val = compute_window(n, 31, wtype);
let f64_val = compute_window_f64(n, 31, wtype);
assert_relative_eq!(f32_val as f64, f64_val, epsilon = 1e-6);
}
}
}
#[test]
fn test_sinc_at_zero() {
assert_relative_eq!(sinc(0.0), 1.0, epsilon = 1e-12);
}
#[test]
fn test_sinc_at_integers() {
for n in 1..10 {
assert_relative_eq!(sinc(n as f64), 0.0, epsilon = 1e-12);
assert_relative_eq!(sinc(-(n as f64)), 0.0, epsilon = 1e-12);
}
}
#[test]
fn test_sinc_symmetry() {
for &x in &[0.1, 0.5, 1.5, 3.7] {
assert_relative_eq!(sinc(x), sinc(-x), epsilon = 1e-12);
}
}
}