Script Patterns
This content is for v0.7. Switch to the latest version for up-to-date documentation.
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.
Defining a Script Pattern
Section titled “Defining a Script Pattern”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.
How It Works
Section titled “How It Works”Each cycle, the script pattern:
- Recomputes from cycle 0 to determine the current state (like streams)
- Calls
query(cycle)with the cycle number - Converts the return value to pattern events
- 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.
State Persistence
Section titled “State Persistence”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:
| Cycle | this.idx before | query() returns | this.idx after |
|---|---|---|---|
| 0 | 0 | notes[0] = C4 | 1 |
| 1 | 1 | notes[1] = D4 | 2 |
| 2 | 2 | notes[2] = E4 | 3 |
| 3 | 3 | notes[3] = F4 | 4 |
| 4 | 4 | notes[4] = G4 | 5 |
| 5 | 5 | notes[0] = C4 | 6 |
Return Types
Section titled “Return Types”The query() method can return any value that converts to a pattern:
| Return type | Result |
|---|---|
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 |
Helper Methods
Section titled “Helper Methods”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);Cycle-Dependent Logic
Section titled “Cycle-Dependent Logic”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);Built-in Variables
Section titled “Built-in Variables”Inside query(), these variables are available automatically:
| Variable | Description |
|---|---|
cycle | The cycle parameter you defined |
cps | Current tempo in cycles per second |
Plus any variables from the surrounding scope that were visible when the instance was assigned to a track.
Callback Restrictions
Section titled “Callback Restrictions”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
Comparison
Section titled “Comparison”| Script Pattern | Stream | |
|---|---|---|
| Defined as | Class with query() | stream(init, fn(prev, cycle) { ... }) |
| State | Multiple named fields | Single value (prev) |
| State mutation | this.field = value | Return next state |
| Encapsulation | Methods, fields, helpers | Closure only |
| Seekable | Yes (O(N) replay) | Yes (O(N) replay) |
| Best for | Complex stateful logic with multiple state variables | Single-value walking patterns |
Musical Examples
Section titled “Musical Examples”Ascending Arpeggiator
Section titled “Ascending Arpeggiator”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);Euclidean Stepper
Section titled “Euclidean Stepper”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);See Also
Section titled “See Also”- Classes — Class syntax and semantics
- Streams — Single-value stateful patterns
- Pattern Methods — Transform pattern output