Skip to main content

audio_lab/signal/
blocks.rs

1use audio_blocks::{AudioBlock, Planar, Sample};
2use ndarray::Array2;
3use num::NumCast;
4
5use crate::signal::{SignalError, TimeSignal};
6
7#[derive(Debug, thiserror::Error)]
8pub enum BlockAdapterError {
9    #[error("Frames per block must be > 0")]
10    FramesPerBlockZero,
11    #[error("Sample Rate must be > 0")]
12    SampleRateZeroOrNeg,
13    #[error("At least one block is required")]
14    EmptyBlockList,
15    #[error("All blocks must have the same number of channels")]
16    ChannelMismatch,
17    #[error("All blocks must have the same number of frames")]
18    FrameMismatch,
19    #[error("Number of channels must fit into u16")]
20    TooManyChannels,
21    #[error(transparent)]
22    Signal(#[from] SignalError),
23}
24
25pub fn signal_to_blocks<S>(
26    signal: &TimeSignal,
27    frames_per_block: usize,
28) -> Result<Vec<Planar<S>>, BlockAdapterError>
29where
30    S: Sample + NumCast + Default,
31{
32    if frames_per_block == 0 {
33        return Err(BlockAdapterError::FramesPerBlockZero);
34    }
35
36    let num_channels =
37        u16::try_from(signal.num_channels()).map_err(|_| BlockAdapterError::TooManyChannels)?;
38    let num_samples = signal.num_time_steps();
39    let num_blocks = num_samples.div_ceil(frames_per_block);
40    let mut blocks = Vec::with_capacity(num_blocks);
41
42    for block_index in 0..num_blocks {
43        let start = block_index * frames_per_block;
44        let end = usize::min(start + frames_per_block, num_samples);
45        let mut block = Planar::<S>::new(num_channels, frames_per_block);
46
47        for (channel_index, dst_channel) in block.channels_mut().enumerate() {
48            let src_channel = signal.channel(channel_index);
49            let len = end - start;
50            for (dst, src) in dst_channel[..len]
51                .iter_mut()
52                .zip(src_channel.iter().skip(start).take(len))
53            {
54                *dst = NumCast::from(*src).expect("time signal sample must convert to target type");
55            }
56        }
57
58        blocks.push(block);
59    }
60
61    Ok(blocks)
62}
63
64pub fn signal_to_block<S>(signal: &TimeSignal) -> Result<Planar<S>, BlockAdapterError>
65where
66    S: Sample + NumCast + Default,
67{
68    let num_channels =
69        u16::try_from(signal.num_channels()).map_err(|_| BlockAdapterError::TooManyChannels)?;
70    let mut block = Planar::<S>::new(num_channels, signal.num_time_steps());
71
72    for (channel_index, dst_channel) in block.channels_mut().enumerate() {
73        for (dst, src) in dst_channel
74            .iter_mut()
75            .zip(signal.channel(channel_index).iter())
76        {
77            *dst = NumCast::from(*src).expect("time signal sample must convert to target type");
78        }
79    }
80
81    Ok(block)
82}
83
84pub fn signal_from_blocks<S>(
85    blocks: &[impl AudioBlock<S>],
86    sample_rate: f64,
87) -> Result<TimeSignal, BlockAdapterError>
88where
89    S: Sample + NumCast,
90{
91    if sample_rate <= 0.0 {
92        return Err(BlockAdapterError::SampleRateZeroOrNeg);
93    }
94
95    let mut iter = blocks.iter();
96    let Some(first_block) = iter.next() else {
97        return Err(BlockAdapterError::EmptyBlockList);
98    };
99
100    let num_channels = first_block.num_channels() as usize;
101    let frames_per_block = first_block.num_frames();
102    let mut owned_blocks = vec![first_block];
103
104    for block in iter {
105        if block.num_channels() as usize != num_channels {
106            return Err(BlockAdapterError::ChannelMismatch);
107        }
108        if block.num_frames() != frames_per_block {
109            return Err(BlockAdapterError::FrameMismatch);
110        }
111        owned_blocks.push(block);
112    }
113
114    let total_frames = frames_per_block * owned_blocks.len();
115    let mut data = Array2::zeros((num_channels, total_frames));
116
117    for (block_index, block) in owned_blocks.iter().enumerate() {
118        let start = block_index * frames_per_block;
119        let end = start + frames_per_block;
120
121        for (channel_index, src_channel) in block.channels_iter().enumerate() {
122            for (frame_index, sample) in src_channel.enumerate() {
123                data[(channel_index, start + frame_index)] =
124                    NumCast::from(*sample).expect("block sample must convert to f64");
125            }
126        }
127
128        debug_assert_eq!(end - start, frames_per_block);
129    }
130
131    TimeSignal::new(data, sample_rate).map_err(BlockAdapterError::from)
132}
133
134pub fn signal_from_block<S>(
135    block: impl AudioBlock<S>,
136    sample_rate: f64,
137) -> Result<TimeSignal, BlockAdapterError>
138where
139    S: Sample + NumCast,
140{
141    signal_from_blocks::<S>(&[block], sample_rate)
142}
143
144#[cfg(test)]
145mod tests {
146    use audio_blocks::Planar;
147    use ndarray::arr2;
148
149    use super::*;
150
151    #[test]
152    fn signal_to_blocks_pads_last_block_with_zeros() -> Result<(), BlockAdapterError> {
153        let signal = TimeSignal::new(arr2(&[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), 48_000.0)?;
154
155        let blocks = signal_to_blocks::<f64>(&signal, 2)?;
156
157        assert_eq!(blocks.len(), 2);
158        assert_eq!(blocks[0].channel(0), &[1.0, 2.0]);
159        assert_eq!(blocks[0].channel(1), &[4.0, 5.0]);
160        assert_eq!(blocks[1].channel(0), &[3.0, 0.0]);
161        assert_eq!(blocks[1].channel(1), &[6.0, 0.0]);
162        Ok(())
163    }
164
165    #[test]
166    fn signal_to_blocks_supports_f32_output() -> Result<(), BlockAdapterError> {
167        let signal = TimeSignal::new(arr2(&[[1.0, 2.0, 3.0]]), 48_000.0)?;
168
169        let blocks = signal_to_blocks::<f32>(&signal, 2)?;
170
171        assert_eq!(blocks.len(), 2);
172        assert_eq!(blocks[0].channel(0), &[1.0_f32, 2.0]);
173        assert_eq!(blocks[1].channel(0), &[3.0_f32, 0.0]);
174        Ok(())
175    }
176
177    #[test]
178    fn signal_from_blocks_concatenates_blocks() -> Result<(), BlockAdapterError> {
179        let first = Planar::from_slice(&[&[1.0, 2.0][..], &[10.0, 20.0][..]]);
180        let second = Planar::from_slice(&[&[3.0, 0.0][..], &[30.0, 0.0][..]]);
181
182        let signal = signal_from_blocks(&[first, second], 48_000.0)?;
183
184        assert_eq!(
185            signal.time_data(),
186            arr2(&[[1.0, 2.0, 3.0, 0.0], [10.0, 20.0, 30.0, 0.0]])
187        );
188        Ok(())
189    }
190
191    #[test]
192    fn signal_to_block_converts_whole_signal_to_single_block() -> Result<(), BlockAdapterError> {
193        let signal = TimeSignal::new(arr2(&[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), 48_000.0)?;
194
195        let block = signal_to_block::<f64>(&signal)?;
196
197        assert_eq!(block.channel(0), &[1.0, 2.0, 3.0]);
198        assert_eq!(block.channel(1), &[4.0, 5.0, 6.0]);
199        Ok(())
200    }
201
202    #[test]
203    fn signal_to_block_supports_f32_output() -> Result<(), BlockAdapterError> {
204        let signal = TimeSignal::new(arr2(&[[1.0, 2.0, 3.0]]), 48_000.0)?;
205
206        let block = signal_to_block::<f32>(&signal)?;
207
208        assert_eq!(block.channel(0), &[1.0_f32, 2.0, 3.0]);
209        Ok(())
210    }
211
212    #[test]
213    fn signal_from_block_supports_single_block() -> Result<(), BlockAdapterError> {
214        let block = Planar::from_slice(&[&[1.0, 2.0, 3.0][..]]);
215
216        let signal = signal_from_block(block, 48_000.0)?;
217
218        assert_eq!(signal.time_data(), arr2(&[[1.0, 2.0, 3.0]]));
219        Ok(())
220    }
221
222    #[test]
223    fn signal_to_blocks_rejects_zero_block_size() {
224        let signal = TimeSignal::zeros(1, 4, 48_000.0).unwrap();
225
226        let result = signal_to_blocks::<f64>(&signal, 0);
227
228        assert!(matches!(result, Err(BlockAdapterError::FramesPerBlockZero)));
229    }
230
231    #[test]
232    fn signal_from_blocks_rejects_empty_input() {
233        let result = signal_from_blocks(&Vec::<Planar<f64>>::new(), 48_000.0);
234
235        assert!(matches!(result, Err(BlockAdapterError::EmptyBlockList)));
236    }
237
238    #[test]
239    fn signal_from_blocks_rejects_channel_mismatch() {
240        let mono = Planar::from_slice(&[&[1.0, 2.0][..]]);
241        let stereo = Planar::from_slice(&[&[3.0, 4.0][..], &[5.0, 6.0][..]]);
242
243        let result = signal_from_blocks(&[mono, stereo], 48_000.0);
244
245        assert!(matches!(result, Err(BlockAdapterError::ChannelMismatch)));
246    }
247
248    #[test]
249    fn signal_from_blocks_rejects_frame_mismatch() {
250        let first = Planar::from_slice(&[&[1.0, 2.0][..]]);
251        let second = Planar::from_slice(&[&[3.0, 4.0, 5.0][..]]);
252
253        let result = signal_from_blocks(&[first, second], 48_000.0);
254
255        assert!(matches!(result, Err(BlockAdapterError::FrameMismatch)));
256    }
257
258    #[test]
259    fn signal_from_blocks_rejects_invalid_sample_rate() {
260        let block = Planar::from_slice(&[&[1.0, 2.0][..]]);
261
262        let result = signal_from_block(block, 0.0);
263
264        assert!(matches!(
265            result,
266            Err(BlockAdapterError::SampleRateZeroOrNeg)
267        ));
268    }
269
270    #[test]
271    fn signal_from_blocks_supports_f32_input() -> Result<(), BlockAdapterError> {
272        let first = Planar::from_slice(&[&[1.0_f32, 2.0][..]]);
273        let second = Planar::from_slice(&[&[3.0_f32, 0.0][..]]);
274
275        let signal = signal_from_blocks(&[first, second], 48_000.0)?;
276
277        assert_eq!(signal.time_data(), arr2(&[[1.0, 2.0, 3.0, 0.0]]));
278        Ok(())
279    }
280}