Cell Architecture Documentation¶
Overview¶
The Cell Architecture provides the foundational framework for all L2 cells in the AudioLab system. It defines interfaces, base classes, and infrastructure that all cells inherit.
Core Concepts¶
What is a Cell?¶
A Cell L2 is a molecular-level DSP component that combines multiple L1 atoms into a functional unit. Think of it as:
- Atoms L1 = amino acids (basic building blocks)
- Cells L2 = proteins (complex functional units)
Examples: - Synthesis Cell: Oscillator + Filter + Envelopes + VCA = Complete synth voice - Effect Cell: Delay lines + Filters + Modulation = Complex reverb - Modulation Cell: Sources + Routing + Transformers = Modulation matrix
Key Principles¶
- Encapsulation: Internal complexity hidden behind clean interface
- Composability: Cells combine atoms, cells combine into engines
- Real-time Safety: Zero allocations, lock-free where possible
- Thread Safety: Safe parameter updates from any thread
- State Management: Complete capture/restore capability
Architecture Components¶
┌─────────────────────────────────────────────────────────────┐
│ ICellL2 │
│ (Interface all cells implement) │
└─────────────────────────────────────────────────────────────┘
↑
│ inherits
│
┌─────────────────────────────────────────────────────────────┐
│ CellBase │
│ (Base implementation with infrastructure) │
│ │
│ ┌───────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ Atom Registry │ │ Routing │ │ Parameter │ │
│ │ │ │ Matrix │ │ Mapper │ │
│ └───────────────┘ └──────────────┘ └─────────────────┘ │
│ │
│ ┌───────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ Buffer Pool │ │ Modulation │ │ Lifecycle │ │
│ │ │ │ Pool │ │ Manager │ │
│ └───────────────┘ └──────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↑
│ inherits
│
┌───────────────────┼───────────────────┐
│ │ │
┌───────────────┐ ┌────────────────┐ ┌──────────────────┐
│ SubtractiveSyn│ │ ReverbCell │ │ ModMatrixCell │
│ thCell │ │ │ │ │
└───────────────┘ └────────────────┘ └──────────────────┘
(Concrete cell) (Concrete cell) (Concrete cell)
Interface: ICellL2¶
The ICellL2 interface defines the contract all cells must fulfill.
Lifecycle Methods¶
// Prepare for processing
void prepare(double sampleRate, int blockSize);
// Reset to initial state (keep resources)
void reset();
// Release all resources
void release();
// Get current state
CellLifecycleState getState() const;
Lifecycle States:
UNINITIALIZED → prepare() → PREPARED → processBlock() → PROCESSING
↓ ↓
reset() ←───────────────────────┘
↓
release() → RELEASED
Processing Methods (Real-Time Thread)¶
// Process audio block (MUST be RT-safe!)
void processBlock(AudioBuffer& buffer);
// Process MIDI events (MUST be RT-safe!)
void processMidi(MidiBuffer& midi);
Real-Time Safety Requirements: - ✅ No memory allocations - ✅ No system calls - ✅ No locks (or bounded lock-free) - ✅ Bounded execution time - ✅ No virtual calls in hot path (where possible)
Parameter Methods (Thread-Safe)¶
// Set parameter (RT-safe, can be called from any thread)
void setParameter(ParamID id, float value);
// Get parameter (thread-safe)
float getParameter(ParamID id) const;
// Get parameter metadata
ParameterRange getParameterRange(ParamID id) const;
State Methods¶
// Capture complete state
CellState captureState() const;
// Restore from captured state
void restoreState(const CellState& state);
Routing Methods¶
// Get internal routing configuration
AudioRouting getInternalRouting() const;
// Set internal routing configuration
void setInternalRouting(const AudioRouting& routing);
Performance Methods¶
// Set quality mode (Eco/Normal/Pristine/Auto)
void setQualityMode(QualityMode mode);
// Get CPU usage estimate
CPULoad estimateCPULoad() const;
Base Class: CellBase¶
CellBase provides common infrastructure that all cells inherit.
What CellBase Provides¶
- Atom Management
- Create internal atoms
- Register and access by ID
-
Automatic lifecycle coordination
-
Routing Matrix
- Connect atoms internally
- Compute processing order
-
Detect feedback loops
-
Parameter Mapping
- Map cell params → atom params
- Support scale/offset transforms
-
Automatic propagation
-
Resource Pooling
- Buffer pool for temporary storage
- Modulation source sharing
-
Automatic cleanup
-
Thread Safety
- Lock-free parameter updates
- Atomic state management
-
Safe cross-thread communication
-
Performance Monitoring
- CPU usage tracking
- Block time measurement
- Automatic quality adaptation
Creating a Custom Cell¶
Step 1: Inherit from CellBase
class MyCustomCell : public CellBase {
public:
// Define parameter IDs
enum Params : ParamID {
PARAM_GAIN = 1,
PARAM_CUTOFF = 2
};
MyCustomCell();
std::string getCellType() const override;
protected:
void buildInternalGraph() override;
void processInternal(AudioBuffer& buffer, MidiBuffer& midi) override;
};
Step 2: Configure in Constructor
MyCustomCell::MyCustomCell() {
// Configure I/O
inputChannels = 2;
outputChannels = 2;
acceptMidi = false;
produceMidi = false;
// Register parameters
ParameterRange gainParam;
gainParam.id = PARAM_GAIN;
gainParam.name = "Gain";
gainParam.unit = "dB";
gainParam.type = ParameterType::FLOAT;
gainParam.curve = ParameterCurve::LOGARITHMIC;
gainParam.minValue = -60.0f;
gainParam.maxValue = 12.0f;
gainParam.defaultValue = 0.0f;
gainParam.automatable = true;
registerParameter(gainParam);
}
Step 3: Build Internal Graph
void MyCustomCell::buildInternalGraph() {
// Create internal atoms
auto osc = createAtom<Oscillator>("osc1");
auto filter = createAtom<Filter>("filter1");
auto vca = createAtom<VCA>("vca1");
// Connect atoms
connectAtoms("osc1", 0, "filter1", 0);
connectAtoms("filter1", 0, "vca1", 0);
// Map parameters
mapParameter(PARAM_CUTOFF, "filter1", Filter::PARAM_CUTOFF);
mapParameter(PARAM_GAIN, "vca1", VCA::PARAM_GAIN);
}
Step 4: Implement Processing
void MyCustomCell::processInternal(AudioBuffer& buffer, MidiBuffer& midi) {
// CellBase handles routing atoms through the graph
// This is where you add any cell-level processing
// For most cells, atoms handle all processing
// Just need to handle cell-level state/logic here
}
Data Structures¶
AudioBuffer¶
Multi-channel audio storage:
AudioBuffer buffer(2, 512); // 2 channels, 512 samples
float* left = buffer.getChannelData(0);
float* right = buffer.getChannelData(1);
MidiBuffer¶
MIDI event storage:
MidiBuffer midi;
midi.addMessage(MidiMessage::noteOn(1, 60, 100));
for (const auto& msg : midi.getMessages()) {
if (msg.isNoteOn()) {
handleNoteOn(msg.getNote(), msg.getVelocity());
}
}
CellState¶
Complete state snapshot:
// Capture
CellState state = cell->captureState();
// Save to file
std::vector<uint8_t> data = state.serialize();
saveToFile(data);
// Load from file
std::vector<uint8_t> data = loadFromFile();
CellState state = CellState::deserialize(data);
cell->restoreState(state);
AudioRouting¶
Internal routing configuration:
AudioRouting routing;
routing.topology = RoutingTopology::SERIAL;
AudioConnection conn;
conn.sourceAtom = "osc1";
conn.sourceOutput = 0;
conn.destAtom = "filter1";
conn.destInput = 0;
conn.gain = 1.0f;
routing.connections.push_back(conn);
if (routing.isValid()) {
cell->setInternalRouting(routing);
}
Usage Examples¶
Simple Gain Cell¶
auto gainCell = std::make_unique<SimpleGainCell>();
gainCell->prepare(44100.0, 512);
gainCell->setParameter(SimpleGainCell::PARAM_GAIN, 0.5f);
AudioBuffer buffer(2, 512);
// ... fill buffer with input ...
gainCell->processBlock(buffer);
// ... buffer now contains gained output ...
Synthesis Cell with MIDI¶
auto synthCell = std::make_unique<SimpleSynthCell>();
synthCell->prepare(44100.0, 512);
synthCell->setParameter(SimpleSynthCell::PARAM_FILTER_CUTOFF, 0.7f);
MidiBuffer midi;
midi.addMessage(MidiMessage::noteOn(1, 60, 100));
AudioBuffer buffer(2, 512);
buffer.clear(); // Synth generates audio
synthCell->processMidi(midi);
synthCell->processBlock(buffer);
// ... buffer contains synthesized audio ...
State Management¶
// Save state
CellState state = cell->captureState();
std::vector<uint8_t> data = state.serialize();
savePreset("my_preset.state", data);
// Load state
std::vector<uint8_t> data = loadPreset("my_preset.state");
CellState state = CellState::deserialize(data);
cell->restoreState(state);
Performance Considerations¶
Quality Modes¶
// Eco: Minimum CPU
cell->setQualityMode(QualityMode::ECO);
// Normal: Balanced
cell->setQualityMode(QualityMode::NORMAL);
// Pristine: Maximum quality
cell->setQualityMode(QualityMode::PRISTINE);
// Auto: CPU-adaptive
cell->setQualityMode(QualityMode::AUTO);
CPU Monitoring¶
CPULoad load = cell->estimateCPULoad();
std::cout << "CPU: " << (load.percentage * 100.0f) << "%\n";
std::cout << "Avg: " << load.averageBlockTime << "ms\n";
std::cout << "Peak: " << load.peakBlockTime << "ms\n";
if (load.percentage > 0.8f) {
// Switch to eco mode
cell->setQualityMode(QualityMode::ECO);
}
Thread Safety¶
Safe Operations from Any Thread¶
// These are safe from any thread:
cell->setParameter(id, value); // RT-safe atomic update
float val = cell->getParameter(id); // Thread-safe read
CPULoad load = cell->estimateCPULoad();
Non-RT Thread Only¶
// These MUST be called from non-RT thread:
cell->prepare(sampleRate, blockSize);
cell->reset();
cell->release();
cell->setInternalRouting(routing);
CellState state = cell->captureState();
cell->restoreState(state);
RT Audio Thread Only¶
Best Practices¶
DO:¶
✅ Keep cells modular (max 10 atoms) ✅ Use CellBase infrastructure ✅ Make processInternal() RT-safe ✅ Register all parameters in constructor ✅ Use parameter mapping for atom params ✅ Validate routing in buildInternalGraph() ✅ Write comprehensive tests
DON'T:¶
❌ Allocate in processInternal() ❌ Use locks in audio thread ❌ Create monolithic cells (>10 atoms) ❌ Hardcode routing ❌ Skip state versioning ❌ Forget to check feedback loops ❌ Ignore thread safety
Next Steps¶
- Review
ICellL2.hfor complete interface - Study
CellBase.hfor base implementation - Check
examples/SimpleGainCell.hfor minimal example - Read implementation docs in
src/when available - Write tests using patterns in
tests/when available
Version: 1.0.0 Date: 2025-10-14 Status: Foundation Complete - Implementation In Progress