Skip to main content

Actions

Actions are methods that can be invoked on your custom node from the application layer. Unlike properties which represent state, actions represent operations or commands that your node can execute.

Why Use Actions?

Actions enable:

  • Triggering operations - Load files, start/stop processing, reset state
  • Command execution - Execute discrete operations that aren't simple property changes
  • Complex operations - Operations that may have side effects or require validation
  • Parameterized methods - Methods that accept multiple typed parameters
  • Asynchronous operations - Trigger operations that may take time to complete

Registering Actions

Actions are registered in your node's constructor using the registerAction() method. Each action requires a unique name, a description, optional parameters, and a handler function.

Basic Example (No Parameters)

Here's a simple action with no parameters:

class RecorderNode : public SingleBusAudioSinkNode {
public:
RecorderNode() {
type = "RecorderNode";

registerAction(
"start",
{
{ ACTION_FIELD_DESCRIPTION, "Starts the recording." },
{ ACTION_FIELD_HANDLER, std::function([this](const SBAnyMap& params) {
return handleStart(params);
}) }
}
);

registerAction(
"stop",
{
{ ACTION_FIELD_DESCRIPTION, "Stops the recording." },
{ ACTION_FIELD_HANDLER, std::function([this](const SBAnyMap& params) {
return handleStop(params);
}) }
}
);
}

private:
Result<void> handleStart(const SBAnyMap& params) {
start();
return makeSuccess();
}

Result<void> handleStop(const SBAnyMap& params) {
stop();
return makeSuccess();
}

void start() { /* Implementation */ }
void stop() { /* Implementation */ }
};

Example with Parameters

Here's an action that accepts a parameter:

registerAction(
"load",
{
{ ACTION_FIELD_DESCRIPTION, "Loads an audio file" },
{ ACTION_FIELD_PARAMETERS,
SBAnyMap({
{ "audioFilePath",
SBAnyMap({
{ ACTION_PARAMETER_FIELD_NAME, "audioFilePath" },
{ ACTION_PARAMETER_FIELD_DESCRIPTION, "The path of the audio file to load" },
{ ACTION_PARAMETER_FIELD_TYPE, ACTION_PARAMETER_TYPE_STRING },
{ ACTION_PARAMETER_FIELD_IS_OPTIONAL, false }
})
}
})
},
{ ACTION_FIELD_HANDLER, std::function([this](const SBAnyMap& params) {
return handleLoad(params);
}) }
}
);

And the corresponding handler:

Result<void> handleLoad(const SBAnyMap& params) {
if (!params.hasKey("audioFilePath")) {
return makeError("Missing required parameter: audioFilePath");
}

const auto audioFilePath = SBAny::convert<std::string>(params.at("audioFilePath"));

if (!load(audioFilePath)) {
return makeError("Failed to load audio file: " + audioFilePath);
}

return makeSuccess();
}

Action Metadata Fields

Actions are described using a metadata map with the following fields:

Required Fields

FieldConstantDescription
DescriptionACTION_FIELD_DESCRIPTIONHuman-readable description of what the action does
HandlerACTION_FIELD_HANDLERFunction that executes the action, takes const SBAnyMap&, returns Result<void>

Optional Fields

FieldConstantDescription
ParametersACTION_FIELD_PARAMETERSMap of parameter definitions (see below)

Parameter Metadata Fields

Each parameter in the ACTION_FIELD_PARAMETERS map is defined with:

FieldConstantDescription
NameACTION_PARAMETER_FIELD_NAMEParameter name (must match the key in the map)
DescriptionACTION_PARAMETER_FIELD_DESCRIPTIONHuman-readable description of the parameter
TypeACTION_PARAMETER_FIELD_TYPEParameter type (see below)
Is OptionalACTION_PARAMETER_FIELD_IS_OPTIONALWhether the parameter is optional (default: false)

Supported Parameter Types

  • ACTION_PARAMETER_TYPE_BOOLEAN - Boolean value (true/false)
  • ACTION_PARAMETER_TYPE_FLOAT - Floating point number
  • ACTION_PARAMETER_TYPE_INT - Integer number
  • ACTION_PARAMETER_TYPE_STRING - String value
  • ACTION_PARAMETER_TYPE_OBJECT - Complex object/map

Complete Example

Here's a complete example showing multiple actions with different parameter types:

