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
| Field | Constant | Description |
|---|---|---|
| Name | CONFIGURATION_FIELD_NAME | Unique identifier for the configuration parameter |
| Description | CONFIGURATION_FIELD_DESCRIPTION | Human-readable description of what the parameter does |
| Type | CONFIGURATION_FIELD_TYPE | Data type of the parameter (see below) |
Optional Fields
| Field | Constant | Description |
|---|---|---|
| Value | CONFIGURATION_FIELD_VALUE | The current or default value of the configuration |
| Default | CONFIGURATION_FIELD_DEFAULT | The default value if not provided |
| Min Value | CONFIGURATION_FIELD_MIN_VALUE | Minimum allowed value (for numeric types) |
| Max Value | CONFIGURATION_FIELD_MAX_VALUE | Maximum allowed value (for numeric types) |
| Options | CONFIGURATION_FIELD_OPTIONS | List of valid options (for string or enum-like types) |
Supported Configuration Types
CONFIGURATION_TYPE_BOOLEAN- Boolean value (true/false)CONFIGURATION_TYPE_FLOAT- Floating point numberCONFIGURATION_TYPE_INT- Integer numberCONFIGURATION_TYPE_STRING- String valueCONFIGURATION_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
- Document expected values - Clearly describe what values are valid and what they do
- Provide sensible defaults - Use
CONFIGURATION_FIELD_DEFAULTor handle missing values gracefully - Validate input - Check that provided values are valid before using them
- Use appropriate types - Choose the correct type (int, float, string, etc.) for your data
- Consider immutability - Most configurations should be immutable after construction
- Use options for enums - For string configurations with fixed choices, specify the options
- 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