Control Flow & Match
If / Else
Section titled “If / Else”let x = 5;
if x > 0 { PRINT "positive";}
if x > 10 { PRINT "big";} else { PRINT "small";}Nested if/else chains work for multi-branch logic:
let score = 85;if score >= 90 { PRINT "A";} else { if score >= 80 { PRINT "B"; } else { PRINT "below B"; }}If expressions
Section titled “If expressions”When both branches are present, if can be used as an expression. The last value in each branch is the result.
let mood = "happy";let tempo = if mood == "happy" { 140 } else { 80 };Chained if/else if/else works in expression position too:
let hour = 14;let greeting = if hour < 12 { "morning" } else if hour < 18 { "afternoon" } else { "evening" };Infinite loop with break / continue
Section titled “Infinite loop with break / continue”let count = 0;loop { PRINT count; count += 1; if count >= 3 { break; }}Use continue to skip the rest of the current iteration:
let i = 0;loop { i += 1; if i > 6 { break; } if i % 2 == 0 { continue; } // skip even numbers PRINT i; // 1, 3, 5}Labeled loops
Section titled “Labeled loops”Labels let break and continue target a specific enclosing loop.
loop:rows { loop:cols { if done_with_rows { break:rows; // exits the outer loop } if done_with_col { continue:rows; // jumps to next row iteration } }}Do N times
Section titled “Do N times”do 3 { PRINT "Hello!";}The count expression is evaluated once before the loop begins and truncated to an integer. Any numeric expression works:
let reps = 4;do reps { PRINT "go";}
do 2 + 2 { PRINT "four times";}Use break to exit a do loop early:
do 100 { if some_condition { break; }}For loops
Section titled “For loops”Iterate over ranges, arrays, patterns, or dictionaries.
// Rangefor i in range(5) { PRINT i; // 0, 1, 2, 3, 4}
// Range with startfor i in range(2, 5) { PRINT i; // 2, 3, 4}
// Over an arraylet numbers = #[10, 20, 30];for n in numbers { PRINT n;}
// Over a patternlet melody = [C4 D4 E4];for note in melody { PRINT note;}break and continue work inside for loops as well:
for n in #[1, 3, 5, 7, 8, 9, 10] { if n > 5 && n % 2 == 0 { PRINT n; // 8 break; }}Over dictionaries
Section titled “Over dictionaries”for k in dict iterates over the keys of a dictionary. Use dict[k] to access the corresponding value:
let cc_map = #{"filter": 74, "resonance": 71, "volume": 7};
for param in cc_map { PRINT param + " -> CC " + cc_map[param];}// filter -> CC 74// resonance -> CC 71// volume -> CC 7For simultaneous access to keys and values, use .entries() which yields [key, value] pairs:
for entry in cc_map.entries() { let name = entry[0]; let cc = entry[1]; PRINT name + ": " + cc;}See Dictionaries for more dict operations.
Enumerate
Section titled “Enumerate”.iter().enumerate() yields [index, value] pairs, useful when you need both position and element:
let notes = #[C4, E4, G4, B4];for pair in notes.iter().enumerate() { let i = pair[0]; let note = pair[1]; PRINT "Step " + i + ": " + note;}// Step 0: C4// Step 1: E4// Step 2: G4// Step 3: B4See Iterators for more iterator methods.
Match expressions
Section titled “Match expressions”match selects the first arm whose pattern matches the subject. For full coverage of variable binding, guards, and block bodies, see Match Expressions.
let day = match 3 { 1 => "Monday" 2 => "Tuesday" 3 => "Wednesday" _ => "other"};Guards add conditions to arms:
let temp = 35;let weather = match temp { t if t > 30 => "hot" t if t > 15 => "mild" _ => "cold"};Sequencer control
Section titled “Sequencer control”These commands control the global transport.
| Command | Description |
|---|---|
PLAY | Start or resume playback |
PAUSE | Suspend playback without resetting position |
STOP | Halt playback, reset position, stop recording |
RECORD | Start playback and record on armed tracks |
SEEK expr | Jump to a cycle position |
Starts or resumes MIDI and audio playback from the current position.
PLAY;Suspends playback without resetting the transport position. Calling PLAY afterwards resumes from where it left off.
PLAY;// ... playback runs ...PAUSE;// position is preservedPLAY; // resumes from the same pointHalts playback, resets the transport position to the beginning, and stops any active recording.
STOP;RECORD
Section titled “RECORD”Like PLAY, but also starts recording on any armed tracks. Useful for capturing live MIDI input or audio.
track.input("keyboard").monitor("in");track.arm();RECORD; // playback + recording beginsSee Audio Input for track arming and input setup.
Jumps the transport to a specific cycle position. Takes any numeric expression.
SEEK 0; // jump to the beginning
let drop = 32;SEEK drop; // jump to cycle 32
SEEK drop - 4; // jump to 4 cycles before the dropTransport workflow
Section titled “Transport workflow”A practical example combining transport commands:
PLAY;// ... listen to the intro ...PAUSE;SEEK 16; // skip ahead to the chorusPLAY; // resume from cycle 16// ... done ...STOP; // halt and reset to beginningmatch selects the first arm whose pattern matches the subject and evaluates its body.
Basic literal matching
Section titled “Basic literal matching”Match against numbers, strings, and booleans.
let label = match 42 { 1 => "one" 42 => "forty-two" _ => "other"};PRINT label; // "forty-two"
let lang = match "resonon" { "python" => "snake" "resonon" => "herb" _ => "unknown"};PRINT lang; // "herb"
let answer = match true { false => "nope" true => "yep"};PRINT answer; // "yep"Note matching
Section titled “Note matching”Note literals can be used as match patterns.
let root = C4;let name = match root { C4 => "C" D4 => "D" E4 => "E" _ => "other"};PRINT name; // "C"Note–number coercion
Section titled “Note–number coercion”Notes match against their MIDI number, and numbers match against notes. C4 is MIDI note 60, so C4 and 60 are interchangeable in match patterns.
// Note subject, number patternlet name = match C4 { 60 => "middle C" _ => "other"};PRINT name; // "middle C"
// Number subject, note patternlet name2 = match 60 { C4 => "middle C" D4 => "D" _ => "other"};PRINT name2; // "middle C"This is useful for dispatching on raw MIDI note numbers:
fn drum_name(note_num) { match note_num { C2 => "kick" Cs2 => "side stick" D2 => "snare" Fs2 => "hi-hat closed" As2 => "hi-hat open" _ => "other" }}PRINT drum_name(36); // "kick" (36 = C2)PRINT drum_name(38); // "snare" (38 = D2)NUL matching
Section titled “NUL matching”let val = NUL;let status = match val { NUL => "empty" _ => "has value"};PRINT status; // "empty"Non-NUL values do not match the NUL arm:
let status2 = match 42 { NUL => "empty" _ => "has value"};PRINT status2; // "has value"Wildcard catch-all
Section titled “Wildcard catch-all”_ matches anything. Place it last to handle all remaining cases.
let result = match 999 { 1 => "one" 2 => "two" _ => "unknown"};PRINT result; // "unknown"Variable binding
Section titled “Variable binding”A bare identifier captures the matched value into a variable available in the arm body.
let doubled = match 21 { n => n * 2};PRINT doubled; // 42Bindings can follow literal arms — the first match wins:
let desc = match 7 { 1 => "one" 2 => "two" x => x + 100};PRINT desc; // 107Guards
Section titled “Guards”Add if <condition> after a pattern to further constrain when an arm matches. The bound variable is available in the guard expression.
let size = match 15 { n if n > 100 => "huge" n if n > 10 => "big" n if n > 5 => "medium" _ => "small"};PRINT size; // "big"A guard that evaluates to false causes the arm to be skipped, and matching continues with the next arm:
let size2 = match 3 { n if n > 10 => "big" _ => "small"};PRINT size2; // "small"Wildcard with guard
Section titled “Wildcard with guard”The wildcard _ doesn’t bind a variable, but guards on wildcard arms can reference variables from the surrounding scope.
let threshold = 50;let level = match 42 { _ if threshold > 100 => "high threshold" _ if threshold > 25 => "medium threshold" _ => "low threshold"};PRINT level; // "medium threshold"Musical example: note range routing
Section titled “Musical example: note range routing”fn register(note) { match note { n if n < C3 => "bass" n if n < C5 => "mid" n if n < C7 => "treble" _ => "ultra-high" }}
PRINT register(A1); // "bass"PRINT register(E4); // "mid"PRINT register(C6); // "treble"Block bodies
Section titled “Block bodies”Use { ... } for multi-statement arm bodies. The last expression in the block is the arm’s value.
let computed = match "calc" { "calc" => { let a = 10; let b = 20; a + b } _ => 0};PRINT computed; // 30Match as expression
Section titled “Match as expression”match can appear anywhere an expression is expected, including let bindings.
let grade = match 92 { n if n >= 90 => "A" n if n >= 80 => "B" n if n >= 70 => "C" _ => "F"};
// Match with a computed subjectlet parity = match 2 + 3 { n if n % 2 == 0 => "even" _ => "odd"};PRINT parity; // "odd"Match as statement
Section titled “Match as statement”match can also stand alone as a statement.
let out = "";match 42 { 42 => { out = "matched 42"; } _ => { out = "no match"; }}PRINT out; // "matched 42"Exhaustiveness
Section titled “Exhaustiveness”Resonon does not check exhaustiveness at compile time. If no arm matches at runtime, a runtime error is raised.
// Runtime error: "No match arm matched value: 3. Add a wildcard: _ => ..."match 3 { 1 => "one" 2 => "two"};Nested match
Section titled “Nested match”Match expressions can be nested inside block bodies for multi-level dispatch.
fn articulation(instrument, velocity) { match instrument { "strings" => { match velocity { v if v > 100 => "marcato" v if v > 60 => "detaché" _ => "pizzicato" } } "brass" => { match velocity { v if v > 100 => "sforzando" v if v > 60 => "sustained" _ => "muted" } } _ => "default" }}
PRINT articulation("strings", 120); // "marcato"PRINT articulation("strings", 40); // "pizzicato"PRINT articulation("brass", 80); // "sustained"Matchable types
Section titled “Matchable types”Practical examples
Section titled “Practical examples”MIDI velocity description
Section titled “MIDI velocity description”fn describe_velocity(vel) { match vel { v if v > 100 => "fortissimo" v if v > 80 => "forte" v if v > 60 => "mezzo-forte" v if v > 40 => "piano" _ => "pianissimo" }}
for v in #[30, 64, 90, 120] { PRINT f"{v} -> {describe_velocity(v)}";}// 30 -> pianissimo// 64 -> mezzo-forte// 90 -> forte// 120 -> fortissimoScale routing
Section titled “Scale routing”Match a mode name to select scale intervals.
fn scale_intervals(mode) { match mode { "ionian" => #[0, 2, 4, 5, 7, 9, 11] "dorian" => #[0, 2, 3, 5, 7, 9, 10] "phrygian" => #[0, 1, 3, 5, 7, 8, 10] "mixolydian" => #[0, 2, 4, 5, 7, 9, 10] "aeolian" => #[0, 2, 3, 5, 7, 8, 10] _ => #[0, 2, 4, 5, 7, 9, 11] }}
let intervals = scale_intervals("dorian");PRINT intervals; // #[0, 2, 3, 5, 7, 9, 10]Drum dispatch
Section titled “Drum dispatch”Map General MIDI drum note numbers to names using note–number coercion.
fn drum_label(n) { match n { C2 => "kick" D2 => "snare" Fs2 => "hi-hat closed" As2 => "hi-hat open" C3 => "high tom" _ => f"drum {n}" }}
for note in #[36, 38, 42, 46, 48, 99] { PRINT f"{note}: {drum_label(note)}";}// 36: kick// 38: snare// 42: hi-hat closed// 46: hi-hat open// 48: high tom// 99: drum 99