Skip to content

Script Patterns

When streams and Markov chains aren’t enough — you need several state variables, helper methods, or logic that doesn’t fit a single fn(prev, cycle) — you can write the pattern as a class. Any class with a query(cycle) method becomes a pattern: send an instance to a track and Resonon calls query() once per cycle, turning whatever you return into notes. It’s like scripting your own sequencer device.

Here’s a stepper that walks through a list of notes, one per 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.load_instrument(SamplerMelodic(Kit("keys")));
lead >> master;
lead << Stepper(#[C4, D4, E4, F4, G4]);
PLAY;

Assigning the instance to the track is all it takes — Resonon spots the query() method and drives it. Each cycle, Stepper returns the next note and bumps its index.

Each cycle, the track:

  1. Recomputes the state from cycle 0 — replays every past query() to rebuild the current field values (just like streams and Markov chains)
  2. Calls query(cycle) with the current cycle number
  3. Converts the return value into pattern events for that cycle

Because state is always rebuilt from the initial values, script patterns are seekable and deterministic — querying cycle 100 always produces the same result, no matter how you got there.

Mutating a field inside query() carries forward. When Stepper runs this.idx = this.idx + 1, the next cycle sees the bumped value:

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

query() can return anything that converts to a pattern:

Return valueResult
Number (60)MIDI note number, one note filling the cycle
Note (C4)A single note filling the cycle
Array (#[C4, E4, G4])A chord — all notes sound together
Pattern ([C4 E4 G4])A sub-pattern, distributed across the cycle
[_]A rest — silence for that cycle

A script pattern is a normal class, so query() can call helper methods. This bounded random walk keeps its step list in a field (set up in new()) and clamps the result 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 mutable state — the cycle argument alone is enough for time-varying patterns. This progression just indexes its chord list by cycle:

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.load_instrument(SamplerMelodic(Kit("keys")));
pad >> master;
pad << prog;
PLAY;

Inside query() the current tempo is also available as cps (cycles per second), in case your logic needs to react to it.

Script patterns run in a restricted evaluator for thread-safety and determinism:

  • Supported: let, if/else, match, return, for, loop, arithmetic, field and array access, calls to your own functions and helper methods, and the seeded rand() / choose() from std/random.
  • Not supported: PRINT / PUT, audio or MIDI operations, and native functions. Keep query() to pure computation that returns a value.
Script patternStream
Defined asA class with query(cycle)stream(init, fn(prev, cycle))
StateAny number of named fieldsA single value (prev)
ExtrasHelper methods, captured tablesClosure only
Reach for it whenLogic needs structure or several state variablesWalking one value forward

Patterns make the notes — now make them sound.

  • Samplers — play patterns through the built-in sample engine
  • Effects — shape the sound with effect chains