Adding New Node
Subclassing AudioNode
The first step in creating a custom node is choosing and subclassing the appropriate base class. Switchboard SDK provides three core AudioNode types, each designed for a specific role in the audio graph:
Available Base Classes
Switchboard SDK provides several base classes to choose from, depending on your node's requirements:
| Base Class | Inputs | Outputs | Processing Method | Best For |
|---|---|---|---|---|
AudioSourceNode | ❌ No | ✅ Yes | produce(AudioBusList&) | Complex audio generators with multiple output buses (e.g., multi-track players) |
AudioProcessorNode | ✅ Yes | ✅ Yes | process(AudioBusList&, AudioBusList&) | Complex processors with multiple input/output buses (e.g., multi-band processors, advanced mixers) |
AudioSinkNode | ✅ Yes | ❌ No | consume(AudioBusList&) | Complex audio consumers with multiple input buses (e.g., multi-channel recorders) |
SingleBusAudioSourceNode | ❌ No | ✅ Yes | produce(AudioBus&) | Most common for sources - Simple audio generators (e.g., oscillators, file players) |
SingleBusAudioProcessorNode | ✅ Yes | ✅ Yes | process(AudioBus&, AudioBus&) | Most common for processors - Standard audio effects and filters (e.g., gain, EQ, reverb) |
SingleBusAudioSinkNode | ✅ Yes | ❌ No | consume(AudioBus&) | Most common for sinks - Simple audio consumers (e.g., audio output, analyzers) |
Start with Single Bus variants - Most custom nodes only need a single input and/or output bus. The SingleBus* classes provide a simplified API that's easier to implement and maintain. Only use the multi-bus variants if you specifically need to handle multiple buses.
Common Use Cases by Type
Source Nodes (Generate Audio)
- Audio file players
- Signal generators (oscillators, noise generators)
- Microphone input nodes
- Network audio receivers
- Text-to-speech engines
Processor Nodes (Transform Audio)
- Audio effects (reverb, delay, distortion, EQ)
- Filters (low-pass, high-pass, band-pass)
- Dynamics processors (compressors, limiters, gates)
- Pitch shifters and time stretchers
- Mixing and routing logic
Sink Nodes (Consume Audio)
- Audio output to speakers/headphones
- Audio file recorders
- Network audio transmitters
- Audio analyzers (FFT, level meters)
- Audio-to-text transcription
Example Implementation
Here's a complete example of a custom processor node that implements a simple low-pass filter using the recommended SingleBusAudioProcessorNode base class:
#include <switchboard_core/SingleBusAudioProcessorNode.hpp>
class LowPassFilterNode : public SingleBusAudioProcessorNode {
public:
LowPassFilterNode() : previousSample(0.0f) {
type = "LowPassFilterNode";
}
// Set the bus format (covered in the next section)
bool setBusFormat(AudioBusFormat& inputBusFormat, AudioBusFormat& outputBusFormat) override {
// Typically, processors match input and output formats
return AudioBusFormat::matchBusFormats(inputBusFormat, outputBusFormat);
}
// Process audio data (covered in detail later)
bool process(AudioBus& inBus, AudioBus& outBus) override {
AudioBuffer<float>& inBuffer = *inBus.getBuffer();
AudioBuffer<float>& outBuffer = *outBus.getBuffer();
const uint numFrames = inBuffer.getNumberOfFrames();
const uint numChannels = inBuffer.getNumberOfChannels();
for (uint frame = 0; frame < numFrames; ++frame) {
for (uint channel = 0; channel < numChannels; ++channel) {
float input = inBuffer.getSample(channel, frame);
float output = (input + previousSample) * 0.5f;
outBuffer.setSample(channel, frame, output);
previousSample = input;
}
}
return true;
}
private:
float previousSample;
};
Key Considerations
When subclassing AudioNode, keep these important points in mind:
Real-Time Safety
Your node's processing code will run in a dedicated audio thread with strict timing requirements. You must ensure your code is real-time safe, which means:
- ❌ No memory allocation in the audio thread (no
new,malloc,std::vector::push_back, etc.) - ❌ No blocking operations (no file I/O, network calls, mutexes, or locks)
- ❌ No unbounded loops (no operations with unpredictable execution time)
- ✅ Use pre-allocated buffers and lock-free data structures
- ✅ Keep processing deterministic and fast
Violating real-time safety can cause audio glitches, dropouts, or complete audio system failure.
Thread Safety
If your node exposes properties or handles actions that can be called from the UI thread, you need to ensure thread-safe communication between the UI thread and the audio thread. Use atomic operations or lock-free data structures for parameters that change during processing.
State Management
Initialize all audio processing state (buffers, filter coefficients, internal variables) in your constructor or in a dedicated initialization method. The process() function should only update state, not allocate or initialize it.
Implementing Bus Formats
The bus format method is called by the SDK during graph initialization to negotiate audio formats between connected nodes. Single-bus nodes implement setBusFormat() with individual formats, while multi-bus nodes implement setBusFormats() with format lists.
Method signatures:
setBusFormat(AudioBusFormat&, AudioBusFormat&) // for processors
setBusFormat(AudioBusFormat&) // for sources/sinks
setBusFormats(AudioBusFormatList&, ...) // for multi-bus variants
AudioBusFormat contains:
sampleRate- Sample rate in Hz (e.g., 48000)numberOfChannels- Channel count (1=mono, 2=stereo)numberOfFrames- Buffer size in frames