Properties
Properties are real-time parameters that can be safely changed throughout node's lifetime and apply immediately when updated. Each node declares the properties it supports and every property has a default value. Properties are part of the snapshot exported from an objec (alongside its configurations), re-applying them through the constructor reproduces the object's prior state, including changes that accumulated while it was in use.
Why Use Properties?
Properties enable:
- Real-time control of audio parameters (gain, frequency, mix, etc.)
- UI integration - properties can be bound to sliders, knobs, and other controls
- Automation - properties can be automated over time
- Serialization - properties can be saved and restored with audio graphs
- Introspection - applications can discover available parameters and their ranges
Registering Properties
Properties are registered in your node's constructor using the registerProperty() method. Each property requires a unique name and a metadata map that describes the property.
Basic Example
Here's a complete example of registering a gain property with getter and setter:
class GainNode : public SingleBusAudioProcessorNode {
public:
GainNode() : gain(1.0f) {
type = "GainNode";
registerProperty(
"gain",
{
{ PROPERTY_FIELD_DESCRIPTION, "The gain of the audio signal. The range is [0, 1]." },
{ PROPERTY_FIELD_TYPE, PROPERTY_TYPE_FLOAT },
{ PROPERTY_FIELD_MIN_VALUE, 0.0f },
{ PROPERTY_FIELD_MAX_VALUE, 1.0f },
{ PROPERTY_FIELD_DEFAULT_VALUE, gain.load() },
{ PROPERTY_FIELD_GETTER, std::function([this]() -> Result<SBAny> {
return makeSuccess<SBAny>(getGain());
}) },
{ PROPERTY_FIELD_SETTER, std::function([this](const SBAny& value) -> Result<void> {
const auto newGain = SBAny::convert<float>(value);
setGain(newGain);
return makeSuccess();
}) }
}
);
}
float getGain() const { return gain.load(); }
void setGain(float newValue) { gain.store(newValue); }
private:
std::atomic<float> gain;
};
Property Metadata Fields
Properties are described using a metadata map with the following fields:
Required Fields
| Field | Constant | Description |
|---|---|---|
| Type | PROPERTY_FIELD_TYPE | Data type: PROPERTY_TYPE_FLOAT, PROPERTY_TYPE_INT, PROPERTY_TYPE_BOOLEAN, PROPERTY_TYPE_STRING |
| Getter | PROPERTY_FIELD_GETTER | Function that returns the current property value as Result<SBAny> |
| Setter | PROPERTY_FIELD_SETTER | Function that sets a new property value, takes const SBAny&, returns Result<void> |
| Default Value | PROPERTY_FIELD_DEFAULT_VALUE | Initial/default value for the property |
Optional Fields
| Field | Constant | Description |
|---|---|---|
| Description | PROPERTY_FIELD_DESCRIPTION | Human-readable description of the property |
| Min Value | PROPERTY_FIELD_MIN_VALUE | Minimum allowed value (for numeric types) |
| Max Value | PROPERTY_FIELD_MAX_VALUE | Maximum allowed value (for numeric types) |
| Unit | PROPERTY_FIELD_UNIT | Unit of measurement (see Units section below) |
| Options | PROPERTY_FIELD_OPTIONS | List of valid options (for string enumerations) |
| Read Only | PROPERTY_FIELD_READ_ONLY | Whether the property can only be read, not written |
Supported Units
Use these constants for the PROPERTY_FIELD_UNIT field:
PROPERTY_UNIT_MS- MillisecondsPROPERTY_UNIT_DB- DecibelsPROPERTY_UNIT_PERCENT- Percentage (%)PROPERTY_UNIT_HZ- Hertz (frequency)PROPERTY_UNIT_SEMITONES- Semitones (pitch)PROPERTY_UNIT_BPM- Beats per minutePROPERTY_UNIT_SAMPLES- Audio samplesPROPERTY_UNIT_FRAMES- Audio framesPROPERTY_UNIT_CHANNELS- Channel countPROPERTY_UNIT_BYTES- BytesPROPERTY_UNIT_BITS- Bits
Property Types
Float Properties
registerProperty(
"frequency",
{
{ PROPERTY_FIELD_TYPE, PROPERTY_TYPE_FLOAT },
{ PROPERTY_FIELD_DESCRIPTION, "The frequency of the sine wave in Hz." },
{ PROPERTY_FIELD_UNIT, PROPERTY_UNIT_HZ },
{ PROPERTY_FIELD_MIN_VALUE, 20.0f },
{ PROPERTY_FIELD_MAX_VALUE, 20000.0f },
{ PROPERTY_FIELD_DEFAULT_VALUE, 440.0f },
{ PROPERTY_FIELD_GETTER, std::function([this]() -> Result<SBAny> {
return makeSuccess<SBAny>(getFrequency());
}) },
{ PROPERTY_FIELD_SETTER, std::function([this](const SBAny& value) -> Result<void> {
setFrequency(SBAny::convert<float>(value));
return makeSuccess();
}) }
}
);
Integer Properties
registerProperty(
"bufferSize",
{
{ PROPERTY_FIELD_TYPE, PROPERTY_TYPE_INT },
{ PROPERTY_FIELD_DESCRIPTION, "Size of the internal buffer." },
{ PROPERTY_FIELD_UNIT, PROPERTY_UNIT_SAMPLES },
{ PROPERTY_FIELD_MIN_VALUE, 64 },
{ PROPERTY_FIELD_MAX_VALUE, 4096 },
{ PROPERTY_FIELD_DEFAULT_VALUE, 512 },
{ PROPERTY_FIELD_GETTER, std::function([this]() -> Result<SBAny> {
return makeSuccess<SBAny>(getBufferSize());
}) },
{ PROPERTY_FIELD_SETTER, std::function([this](const SBAny& value) -> Result<void> {
setBufferSize(SBAny::convert<int>(value));
return makeSuccess();
}) }
}
);
Boolean Properties
registerProperty(
"bypass",
{
{ PROPERTY_FIELD_TYPE, PROPERTY_TYPE_BOOLEAN },
{ PROPERTY_FIELD_DESCRIPTION, "Bypass the effect processing." },
{ PROPERTY_FIELD_DEFAULT_VALUE, false },
{ PROPERTY_FIELD_GETTER, std::function([this]() -> Result<SBAny> {
return makeSuccess<SBAny>(isBypassed());
}) },
{ PROPERTY_FIELD_SETTER, std::function([this](const SBAny& value) -> Result<void> {
setBypassed(SBAny::convert<bool>(value));
return makeSuccess();
}) }
}
);
Read-Only Properties
For properties that should only be read (e.g., analysis results):
registerProperty(
"level",
{
{ PROPERTY_FIELD_TYPE, PROPERTY_TYPE_FLOAT },
{ PROPERTY_FIELD_DESCRIPTION, "The current audio level (RMS)." },
{ PROPERTY_FIELD_READ_ONLY, true },
{ PROPERTY_FIELD_GETTER, std::function([this]() -> Result<SBAny> {
return makeSuccess<SBAny>(getLevel());
}) }
}
);
Thread Safety
Properties can be accessed from both the UI thread (via setter) and the audio thread (in process()). You must ensure thread-safe access:
Use Atomics for Simple Types
For simple numeric types, use std::atomic:
private:
std::atomic<float> gain;
std::atomic<bool> bypass;
Use Lock-Free Data Structures
For complex data, use lock-free structures or double-buffering techniques to avoid blocking the audio thread.
Never Use Locks
❌ Do not use mutexes, locks, or other blocking primitives in code that runs in the audio thread. This violates real-time safety and causes audio glitches.
Best Practices
- Always use atomics for properties accessed in
process() - Validate input in your setter and return errors for invalid values
- Provide meaningful descriptions to help users understand parameters
- Set appropriate min/max values to prevent invalid configurations
- Use standard units for consistency across the SDK
- Keep getters/setters lightweight - avoid complex calculations
- Document side effects if setting a property affects other properties
Common Patterns
Smoothed Parameter Changes
For parameters that need smooth transitions (like gain or frequency), use a ramping class:
private:
std::atomic<float> targetGain;
std::unique_ptr<math::Ramp> gainRamp;
// In constructor:
gainRamp = std::make_unique<math::Ramp>();
gainRamp->setRampDurationMs(10); // 10ms smooth transition
// In process():
gainRamp->setTargetValue(targetGain.load());
for (uint frame = 0; frame < numFrames; ++frame) {
float currentGain = gainRamp->process(1);
// Apply currentGain to audio...
}
Enum-Like String Properties
For properties with a fixed set of options:
registerProperty(
"filterType",
{
{ PROPERTY_FIELD_TYPE, PROPERTY_TYPE_STRING },
{ PROPERTY_FIELD_DESCRIPTION, "Type of filter to apply." },
{ PROPERTY_FIELD_OPTIONS, std::vector<std::string>{"lowpass", "highpass", "bandpass"} },
{ PROPERTY_FIELD_DEFAULT_VALUE, std::string("lowpass") },
// getter/setter...
}
);