1use std::f64::consts::PI;
2
3use ndarray::Array1;
4
5#[derive(Debug, Clone, Copy, PartialEq, Default)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub enum WindowFn {
9 #[default]
10 Hann,
11 Hamming,
12 Blackman,
13 Rectangular,
14}
15
16pub fn generate_window(window_fn: WindowFn, size: usize) -> Array1<f64> {
20 window_with_period(window_fn, size, size.saturating_sub(1))
21}
22
23pub fn generate_periodic_window(window_fn: WindowFn, size: usize) -> Array1<f64> {
29 window_with_period(window_fn, size, size)
30}
31
32fn window_with_period(window_fn: WindowFn, size: usize, period: usize) -> Array1<f64> {
35 if size <= 1 {
36 return Array1::ones(size);
37 }
38
39 let n = period as f64;
40 Array1::from_iter((0..size).map(|i| {
41 let x = i as f64;
42 match window_fn {
43 WindowFn::Hann => 0.5 * (1.0 - (2.0 * PI * x / n).cos()),
44 WindowFn::Hamming => 0.54 - 0.46 * (2.0 * PI * x / n).cos(),
45 WindowFn::Blackman => {
46 0.42 - 0.5 * (2.0 * PI * x / n).cos() + 0.08 * (4.0 * PI * x / n).cos()
47 }
48 WindowFn::Rectangular => 1.0,
49 }
50 }))
51}
52
53pub fn apply_hann(data: &mut [f64], start: usize, len: usize) {
58 let start = start.min(data.len());
59 let len = len.min(data.len().saturating_sub(start));
60 if len == 0 {
61 return;
62 }
63
64 data.iter_mut()
65 .skip(start)
66 .take(len)
67 .zip(generate_window(WindowFn::Hann, len))
68 .for_each(|(sample, weight)| *sample *= weight);
69}
70
71pub fn apply_hann_left(data: &mut [f64], start: usize, len: usize) {
73 let start = start.min(data.len());
74 let len = len.min(data.len().saturating_sub(start));
75 if len == 0 {
76 return;
77 }
78
79 data.iter_mut()
80 .skip(start)
81 .take(len)
82 .zip(
83 generate_window(WindowFn::Hann, len * 2)
84 .into_iter()
85 .take(len),
86 )
87 .for_each(|(sample, weight)| *sample *= weight);
88}
89
90pub fn apply_hann_right(data: &mut [f64], start: usize, len: usize) {
92 let start = start.min(data.len());
93 let len = len.min(data.len().saturating_sub(start));
94 if len == 0 {
95 return;
96 }
97
98 data.iter_mut()
99 .skip(start)
100 .take(len)
101 .zip(
102 generate_window(WindowFn::Hann, len * 2)
103 .into_iter()
104 .skip(len),
105 )
106 .for_each(|(sample, weight)| *sample *= weight);
107}
108
109#[cfg(test)]
110mod tests {
111 use approx::assert_abs_diff_eq;
112
113 use super::*;
114
115 #[test]
116 fn apply_hann_matches_expected_full_window_taper() {
117 let mut data = [1.0; 5];
118 let len = data.len();
119
120 apply_hann(&mut data, 0, len);
121
122 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
123 for (sample, expected) in data.into_iter().zip(expected) {
124 assert_abs_diff_eq!(sample, expected, epsilon = 1e-12);
125 }
126 }
127
128 #[test]
129 fn apply_hann_left_matches_rising_half_window_taper() {
130 let mut data = [1.0; 4];
131 let len = data.len();
132
133 apply_hann_left(&mut data, 0, len);
134
135 let expected = generate_window(WindowFn::Hann, 8);
136 for (sample, expected) in data.into_iter().zip(expected.into_iter().take(4)) {
137 assert_abs_diff_eq!(sample, expected, epsilon = 1e-12);
138 }
139 }
140
141 #[test]
142 fn apply_hann_right_matches_falling_half_window_taper() {
143 let mut data = [1.0; 4];
144 let len = data.len();
145
146 apply_hann_right(&mut data, 0, len);
147
148 let expected = generate_window(WindowFn::Hann, 8);
149 for (sample, expected) in data.into_iter().zip(expected.into_iter().skip(4)) {
150 assert_abs_diff_eq!(sample, expected, epsilon = 1e-12);
151 }
152 }
153
154 #[test]
155 fn apply_hann_matches_generated_window_for_subslice() {
156 let mut data = [1.0; 8];
157 apply_hann(&mut data, 2, 4);
158
159 let expected = generate_window(WindowFn::Hann, 4);
160 assert_abs_diff_eq!(data[0], 1.0, epsilon = 1e-12);
161 assert_abs_diff_eq!(data[1], 1.0, epsilon = 1e-12);
162 assert_abs_diff_eq!(data[2], expected[0], epsilon = 1e-12);
163 assert_abs_diff_eq!(data[3], expected[1], epsilon = 1e-12);
164 assert_abs_diff_eq!(data[4], expected[2], epsilon = 1e-12);
165 assert_abs_diff_eq!(data[5], expected[3], epsilon = 1e-12);
166 assert_abs_diff_eq!(data[6], 1.0, epsilon = 1e-12);
167 assert_abs_diff_eq!(data[7], 1.0, epsilon = 1e-12);
168 }
169
170 #[test]
171 fn periodic_window_is_symmetric_window_one_sample_longer_without_the_last() {
172 for window_fn in [
173 WindowFn::Hann,
174 WindowFn::Hamming,
175 WindowFn::Blackman,
176 WindowFn::Rectangular,
177 ] {
178 for size in [2, 3, 8, 9, 64] {
179 let periodic = generate_periodic_window(window_fn, size);
180 let symmetric = generate_window(window_fn, size + 1);
181 for (p, s) in periodic.iter().zip(symmetric.iter()) {
182 assert_abs_diff_eq!(*p, *s, epsilon = 1e-12);
183 }
184 }
185 }
186 assert_eq!(
187 generate_periodic_window(WindowFn::Hann, 1),
188 Array1::<f64>::ones(1)
189 );
190 assert_eq!(
191 generate_periodic_window(WindowFn::Hann, 0),
192 Array1::<f64>::ones(0)
193 );
194 }
195
196 #[test]
197 fn periodic_hann_sums_to_a_constant_at_50_and_75_percent_overlap() {
198 let size = 16;
199 let window = generate_periodic_window(WindowFn::Hann, size);
200 for hop in [size / 2, size / 4] {
201 let expected = size as f64 / (2 * hop) as f64;
202 for offset in 0..hop {
203 let sum: f64 = window.iter().skip(offset).step_by(hop).sum();
204 assert_abs_diff_eq!(sum, expected, epsilon = 1e-12);
205 }
206 }
207 }
208
209 #[test]
210 fn periodic_windows_are_never_all_zero() {
211 for window_fn in [WindowFn::Hann, WindowFn::Blackman] {
213 assert!(
214 generate_window(window_fn, 2)
215 .iter()
216 .all(|w| w.abs() < 1e-12)
217 );
218 assert!(generate_periodic_window(window_fn, 2).sum() > 0.4);
219 }
220 }
221}