Buffer Management
How audio buffers are allocated and managed.
Buffer Layout
Each block's outputs get contiguous buffer indices:
Block 0 (Oscillator): 1 output -> Buffer 0
Block 1 (Panner): 2 outputs -> Buffers 1, 2
Block 2 (Output): 2 outputs -> Buffers 3, 4
Pre-Allocation
Buffers are allocated when blocks are added:
#![allow(unused)] fn main() { pub fn add_block(&mut self, block: BlockType<S>) -> BlockId { let block_id = BlockId(self.blocks.len()); // Record where this block's buffers start self.block_buffer_start.push(self.audio_buffers.len()); self.blocks.push(block); // Allocate buffers for each output let output_count = self.blocks[block_id.0].output_count(); for _ in 0..output_count { self.audio_buffers.push(AudioBuffer::new(self.buffer_size)); } block_id } }
Buffer Indexing
Fast O(1) lookup:
#![allow(unused)] fn main() { fn get_buffer_index(&self, block_id: BlockId, output_index: usize) -> usize { self.block_buffer_start[block_id.0] + output_index } }
Connection Lookup
Input connections are pre-computed for O(1) access:
#![allow(unused)] fn main() { // Pre-computed during prepare_for_playback() self.block_input_buffers = vec![Vec::new(); self.blocks.len()]; for conn in &self.connections { let buffer_idx = self.get_buffer_index(conn.from, conn.from_output); self.block_input_buffers[conn.to.0].push(buffer_idx); } // During processing - O(1) lookup let input_indices = &self.block_input_buffers[block_id.0]; }
Buffer Clearing
All buffers are zeroed at the start of each processing cycle:
#![allow(unused)] fn main() { for buffer in &mut self.audio_buffers { buffer.zeroize(); } }
This allows multiple connections to the same input (signals are summed).
Memory Efficiency
- Buffers are reused across processing cycles
- No allocations during
process_buffers() - Fixed memory footprint based on block count