Skip to content

AudioLab Preset System: Morphing Engine

Overview

The Morphing Engine provides smooth interpolation between presets, enabling seamless transitions, creative sound design, and dynamic preset evolution. It supports multiple interpolation strategies and real-time morphing control.

Features

Core Capabilities

  • Parameter Interpolation - Smooth transitions between parameter values
  • Multiple Strategies - Linear, exponential, S-curve, cubic spline interpolation
  • Type-Aware Morphing - Intelligent handling of different parameter types
  • Multi-Preset Paths - Morph through sequences of multiple presets
  • Real-Time Control - Dynamic morphing with position control
  • Easing Functions - Professional animation curves (ease-in, ease-out, etc.)

Advanced Features

  • Weighted Morphing - Blend multiple presets with custom weights
  • Selective Morphing - Control which parameters participate
  • Crossfade Curves - Audio-quality crossfading for smooth transitions
  • Automation Support - Time-based morphing automation
  • Morphing Presets - Save morphing configurations
  • Undo/Redo Support - Full history tracking

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Morphing Engine                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────────┐      ┌──────────────────┐          │
│  │ ParameterMorpher │◄─────┤ MorphingStrategy │          │
│  └────────┬─────────┘      └──────────────────┘          │
│           │                         │                      │
│           │                    Strategies:                 │
│           │                    • Linear                    │
│           │                    • Exponential               │
│           │                    • SCurve                    │
│           │                    • CubicSpline               │
│           │                    • CustomEasing              │
│           │                                                │
│           │                                                │
│  ┌────────▼─────────┐      ┌──────────────────┐          │
│  │  PresetMorpher   │◄─────┤  MorphingPath    │          │
│  └────────┬─────────┘      └──────────────────┘          │
│           │                                                │
│           │                                                │
│  ┌────────▼─────────┐      ┌──────────────────┐          │
│  │ MorphingController│◄────┤ MorphingPreset   │          │
│  └──────────────────┘      └──────────────────┘          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Components

1. ParameterMorpher

Purpose: Interpolates individual parameter values with type awareness.

Features: - Float interpolation (linear, exponential, logarithmic) - Integer interpolation (round, floor, ceil strategies) - Boolean interpolation (threshold-based) - Enum interpolation (nearest or weighted blend) - String interpolation (discrete switch at threshold) - Range clamping and validation

Example:

ParameterMorpher morpher;

// Float interpolation
float result = morpher.morphFloat(
    1.0f,           // From value
    10.0f,          // To value
    0.5f,           // Position (0-1)
    InterpolationType::Linear
);
// result = 5.5f

// Exponential curve
float exp_result = morpher.morphFloat(
    1.0f, 10.0f, 0.5f,
    InterpolationType::Exponential
);
// result ≈ 3.16f (square root behavior)

2. MorphingStrategy

Purpose: Defines how parameter values transition over time.

Built-in Strategies:

  1. Linear
  2. Constant rate interpolation
  3. f(t) = a + (b - a) * t
  4. Best for: Simple, predictable transitions

  5. Exponential

  6. Exponential curve interpolation
  7. f(t) = a * (b/a)^t
  8. Best for: Frequency, gain, time-based parameters

  9. SCurve (Sigmoid)

  10. S-shaped transition curve
  11. f(t) = a + (b - a) * (3t² - 2t³)
  12. Best for: Smooth, natural-feeling transitions

  13. CubicSpline

  14. Cubic Hermite spline interpolation
  15. Smooth first derivative
  16. Best for: Multi-point paths, complex curves

  17. CustomEasing

  18. User-defined easing functions
  19. Supports common animation curves
  20. Best for: UI animations, artistic control

Easing Functions: - Ease-In (Quadratic, Cubic, Quartic, Quintic) - Ease-Out (Quadratic, Cubic, Quartic, Quintic) - Ease-In-Out (Quadratic, Cubic, Quartic, Quintic) - Bounce, Elastic, Back, Circ

3. PresetMorpher

Purpose: Main morphing engine that interpolates entire presets.

Capabilities: - Two-preset morphing - Multi-preset morphing (weighted blend) - Selective parameter morphing (include/exclude lists) - Metadata preservation - Resource handling - Version compatibility checking

Example:

PresetMorpher morpher;

// Simple two-preset morph
PresetSchema result = morpher.morph(
    preset_a,       // Source preset
    preset_b,       // Target preset
    0.5f,           // Position (50% blend)
    InterpolationType::SCurve
);

