Write a Pattern as a Class
Write a script pattern when a stream or a
Markov chain is not enough — when you need several state variables, helper methods, or logic that does not fit
one fn(prev, cycle). The contract (what query() may return and do) is in the
script pattern reference.
| Script pattern | Stream | |
|---|---|---|
| Defined as | A class with query(cycle) | stream(init, fn(prev, cycle)) |
| State | Any number of named fields | A single value (prev) |
| Extras | Helper methods, tables in fields | Closure only |
| Use it for | Logic with structure or several state variables | Walking one value forward |
Step through a list of notes
Section titled “Step through a list of notes”Give the class a query(cycle) method and send an instance to a track. Fields you change in
query() carry over to the next cycle:
use "std/instruments" { SamplerMelodic, Kit };
class Stepper { let notes; let idx;
fn new(notes) { this.notes = notes; this.idx = 0; }
fn query(cycle) { let note = this.notes[this.idx % this.notes.length()]; this.idx = this.idx + 1; return note; }}
let lead = AudioTrack("lead");lead.set_instrument(SamplerMelodic(Kit("keys")));lead << Stepper(#[C4, D4, E4, F4, G4]);
PLAY;| Cycle | this.idx before | query() returns | this.idx after |
|---|---|---|---|
| 0 | 0 | notes[0] = C4 | 1 |
| 1 | 1 | notes[1] = D4 | 2 |
| … | … | … | … |
| 5 | 5 | notes[0] = C4 | 6 |
Use helper methods and lookup tables
Section titled “Use helper methods and lookup tables”A script pattern is an ordinary class, so query() can call your own methods. Build tables once in
new(), store them in a field, and read the field in query(). This walk clamps itself to a
register:
use "std/random" { choose };
class BoundedWalk { let pos; let lower; let upper; let steps;
fn new(start, lower, upper) { this.pos = start; this.lower = lower; this.upper = upper; this.steps = #[-2, -1, 1, 2]; }
fn clamp(val) { if val < this.lower { return this.lower; } if val > this.upper { return this.upper; } return val; }
fn query(cycle) { this.pos = this.clamp(this.pos + choose(cycle, this.steps)); return this.pos; }}
let walk = BoundedWalk(60, 48, 72);print(walk.query(0));print(walk.query(1));Drive the pattern from the cycle number
Section titled “Drive the pattern from the cycle number”You don’t always need state. Index a list with cycle to play a chord progression:
use "std/instruments" { SamplerMelodic, Kit };
class ChordProgression { let chords;
fn new(chords) { this.chords = chords; }
fn query(cycle) { return this.chords[cycle % this.chords.length()]; }}
let prog = ChordProgression(#[ #[C4, E4, G4], #[D4, F4, A4], #[G4, B4, D5], #[C4, E4, G4],]);
let pad = AudioTrack("pad");pad.set_instrument(SamplerMelodic(Kit("keys")));pad << prog;
PLAY;An array return plays as a chord. Return a pattern such as [C4 E4 G4] to spread notes across the
cycle, or [_] for a silent cycle.