Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Oscillators

Oscillators are the sound sources in any synthesizer—they generate the raw waveforms that filters and effects shape.

VCO (Voltage-Controlled Oscillator)

The primary sound source for subtractive synthesis.

let vco = patch.add("vco", Vco::new(44100.0));

Inputs

PortSignalRangeDescription
voctV/Oct±5VPitch (0V = C4)
fmBipolar CV±5VExponential FM (±5V ≈ ±5 octaves)
pwUnipolar CV0-10VPulse width, default 50%
syncGate0/5VHard sync reset
fm_linBipolar CV±5VLinear through-zero FM (±5V ≈ ±100% of base freq)

Outputs

PortSignalDescription
sinAudioSine wave
triAudioTriangle wave (bandlimited, PolyBLAMP)
sawAudioSawtooth wave (bandlimited, PolyBLEP)
sqrAudioSquare/pulse wave (bandlimited, PolyBLEP)

Waveform Mathematics

Sine: \[ y(t) = A \sin(2\pi f t) \]

Sawtooth (BLIT): \[ y(t) = 2 \left( \frac{t}{T} - \lfloor \frac{t}{T} + 0.5 \rfloor \right) \]

Triangle: \[ y(t) = 2 \left| 2 \left( \frac{t}{T} - \lfloor \frac{t}{T} + 0.5 \rfloor \right) \right| - 1 \]

Square/Pulse: \[ y(t) = \text{sign}(\sin(2\pi f t) - \cos(\pi \cdot \text{PW})) \]

Usage Example

// Basic VCO with external pitch
patch.connect(pitch_cv.out("out"), vco.in_("voct"))?;

// FM synthesis
patch.connect(modulator.out("sin"), vco.in_("fm"))?;

// PWM (pulse width modulation)
patch.connect(lfo.out("tri"), vco.in_("pw"))?;

LFO (Low-Frequency Oscillator)

Sub-audio oscillator for modulation.

let lfo = patch.add("lfo", Lfo::new(44100.0));

Inputs

PortSignalRangeDescription
rateUnipolar CV0-10VFrequency (0.01-30 Hz)
depthUnipolar CV0-10VOutput amplitude
resetTrigger0/5VPhase reset

Outputs

PortSignalDescription
sinBipolar CVSine wave (±5V)
triBipolar CVTriangle wave
sawBipolar CVSawtooth wave
sqrBipolar CVSquare wave
sin_uniUnipolar CVUnipolar sine (0-10V)

Rate Mapping

Default rate curve: \[ f = 0.01 \cdot e^{(\text{CV}/10) \cdot \ln(3000)} \]

CVFrequency
0V0.01 Hz
5V~1 Hz
10V30 Hz

Noise Generator

White and pink noise sources with a CV-controllable stereo second channel.

let noise = patch.add("noise", NoiseGenerator::new());

Inputs

PortSignalRangeDescription
correlationUnipolar CV0-10VStereo correlation (0 = independent, 1 = identical), default 0.3

Outputs

PortSignalDescription
whiteAudioWhite noise
pinkAudioPink noise
white2AudioSecond white channel, correlated with white
pink2AudioSecond pink channel, correlated with pink

Noise Spectra

White noise: Equal energy per frequency (flat spectrum)

\[ S(f) = \text{constant} \]

Pink noise: Equal energy per octave (-3dB/octave)

\[ S(f) \propto \frac{1}{f} \]

Pink noise is generated using the Voss-McCartney algorithm.


AnalogVco

VCO with analog imperfections for authentic vintage sound.

use quiver::analog::AnalogVco;

let vco = patch.add("vco", AnalogVco::new(44100.0));

Additional Features

  • V/Oct tracking errors
  • Component tolerance variation
  • High-frequency rolloff
  • Soft saturation

See Analog Modeling for details.


Supersaw

JP-8000-style stack of seven detuned PolyBLEP saws with an octave-down sub. type_id: supersaw.

let saw = patch.add("saw", Supersaw::new(44100.0));

Inputs

PortSignalRangeDescription
voctV/Oct±5VPitch (0V = C4)
detuneUnipolar CV0-10VDetune spread of the 7 voices, default 50%
mixUnipolar CV0-10VBlend between center voice and full supersaw, default 50%

Outputs

PortSignalDescription
outAudioMixed 7-oscillator supersaw
subAudioOctave-down bandlimited saw sub-oscillator

Wavetable

Mip-mapped, bandlimited wavetable oscillator with 8 tables and smooth crossfade morphing. type_id: wavetable.

let wt = patch.add("wt", Wavetable::new(44100.0));

Inputs

PortSignalRangeDescription
v_octV/Oct±5VPitch (0V = C4)
tableUnipolar CV0-10VTable select across 8 tables
morphUnipolar CV0-10VCrossfade morph between adjacent tables
syncGate0/5VHard sync (resets phase)

Outputs

PortSignalDescription
outAudioWavetable output

The 8 built-in tables are: Sine, Triangle, Saw, Square, Pulse (25%), Pulse (12%), Formant A, Formant O.


FormantOsc

Vocal-synthesis oscillator: a glottal pulse driven through five parallel resonant formant filters. type_id: formant_osc.

