Skip to main content

audio_blocks/interleaved/
view.rs

1use rtsan_standalone::nonblocking;
2
3use core::{marker::PhantomData, ptr::NonNull};
4
5use crate::{AudioBlock, Sample, iter::StridedSampleIter};
6
7/// A read-only view of interleaved audio data.
8///
9/// * **Layout:** `[ch0, ch1, ch0, ch1, ch0, ch1]`
10/// * **Interpretation:** Each group of channel samples represents a frame. So, this layout stores frames one after another.
11/// * **Terminology:** Described as “packed” or “frames first” because each time step is grouped and processed as a unit (a frame).
12/// * **Usage:** Often used in APIs or hardware-level interfaces, where synchronized playback across channels is crucial.
13///
14/// # Example
15///
16/// ```
17/// use audio_blocks::*;
18///
19/// let data = vec![0.0, 1.0, 0.0, 1.0, 0.0, 1.0];
20///
21/// let block = AudioBlockInterleavedView::from_slice(&data, 2);
22///
23/// assert_eq!(block.frame(0), &[0.0, 1.0]);
24/// assert_eq!(block.frame(1), &[0.0, 1.0]);
25/// assert_eq!(block.frame(2), &[0.0, 1.0]);
26/// ```
27pub struct AudioBlockInterleavedView<'a, S: Sample> {
28    data: &'a [S],
29    num_channels: u16,
30    num_frames: usize,
31    num_channels_allocated: u16,
32    num_frames_allocated: usize,
33}
34
35impl<'a, S: Sample> AudioBlockInterleavedView<'a, S> {
36    /// Creates a new interleaved audio block from a slice of interleaved audio data.
37    ///
38    /// # Parameters
39    /// * `data` - The slice containing interleaved audio samples
40    /// * `num_channels` - Number of audio channels in the data
41    ///
42    /// # Panics
43    /// Panics if the length of `data` is not evenly divisible by `num_channels`.
44    #[nonblocking]
45    pub fn from_slice(data: &'a [S], num_channels: u16) -> Self {
46        assert!(
47            num_channels > 0 && data.len() % num_channels as usize == 0,
48            "data length {} must be divisible by num_channels {}",
49            data.len(),
50            num_channels
51        );
52        let num_frames = data.len() / num_channels as usize;
53        Self {
54            data,
55            num_channels,
56            num_frames,
57            num_channels_allocated: num_channels,
58            num_frames_allocated: num_frames,
59        }
60    }
61
62    /// Creates a new interleaved audio block from a slice with limited visibility.
63    ///
64    /// This function allows creating a view that exposes only a subset of the allocated channels
65    /// and frames, which is useful for working with a logical section of a larger buffer.
66    ///
67    /// # Parameters
68    /// * `data` - The slice containing interleaved audio samples
69    /// * `num_channels_visible` - Number of audio channels to expose in the view
70    /// * `num_frames_visible` - Number of audio frames to expose in the view
71    /// * `num_channels_allocated` - Total number of channels allocated in the data buffer
72    /// * `num_frames_allocated` - Total number of frames allocated in the data buffer
73    ///
74    /// # Panics
75    /// * Panics if the length of `data` doesn't equal `num_channels_allocated * num_frames_allocated`
76    /// * Panics if `num_channels_visible` exceeds `num_channels_allocated`
77    /// * Panics if `num_frames_visible` exceeds `num_frames_allocated`
78    #[nonblocking]
79    pub fn from_slice_limited(
80        data: &'a [S],
81        num_channels_visible: u16,
82        num_frames_visible: usize,
83        num_channels_allocated: u16,
84        num_frames_allocated: usize,
85    ) -> Self {
86        assert_eq!(
87            data.len(),
88            num_channels_allocated as usize * num_frames_allocated
89        );
90        assert!(num_channels_visible <= num_channels_allocated);
91        assert!(num_frames_visible <= num_frames_allocated);
92        Self {
93            data,
94            num_channels: num_channels_visible,
95            num_frames: num_frames_visible,
96            num_channels_allocated,
97            num_frames_allocated,
98        }
99    }
100
101    /// Creates a new interleaved audio block from a pointer.
102    ///
103    /// # Safety
104    ///
105    /// The caller must ensure that:
106    /// - `ptr` points to valid memory containing at least `num_channels_allocated * num_frames_allocated` elements
107    /// - The memory referenced by `ptr` must be valid for the lifetime of the returned `SequentialView`
108    /// - The memory must not be mutated through other pointers while this view exists
109    #[nonblocking]
110    pub unsafe fn from_ptr(ptr: *const S, num_channels: u16, num_frames: usize) -> Self {
111        Self {
112            data: unsafe { std::slice::from_raw_parts(ptr, num_channels as usize * num_frames) },
113            num_channels,
114            num_frames,
115            num_channels_allocated: num_channels,
116            num_frames_allocated: num_frames,
117        }
118    }
119
120    /// Creates a new audio block from a pointer with a limited amount of channels and/or frames.
121    ///
122    /// # Safety
123    ///
124    /// The caller must ensure that:
125    /// - `ptr` points to valid memory containing at least `num_channels_allocated * num_frames_allocated` elements
126    /// - The memory referenced by `ptr` must be valid for the lifetime of the returned `SequentialView`
127    /// - The memory must not be mutated through other pointers while this view exists
128    #[nonblocking]
129    pub unsafe fn from_ptr_limited(
130        ptr: *const S,
131        num_channels_visible: u16,
132        num_frames_visible: usize,
133        num_channels_allocated: u16,
134        num_frames_allocated: usize,
135    ) -> Self {
136        assert!(num_channels_visible <= num_channels_allocated);
137        assert!(num_frames_visible <= num_frames_allocated);
138        Self {
139            data: unsafe {
140                std::slice::from_raw_parts(
141                    ptr,
142                    num_channels_allocated as usize * num_frames_allocated,
143                )
144            },
145            num_channels: num_channels_visible,
146            num_frames: num_frames_visible,
147            num_channels_allocated,
148            num_frames_allocated,
149        }
150    }
151
152    /// Returns a slice for a single frame.
153    ///
154    /// # Panics
155    ///
156    /// Panics if frame index is out of bounds.
157    #[nonblocking]
158    pub fn frame(&self, frame: usize) -> &[S] {
159        assert!(frame < self.num_frames);
160        let start = frame * self.num_channels_allocated as usize;
161        let end = start + self.num_channels as usize;
162        &self.data[start..end]
163    }
164
165    /// Returns an iterator over all frames in the block.
166    ///
167    /// Each frame is represented as a slice of samples.
168    #[nonblocking]
169    pub fn frames(&self) -> impl Iterator<Item = &[S]> {
170        self.data
171            .chunks(self.num_channels_allocated as usize)
172            .take(self.num_frames)
173            .map(move |frame| &frame[..self.num_channels as usize])
174    }
175
176    /// Provides direct access to the underlying memory as an interleaved slice.
177    ///
178    /// This function gives access to all allocated data, including any reserved capacity
179    /// beyond the visible range.
180    #[nonblocking]
181    pub fn raw_data(&self) -> &[S] {
182        &self.data
183    }
184
185    #[nonblocking]
186    pub fn view(&self) -> AudioBlockInterleavedView<'_, S> {
187        AudioBlockInterleavedView::from_slice_limited(
188            self.data,
189            self.num_channels,
190            self.num_frames,
191            self.num_channels_allocated,
192            self.num_frames_allocated,
193        )
194    }
195}
196
197impl<S: Sample> AudioBlock<S> for AudioBlockInterleavedView<'_, S> {
198    type PlanarView = [S; 0];
199
200    #[nonblocking]
201    fn num_channels(&self) -> u16 {
202        self.num_channels
203    }
204
205    #[nonblocking]
206    fn num_frames(&self) -> usize {
207        self.num_frames
208    }
209
210    #[nonblocking]
211    fn num_channels_allocated(&self) -> u16 {
212        self.num_channels_allocated
213    }
214
215    #[nonblocking]
216    fn num_frames_allocated(&self) -> usize {
217        self.num_frames_allocated
218    }
219
220    #[nonblocking]
221    fn layout(&self) -> crate::BlockLayout {
222        crate::BlockLayout::Interleaved
223    }
224
225    #[nonblocking]
226    fn sample(&self, channel: u16, frame: usize) -> S {
227        assert!(channel < self.num_channels);
228        assert!(frame < self.num_frames);
229        unsafe {
230            *self
231                .data
232                .get_unchecked(frame * self.num_channels_allocated as usize + channel as usize)
233        }
234    }
235
236    #[nonblocking]
237    fn channel_iter(&self, channel: u16) -> impl Iterator<Item = &S> {
238        assert!(channel < self.num_channels);
239        self.data
240            .iter()
241            .skip(channel as usize)
242            .step_by(self.num_channels_allocated as usize)
243            .take(self.num_frames)
244    }
245
246    #[nonblocking]
247    fn channels_iter(&self) -> impl Iterator<Item = impl Iterator<Item = &S> + '_> + '_ {
248        let num_channels = self.num_channels as usize;
249        let num_frames = self.num_frames;
250        let stride = self.num_channels_allocated as usize;
251        let data_ptr = self.data.as_ptr();
252
253        (0..num_channels).map(move |channel_idx| {
254            // Safety check: Ensure data isn't empty if we calculate a start_ptr.
255            // If num_frames or num_channels is 0, remaining will be 0, iterator is safe.
256            // If data is empty, ptr is dangling, but add(0) is okay. add(>0) is UB.
257            // But if data is empty, num_channels or num_frames must be 0.
258            let start_ptr = if self.data.is_empty() {
259                NonNull::dangling().as_ptr() // Use dangling pointer if slice is empty
260            } else {
261                // Safety: channel_idx is < num_channels <= num_channels_allocated.
262                // Adding it to a valid data_ptr is safe within slice bounds.
263                unsafe { data_ptr.add(channel_idx) }
264            };
265
266            StridedSampleIter::<'_, S> {
267                // Note: '_ lifetime from &self borrow
268                // Safety: Pointer is either dangling (if empty) or valid start pointer.
269                // NonNull::new is safe if start_ptr is non-null (i.e., data not empty).
270                ptr: NonNull::new(start_ptr as *mut S).unwrap_or(NonNull::dangling()), // Use dangling on null/empty
271                stride,
272                remaining: num_frames, // If 0, iterator yields None immediately
273                _marker: PhantomData,
274            }
275        })
276    }
277
278    #[nonblocking]
279    fn frame_iter(&self, frame: usize) -> impl Iterator<Item = &S> {
280        assert!(frame < self.num_frames);
281        self.data
282            .iter()
283            .skip(frame * self.num_channels_allocated as usize)
284            .take(self.num_channels as usize)
285    }
286
287    #[nonblocking]
288    fn frames_iter(&self) -> impl Iterator<Item = impl Iterator<Item = &S> + '_> + '_ {
289        let num_channels = self.num_channels as usize;
290        let num_channels_allocated = self.num_channels_allocated as usize;
291        self.data
292            .chunks(num_channels_allocated)
293            .take(self.num_frames)
294            .map(move |channel_chunk| channel_chunk.iter().take(num_channels))
295    }
296
297    #[nonblocking]
298    fn as_view(&self) -> impl AudioBlock<S> {
299        self.view()
300    }
301
302    #[nonblocking]
303    fn as_interleaved_view(&self) -> Option<AudioBlockInterleavedView<'_, S>> {
304        Some(self.view())
305    }
306}
307
308impl<S: Sample + core::fmt::Debug> core::fmt::Debug for AudioBlockInterleavedView<'_, S> {
309    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
310        writeln!(f, "AudioBlockInterleavedView {{")?;
311        writeln!(f, "  num_channels: {}", self.num_channels)?;
312        writeln!(f, "  num_frames: {}", self.num_frames)?;
313        writeln!(
314            f,
315            "  num_channels_allocated: {}",
316            self.num_channels_allocated
317        )?;
318        writeln!(f, "  num_frames_allocated: {}", self.num_frames_allocated)?;
319        writeln!(f, "  frames:")?;
320
321        for (i, frame) in self.frames().enumerate() {
322            writeln!(f, "    {}: {:?}", i, frame)?;
323        }
324
325        writeln!(f, "  raw_data: {:?}", self.raw_data())?;
326        writeln!(f, "}}")?;
327
328        Ok(())
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335    use rtsan_standalone::no_sanitize_realtime;
336
337    #[test]
338    fn test_member_functions() {
339        let block = AudioBlockInterleavedView::from_slice_limited(
340            &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0, 0.0],
341            3,
342            2,
343            4,
344            3,
345        );
346
347        // single frame
348        assert_eq!(block.frame(0), &[0.0, 1.0, 2.0]);
349        assert_eq!(block.frame(1), &[4.0, 5.0, 6.0]);
350
351        // all frames
352        let mut frames = block.frames();
353        assert_eq!(frames.next().unwrap(), &[0.0, 1.0, 2.0]);
354        assert_eq!(frames.next().unwrap(), &[4.0, 5.0, 6.0]);
355        assert_eq!(frames.next(), None);
356        drop(frames);
357
358        // raw data
359        assert_eq!(
360            block.raw_data(),
361            &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0, 0.0]
362        );
363
364        // views
365        let view = block.view();
366        assert_eq!(view.num_channels(), block.num_channels());
367        assert_eq!(view.num_frames(), block.num_frames());
368        assert_eq!(
369            view.num_channels_allocated(),
370            block.num_channels_allocated()
371        );
372        assert_eq!(view.num_frames_allocated(), block.num_frames_allocated());
373        assert_eq!(view.raw_data(), block.raw_data());
374    }
375
376    #[test]
377    fn test_channel_iter() {
378        let data = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
379        let block = AudioBlockInterleavedView::<f32>::from_slice(&data, 2);
380
381        let channel = block.channel_iter(0).copied().collect::<Vec<_>>();
382        assert_eq!(channel, vec![0.0, 2.0, 4.0, 6.0, 8.0]);
383        let channel = block.channel_iter(1).copied().collect::<Vec<_>>();
384        assert_eq!(channel, vec![1.0, 3.0, 5.0, 7.0, 9.0]);
385    }
386
387    #[test]
388    fn test_channel_iters() {
389        let data = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
390        let block = AudioBlockInterleavedView::<f32>::from_slice(&data, 2);
391
392        let mut channels_iter = block.channels_iter();
393        let channel = channels_iter.next().unwrap().copied().collect::<Vec<_>>();
394        assert_eq!(channel, vec![0.0, 2.0, 4.0, 6.0, 8.0]);
395
396        let channel = channels_iter.next().unwrap().copied().collect::<Vec<_>>();
397        assert_eq!(channel, vec![1.0, 3.0, 5.0, 7.0, 9.0]);
398        assert!(channels_iter.next().is_none());
399    }
400
401    #[test]
402    fn test_frame_iter() {
403        let data = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
404        let block = AudioBlockInterleavedView::<f32>::from_slice(&data, 2);
405
406        let channel = block.frame_iter(0).copied().collect::<Vec<_>>();
407        assert_eq!(channel, vec![0.0, 1.0]);
408        let channel = block.frame_iter(1).copied().collect::<Vec<_>>();
409        assert_eq!(channel, vec![2.0, 3.0]);
410        let channel = block.frame_iter(2).copied().collect::<Vec<_>>();
411        assert_eq!(channel, vec![4.0, 5.0]);
412        let channel = block.frame_iter(3).copied().collect::<Vec<_>>();
413        assert_eq!(channel, vec![6.0, 7.0]);
414        let channel = block.frame_iter(4).copied().collect::<Vec<_>>();
415        assert_eq!(channel, vec![8.0, 9.0]);
416    }
417
418    #[test]
419    fn test_frame_iters() {
420        let data = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
421        let block = AudioBlockInterleavedView::<f32>::from_slice(&data, 2);
422
423        let mut frames_iter = block.frames_iter();
424        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
425        assert_eq!(channel, vec![0.0, 1.0]);
426        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
427        assert_eq!(channel, vec![2.0, 3.0]);
428        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
429        assert_eq!(channel, vec![4.0, 5.0]);
430        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
431        assert_eq!(channel, vec![6.0, 7.0]);
432        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
433        assert_eq!(channel, vec![8.0, 9.0]);
434        assert!(frames_iter.next().is_none());
435    }
436
437    #[test]
438    fn test_from_slice() {
439        let data = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
440        let block = AudioBlockInterleavedView::<f32>::from_slice(&data, 2);
441        assert_eq!(block.num_channels(), 2);
442        assert_eq!(block.num_channels_allocated, 2);
443        assert_eq!(block.num_frames(), 5);
444        assert_eq!(block.num_frames_allocated, 5);
445        assert_eq!(
446            block.channel_iter(0).copied().collect::<Vec<_>>(),
447            vec![0.0, 2.0, 4.0, 6.0, 8.0]
448        );
449        assert_eq!(
450            block.channel_iter(1).copied().collect::<Vec<_>>(),
451            vec![1.0, 3.0, 5.0, 7.0, 9.0]
452        );
453        assert_eq!(
454            block.frame_iter(0).copied().collect::<Vec<_>>(),
455            vec![0.0, 1.0]
456        );
457        assert_eq!(
458            block.frame_iter(1).copied().collect::<Vec<_>>(),
459            vec![2.0, 3.0]
460        );
461        assert_eq!(
462            block.frame_iter(2).copied().collect::<Vec<_>>(),
463            vec![4.0, 5.0]
464        );
465        assert_eq!(
466            block.frame_iter(3).copied().collect::<Vec<_>>(),
467            vec![6.0, 7.0]
468        );
469        assert_eq!(
470            block.frame_iter(4).copied().collect::<Vec<_>>(),
471            vec![8.0, 9.0]
472        );
473    }
474
475    #[test]
476    fn test_view() {
477        let data = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
478        let block = AudioBlockInterleavedView::<f32>::from_slice(&data, 2);
479        assert!(block.as_interleaved_view().is_some());
480        assert!(block.as_planar_view().is_none());
481        assert!(block.as_sequential_view().is_none());
482
483        let view = block.view();
484        assert_eq!(
485            view.channel_iter(0).copied().collect::<Vec<_>>(),
486            vec![0.0, 2.0, 4.0, 6.0, 8.0]
487        );
488        assert_eq!(
489            view.channel_iter(1).copied().collect::<Vec<_>>(),
490            vec![1.0, 3.0, 5.0, 7.0, 9.0]
491        );
492    }
493
494    #[test]
495    fn test_limited() {
496        let data = [1.0, 2.0, 0.0, 3.0, 4.0, 0.0, 5.0, 6.0, 0.0, 0.0, 0.0, 0.0];
497
498        let block = AudioBlockInterleavedView::from_slice_limited(&data, 2, 3, 3, 4);
499
500        assert_eq!(block.num_channels(), 2);
501        assert_eq!(block.num_frames(), 3);
502        assert_eq!(block.num_channels_allocated, 3);
503        assert_eq!(block.num_frames_allocated, 4);
504
505        for i in 0..block.num_channels() {
506            assert_eq!(block.channel_iter(i).count(), 3);
507        }
508        for i in 0..block.num_frames() {
509            assert_eq!(block.frame_iter(i).count(), 2);
510        }
511    }
512
513    #[test]
514    fn test_from_ptr() {
515        let mut data = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
516        let block = unsafe { AudioBlockInterleavedView::<f32>::from_ptr(data.as_mut_ptr(), 2, 5) };
517        assert_eq!(block.num_channels(), 2);
518        assert_eq!(block.num_channels_allocated, 2);
519        assert_eq!(block.num_frames(), 5);
520        assert_eq!(block.num_frames_allocated, 5);
521        assert_eq!(
522            block.channel_iter(0).copied().collect::<Vec<_>>(),
523            vec![0.0, 2.0, 4.0, 6.0, 8.0]
524        );
525        assert_eq!(
526            block.channel_iter(1).copied().collect::<Vec<_>>(),
527            vec![1.0, 3.0, 5.0, 7.0, 9.0]
528        );
529        assert_eq!(
530            block.frame_iter(0).copied().collect::<Vec<_>>(),
531            vec![0.0, 1.0]
532        );
533        assert_eq!(
534            block.frame_iter(1).copied().collect::<Vec<_>>(),
535            vec![2.0, 3.0]
536        );
537        assert_eq!(
538            block.frame_iter(2).copied().collect::<Vec<_>>(),
539            vec![4.0, 5.0]
540        );
541        assert_eq!(
542            block.frame_iter(3).copied().collect::<Vec<_>>(),
543            vec![6.0, 7.0]
544        );
545        assert_eq!(
546            block.frame_iter(4).copied().collect::<Vec<_>>(),
547            vec![8.0, 9.0]
548        );
549    }
550
551    #[test]
552    fn test_from_ptr_limited() {
553        let data = [1.0, 2.0, 0.0, 3.0, 4.0, 0.0, 5.0, 6.0, 0.0, 0.0, 0.0, 0.0];
554
555        let block =
556            unsafe { AudioBlockInterleavedView::from_ptr_limited(data.as_ptr(), 2, 3, 3, 4) };
557
558        assert_eq!(block.num_channels(), 2);
559        assert_eq!(block.num_frames(), 3);
560        assert_eq!(block.num_channels_allocated, 3);
561        assert_eq!(block.num_frames_allocated, 4);
562
563        for i in 0..block.num_channels() {
564            assert_eq!(block.channel_iter(i).count(), 3);
565        }
566        for i in 0..block.num_frames() {
567            assert_eq!(block.frame_iter(i).count(), 2);
568        }
569    }
570
571    #[test]
572    #[should_panic]
573    #[no_sanitize_realtime]
574    fn test_slice_out_of_bounds() {
575        let data = [1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0];
576        let block = AudioBlockInterleavedView::<f32>::from_slice_limited(&data, 2, 3, 3, 4);
577        block.frame(5);
578    }
579}