Skip to content

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

Parameter = ParameterValue + ParameterSmoother + Metadata

Components

  1. ParameterSmoother - RT-safe linear interpolation
  2. Lock-free (atomic target, non-atomic current)
  3. Configurable smoothing time
  4. Exact target reaching (no drift)

  5. ParameterRange (from type system)

  6. LinearRange: y = x
  7. LogRange: y = exp(x) for frequency
  8. ExpRange: y = x^exp for envelopes

  9. Parameter - Complete automatable parameter

  10. Combines smoother + range + metadata
  11. Thread-safe value changes
  12. 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

LinearRange{min, max}
- Use for: Gain (0-1), Pan (-1 to +1), Mix (0-100%) - Mapping: plain = min + normalized * (max - min)

Logarithmic Range

LogRange{min, max}  // min must be > 0
- Use for: Frequency (20-20kHz), Time (1ms-10s), Q (0.1-100) - Mapping: plain = min * pow(max/min, normalized)

Exponential Range

ExpRange{min, max, exponent}
- 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

ctest --test-dir build -C Release --output-on-failure

Run Example

.\build\examples\Release\automated_gain.exe

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

  1. Linear Smoothing Only
  2. Future: Add exponential smoother for more natural transitions
  3. Workaround: Use longer smoothing time for critical parameters

  4. No Parameter Coupling

  5. Q depends on frequency in filters
  6. Solution: Handle coupling in processor, not parameter system

  7. No Modulation Matrix

  8. LFO/envelope modulation not supported
  9. Future: Separate modulation system

Files

  • parameter_smoothing.hpp - RT-safe smoother
  • parameter.hpp - Complete parameter class
  • examples/automated_gain.cpp - Integration example
  • tests/test_smoothing.cpp - Smoother tests (10 cases)
  • tests/test_parameter.cpp - Parameter tests (10 cases)

Dependencies

  • 04_00_type_system/parameter_types - Range definitions
  • 04_02_math_primitives/db_conversion - dB conversions (example)
  • C++17 standard library (<atomic>, <functional>)

License

MIT License - See LICENSE file for details