CV Modulation
Control Voltage (CV) modulation allows you to dynamically control parameters using signals from CV generators like LFOs, envelopes, and sequencers. This is key to creating expressive, evolving sounds.
What is CV?
CV (Control Voltage) is a signal used to control audio parameters. In hardware modular synths, CV is actual voltage controlling things like filter cutoff or oscillator pitch. In mod, it's an audio-rate signal that modulates parameters.
Audio vs. CV
- Audio signals: Audible frequencies (20Hz - 20kHz), sent to speakers
- CV signals: Sub-audio or audio-rate control (0.01Hz - 20kHz+), control parameters
Basic CV Modulation
Connect a CV generator's output to a processor's cvInput:
import { ToneGenerator, LFO, Filter, Monitor, useModStream } from '@mode-7/mod';
function BasicModulation() {
const audio = useModStream();
const lfo = useModStream();
const output = useModStream();
return (
<>
<ToneGenerator output={audio} frequency={220} />
<LFO output={lfo} rate={2} />
<Filter
input={audio}
output={output}
cvInput={lfo}
cvTarget="frequency"
cvAmount={5000}
/>
<Monitor input={output} />
</>
);
}This creates an LFO that sweeps the filter cutoff.
CV Targets
Different components expose different CV targets:
Filter
frequency- Cutoff frequencyQ- Resonance
Processors
- Most effect parameters can be modulated
- Check component API docs for available targets
CV Amount
The cvAmount prop controls modulation depth:
<Filter
cvInput={lfo}
cvTarget="frequency"
cvAmount={5000} // Modulates ±5000Hz
frequency={1000} // Center frequency
/>With this setup:
- Center: 1000Hz
- Min: 1000 - 5000 = -4000Hz (clamped to minimum)
- Max: 1000 + 5000 = 6000Hz
LFO Modulation
Low Frequency Oscillators create cyclic modulation:
function LFOExample() {
const audio = useModStream();
const lfo = useModStream();
const filtered = useModStream();
return (
<>
<NoiseGenerator output={audio} />
<LFO output={lfo}>
{({ rate, setRate, depth, setDepth, waveform, setWaveform }) => (
<div>
<h3>LFO</h3>
<label>
Rate: {rate} Hz
<input
type="range"
min={0.01}
max={20}
step={0.01}
value={rate}
onChange={e => setRate(+e.target.value)}
/>
</label>
<label>
Waveform:
<select value={waveform} onChange={e => setWaveform(e.target.value)}>
<option value="sine">Sine</option>
<option value="triangle">Triangle</option>
<option value="square">Square</option>
<option value="sawtooth">Sawtooth</option>
</select>
</label>
</div>
)}
</LFO>
<Filter
input={audio}
output={filtered}
cvInput={lfo}
cvTarget="frequency"
cvAmount={8000}
frequency={1000}
/>
<Monitor input={filtered} />
</>
);
}LFO Waveforms
Different waveforms create different modulation patterns:
- Sine: Smooth, natural wobble
- Triangle: Linear sweep up/down
- Square: Abrupt on/off switching
- Sawtooth: Ramp up, instant reset
Envelope Modulation
ADSR envelopes create one-shot modulation curves:
function EnvelopeExample() {
const audio = useModStream();
const envelope = useModStream();
const filtered = useModStream();
return (
<>
<NoiseGenerator output={audio} />
<ADSR output={envelope}>
{({ trigger, attack, decay, sustain, release }) => (
<div>
<button onClick={trigger}>Trigger</button>
{/* ADSR controls... */}
</div>
)}
</ADSR>
<Filter
input={audio}
output={filtered}
cvInput={envelope}
cvTarget="frequency"
cvAmount={10000}
frequency={100}
/>
<Monitor input={filtered} />
</>
);
}When triggered, the envelope:
- Attack: Rises from 0 to 1
- Decay: Falls to sustain level
- Sustain: Holds at sustain level
- Release: Falls back to 0 when released
Multiple Modulators
Apply multiple CV sources to the same parameter by cascading:
function MultipleModulators() {
const audio = useModStream();
const lfo1 = useModStream();
const lfo2 = useModStream();
const mod1 = useModStream();
const mod2 = useModStream();
return (
<>
<ToneGenerator output={audio} />
<LFO output={lfo1} rate={0.5} />
<LFO output={lfo2} rate={5} />
{/* First modulation */}
<Filter
input={audio}
output={mod1}
cvInput={lfo1}
cvTarget="frequency"
cvAmount={2000}
/>
{/* Second modulation */}
<Filter
input={mod1}
output={mod2}
cvInput={lfo2}
cvTarget="frequency"
cvAmount={500}
/>
<Monitor input={mod2} />
</>
);
}Sequencer Modulation
Sequencers create stepped patterns:
function SequencerModulation() {
const audio = useModStream();
const seq = useModStream();
const output = useModStream();
return (
<>
<ToneGenerator output={audio} frequency={110} />
<Sequencer output={seq}>
{({ steps, setStep, isPlaying, play, stop }) => (
<div>
<button onClick={isPlaying ? stop : play}>
{isPlaying ? 'Stop' : 'Play'}
</button>
{steps.map((value, i) => (
<input
key={i}
type="range"
min={0}
max={1}
step={0.01}
value={value}
onChange={e => setStep(i, +e.target.value)}
/>
))}
</div>
)}
</Sequencer>
<Filter
input={audio}
output={output}
cvInput={seq}
cvTarget="frequency"
cvAmount={5000}
frequency={500}
/>
<Monitor input={output} />
</>
);
}Clock Synchronization
Use Clock to sync multiple modulators:
function SyncedModulation() {
const clock = useModStream();
const lfo1 = useModStream();
const lfo2 = useModStream();
return (
<>
<Clock output={clock} bpm={120} />
{/* Both LFOs sync to clock */}
<LFO output={lfo1} clockInput={clock} />
<LFO output={lfo2} clockInput={clock} />
{/* Use lfo1 and lfo2 for modulation... */}
</>
);
}Common Modulation Patterns
Vibrato
Pitch modulation using LFO:
<LFO output={lfo} rate={5} />
<ToneGenerator
output={audio}
cvInput={lfo}
cvTarget="frequency"
cvAmount={10} // ±10Hz wiggle
frequency={440}
/>Tremolo
Amplitude modulation using LFO:
<LFO output={lfo} rate={4} />
<ToneGenerator output={audio} frequency={440} />
<Tremolo
input={audio}
output={output}
cvInput={lfo}
cvTarget="depth"
cvAmount={0.8}
/>Filter Sweep
Envelope-controlled filter:
<ADSR output={env} attack={0.1} decay={0.3} sustain={0.2} release={0.5} />
<Filter
input={audio}
output={output}
cvInput={env}
cvTarget="frequency"
cvAmount={8000}
frequency={100}
/>Rhythmic Gating
Sequencer-controlled gain:
<Sequencer output={seq} steps={[1, 0, 0.5, 0, 1, 0, 0.7, 0]} />
<Gate
input={audio}
output={output}
cvInput={seq}
cvTarget="threshold"
cvAmount={-40} // dB
/>Best Practices
- Start subtle: Begin with small
cvAmountvalues - Match rates: Keep LFO rates musical (divisions of tempo)
- Layer modulation: Combine slow and fast modulators
- Visualize: Use oscilloscope to see modulation
- Automate: Use ADSR for dynamic changes
Debugging CV
Check CV Signal
<Oscilloscope input={cvSignal}>
{({ dataArray, bufferLength }) => (
<OscilloscopeCanvas dataArray={dataArray} bufferLength={bufferLength} />
)}
</Oscilloscope>Monitor Levels
<LevelMeter input={cvSignal}>
{({ level }) => <div>CV Level: {level.toFixed(3)}</div>}
</LevelMeter>Next Steps
- Explore CV Generators in detail
- Learn about Processors
- Build LFO Modulation example
