bbx_dsp/blocks/io/
file_input.rs1use crate::{block::Block, context::DspContext, parameter::ModulationOutput, reader::Reader, sample::Sample};
4
5pub struct FileInputBlock<S: Sample> {
10 reader: Box<dyn Reader<S>>,
11 current_position: usize,
12 loop_enabled: bool,
13}
14
15impl<S: Sample> FileInputBlock<S> {
16 pub fn new(reader: Box<dyn Reader<S>>) -> Self {
18 Self {
19 reader,
20 current_position: 0,
21 loop_enabled: false,
22 }
23 }
24
25 #[inline]
27 pub fn set_loop_enabled(&mut self, enabled: bool) {
28 self.loop_enabled = enabled;
29 }
30
31 #[inline]
33 pub fn set_position(&mut self, position: usize) {
34 self.current_position = position;
35 }
36
37 #[inline]
39 pub fn get_position(&self) -> usize {
40 self.current_position
41 }
42
43 #[inline]
45 pub fn is_finished(&self) -> bool {
46 self.current_position >= self.reader.num_samples()
47 }
48
49 #[inline]
51 pub fn get_reader(&self) -> &dyn Reader<S> {
52 self.reader.as_ref()
53 }
54
55 fn advance_position(&mut self, samples: usize) {
56 self.current_position += samples;
57
58 if self.loop_enabled {
59 let file_length = self.reader.num_samples();
60 if file_length > 0 && self.current_position >= file_length {
61 self.current_position %= file_length;
62 }
63 }
64 }
65}
66
67impl<S: Sample> Block<S> for FileInputBlock<S> {
68 fn process(&mut self, _inputs: &[&[S]], outputs: &mut [&mut [S]], _modulation_values: &[S], context: &DspContext) {
69 let buffer_size = context.buffer_size;
70 let num_file_channels = self.reader.num_channels();
71 let file_length = self.reader.num_samples();
72
73 for (channel_index, output_buffer) in outputs.iter_mut().enumerate() {
74 if channel_index >= num_file_channels {
75 output_buffer.fill(S::ZERO);
76 continue;
77 }
78
79 let input_channel = self.reader.read_channel(channel_index);
80
81 for (sample_index, output_sample) in output_buffer.iter_mut().enumerate() {
82 let read_position = self.current_position + sample_index;
83 if read_position < file_length {
84 *output_sample = input_channel[read_position];
85 } else if self.loop_enabled && file_length > 0 {
86 *output_sample = input_channel[read_position % file_length];
87 } else {
88 *output_sample = S::ZERO;
89 }
90 }
91 }
92
93 self.advance_position(buffer_size);
94 }
95
96 #[inline]
97 fn input_count(&self) -> usize {
98 0
99 }
100
101 #[inline]
102 fn output_count(&self) -> usize {
103 self.reader.num_channels()
104 }
105
106 #[inline]
107 fn modulation_outputs(&self) -> &[ModulationOutput] {
108 &[]
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::channel::ChannelLayout;
116
117 struct MockReader<S: Sample> {
118 sample_rate: f64,
119 channels: Vec<Vec<S>>,
120 }
121
122 impl<S: Sample> MockReader<S> {
123 fn new(sample_rate: f64, channels: Vec<Vec<S>>) -> Self {
124 Self { sample_rate, channels }
125 }
126 }
127
128 impl<S: Sample> Reader<S> for MockReader<S> {
129 fn sample_rate(&self) -> f64 {
130 self.sample_rate
131 }
132
133 fn num_channels(&self) -> usize {
134 self.channels.len()
135 }
136
137 fn num_samples(&self) -> usize {
138 self.channels.first().map(|c| c.len()).unwrap_or(0)
139 }
140
141 fn read_channel(&self, channel_index: usize) -> &[S] {
142 &self.channels[channel_index]
143 }
144 }
145
146 fn test_context(buffer_size: usize) -> DspContext {
147 DspContext {
148 sample_rate: 44100.0,
149 buffer_size,
150 num_channels: 2,
151 current_sample: 0,
152 channel_layout: ChannelLayout::Stereo,
153 }
154 }
155
156 #[test]
157 fn test_file_input_block_counts() {
158 let reader = MockReader::new(44100.0, vec![vec![0.0f32; 100], vec![0.0f32; 100]]);
159 let block = FileInputBlock::new(Box::new(reader));
160 assert_eq!(block.input_count(), 0);
161 assert_eq!(block.output_count(), 2);
162 }
163
164 #[test]
165 fn test_file_input_block_reads_samples() {
166 let samples: Vec<f32> = (0..100).map(|i| i as f32 / 100.0).collect();
167 let reader = MockReader::new(44100.0, vec![samples.clone()]);
168 let mut block = FileInputBlock::new(Box::new(reader));
169
170 let context = test_context(10);
171 let mut output = vec![0.0f32; 10];
172 let mut outputs: [&mut [f32]; 1] = [&mut output];
173
174 block.process(&[], &mut outputs, &[], &context);
175
176 for (i, &sample) in output.iter().enumerate() {
177 assert!((sample - (i as f32 / 100.0)).abs() < 1e-6);
178 }
179 }
180
181 #[test]
182 fn test_file_input_block_position_advances() {
183 let reader = MockReader::new(44100.0, vec![vec![0.0f32; 100]]);
184 let mut block = FileInputBlock::new(Box::new(reader));
185
186 assert_eq!(block.get_position(), 0);
187
188 let context = test_context(10);
189 let mut output = vec![0.0f32; 10];
190 let mut outputs: [&mut [f32]; 1] = [&mut output];
191
192 block.process(&[], &mut outputs, &[], &context);
193 assert_eq!(block.get_position(), 10);
194
195 block.process(&[], &mut outputs, &[], &context);
196 assert_eq!(block.get_position(), 20);
197 }
198
199 #[test]
200 fn test_file_input_block_set_position() {
201 let reader = MockReader::new(44100.0, vec![vec![0.0f32; 100]]);
202 let mut block = FileInputBlock::new(Box::new(reader));
203
204 block.set_position(50);
205 assert_eq!(block.get_position(), 50);
206 }
207
208 #[test]
209 fn test_file_input_block_is_finished() {
210 let reader = MockReader::new(44100.0, vec![vec![0.0f32; 20]]);
211 let mut block = FileInputBlock::new(Box::new(reader));
212
213 assert!(!block.is_finished());
214
215 let context = test_context(10);
216 let mut output = vec![0.0f32; 10];
217 let mut outputs: [&mut [f32]; 1] = [&mut output];
218
219 block.process(&[], &mut outputs, &[], &context);
220 assert!(!block.is_finished());
221
222 block.process(&[], &mut outputs, &[], &context);
223 assert!(block.is_finished());
224 }
225
226 #[test]
227 fn test_file_input_block_outputs_zero_past_end() {
228 let samples: Vec<f32> = vec![1.0; 5];
229 let reader = MockReader::new(44100.0, vec![samples]);
230 let mut block = FileInputBlock::new(Box::new(reader));
231
232 let context = test_context(10);
233 let mut output = vec![0.0f32; 10];
234 let mut outputs: [&mut [f32]; 1] = [&mut output];
235
236 block.process(&[], &mut outputs, &[], &context);
237
238 for i in 0..5 {
239 assert!((output[i] - 1.0).abs() < 1e-6);
240 }
241 for i in 5..10 {
242 assert!((output[i]).abs() < 1e-6);
243 }
244 }
245
246 #[test]
247 fn test_file_input_block_looping() {
248 let samples: Vec<f32> = vec![0.0, 1.0, 2.0, 3.0];
249 let reader = MockReader::new(44100.0, vec![samples]);
250 let mut block = FileInputBlock::new(Box::new(reader));
251 block.set_loop_enabled(true);
252
253 let context = test_context(8);
254 let mut output = vec![0.0f32; 8];
255 let mut outputs: [&mut [f32]; 1] = [&mut output];
256
257 block.process(&[], &mut outputs, &[], &context);
258
259 assert!((output[0] - 0.0).abs() < 1e-6);
260 assert!((output[1] - 1.0).abs() < 1e-6);
261 assert!((output[2] - 2.0).abs() < 1e-6);
262 assert!((output[3] - 3.0).abs() < 1e-6);
263 assert!((output[4] - 0.0).abs() < 1e-6);
264 assert!((output[5] - 1.0).abs() < 1e-6);
265 assert!((output[6] - 2.0).abs() < 1e-6);
266 assert!((output[7] - 3.0).abs() < 1e-6);
267 }
268
269 #[test]
270 fn test_file_input_block_stereo() {
271 let left: Vec<f32> = vec![1.0; 10];
272 let right: Vec<f32> = vec![0.5; 10];
273 let reader = MockReader::new(44100.0, vec![left, right]);
274 let mut block = FileInputBlock::new(Box::new(reader));
275
276 let context = test_context(5);
277 let mut output_l = vec![0.0f32; 5];
278 let mut output_r = vec![0.0f32; 5];
279 let mut outputs: [&mut [f32]; 2] = [&mut output_l, &mut output_r];
280
281 block.process(&[], &mut outputs, &[], &context);
282
283 for &sample in &output_l {
284 assert!((sample - 1.0).abs() < 1e-6);
285 }
286 for &sample in &output_r {
287 assert!((sample - 0.5).abs() < 1e-6);
288 }
289 }
290
291 #[test]
292 fn test_file_input_block_more_outputs_than_channels() {
293 let samples: Vec<f32> = vec![1.0; 10];
294 let reader = MockReader::new(44100.0, vec![samples]);
295 let mut block = FileInputBlock::new(Box::new(reader));
296
297 let context = test_context(5);
298 let mut output_0 = vec![0.0f32; 5];
299 let mut output_1 = vec![0.5f32; 5];
300 let mut outputs: [&mut [f32]; 2] = [&mut output_0, &mut output_1];
301
302 block.process(&[], &mut outputs, &[], &context);
303
304 for &sample in &output_0 {
305 assert!((sample - 1.0).abs() < 1e-6);
306 }
307 for &sample in &output_1 {
308 assert!(sample.abs() < 1e-6);
309 }
310 }
311}