Skip to content

Script Patterns

Script patterns let you define custom pattern types as classes. Any class with a query(cycle) method can be used as a pattern by assigning it to a track.

A script pattern is a class with a query(cycle) method that returns notes, arrays, or patterns:

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 p = Stepper(#[C4, D4, E4, F4, G4]);
track(1, p);

When p is assigned to a track, Resonon detects the query() method and wraps the instance in a ScriptPattern. Each cycle, query() is called with the current cycle number, and the return value becomes the pattern output.

Each cycle, the script pattern:

  1. Recomputes from cycle 0 to determine the current state (like streams)
  2. Calls query(cycle) with the cycle number
  3. Converts the return value to pattern events
  4. Delivers events to the track

Because state is recomputed from the initial values each time, script patterns are seekable and deterministic — querying cycle 100 always produces the same result.

Field mutations persist across cycles via recomputation. When you write this.idx = this.idx + 1 inside query(), the updated value carries forward to the next cycle:

Cyclethis.idx beforequery() returnsthis.idx after
00notes[0] = C41
11notes[1] = D42
22notes[2] = E43
33notes[3] = F44
44notes[4] = G45
55notes[0] = C46

The query() method can return any value that converts to a pattern:

Return typeResult
Note (C4)Single note filling the cycle
Number (60)MIDI note number as a single note
Array (#[C4, E4, G4])Sequence distributed across the cycle
Pattern ([C4 E4 G4])Pattern queried for the cycle
Rest (~)Silence for that cycle

Script pattern classes can define helper methods that query() calls:

class BoundedWalk {
let pos;
let lower;
let upper;
fn new(start, lower, upper) {
this.pos = start;
this.lower = lower;
this.upper = upper;
}
fn step(cycle) {
let delta = choose(cycle, #[-2, -1, 1, 2]);
return this.clamp(this.pos + delta);
}
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.step(cycle);
return this.pos;
}
}
let walk = BoundedWalk(60, 48, 72);
track(1, walk);

The cycle parameter lets you build time-varying patterns without mutable state:

class ChordProgression {
let chords;
fn new(chords) {
this.chords = chords;
}
fn query(cycle) {
let idx = cycle % this.chords.length;
return this.chords[idx];
}
}
let prog = ChordProgression(#[
#[C4, E4, G4],
#[D4, F4, A4],
#[G4, B4, D5],
#[C4, E4, G4],
]);
track(1, prog);

Inside query(), these variables are available automatically:

VariableDescription
cycleThe cycle parameter you defined
cpsCurrent tempo in cycles per second

Plus any variables from the surrounding scope that were visible when the instance was assigned to a track.

Script patterns use a restricted evaluator for thread-safety and determinism. The rules:

  • Supported: let, if/else, match, return, for, loop, arithmetic, array access, user-defined functions, rand(), choose()
  • Not supported: PRINT/PUT, audio/MIDI operations, native functions
Script PatternStream
Defined asClass with query()stream(init, fn(prev, cycle) { ... })
StateMultiple named fieldsSingle value (prev)
State mutationthis.field = valueReturn next state
EncapsulationMethods, fields, helpersClosure only
SeekableYes (O(N) replay)Yes (O(N) replay)
Best forComplex stateful logic with multiple state variablesSingle-value walking patterns
class Arpeggiator {
let chord;
fn new(chord) {
this.chord = chord;
}
fn query(cycle) {
return this.chord[cycle % this.chord.length];
}
}
let arp = Arpeggiator(#[C4, E4, G4, B4]);
track(1, arp);

A pattern that steps through a scale using Euclidean-distributed accents:

class EuclidStepper {
let notes;
let idx;
let accents;
fn new(notes, hits, steps) {
this.notes = notes;
this.idx = 0;
this.accents = euclid(hits, steps, [1]);
}
fn query(cycle) {
let note = this.notes[this.idx % this.notes.length];
this.idx = this.idx + 1;
return note;
}
}
let stepper = EuclidStepper(#[C4, D4, E4, F4, G4, A4, B4], 5, 8);
track(1, stepper);