Skip to content

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 patternStream
Defined asA class with query(cycle)stream(init, fn(prev, cycle))
StateAny number of named fieldsA single value (prev)
ExtrasHelper methods, tables in fieldsClosure only
Use it forLogic with structure or several state variablesWalking one value forward

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;
Cyclethis.idx beforequery() returnsthis.idx after
00notes[0] = C41
11notes[1] = D42
…………
55notes[0] = C46

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));

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.