audio_blocks/
lib.rs

1//! # audio-blocks
2//!
3//! This crate provides traits for audio blocks to generalize common problems in handling audio data, like different channel layouts, adapting between them and receiving varying number of samples.
4//! You will get [`Interleaved`], [`Sequential`] and [`Stacked`] blocks and you can select where the data is stored by choosing between owned data, views and mutable views.
5//! Owned blocks will store the data on the heap, while views can be created over slices, raw pointers or from any other block.
6//! All of them implement the [`AudioBlock`] / [`AudioBlockMut`] traits and the mutable blocks implement operations.
7//!
8//! This crate can be used in `no_std` contexts when disabling the default features. Owned blocks are stored on the heap and thus need either the `alloc` or the `std` feature.
9//! Everything in this library, except for generating new owned blocks, is real-time safe.
10//!
11//! The main problem this crate is solving is that audio data can have different formats:
12//!
13//! * **Interleaved:** `[ch0, ch1, ch0, ch1, ch0, ch1]`
14//!     * **Interpretation:** Each group of channel samples represents a frame. So, this layout stores frames one after another.
15//!     * **Terminology:** Described as “packed” or “frames first” because each time step is grouped and processed as a unit (a frame).
16//!     * **Usage:** Often used in APIs or hardware-level interfaces, where synchronized playback across channels is crucial.
17//!
18//! * **Sequential:** `[ch0, ch0, ch0, ch1, ch1, ch1]`
19//!     * **Interpretation:** All samples from `ch0` are stored first, followed by all from `ch1`, etc.
20//!     * **Terminology:** Described as “planar” or “channels first” in the sense that all data for one channel appears before any data for the next.
21//!     * **Usage:** Used in DSP pipelines where per-channel processing is easier and more efficient.
22//!
23//! * **Stacked:** `[[ch0, ch0, ch0], [ch1, ch1, ch1]]`
24//!     * **Interpretation:** Each channel has its own separate buffer or array.
25//!     * **Terminology:** Also described as “planar” or “channels first” though more specifically it’s channel-isolated buffers.
26//!     * **Usage:** Very common in real-time DSP, as it simplifies memory access and can improve SIMD/vectorization efficiency.
27//!
28//! So if you write your processor functions expecting an `impl [`AudioBlock`]<S>` you can receive any kind of audio data, no matter which layout the audio API is using.
29//! [`AudioBlock`]s can contain any type of sample that is [`Copy`], [`Default`] and `'static` which is true for all kinds of numbers.
30//!
31//! As you mostly don't want your process function to work with any kind of number type, you can write a specialized process block, expecting only `f32` samples.
32//!
33//! ```ignore
34//! fn process(block: &mut impl AudioBlockMut<f32>) {
35//!     for channel in block.channels_mut() {
36//!         for sample in channel {
37//!             *sample *= 0.5;
38//!         }
39//!     }
40//! }
41//! ```
42//!
43//! or you can write a generic process block which works on all floating point values (`f32`, `f64` and optionally `half::f16`) by using the `Float` trait from the `num` or `num_traits` crate:
44//!
45//! ```ignore
46//! use num_traits::Float;
47//!
48//! fn process<F: Float>(block: &mut impl AudioBlockMut<F>) {
49//!     let gain = F::from(0.5).unwrap();
50//!     for channel in block.channels_mut() {
51//!         for sample in channel {
52//!             *sample *= gain;
53//!         }
54//!     }
55//! }
56//! ```
57//!
58//! Access to the audio data can be achieved with the iterators [`AudioBlock::channels()`] or [`AudioBlock::frames()`], by accessing a specific one with [`AudioBlock::channel()`] or [`AudioBlock::frame()`] and accessing only one value with [`AudioBlock::sample()`].
59//! Iterating over frames can be faster for interleaved data, while iterating over channels is always faster for sequential or stacked data.
60//!
61//! ## All Trait Functions
62//!
63//! ### [`AudioBlock`]
64//!
65//! ```ignore
66//! fn num_channels(&self) -> u16;
67//! fn num_frames(&self) -> usize;
68//! fn num_channels_allocated(&self) -> u16;
69//! fn num_frames_allocated(&self) -> usize;
70//! fn sample(&self, channel: u16, frame: usize) -> S;
71//! fn channel(&self, channel: u16) -> impl Iterator<Item = &S>;
72//! fn channels(&self) -> impl Iterator<Item = impl Iterator<Item = &S> + '_> + '_;
73//! fn frame(&self, frame: usize) -> impl Iterator<Item = &S>;
74//! fn frames(&self) -> impl Iterator<Item = impl Iterator<Item = &S> + '_> + '_;
75//! fn view(&self) -> impl AudioBlock<S>;
76//! fn layout(&self) -> BlockLayout;
77//! fn raw_data(&self, stacked_ch: Option<u16>) -> &[S];
78//! ```
79//! ### [`AudioBlockMut`]
80//!
81//! contains all of the non-mutable functions plus:
82//!
83//! ```ignore
84//! fn resize(&mut self, num_channels: u16, num_frames: usize);
85//! fn sample_mut(&mut self, channel: u16, frame: usize) -> &mut S;
86//! fn channel_mut(&mut self, channel: u16) -> impl Iterator<Item = &mut S>;
87//! fn channels_mut(&mut self) -> impl Iterator<Item = impl Iterator<Item = &mut S> + '_> + '_;
88//! fn frame_mut(&mut self, frame: usize) -> impl Iterator<Item = &mut S>;
89//! fn frames_mut(&mut self) -> impl Iterator<Item = impl Iterator<Item = &mut S> + '_> + '_;
90//! fn view_mut(&mut self) -> impl AudioBlockMut<S>;
91//! fn raw_data_mut(&mut self, stacked_ch: Option<u16>) -> &mut [S];
92//! ```
93//!
94//! ## Operations
95//!
96//! There are multiple operations defined on audio blocks, which allow copying data between them and applying an operation on each sample.
97//!
98//! ```ignore
99//! fn copy_from_block(&mut self, block: &impl AudioBlock<S>);
100//! fn copy_from_block_resize(&mut self, block: &impl AudioBlock<S>);
101//! fn for_each(&mut self, f: impl FnMut(&mut S));
102//! fn for_each_including_non_visible(&mut self, f: impl FnMut(&mut S));
103//! fn enumerate(&mut self, f: impl FnMut(u16, usize, &mut S));
104//! fn enumerate_including_non_visible(&mut self, f: impl FnMut(u16, usize, &mut S));
105//! fn fill_with(&mut self, sample: S);
106//! fn clear(&mut self);
107//! ```
108//!
109//! ## Create Audio Blocks
110//!
111//! ### Owned
112//!
113//! Types:
114//! * [`Interleaved`]
115//! * [`Sequential`]
116//! * [`Stacked`]
117//!
118//! ```ignore
119//! fn new(num_channels: u16, num_frames: usize) -> Self;
120//! fn from_block(block: &impl AudioBlock<S>) -> Self;
121//! ```
122//!
123//! ### Views
124//!
125//! Types:
126//! * [`InterleavedView`] / [`InterleavedViewMut`]
127//! * [`SequentialView`] / [`SequentialViewMut`]
128//! * [`StackedView`] / [`StackedViewMut`]
129//!
130//! ```ignore
131//! fn from_slice(data: &'a [S], num_channels: u16, num_frames: usize) -> Self;
132//! fn from_slice_limited(data: &'a [S], num_channels_visible: u16, num_frames_visible: usize, num_channels_allocated: u16, num_frames_allocated: usize) -> Self;
133//! ```
134//!
135//! Interleaved and sequential blocks can be directly generated from pointers:
136//!
137//! ```ignore
138//! unsafe fn from_ptr(data: *const S, num_channels: u16, num_frames: usize) -> Self;
139//! unsafe fn from_ptr_limited(data: *const S, num_channels_visible: u16, num_frames_visible: usize, num_channels_allocated: u16, num_frames_allocated: usize) -> Self;
140//! ```
141//!
142//! Stacked blocks can only be generated from pointers using [`StackedPtrAdapter`]:
143//!
144//! ```ignore
145//! let mut adapter = unsafe { StackedPtrAdapter::<_, 16>::from_ptr(data, num_channels, num_frames) };
146//! let block = adapter.stacked_view();
147//! ```
148//!
149//! ## Handling Varying Number of Frames
150//!
151//! The number of samples in audio buffers coming from audio APIs can vary with each process call and often only the maximum number of frames is given.
152//! This is the reason why all blocks have a number of **allocated** frames and channels and **visible** frames and channels.
153//!
154//! With the function [`AudioBlockMut::resize()`] the buffers can be resized as long as they do not grow larger than the allocated memory. Resize is always real-time safe!
155//! When using the [`Ops::copy_from_block_resize()`] function, the destination block will automatically adapt the size of the source block.
156//! For views the `from_slice_limited` or `from_ptr_limited` functions will provide you with a way to directly limit the visible data of the underlying memory.
157//!
158//! Here you see how you adapt your block size to incoming blocks with changing sizes if you need to copy the data for any reason:
159//!
160//! ```ignore
161//! fn process(&mut self, other_block: &mut impl AudioBlock<f32>) {
162//!     self.block.copy_from_block_resize(other_block);
163//! }
164//! ```
165//!
166//! ## Performance Optimizations
167//!
168//! The most performant way to iterate over blocks will be by accessing the raw data. But this can be dangerous because in limited blocks it will retrieve samples that
169//! are not meant to be visible and you have to figure out the data layout. For simple operations that do not depend on other samples like applying a gain, doing so for all samples,
170//! can be faster (if the amount of invisible samples is not exceptionally high). In the [`AudioBlockMut`] trait you will find [`Ops::for_each()`] and [`Ops::for_each_including_non_visible()`] or [`Ops::enumerate()`] and [`Ops::enumerate_including_non_visible()`] for this reason.
171//!
172//! If you use the iterators [`AudioBlock::channels()`] or [`AudioBlock::frames()`] it depends on the layout which one will be more performant.
173//! For [`Sequential`] and [`Stacked`] it will be always faster to iterate over the channels, for [`Interleaved`] with higher channel counts it can be faster to iterate over frames.
174//!
175//! The layout of a block can be retrieved using the [`AudioBlock::layout()`] function.
176
177#![cfg_attr(not(feature = "std"), no_std)] // enable std library when feature std is provided
178#![cfg_attr(not(test), no_std)] // activate std library only for tests
179
180#[cfg(all(feature = "alloc", not(feature = "std")))]
181extern crate alloc;
182
183#[cfg(not(feature = "std"))]
184extern crate core as std;
185
186#[cfg(feature = "std")]
187extern crate std;
188
189pub use ops::Ops;
190
191#[cfg(any(feature = "std", feature = "alloc"))]
192pub use interleaved::Interleaved;
193pub use interleaved::InterleavedView;
194pub use interleaved::InterleavedViewMut;
195
196#[cfg(any(feature = "std", feature = "alloc"))]
197pub use sequential::Sequential;
198pub use sequential::SequentialView;
199pub use sequential::SequentialViewMut;
200
201#[cfg(any(feature = "std", feature = "alloc"))]
202pub use stacked::Stacked;
203pub use stacked::StackedPtrAdapter;
204pub use stacked::StackedPtrAdapterMut;
205pub use stacked::StackedView;
206pub use stacked::StackedViewMut;
207
208pub mod interleaved;
209mod iter;
210pub mod ops;
211pub mod sequential;
212pub mod stacked;
213
214/// Represents the memory layout of audio data returned by [`AudioBlock::layout`].
215///
216/// This enum allows consumers to determine the underlying data layout, which is essential for:
217/// - Direct raw data access
218/// - Performance optimizations
219/// - Efficient interfacing with external audio APIs
220///
221/// # Examples of layouts
222///
223/// Each variant represents a common pattern used in audio processing.
224#[derive(PartialEq, Debug)]
225pub enum BlockLayout {
226    /// Samples from different channels alternate in sequence.
227    ///
228    /// Format: `[ch0, ch1, ..., ch0, ch1, ..., ch0, ch1, ...]`
229    ///
230    /// This layout is common in consumer audio formats and some APIs.
231    Interleaved,
232
233    /// All samples from one channel appear consecutively before the next channel.
234    ///
235    /// Format: `[ch0, ch0, ch0, ..., ch1, ch1, ch1, ...]`
236    ///
237    /// Also known as "planar" format in some audio libraries.
238    Sequential,
239
240    /// Channels are separated into discrete chunks of memory.
241    ///
242    /// Format: `[[ch0, ch0, ch0, ...], [ch1, ch1, ch1, ...]]`
243    ///
244    /// Useful for operations that work on one channel at a time.
245    Stacked,
246}
247
248/// Represents a sample type that can be stored and processed in audio blocks.
249///
250/// This trait is automatically implemented for any type that meets the following requirements:
251/// - `Copy`: The type can be copied by value efficiently
252/// - `Default`: The type has a reasonable default/zero value
253/// - `'static`: The type doesn't contain any non-static references
254///
255/// All numeric types (f32, f64, i16, i32, etc.) automatically implement this trait,
256/// as well as any custom types that satisfy these bounds.
257pub trait Sample: Copy + Default + 'static {}
258impl<T> Sample for T where T: Copy + Default + 'static {}
259
260/// Core trait for audio data access operations across various memory layouts.
261///
262/// [`AudioBlock`] provides a unified interface for interacting with audio data regardless of its
263/// underlying memory representation ([`BlockLayout::Interleaved`], [`BlockLayout::Sequential`], or [`BlockLayout::Stacked`]). It supports operations
264/// on both owned audio blocks and temporary views.
265///
266/// # Usage
267///
268/// This trait gives you multiple ways to access audio data:
269/// - Direct sample access via indices
270/// - Channel and frame iterators for processing data streams
271/// - Raw data access for optimized operations
272/// - Layout information for specialized handling
273///
274/// # Example
275///
276/// ```
277/// use audio_blocks::AudioBlock;
278///
279/// fn example(audio: &impl AudioBlock<f32>) {
280///     // Get number of channels and frames
281///     let channels = audio.num_channels();
282///     let frames = audio.num_frames();
283///
284///     // Access individual samples
285///     let first_sample = audio.sample(0, 0);
286///
287///     // Process one channel
288///     for sample in audio.channel(0) {
289///         // work with each sample
290///     }
291///
292///     // Process all channels
293///     for channel in audio.channels() {
294///         for sample in channel {
295///             // Apply processing to each sample
296///         }
297///     }
298/// }
299/// ```
300pub trait AudioBlock<T: Sample> {
301    /// Returns the number of active audio channels.
302    fn num_channels(&self) -> u16;
303
304    /// Returns the number of audio frames (samples per channel).
305    fn num_frames(&self) -> usize;
306
307    /// Returns the total number of channels allocated in memory.
308    ///
309    /// This may be greater than `num_channels()` if the buffer has reserved capacity.
310    fn num_channels_allocated(&self) -> u16;
311
312    /// Returns the total number of frames allocated in memory.
313    ///
314    /// This may be greater than `num_frames()` if the buffer has reserved capacity.
315    fn num_frames_allocated(&self) -> usize;
316
317    /// Returns the sample value at the specified channel and frame position.
318    ///
319    /// # Panics
320    ///
321    /// Panics if channel or frame indices are out of bounds.
322    fn sample(&self, channel: u16, frame: usize) -> T;
323
324    /// Returns an iterator over all samples in the specified channel.
325    ///
326    /// # Panics
327    ///
328    /// Panics if channel index is out of bounds.
329    fn channel(&self, channel: u16) -> impl Iterator<Item = &T>;
330
331    /// Returns an iterator that yields iterators for each channel.
332    fn channels(&self) -> impl Iterator<Item = impl Iterator<Item = &T> + '_> + '_;
333
334    /// Returns an iterator over all samples in the specified frame (across all channels).
335    ///
336    /// # Panics
337    ///
338    /// Panics if frame index is out of bounds.
339    fn frame(&self, frame: usize) -> impl Iterator<Item = &T>;
340
341    /// Returns an iterator that yields iterators for each frame.
342    fn frames(&self) -> impl Iterator<Item = impl Iterator<Item = &T> + '_> + '_;
343
344    /// Creates a non-owning view of this audio block.
345    ///
346    /// This operation is zero-cost (no allocation or copying) and real-time safe,
347    /// as it returns a lightweight wrapper around the original data.
348    fn view(&self) -> impl AudioBlock<T>;
349
350    /// Returns the memory layout of this audio block (interleaved, sequential, or stacked).
351    fn layout(&self) -> BlockLayout;
352
353    /// Provides direct access to the underlying memory as a slice.
354    ///
355    /// # Parameters
356    ///
357    /// * `stacked_ch` - For `Layout::Stacked`, specifies which channel to access (required).
358    ///   For other layouts, this parameter is ignored.
359    ///
360    /// # Returns
361    ///
362    /// A slice containing all allocated data, including any reserved capacity beyond
363    /// the visible/active range. The data format follows the block's layout:
364    /// - For `Interleaved`: returns interleaved samples across all channels
365    /// - For `Sequential`: returns planar data with all channels
366    /// - For `Stacked`: returns data for the specified channel only
367    fn raw_data(&self, stacked_ch: Option<u16>) -> &[T];
368}
369
370/// Extends the [`AudioBlock`] trait with mutable access operations.
371///
372/// [`AudioBlockMut`] provides methods for modifying audio data across different memory layouts.
373/// It enables in-place processing, buffer resizing, and direct mutable access to the underlying data.
374///
375/// # Usage
376///
377/// This trait gives you multiple ways to modify audio data:
378/// - Change individual samples at specific positions
379/// - Iterate through and modify channels or frames
380/// - Resize the buffer to accommodate different audio requirements
381/// - Access raw data for optimized processing
382///
383/// # Example
384///
385/// ```
386/// use audio_blocks::{AudioBlock, AudioBlockMut};
387///
388/// fn process_audio(audio: &mut impl AudioBlockMut<f32>) {
389///     // Resize to 2 channels, 1024 frames
390///     audio.resize(2, 1024);
391///
392///     // Modify individual samples
393///     *audio.sample_mut(0, 0) = 0.5;
394///
395///     // Process one channel with mutable access
396///     for sample in audio.channel_mut(0) {
397///         *sample *= 0.8; // Apply gain reduction
398///     }
399///
400///     // Process all channels
401///     for mut channel in audio.channels_mut() {
402///         for sample in channel {
403///             // Apply processing to each sample
404///         }
405///     }
406/// }
407/// ```
408pub trait AudioBlockMut<T: Sample>: AudioBlock<T> {
409    /// Resizes the audio block to the specified number of channels and frames.
410    ///
411    /// This operation is real-time safe but only works up to [`AudioBlock::num_channels_allocated`]
412    /// and [`AudioBlock::num_frames_allocated`]. Attempting to resize beyond the allocated capacity
413    /// will have implementation-dependent behavior.
414    ///
415    /// # Panics
416    ///
417    /// This function may panic when attempting to resize beyond the allocated capacity
418    /// (`num_channels_allocated` and `num_frames_allocated`).
419    fn resize(&mut self, num_channels: u16, num_frames: usize);
420
421    /// Returns a mutable reference to the sample at the specified channel and frame position.
422    ///
423    /// # Panics
424    ///
425    /// Panics if channel or frame indices are out of bounds.
426    fn sample_mut(&mut self, channel: u16, frame: usize) -> &mut T;
427
428    /// Returns a mutable iterator over all samples in the specified channel.
429    ///
430    /// # Panics
431    ///
432    /// Panics if channel index is out of bounds.
433    fn channel_mut(&mut self, channel: u16) -> impl Iterator<Item = &mut T>;
434
435    /// Returns a mutable iterator that yields mutable iterators for each channel.
436    fn channels_mut(&mut self) -> impl Iterator<Item = impl Iterator<Item = &mut T> + '_> + '_;
437
438    /// Returns a mutable iterator over all samples in the specified frame (across all channels).
439    ///
440    /// # Panics
441    ///
442    /// Panics if frame index is out of bounds.
443    fn frame_mut(&mut self, frame: usize) -> impl Iterator<Item = &mut T>;
444
445    /// Returns a mutable iterator that yields mutable iterators for each frame.
446    fn frames_mut(&mut self) -> impl Iterator<Item = impl Iterator<Item = &mut T> + '_> + '_;
447
448    /// Creates a non-owning mutable view of this audio block.
449    ///
450    /// This operation is zero-cost (no allocation or copying) and real-time safe,
451    /// as it returns a lightweight wrapper around the original data.
452    fn view_mut(&mut self) -> impl AudioBlockMut<T>;
453
454    /// Provides direct mutable access to the underlying memory as a slice.
455    ///
456    /// # Parameters
457    ///
458    /// * `stacked_ch` - For `BlockLayout::Stacked`, specifies which channel to access (required).
459    ///   For other layouts, this parameter is ignored.
460    ///
461    /// # Returns
462    ///
463    /// A mutable slice containing all allocated data, including any reserved capacity beyond
464    /// the visible/active range. The data format follows the block's layout:
465    /// - For `Interleaved`: returns interleaved samples across all channels
466    /// - For `Sequential`: returns planar data with all channels
467    /// - For `Stacked`: returns data for the specified channel only
468    fn raw_data_mut(&mut self, stacked_ch: Option<u16>) -> &mut [T];
469}