1use scirs2_core::ndarray::{Array1, Array2};
7use std::f64::consts::PI;
8
9use crate::error::{FFTError, FFTResult};
10use crate::fft::fft;
11use crate::helper::fftfreq;
12
13#[derive(Debug, Clone, PartialEq)]
15pub enum ExtendedWindow {
16 Chebyshev { attenuation_db: f64 },
18 Slepian { width: f64 },
20 Lanczos,
22 PlanckTaper { epsilon: f64 },
24 DolphChebyshev { attenuation_db: f64 },
26 Poisson { alpha: f64 },
28 HannPoisson { alpha: f64 },
30 Cauchy { alpha: f64 },
32 Advancedspherical { mu: f64, x0: f64 },
34 Taylor {
36 n_sidelobes: usize,
37 sidelobe_level_db: f64,
38 },
39}
40
41#[allow(dead_code)]
43pub fn get_extended_window(window: ExtendedWindow, n: usize) -> FFTResult<Array1<f64>> {
44 let mut w = Array1::zeros(n);
45
46 match window {
47 ExtendedWindow::Chebyshev { attenuation_db } => {
48 generate_chebyshev_window(&mut w, attenuation_db)?;
49 }
50 ExtendedWindow::Slepian { width } => {
51 generate_slepian_window(&mut w, width)?;
52 }
53 ExtendedWindow::Lanczos => {
54 generate_lanczos_window(&mut w);
55 }
56 ExtendedWindow::PlanckTaper { epsilon } => {
57 generate_planck_taper_window(&mut w, epsilon)?;
58 }
59 ExtendedWindow::DolphChebyshev { attenuation_db } => {
60 generate_dolph_chebyshev_window(&mut w, attenuation_db)?;
61 }
62 ExtendedWindow::Poisson { alpha } => {
63 generate_poisson_window(&mut w, alpha);
64 }
65 ExtendedWindow::HannPoisson { alpha } => {
66 generate_hann_poisson_window(&mut w, alpha);
67 }
68 ExtendedWindow::Cauchy { alpha } => {
69 generate_cauchy_window(&mut w, alpha);
70 }
71 ExtendedWindow::Advancedspherical { mu, x0 } => {
72 generate_advancedspherical_window(&mut w, mu, x0)?;
73 }
74 ExtendedWindow::Taylor {
75 n_sidelobes,
76 sidelobe_level_db,
77 } => {
78 generate_taylor_window(&mut w, n_sidelobes, sidelobe_level_db)?;
79 }
80 }
81
82 Ok(w)
83}
84
85#[allow(dead_code)]
87fn generate_chebyshev_window(w: &mut Array1<f64>, attenuationdb: f64) -> FFTResult<()> {
88 let n = w.len();
89 if attenuationdb <= 0.0 {
90 return Err(FFTError::ValueError(
91 "Attenuation must be positive".to_string(),
92 ));
93 }
94
95 let r = 10.0_f64.powf(attenuationdb / 20.0);
97 let beta = (r + (r * r - 1.0).sqrt()).ln() / n as f64;
98
99 for i in 0..n {
100 let x = 2.0 * i as f64 / (n - 1) as f64 - 1.0;
101 w[i] = ((n as f64 - 1.0) * beta * (1.0 - x * x).sqrt().acos()).cosh() / r.cosh();
102 }
103
104 Ok(())
105}
106
107#[allow(dead_code)]
109fn generate_slepian_window(w: &mut Array1<f64>, width: f64) -> FFTResult<()> {
110 let n = w.len();
111 if width <= 0.0 || width >= 0.5 {
112 return Err(FFTError::ValueError(
113 "Width must be between 0 and 0.5".to_string(),
114 ));
115 }
116
117 for i in 0..n {
119 let t = 2.0 * i as f64 / (n - 1) as f64 - 1.0;
120 w[i] = (PI * width * n as f64 * t).sin() / (PI * t);
121 if t == 0.0 {
122 w[i] = width * n as f64;
123 }
124 }
125
126 let sum: f64 = w.sum();
128 w.mapv_inplace(|x| x / sum * n as f64);
129
130 Ok(())
131}
132
133#[allow(dead_code)]
135fn generate_lanczos_window(w: &mut Array1<f64>) {
136 let n = w.len();
137 for i in 0..n {
138 let x = 2.0 * i as f64 / (n - 1) as f64 - 1.0;
139 w[i] = if x == 0.0 {
140 1.0
141 } else {
142 (PI * x).sin() / (PI * x)
143 };
144 }
145}
146
147#[allow(dead_code)]
149fn generate_planck_taper_window(w: &mut Array1<f64>, epsilon: f64) -> FFTResult<()> {
150 let n = w.len();
151 if epsilon <= 0.0 || epsilon >= 0.5 {
152 return Err(FFTError::ValueError(
153 "Epsilon must be between 0 and 0.5".to_string(),
154 ));
155 }
156
157 for i in 0..n {
158 let t = i as f64 / (n - 1) as f64;
159 w[i] = if t < epsilon {
160 1.0 / ((-epsilon / (t * (epsilon - t))).exp() + 1.0)
161 } else if t > 1.0 - epsilon {
162 1.0 / ((-epsilon / ((1.0 - t) * (t - 1.0 + epsilon))).exp() + 1.0)
163 } else {
164 1.0
165 };
166 }
167
168 Ok(())
169}
170
171#[allow(dead_code)]
173fn generate_dolph_chebyshev_window(w: &mut Array1<f64>, attenuationdb: f64) -> FFTResult<()> {
174 generate_chebyshev_window(w, attenuationdb)?;
176
177 let sum: f64 = w.sum();
179 let n = w.len() as f64;
180 w.mapv_inplace(|x| x / sum * n);
181
182 Ok(())
183}
184
185#[allow(dead_code)]
187fn generate_poisson_window(w: &mut Array1<f64>, alpha: f64) {
188 let n = w.len();
189 let half_n = n as f64 / 2.0;
190
191 for i in 0..n {
192 let t = (i as f64 - half_n).abs() / half_n;
193 w[i] = (-alpha * t).exp();
194 }
195}
196
197#[allow(dead_code)]
199fn generate_hann_poisson_window(w: &mut Array1<f64>, alpha: f64) {
200 let n = w.len();
201
202 for i in 0..n {
203 let hann_part = 0.5 * (1.0 - (2.0 * PI * i as f64 / (n - 1) as f64).cos());
204 let poisson_part = (-alpha * (n as f64 / 2.0 - i as f64).abs() / (n as f64 / 2.0)).exp();
205 w[i] = hann_part * poisson_part;
206 }
207}
208
209#[allow(dead_code)]
211fn generate_cauchy_window(w: &mut Array1<f64>, alpha: f64) {
212 let n = w.len();
213 let center = (n - 1) as f64 / 2.0;
214
215 for i in 0..n {
216 let t = (i as f64 - center) / center;
217 w[i] = 1.0 / (1.0 + (alpha * t).powi(2));
218 }
219}
220
221#[allow(dead_code)]
223fn generate_advancedspherical_window(w: &mut Array1<f64>, mu: f64, x0: f64) -> FFTResult<()> {
224 let n = w.len();
225 if x0 <= 0.0 || x0 >= 1.0 {
226 return Err(FFTError::ValueError(
227 "x0 must be between 0 and 1".to_string(),
228 ));
229 }
230
231 for i in 0..n {
233 let x = 2.0 * i as f64 / (n - 1) as f64 - 1.0;
234 if x.abs() < x0 {
235 w[i] = (1.0 - (x / x0).powi(2)).powf(mu);
236 } else {
237 w[i] = 0.0;
238 }
239 }
240
241 Ok(())
242}
243
244#[allow(dead_code)]
246fn generate_taylor_window(
247 w: &mut Array1<f64>,
248 n_sidelobes: usize,
249 sidelobe_level_db: f64,
250) -> FFTResult<()> {
251 let n = w.len();
252 if n_sidelobes == 0 {
253 return Err(FFTError::ValueError(
254 "Number of _sidelobes must be positive".to_string(),
255 ));
256 }
257
258 let a = sidelobe_level_db.abs() / 20.0;
260 let r = 10.0_f64.powf(a);
261
262 for i in 0..n {
263 let mut sum = 0.0;
264 for k in 1..=n_sidelobes {
265 let x = 2.0 * PI * k as f64 * (i as f64 / (n - 1) as f64 - 0.5);
266 sum += (-1.0_f64).powi(k as i32 + 1) * x.cos() / k as f64;
267 }
268 w[i] = 1.0 + 2.0 * r * sum;
269 }
270
271 let max_val = w.iter().cloned().fold(0.0, f64::max);
273 w.mapv_inplace(|x| x / max_val);
274
275 Ok(())
276}
277
278#[derive(Debug, Clone)]
280pub struct WindowProperties {
281 pub main_lobe_width: f64,
283 pub sidelobe_level_db: f64,
285 pub coherent_gain: f64,
287 pub processing_gain: f64,
289 pub enbw: f64,
291 pub scalloping_loss_db: f64,
293 pub worst_case_loss_db: f64,
295}
296
297#[allow(dead_code)]
299pub fn analyze_window(
300 window: &Array1<f64>,
301 sample_rate: Option<f64>,
302) -> FFTResult<WindowProperties> {
303 let n = window.len();
304 let fs = sample_rate.unwrap_or(1.0);
305
306 let coherent_gain = window.sum() / n as f64;
308
309 let sum_squared: f64 = window.mapv(|x| x * x).sum();
311 let processing_gain = window.sum().powi(2) / (n as f64 * sum_squared);
312
313 let enbw = n as f64 * sum_squared / window.sum().powi(2);
315
316 let n_fft = 8 * n; let mut padded = Array1::zeros(n_fft);
319 for i in 0..n {
320 padded[i] = window[i];
321 }
322
323 let fft_result = fft(
324 &padded
325 .mapv(|x| scirs2_core::numeric::Complex64::new(x, 0.0))
326 .to_vec(),
327 None,
328 )?;
329 let magnitude: Vec<f64> = fft_result.iter().map(|c| c.norm()).collect();
330
331 let max_mag = magnitude.iter().cloned().fold(0.0, f64::max);
333 let mag_db: Vec<f64> = magnitude
334 .iter()
335 .map(|&m| 20.0 * (m / max_mag).log10())
336 .collect();
337
338 let mut main_lobe_width = 0.0;
340 for (i, &val) in mag_db.iter().enumerate().take(n_fft / 2).skip(1) {
341 if val < -3.0 {
342 main_lobe_width = 2.0 * i as f64 * fs / n_fft as f64;
343 break;
344 }
345 }
346
347 let mut sidelobe_level_db = -1000.0;
349 let main_lobe_end = (main_lobe_width * n_fft as f64 / fs).ceil() as usize;
350 for &val in mag_db.iter().take(n_fft / 2).skip(main_lobe_end) {
351 if val > sidelobe_level_db {
352 sidelobe_level_db = val;
353 }
354 }
355
356 let bin_edge_response = magnitude[n_fft / (2 * n)];
358 let scalloping_loss_db = -20.0 * (bin_edge_response / max_mag).log10();
359
360 let worst_case_loss_db = scalloping_loss_db + 10.0 * processing_gain.log10();
362
363 Ok(WindowProperties {
364 main_lobe_width,
365 sidelobe_level_db,
366 coherent_gain,
367 processing_gain,
368 enbw,
369 scalloping_loss_db,
370 worst_case_loss_db,
371 })
372}
373
374#[allow(dead_code)]
376pub fn visualize_window(
377 window: &Array1<f64>,
378) -> FFTResult<(Array1<f64>, Array1<f64>, Array2<f64>)> {
379 let n = window.len();
380
381 let time = Array1::range(0.0, n as f64, 1.0);
383
384 let n_fft = 8 * n;
386 let mut padded = vec![scirs2_core::numeric::Complex64::new(0.0, 0.0); n_fft];
387 for i in 0..n {
388 padded[i] = scirs2_core::numeric::Complex64::new(window[i], 0.0);
389 }
390
391 let fft_result = fft(&padded, None).unwrap_or(padded);
392 let freq = fftfreq(n_fft, 1.0)?;
393 let magnitude: Vec<f64> = fft_result.iter().map(|c| c.norm()).collect();
394
395 let max_mag = magnitude.iter().cloned().fold(0.0, f64::max);
397 let mag_db: Vec<f64> = magnitude
398 .iter()
399 .map(|&m| 20.0 * (m / max_mag).max(1e-100).log10())
400 .collect();
401
402 let mut freq_response = Array2::zeros((n_fft, 2));
404 for i in 0..n_fft {
405 freq_response[[i, 0]] = freq[i];
406 freq_response[[i, 1]] = mag_db[i];
407 }
408
409 Ok((time, window.clone(), freq_response))
410}
411
412#[allow(dead_code)]
414pub fn compare_windows(
415 windows: &[(String, Array1<f64>)],
416) -> FFTResult<Vec<(String, WindowProperties)>> {
417 let mut results = Vec::new();
418
419 for (name, window) in windows {
420 let props = analyze_window(window, None)?;
421 results.push((name.clone(), props));
422 }
423
424 Ok(results)
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use crate::Window;
431
432 #[test]
433 fn test_extended_windows() {
434 let n = 64;
435
436 let cheby = get_extended_window(
438 ExtendedWindow::Chebyshev {
439 attenuation_db: 60.0,
440 },
441 n,
442 )
443 .unwrap();
444 assert_eq!(cheby.len(), n);
445
446 let lanczos = get_extended_window(ExtendedWindow::Lanczos, n).unwrap();
448 assert_eq!(lanczos.len(), n);
449
450 let poisson = get_extended_window(ExtendedWindow::Poisson { alpha: 2.0 }, n).unwrap();
452 assert_eq!(poisson.len(), n);
453 }
454
455 #[test]
456 fn test_window_analysis() {
457 let n = 64;
458 let window = crate::window::get_window(Window::Hann, n, true).unwrap();
459
460 let props = analyze_window(&window, Some(1000.0)).unwrap();
461
462 assert!(props.coherent_gain > 0.0);
464 assert!(props.processing_gain > 0.0);
465 assert!(props.enbw > 0.0);
466 assert!(props.sidelobe_level_db < 0.0);
467 }
468
469 #[test]
470 fn test_window_comparison() {
471 let n = 64;
472 let windows = vec![
473 (
474 "Hann".to_string(),
475 crate::window::get_window(Window::Hann, n, true).unwrap(),
476 ),
477 (
478 "Hamming".to_string(),
479 crate::window::get_window(Window::Hamming, n, true).unwrap(),
480 ),
481 ];
482
483 let comparison = compare_windows(&windows).unwrap();
484 assert_eq!(comparison.len(), 2);
485 }
486}