audio_blocks/stacked/
view.rs

1use core::mem::MaybeUninit;
2use rtsan_standalone::nonblocking;
3use std::marker::PhantomData;
4
5use crate::{AudioBlock, Sample};
6
7/// A read-only view of stacked / separate-channel audio data.
8///
9/// * **Layout:** `[[ch0, ch0, ch0], [ch1, ch1, ch1]]`
10/// * **Interpretation:** Each channel has its own separate buffer or array.
11/// * **Terminology:** Also described as “planar” or “channels first” though more specifically it’s channel-isolated buffers.
12/// * **Usage:** Very common in real-time DSP, as it simplifies memory access and can improve SIMD/vectorization efficiency.
13///
14/// # Example
15///
16/// ```
17/// use audio_blocks::*;
18///
19/// let data = vec![[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]];
20///
21/// let block = StackedView::from_slice(&data);
22///
23/// block.channel(0).for_each(|&v| assert_eq!(v, 0.0));
24/// block.channel(1).for_each(|&v| assert_eq!(v, 1.0));
25/// ```
26pub struct StackedView<'a, S: Sample, V: AsRef<[S]>> {
27    data: &'a [V],
28    num_channels: u16,
29    num_frames: usize,
30    num_channels_allocated: u16,
31    num_frames_allocated: usize,
32    _phantom: PhantomData<S>,
33}
34
35impl<'a, S: Sample, V: AsRef<[S]>> StackedView<'a, S, V> {
36    /// Creates a new [`StackedView`] from a slice of stacked audio data.
37    ///
38    /// # Parameters
39    /// * `data` - The slice containing stacked audio samples (one slice per channel)
40    ///
41    /// # Panics
42    /// Panics if the channel slices have different lengths.
43    #[nonblocking]
44    pub fn from_slice(data: &'a [V]) -> Self {
45        let num_frames_available = if data.is_empty() {
46            0
47        } else {
48            data[0].as_ref().len()
49        };
50        Self::from_slice_limited(data, data.len() as u16, num_frames_available)
51    }
52
53    /// Creates a new [`StackedView`] from a slice with limited visibility.
54    ///
55    /// This function allows creating a view that exposes only a subset of the allocated channels
56    /// and frames, which is useful for working with a logical section of a larger buffer.
57    ///
58    /// # Parameters
59    /// * `data` - The slice containing stacked audio samples (one slice per channel)
60    /// * `num_channels_visible` - Number of audio channels to expose in the view
61    /// * `num_frames_visible` - Number of audio frames to expose in the view
62    ///
63    /// # Panics
64    /// * Panics if `num_channels_visible` exceeds the number of channels in `data`
65    /// * Panics if `num_frames_visible` exceeds the length of any channel buffer
66    /// * Panics if channel slices have different lengths
67    #[nonblocking]
68    pub fn from_slice_limited(
69        data: &'a [V],
70        num_channels_visible: u16,
71        num_frames_visible: usize,
72    ) -> Self {
73        let num_channels_allocated = data.len() as u16;
74        let num_frames_allocated = if num_channels_allocated == 0 {
75            0
76        } else {
77            data[0].as_ref().len()
78        };
79        assert!(num_channels_visible <= num_channels_allocated);
80        assert!(num_frames_visible <= num_frames_allocated);
81        data.iter()
82            .for_each(|v| assert_eq!(v.as_ref().len(), num_frames_allocated));
83
84        Self {
85            data,
86            num_channels: num_channels_visible,
87            num_frames: num_frames_visible,
88            num_channels_allocated,
89            num_frames_allocated,
90            _phantom: PhantomData,
91        }
92    }
93}
94
95impl<S: Sample, V: AsRef<[S]>> AudioBlock<S> for StackedView<'_, S, V> {
96    #[nonblocking]
97    fn num_frames(&self) -> usize {
98        self.num_frames
99    }
100
101    #[nonblocking]
102    fn num_channels(&self) -> u16 {
103        self.num_channels
104    }
105
106    #[nonblocking]
107    fn num_channels_allocated(&self) -> u16 {
108        self.num_channels_allocated
109    }
110
111    #[nonblocking]
112    fn num_frames_allocated(&self) -> usize {
113        self.num_frames_allocated
114    }
115
116    #[nonblocking]
117    fn sample(&self, channel: u16, frame: usize) -> S {
118        assert!(channel < self.num_channels);
119        assert!(frame < self.num_frames);
120        unsafe {
121            *self
122                .data
123                .get_unchecked(channel as usize)
124                .as_ref()
125                .get_unchecked(frame)
126        }
127    }
128
129    #[nonblocking]
130    fn channel(&self, channel: u16) -> impl Iterator<Item = &S> {
131        assert!(channel < self.num_channels);
132        unsafe {
133            self.data
134                .get_unchecked(channel as usize)
135                .as_ref()
136                .iter()
137                .take(self.num_frames)
138        }
139    }
140
141    #[nonblocking]
142    fn channels(&self) -> impl Iterator<Item = impl Iterator<Item = &S> + '_> + '_ {
143        let num_frames = self.num_frames; // Capture num_frames for the closure
144        self.data
145            .iter()
146            // Limit to the active number of channels
147            .take(self.num_channels as usize)
148            // For each channel slice, create an iterator over its samples
149            .map(move |channel_data| channel_data.as_ref().iter().take(num_frames))
150    }
151
152    #[nonblocking]
153    fn channel_slice(&self, channel: u16) -> Option<&[S]> {
154        assert!(channel < self.num_channels);
155        Some(&self.data[channel as usize].as_ref()[..self.num_frames])
156    }
157
158    #[nonblocking]
159    fn frame(&self, frame: usize) -> impl Iterator<Item = &S> {
160        assert!(frame < self.num_frames);
161        self.data
162            .iter()
163            .take(self.num_channels as usize)
164            .map(move |channel_data| unsafe { channel_data.as_ref().get_unchecked(frame) })
165    }
166
167    #[nonblocking]
168    fn frames(&self) -> impl Iterator<Item = impl Iterator<Item = &'_ S> + '_> + '_ {
169        let num_channels = self.num_channels as usize;
170        let num_frames = self.num_frames;
171        let data_slice: &[V] = self.data;
172
173        (0..num_frames).map(move |frame_idx| {
174            // For each frame index, create an iterator over the relevant channel views.
175            data_slice[..num_channels]
176                .iter() // Yields `&'a V`
177                .map(move |channel_view: &V| {
178                    // Get the immutable slice `&[S]` from the view using AsRef.
179                    let channel_slice: &[S] = channel_view.as_ref();
180                    // Access the sample immutably using safe indexing.
181                    // Assumes frame_idx is valid based on outer loop and struct invariants.
182                    &channel_slice[frame_idx]
183                    // For max performance (if bounds are absolutely guaranteed):
184                    // unsafe { channel_slice.get_unchecked(frame_idx) }
185                })
186        })
187    }
188
189    #[nonblocking]
190    fn view(&self) -> impl AudioBlock<S> {
191        StackedView::<S, V>::from_slice_limited(self.data, self.num_channels, self.num_frames)
192    }
193
194    #[nonblocking]
195    fn layout(&self) -> crate::BlockLayout {
196        crate::BlockLayout::Stacked
197    }
198
199    #[nonblocking]
200    fn raw_data(&self, stacked_ch: Option<u16>) -> &[S] {
201        let ch = stacked_ch.expect("For stacked layout channel needs to be provided!");
202        assert!(ch < self.num_channels_allocated);
203        unsafe { self.data.get_unchecked(ch as usize).as_ref() }
204    }
205}
206
207/// Adapter for creating stacked audio block views from raw pointers.
208///
209/// This adapter provides a safe interface to work with audio data stored in external buffers,
210/// which is common when interfacing with audio APIs or hardware.
211///
212/// # Example
213///
214/// ```
215/// use audio_blocks::*;
216///
217/// // Create sample data for two channels with five frames each
218/// let ch1 = vec![0.0f32, 1.0, 2.0, 3.0, 4.0];
219/// let ch2 = vec![5.0f32, 6.0, 7.0, 8.0, 9.0];
220///
221/// // Create pointers to the channel data
222/// let ptrs = [ch1.as_ptr(), ch2.as_ptr()];
223/// let data = ptrs.as_ptr();
224/// let num_channels = 2u16;
225/// let num_frames = 5;
226///
227/// // Create an adapter from raw pointers to audio channel data
228/// let adapter = unsafe { StackedPtrAdapter::<f32, 16>::from_ptr(data, num_channels, num_frames) };
229///
230/// // Get a safe view of the audio data
231/// let block = adapter.stacked_view();
232///
233/// // Verify the data access works
234/// assert_eq!(block.sample(0, 2), 2.0);
235/// assert_eq!(block.sample(1, 3), 8.0);
236/// ```
237///
238/// # Safety
239///
240/// When creating an adapter from raw pointers, you must ensure that:
241/// - The pointers are valid and properly aligned
242/// - The memory they point to remains valid for the lifetime of the adapter
243/// - The channel count doesn't exceed the adapter's `MAX_CHANNELS` capacity
244pub struct StackedPtrAdapter<'a, S: Sample, const MAX_CHANNELS: usize> {
245    data: [MaybeUninit<&'a [S]>; MAX_CHANNELS],
246    num_channels: u16,
247}
248
249impl<'a, S: Sample, const MAX_CHANNELS: usize> StackedPtrAdapter<'a, S, MAX_CHANNELS> {
250    /// Creates new StackedPtrAdapter from raw pointers.
251    ///
252    /// # Safety
253    ///
254    /// - `ptr` must be a valid pointer to an array of pointers
255    /// - The array must contain at least `num_channels` valid pointers
256    /// - Each pointer in the array must point to a valid array of samples with `num_frames` length
257    /// - The pointed memory must remain valid for the lifetime of the returned adapter
258    /// - The data must not be modified through other pointers for the lifetime of the returned adapter
259    #[nonblocking]
260    #[nonblocking]
261    pub unsafe fn from_ptr(ptr: *const *const S, num_channels: u16, num_frames: usize) -> Self {
262        assert!(
263            num_channels as usize <= MAX_CHANNELS,
264            "num_channels exceeds MAX_CHANNELS"
265        );
266
267        let mut data: [core::mem::MaybeUninit<&'a [S]>; MAX_CHANNELS] =
268            unsafe { core::mem::MaybeUninit::uninit().assume_init() }; // Or other safe initialization
269
270        // SAFETY: Caller guarantees `ptr` is valid for `num_channels` elements.
271        let ptr_slice: &[*const S] =
272            unsafe { core::slice::from_raw_parts(ptr, num_channels as usize) };
273
274        for ch in 0..num_channels as usize {
275            // SAFETY: See previous explanation
276            data[ch].write(unsafe { core::slice::from_raw_parts(ptr_slice[ch], num_frames) });
277        }
278
279        Self { data, num_channels }
280    }
281
282    /// Returns a slice of references to the initialized channel data buffers.
283    ///
284    /// This method provides access to the underlying audio data as a slice of slices,
285    /// with each inner slice representing one audio channel.
286    #[inline]
287    pub fn data_slice(&self) -> &[&'a [S]] {
288        let initialized_part: &[MaybeUninit<&'a [S]>] = &self.data[..self.num_channels as usize];
289        unsafe {
290            core::slice::from_raw_parts(
291                initialized_part.as_ptr() as *const &'a [S],
292                self.num_channels as usize,
293            )
294        }
295    }
296
297    /// Creates a safe [`StackedView`] for accessing the audio data.
298    ///
299    /// This provides a convenient way to interact with the audio data through
300    /// the full [`AudioBlock`] interface, enabling operations like iterating
301    /// through channels or frames.
302    ///
303    /// # Returns
304    ///
305    /// A [`StackedView`] that provides safe, immutable access to the audio data.
306    #[nonblocking]
307    pub fn stacked_view(&self) -> StackedView<'a, S, &[S]> {
308        StackedView::from_slice(self.data_slice())
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use rtsan_standalone::no_sanitize_realtime;
315
316    use super::*;
317
318    #[test]
319    fn test_samples() {
320        let ch1 = &[0.0, 1.0, 2.0, 3.0, 4.0];
321        let ch2 = &[5.0, 6.0, 7.0, 8.0, 9.0];
322        let data = [ch1, ch2];
323        let block = StackedView::from_slice(&data);
324
325        for ch in 0..block.num_channels() {
326            for f in 0..block.num_frames() {
327                assert_eq!(
328                    block.sample(ch, f),
329                    (ch as usize * block.num_frames() + f) as f32
330                );
331            }
332        }
333    }
334
335    #[test]
336    fn test_channel() {
337        let ch1 = vec![0.0, 2.0, 4.0, 6.0, 8.0];
338        let ch2 = vec![1.0, 3.0, 5.0, 7.0, 9.0];
339        let data = vec![ch1, ch2];
340        let block = StackedView::from_slice(&data);
341
342        let channel = block.channel(0).copied().collect::<Vec<_>>();
343        assert_eq!(channel, vec![0.0, 2.0, 4.0, 6.0, 8.0]);
344        let channel = block.channel(1).copied().collect::<Vec<_>>();
345        assert_eq!(channel, vec![1.0, 3.0, 5.0, 7.0, 9.0]);
346    }
347
348    #[test]
349    fn test_channels() {
350        let ch1 = vec![0.0, 2.0, 4.0, 6.0, 8.0];
351        let ch2 = vec![1.0, 3.0, 5.0, 7.0, 9.0];
352        let data = vec![ch1, ch2];
353        let block = StackedView::from_slice(&data);
354
355        let mut channels_iter = block.channels();
356        let channel = channels_iter.next().unwrap().copied().collect::<Vec<_>>();
357        assert_eq!(channel, vec![0.0, 2.0, 4.0, 6.0, 8.0]);
358
359        let channel = channels_iter.next().unwrap().copied().collect::<Vec<_>>();
360        assert_eq!(channel, vec![1.0, 3.0, 5.0, 7.0, 9.0]);
361        assert!(channels_iter.next().is_none());
362    }
363
364    #[test]
365    fn test_frame() {
366        let ch1 = vec![0.0, 2.0, 4.0, 6.0, 8.0];
367        let ch2 = vec![1.0, 3.0, 5.0, 7.0, 9.0];
368        let data = vec![ch1.as_slice(), ch2.as_slice()];
369        let block = StackedView::from_slice(&data);
370
371        let channel = block.frame(0).copied().collect::<Vec<_>>();
372        assert_eq!(channel, vec![0.0, 1.0]);
373        let channel = block.frame(1).copied().collect::<Vec<_>>();
374        assert_eq!(channel, vec![2.0, 3.0]);
375        let channel = block.frame(2).copied().collect::<Vec<_>>();
376        assert_eq!(channel, vec![4.0, 5.0]);
377        let channel = block.frame(3).copied().collect::<Vec<_>>();
378        assert_eq!(channel, vec![6.0, 7.0]);
379        let channel = block.frame(4).copied().collect::<Vec<_>>();
380        assert_eq!(channel, vec![8.0, 9.0]);
381    }
382
383    #[test]
384    fn test_frames() {
385        let ch1 = vec![0.0, 2.0, 4.0, 6.0, 8.0];
386        let ch2 = vec![1.0, 3.0, 5.0, 7.0, 9.0];
387        let data = vec![ch1.as_slice(), ch2.as_slice()];
388        let block = StackedView::from_slice(&data);
389
390        let mut frames_iter = block.frames();
391        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
392        assert_eq!(channel, vec![0.0, 1.0]);
393        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
394        assert_eq!(channel, vec![2.0, 3.0]);
395        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
396        assert_eq!(channel, vec![4.0, 5.0]);
397        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
398        assert_eq!(channel, vec![6.0, 7.0]);
399        let channel = frames_iter.next().unwrap().copied().collect::<Vec<_>>();
400        assert_eq!(channel, vec![8.0, 9.0]);
401        assert!(frames_iter.next().is_none());
402    }
403
404    #[test]
405    fn test_from_vec() {
406        let vec = vec![vec![0.0, 2.0, 4.0, 6.0, 8.0], vec![1.0, 3.0, 5.0, 7.0, 9.0]];
407        let block = StackedView::from_slice(&vec);
408        assert_eq!(block.num_channels(), 2);
409        assert_eq!(block.num_frames(), 5);
410        assert_eq!(
411            block.channel(0).copied().collect::<Vec<_>>(),
412            vec![0.0, 2.0, 4.0, 6.0, 8.0]
413        );
414        assert_eq!(
415            block.channel(1).copied().collect::<Vec<_>>(),
416            vec![1.0, 3.0, 5.0, 7.0, 9.0]
417        );
418        assert_eq!(block.frame(0).copied().collect::<Vec<_>>(), vec![0.0, 1.0]);
419        assert_eq!(block.frame(1).copied().collect::<Vec<_>>(), vec![2.0, 3.0]);
420        assert_eq!(block.frame(2).copied().collect::<Vec<_>>(), vec![4.0, 5.0]);
421        assert_eq!(block.frame(3).copied().collect::<Vec<_>>(), vec![6.0, 7.0]);
422        assert_eq!(block.frame(4).copied().collect::<Vec<_>>(), vec![8.0, 9.0]);
423    }
424
425    #[test]
426    fn test_view() {
427        let vec = vec![vec![0.0, 2.0, 4.0, 6.0, 8.0], vec![1.0, 3.0, 5.0, 7.0, 9.0]];
428        let block = StackedView::from_slice(&vec);
429        let view = block.view();
430        assert_eq!(
431            view.channel(0).copied().collect::<Vec<_>>(),
432            vec![0.0, 2.0, 4.0, 6.0, 8.0]
433        );
434        assert_eq!(
435            view.channel(1).copied().collect::<Vec<_>>(),
436            vec![1.0, 3.0, 5.0, 7.0, 9.0]
437        );
438    }
439
440    #[test]
441    fn test_limited() {
442        let data = vec![vec![0.0; 4]; 3];
443
444        let block = StackedView::from_slice_limited(&data, 2, 3);
445
446        assert_eq!(block.num_channels(), 2);
447        assert_eq!(block.num_frames(), 3);
448        assert_eq!(block.num_channels_allocated, 3);
449        assert_eq!(block.num_frames_allocated, 4);
450
451        for i in 0..block.num_channels() {
452            assert_eq!(block.channel(i).count(), 3);
453        }
454        for i in 0..block.num_frames() {
455            assert_eq!(block.frame(i).count(), 2);
456        }
457    }
458
459    #[test]
460    fn test_pointer() {
461        unsafe {
462            let num_channels = 2;
463            let num_frames = 5;
464            let mut vec = [vec![0.0, 2.0, 4.0, 6.0, 8.0], vec![1.0, 3.0, 5.0, 7.0, 9.0]];
465
466            let ptr_vec: Vec<*const f32> =
467                vec.iter_mut().map(|inner_vec| inner_vec.as_ptr()).collect();
468            let ptr = ptr_vec.as_ptr();
469
470            let adapter = StackedPtrAdapter::<_, 16>::from_ptr(ptr, num_channels, num_frames);
471            let stacked = adapter.stacked_view();
472
473            assert_eq!(
474                stacked.channel(0).copied().collect::<Vec<_>>(),
475                vec![0.0, 2.0, 4.0, 6.0, 8.0]
476            );
477
478            assert_eq!(
479                stacked.channel(1).copied().collect::<Vec<_>>(),
480                vec![1.0, 3.0, 5.0, 7.0, 9.0]
481            );
482        }
483    }
484
485    #[test]
486    fn test_slice() {
487        let data = [[1.0; 4], [2.0; 4], [0.0; 4]];
488        let block = StackedView::from_slice_limited(&data, 2, 3);
489
490        assert!(block.frame_slice(0).is_none());
491
492        assert_eq!(block.channel_slice(0).unwrap(), &[1.0; 3]);
493        assert_eq!(block.channel_slice(1).unwrap(), &[2.0; 3]);
494    }
495
496    #[test]
497    #[should_panic]
498    #[no_sanitize_realtime]
499    fn test_slice_out_of_bounds() {
500        let data = [[1.0; 4], [2.0; 4], [0.0; 4]];
501        let block = StackedView::from_slice_limited(&data, 2, 3);
502
503        block.channel_slice(2);
504    }
505
506    #[test]
507    fn test_raw_data() {
508        let vec = vec![vec![0.0, 2.0, 4.0, 6.0, 8.0], vec![1.0, 3.0, 5.0, 7.0, 9.0]];
509        let block = StackedView::from_slice(&vec);
510
511        assert_eq!(block.layout(), crate::BlockLayout::Stacked);
512
513        assert_eq!(block.raw_data(Some(0)), &[0.0, 2.0, 4.0, 6.0, 8.0]);
514        assert_eq!(block.raw_data(Some(1)), &[1.0, 3.0, 5.0, 7.0, 9.0]);
515    }
516}