// Multi-preset weighted morph
std::vector<PresetSchema> presets = {preset_a, preset_b, preset_c};
std::vector<float> weights = {0.5f, 0.3f, 0.2f};
PresetSchema blended = morpher.morphWeighted(presets, weights);

4. MorphingPath

Purpose: Defines multi-stage morphing sequences.

Features: - Sequence of presets with positions - Automatic position calculation - Custom segment timing - Path validation - Looping support

Example:

MorphingPath path;

// Add presets to path
path.addPreset(preset_bass, 0.0f);      // Start
path.addPreset(preset_lead, 0.5f);      // Midpoint
path.addPreset(preset_pad, 1.0f);       // End

// Morph along path
PresetSchema result = path.morphAt(0.25f);  // 25% = halfway between bass and lead

5. MorphingController

Purpose: Real-time morphing control with automation.

Features: - Position control (0.0 - 1.0) - Speed control - Direction control (forward/reverse) - Time-based automation - Trigger-based morphing - Preset recall during morph

Example:

MorphingController controller(preset_a, preset_b);

// Real-time control
controller.setPosition(0.0f);           // Start at preset A
controller.setSpeed(0.1f);              // 10% per second
controller.setDirection(Direction::Forward);

// Update in audio callback
while (processing) {
    controller.update(delta_time);
    PresetSchema current = controller.getCurrentPreset();
    // Apply current to audio engine
}

Interpolation Types

Parameter Type Handling

Parameter Type Default Strategy Options
Float Linear Linear, Exponential, SCurve, Spline
Integer Round Round, Floor, Ceil, Nearest
Boolean Threshold Threshold (0.5 default)
Enum Nearest Nearest, Weighted
String Switch Discrete switch at threshold
Array Element-wise Per-element interpolation

Interpolation Curves

Linear:

1.0 ┤         ╱
    │       ╱
0.5 ┤     ╱
    │   ╱
0.0 └─┴─────┴─
    0.0   0.5   1.0

Exponential:

1.0 ┤        ╱
    │      ╱
0.5 ┤    ╱
    │  ╱
0.0 └─┴──────┴─
    0.0   0.5   1.0

S-Curve:

1.0 ┤      ╱─
    │    ╱
0.5 ┤  ╱
    │ ╱
0.0 └──────┴─
    0.0   0.5   1.0

Use Cases

1. Seamless Preset Transitions

// Crossfade between two presets over 2 seconds
MorphingController controller(current_preset, next_preset);
controller.setDuration(2.0f);
controller.start();

// In audio callback:
controller.update(delta_time);
applyPreset(controller.getCurrentPreset());

2. Performance Control

// Map MIDI CC to morphing position
void onMidiCC(int cc, int value) {
    if (cc == 1) {  // Mod wheel
        float position = value / 127.0f;
        controller.setPosition(position);
    }
}

3. Sound Design Exploration

// Create variations by morphing with random targets
PresetSchema base = loadPreset("bass_001");
PresetSchema random_variation = morpher.morphRandom(
    base,
    0.2f,  // 20% randomization
    {"cutoff", "resonance"}  // Only morph these params
);

4. Automation Sequences

// Create evolving pad that morphs through 4 presets
MorphingPath path;
path.addPreset(dark_pad, 0.0f);
path.addPreset(bright_pad, 0.25f);
path.addPreset(warm_pad, 0.5f);
path.addPreset(cold_pad, 0.75f);
path.setLooping(true);

// Automate position over time
controller.setPath(path);
controller.setSpeed(0.05f);  // Complete cycle every 20 seconds

API Reference

ParameterMorpher

class ParameterMorpher {
public:
    // Float interpolation
    float morphFloat(float from, float to, float position,
                    InterpolationType type = InterpolationType::Linear);

    // Integer interpolation
    int morphInt(int from, int to, float position,
                IntegerMorphMode mode = IntegerMorphMode::Round);

    // Boolean interpolation
    bool morphBool(bool from, bool to, float position,
                  float threshold = 0.5f);

    // Custom easing
    float applyEasing(float position, EasingFunction easing);
};

PresetMorpher

class PresetMorpher {
public:
    // Two-preset morph
    PresetSchema morph(const PresetSchema& from,
                      const PresetSchema& to,
                      float position,
                      InterpolationType type = InterpolationType::Linear);

    // Weighted multi-preset morph
    PresetSchema morphWeighted(const std::vector<PresetSchema>& presets,
                              const std::vector<float>& weights);

    // Selective morphing
    PresetSchema morphSelective(const PresetSchema& from,
                               const PresetSchema& to,
                               float position,
                               const std::vector<std::string>& include_params);

    // Configuration
    void setInterpolationType(InterpolationType type);
    void setExcludeParameters(const std::vector<std::string>& params);
};

