Skip to main content

embedded_dsp/
window.rs

1//! Window functions (Hanning, Hamming, Blackman, Blackman-Harris, Bartlett, Welch, Flat-top window generators).
2
3#[allow(unused_imports)]
4use crate::math::FloatMath;
5use crate::types::q15;
6
7#[inline]
8fn q15_from_unit(v: f32) -> q15 {
9    (v * 32767.0).clamp(-32768.0, 32767.0) as q15
10}
11
12/// Generate Hanning window of length `n`.
13pub fn hanning_f32(dst: &mut [f32]) {
14    let n = dst.len();
15    if n == 0 {
16        return;
17    }
18    if n == 1 {
19        dst[0] = 1.0;
20        return;
21    }
22    let factor = 2.0 * core::f32::consts::PI / ((n - 1) as f32);
23    for i in 0..n {
24        dst[i] = 0.5 * (1.0 - ((i as f32) * factor).cos());
25    }
26}
27
28/// Generate Hamming window of length `n`.
29pub fn hamming_f32(dst: &mut [f32]) {
30    let n = dst.len();
31    if n == 0 {
32        return;
33    }
34    if n == 1 {
35        dst[0] = 1.0;
36        return;
37    }
38    let factor = 2.0 * core::f32::consts::PI / ((n - 1) as f32);
39    for i in 0..n {
40        dst[i] = 0.54 - 0.46 * ((i as f32) * factor).cos();
41    }
42}
43
44/// Generate Blackman window of length `n`.
45pub fn blackman_f32(dst: &mut [f32]) {
46    let n = dst.len();
47    if n == 0 {
48        return;
49    }
50    if n == 1 {
51        dst[0] = 1.0;
52        return;
53    }
54    let factor = 2.0 * core::f32::consts::PI / ((n - 1) as f32);
55    for i in 0..n {
56        let a = (i as f32) * factor;
57        dst[i] = 0.42 - 0.5 * a.cos() + 0.08 * (2.0 * a).cos();
58    }
59}
60
61/// Generate 4-term Blackman-Harris window of length `n` (>92 dB sidelobe rejection).
62pub fn blackman_harris_f32(dst: &mut [f32]) {
63    let n = dst.len();
64    if n == 0 {
65        return;
66    }
67    if n == 1 {
68        dst[0] = 1.0;
69        return;
70    }
71    let factor = 2.0 * core::f32::consts::PI / ((n - 1) as f32);
72    for i in 0..n {
73        let a = (i as f32) * factor;
74        dst[i] =
75            0.35875 - 0.48829 * a.cos() + 0.14128 * (2.0 * a).cos() - 0.01168 * (3.0 * a).cos();
76    }
77}
78
79/// Generate Bartlett (Triangular) window of length `n`.
80pub fn bartlett_f32(dst: &mut [f32]) {
81    let n = dst.len();
82    if n == 0 {
83        return;
84    }
85    if n == 1 {
86        dst[0] = 1.0;
87        return;
88    }
89    let half = ((n - 1) as f32) / 2.0;
90    for i in 0..n {
91        dst[i] = 1.0 - ((i as f32 - half) / half).abs();
92    }
93}
94
95/// Generate Welch parabolic window of length `n`.
96pub fn welch_f32(dst: &mut [f32]) {
97    let n = dst.len();
98    if n == 0 {
99        return;
100    }
101    if n == 1 {
102        dst[0] = 1.0;
103        return;
104    }
105    let half = ((n - 1) as f32) / 2.0;
106    for i in 0..n {
107        let term = (i as f32 - half) / half;
108        dst[i] = 1.0 - term * term;
109    }
110}
111
112/// Generate Flat-top window of length `n`.
113pub fn flattop_f32(dst: &mut [f32]) {
114    let n = dst.len();
115    if n == 0 {
116        return;
117    }
118    if n == 1 {
119        dst[0] = 1.0;
120        return;
121    }
122    let factor = 2.0 * core::f32::consts::PI / ((n - 1) as f32);
123    for i in 0..n {
124        let a = (i as f32) * factor;
125        dst[i] = 0.21557895 - 0.41663158 * a.cos() + 0.277_263_16 * (2.0 * a).cos()
126            - 0.08357895 * (3.0 * a).cos()
127            + 0.006947368 * (4.0 * a).cos();
128    }
129}
130
131/// Zero-order modified Bessel function of the first kind $I_0(x)$ for Kaiser windowing.
132#[inline]
133pub fn bessel_i0(x: f32) -> f32 {
134    let mut sum = 1.0f32;
135    let mut term = 1.0f32;
136    let half_x = 0.5 * x;
137    for k in 1..=16 {
138        term *= (half_x / k as f32) * (half_x / k as f32);
139        sum += term;
140        if term < 1e-7 * sum {
141            break;
142        }
143    }
144    sum
145}
146
147/// Generate Kaiser-Bessel window with shape parameter `beta`.
148///
149/// `beta = 0.0` yields rectangular window.
150/// `beta = 5.0` approximates Hamming window.
151/// `beta = 6.0` approximates Hanning window.
152/// `beta = 8.6` approximates Blackman window.
153pub fn kaiser_f32(dst: &mut [f32], beta: f32) {
154    let n = dst.len();
155    if n == 0 {
156        return;
157    }
158    if n == 1 {
159        dst[0] = 1.0;
160        return;
161    }
162    let den = bessel_i0(beta);
163    let n_minus_1 = (n - 1) as f32;
164    for (i, val) in dst.iter_mut().enumerate() {
165        let frac = (2.0 * i as f32 / n_minus_1) - 1.0;
166        let arg = (1.0 - frac * frac).max(0.0).sqrt();
167        *val = bessel_i0(beta * arg) / den;
168    }
169}
170
171/// Multiply signal elements in-place by window array.
172pub fn apply_window_f32(signal: &mut [f32], window: &[f32]) {
173    let len = signal.len().min(window.len());
174    for i in 0..len {
175        signal[i] *= window[i];
176    }
177}
178
179fn fill_window_q15(dst: &mut [q15], fill: impl Fn(usize, usize) -> f32) {
180    let n = dst.len();
181    if n == 0 {
182        return;
183    }
184    if n == 1 {
185        dst[0] = 32767;
186        return;
187    }
188    for i in 0..n {
189        dst[i] = q15_from_unit(fill(i, n));
190    }
191}
192
193/// Q15 Hanning window.
194pub fn hanning_q15(dst: &mut [q15]) {
195    fill_window_q15(dst, |i, n| {
196        let factor = 2.0 * core::f32::consts::PI / ((n - 1) as f32);
197        0.5 * (1.0 - ((i as f32) * factor).cos())
198    });
199}
200
201/// Q15 Hamming window.
202pub fn hamming_q15(dst: &mut [q15]) {
203    fill_window_q15(dst, |i, n| {
204        let factor = 2.0 * core::f32::consts::PI / ((n - 1) as f32);
205        0.54 - 0.46 * ((i as f32) * factor).cos()
206    });
207}
208
209/// Q15 Blackman window.
210pub fn blackman_q15(dst: &mut [q15]) {
211    fill_window_q15(dst, |i, n| {
212        let factor = 2.0 * core::f32::consts::PI / ((n - 1) as f32);
213        let a = (i as f32) * factor;
214        0.42 - 0.5 * a.cos() + 0.08 * (2.0 * a).cos()
215    });
216}
217
218/// Q15 Bartlett (triangular) window.
219pub fn bartlett_q15(dst: &mut [q15]) {
220    fill_window_q15(dst, |i, n| {
221        let half = (n - 1) as f32 / 2.0;
222        1.0 - ((i as f32 - half) / half).abs()
223    });
224}
225
226/// Multiply Q15 signal in-place by a Q15 window (`>> 15`).
227pub fn apply_window_q15(signal: &mut [q15], window: &[q15]) {
228    let len = signal.len().min(window.len());
229    for i in 0..len {
230        signal[i] = (((signal[i] as i32) * (window[i] as i32)) >> 15) as q15;
231    }
232}