Building a Sequenced Bass
Let’s create something musical: a step sequencer driving a bass synthesizer. This is the foundation of countless electronic music tracks.
flowchart LR
CLK[Clock] -->|tempo| SEQ[Step<br/>Sequencer]
SEQ -->|V/Oct| VCO[VCO]
SEQ -->|gate| ENV[ADSR]
VCO --> VCF[VCF]
ENV --> VCF
ENV --> VCA[VCA]
VCF --> VCA
VCA --> OUT[Output]
style SEQ fill:#e74c3c,color:#fff
style CLK fill:#50c878,color:#fff
The Step Sequencer
A step sequencer cycles through a series of values, advancing on each clock pulse:
Step: 1 2 3 4 5 6 7 8
CV: ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
│ │ │ │ │ │ │ │ │ │ │ │
Gate: └─┘ └─┘ └─┘ └─┘ └─┘ └─┘
C3 D3 rest G3 C3 rest E3 D3
Each step can have:
- CV value: The pitch (in V/Oct)
- Gate: On or off (rest = off)
V/Oct and Musical Pitches
Converting notes to voltages:
| Note | MIDI | V/Oct |
|---|---|---|
| C3 | 48 | -1.0V |
| C4 | 60 | 0.0V |
| D4 | 62 | +0.167V |
| E4 | 64 | +0.333V |
| G4 | 67 | +0.583V |
| C5 | 72 | +1.0V |
The formula:
$$V = \frac{\text{MIDI} - 60}{12}$$
Building the Patch
//! Tutorial: Building a Sequenced Bass
//!
//! A step sequencer driving a classic subtractive bass voice.
//! This pattern is the foundation of house, techno, and many other genres.
//!
//! Run with: cargo run --example tutorial_sequenced_bass
use quiver::prelude::*;
fn main() {
let sample_rate = 44100.0;
let mut patch = Patch::new(sample_rate);
// Master clock - sets the tempo
let clock = patch.add("clock", Clock::new(sample_rate));
// Step sequencer - stores our bassline pattern
let seq = patch.add("seq", StepSequencer::new());
// Bass voice: VCO → VCF → VCA
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));
// Output
let output = patch.add("output", StereoOutput::new());
// Clock → Sequencer
patch.connect(clock.out("div_8"), seq.in_("clock")).unwrap();
// Sequencer → Voice
patch.connect(seq.out("cv"), vco.in_("voct")).unwrap();
patch.connect(seq.out("gate"), env.in_("gate")).unwrap();
// 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 → Filter & VCA
patch.connect(env.out("env"), vcf.in_("cutoff")).unwrap();
patch.connect(env.out("env"), vca.in_("cv")).unwrap();
patch.set_output(output.id());
patch.compile().unwrap();
println!("=== Sequenced Bass Demo ===\n");
// The sequencer has 8 steps with default values
// In a real application, you'd set the step CVs programmatically
// Convert MIDI note to V/Oct
fn midi_to_voct(note: u8) -> f64 {
(note as f64 - 60.0) / 12.0
}
// Our bassline: C3, D3, rest, G2, C3, rest, E3, D3
let pattern = [
(48, true), // C3
(50, true), // D3
(0, false), // rest
(43, true), // G2
(48, true), // C3
(0, false), // rest
(52, true), // E3
(50, true), // D3
];
println!("Bassline pattern:");
for (i, (note, active)) in pattern.iter().enumerate() {
if *active {
let voct = midi_to_voct(*note);
let note_name = match note % 12 {
0 => "C",
1 => "C#",
2 => "D",
3 => "D#",
4 => "E",
5 => "F",
6 => "F#",
7 => "G",
8 => "G#",
9 => "A",
10 => "A#",
11 => "B",
_ => "?",
};
let octave = (note / 12) - 1;
println!(" Step {}: {}{} ({:.3}V)", i + 1, note_name, octave, voct);
} else {
println!(" Step {}: rest", i + 1);
}
}
println!("\nRunning sequencer for 2 seconds...\n");
// Run the patch
let total_samples = (sample_rate * 2.0) as usize;
let step_samples = total_samples / 16; // ~8 steps at default tempo
for step in 0..16 {
let mut peak = 0.0_f64;
for _ in 0..step_samples {
let (left, _) = patch.tick();
peak = peak.max(left.abs());
}
let step_num = (step % 8) + 1;
let bar = "█".repeat((peak * 10.0) as usize);
println!("Step {}: {:5.2}V |{}", step_num, peak, bar);
}
println!("\nThe sequencer cycles through the pattern,");
println!("triggering the envelope on each gated step.");
}
Clock Divisions
The clock module provides multiple time divisions:
graph TB
MASTER[Master Clock<br/>120 BPM] --> D1[1/1<br/>Whole notes]
MASTER --> D2[1/2<br/>Half notes]
MASTER --> D4[1/4<br/>Quarter notes]
MASTER --> D8[1/8<br/>Eighth notes]
MASTER --> D16[1/16<br/>Sixteenth notes]
For a bassline at 120 BPM:
- 1/8 notes = 4 Hz (classic house tempo)
- 1/16 notes = 8 Hz (driving techno)
Filter Envelope Relationship
The key to punchy bass is the filter envelope:
Attack: Fast (5ms)
Decay: Medium (100-200ms)
Sustain: Low (20-40%)
Release: Quick (50-100ms)
This creates the characteristic “pluck” where brightness fades quickly.
Accent and Dynamics
Real sequences have accents—emphasized notes. Implement with velocity:
sequenceDiagram
participant SEQ as Sequencer
participant ENV as Envelope
SEQ->>ENV: Step 1 (normal)
Note over ENV: Attack → Sustain
SEQ->>ENV: Step 2 (accented)
Note over ENV: Attack → Higher peak<br/>→ Sustain
Classic Patterns
House Bass
Step: 1 2 3 4 5 6 7 8
Note: C - C - C - C C
The off-beat creates the groove.
Acid (TB-303 Style)
Step: 1 2 3 4 5 6 7 8
Note: C C D - F - D C
Acc: X X
Slide: → →
Accents and slides define the style.
Minimal Techno
Step: 1 2 3 4 5 6 7 8
Note: C - - - C - - -
Space and repetition create hypnotic effect.
Going Further
- Add slide/portamento with
SlewLimiter - Randomize steps with
BernoulliGate - Quantize to scale with
Quantizer - Layer with detuned second VCO
Next: FM Synthesis Basics