1use crate::error::{FFTError, FFTResult};
10use scirs2_core::ndarray::{Array1, ArrayBase, Data, Ix1};
11use scirs2_core::numeric::{Float, FromPrimitive};
12use std::f64::consts::PI;
13use std::fmt::Debug;
14use std::str::FromStr;
15
16#[derive(Debug, Clone, PartialEq)]
18pub enum Window {
19 Rectangular,
21 Hann,
23 Hamming,
25 Blackman,
27 Bartlett,
29 FlatTop,
31 Parzen,
33 Bohman,
35 BlackmanHarris,
37 Nuttall,
39 Barthann,
41 Cosine,
43 Exponential,
45 Tukey(f64),
47 Kaiser(f64),
49 Gaussian(f64),
51 GeneralCosine(Vec<f64>),
53}
54
55impl FromStr for Window {
56 type Err = FFTError;
57
58 fn from_str(s: &str) -> Result<Self, Self::Err> {
59 match s.to_lowercase().as_str() {
60 "rectangular" | "boxcar" | "rect" => Ok(Window::Rectangular),
61 "hann" | "hanning" => Ok(Window::Hann),
62 "hamming" => Ok(Window::Hamming),
63 "blackman" => Ok(Window::Blackman),
64 "bartlett" | "triangular" | "triangle" => Ok(Window::Bartlett),
65 "flattop" | "flat" => Ok(Window::FlatTop),
66 "parzen" => Ok(Window::Parzen),
67 "bohman" => Ok(Window::Bohman),
68 "blackmanharris" | "blackman-harris" => Ok(Window::BlackmanHarris),
69 "nuttall" => Ok(Window::Nuttall),
70 "barthann" => Ok(Window::Barthann),
71 "cosine" | "cos" => Ok(Window::Cosine),
72 "exponential" | "exp" => Ok(Window::Exponential),
73 _ => Err(FFTError::ValueError(format!("Unknown window type: {s}"))),
74 }
75 }
76}
77
78#[allow(dead_code)]
108pub fn get_window<T>(window: T, n: usize, sym: bool) -> FFTResult<Array1<f64>>
109where
110 T: Into<WindowParam>,
111{
112 if n == 0 {
113 return Err(FFTError::ValueError(
114 "Window length must be positive".to_string(),
115 ));
116 }
117
118 let window_param = window.into();
119 let window_type = match window_param {
120 WindowParam::Type(wt) => wt,
121 WindowParam::Name(s) => Window::from_str(&s)?,
122 };
123
124 match window_type {
125 Window::Rectangular => rectangular(n),
126 Window::Hann => hann(n, sym),
127 Window::Hamming => hamming(n, sym),
128 Window::Blackman => blackman(n, sym),
129 Window::Bartlett => bartlett(n, sym),
130 Window::FlatTop => flattop(n, sym),
131 Window::Parzen => parzen(n, sym),
132 Window::Bohman => bohman(n),
133 Window::BlackmanHarris => blackmanharris(n, sym),
134 Window::Nuttall => nuttall(n, sym),
135 Window::Barthann => barthann(n, sym),
136 Window::Cosine => cosine(n, sym),
137 Window::Exponential => exponential(n, sym, 1.0),
138 Window::Tukey(alpha) => tukey(n, sym, alpha),
139 Window::Kaiser(beta) => kaiser(n, sym, beta),
140 Window::Gaussian(std) => gaussian(n, sym, std),
141 Window::GeneralCosine(coeffs) => general_cosine(n, sym, &coeffs),
142 }
143}
144
145#[derive(Debug)]
147pub enum WindowParam {
148 Type(Window),
150 Name(String),
152}
153
154impl From<Window> for WindowParam {
155 fn from(window: Window) -> Self {
156 WindowParam::Type(window)
157 }
158}
159
160impl From<&str> for WindowParam {
161 fn from(s: &str) -> Self {
162 WindowParam::Name(s.to_string())
163 }
164}
165
166impl From<String> for WindowParam {
167 fn from(s: String) -> Self {
168 WindowParam::Name(s)
169 }
170}
171
172#[allow(dead_code)]
176fn rectangular(n: usize) -> FFTResult<Array1<f64>> {
177 Ok(Array1::ones(n))
178}
179
180#[allow(dead_code)]
185fn hann(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
186 general_cosine(n, sym, &[0.5, 0.5])
187}
188
189#[allow(dead_code)]
194fn hamming(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
195 general_cosine(n, sym, &[0.54, 0.46])
196}
197
198#[allow(dead_code)]
203fn blackman(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
204 general_cosine(n, sym, &[0.42, 0.5, 0.08])
205}
206
207#[allow(dead_code)]
211fn bartlett(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
212 if n == 1 {
213 return Ok(Array1::ones(1));
214 }
215
216 let mut n = n;
217 if !sym {
218 n += 1;
219 }
220
221 let mut w = Array1::zeros(n);
222 let range: Vec<f64> = (0..n).map(|i| i as f64).collect();
223
224 for (i, &x) in range.iter().enumerate() {
225 if x < (n as f64) / 2.0 {
226 w[i] = 2.0 * x / (n as f64 - 1.0);
227 } else {
228 w[i] = 2.0 - 2.0 * x / (n as f64 - 1.0);
229 }
230 }
231
232 if !sym {
233 let w_slice = w.slice(scirs2_core::ndarray::s![0..n - 1]).to_owned();
235 Ok(w_slice)
236 } else {
237 Ok(w)
238 }
239}
240
241#[allow(dead_code)]
246fn flattop(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
247 general_cosine(
248 n,
249 sym,
250 &[
251 0.215_578_95,
252 0.416_631_58,
253 0.277_263_158,
254 0.083_578_947,
255 0.006_947_368,
256 ],
257 )
258}
259
260#[allow(dead_code)]
264fn parzen(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
265 if n == 1 {
266 return Ok(Array1::ones(1));
267 }
268
269 let mut n = n;
270 if !sym {
271 n += 1;
272 }
273
274 let mut w = Array1::zeros(n);
275 let half_n = (n as f64) / 2.0;
276
277 for i in 0..n {
278 let x = (i as f64 - half_n + 0.5).abs() / half_n;
279
280 if x <= 0.5 {
281 w[i] = 1.0 - 6.0 * x.powi(2) * (1.0 - x);
282 } else if x <= 1.0 {
283 w[i] = 2.0 * (1.0 - x).powi(3);
284 }
285 }
286
287 if !sym {
288 let w_slice = w.slice(scirs2_core::ndarray::s![0..n - 1]).to_owned();
290 Ok(w_slice)
291 } else {
292 Ok(w)
293 }
294}
295
296#[allow(dead_code)]
300fn bohman(n: usize) -> FFTResult<Array1<f64>> {
301 if n == 1 {
302 return Ok(Array1::ones(1));
303 }
304
305 let mut w = Array1::zeros(n);
306 let half_n = (n as f64 - 1.0) / 2.0;
307
308 for i in 0..n {
309 let x = ((i as f64) - half_n).abs() / half_n;
310 if x <= 1.0 {
311 w[i] = (1.0 - x) * (PI * x).cos() + (PI * x).sin() / PI;
312 }
313 }
314
315 Ok(w)
316}
317
318#[allow(dead_code)]
323fn blackmanharris(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
324 general_cosine(n, sym, &[0.35875, 0.48829, 0.14128, 0.01168])
325}
326
327#[allow(dead_code)]
332fn nuttall(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
333 general_cosine(
334 n,
335 sym,
336 &[0.363_581_9, 0.489_177_5, 0.136_599_5, 0.010_641_1],
337 )
338}
339
340#[allow(dead_code)]
344fn barthann(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
345 if n == 1 {
346 return Ok(Array1::ones(1));
347 }
348
349 let mut n = n;
350 if !sym {
351 n += 1;
352 }
353
354 let mut w = Array1::zeros(n);
355 let fac = 1.0 / (n as f64 - 1.0);
356
357 for i in 0..n {
358 let x = i as f64 * fac;
359 w[i] = 0.62 - 0.48 * (2.0 * x - 1.0).abs() + 0.38 * (2.0 * PI * (2.0 * x - 1.0)).cos();
360 }
361
362 if !sym {
363 let w_slice = w.slice(scirs2_core::ndarray::s![0..n - 1]).to_owned();
365 Ok(w_slice)
366 } else {
367 Ok(w)
368 }
369}
370
371#[allow(dead_code)]
375fn cosine(n: usize, sym: bool) -> FFTResult<Array1<f64>> {
376 if n == 1 {
377 return Ok(Array1::ones(1));
378 }
379
380 let mut w = Array1::zeros(n);
381
382 let range: Vec<f64> = if sym {
383 (0..n).map(|i| i as f64 / (n as f64 - 1.0)).collect()
384 } else {
385 (0..n).map(|i| i as f64 / n as f64).collect()
386 };
387
388 for (i, &x) in range.iter().enumerate() {
389 w[i] = (PI * x).sin();
390 }
391
392 Ok(w)
393}
394
395#[allow(dead_code)]
405fn exponential(n: usize, sym: bool, tau: f64) -> FFTResult<Array1<f64>> {
406 if tau <= 0.0 {
407 return Err(FFTError::ValueError("tau must be positive".to_string()));
408 }
409
410 if n == 1 {
411 return Ok(Array1::ones(1));
412 }
413
414 let center = if sym { (n as f64 - 1.0) / 2.0 } else { 0.0 };
415
416 let mut w = Array1::zeros(n);
417
418 for i in 0..n {
419 let x = (i as f64 - center).abs() / (tau * (n as f64));
420 w[i] = (-x).exp();
421 }
422
423 Ok(w)
424}
425
426#[allow(dead_code)]
438fn tukey(n: usize, sym: bool, alpha: f64) -> FFTResult<Array1<f64>> {
439 if !(0.0..=1.0).contains(&alpha) {
440 return Err(FFTError::ValueError(
441 "alpha must be between 0 and 1".to_string(),
442 ));
443 }
444
445 if n == 1 {
446 return Ok(Array1::ones(1));
447 }
448
449 if alpha == 0.0 {
450 return rectangular(n);
451 }
452
453 if alpha == 1.0 {
454 return hann(n, sym);
455 }
456
457 let mut w = Array1::ones(n);
458 let width = (alpha * (n as f64 - 1.0) / 2.0).floor() as usize;
459
460 for i in 0..width {
462 let x = 0.5 * (1.0 + ((PI * i as f64) / width as f64).cos());
463 w[i] = x;
464 }
465
466 for i in 0..width {
468 let idx = n - 1 - i;
469 let x = 0.5 * (1.0 + ((PI * i as f64) / width as f64).cos());
470 w[idx] = x;
471 }
472
473 Ok(w)
474}
475
476#[allow(dead_code)]
486fn kaiser(n: usize, sym: bool, beta: f64) -> FFTResult<Array1<f64>> {
487 if n == 1 {
488 return Ok(Array1::ones(1));
489 }
490
491 if beta < 0.0 {
492 return Err(FFTError::ValueError(
493 "beta must be non-negative".to_string(),
494 ));
495 }
496
497 let mut n = n;
498 if !sym {
499 n += 1;
500 }
501
502 let mut w = Array1::zeros(n);
503 let alpha = 0.5 * (n as f64 - 1.0);
504 let i0_beta = bessel_i0(beta);
505
506 for i in 0..n {
507 let x = beta * (1.0 - ((i as f64 - alpha) / alpha).powi(2)).sqrt();
508 w[i] = bessel_i0(x) / i0_beta;
509 }
510
511 if !sym {
512 let w_slice = w.slice(scirs2_core::ndarray::s![0..n - 1]).to_owned();
514 Ok(w_slice)
515 } else {
516 Ok(w)
517 }
518}
519
520#[allow(dead_code)]
530fn gaussian(n: usize, sym: bool, std: f64) -> FFTResult<Array1<f64>> {
531 if n == 1 {
532 return Ok(Array1::ones(1));
533 }
534
535 if std <= 0.0 {
536 return Err(FFTError::ValueError("std must be positive".to_string()));
537 }
538
539 let mut w = Array1::zeros(n);
540 let center = if sym { (n as f64 - 1.0) / 2.0 } else { 0.0 };
541
542 for i in 0..n {
543 let x = (i as f64 - center) / (std * (n as f64 - 1.0) / 2.0);
544 w[i] = (-0.5 * x.powi(2)).exp();
545 }
546
547 Ok(w)
548}
549
550#[allow(dead_code)]
560fn general_cosine(n: usize, sym: bool, a: &[f64]) -> FFTResult<Array1<f64>> {
561 if n == 1 {
562 return Ok(Array1::ones(1));
563 }
564
565 let mut w = Array1::zeros(n);
566
567 let fac = if sym {
569 2.0 * PI / (n as f64 - 1.0)
570 } else {
571 2.0 * PI / n as f64
572 };
573
574 for i in 0..n {
575 let mut win_val = a[0];
576
577 for (k, &coef) in a.iter().enumerate().skip(1) {
578 let sign = if k % 2 == 1 { -1.0 } else { 1.0 };
579 win_val += sign * coef * ((k as f64) * fac * (i as f64)).cos();
580 }
581
582 w[i] = win_val;
583 }
584
585 Ok(w)
586}
587
588#[allow(dead_code)]
593fn bessel_i0(x: f64) -> f64 {
594 let ax = x.abs();
595
596 if ax < 3.75 {
598 let y = (x / 3.75).powi(2);
599 return y.mul_add(
600 3.515_622_9
601 + y * (3.089_942_4
602 + y * (1.206_749_2 + y * (0.265_973_2 + y * (0.036_076_8 + y * 0.004_581_3)))),
603 1.0,
604 );
605 }
606
607 let y = 3.75 / ax;
609 let exp_term = (ax).exp() / (ax).sqrt();
610
611 exp_term
612 * y.mul_add(
613 0.013_285_92
614 + y * (0.002_253_19
615 + y * (-0.001_575_65
616 + y * (0.009_162_81
617 + y * (-0.020_577_06
618 + y * (0.026_355_37 + y * (-0.016_476_33 + y * 0.003_923_77)))))),
619 0.398_942_28,
620 )
621}
622
623#[allow(dead_code)]
653pub fn apply_window<F, S>(x: &ArrayBase<S, Ix1>, window: Window) -> FFTResult<Array1<F>>
654where
655 S: Data<Elem = F>,
656 F: Float + FromPrimitive + Debug,
657{
658 let n = x.len();
659 let win = get_window(window, n, true)?;
660
661 let mut result = Array1::zeros(n);
662 for i in 0..n {
663 result[i] = x[i] * F::from_f64(win[i]).unwrap();
664 }
665
666 Ok(result)
667}
668
669#[allow(dead_code)]
693pub fn enbw(window: Window, n: usize) -> FFTResult<f64> {
694 let w = get_window(window, n, true)?;
695
696 let sum_squared = w.iter().map(|&x| x.powi(2)).sum::<f64>();
697 let square_sum = w.iter().sum::<f64>().powi(2);
698
699 let n_f64 = n as f64;
701 Ok(n_f64 * sum_squared / square_sum)
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707 use approx::assert_relative_eq;
708
709 #[test]
710 fn test_rectangular() {
711 let win = rectangular(5).unwrap();
712 let expected = [1.0, 1.0, 1.0, 1.0, 1.0];
713 for (a, &b) in win.iter().zip(expected.iter()) {
714 assert_relative_eq!(a, &b, epsilon = 1e-10);
715 }
716 }
717
718 #[test]
719 fn test_hann() {
720 let win = hann(5, true).unwrap();
721 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
722 for (a, &b) in win.iter().zip(expected.iter()) {
723 assert_relative_eq!(a, &b, epsilon = 1e-10);
724 }
725 }
726
727 #[test]
728 fn test_hamming() {
729 let win = hamming(5, true).unwrap();
730 let expected = [0.08, 0.54, 1.0, 0.54, 0.08];
731 for (a, &b) in win.iter().zip(expected.iter()) {
732 assert_relative_eq!(a, &b, epsilon = 1e-10);
733 }
734 }
735
736 #[test]
737 fn test_blackman() {
738 let win = blackman(5, true).unwrap();
739 let expected = [0.0, 0.34, 1.0, 0.34, 0.0];
740 for (a, &b) in win.iter().zip(expected.iter()) {
741 assert_relative_eq!(a, &b, epsilon = 0.01);
742 }
743 }
744
745 #[test]
746 fn test_from_str() {
747 assert_eq!(Window::from_str("hann").unwrap(), Window::Hann);
748 assert_eq!(Window::from_str("hamming").unwrap(), Window::Hamming);
749 assert_eq!(Window::from_str("blackman").unwrap(), Window::Blackman);
750 assert_eq!(
751 Window::from_str("rectangular").unwrap(),
752 Window::Rectangular
753 );
754 assert!(Window::from_str("invalid").is_err());
755 }
756
757 #[test]
758 fn test_get_window() {
759 let win1 = get_window(Window::Hann, 5, true).unwrap();
760 let win2 = get_window("hann", 5, true).unwrap();
761
762 for (a, b) in win1.iter().zip(win2.iter()) {
763 assert_relative_eq!(a, b, epsilon = 1e-10);
764 }
765 }
766
767 #[test]
768 fn test_apply_window() {
769 let signal = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
770 let win = apply_window(&signal.view(), Window::Hann).unwrap();
771
772 let expected = Array1::from_vec(vec![0.0, 1.0, 3.0, 2.0, 0.0]);
773 for (a, b) in win.iter().zip(expected.iter()) {
774 assert_relative_eq!(a, b, epsilon = 1e-10);
775 }
776 }
777
778 #[test]
779 fn test_enbw() {
780 let rect_enbw = enbw(Window::Rectangular, 1024).unwrap();
782 assert_relative_eq!(rect_enbw, 1.0, epsilon = 1e-10);
783
784 let hann_enbw = enbw(Window::Hann, 1024).unwrap();
785 assert_relative_eq!(hann_enbw, 1.5, epsilon = 0.01);
786
787 let hamming_enbw = enbw(Window::Hamming, 1024).unwrap();
788 assert_relative_eq!(hamming_enbw, 1.36, epsilon = 0.01);
789 }
790}