Skip to main content

Configurations

Configurations are initial setup parameters that are passed to your custom node during construction. Unlike properties (which can be changed at runtime), configurations are typically used for one-time initialization that determines the node's behavior or structure.

Why Use Configurations?

Configurations enable:

  • Initial setup - Set up the node with specific parameters at creation time
  • Structural parameters - Define the number of buses, channels, or other architectural aspects
  • Default values - Provide sensible defaults that can be overridden during construction
  • File loading - Load initial resources (audio files, presets) when the node is created
  • Mode selection - Choose between different operating modes at construction time
  • Preset application - Initialize the node with a specific preset or configuration

Registering Configurations

Configurations are registered in your node's constructor using the registerConfiguration() method. Each configuration requires a name and metadata describing the parameter.

Basic Example

Here's a simple configuration for a file path:

class AudioPlayerNode : public SingleBusAudioSourceNode {
public:
AudioPlayerNode(const SBAnyMap& config) {
type = "AudioPlayerNode";

registerConfiguration(
"audioFilePath",
{
{ CONFIGURATION_FIELD_NAME, "audioFilePath" },
{ CONFIGURATION_FIELD_DESCRIPTION, "The path of the audio file to load." },
{ CONFIGURATION_FIELD_TYPE, CONFIGURATION_TYPE_STRING },
{ CONFIGURATION_FIELD_VALUE, "" }
}
);

if (config.hasKey("audioFilePath")) {
const auto audioFilePath = SBAny::convert<std::string>(config.at("audioFilePath"));
this->load(audioFilePath);
this->play();
this->setConfigurationValue("audioFilePath", audioFilePath);
}
}

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

Example with Default Value

Here's a configuration with a default value:

class BusSelectNode : public AudioProcessorNode {
public:
BusSelectNode(const SBAnyMap& config) :
AudioProcessorNode(
SBAnyMap::get<uint>(config, "numberOfInputs", 2), // Default to 2 inputs
1
) {
type = "BusSelectNode";

registerConfiguration(
"numberOfInputs",
{
{ CONFIGURATION_FIELD_NAME, "numberOfInputs" },
{ CONFIGURATION_FIELD_DESCRIPTION, "The number of input buses." },
{ CONFIGURATION_FIELD_TYPE, CONFIGURATION_TYPE_INT },
{ CONFIGURATION_FIELD_VALUE, getNumberOfInputs() },
{ CONFIGURATION_FIELD_MIN_VALUE, 1 },
{ CONFIGURATION_FIELD_MAX_VALUE, 16 }
}
);
}
};

Configuration Metadata Fields

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

Required Fields

FieldConstantDescription
NameCONFIGURATION_FIELD_NAMEUnique identifier for the configuration parameter
DescriptionCONFIGURATION_FIELD_DESCRIPTIONHuman-readable description of what the parameter does
TypeCONFIGURATION_FIELD_TYPEData type of the parameter (see below)

Optional Fields

FieldConstantDescription
ValueCONFIGURATION_FIELD_VALUEThe current or default value of the configuration
DefaultCONFIGURATION_FIELD_DEFAULTThe default value if not provided
Min ValueCONFIGURATION_FIELD_MIN_VALUEMinimum allowed value (for numeric types)
Max ValueCONFIGURATION_FIELD_MAX_VALUEMaximum allowed value (for numeric types)
OptionsCONFIGURATION_FIELD_OPTIONSList of valid options (for string or enum-like types)

Supported Configuration Types

