Parameter Automation System¶
RT-safe parameter system with smoothing for DAW automation.
Features¶
- ✅ RT-Safe Smoothing - Lock-free parameter interpolation
- ✅ Multiple Range Types - Linear, logarithmic, exponential
- ✅ DAW Integration - Normalized [0,1] values for host communication
- ✅ Zipper Noise Prevention - Configurable smoothing time (default 20ms)
- ✅ Type-Safe - Compile-time range verification
- ✅ Callback Support - Change notifications
Architecture¶
Components¶
- ParameterSmoother - RT-safe linear interpolation
- Lock-free (atomic target, non-atomic current)
- Configurable smoothing time
-
Exact target reaching (no drift)
-
ParameterRange (from type system)
- LinearRange: y = x
- LogRange: y = exp(x) for frequency
-
ExpRange: y = x^exp for envelopes
-
Parameter - Complete automatable parameter
- Combines smoother + range + metadata
- Thread-safe value changes
- Text formatting with units
Quick Start¶
Basic Usage¶
#include <parameter.hpp>
using namespace audiolab::core;
// Create gain parameter (-60 to +12 dB)
LinearParameter gain{
ParameterInfo{
.id = "gain",
.name = "Gain",
.label = "dB",
.group = "Main"
},
LinearRange{-60.0f, 12.0f},
0.0f // default: 0 dB
};
// Configure smoothing (call in on_prepare())
gain.set_smoothing_time(20.0f, sample_rate);
// Change value (from GUI thread)
gain.set_value(-6.0f);
// In audio callback (RT-safe):
float gain_value = gain.get_smoothed_value();
output[i] = input[i] * db_to_linear(gain_value);
Frequency Parameter (Logarithmic)¶
LogParameter frequency{
ParameterInfo{.id = "freq", .name = "Frequency", .label = "Hz"},
LogRange{20.0f, 20000.0f},
1000.0f
};
frequency.set_smoothing_time(50.0f, sample_rate); // 50ms for smooth sweeps
frequency.set_value(5000.0f); // Set to 5 kHz
DAW Automation (Normalized Values)¶
// Host sends normalized [0, 1] from automation curve
void setParameterNormalized(int index, float normalized) {
switch (index) {
case 0: gain.set_value_normalized(normalized); break;
case 1: frequency.set_value_normalized(normalized); break;
}
}
// Host requests current value
float getParameterNormalized(int index) {
switch (index) {
case 0: return gain.get_normalized();
case 1: return frequency.get_normalized();
}
}
Smoothing Algorithm¶
Linear Interpolation:
Given:
- target_value (set by GUI/automation)
- current_value (audio thread)
- smoothing_time_ms
Calculate:
- num_steps = (smoothing_time_ms * sample_rate) / 1000
- step_size = (target - current) / num_steps
Each sample:
- current += step_size
- steps_remaining--
When steps_remaining == 0:
- current = target // Snap to avoid drift
Why Linear? - Simple, predictable - Sufficient for 95% of use cases - ~10 CPU cycles per sample - Future: add exponential smoother if needed
Performance¶
| Operation | Cost | RT-Safe? |
|---|---|---|
get_smoothed_value() |
~10 cycles | ✅ Yes |
get_current_value() |
~2 cycles | ✅ Yes |
set_value() |
~50 cycles | ❌ No (callback) |
set_smoothing_time() |
~20 cycles | ⚠️ Non-RT only |
Thread Safety¶
Audio Thread (RT-safe):
- get_smoothed_value() - Advance and read
- get_current_value() - Read without advancing
- is_smoothing() - Check state
GUI/Host Thread (non-RT):
- set_value(float) - Change parameter
- set_value_normalized(float) - From DAW automation
- set_change_callback() - Register listener
Setup (non-RT, usually in on_prepare()):
- set_smoothing_time() - Configure duration
Range Types¶
Linear Range¶
- Use for: Gain (0-1), Pan (-1 to +1), Mix (0-100%) - Mapping:plain = min + normalized * (max - min)
Logarithmic Range¶
- Use for: Frequency (20-20kHz), Time (1ms-10s), Q (0.1-100) - Mapping:plain = min * pow(max/min, normalized)
Exponential Range¶
- Use for: Attack/release curves, resonance - Mapping:plain = min + pow(normalized, exponent) * (max - min)
- Exponent > 1: slow start, fast end
- Exponent < 1: fast start, slow end
Testing¶
Build Tests¶
cd "2 - FOUNDATION/04_CORE/04_08_parameter_system"
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
Run Tests¶
Run Example¶
Expected output: - Part 1: Gain at -6 dB (RMS ~-9 dB) - Part 2: Smooth ramp from -6 to +6 dB over 20ms - Part 3: Instant change to -12 dB
Integration Example¶
class SimpleReverb : public AudioProcessor {
public:
SimpleReverb() {
// Create parameters
mix_ = std::make_unique<LinearParameter>(
ParameterInfo{.id = "mix", .name = "Mix", .label = "%"},
LinearRange{0.0f, 100.0f},
50.0f
);
room_size_ = std::make_unique<LogParameter>(
ParameterInfo{.id = "room", .name = "Room Size", .label = ""},
LogRange{0.1f, 10.0f},
1.0f
);
}
void on_prepare(const ProcessConfig& config) override {
// Configure smoothing
mix_->set_smoothing_time(20.0f, config.sample_rate);
room_size_->set_smoothing_time(50.0f, config.sample_rate);
}
void on_process(ProcessData& data) override {
for (size_t i = 0; i < data.num_samples; ++i) {
float mix = mix_->get_smoothed_value() / 100.0f;
float room = room_size_->get_smoothed_value();
// Process with smoothed parameters
float wet = reverb_algorithm(data.input[i], room);
data.output[i] = data.input[i] * (1.0f - mix) + wet * mix;
}
}
private:
std::unique_ptr<LinearParameter> mix_;
std::unique_ptr<LogParameter> room_size_;
};
Design Decisions¶
Why Lock-Free?¶
- Target value:
std::atomic<float>(any thread can write) - Current value:
float(only audio thread writes) - Safety: Audio thread is sole writer of current value
- Performance: No mutex overhead in RT path
Why Linear Smoothing?¶
- Simplicity: Easy to understand and debug
- Predictability: Exact duration guarantee
- Performance: Minimal CPU cost
- Sufficient: Zipper-free for most parameters
- Future: Can add exponential/cubic smoothers later
Smoothing Time Selection¶
- Too short (< 5ms): May hear zipper on slow sweeps
- Too long (> 50ms): Parameter feels sluggish
- Recommended: 20ms (good balance)
- Per-parameter: Allow customization (e.g., 50ms for frequency sweeps)
Known Limitations¶
- Linear Smoothing Only
- Future: Add exponential smoother for more natural transitions
-
Workaround: Use longer smoothing time for critical parameters
-
No Parameter Coupling
- Q depends on frequency in filters
-
Solution: Handle coupling in processor, not parameter system
-
No Modulation Matrix
- LFO/envelope modulation not supported
- Future: Separate modulation system
Files¶
parameter_smoothing.hpp- RT-safe smootherparameter.hpp- Complete parameter classexamples/automated_gain.cpp- Integration exampletests/test_smoothing.cpp- Smoother tests (10 cases)tests/test_parameter.cpp- Parameter tests (10 cases)
Dependencies¶
04_00_type_system/parameter_types- Range definitions04_02_math_primitives/db_conversion- dB conversions (example)- C++17 standard library (
<atomic>,<functional>)
License¶
MIT License - See LICENSE file for details