Properties
Properties are real-time parameters that can be dynamically changed during audio processing. They allow your custom node to be controlled from the application layer, making your node interactive and configurable at runtime.
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> |
Optional Fields
| Field | Constant | Description |
|---|---|---|
| Description | PROPERTY_FIELD_DESCRIPTION | Human-readable description of the property |
| Default Value | PROPERTY_FIELD_DEFAULT_VALUE | Initial/default value for 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...
}
);