Control Flow & Match
Control flow decides what runs when. Resonon gives you the usual conditionals and loops, plus a
match expression for clean multi-way dispatch — handy for mapping MIDI note numbers to drum
names, velocities to dynamics, or mode names to scale intervals. A useful twist: if and match
are expressions, so they produce a value you can bind directly.
let velocity = 92;let dynamic = match velocity { v if v > 100 => "fortissimo" v if v > 60 => "forte" _ => "piano"};PRINT dynamic; // "forte"If / Else
Section titled “If / Else”let x = 5;
if x > 0 { PRINT "positive";}
if x > 10 { PRINT "big";} else { PRINT "small";}If as an expression
Section titled “If as an expression”When both branches are present, if yields a value — the last expression in the chosen branch.
This is the idiomatic way to choose a value:
let mood = "happy";let tempo = if mood == "happy" { 140 } else { 80 };PRINT tempo; // 140else if chains work in expression position too:
let hour = 14;let greeting = if hour < 12 { "morning" } else if hour < 18 { "afternoon" } else { "evening" };PRINT greeting; // "afternoon"loop, break, continue
Section titled “loop, break, continue”loop { } repeats forever until you break. Use continue to skip to the next 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”A label lets break and continue target a specific enclosing loop instead of the innermost one:
let r = 0;loop:rows { r += 1; let c = 0; loop:cols { c += 1; if c >= 2 { continue:rows; } // jump to the next row if r >= 3 { break:rows; } // exit the outer loop entirely }}PRINT "rows: " + r; // 3do N { } runs its body a fixed number of times. The count is evaluated once, up front, and
truncated to an integer — any numeric expression works:
do 3 { PRINT "Hello!";}
let reps = 2 + 2;do reps { PRINT "go"; // runs 4 times}break exits a do loop early. The count must be a Number — passing anything else is a type
error.
For loops
Section titled “For loops”for … in … iterates over ranges, arrays, patterns, and 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 arrayfor n in #[10, 20, 30] { PRINT n;}Iterating a pattern yields its events (not bare notes), one per step:
for event in [C4 D4 E4] { PRINT event.note(); // 60, 62, 64}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; index back in for the value:
let cc_map = #{"filter": 74, "resonance": 71, "volume": 7};
for param in cc_map { PRINT param + " -> CC " + cc_map[param];}For keys and values together, use .entries(), which yields [key, value] pairs:
let cc_map = #{"filter": 74, "resonance": 71};for entry in cc_map.entries() { PRINT entry[0] + ": " + entry[1];}Enumerate
Section titled “Enumerate”.iter().enumerate() yields [index, value] pairs when you need the position too:
let notes = #[C4, E4, G4, B4];for pair in notes.iter().enumerate() { PRINT "step " + pair[0] + ": " + pair[1];}See Collections and Iterators for the full story on iterating data.
match tests its subject against each arm top to bottom and evaluates the first that matches.
It works as both an expression and a statement.
Literals
Section titled “Literals”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"Note ↔ number coercion
Section titled “Note ↔ number coercion”Notes match against their MIDI number and vice versa — C4 is MIDI 60, so the two are
interchangeable as patterns. This is perfect for dispatching on raw MIDI note numbers:
fn drum_name(n) { match n { C2 => "kick" C#2 => "side stick" D2 => "snare" F#2 => "hi-hat closed" A#2 => "hi-hat open" _ => "other" }}PRINT drum_name(36); // "kick" (36 = C2)PRINT drum_name(38); // "snare" (38 = D2)PRINT drum_name(42); // "hi-hat closed" (42 = F#2)NUL and the wildcard
Section titled “NUL and the wildcard”NUL matches only NUL. The wildcard _ matches anything — place it last as a catch-all:
let status = match NUL { NUL => "empty" _ => "has value"};PRINT status; // "empty"Variable binding
Section titled “Variable binding”A bare identifier captures the matched value into the arm body. Because the first match wins, a literal arm above a binding still takes priority:
let desc = match 7 { 1 => "one" 2 => "two" x => x + 100};PRINT desc; // 107Guards
Section titled “Guards”Add if <condition> after a pattern to constrain it further. The bound variable is in scope in
the guard. A guard that is false skips the arm and matching continues:
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 must evaluate to a Boolean; anything else is a runtime type error.
Block bodies
Section titled “Block bodies”Use { … } for multi-statement arms; the last expression is the arm’s value:
let computed = match "calc" { "calc" => { let a = 10; let b = 20; a + b } _ => 0};PRINT computed; // 30Expression or statement
Section titled “Expression or statement”match can produce a value (in a let, an argument, anywhere an expression fits) or stand alone
as a statement that performs side effects:
let out = "";match 42 { 42 => { out = "matched"; } _ => { out = "no match"; }}PRINT out; // "matched"Exhaustiveness
Section titled “Exhaustiveness”Resonon does not check exhaustiveness at compile time. If no arm matches at runtime, it raises
an error — so always include a _ catch-all when the subject could be anything:
// Runtime error: no arm matched 3match 3 { 1 => "one" 2 => "two"};Matchable types
Section titled “Matchable types”Only numbers, strings, booleans, notes, and NUL work as match patterns. Arrays, dictionaries, patterns, functions, and iterators never match an arm — they fall through to the wildcard, or error if none exists.
| Type | Matchable? |
|---|---|
| Number | Yes |
| String | Yes |
| Boolean | Yes |
| Note | Yes (coerces with numbers) |
| NUL | Yes |
| Array | No |
| Dict | No |
A musical example: drum dispatch
Section titled “A musical example: drum dispatch”Map General MIDI drum numbers to kit sample names while iterating a pattern’s events:
fn drum_label(n) { match n { C2 => "bd" D2 => "sd" F#2 => "hh" _ => "?" }}
for event in [36 38 42 38].iter().take(4) { PRINT drum_label(event.note());}// bd, sd, hh, sdTransport commands
Section titled “Transport commands”A handful of statement keywords — PLAY, PAUSE, STOP, RECORD, SEEK — drive the global
transport. They aren’t really control flow, but they often sit alongside it in a live-coding
script. See Transport Controls for the
full set, and the Cycle Model for what cycle positions mean.
Next Steps
Section titled “Next Steps”- Collections — the arrays and dicts you loop over
- Iterators — lazy pipelines and pattern-event iteration
- Functions — package this logic up for reuse