  • CONFIGURATION_TYPE_BOOLEAN - Boolean value (true/false)
  • CONFIGURATION_TYPE_FLOAT - Floating point number
  • CONFIGURATION_TYPE_INT - Integer number
  • CONFIGURATION_TYPE_STRING - String value
  • CONFIGURATION_TYPE_ARRAY - Array of values

Reading Configuration Values

Configuration values are passed to your node's constructor via the config parameter. You read them using the hasKey() and at() methods on SBAnyMap:

MyNode(const SBAnyMap& config) {
// Check if a configuration value was provided
if (config.hasKey("parameterName")) {
const auto value = SBAny::convert<Type>(config.at("parameterName"));
// Use the value...
}
}

Using Helper Methods

You can use SBAnyMap::get() to provide a default value:

// Get value from config, or use default if not present
const uint numInputs = SBAnyMap::get<uint>(config, "numberOfInputs", 2);

Complete Example

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

class SynthesizerNode : public SingleBusAudioSourceNode {
public:
SynthesizerNode(const SBAnyMap& config) :
frequency(440.0f),
waveform("sine"),
gain(0.5f),
numberOfVoices(1) {
type = "SynthesizerNode";

// String configuration with options
registerConfiguration(
"waveform",
{
{ CONFIGURATION_FIELD_NAME, "waveform" },
{ CONFIGURATION_FIELD_DESCRIPTION, "The waveform type to generate." },
{ CONFIGURATION_FIELD_TYPE, CONFIGURATION_TYPE_STRING },
{ CONFIGURATION_FIELD_VALUE, "sine" },
{ CONFIGURATION_FIELD_OPTIONS, SBAnyArray({ "sine", "square", "sawtooth", "triangle" }) }
}
);

// Float configuration with range
registerConfiguration(
"frequency",
{
{ CONFIGURATION_FIELD_NAME, "frequency" },
{ CONFIGURATION_FIELD_DESCRIPTION, "The initial frequency in Hz." },
{ CONFIGURATION_FIELD_TYPE, CONFIGURATION_TYPE_FLOAT },
{ CONFIGURATION_FIELD_VALUE, 440.0f },
{ CONFIGURATION_FIELD_MIN_VALUE, 20.0f },
{ CONFIGURATION_FIELD_MAX_VALUE, 20000.0f }
}
);

// Integer configuration
registerConfiguration(
"numberOfVoices",
{
{ CONFIGURATION_FIELD_NAME, "numberOfVoices" },
{ CONFIGURATION_FIELD_DESCRIPTION, "The number of polyphonic voices." },
{ CONFIGURATION_FIELD_TYPE, CONFIGURATION_TYPE_INT },
{ CONFIGURATION_FIELD_VALUE, 1 },
{ CONFIGURATION_FIELD_MIN_VALUE, 1 },
{ CONFIGURATION_FIELD_MAX_VALUE, 16 }
}
);

// Apply configuration values
if (config.hasKey("waveform")) {
waveform = SBAny::convert<std::string>(config.at("waveform"));
setWaveform(waveform);
}

if (config.hasKey("frequency")) {
frequency = SBAny::convert<float>(config.at("frequency"));
setFrequency(frequency);
}

if (config.hasKey("numberOfVoices")) {
numberOfVoices = SBAny::convert<uint>(config.at("numberOfVoices"));
initializeVoices(numberOfVoices);
}
}

private:
float frequency;
std::string waveform;
float gain;
uint numberOfVoices;

void setWaveform(const std::string& type) { /* Implementation */ }
void setFrequency(float freq) { /* Implementation */ }
void initializeVoices(uint count) { /* Implementation */ }
};

Configurations vs Properties

Understanding when to use configurations versus properties is important:

Use Configurations When:

  • The parameter affects the node's structure (number of buses, buffer sizes)
  • The value should be set once during construction
  • Changing the value would require recreating the node
  • The parameter is a file path or resource to load at startup
  • The value determines the node's operating mode

Use Properties When:

  • The parameter can be changed at runtime
  • Changes should take effect immediately or smoothly
  • The value is a real-time parameter (gain, frequency, filter cutoff)
  • You want to automate or modulate the parameter
  • The parameter represents current state that can be queried

Example distinction:

  • Configuration: numberOfInputs - Structural, requires reconstruction to change
  • Property: selectedBus - Can be changed at runtime without reconstruction

Updating Configuration Values

You can update a configuration's stored value using setConfigurationValue():

Result<SBAny> handleLoad(const SBAnyMap& params) {
const std::string audioFilePath = SBAny::convert<std::string>(params.at("audioFilePath"));

if (load(audioFilePath)) {
// Update the configuration to reflect the new file
setConfigurationValue("audioFilePath", audioFilePath);
return makeSuccess<SBAny>(true);
}

return makeError<SBAny>("Failed to load audio file: " + audioFilePath);
}

This updates the configuration's metadata but doesn't trigger any automatic behavior. It's useful for keeping track of the current configuration state.

Best Practices

  1. Document expected values - Clearly describe what values are valid and what they do
  2. Provide sensible defaults - Use CONFIGURATION_FIELD_DEFAULT or handle missing values gracefully
  3. Validate input - Check that provided values are valid before using them
  4. Use appropriate types - Choose the correct type (int, float, string, etc.) for your data
  5. Consider immutability - Most configurations should be immutable after construction
  6. Use options for enums - For string configurations with fixed choices, specify the options
  7. Keep it structural - Use configurations for setup, not for runtime parameter changes

Common Use Cases

  • Resource loading - Initial audio files, presets, or assets to load
  • Structural parameters - Number of buses, channels, buffer sizes
  • Mode selection - Operating mode, algorithm type, processing quality
  • Device selection - Audio input/output device, MIDI port
  • Path specification - File paths, directory paths, URLs
  • Architectural choices - Number of voices, effect chain configuration

Threading Considerations

Configurations are read during node construction, which happens on the application thread. There are no threading concerns when reading configuration values in the constructor.

However, if you expose methods to update configuration values at runtime (via actions), ensure that:

  • The update is thread-safe if the value is accessed in the audio thread
  • The update doesn't cause memory allocation or blocking operations
  • Consider whether a property would be more appropriate for runtime changes

Next Steps

  • Properties - Learn how to add runtime-adjustable parameters to your node