Generative Patterns
So far your patterns have been written out note by note. Generative patterns flip that around: you describe a process, and the pattern produces its own notes as it plays. Random choices, walking melodies, chord progressions that wander and resolve — all from a few lines of rules.
The catch with most generative tools is that they’re unrepeatable: hit play twice and you get two different takes. Resonon’s generators are different. They’re all deterministic and seekable — driven by the cycle number rather than live randomness, so the same cycle always produces the same result. You can loop, seek, or restart and hear the exact same performance every time. It’s like a generative MIDI plugin that never forgets what it played.
This chapter builds up in three steps: the randomness primitives that make choices, streams that walk a value forward cycle by cycle, and Markov chains that move between states by probability.
Randomness
Section titled “Randomness”The randomness functions live in std/random. The key ones take a seed and
are fully deterministic — same seed, same answer, every time:
use "std/random" { rand, choose, choose_weighted };
PRINT rand(0); // a float in [0.0, 1.0), fixed for seed 0PRINT rand(1); // a different float, fixed for seed 1
let notes = #[C4, E4, G4, B4];PRINT choose(0, notes); // picks one element, deterministicallyPRINT choose(2, notes); // a different seed, a different (fixed) pickchoose_weighted biases the pick — pass a parallel array of weights, and higher
weights are chosen more often:
use "std/random" { choose_weighted };
let chords = #[[C4 E4 G4], [F4 A4 C5], [G4 B4 D5]];let weights = #[3, 1, 1]; // C major three times as likely
PRINT choose_weighted(0, chords, weights);On their own these just return values. Their real power shows up when you feed them the cycle number as the seed inside a stream or script pattern — then every cycle makes a reproducible “random” choice. That’s the next section.
Streams
Section titled “Streams”A stream walks a value forward: each cycle’s output is computed from the
previous cycle’s. You give stream() an initial value and a function
fn(prev, cycle) that returns the next one. It lives in std/generative.
Streams work with plain MIDI numbers (60 = C4, 64 = E4, 67 = G4), so you can do arithmetic on them directly. The simplest walk just climbs by a semitone each cycle:
use "std/generative" { stream };
let chromatic = stream(60, fn(prev, cycle) { return prev + 1;});
PRINT chromatic.query(0); // 60PRINT chromatic.query(1); // 61PRINT chromatic.query(4); // 64.query(n) asks the stream for the value at cycle n — handy for checking what
a stream does before you play it. Now feed choose the cycle number and you have
a random walk — a melody that steps around unpredictably, but identically on
every playback:
use "std/instruments" { SamplerMelodic, Kit };use "std/generative" { stream };use "std/random" { choose };
let steps = #[-2, -1, 1, 2];let walk = stream(64, fn(prev, cycle) { return prev + choose(cycle, steps);});
let lead = AudioTrack("lead");lead.load_instrument(SamplerMelodic(Kit("keys")));lead >> master;lead << walk;
PLAY;To keep a walk inside a register, check the bounds and bounce it back:
use "std/generative" { stream };use "std/random" { choose };
let bound_steps = #[-3, -1, 1, 3];let bounded = stream(60, fn(prev, cycle) { let next = prev + choose(cycle, bound_steps); if (next > 72) { return prev - 3; } // too high — turn around if (next < 60) { return prev + 3; } // too low — turn around return next;});
PRINT bounded.query(0);PRINT bounded.query(8);rand gives you probability-based decisions — here, a 70% chance to step up:
use "std/generative" { stream };use "std/random" { rand };
let prob_walk = stream(65, fn(prev, cycle) { if (rand(cycle) < 0.7) { return prev + 1; } return prev - 1;});
PRINT prob_walk.query(3);The initial value can also be an array, which lets you evolve a whole chord:
use "std/generative" { stream };
// A chord that drifts up a whole step each cyclelet voicing = stream(#[60, 64, 67], fn(prev, cycle) { let result = #[]; for note in prev { result.push(note + 2); } return result;});
PRINT voicing.query(2);Markov chains
Section titled “Markov chains”A Markov chain moves between numbered states by probability. You give
markov() the number of states, a transition matrix, and a starting state. Each
cycle it looks at the current state’s row, rolls the (cycle-seeded) dice, and
jumps to the next state.
Read the matrix row by row: row i says “when in state i, here are the relative chances of going to each state.” This 2-state chain just alternates — state 0 always goes to 1, state 1 always back to 0:
use "std/generative" { markov };
let alternating = markov( 2, #[ #[0.0, 1.0], // from state 0 -> always state 1 #[1.0, 0.0] // from state 1 -> always state 0 ], 0 // start in state 0);
PRINT alternating.query(0); // 0PRINT alternating.query(1); // 1PRINT alternating.query(2); // 0markov() outputs state indices (0, 1, 2…), not notes. To turn states into
music, map them through a note array. The cleanest way is a small
script pattern — a class with a query()
method that asks the chain for a state, then looks up the note:
use "std/instruments" { SamplerMelodic, Kit };use "std/generative" { markov };
class Mapped { let chain; let notes;
fn new(chain, notes) { this.chain = chain; this.notes = notes; }
fn query(cycle) { return this.notes[this.chain.query(cycle)]; }}
let three_state = markov( 3, #[ #[0.5, 0.5, 0.0], #[0.0, 0.5, 0.5], #[0.5, 0.0, 0.5] ], 0);
let lead = AudioTrack("lead");lead.load_instrument(SamplerMelodic(Kit("keys")));lead >> master;lead << Mapped(three_state, #[C4, E4, G4]);
PLAY;The zero entries enforce constraints: state 0 can never jump straight to state 2, and state 2 can never reach state 1. That creates a directed flow with occasional self-loops.
Weights don’t have to sum to 1
Section titled “Weights don’t have to sum to 1”Rows are normalized automatically, so you can use intuitive whole-number weights instead of fiddling with probabilities:
use "std/generative" { markov };
let weighted = markov( 3, #[ #[1, 3, 1], // "3" means three times as likely as "1" #[1, 1, 3], #[3, 1, 1] ], 0);
PRINT weighted.query(0);PRINT weighted.query(4);Absorbing states
Section titled “Absorbing states”A state whose row points only to itself is absorbing: once reached, the chain never leaves. That’s a natural fit for a cadence — wander through tension, then resolve and stay home:
use "std/generative" { markov };
let resolving = markov( 3, #[ #[0.0, 0.7, 0.3], // start: mostly move toward state 1 #[0.0, 0.3, 0.7], // middle: mostly move toward state 2 #[0.0, 0.0, 1.0] // end: stay forever (absorbing) ], 0);
PRINT resolving.query(2);PRINT resolving.query(10); // settled on state 2Determinism and seekability
Section titled “Determinism and seekability”Every generator here is built on the cycle number, so querying the same cycle always returns the same value — no matter when or how often you ask:
use "std/generative" { markov };
let chain = markov(3, #[#[0.5,0.5,0.0],#[0.0,0.5,0.5],#[0.5,0.0,0.5]], 0);
PRINT chain.query(20);PRINT chain.query(20); // identicalThis is what makes loops, restarts, and seeking behave: there’s no hidden live state to drift. The cost is that finding the value at cycle N replays all N steps from the start (it’s O(N)). Each step is cheap, and the scheduler stays responsive, so for normal musical ranges — hundreds to a few thousand cycles — you’ll never notice.
Which generator?
Section titled “Which generator?”| Randomness | Stream | Markov chain | |
|---|---|---|---|
| What it is | Seeded one-off picks | A value walked forward each cycle | Probabilistic moves between states |
| State | None | One value (prev) | Current state index |
| You provide | A seed | fn(prev, cycle) | A transition matrix |
| Reach for it when | Making a single choice | Evolving one value over time | Directed, rule-based progressions |
For anything more elaborate — multiple state variables, helper methods, custom logic — graduate to a full script pattern.
See Also
Section titled “See Also”- Transforming Patterns — reshape any pattern
- Script Patterns — class-based patterns with custom state
Next Steps
Section titled “Next Steps”For full control over generation, write patterns as code.
- Script Patterns — class-based patterns with custom state