Values & Variables
Before you can sequence a note, Resonon has to know what a note is. This chapter covers the raw material you build music from: the literal values you can write down, the variables that hold them, and the one rule about copying that trips up newcomers most often. Everything here is plain programming-language groundwork — but it is groundwork you will lean on constantly while live-coding.
Here is a taste. Every line below is a value bound to a name; run it and you’ll see the string printed back:
let bpm = 120; // a Numberlet title = "first sketch"; // a Stringlet root = C4; // a Notelet riff = [C4 E4 G4 C5]; // a Patternlet scale = #[0, 2, 4, 5, 7]; // an Array
print(title + " at " + bpm + " BPM");Now let’s take the pieces apart.
Comments
Section titled “Comments”Resonon supports three styles of comment.
// Single-line comment
/* Multi-line comment */
/// Doc comment — attached to the next function definition, and shown by show().fn double(x) { return x * 2;}Use // for quick notes, /* */ to block out a region, and /// to document a function — the
doc comment is what show() displays when you inspect that function in a session.
Literals
Section titled “Literals”A literal is a value written out directly in your code.
Numbers
Section titled “Numbers”Integer, decimal, scientific, hexadecimal, and binary notation are all valid. Hex and binary literals accept underscore separators for readability.
print(42); // integerprint(3.14); // decimalprint(1e-3); // scientific notation (0.001)print(2.5e2); // scientific notation (250)print(.5); // leading dot (0.5)print(5.); // trailing dot (5.0)print(0xFF); // hexadecimal (255)print(0b1010); // binary (10)print(0xFF_FF); // hex with separator (65535)print(0b1010_0011); // binary with separator (163)Resonon has a single Number type — there is no separate integer type, so 5 and 5.0 are the
same value.
Strings
Section titled “Strings”Strings are delimited by double quotes. The + operator concatenates them. For methods and
format strings, see Strings.
print("Hello, " + "RESONON!"); // concatenation with +Booleans
Section titled “Booleans”print(true);print(false);Truthiness
Section titled “Truthiness”Only false and NUL are falsy — everything else is truthy, including values that are falsy
in many other languages (0, the empty string, the empty array):
// .filter() keeps elements whose callback returns a truthy value.// 0, "" and #[] are all truthy — only false and NUL are falsy.print(#[0, "", false, NUL, #[]].filter(fn(x) { return x; }));// → [0, "", []]Truthiness is what higher-order methods like .filter(), .find(), .any(), and .all() test
when they call your callback.
NUL represents the absence of a value. Write it as the literal NUL. Reading a
variable that was never defined is an error, not NUL — so typos surface instead of
silently propagating.
print(NUL); // NUL
print(NUL == NUL); // trueprint(5 == NUL); // falseprint(NUL == false); // false — NUL is not falseprint(NUL == 0); // false — NUL is not zeroA parameter with a default value is optional — omit the argument and it takes the default. Give
it = NUL to make it default to NUL. Calling with too few arguments (for a parameter that has
no default) is an error. A function with no explicit return yields NUL:
fn greet(name = NUL) { if name != NUL { print("Hello, " + name); }}greet(); // name defaults to NUL — prints nothing
fn no_return() { let x = 1; }print(no_return()); // NULUnlike SQL’s NULL, NUL does not propagate through arithmetic — using it as an operand is a
type error. It can, however, live inside collections perfectly well:
let items = #[1, NUL, 3];let config = #{ "key": NUL };print(items); // [1, NUL, 3]print(config); // {"key": NUL}A note literal is a letter, an optional accidental, and an octave: Name Accidental? Octave.
Accidentals are # (sharp) and b (flat) — there is no s form. Octaves range from -1 to 9.
print(C4); // middle Cprint(A4); // concert A (440 Hz by default)print(Bb3); // B-flat in octave 3print(D#5); // D-sharp in octave 5print(C-1); // lowest MIDI noteprint(G9); // highest MIDI noteNotes map to MIDI values 0–127 (C-1 through G9); a note outside that range is a parse error. They compare against Numbers by MIDI value:
print(C4 == 60); // trueprint(D4 > C4); // trueprint(A4 >= 69); // trueAn underscore _ is a rest (silence). Inside a pattern it occupies time like any other step:
let offbeat = [C4 _ E4 _]; // play, rest, play, restprint(offbeat);Variables
Section titled “Variables”Declare variables with let. Resonon is dynamically typed, so any variable can hold any value,
and can be reassigned to a different type later.
let x = 10;let name = "RESONON";let melody = [C4 D4 E4];
x = "now a string"; // reassignment can change the typeprint(x);Compound assignment
Section titled “Compound assignment”let count = 1;count += 1; // 2count *= 3; // 6print(count);Block scoping
Section titled “Block scoping”A let inside a block is local to that block, and an inner block can shadow an outer variable:
let x = "outer";{ let x = "inner"; // shadows the outer x print(x); // "inner"}print(x); // "outer"Sharing & Cloning
Section titled “Sharing & Cloning”This is the one rule worth burning into memory. Containers — arrays, dictionaries, and class instances — share on assignment. Two variables then refer to the same underlying data, and a change through one is visible through the other:
let a = #[1, 2, 3];let b = a; // b shares the same arrayb.push(99);print(a); // [1, 2, 3, 99] — both see the changeFunction parameters share too — no special syntax needed:
fn append_high(arr) { arr.push(C5);}let melody = #[C4, E4, G4];append_high(melody);print(melody); // [C4, E4, G4, C5] — the caller's array changedTo work on an independent copy, call .clone():
let a = #[1, 2, 3];let b = a.clone(); // b is a separate copyb.push(99);print(a); // [1, 2, 3] — unchangedprint(b); // [1, 2, 3, 99]Primitives — numbers, strings, booleans, notes, and NUL — are always copied by value, so this
sharing only ever applies to containers.
The same rule extends to the object-like handles you’ll meet later — audio tracks, visuals, and
MIDI tracks are shared references too, so assigning one to a new name points both names at the
same underlying object. When you need an independent copy of any of these, reach for .clone().
Type coercion
Section titled “Type coercion”Resonon is strict: most operations require matching types, with no implicit coercion. The handful of cross-type rules are worth knowing:
+also concatenates strings (coercing the other side to a string) and transposes events==/!=work cross-type for Note↔Number and NUL↔anything</>/<=/>=work for Note↔Number comparisons&&/||require Boolean operands- anything else across mismatched types is a type error
print(C4 == 60); // true — Note↔Number by MIDI valueprint("got: " + 1); // "got: 1" — Number coerced to StringThe full operator behaviour — including the type-compatibility table — is in Operators.
Next Steps
Section titled “Next Steps”You now have the values and the variables to hold them. Next, learn how to combine them.
- Operators — arithmetic, comparison, logical, and the musical
<</>>operators - Collections — arrays and dictionaries in depth
- Music Theory — turn notes into scales, keys, and chords