bbx_dsp/blocks/io/
file_input.rs

1//! Audio file input block.
2
3use crate::{block::Block, context::DspContext, parameter::ModulationOutput, reader::Reader, sample::Sample};
4
5/// Reads audio from a file into the DSP graph.
6///
7/// Wraps a [`Reader`] implementation to stream audio samples into the graph.
8/// Supports optional looping for continuous playback.
9pub 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    /// Create a `FileInputBlock` with the `Reader` implementation for a particular type of audio file.
17    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    /// Set whether the audio will be looped or not.
26    #[inline]
27    pub fn set_loop_enabled(&mut self, enabled: bool) {
28        self.loop_enabled = enabled;
29    }
30
31    /// Set the position at which the audio file's samples are being read.
32    #[inline]
33    pub fn set_position(&mut self, position: usize) {
34        self.current_position = position;
35    }
36
37    /// Get the position at which the audio file's samples are being read.
38    #[inline]
39    pub fn get_position(&self) -> usize {
40        self.current_position
41    }
42
43    /// Check whether the reader has finished reading every sample in the audio file.
44    #[inline]
45    pub fn is_finished(&self) -> bool {
46        self.current_position >= self.reader.num_samples()
47    }
48
49    /// Get the underlying `Reader` implementation of the `FileInputBlock`.
50    #[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}