Skip to content

🎛️ Modulation Matrix

📖 Overview

The Modulation Matrix provides flexible N-to-M modulation routing with advanced depth control, polyphonic support, and visualization capabilities.


📂 Components

08_03_02_modulation_matrix/
├── include/
│   ├── ModulationRouter.hpp          ← N-to-M modulation routing
│   ├── ModulationDepthControl.hpp    ← Curve shaping & ranges
│   ├── PolyphonicModulation.hpp      ← Per-voice modulation
│   └── ModulationVisualizer.hpp      ← UI visualization data
└── CMakeLists.txt                    ← Build configuration

🎯 Quick Start

Basic Modulation Routing

#include "ModulationRouter.hpp"

ModulationRouter router;

// Register LFO as modulation source
class MyLFO : public ModulationRouter::IModulationSource {
    float getValue() const override { return sin(phase); }
    void process(uint32_t samples) override { phase += freq * samples; }
    float phase = 0.0f;
    float freq = 0.01f;
};

MyLFO lfo;
router.registerSource("lfo1", &lfo);

// Create modulation connection
router.addConnection("lfo1", "filter_cutoff");
router.setDepth("lfo1", "filter_cutoff", 0.5f);  // 50% depth
router.setBipolar("lfo1", "filter_cutoff", true); // -1 to +1

// Process modulation (audio thread)
router.process(bufferSize);

// Get modulated value
float modAmount = router.getModulatedValue("filter_cutoff");

Advanced Depth Control

#include "ModulationDepthControl.hpp"

ModulationDepthControl depthCtrl;

// Configure shaping
depthCtrl.setDepth(0.5f);                          // 50% depth
depthCtrl.setRange(-12.0f, 12.0f);                 // ±12 dB
depthCtrl.setCurve(ModulationCurve::Exponential);  // Exponential curve
depthCtrl.setInvert(false);                        // No inversion

// Apply to modulation signal
float rawMod = lfo.getValue();  // [-1, 1]
float shapedMod = depthCtrl.apply(rawMod);
// Result: Exponentially curved, scaled to [-6, 6] dB

// Use in parameter calculation
float baseCutoff = 1000.0f;
float finalCutoff = baseCutoff * pow(2.0f, shapedMod / 12.0f);  // Convert dB to ratio

Polyphonic Modulation

#include "PolyphonicModulation.hpp"

PolyphonicModulation polyMod(8);  // 8 voices

// Note on
int voice = polyMod.allocateVoice(60, 100);  // MIDI note 60, velocity 100

// Set per-voice modulation
polyMod.setModulationValue(voice, "filter_cutoff", 0.5f);
polyMod.setModulationValue(voice, "resonance", 0.3f);

// Process all voices
polyMod.process(bufferSize);

// Get modulated value for specific voice
float cutoff = polyMod.getModulatedValue(voice, "filter_cutoff");

// Note off
polyMod.releaseVoice(voice);

Modulation Visualization

#include "ModulationVisualizer.hpp"

ModulationVisualizer visualizer;
visualizer.setHistoryLength(100);

// In audio callback
visualizer.capture(router);

// In UI update (30-60 Hz)
auto connections = visualizer.getConnectionData();
for (const auto& conn : connections) {
    // Draw connection line
    drawLine(conn.sourceId, conn.targetId, conn.active);

    // Draw activity meter
    drawMeter(conn.peakValue);

    // Draw current value
    drawValue(conn.currentValue);
}

// Get waveform trace
auto history = visualizer.getHistory("lfo1", "filter_cutoff");
drawWaveform(history);

// Reset peaks
visualizer.resetPeaks();

🏗️ Architecture

ModulationRouter

N-to-M routing with per-connection control.

[LFO 1] ───┬──→ Filter Cutoff (50% depth)
           └──→ Resonance (30% depth)

[LFO 2] ───┬──→ Filter Cutoff (20% depth)
           └──→ Pitch (10% depth)

[Envelope] ───→ Filter Cutoff (80% depth)

Features: - N sources → M targets - Per-connection depth - Bipolar/unipolar modes - Smooth depth interpolation - Enable/disable per connection

ModulationDepthControl

Curve shaping and range control.

Curve Types: - Linear: No shaping - Exponential: Slow start, fast end - Logarithmic: Fast start, slow end - S-Curve: Smooth acceleration/deceleration - Parabolic: Symmetrical around center

Range Mapping:

// Map LFO [-1, 1] to frequency range [100 Hz, 10 kHz]
depthCtrl.setRange(100.0f, 10000.0f);
depthCtrl.setCurve(ModulationCurve::Exponential);

PolyphonicModulation

Per-voice modulation for synthesizers.

Voice 0 (Note 60):
  Envelope → Cutoff (1.0)
  LFO      → Cutoff (0.5)

Voice 1 (Note 64):
  Envelope → Cutoff (1.0)
  LFO      → Cutoff (0.5)

Voice 2 (Note 67):
  Envelope → Cutoff (1.0)
  LFO      → Cutoff (0.5)

Features: - Voice allocation with stealing - Per-voice mod values - MIDI note/velocity tracking - Voice age tracking

ModulationVisualizer

UI visualization data generator.

Data Types: - Connection diagrams (source → target) - Activity meters (current, peak, average) - Historical traces (waveform display) - Matrix view (sources × targets grid)


🎮 Usage Patterns

LFO to Multiple Parameters

router.addConnection("lfo1", "filter_cutoff");
router.setDepth("lfo1", "filter_cutoff", 0.8f);

router.addConnection("lfo1", "resonance");
router.setDepth("lfo1", "resonance", 0.3f);

router.addConnection("lfo1", "pan");
router.setDepth("lfo1", "pan", 0.5f);
router.setBipolar("lfo1", "pan", true);  // Pan needs bipolar

Multiple Sources to One Parameter

// LFO
router.addConnection("lfo1", "filter_cutoff");
router.setDepth("lfo1", "filter_cutoff", 0.3f);

// Envelope
router.addConnection("env1", "filter_cutoff");
router.setDepth("env1", "filter_cutoff", 0.7f);

// Mod wheel
router.addConnection("modwheel", "filter_cutoff");
router.setDepth("modwheel", "filter_cutoff", 0.5f);

// All three modulate cutoff simultaneously (additive)

Polyphonic Synth Example

PolyphonicModulation polyMod(16);  // 16 voices
PolyphonicDepthControl depthCtrl(16);

// Configure depth per voice (slight variation)
for (int v = 0; v < 16; ++v) {
    float variation = 1.0f + (v * 0.02f);  // ±2% per voice
    depthCtrl.setDepth(v, 0.8f * variation);
    depthCtrl.setCurve(v, ModulationCurve::Exponential);
}

// MIDI note on
int voice = polyMod.allocateVoice(note, velocity);

// Apply envelope to cutoff
float envValue = envelope.process();
float shapedEnv = depthCtrl.apply(voice, envValue);
polyMod.setModulationValue(voice, "cutoff", shapedEnv);

⚡ Performance

Operation Complexity Typical Time
router.process() O(n) ~100ns per connection
getModulatedValue() O(1) ~20ns
depthCtrl.apply() O(1) ~30ns
visualizer.capture() O(n) ~500ns per 10 connections
polyMod.allocateVoice() O(v) ~200ns per 8 voices

🔗 Dependencies

  • 08_00 - Plugin Infrastructure
  • 08_02 - DSP Integration Layer (ModulationMatrix)
  • 08_03_00 - Aggregation System


Status: ✅ Complete Version: 1.0.0 Last Updated: 2025-10-09