Skip to content

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 cycle
seq(4, '0,_,(rand:2,4,3),_') // random on beat 3 only
seq(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 order
seq(4, '0,_,(robin:2,4,5,3),_') // rotates the middle note
seq(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 cycle
seq(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 cycle
seq(4, '(every2:...x)') // open hat on every 2nd cycle
seq(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 leaps
seq(4, '(w:2*x...,1*..x.,1*.x..)') // kick twice as common as alternatives

Combining modifiers

Modifiers can be nested or combined in a single pattern:

// Round-robin between two random sub-patterns
seq(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: