Generative — markov() & lsys()
markov() — Discrete Markov Chain
markov<T>(states: T[], matrix: number[][], seed?: number): MarkovChain<T>Returns a MarkovChain whose .next(ctx) method deterministically steps the
chain to ctx.cycle, producing the same state for the same cycle number
regardless of where it is called. The randomness is seeded by seed (default
42).
Arguments
| Argument | Type | Description |
|---|---|---|
states | T[] | List of values the chain can emit |
matrix | number[][] | Transition probability matrix — matrix[i][j] is the probability of moving from state i to state j. Each row must sum to 1. |
seed | number (optional) | Random seed for reproducibility (default: 42) |
Example — evolving chord progressions
const prog = markov( ['I,IV', 'IV,V', 'V,I'], [ [0.5, 0.3, 0.2], // from I,IV [0.3, 0.4, 0.3], // from IV,V [0.6, 0.2, 0.2], // from V,I ],)
function* song(ctx) { const key = scales(8, `C3:major`) const pad = strings({ attack: 0.8, gain: 0.4 })
yield chords(8, key, prog.next(ctx))}Each cycle prog.next(ctx) returns the chord string for that cycle, determined
by the transition probabilities. Because the chain is replayed from cycle 0, the
same cycle always yields the same result — safe to use with live swap().
Deterministic replay
markov() is stateless between cycles — calling .next(ctx) replays the
chain from cycle 0 to ctx.cycle using the fixed seed. This means:
- Create the chain once (outside or inside the generator — both work)
swap()does not break the sequence- The result for cycle N is always the same for a given seed and matrix
lsys() — L-System String Expansion
lsys(axiom: string, rules: Record<string, string>, generations: number): stringExpands an axiom string by applying rewrite rules for generations steps.
Characters with no matching rule are kept as-is.
Arguments
| Argument | Type | Description |
|---|---|---|
axiom | string | Starting string |
rules | Record<string, string> | Rewrite rules — each key is replaced by its value |
generations | number | Number of expansion steps |
Example — rhythmic pattern from L-system
// x → x.x . → ..const rhythm = lsys('x', { x: 'x.x', '.': '..' }, 3)// gen 0: 'x'// gen 1: 'x.x'// gen 2: 'x.xx..x' (note: . not replaced first gen because it wasn't there)// gen 3: 'x.xx..xx.xx..x.xx..x' — use as a drum seq string
function* song(ctx) { const kk = kick({ freq: 55, gain: 0.7 }) yield cast(kk, 'drums', seq(4, rhythm), ctx)}lsys() is a pure function — call it once and reuse the result across cycles.
Melodic example
Use symbols that map to seq degree notation:
const melody = lsys('0', { '0': '0,2,4', '4': '4,5,4,2' }, 2)// → '0,2,4,5,4,2,0,2,4'
function* song(ctx) { const key = scales(4, 'C4:major') const synth = vasynth({ wave: 'triangle', gain: 0.4 }) yield cast(synth, key, seq(4, melody), ctx)}