Skip to content

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 Number
let title = "first sketch"; // a String
let root = C4; // a Note
let riff = [C4 E4 G4 C5]; // a Pattern
let scale = #[0, 2, 4, 5, 7]; // an Array
PRINT title + " at " + bpm + " BPM";

Now let’s take the pieces apart.

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.

A literal is a value written out directly in your code.

Integer, decimal, scientific, hexadecimal, and binary notation are all valid. Hex and binary literals accept underscore separators for readability.

PRINT 42; // integer
PRINT 3.14; // decimal
PRINT 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 are delimited by double quotes. The + operator concatenates them. For methods and format strings, see Strings.

PRINT "Hello, " + "RESONON!"; // concatenation with +
PRINT true;
PRINT false;

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. Undefined variables evaluate to NUL.

PRINT NUL; // NUL
PRINT NUL == NUL; // true
PRINT 5 == NUL; // false
PRINT NUL == false; // false — NUL is not false
PRINT NUL == 0; // false — NUL is not zero

Missing function arguments default to NUL, and a function with no explicit return yields NUL:

fn greet(name) {
if name != NUL {
PRINT "Hello, " + name;
}
}
greet(); // name is NUL — prints nothing
fn no_return() { let x = 1; }
PRINT no_return(); // NUL

Unlike 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 C
PRINT A4; // concert A (440 Hz by default)
PRINT Bb3; // B-flat in octave 3
PRINT D#5; // D-sharp in octave 5
PRINT C-1; // lowest MIDI note
PRINT G9; // highest MIDI note

Notes 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; // true
PRINT D4 > C4; // true
PRINT A4 >= 69; // true

An underscore _ is a rest (silence). Inside a pattern it occupies time like any other step:

let offbeat = [C4 _ E4 _]; // play, rest, play, rest
PRINT offbeat;

Outside a pattern, a bare _ evaluates to NUL:

let r = _;
PRINT r; // NUL
PRINT r == NUL; // true

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 type
PRINT x;
let count = 1;
count += 1; // 2
count *= 3; // 6
PRINT count;

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"

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 array
b.push(99);
PRINT a; // [1, 2, 3, 99] — both see the change

Function 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 changed

To work on an independent copy, call .clone():

let a = #[1, 2, 3];
let b = a.clone(); // b is a separate copy
b.push(99);
PRINT a; // [1, 2, 3] — unchanged
PRINT 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.

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 value
PRINT "got: " + 1; // "got: 1" — Number coerced to String

The full operator behaviour — including the type-compatibility table — is in Operators.

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