Skip to main content

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

FieldConstantDescription
NameEVENT_FIELD_NAMEUnique identifier for the event
DescriptionEVENT_FIELD_DESCRIPTIONHuman-readable description of when/why the event is emitted

Optional Fields

FieldConstantDescription
DataEVENT_FIELD_DATAMap of data field definitions (see below)

Event Data Fields

Each data field in the EVENT_FIELD_DATA map is defined with:

FieldConstantDescription
NameEVENT_DATA_FIELD_NAMEField name (must match the key in the map)
DescriptionEVENT_DATA_FIELD_DESCRIPTIONHuman-readable description of the field
TypeEVENT_DATA_FIELD_TYPEData type (see below)

Supported Data Types

  • EVENT_DATA_TYPE_BOOLEAN - Boolean value (true/false)
  • EVENT_DATA_TYPE_FLOAT - Floating point number
  • EVENT_DATA_TYPE_INT - Integer number
  • EVENT_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

  1. Keep it lightweight - Emitting events should be fast, especially from the audio thread
  2. Meaningful names - Use clear, descriptive event names (e.g., beatDetected, not event1)
  3. Include relevant data - Provide useful information with events (timestamps, values, etc.)
  4. Document when events fire - Clearly describe the conditions that trigger each event
  5. Avoid excessive emission - Don't emit events for every sample or frame
  6. Use appropriate types - Choose the correct data type for each field
  7. 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