class AudioPlayerNode : public SingleBusAudioSourceNode {
public:
AudioPlayerNode() {
type = "AudioPlayerNode";

// Action with required string parameter
registerAction(
"load",
{
{ ACTION_FIELD_DESCRIPTION, "Loads an audio file" },
{ ACTION_FIELD_PARAMETERS,
SBAnyMap({
{ "audioFilePath",
SBAnyMap({
{ ACTION_PARAMETER_FIELD_NAME, "audioFilePath" },
{ ACTION_PARAMETER_FIELD_DESCRIPTION, "The path of the audio file to load" },
{ ACTION_PARAMETER_FIELD_TYPE, ACTION_PARAMETER_TYPE_STRING },
{ ACTION_PARAMETER_FIELD_IS_OPTIONAL, false }
})
}
})
},
{ ACTION_FIELD_HANDLER, std::function([this](const SBAnyMap& params) {
return handleLoad(params);
}) }
}
);

// Simple action with no parameters
registerAction(
"play",
{
{ ACTION_FIELD_DESCRIPTION, "Starts the audio playback." },
{ ACTION_FIELD_HANDLER, std::function([this](const SBAnyMap& params) {
return handlePlay(params);
}) }
}
);

registerAction(
"pause",
{
{ ACTION_FIELD_DESCRIPTION, "Pauses the audio playback." },
{ ACTION_FIELD_HANDLER, std::function([this](const SBAnyMap& params) {
return handlePause(params);
}) }
}
);

registerAction(
"stop",
{
{ ACTION_FIELD_DESCRIPTION, "Stops the audio playback." },
{ ACTION_FIELD_HANDLER, std::function([this](const SBAnyMap& params) {
return handleStop(params);
}) }
}
);
}

private:
Result<void> handleLoad(const SBAnyMap& params) {
const auto audioFilePath = SBAny::convert<std::string>(params.at("audioFilePath"));
if (!load(audioFilePath)) {
return makeError("Failed to load: " + audioFilePath);
}
return makeSuccess();
}

Result<void> handlePlay(const SBAnyMap& params) {
play();
return makeSuccess();
}

Result<void> handlePause(const SBAnyMap& params) {
pause();
return makeSuccess();
}

Result<void> handleStop(const SBAnyMap& params) {
stop();
return makeSuccess();
}

bool load(const std::string& path) { /* Implementation */ }
void play() { /* Implementation */ }
void pause() { /* Implementation */ }
void stop() { /* Implementation */ }
};

Action with Multiple Parameters

Actions can accept multiple parameters of different types:

registerAction(
"seek",
{
{ ACTION_FIELD_DESCRIPTION, "Seeks to a specific position in the audio" },
{ ACTION_FIELD_PARAMETERS,
SBAnyMap({
{ "position",
SBAnyMap({
{ ACTION_PARAMETER_FIELD_NAME, "position" },
{ ACTION_PARAMETER_FIELD_DESCRIPTION, "Target position in seconds" },
{ ACTION_PARAMETER_FIELD_TYPE, ACTION_PARAMETER_TYPE_FLOAT },
{ ACTION_PARAMETER_FIELD_IS_OPTIONAL, false }
})
},
{ "relative",
SBAnyMap({
{ ACTION_PARAMETER_FIELD_NAME, "relative" },
{ ACTION_PARAMETER_FIELD_DESCRIPTION, "Whether position is relative to current" },
{ ACTION_PARAMETER_FIELD_TYPE, ACTION_PARAMETER_TYPE_BOOLEAN },
{ ACTION_PARAMETER_FIELD_IS_OPTIONAL, true }
})
}
})
},
{ ACTION_FIELD_HANDLER, std::function([this](const SBAnyMap& params) {
const float position = SBAny::convert<float>(params.at("position"));
const bool relative = params.hasKey("relative")
? SBAny::convert<bool>(params.at("relative"))
: false;

seek(position, relative);
return makeSuccess();
}) }
}
);

Best Practices

  1. Validate parameters - Check for required parameters and validate their values
  2. Return meaningful errors - Use makeError() with descriptive messages for failures
  3. Keep handlers lightweight - Avoid long-running operations in handlers
  4. Use async operations - For time-consuming tasks, trigger them and return immediately
  5. Document side effects - Clearly describe what the action does and its effects
  6. Thread safety - If the action modifies state accessed in process(), ensure thread safety
  7. Idempotency - Consider making actions safe to call multiple times

Thread Safety Considerations

Actions are called from the application/UI thread, not the audio thread. If an action modifies state that's accessed in your process() function:

  • Use atomics for simple state flags
  • Use lock-free queues for commands or data
  • Never block the audio thread waiting for an action to complete
  • Avoid locks that could be held by the audio thread

Example: Thread-Safe State Changes

class ProcessorNode : public SingleBusAudioProcessorNode {
public:
ProcessorNode() : bypass(false) {
registerAction(
"setBypass",
{
{ ACTION_FIELD_DESCRIPTION, "Enable or disable bypass mode" },
{ ACTION_FIELD_PARAMETERS,
SBAnyMap({
{ "enabled",
SBAnyMap({
{ ACTION_PARAMETER_FIELD_NAME, "enabled" },
{ ACTION_PARAMETER_FIELD_TYPE, ACTION_PARAMETER_TYPE_BOOLEAN },
{ ACTION_PARAMETER_FIELD_IS_OPTIONAL, false }
})
}
})
},
{ ACTION_FIELD_HANDLER, std::function([this](const SBAnyMap& params) {
bypass.store(SBAny::convert<bool>(params.at("enabled")));
return makeSuccess();
}) }
}
);
}

bool process(AudioBus& inBus, AudioBus& outBus) override {
if (bypass.load()) {
outBus.copyFrom(inBus);
} else {
// Normal processing...
}
return true;
}

private:
std::atomic<bool> bypass;
};

Common Use Cases

  • File operations - Load, save, import, export
  • Playback control - Play, pause, stop, seek
  • State management - Reset, clear, initialize
  • Recording control - Start recording, stop recording
  • Configuration changes - Load presets, change modes
  • Analysis triggers - Trigger analysis, capture snapshot

Next Steps

  • Events - Learn how to emit notifications from your node when actions complete or state changes occur