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}