Sweep a Parameter with an LFO
Anything you set with << — an effect parameter, a track’s volume or pan, a send level — takes
a signal as well as a number. The LFOs are in std/signals.
Signals & Time explains how a signal keeps time.
Bind an LFO to the parameter
Section titled “Bind an LFO to the parameter”Put the effect on the track, then send a signal to its parameter:
use "std/instruments" { Sampler, Kit };use "std/effects" { Lowpass, Delay };use "std/signals" { Sine, Sine2, Tri };
let drums = AudioTrack("drums");drums.set_instrument(Sampler(Kit("CR-78")));drums << [bd sd bd sd];
let filter = Lowpass("lowpass", 800);let delay = Delay("delay", 0.25, 0.4);drums.set_effects(#[filter, delay]);
filter.parameter("Cutoff") << Sine(1).range_exp(400, 4000);
PLAY;For pan, take a bipolar LFO — Sine2, Tri2, Saw2 or Square2 — which swings from -1 to 1:
drums.pan << Sine2(0.5);Set the speed
Section titled “Set the speed”The argument is sweeps per cycle, so the LFO follows the tempo: Sine(4) sweeps four times a cycle,
Sine(0.25) once every four cycles. To count in another unit, give the LFO a timebase. A note value
keeps it synced to the tempo:
filter.parameter("Cutoff") << Sine(1).set_timebase(note(1, 8).dotted()).range_exp(200, 4000); // a dotted eighthfilter.parameter("Cutoff") << Tri(1).set_timebase(note(1, 4).triplet()).range_exp(200, 4000); // a quarter tripletfilter.parameter("Cutoff") << Sine(1).set_timebase(cycles(2)).range_exp(200, 4000); // two barsSeconds free it from the tempo, like switching an LFO’s sync off:
filter.parameter("Cutoff") << Sine(2).set_timebase(seconds(1)).range_exp(200, 4000); // 2 Hzdelay.parameter("Time") << Sine(1).set_timebase(seconds(0.5)).range(0.1, 0.4); // a 0.5 s periodSet the range
Section titled “Set the range”.range(min, max) maps linearly. For cutoff, and anything else you hear as pitch, use
.range_exp(min, max), so the sweep spends equal time in each octave:
filter.parameter("Cutoff") << Sine(1).range(200, 4000); // midpoint ~2100 Hzfilter.parameter("Cutoff") << Sine(1).range_exp(200, 4000); // midpoint ~894 HzShape the sweep
Section titled “Shape the sweep”Add, scale and invert LFOs with arithmetic:
filter.parameter("Cutoff") << (Sine(1) + Tri(3) * 0.5).range_exp(200, 8000);delay.parameter("Feedback") << Sine(4) * 0.2 + 0.5; // a small wobble around 0.5filter.parameter("Cutoff") << (-Sine(1)).range(200, 4000); // invertedKeep the phase when you change the speed
Section titled “Keep the phase when you change the speed”Binding a signal restarts it at the next cycle boundary. To change an LFO’s speed without its phase
jumping, put it on the global clock with .continuous():
filter.parameter("Cutoff") << Sine(2).set_timebase(seconds(1)).range(200, 4000).continuous();To restart it on a grid instead, give .set_retrigger() the period: .set_retrigger(cycles(1)) on every
cycle, .set_retrigger(beats(1)) on every beat, .set_retrigger(note(1, 8).dotted()) every dotted eighth.
Lock two parameters together
Section titled “Lock two parameters together”Bind one signal to both, and they move in phase. Two separate signals drift independently:
let lfo = Sine(0.5).range(0.1, 0.4);delay.parameter("Time") << lfo;delay.parameter("Feedback") << lfo;Related
Section titled “Related”- Signals — every LFO and signal method
- Draw an Automation Curve — a shape you place in time instead of a loop
- Write a Modulation Source — an LFO of your own
- Map a Controller — drive a parameter from a controller