Skip to content

Control Flow & Match

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

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

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

Iterate over ranges, arrays, patterns, or 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
let numbers = #[10, 20, 30];
for n in numbers {
PRINT n;
}
// Over a pattern
let 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;
}
}

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 7

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

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

See Iterators for more iterator methods.

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

These commands control the global transport.

CommandDescription
PLAYStart or resume playback
PAUSESuspend playback without resetting position
STOPHalt playback, reset position, stop recording
RECORDStart playback and record on armed tracks
SEEK exprJump 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 preserved
PLAY; // resumes from the same point

Halts playback, resets the transport position to the beginning, and stops any active recording.

STOP;

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 begins

See 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 drop

A practical example combining transport commands:

PLAY;
// ... listen to the intro ...
PAUSE;
SEEK 16; // skip ahead to the chorus
PLAY; // resume from cycle 16
// ... done ...
STOP; // halt and reset to beginning

match selects the first arm whose pattern matches the subject and evaluates its body.

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 literals can be used as match patterns.

let root = C4;
let name = match root {
C4 => "C"
D4 => "D"
E4 => "E"
_ => "other"
};
PRINT name; // "C"

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 pattern
let name = match C4 {
60 => "middle C"
_ => "other"
};
PRINT name; // "middle C"
// Number subject, note pattern
let 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)
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"

_ matches anything. Place it last to handle all remaining cases.

let result = match 999 {
1 => "one"
2 => "two"
_ => "unknown"
};
PRINT result; // "unknown"

A bare identifier captures the matched value into a variable available in the arm body.

let doubled = match 21 {
n => n * 2
};
PRINT doubled; // 42

Bindings can follow literal arms — the first match wins:

let desc = match 7 {
1 => "one"
2 => "two"
x => x + 100
};
PRINT desc; // 107

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"

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

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

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 subject
let parity = match 2 + 3 {
n if n % 2 == 0 => "even"
_ => "odd"
};
PRINT parity; // "odd"

match can also stand alone as a statement.

let out = "";
match 42 {
42 => { out = "matched 42"; }
_ => { out = "no match"; }
}
PRINT out; // "matched 42"

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

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

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]

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