6. Generative Music
Generative music creates variety without writing every note by hand. Nimbus has several tools for controlled randomness.
Seq modifiers
Modifiers wrap part of a seq string and change how it plays each cycle.
(rand: a, b, c) — random pick
Picks one child at random each cycle:
seq(4, '(rand:0,2,4,3,5)') // random note from list each cycleseq(4, '0,_,(rand:2,4,3),_') // random on beat 3 onlyseq(4, '(rand:x....,..x.,.x..x.)') // random drum pattern(robin: a, b, c) — round-robin
Cycles through children in order — one child per cycle:
seq(4, '(robin:0,2,4,5,3,1)') // step through scale tones in orderseq(4, '0,_,(robin:2,4,5,3),_') // rotates the middle noteseq(4, '(robin:x...,x.x.,.x.x)') // alternate between three drum patterns(rev: a, b, c) — forward / backward
Plays children forward on even cycles, backward on odd:
seq(4, '(rev:0,2,4,5)') // 0 2 4 5 → 5 4 2 0 → 0 2 4 5 → ...seq(4, '(rev:x..x,..x.)') // alternates direction of two patterns(shuffle: a, b, c) — random order
Shuffles all children randomly each cycle:
seq(4, '(shuffle:0,2,3,4,5)') // same notes, random order every cycleseq(4, '(shuffle:x...,..x.,x.x.)') // random drum pattern selection(everyN: pattern) — sparse trigger
Plays the child only on every N-th cycle, silent otherwise. The N is specified in the syntax (every2:...), (every4:...):
seq(4, '(every4:x...)') // hit only every 4th cycleseq(4, '(every2:...x)') // open hat on every 2nd cycleseq(4, '0,_,(every3:4),_') // occasional accent note(w: N*a, M*b) — weighted random
Like (rand:) but with weights. 3*a means item a is 3× as likely as 1*b:
seq(4, '(w:4*0,1*5,1*7)') // mostly root, occasionally leapsseq(4, '(w:2*x...,1*..x.,1*.x..)') // kick twice as common as alternativesCombining modifiers
Modifiers can be nested or combined in a single pattern:
// Round-robin between two random sub-patternsseq(4, '(robin:(rand:0,2,4),(rand:3,5,7)),(every2:4),_,0')markov — Markov chain
A Markov chain produces sequences where each element is chosen based on probabilities from the previous one. Great for evolving chord progressions or melodies.
const chain = markov( ['I', 'IV', 'V', 'VI'], // states [ // transition matrix [from][to] [0, 0.4, 0.3, 0.3], // from I: → IV 40%, → V 30%, → VI 30% [0.5, 0, 0.4, 0.1], // from IV: → I 50%, → V 40%, → VI 10% [0.7, 0.2, 0, 0.1], // from V: → I 70%, → IV 20%, → VI 10% [0.3, 0.3, 0.4, 0], // from VI: → I 30%, → IV 30%, → V 40% ])Each cycle, call chain.next() to get the next state:
function* song(ctx) { const lead = vasynth({ wave: 'triangle', release: 0.35 }) const key = scales(4, 'C4:major')
const mc = markov( [0, 2, 4, 5, 7], // scale degrees [ [0, 0.4, 0.3, 0.2, 0.1], [0.2, 0, 0.4, 0.3, 0.1], [0.3, 0.2, 0, 0.3, 0.2], [0.2, 0.3, 0.3, 0, 0.2], [0.4, 0.2, 0.2, 0.2, 0 ], ] )
const degree = mc.next() // get next note each cycle yield cast(lead, key, seq(4, `${degree},_,_,_`), ctx)}Markov chord progression
function* song(ctx) { const lead = vasynth({ wave: 'triangle', release: 0.3 }) const pad = vasynth({ wave: 'sawtooth', cutoff: 700, attack: 0.4, gain: 0.25 }) const kk = kick() const key = scales(4, 'D4:minor')
const mc = markov( ['I', 'VI', 'III', 'VII'], [ [0, 0.4, 0.3, 0.3], [0.4, 0, 0.3, 0.3], [0.3, 0.3, 0, 0.4], [0.6, 0.2, 0.2, 0 ], ] )
const chord = mc.next() const prog = chords(4, key, chord)
yield cast(lead, prog, seq(4, '0,2,4,3'), ctx) yield cast(pad, prog, seq(4, '{0,2,4},~,~,~'), ctx) yield cast(kk, 'drums', seq(4, 'x...x.x.'), ctx)}lsys — L-system
An L-system rewrites a string of symbols according to rules, then maps each symbol to a musical value. Good for fractal-like melodies and rhythms.
const ls = lsys( 'A', // axiom (starting string) { A: 'AB', B: 'A' }, // rewrite rules 4, // number of generations)Map symbols to musical data:
function* song(ctx) { const synth = vasynth({ wave: 'triangle', release: 0.25 }) const key = scales(4, 'C4:pentatonic minor')
const ls = lsys('A', { A: 'ABA', B: 'BAB' }, 3) const notes = ls.map({ A: 0, B: 2 }).join(',') // [0,2,0,2,0,2,0,2,...]
yield cast(synth, key, seq(4, notes), ctx)}L-system drum rhythm
Map symbols to x and . for drum patterns:
const ls = lsys('A', { A: 'Ax', B: 'xA', x: 'AB' }, 3)const drum = ls.map({ A: 'x', B: '.', x: 'x.' }).join('')
yield cast(kk, 'drums', seq(4, drum), ctx)bus — routing and sidechain ducking
bus() creates a signal bus that multiple machines can share. You can then apply sidechain compression — when the bus machine plays, it ducks (lowers the volume of) another bus.
Basic bus routing
const kickBus = bus()yield cast(kk, 'drums', seq(4, 'x...x.x.'), ctx, { bus: kickBus })Sidechain ducking
Make the pad duck when the kick hits:
function* song(ctx) { const kk = kick() const pad = vasynth({ wave: 'sawtooth', cutoff: 700, attack: 0.001, release: 0.5, gain: 0.5 }) const key = scales(4, 'C4:minor')
const kickBus = bus() kickBus.sidechain(pad, { amount: 0.8, decay: 0.15 }) // duck 80%, recover in 0.15s
yield cast(kk, 'drums', seq(4, 'x...x.x.'), ctx, { bus: kickBus }) yield cast(pad, key, seq(4, '{0,2,4},~,~,~'), ctx)}amount (0–1) is the depth of ducking; decay is how quickly the pad volume returns.
Routing multiple machines through a bus
const drumBus = bus()yield cast(kk, 'drums', seq(4, 'x.x.'), ctx, { bus: drumBus })yield cast(sn, 'drums', seq(4, '..x.'), ctx, { bus: drumBus })yield cast(hh, 'drums', seq(4, 'x.x.'), ctx, { bus: drumBus })All three drums share the drumBus, so a single sidechain target (e.g., a pad) ducks whenever any drum hit occurs.
Complete generative example
An evolving ambient piece with Markov chord changes, seq modifiers, and sidechain:
function* song(ctx) { const lead = chain( fmsynth({ ratio: 7, modIndex: 2, indexDecay: 1.0, release: 2.5, gain: 0.45 }), (m) => reverb(m, { mix: 0.5 }), ) const pad = chain( vasynth({ wave: 'sawtooth', cutoff: 700, attack: 0.8, release: 2.0, gain: 0.3 }), (m) => chorus(m, { rate: 0.3, depth: 0.5, mix: 0.6 }), (m) => reverb(m, { mix: 0.4 }), ) const kk = kick({ decay: 0.2 }) const hh = hat({ decay: 0.03 })
const key = scales(4, 'G4:dorian')
const mc = markov( ['Im7', 'IVm7', 'bVIImaj7', 'V7'], [ [0, 0.5, 0.3, 0.2], [0.4, 0, 0.4, 0.2], [0.5, 0.3, 0, 0.2], [0.7, 0.2, 0.1, 0 ], ] )
const chord = mc.next() const prog = chords(8, key, chord)
const kickBus = bus() kickBus.sidechain(pad, { amount: 0.7, decay: 0.12 })
yield cast(lead, prog, seq(4, '(rand:0,2,4,3),(every2:5),_,(robin:2,4,0)'), ctx) yield cast(pad, prog, seq(8, '{0,2,4},~,~,~,{0,2,4},~,~,~'), ctx) yield cast(kk, 'drums', seq(4, '(robin:x...x.x.,x...x...,x.x.x.x.)'), ctx, { bus: kickBus }) yield cast(hh, 'drums', seq(4, '(every2:x.x.x.x.)'), ctx)}What’s next?
You’ve covered the full Nimbus API. Explore the reference docs for details on every parameter:
- DSL Reference — full seq, scales, chords, and cast docs
- Machines — all synth parameter tables
- Effects — all FX options