Filter Modulation
Modulation brings patches to life. When we connect an LFO (Low Frequency Oscillator) to the filter cutoff, static becomes dynamic—a still photograph becomes a movie.
flowchart LR
LFO[LFO<br/>~2Hz] -->|mod| VCF[Filter<br/>Cutoff]
VCO[VCO] -->|audio| VCF
VCF --> OUT[Output]
style LFO fill:#f9a826,color:#000
LFO: The Modulation Source
An LFO is simply an oscillator running at sub-audio rates:
| Audio Oscillator | LFO |
|---|---|
| 20Hz - 20kHz | 0.01Hz - 30Hz |
| Creates pitch | Creates movement |
| You hear it | You feel its effect |
graph LR
subgraph "LFO Waveforms"
SIN[Sine<br/>Smooth sweep]
TRI[Triangle<br/>Linear sweep]
SAW[Saw<br/>Ramp + drop]
SQR[Square<br/>Two states]
end
The Mathematics of Modulation
Filter cutoff with LFO modulation:
$$f_c(t) = f_{center} + f_{depth} \cdot \text{LFO}(t)$$
Where:
- $f_{center}$ is the base cutoff frequency
- $f_{depth}$ is the modulation depth (how far it sweeps)
- $\text{LFO}(t)$ oscillates between -1 and +1
Building the Patch
//! Tutorial: Filter Modulation
//!
//! Demonstrates LFO modulation of filter cutoff - the classic "wobble"
//! that brings patches to life.
//!
//! Run with: cargo run --example tutorial_filter_mod
use quiver::prelude::*;
fn main() {
let sample_rate = 44100.0;
let mut patch = Patch::new(sample_rate);
// Sound source - sawtooth oscillator
let vco = patch.add("vco", Vco::new(sample_rate));
// LFO for modulation (runs at sub-audio rate)
let lfo = patch.add("lfo", Lfo::new(sample_rate));
// Filter - we'll modulate its cutoff
let vcf = patch.add("vcf", Svf::new(sample_rate));
// Base cutoff offset
let cutoff_base = patch.add("cutoff_base", Offset::new(3.0));
// Output
let output = patch.add("output", StereoOutput::new());
// Audio path: VCO → Filter → Output
patch.connect(vco.out("saw"), vcf.in_("in")).unwrap();
patch.connect(vcf.out("lp"), output.in_("left")).unwrap();
// Modulation: LFO → Filter cutoff (with base offset)
patch
.connect(cutoff_base.out("out"), vcf.in_("cutoff"))
.unwrap();
patch.connect(lfo.out("sin"), vcf.in_("fm")).unwrap();
patch.set_output(output.id());
patch.compile().unwrap();
println!("=== Filter Modulation Demo ===\n");
println!("LFO modulating filter cutoff creates the classic 'wobble' effect.\n");
// Generate 2 seconds of audio to hear multiple LFO cycles
let duration = 2.0;
let total_samples = (sample_rate * duration) as usize;
// Track the signal envelope over time
let block_size = (sample_rate / 10.0) as usize; // 100ms blocks
let mut time = 0.0;
println!("Time(s) | Peak Level | Character");
println!("---------|------------|----------");
for block in 0..(total_samples / block_size) {
let mut peak = 0.0_f64;
for _ in 0..block_size {
let (left, _) = patch.tick();
peak = peak.max(left.abs());
}
// Describe the sound character based on peak
let character = if peak > 4.0 {
"Bright (filter open)"
} else if peak > 2.0 {
"Medium"
} else {
"Dark (filter closed)"
};
if block % 5 == 0 {
println!("{:7.2} | {:10.2}V | {}", time, peak, character);
}
time += block_size as f64 / sample_rate;
}
println!("\nThe LFO creates a periodic sweep of the filter,");
println!("cycling between bright (open) and dark (closed) states.");
println!("\nTry different LFO waveforms:");
println!(" - sin: smooth, natural sweep");
println!(" - tri: linear ramp up and down");
println!(" - saw: slow rise, fast drop");
println!(" - sqr: instant toggle between states");
}
Modulation Depth and Attenuverters
The amount of modulation matters:
| Depth | Effect |
|---|---|
| 10% | Subtle shimmer |
| 25% | Noticeable movement |
| 50% | Dramatic sweep |
| 100% | Extreme wah-wah |
Quiver cables support attenuation:
// Connect with 50% modulation depth
patch.connect_with(
lfo.out("sin"),
vcf.in_("cutoff"),
Cable::new().with_attenuation(0.5),
)?;
Waveform Shapes
Each LFO waveform creates a different movement:
Sine Wave
Smooth, natural sweeping—good for gentle effects.
╱╲ ╱╲ ╱╲
╱ ╲ ╱ ╲ ╱ ╲
──╱────╲╱────╲╱────╲──
Triangle Wave
Linear sweeping—predictable, good for trills.
╱╲ ╱╲ ╱╲
╱ ╲ ╱ ╲ ╱ ╲
─╱────╲╱────╲╱────╲─
Sawtooth Wave
Rises slowly, drops instantly—creates rhythmic “pumping.”
╱│ ╱│ ╱│
╱ │ ╱ │ ╱ │
─╱──│─╱──│─╱──│──
Square Wave
Instant alternation between two states—tremolo/vibrato effect.
┌──┐ ┌──┐ ┌──┐
│ │ │ │ │ │
─┘ └──┘ └──┘ └─
Rate and Depth Interaction
quadrantChart
title LFO Character
x-axis Slow Rate --> Fast Rate
y-axis Subtle Depth --> Deep Depth
quadrant-1 Vibrato/Tremolo
quadrant-2 Slow Sweep
quadrant-3 Subtle Texture
quadrant-4 Frantic Motion
| Rate | Depth | Classic Use |
|---|---|---|
| 0.5Hz | 30% | Slow filter sweep |
| 2Hz | 10% | Subtle shimmer |
| 6Hz | 50% | Dubstep wobble |
| 8Hz | 5% | Guitar vibrato |
Multiple Modulation Sources
Combine LFO with envelope for evolving sounds:
flowchart TD
LFO[LFO<br/>Ongoing movement]
ENV[Envelope<br/>Per-note shape]
SUM((Σ))
VCF[Filter Cutoff]
LFO --> SUM
ENV --> SUM
SUM --> VCF
The envelope provides the initial “brightness burst,” while the LFO adds continuous movement during sustain.