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}