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}