microdsp/common/
window_processor.rs1use alloc::{boxed::Box, vec};
2
3pub struct WindowProcessor {
9 downsampled_window: Box<[f32]>,
10 downsampling: usize,
11 downsampled_hop_size: usize,
12 write_index: usize,
14 wrapped_sample_counter: usize,
15}
16
17fn validate_sizes(downsampled_size: usize, downsampled_hop_size: usize, downsampling: usize) {
18 if downsampled_size == 0 {
19 panic!("Downsampled size must be greater than 0")
20 }
21 if downsampled_hop_size == 0 {
22 panic!("Downsampled hop size must be greater than 0")
23 }
24 if downsampling == 0 {
25 panic!("Downsampling must be greater than 0")
26 }
27 if downsampled_hop_size > downsampled_size {
28 panic!("Downsampled hop size must not be greater than downsampled size")
29 }
30}
31
32impl WindowProcessor {
33 pub fn new(
40 downsampling: usize,
41 downsampled_window_size: usize,
42 downsampled_hop_size: usize,
43 ) -> Self {
44 validate_sizes(downsampled_window_size, downsampled_hop_size, downsampling);
45 WindowProcessor {
46 downsampled_window: vec![0.; downsampled_window_size].into_boxed_slice(),
47 downsampled_hop_size,
48 downsampling,
49 write_index: 0,
50 wrapped_sample_counter: 0,
51 }
52 }
53
54 pub fn reset(&mut self) {
55 self.write_index = 0;
56 self.wrapped_sample_counter = 0;
57 }
58
59 pub fn downsampling(&self) -> usize {
61 self.downsampling
62 }
63
64 pub fn downsampled_hop_size(&self) -> usize {
66 self.downsampled_hop_size
67 }
68
69 pub fn downsampled_window_size(&self) -> usize {
71 self.downsampled_window.len()
72 }
73
74 pub fn process<F>(&mut self, buffer: &[f32], mut handler: F)
77 where
78 F: FnMut(&[f32]),
79 {
80 let downsampled_window_size = self.downsampled_window.len();
81 let skip = (self.downsampling - self.wrapped_sample_counter) % self.downsampling;
82 for input in buffer.iter().skip(skip).step_by(self.downsampling) {
83 self.downsampled_window[self.write_index] = *input;
84 self.write_index += 1;
85 if self.write_index == downsampled_window_size {
86 handler(&self.downsampled_window);
87 self.downsampled_window
88 .rotate_left(self.downsampled_hop_size);
89 self.write_index = downsampled_window_size - self.downsampled_hop_size;
90 }
91 }
92
93 self.wrapped_sample_counter =
94 (self.wrapped_sample_counter + buffer.len()) % self.downsampling
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use alloc::vec;
101 use alloc::vec::Vec;
102
103 use super::WindowProcessor;
104
105 #[test]
106 #[should_panic]
107 fn test_zero_window_size() {
108 WindowProcessor::new(1, 0, 256);
109 }
110
111 #[test]
112 #[should_panic]
113 fn test_zero_hop_size() {
114 WindowProcessor::new(1, 256, 0);
115 }
116
117 #[test]
118 #[should_panic]
119 fn test_too_large_hop_size() {
120 WindowProcessor::new(1, 256, 257);
121 }
122
123 #[test]
124 #[should_panic]
125 fn test_zero_downsampling() {
126 WindowProcessor::new(0, 256, 256);
127 }
128
129 #[test]
130 fn test_hop_size_equals_window_size() {
131 let hop_size = 128;
132 let window_size = 128;
133 let downsampling = 2;
134 let chunk_size = 256;
135 let window_count = 10;
136 let sample_count = chunk_size * window_count;
137 let samples = vec![0.0; sample_count];
138 let mut processor = WindowProcessor::new(downsampling, window_size, hop_size);
139 let mut first_idx = 0;
140 let mut winow_counter = 0;
141 while first_idx < sample_count {
142 let chunk = &samples[first_idx..(first_idx + chunk_size)];
143 processor.process(chunk, |_| {
144 winow_counter += 1;
145 });
146 first_idx += chunk_size;
147 }
148 assert_eq!(winow_counter, window_count);
149 }
150
151 #[test]
152 fn test_window_processing() {
153 let window_size = 15;
154
155 let input_buffer: Vec<f32> = (0..(5 * window_size)).map(|v| v as f32).collect();
157 assert_eq!(input_buffer.len(), 5 * window_size);
158
159 for downsampling in 1..10 {
161 for hop_size in 1..=window_size {
162 for chunk_size in 1..5 * window_size {
163 let mut processor = WindowProcessor::new(downsampling, window_size, hop_size);
164 let mut processed_window_count = 0;
165 let mut input_buffer_pos = 0;
166 while input_buffer_pos < input_buffer.len() {
168 let chunk_start_idx = input_buffer_pos;
169 let current_chunk_size =
170 chunk_size.min(input_buffer.len() - chunk_start_idx);
171 let chunk_end_idx = input_buffer_pos + current_chunk_size;
172 let current_chunk_size = chunk_size.min(chunk_end_idx - chunk_start_idx);
173 let chunk = &input_buffer[chunk_start_idx..chunk_end_idx];
174 assert_eq!(chunk.len(), current_chunk_size);
175
176 processor.process(chunk, |window| {
177 assert_eq!(
180 window[0],
181 input_buffer[downsampling * processed_window_count * hop_size]
182 );
183 assert_eq!(window.len(), window_size);
184 processed_window_count += 1;
185 });
186
187 input_buffer_pos += chunk_size
188 }
189 }
190 }
191 }
192 }
193}