//! Analysis windows for the STFT. //! //! A raw rectangular frame leaks spectral energy across bins (the abrupt edges //! look like discontinuities to the FFT). A Hann window tapers each frame to //! zero at its edges, trading a little main-lobe width for much lower side-lobe //! leakage — the standard choice for a spectrogram. use std::f64::consts::PI; /// Returns a length-`n` periodic Hann window, `w[i] = 0.5·(1 - cos(2πi/n))`. /// /// The *periodic* form (denominator `n`, not `n-1`) is used because STFT frames /// tile the signal; it gives perfect overlap-add reconstruction at 50% hop. pub fn hann(n: usize) -> Vec { if n <= 1 { return vec![1.0; n]; } (0..n) .map(|i| (0.5 - 0.5 * (2.0 * PI * i as f64 / n as f64).cos()) as f32) .collect() } /// Applies `window` to `frame` element-wise into a new buffer. Lengths must match. pub fn apply(frame: &[f32], window: &[f32]) -> Vec { debug_assert_eq!(frame.len(), window.len()); frame.iter().zip(window).map(|(&s, &w)| s * w).collect() } #[cfg(test)] mod tests { use super::*; #[test] fn hann_endpoints_are_zero_and_center_is_one() { let w = hann(8); assert!(w[0].abs() < 1e-6, "first sample tapers to ~0, got {}", w[0]); // Periodic Hann peaks at the midpoint n/2. assert!((w[4] - 1.0).abs() < 1e-6, "center peaks at 1, got {}", w[4]); } #[test] fn hann_is_symmetric_about_center() { let w = hann(16); // Periodic window is symmetric across indices 1..n-1. for i in 1..8 { assert!((w[i] - w[16 - i]).abs() < 1e-6); } } #[test] fn apply_scales_samples() { let out = apply(&[2.0, 2.0], &[0.5, 0.25]); assert_eq!(out, vec![1.0, 0.5]); } }