scirs2_fft/fft/windowing.rs
1//! Window functions for FFT operations
2//!
3//! This module provides windowing functions that can be applied to signals before
4//! applying FFT to reduce spectral leakage.
5//!
6//! Common window functions include:
7//! - Hann window
8//! - Hamming window
9//! - Blackman window
10//! - Kaiser window
11//! - Tukey window
12
13use crate::error::{FFTError, FFTResult};
14use std::f64::consts::PI;
15
16/// Different types of window functions
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum WindowType {
19 /// Rectangular window (no windowing)
20 Rectangular,
21 /// Hann window (raised cosine)
22 Hann,
23 /// Hamming window (raised cosine with non-zero endpoints)
24 Hamming,
25 /// Blackman window (three-term cosine)
26 Blackman,
27 /// Blackman-Harris window (four-term cosine)
28 BlackmanHarris,
29 /// Flat-top window (optimized for amplitude accuracy)
30 FlatTop,
31 /// Bartlett window (triangular)
32 Bartlett,
33 /// Bartlett-Hann window (combination of Bartlett and Hann)
34 BartlettHann,
35 /// Tukey window (tapered cosine)
36 Tukey(f64), // parameter is alpha
37 /// Kaiser window (based on Bessel function)
38 Kaiser(f64), // parameter is beta
39 /// Gaussian window
40 Gaussian(f64), // parameter is sigma
41}
42
43/// Creates a window function of the specified type and length
44///
45/// # Arguments
46///
47/// * `window_type` - The type of window to create
48/// * `length` - The length of the window
49///
50/// # Returns
51///
52/// A vector containing the window values
53///
54/// # Examples
55///
56/// ```
57/// use scirs2_fft::fft::windowing::{create_window, WindowType};
58///
59/// // Create a Hann window of length 10
60/// let window = create_window(WindowType::Hann, 10).unwrap();
61/// assert_eq!(window.len(), 10);
62/// assert!(window[0] < 0.01); // Near zero at the edges
63/// assert!(window[5] > 0.9); // Near one in the middle
64/// ```
65#[allow(dead_code)]
66pub fn create_window(windowtype: WindowType, length: usize) -> FFTResult<Vec<f64>> {
67 if length == 0 {
68 return Err(FFTError::ValueError("Window length cannot be zero".into()));
69 }
70
71 let mut window = vec![0.0; length];
72 let n = length as f64;
73
74 match windowtype {
75 WindowType::Rectangular => {
76 // Rectangular window (all ones)
77 window.iter_mut().for_each(|w| *w = 1.0);
78 }
79 WindowType::Hann => {
80 // Hann window (raised cosine)
81 for (i, w) in window.iter_mut().enumerate() {
82 let x = i as f64 / (n - 1.0);
83 *w = 0.5 * (1.0 - (2.0 * PI * x).cos());
84 }
85 }
86 WindowType::Hamming => {
87 // Hamming window (raised cosine with non-zero endpoints)
88 for (i, w) in window.iter_mut().enumerate() {
89 let x = i as f64 / (n - 1.0);
90 *w = 0.54 - 0.46 * (2.0 * PI * x).cos();
91 }
92 }
93 WindowType::Blackman => {
94 // Blackman window (three-term cosine)
95 for (i, w) in window.iter_mut().enumerate() {
96 let x = i as f64 / (n - 1.0);
97 *w = 0.42 - 0.5 * (2.0 * PI * x).cos() + 0.08 * (4.0 * PI * x).cos();
98 }
99 }
100 WindowType::BlackmanHarris => {
101 // Blackman-Harris window (four-term cosine)
102 for (i, w) in window.iter_mut().enumerate() {
103 let x = i as f64 / (n - 1.0);
104 *w = 0.35875 - 0.48829 * (2.0 * PI * x).cos() + 0.14128 * (4.0 * PI * x).cos()
105 - 0.01168 * (6.0 * PI * x).cos();
106 }
107 }
108 WindowType::FlatTop => {
109 // Flat-top window (good amplitude accuracy)
110 for (i, w) in window.iter_mut().enumerate() {
111 let x = i as f64 / (n - 1.0);
112 *w = 0.21557895 - 0.41663158 * (2.0 * PI * x).cos()
113 + 0.277263158 * (4.0 * PI * x).cos()
114 - 0.083578947 * (6.0 * PI * x).cos()
115 + 0.006947368 * (8.0 * PI * x).cos();
116 }
117 }
118 WindowType::Bartlett => {
119 // Bartlett window (triangular)
120 for (i, w) in window.iter_mut().enumerate() {
121 let x = i as f64 / (n - 1.0);
122 *w = 1.0 - (2.0 * x - 1.0).abs();
123 }
124 }
125 WindowType::BartlettHann => {
126 // Bartlett-Hann window
127 for (i, w) in window.iter_mut().enumerate() {
128 let x = i as f64 / (n - 1.0);
129 *w = 0.62 - 0.48 * (x - 0.5).abs() - 0.38 * (2.0 * PI * x).cos();
130 }
131 }
132 WindowType::Tukey(alpha) => {
133 // Tukey window (tapered cosine)
134 if alpha <= 0.0 {
135 // If alpha <= 0, it's a rectangular window
136 window.iter_mut().for_each(|w| *w = 1.0);
137 } else if alpha >= 1.0 {
138 // If alpha >= 1, it's a Hann window
139 for (i, w) in window.iter_mut().enumerate() {
140 let x = i as f64 / (n - 1.0);
141 *w = 0.5 * (1.0 - (2.0 * PI * x).cos());
142 }
143 } else {
144 // Otherwise, it's a true Tukey window
145 let alpha_n = alpha * (n - 1.0) / 2.0;
146 for (i, w) in window.iter_mut().enumerate() {
147 let x = i as f64;
148 if x < alpha_n {
149 *w = 0.5 * (1.0 - (PI * x / alpha_n).cos());
150 } else if x <= (n - 1.0) - alpha_n {
151 *w = 1.0;
152 } else {
153 *w = 0.5 * (1.0 - (PI * (x - (n - 1.0) + alpha_n) / alpha_n).cos());
154 }
155 }
156 }
157 }
158 WindowType::Kaiser(beta) => {
159 // Kaiser window
160 let bessel_i0 = |x: f64| -> f64 {
161 // Approximate the modified Bessel function of order 0
162 let mut sum = 1.0;
163 let mut term = 1.0;
164 for k in 1..20 {
165 let k_f = k as f64;
166 term *= (x / 2.0).powi(2) / (k_f * k_f);
167 sum += term;
168 if term < 1e-12 * sum {
169 break;
170 }
171 }
172 sum
173 };
174
175 let beta_i0 = bessel_i0(beta);
176 for (i, w) in window.iter_mut().enumerate() {
177 let x = 2.0 * i as f64 / (n - 1.0) - 1.0;
178 let arg = beta * (1.0 - x * x).sqrt();
179 *w = bessel_i0(arg) / beta_i0;
180 }
181 }
182 WindowType::Gaussian(sigma) => {
183 // Gaussian window
184 let center = (n - 1.0) / 2.0;
185 for (i, w) in window.iter_mut().enumerate() {
186 let x = i as f64 - center;
187 *w = (-0.5 * (x / (sigma * center)).powi(2)).exp();
188 }
189 }
190 }
191
192 Ok(window)
193}
194
195/// Apply a window function to a signal
196///
197/// # Arguments
198///
199/// * `signal` - The signal to apply the window to
200/// * `window` - The window function to apply
201///
202/// # Returns
203///
204/// A vector containing the windowed signal
205///
206/// # Examples
207///
208/// ```
209/// use scirs2_fft::fft::windowing::{apply_window, create_window, WindowType};
210///
211/// // Create a simple signal
212/// let signal = vec![1.0, 2.0, 3.0, 4.0, 3.0, 2.0, 1.0];
213///
214/// // Create a Hann window
215/// let window = create_window(WindowType::Hann, signal.len()).unwrap();
216///
217/// // Apply the window to the signal
218/// let windowed_signal = apply_window(&signal, &window).unwrap();
219///
220/// // Check that the window was applied correctly
221/// assert_eq!(windowed_signal.len(), signal.len());
222/// assert!(windowed_signal[0] < signal[0]); // Edges are attenuated
223/// assert!(windowed_signal[3] <= signal[3]); // Middle is preserved or slightly attenuated
224/// ```
225#[allow(dead_code)]
226pub fn apply_window(signal: &[f64], window: &[f64]) -> FFTResult<Vec<f64>> {
227 if signal.len() != window.len() {
228 return Err(FFTError::ValueError(
229 "Signal and window lengths must match".into(),
230 ));
231 }
232
233 let mut windowed_signal = vec![0.0; signal.len()];
234 for (i, w) in windowed_signal.iter_mut().enumerate() {
235 *w = signal[i] * window[i];
236 }
237
238 Ok(windowed_signal)
239}
240
241/// Calculate window properties like the equivalent noise bandwidth
242///
243/// # Arguments
244///
245/// * `window` - The window function
246///
247/// # Returns
248///
249/// A struct containing window properties
250#[allow(dead_code)]
251pub fn window_properties(window: &[f64]) -> WindowProperties {
252 let n = window.len();
253 let mut sum = 0.0;
254 let mut sum_squared = 0.0;
255 let mut coherent_gain = 0.0;
256
257 for &w in window {
258 sum += w;
259 sum_squared += w * w;
260 coherent_gain += w;
261 }
262
263 coherent_gain /= n as f64;
264 let processing_gain = coherent_gain.powi(2) / (sum_squared / n as f64);
265 let equivalent_noise_bandwidth = n as f64 * sum_squared / (sum * sum);
266
267 WindowProperties {
268 coherent_gain,
269 processing_gain,
270 equivalent_noise_bandwidth,
271 }
272}
273
274/// Properties of a window function
275#[derive(Debug, Clone, Copy)]
276pub struct WindowProperties {
277 /// Coherent gain (mean value of the window)
278 pub coherent_gain: f64,
279 /// Processing gain (improvement in SNR)
280 pub processing_gain: f64,
281 /// Equivalent noise bandwidth
282 pub equivalent_noise_bandwidth: f64,
283}