Your First Patch
Let’s build a complete synthesizer voice: an oscillator through a filter, shaped by an envelope. This is the classic subtractive synthesis signal path.
flowchart LR
VCO[VCO] -->|saw| VCF[VCF]
VCF -->|lowpass| VCA[VCA]
ADSR[ADSR] -->|env| VCF
ADSR -->|env| VCA
GATE[Gate] -->|trigger| ADSR
VCA --> OUT[Output]
The Complete Example
//! First Patch Example
//!
//! A complete subtractive synthesizer voice demonstrating the core
//! Quiver workflow: VCO → VCF → VCA with ADSR envelope shaping.
//!
//! Run with: cargo run --example first_patch
use quiver::prelude::*;
use std::sync::Arc;
fn main() {
// CD-quality sample rate
let sample_rate = 44100.0;
// Create our patch (virtual modular case)
let mut patch = Patch::new(sample_rate);
// External control: gate signal for envelope triggering
let gate_cv = Arc::new(AtomicF64::new(0.0));
// Add modules to the patch
let gate = patch.add("gate", ExternalInput::gate(Arc::clone(&gate_cv)));
let vco = patch.add("vco", Vco::new(sample_rate));
let vcf = patch.add("vcf", Svf::new(sample_rate));
let vca = patch.add("vca", Vca::new());
let env = patch.add("env", Adsr::new(sample_rate));
let output = patch.add("output", StereoOutput::new());
// Patch cables: signal flow
// Gate triggers the envelope
patch.connect(gate.out("out"), env.in_("gate")).unwrap();
// VCO → VCF → VCA → Output (main audio path)
patch.connect(vco.out("saw"), vcf.in_("in")).unwrap();
patch.connect(vcf.out("lp"), vca.in_("in")).unwrap();
patch.connect(vca.out("out"), output.in_("left")).unwrap();
patch.connect(vca.out("out"), output.in_("right")).unwrap();
// Envelope modulates both filter and amplitude
patch.connect(env.out("env"), vcf.in_("cutoff")).unwrap();
patch.connect(env.out("env"), vca.in_("cv")).unwrap();
// Compile the patch for processing
patch.set_output(output.id());
patch.compile().unwrap();
println!(
"Patch compiled: {} modules, {} cables",
patch.node_count(),
patch.cable_count()
);
println!();
// Play a note: gate on
println!("Note ON - Gate rises to +5V");
gate_cv.set(5.0);
// Process attack phase (0.5 seconds)
let attack_samples = (sample_rate * 0.5) as usize;
let mut peak = 0.0_f64;
for _ in 0..attack_samples {
let (left, _) = patch.tick();
peak = peak.max(left.abs());
}
println!(" Attack complete, peak level: {:.2}V", peak);
// Release the note: gate off
println!("Note OFF - Gate falls to 0V");
gate_cv.set(0.0);
// Process release phase
let release_samples = (sample_rate * 1.0) as usize;
let mut release_peak = 0.0_f64;
for _ in 0..release_samples {
let (left, _) = patch.tick();
release_peak = release_peak.max(left.abs());
}
println!(" Release complete, final level: {:.4}V", release_peak);
println!();
println!("Subtractive synthesis voice complete!");
}
Understanding the Code
Creating a Patch
let mut patch = Patch::new(44100.0);
The Patch is your virtual modular case. The sample rate (44100 Hz = CD quality) determines timing precision for all modules.
Adding Modules
let vco = patch.add("vco", Vco::new(44100.0));
let vcf = patch.add("vcf", Svf::new(44100.0));
Each module gets a unique name and returns a NodeHandle. This handle lets you reference the module’s ports.
Making Connections
patch.connect(vco.out("saw"), vcf.in_("in")).unwrap();
The syntax mirrors real patching:
vco.out("saw")— the sawtooth output jackvcf.in_("in")— the filter’s audio input jack
Note: We use
in_()instead ofin()becauseinis a Rust keyword.
Compiling the Patch
patch.set_output(output.id());
patch.compile().unwrap();
Compilation:
- Performs topological sort (determines processing order)
- Validates all connections
- Detects any cycles (feedback loops)
Processing Audio
let (left, right) = patch.tick();
Each tick() advances the patch by one sample, returning stereo output.
Signal Flow in Detail
| Stage | Module | Function |
|---|---|---|
| 1 | VCO | Generates raw waveform (saw wave) |
| 2 | VCF | Filters harmonics (lowpass) |
| 3 | VCA | Controls amplitude |
| 4 | ADSR | Shapes volume over time |
| 5 | Output | Routes to stereo outputs |
The envelope simultaneously controls:
- Filter cutoff — brighter attack, darker sustain
- VCA level — shapes volume contour
This dual modulation creates the characteristic “filter sweep” sound of analog synths.
What’s Happening Mathematically
The signal chain computes:
$$\text{output}(t) = \text{env}(t) \cdot \text{LPF}(\text{saw}(t), \text{env}(t) \cdot f_c)$$
Where:
- $\text{saw}(t)$ is the sawtooth oscillator at time $t$
- $\text{LPF}$ is the lowpass filter
- $\text{env}(t)$ is the envelope value
- $f_c$ is the base cutoff frequency
The envelope modulating both the filter and amplitude creates the classic synth timbre.
Experimenting
Try these modifications:
-
Different waveform: Change
vco.out("saw")tovco.out("sqr")for a hollow, clarinet-like tone -
Add an LFO: Modulate the filter for a rhythmic wobble
-
Change envelope times: Longer attack for pads, shorter for percussion