1#![forbid(unsafe_code)]
2use std::f64::consts::PI;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum WindowKind {
22 Rectangular,
23 Hann,
24 Hamming,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum WindowError {
29 InvalidLength,
30 InvalidSample,
31}
32
33pub fn window_coefficients(kind: WindowKind, len: usize) -> Result<Vec<f64>, WindowError> {
34 if len == 0 {
35 return Err(WindowError::InvalidLength);
36 }
37
38 if len == 1 {
39 return Ok(vec![1.0]);
40 }
41
42 let denominator = (len - 1) as f64;
43 let mut coefficients = Vec::with_capacity(len);
44
45 for index in 0..len {
46 let angle = 2.0 * PI * index as f64 / denominator;
47 let coefficient = match kind {
48 WindowKind::Rectangular => 1.0,
49 WindowKind::Hann => 0.5 - 0.5 * angle.cos(),
50 WindowKind::Hamming => 0.54 - 0.46 * angle.cos(),
51 };
52 coefficients.push(coefficient);
53 }
54
55 Ok(coefficients)
56}
57
58pub fn apply_window(samples: &[f64], kind: WindowKind) -> Result<Vec<f64>, WindowError> {
59 if samples.iter().any(|sample| !sample.is_finite()) {
60 return Err(WindowError::InvalidSample);
61 }
62
63 let coefficients = window_coefficients(kind, samples.len())?;
64
65 Ok(samples
66 .iter()
67 .zip(coefficients)
68 .map(|(sample, coefficient)| sample * coefficient)
69 .collect())
70}
71
72#[cfg(test)]
73mod tests {
74 use super::{WindowError, WindowKind, apply_window, window_coefficients};
75
76 #[test]
77 fn computes_window_coefficients_with_expected_lengths() {
78 let coefficients = window_coefficients(WindowKind::Hann, 4).unwrap();
79
80 assert_eq!(coefficients.len(), 4);
81 assert!((coefficients[0] - 0.0).abs() < 1.0e-12);
82 assert!((coefficients[3] - 0.0).abs() < 1.0e-12);
83 }
84
85 #[test]
86 fn handles_single_value_windows_deliberately() {
87 assert_eq!(
88 window_coefficients(WindowKind::Rectangular, 1).unwrap(),
89 vec![1.0]
90 );
91 assert_eq!(window_coefficients(WindowKind::Hann, 1).unwrap(), vec![1.0]);
92 assert_eq!(
93 apply_window(&[2.0], WindowKind::Hamming).unwrap(),
94 vec![2.0]
95 );
96 }
97
98 #[test]
99 fn applies_windows_to_samples() {
100 let windowed = apply_window(&[1.0, 1.0, 1.0, 1.0], WindowKind::Rectangular).unwrap();
101
102 assert_eq!(windowed, vec![1.0, 1.0, 1.0, 1.0]);
103 }
104
105 #[test]
106 fn rejects_empty_windows() {
107 assert_eq!(
108 window_coefficients(WindowKind::Rectangular, 0),
109 Err(WindowError::InvalidLength)
110 );
111 assert_eq!(
112 apply_window(&[], WindowKind::Hann),
113 Err(WindowError::InvalidLength)
114 );
115 }
116
117 #[test]
118 fn rejects_non_finite_samples() {
119 assert_eq!(
120 apply_window(&[1.0, f64::NAN], WindowKind::Hann),
121 Err(WindowError::InvalidSample)
122 );
123 }
124}