Composing systems, not pieces
Learning objectives
- learner can design a rule-system that generates music rather than fixing notes
- learner can apply stochastic, Markov and L-system techniques to pitch and rhythm
- learner can counteract algorithmic flatness with interference, entropy and interactivity
Capstone — one whole task that evidences the objectives
Design and run a generative music system (in SC or Tidal) that combines a Markov or L-system generator with a stochastic layer, deliberately introduces interference between layers, and includes a mechanism countering uniformity — then critique the process-vs-product result by ear.
Prerequisite modules
This module marks the shift from writing notes to writing the machine that writes the notes. In a live-coded techno or ambient set, you cannot hand-place every event mid-performance; what carries a 30-minute set is a generative system whose behaviour you steer. The whole task here is to build such a system — in SuperCollider or TidalCycles, on the same rig your prior pattern modules used — and then judge honestly whether the process produced music worth keeping.
The arc starts supported: you extend a Pbind or Tidal pattern you already know with a single stochastic element, leaning on the bounded random walk (Pbrown) and the survey of algorithmic strategies as just-in-time how-tos. Next you swap flat randomness for memory — training a Markov model on a pitch/rhythm sequence (PPMC in SC, markovPat in Tidal) — and separately grow a rhythm from production rules using Prewrite or lindenmayer. Only then do you combine layers, deliberately letting a structural pattern and a material pattern collide, and add a mechanism (entropy variation, interaction, structural bias) against the dramatic flatness that pure generation drifts toward. The final run is unsupported: your system, your ears, your critique.
The required atoms are exactly what the capstone cannot survive without: the compose-a-system stance and meta-composition concept, one working Markov path and one L-system path, a stochastic layer, and the interference and anti-flatness principles that turn a demo into music. Supporting atoms enrich the edges — Spiegel’s transformation taxonomy, tendency masks, reproducible seeds, the halting problem’s strange resonance — deepening your critique without gating the build.
Walkthrough
This is the shift from writing notes to writing the machine that writes the notes — what carries a 30-minute set. We’ll build it in strudel.cc (Ctrl-Enter play, Ctrl-. stop), the same pattern language as Tidal, browser-first; the ideas port straight to SuperCollider/Tidal for the full capstone. Each step is a complete, playable program — let several cycles pass to hear the system behave.
1 — a stochastic layer. degradeBy(0.4) randomly drops 40% of events each cycle — a hi-hat that’s different every bar without you touching it. This is the seed of generation: controlled chance ([[stochastic-music-generation]] if present).
sound("hh*16").bank("RolandTR909").degradeBy(0.4).gain(0.6)
2 — probabilistic transforms. Instead of dropping events, randomly transform them: sometimesBy(0.3, x => x.speed(2)) doubles the pitch of ~30% of notes, rarely and often are presets. The material mutates as it plays ([[algorithmic-composition]]).
n("0 3 5 7 5 3").scale("C:minor").sound("sawtooth").lpf(1200)
.sometimesBy(0.3, x => x.add(note(12)))
.rarely(x => x.fast(2))
3 — a random generator with memory of range. Draw notes from a distribution rather than a fixed line: irand(8) picks a random scale degree, .segment(8) samples 8 per cycle — an endless, in-key melody the machine writes ([[compose-a-system-not-a-piece]]).
n(irand(8).segment(8)).scale("C:minor").sound("triangle").lpf(1500).gain(0.7)
4 — continuous drift with perlin. A smooth random signal (not jumpy like rand) wanders a parameter over time — here the filter drifts organically, so the timbre evolves on its own ([[bounded-random-walk]] if present).
n(irand(8).segment(8)).scale("C:minor").sound("sawtooth").lpf(perlin.range(400, 2500).slow(4)).gain(0.6)
5 — interference between a structural and a material layer. Let two generators collide: a fixed Euclidean structure against a stochastic material line. Their independent periods cross, producing patterns present in neither ([[interference-between-layers]] if present).
$: sound("bd(5,8)").bank("RolandTR909")
$: n(irand(12).segment(4)).scale("C:minor").sound("sawtooth").lpf(1200).degradeBy(0.3).gain(0.6)
6 — a self-evolving system with an anti-flatness mechanism (the capstone). Pure generation drifts toward sameness; counter it with structural change over time. Here someCyclesBy/every inject periodic shifts (a fill, a transpose) so the system has an arc, not a plateau — a machine you steer rather than a loop you repeat:
setcpm(130/4)
$: sound("bd*4, [~ sd]").bank("RolandTR909").sometimesBy(0.15, x => x.fast(2))
$: sound("hh*16").bank("RolandTR909").degradeBy(0.5).gain(0.5)
$: n(irand(8).segment(8)).scale("C:minor").sound("sawtooth")
.lpf(perlin.range(500, 2500).slow(8))
.every(8, x => x.add(note(12)))
.degradeBy(0.2).gain(0.6)
What good sounds like. A system that stays interesting on its own for minutes — recognisably the same piece, yet never quite repeating, with enough structural change (the every/someCyclesBy shifts) that it goes somewhere instead of flatlining. If it sounds random/aimless, your stochastic amounts are too high or nothing constrains them (tighten the scale, lower degradeBy); if it sounds static, you have generation but no counter-uniformity — add a periodic transform. The honest test is the module’s: let it run, then judge process-vs-product by ear. (Skill map: live-coder Domain E3 / C — composing systems, not pieces.)
Now make it yours. Change the every(8, ...) transpose to a different structural move (rev, chunk). Weight the random selection with wchoose so some notes are likelier. Add a third stochastic layer that only appears someCyclesBy(0.25, ...). Port the whole idea to Tidal/SuperCollider for a Markov generator and per-voice control (the module’s full capstone).
Runnable examples
Generated from the context/ instrument corpus by concept (redistributable idioms only). Do not edit — regenerate with gen-module-examples.mjs.
scale-constraint
n("0 2 4 6").scale("c:minor")
strudel-0009 · CC0
play (scale :c4, :minor).tick; sleep 0.25
sonicpi-0012 · CC0
random-walk-melody
Pbind(\degree, Pbrown(0, 7, 1, inf), \dur, 0.25).play
supercollider-0027 · CC0
@n = (@n || 0) + [-1, 0, 1].choose; play (scale :e3, :minor)[@n % 8]; sleep 0.25
sonicpi-0019 · CC0
weighted-random-choice
play (ring :e3, :e3, :e3, :g3).choose; sleep 0.25
sonicpi-0020 · CC0
Pbind(\degree, Pwrand([0, 3, 7], [0.6, 0.3, 0.1], inf), \dur, 0.25).play
supercollider-0026 · CC0
pattern-sequencing
Pbind(\degree, Pseq([0, 2, 4, 7], inf), \dur, 0.25).play
supercollider-0023 · CC0
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Live Coder — zero to performing live-coded music — Generative Systems & the SuperCollider Stack required
Unlocks — modules that require this one