let vox = patch.add("vox", FormantOsc::new(44100.0));

Inputs

PortSignalRangeDescription
v_octV/Oct±5VPitch (0V = C4)
vowelUnipolar CV0-10VInterpolates the vowel A → E → I → O → U
formant_shiftBipolar CV±5VShifts all formant frequencies (0.5×–2×)
vibratoUnipolar CV0-10VVibrato depth (up to ±0.5 semitone)

Outputs

PortSignalDescription
outAudioVocal formant output

KarplusStrong

Karplus-Strong physical-model plucked string with damping, brightness, and inharmonicity. type_id: karplus_strong.

let string = patch.add("string", KarplusStrong::new(44100.0));

Inputs

PortSignalRangeDescription
voctV/Oct±5VPitch → string period
triggerTrigger0/5VRising edge plucks the string
dampingUnipolar CV0-10VLoop lowpass amount, default 50%
brightnessUnipolar CV0-10VNoise-vs-impulse excitation blend, default 50%
stretchBipolar CV±5VAll-pass stretch / inharmonicity

Outputs

PortSignalDescription
outAudioPlucked-string output

SamplePlayer

Mono sample playback with V/Oct pitch, selectable start position, one-shot / looping modes, and an end-of-sample trigger. Reads are cubic-interpolated; the audio path is allocation-free (only set_buffer allocates). type_id: sample_player.

// buffer: Vec<f64>, recorded at buffer_sample_rate; engine runs at engine_sample_rate.
let sp = patch.add("sp", SamplePlayer::new(buffer, 44100.0, 44100.0));
// Or start empty and load later with `set_buffer`:
let sp = patch.add("sp", SamplePlayer::empty(44100.0));

Inputs

PortSignalRangeDescription
trigTrigger0/5VRising edge starts one-shot playback
gateGate0/5VGated playback (release stops it)
voctV/Oct±5VPlayback pitch (0V = unity rate)
startUnipolar CV0-10VStart position (0–1 of buffer)
loopGate0/5VEnables looping when high

Outputs

PortSignalDescription
outAudioSample output
eosTriggerFires at end of sample

Capture

Records its input into a fixed-size buffer and plays the recording back like a SamplePlayer: resample a signal (an AudioInput, or the patch itself) and play it as a source. type_id: capture.

let cap = patch.add("cap", Capture::new(44100.0));           // 4 s buffer
let cap = patch.add("cap", Capture::with_seconds(44100.0, 10.0));
patch.connect(mic.out("out"), cap.in_("in"))?;
patch.connect(env_gate, cap.in_("gate"))?;                  // play while held
patch.connect(keyboard_voct, cap.in_("voct"))?;             // pitched playback

Inputs

PortSignalRangeDescription
inAudio±5VSignal to record
recordGate0/5VRising edge starts a new take from the top of the buffer; falling edge (or a full buffer) stops it
trigTrigger0/5VRising edge plays the take to its end
gateGate0/5VPlays while high
voctV/Oct±5VPlayback pitch (0V = recorded speed)
loopGate0/5VPlayback wraps while high

Outputs

PortSignalDescription
outAudioPlayback
eosTriggerEnd of a one-shot, or each loop wrap

The recording

The buffer is allocated at construction (f32); recording and playback never allocate. A take remembers the sample rate it was made at and plays at the right speed if the graph’s rate changes. reset() stops recording and playback but keeps the take. The host can read it (recording()), replace it (set_recording(&samples, rate), e.g. with an audition clip) or clear() it.

The take is saved with the patch, in ModuleDef.state:

{ "format": "f32le-base64", "sample_rate": 48000.0,
  "capacity": 192000, "length": 96000, "data": "AAAAAM3MzD0..." }

The samples are little-endian f32 in base64: lossless, so a reloaded patch plays bit-identically. Every state a Capture can save loads back, whatever its rate or length. Patch JSON is untrusted, so the saved capacity is only a request: the buffer becomes capacity clamped to [length, max(length, the buffer the module already has)], and never allocates memory the data does not back. Loading rejects another format, a rate that is not positive and finite, data that is not base64, a length that disagrees with the data, and non-finite samples. An empty capture saves no state.


Common Patterns

Detuned Oscillators

let vco1 = patch.add("vco1", Vco::new(sr));
let vco2 = patch.add("vco2", Vco::new(sr));

// Slight detune for thickness
let detune = patch.add("detune", Offset::new(0.01));  // ~12 cents

patch.connect(pitch.out("out"), vco1.in_("voct"))?;
patch.connect(pitch.out("out"), vco2.in_("voct"))?;
patch.connect(detune.out("out"), vco2.in_("voct"))?;  // Adds to pitch

Hard Sync

// Slave syncs to master
patch.connect(master.out("sqr"), slave.in_("sync"))?;

// Modulate slave pitch for classic sync sweep
patch.connect(lfo.out("sin"), slave.in_("voct"))?;

FM Synthesis

// Carrier:Modulator = 1:1 for harmonic FM
patch.connect(modulator.out("sin"), carrier.in_("fm"))?;

// Control FM depth with envelope
patch.connect(env.out("env"), fm_vca.in_("cv"))?;
patch.connect(fm_vca.out("out"), carrier.in_("fm"))?;