Skip to content

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"
let x = 5;
if x > 0 {
PRINT "positive";
}
if x > 10 {
PRINT "big";
} else {
PRINT "small";
}

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; // 140

else 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 { } 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
}

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; // 3

do 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 … in … iterates over ranges, arrays, patterns, and dictionaries.

// Range
for i in range(5) {
PRINT i; // 0, 1, 2, 3, 4
}
// Range with start
for i in range(2, 5) {
PRINT i; // 2, 3, 4
}
// Over an array
for 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;
}
}

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];
}

.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.

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"

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 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"

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; // 107

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.

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; // 30

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"

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 3
match 3 {
1 => "one"
2 => "two"
};

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.

TypeMatchable?
NumberYes
StringYes
BooleanYes
NoteYes (coerces with numbers)
NULYes
ArrayNo
DictNo

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, sd

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.

  • Collections — the arrays and dicts you loop over
  • Iterators — lazy pipelines and pattern-event iteration
  • Functions — package this logic up for reuse