Stack Allocation Strategy

How bbx_audio avoids heap allocations during processing.

The Problem

Audio processing must avoid heap allocation because:

  • malloc()/free() can take unbounded time
  • System allocator may lock
  • Heap fragmentation over time

StackVec Solution

StackVec<T, N> provides vector-like behavior with stack storage:

#![allow(unused)]
fn main() {
const MAX_BLOCK_INPUTS: usize = 8;
let mut input_slices: StackVec<&[S], MAX_BLOCK_INPUTS> = StackVec::new();

for &index in input_indices {
    input_slices.push_unchecked(/* slice */);
}
}

Fixed Capacity Limits

Blocks are limited to reasonable I/O counts:

#![allow(unused)]
fn main() {
const MAX_BLOCK_INPUTS: usize = 8;
const MAX_BLOCK_OUTPUTS: usize = 8;
}

These limits are validated during build():

#![allow(unused)]
fn main() {
assert!(
    connected_inputs <= MAX_BLOCK_INPUTS,
    "Block {idx} has too many inputs"
);
}

Pre-Allocation Pattern

Storage allocated once, reused forever:

#![allow(unused)]
fn main() {
// In Graph struct
audio_buffers: Vec<AudioBuffer<S>>,
modulation_values: Vec<S>,

// In prepare_for_playback()
self.modulation_values.resize(self.blocks.len(), S::ZERO);

// In process_buffers() - just clear, no resize
for buffer in &mut self.audio_buffers {
    buffer.zeroize();
}
}

Trade-offs

Advantages

  • Zero allocations during processing
  • Predictable memory layout
  • No allocator contention

Disadvantages

  • Fixed maximum I/O counts
  • Higher upfront memory usage
  • Must know limits at compile time