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 that implements Pattern | 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”Declare the class implements Pattern, give it a query(cycle) method that declares what it
returns, -> Note | Number | Rest | Array | Pattern?, 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 implements Pattern { let notes; let idx;
fn new(notes) { this.notes = notes; this.idx = 0; }
fn query(cycle) -> Note | Number | Rest | Array | Pattern? { 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 implements Pattern { 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) -> Note | Number | Rest | Array | Pattern? { 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 implements Pattern { let chords;
fn new(chords) { this.chords = chords; }
fn query(cycle) -> Note | Number | Rest | Array | Pattern? { 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.
Transform it like any pattern
Section titled “Transform it like any pattern”A class that implements Pattern has every combinator, and every function that takes a pattern
takes it. Each captures the instance’s fields as they are at that moment:
use "std/instruments" { SamplerMelodic, Kit };
class Rise implements Pattern { let base;
fn new(base) { this.base = base; }
fn query(cycle) -> Note | Number | Rest | Array | Pattern? { return this.base + cycle % 5; }}
let lead = AudioTrack("lead");lead.set_instrument(SamplerMelodic(Kit("keys")));lead << Rise(60).fast(2).transpose(12);
PLAY;