Events
Events are notifications that your custom node can emit to communicate state changes, analysis results, or other important occurrences to the application layer. Unlike properties (which are polled) and actions (which are invoked), events are pushed from your node when something significant happens.
Why Use Events?
Events enable:
- Asynchronous notifications - Inform the application when something happens
- State change reporting - Signal when important state transitions occur
- Analysis results - Report analysis findings (beat detection, voice activity, etc.)
- Progress updates - Notify about long-running operations
- Error reporting - Communicate errors or warnings to the application
- Timing signals - Emit periodic or triggered timing events
Registering Events
Events are registered in your node's constructor using the registerEvent() method. Each event requires a unique name, a description, and optional data fields that describe what information will be sent with the event.
Basic Example (No Data)
Here's a simple event with no associated data:
class TimerNode : public SingleBusAudioSinkNode {
public:
TimerNode() {
type = "TimerNode";
registerEvent(
"tick",
{
{ EVENT_FIELD_NAME, "tick" },
{ EVENT_FIELD_DESCRIPTION, "Emitted at each interval." }
}
);
}
bool consume(AudioBus& bus) override {
// Timer logic...
if (shouldEmitTick) {
emitEvent("tick", SBAnyMap());
}
return true;
}
};
Example with Data
Here's an event that includes data:
class CounterNode : public Node {
public:
CounterNode() {
type = "CounterNode";
registerEvent(
"valueChanged",
{
{ EVENT_FIELD_DESCRIPTION, "Emitted when the counter value changes." },
{ EVENT_FIELD_DATA,
SBAnyMap({
{ "value",
SBAnyMap({
{ EVENT_DATA_FIELD_NAME, "value" },
{ EVENT_DATA_FIELD_DESCRIPTION, "The new value of the counter." },
{ EVENT_DATA_FIELD_TYPE, EVENT_DATA_TYPE_INT }
})
}
})
}
}
);
}
void increment() {
++value;
emitValueChangedEvent();
}
private:
void emitValueChangedEvent() {
const SBAnyMap eventData = { { "value", value.load() } };
emitEvent("valueChanged", eventData);
}
std::atomic<int> value;
};
Event Metadata Fields
Events are described using a metadata map with the following fields:
Required Fields
| Field | Constant | Description |
|---|---|---|
| Name | EVENT_FIELD_NAME | Unique identifier for the event |
| Description | EVENT_FIELD_DESCRIPTION | Human-readable description of when/why the event is emitted |
Optional Fields
| Field | Constant | Description |
|---|---|---|
| Data | EVENT_FIELD_DATA | Map of data field definitions (see below) |
Event Data Fields
Each data field in the EVENT_FIELD_DATA map is defined with:
| Field | Constant | Description |
|---|---|---|
| Name | EVENT_DATA_FIELD_NAME | Field name (must match the key in the map) |
| Description | EVENT_DATA_FIELD_DESCRIPTION | Human-readable description of the field |
| Type | EVENT_DATA_FIELD_TYPE | Data type (see below) |
Supported Data Types
EVENT_DATA_TYPE_BOOLEAN- Boolean value (true/false)EVENT_DATA_TYPE_FLOAT- Floating point numberEVENT_DATA_TYPE_INT- Integer numberEVENT_DATA_TYPE_STRING- String value
Emitting Events
To emit an event, use the emitEvent() method with the event name and optional data:
// Event with no data
emitEvent("tick", SBAnyMap());
// Event with data
emitEvent("valueChanged", SBAnyMap({ { "value", 42 } }));
// Event with multiple data fields
emitEvent("beatDetected", SBAnyMap({
{ "timestamp", getCurrentTime() },
{ "confidence", 0.95f },
{ "bpm", 120 }
}));
Complete Examples
Example 1: Timer Node
A timer node that emits periodic events:
class TimerNode : public SingleBusAudioSinkNode {
public:
TimerNode() : intervalMs(1000), frameCounter(0) {
type = "TimerNode";
registerEvent(
"tick",
{
{ EVENT_FIELD_NAME, "tick" },
{ EVENT_FIELD_DESCRIPTION, "Emitted at each interval." }
}
);
}
bool setBusFormat(AudioBusFormat& busFormat) override {
sampleRate = busFormat.sampleRate;
return true;
}
bool consume(AudioBus& bus) override {
const uint numFrames = bus.getBuffer()->getNumberOfFrames();
frameCounter += numFrames;
const uint eventIntervalFrames = sampleRate * intervalMs / 1000;
if (frameCounter >= eventIntervalFrames) {
emitEvent("tick", SBAnyMap());
frameCounter -= eventIntervalFrames;
}
return true;
}
private:
uint intervalMs;
uint frameCounter;
uint sampleRate;
};
Example 2: Beat Detector Node
A beat detector that emits events when beats are detected:
class BeatDetectorNode : public SingleBusAudioSinkNode {
public:
BeatDetectorNode() {
type = "BeatDetectorNode";
registerEvent(
"beatDetected",
{
{ EVENT_FIELD_DESCRIPTION, "Emitted when a beat is detected in the audio." },
{ EVENT_FIELD_DATA,
SBAnyMap({
{ "timestamp",
SBAnyMap({
{ EVENT_DATA_FIELD_NAME, "timestamp" },
{ EVENT_DATA_FIELD_DESCRIPTION, "Time when the beat was detected in samples" },
{ EVENT_DATA_FIELD_TYPE, EVENT_DATA_TYPE_INT }
})
},
{ "energy",
SBAnyMap({
{ EVENT_DATA_FIELD_NAME, "energy" },
{ EVENT_DATA_FIELD_DESCRIPTION, "Energy level of the detected beat" },
{ EVENT_DATA_FIELD_TYPE, EVENT_DATA_TYPE_FLOAT }
})
},
{ "confidence",
SBAnyMap({
{ EVENT_DATA_FIELD_NAME, "confidence" },
{ EVENT_DATA_FIELD_DESCRIPTION, "Detection confidence (0.0 to 1.0)" },
{ EVENT_DATA_FIELD_TYPE, EVENT_DATA_TYPE_FLOAT }
})
}
})
}
}
);
}
bool consume(AudioBus& bus) override {
// Beat detection algorithm...
if (beatDetected) {
emitEvent("beatDetected", SBAnyMap({
{ "timestamp", currentSamplePosition },
{ "energy", beatEnergy },
{ "confidence", detectionConfidence }
}));
}
return true;
}
private:
int currentSamplePosition = 0;
float beatEnergy = 0.0f;
float detectionConfidence = 0.0f;
bool beatDetected = false;
};
Example 3: State Change Events
Emit events when node state changes:
class RecorderNode : public SingleBusAudioSinkNode {
public:
RecorderNode() {
type = "RecorderNode";
registerEvent(
"recordingStarted",
{
{ EVENT_FIELD_DESCRIPTION, "Emitted when recording starts." }
}
);
registerEvent(
"recordingStopped",
{
{ EVENT_FIELD_DESCRIPTION, "Emitted when recording stops." },
{ EVENT_FIELD_DATA,
SBAnyMap({
{ "filePath",
SBAnyMap({
{ EVENT_DATA_FIELD_NAME, "filePath" },
{ EVENT_DATA_FIELD_DESCRIPTION, "Path where the recording was saved" },
{ EVENT_DATA_FIELD_TYPE, EVENT_DATA_TYPE_STRING }
})
},
{ "duration",
SBAnyMap({
{ EVENT_DATA_FIELD_NAME, "duration" },
{ EVENT_DATA_FIELD_DESCRIPTION, "Duration of the recording in seconds" },
{ EVENT_DATA_FIELD_TYPE, EVENT_DATA_TYPE_FLOAT }
})
}
})
}
}
);
}
void startRecording() {
isRecording = true;
emitEvent("recordingStarted", SBAnyMap());
}
void stopRecording(const std::string& path, float duration) {
isRecording = false;
emitEvent("recordingStopped", SBAnyMap({
{ "filePath", path },
{ "duration", duration }
}));
}
private:
bool isRecording = false;
};
When to Emit Events
From Audio Thread (process/consume/produce)
You can emit events from the audio thread for:
- Real-time analysis results (beat detection, onset detection)
- Periodic timing signals
- Threshold crossings or condition changes
Important: Keep event emission lightweight. Avoid heavy processing or complex data structures.
From Action Handlers
You can emit events from action handlers for:
- Operation completion notifications
- State change confirmations
- Error reporting
From Internal Methods
You can emit events from any method for:
- State transitions
- Progress updates
- Warnings or errors
Best Practices
- Keep it lightweight - Emitting events should be fast, especially from the audio thread
- Meaningful names - Use clear, descriptive event names (e.g.,
beatDetected, notevent1) - Include relevant data - Provide useful information with events (timestamps, values, etc.)
- Document when events fire - Clearly describe the conditions that trigger each event
- Avoid excessive emission - Don't emit events for every sample or frame
- Use appropriate types - Choose the correct data type for each field
- Handle errors gracefully - Events can fail silently; don't rely on them for critical logic
Thread Safety
Events can be emitted from any thread. The SDK handles thread-safe delivery of events to listeners. However:
- Keep event data simple - Avoid complex objects or shared pointers
- Copy data, don't reference - Event data should be value types, not references
- Avoid locks - Don't hold locks while emitting events
Common Use Cases
- Timing signals - Periodic ticks, beats, tempo changes
- Analysis results - Beat detection, pitch detection, onset detection, voice activity
- State changes - Recording started/stopped, playback started/stopped, mode changes
- Progress updates - File loading progress, processing progress
- Threshold events - Level exceeded, silence detected, clipping detected
- Errors and warnings - Buffer underruns, invalid state, resource errors
Next Steps
- Extensions - Learn how to package your custom node as a distributable extension