MorphingPath

class MorphingPath {
public:
    // Path construction
    void addPreset(const PresetSchema& preset, float position);
    void insertPreset(const PresetSchema& preset, float position, size_t index);
    void removePreset(size_t index);
    void clear();

    // Morphing
    PresetSchema morphAt(float position) const;

    // Configuration
    void setLooping(bool loop);
    void setInterpolationType(InterpolationType type);

    // Query
    size_t getPresetCount() const;
    float getLength() const;
};

MorphingController

class MorphingController {
public:
    // Construction
    MorphingController(const PresetSchema& from, const PresetSchema& to);

    // Control
    void setPosition(float position);
    void setSpeed(float speed);
    void setDirection(Direction direction);
    void start();
    void stop();
    void reset();

    // Update
    void update(float delta_time);

    // Query
    PresetSchema getCurrentPreset() const;
    float getPosition() const;
    bool isActive() const;
};

Performance Characteristics

Operation Time Complexity Notes
Two-preset morph O(n) n = number of parameters
Weighted morph (k presets) O(k*n) Linear in presets and params
Path morph O(log k + n) Binary search + interpolation
Position update O(1) Controller state update
Easing function O(1) Mathematical function

Memory Usage: - ParameterMorpher: ~64 bytes - PresetMorpher: ~256 bytes + config - MorphingPath: ~128 bytes + (k * preset_size) - MorphingController: ~512 bytes + current preset

Real-Time Safety: - ✅ Lock-free parameter interpolation - ✅ No memory allocation in audio thread - ✅ Deterministic execution time - ⚠️ Path/preset changes require synchronization

Best Practices

1. Choose Appropriate Interpolation

// Frequency: Use exponential
morpher.morphFloat(100.0f, 10000.0f, pos, InterpolationType::Exponential);

// Volume/Gain: Use exponential or dB-linear
float db_morph = morpher.morphFloat(-60.0f, 0.0f, pos, InterpolationType::Linear);
float linear_gain = std::pow(10.0f, db_morph / 20.0f);

// Time/Delay: Use linear or exponential
morpher.morphFloat(0.001f, 1.0f, pos, InterpolationType::Exponential);

// Mix/Blend: Use S-curve for smooth transitions
morpher.morphFloat(0.0f, 1.0f, pos, InterpolationType::SCurve);

2. Handle Incompatible Presets

// Check compatibility before morphing
if (!morpher.areCompatible(preset_a, preset_b)) {
    // Handle gracefully: skip, use default, or log warning
    return preset_a;  // Fallback
}

PresetSchema result = morpher.morph(preset_a, preset_b, position);

3. Optimize for Real-Time

// Pre-calculate morphing path
MorphingPath path = buildPath(presets);

// In audio callback: only update position
void audioCallback(float delta_time) {
    controller.update(delta_time);
    preset = controller.getCurrentPreset();  // Fast lookup
    // No heavy computation here
}

4. Smooth Automation

// Use S-curve for natural-feeling automation
controller.setEasing(EasingFunction::EaseInOutCubic);
controller.setDuration(5.0f);
controller.start();

// Avoid jarring transitions
controller.setSmoothing(true, 0.1f);  // 100ms smoothing

Integration

With Preset Browser

// Morph between search results
auto results = browser.search("bass");
PresetSchema morphed = morpher.morph(results[0], results[1], 0.5f);

With Validation System

// Validate morphed preset
PresetSchema morphed = morpher.morph(preset_a, preset_b, position);
ValidationReport report = validator.validate(morphed);
if (report.isValid()) {
    applyPreset(morphed);
}

With Version Management

// Morph between different versions
PresetSchema v1 = loadPreset("sound_v1.json");
PresetSchema v2 = versionManager.migrate(v1, {2, 0, 0});
PresetSchema blend = morpher.morph(v1, v2, 0.5f);

Testing

Run tests:

cd build
ctest -R morphing

Run examples:

./build/examples/morph_example
./build/examples/morphing_path_example
./build/examples/real_time_morph_example

Future Enhancements

  • GPU-accelerated morphing for complex presets
  • Machine learning-guided parameter mapping
  • Spectral morphing for audio-based interpolation
  • Morphing presets marketplace (cloud integration)
  • Visual morphing editor (GUI)
  • MIDI learn for morphing control
  • Morphing automation recording/playback

Dependencies

  • audiolab_preset_schemas - Preset data structures
  • nlohmann/json - JSON serialization
  • Catch2 (testing) - Unit test framework

License

Part of the AudioLab Preset System.