Skip to main content

sound_to_patch/dsp/
window.rs

1//! Analysis windows, in their periodic form.
2
3use std::f64::consts::TAU;
4
5use crate::cast;
6
7/// An analysis window. Every window is periodic: its `N` samples are the
8/// first `N` of a symmetric window of length `N + 1`, as spectral analysis
9/// expects. The symmetric form shifts every bin slightly.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub enum Window {
12    /// Periodic Hann, `w[n] = 0.5 - 0.5 cos(2 pi n / N)`. Main lobe 4 bins
13    /// null to null, -3 dB width 1.44 bins. The default for analysis.
14    Hann,
15    /// Periodic four-term Blackman-Harris, `0.35875 - 0.48829 cos(2 pi n /
16    /// N) + 0.14128 cos(4 pi n / N) - 0.01168 cos(6 pi n / N)`. Lower side
17    /// lobes than Hann, a wider main lobe.
18    BlackmanHarris,
19    /// All ones. Only as a baseline: its frequency estimates are an order of
20    /// magnitude worse than Hann's.
21    Rectangular,
22}
23
24impl Window {
25    /// Returns the `N` window samples.
26    #[must_use]
27    pub fn samples(self, len: usize) -> Vec<f64> {
28        let n = cast::usize_to_f64(len);
29        (0..len)
30            .map(|index| {
31                let phase = TAU * cast::usize_to_f64(index) / n;
32                match self {
33                    Self::Hann => 0.5 - 0.5 * phase.cos(),
34                    Self::BlackmanHarris => {
35                        0.358_75 - 0.488_29 * phase.cos() + 0.141_28 * (2.0 * phase).cos()
36                            - 0.011_68 * (3.0 * phase).cos()
37                    }
38                    Self::Rectangular => 1.0,
39                }
40            })
41            .collect()
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn hann_starts_at_zero_and_peaks_at_the_centre() {
51        let hann = Window::Hann.samples(8);
52        assert_eq!(hann[0], 0.0);
53        assert!((hann[4] - 1.0).abs() < 1e-15);
54        // Periodic: the sample after the last would be the first again.
55        assert!((hann[1] - hann[7]).abs() < 1e-15);
56    }
57
58    #[test]
59    fn blackman_harris_coefficients_sum_to_one_at_the_centre() {
60        let window = Window::BlackmanHarris.samples(16);
61        assert!((window[8] - 1.0).abs() < 1e-12, "{}", window[8]);
62        assert!((window[0] - 6.0e-5).abs() < 1e-12, "{}", window[0]);
63    }
64
65    #[test]
66    fn rectangular_is_all_ones_and_empty_stays_empty() {
67        assert_eq!(Window::Rectangular.samples(3), vec![1.0; 3]);
68        assert_eq!(Window::Hann.samples(0), Vec::<f64>::new());
69    }
70}