Skip to content

Collections

Resonon has two built-in collection types, and you’ll reach for both constantly: arrays for ordered sequences (a list of notes, a set of velocities) and dictionaries for key-value maps (a CC-number lookup, a track config). Both share by default and both come with a generous set of methods, including the map / filter / reduce trio.

let notes = #[C4, E4, G4, B4]; // an array of notes
let cc = #{"cutoff": 74, "reso": 71}; // a dictionary
PRINT notes.length(); // 4
PRINT cc["cutoff"]; // 74

Use the #[…] literal or the Array() constructor. Elements can be of mixed type.

let empty = #[];
let nums = #[1, 2, 3, 4, 5];
let mixed = #[1, "hello", true];
let also_nums = Array(1, 2, 3); // same as #[1, 2, 3]
PRINT also_nums;

Elements are zero-indexed. Use bracket notation, or .get() for the same thing:

let nums = #[1, 2, 3, 4, 5];
PRINT nums[0]; // 1
PRINT nums[2]; // 3
PRINT nums.get(4); // 5
FunctionDescriptionExample
length(arr)Number of elementslength(#[1,2,3])3
slice(arr, start, end)Sub-array [start, end)slice(#[1,2,3,4], 1, 3)[2,3]
concat(a, b)Join two arraysconcat(#[1,2], #[3,4])[1,2,3,4]
range(n)Array 0 … n-1range(5)[0,1,2,3,4]
range(start, end)Array start … end-1range(2, 5)[2,3,4]

.push() and .pop() mutate the array in place; everything else returns a new value and leaves the original untouched.

MethodMutates?Description
.push(item)yesAppend to the end
.pop()yesRemove and return the last
.get(index)noElement at index
.length()noNumber of elements
.reverse()noReversed copy
.slice(start, end)noSub-array [start, end)
.concat(other)noConcatenated copy
.contains(value)notrue if present
let chars = #["a", "b", "c"];
chars.push("d"); // chars is now ["a", "b", "c", "d"]
let last = chars.pop(); // "d"; chars back to ["a", "b", "c"]
PRINT chars;
PRINT last;
let nums = #[1, 2, 3, 4, 5];
PRINT nums.reverse(); // [5, 4, 3, 2, 1]
PRINT nums.slice(1, 4); // [2, 3, 4]
PRINT nums.contains(3); // true

map, filter, and reduce each take a callback and return a new value. They chain naturally:

let result = #[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.filter(fn(x) { return x % 2 == 0; }) // keep evens
.map(fn(x) { return x * x; }) // square them
.reduce(fn(acc, x) { return acc + x; }, 0);
PRINT result; // 220 (sum of squares of even numbers)
MethodReturnsDescription
.map(fn(x))ArrayApply fn to every element
.filter(fn(x))ArrayKeep elements whose fn is truthy
.reduce(fn(acc, x), init)ValueFold into a single accumulated value
let matrix = #[
#[1, 2, 3],
#[4, 5, 6],
#[7, 8, 9],
];
PRINT matrix[0][1]; // 2
PRINT matrix[2][2]; // 9

An array of notes is the natural raw material for a melody — build the data, then send it:

let lead = MidiTrack(1);
let scale = #[C4, D4, E4, G4, A4]; // a pentatonic set
let melody = scale.map(fn(n) { return n; }); // (transform here if you like)
lead << Sequence(melody); // play the array as a pattern
PLAY;

Dictionaries map string or number keys to any value, written with the #{…} literal. The # prefix distinguishes a dict from a code block; keys and values are separated by :.

let person = #{"name": "Alice", "age": 30, "active": true};
let config = #{
"volume": 80,
"tempo": 120,
"swing": 0.6, // trailing commas are fine
};
PRINT person["name"];

Keys must be strings or numbers, and the two are distinct200 and "200" are different keys. Number keys are handy for MIDI maps:

let drums = #{36: "kick", 38: "snare", 42: "closed hat"};
PRINT drums[36]; // "kick"
let both = #{200: "OK", "200": "two hundred"};
PRINT both[200]; // "OK"
PRINT both["200"]; // "two hundred"

Bracket notation raises an error on a missing key; .get() returns NUL instead:

let person = #{"name": "Alice"};
PRINT person["name"]; // "Alice"
PRINT person.get("name"); // "Alice"
PRINT person.get("email");// NUL
let config = #{"volume": 80, "tempo": 120};
config["volume"] = 100; // index assignment
config.set("swing", 0.6); // .set(key, value)
config["volume"] += 5; // compound assignment
let old = config.remove("tempo"); // .remove returns the removed value
PRINT old; // 120
PRINT config;
MethodDescription
.keys()Array of all keys
.values()Array of all values
.entries()Array of [key, value] pairs
.length()Number of entries
.contains_key(key)true if the key exists
let d = #{"a": 1, "b": 2, "c": 3};
PRINT d.length(); // 3
PRINT d.contains_key("a"); // true
PRINT d.contains_key("z"); // false

.merge(other) returns a new dictionary combining both; keys in other win. The originals are untouched, which makes merge ideal for layering a defaults dict with overrides:

let defaults = #{"channel": 1, "velocity": 100, "octave": 4};
let lead = defaults.merge(#{"channel": 3, "octave": 5});
PRINT lead; // channel 3, velocity 100, octave 5
PRINT defaults; // unchanged

Merges chain, later values winning:

let result = #{"a": 1, "b": 2}.merge(#{"b": 20}).merge(#{"c": 30});
PRINT result; // a:1, b:20, c:30

The dictionary callbacks receive both key and value as fn(k, v):

let scores = #{"alice": 85, "bob": 92, "carol": 78};
let doubled = scores.map(fn(k, v) { return v * 2; }); // new dict, values transformed
let top = scores.filter(fn(k, v) { return v > 90; }); // keep entries where fn is truthy
let total = scores.reduce(fn(acc, k, v) { return acc + v; }, 0); // fold to one value
PRINT top; // {"bob": 92}
PRINT total; // 255

for k in dict yields keys; index back in for the value. Use .entries() for pairs:

let colors = #{"r": 255, "g": 128, "b": 0};
for k in colors {
PRINT f"{k} => {colors[k]}";
}

Dictionaries and arrays nest freely, which is how you model richer configuration:

let kit = #{
"kick": #{"note": 36, "velocity": #[100, 110, 120]},
"snare": #{"note": 38, "velocity": #[80, 100, 110]},
};
PRINT kit["kick"]["note"]; // 36
PRINT kit["snare"]["velocity"][2]; // 110

Like all containers, arrays and dictionaries share on assignment — both names point at the same data. Use .clone() for an independent copy:

let original = #[1, 2, 3];
let alias = original; // shares
let copy = original.clone(); // independent
original.push(4);
PRINT alias; // [1, 2, 3, 4] — saw the change
PRINT copy; // [1, 2, 3] — did not

See Sharing & Cloning for the full rule.

Calling .iter() on an array or dictionary gives a lazy iterator — a pull-based pipeline you can take, skip, map, and filter without building intermediate arrays. That’s a topic of its own:

let nums = #[1, 2, 3, 4, 5];
PRINT nums.iter().filter(fn(x) { return x % 2 == 1; }).collect(); // [1, 3, 5]

Iterators covers the full method set, pattern-event iteration, and the lazy-vs-eager distinction.

  • Iterators — lazy pipelines over collections and patterns
  • Functions — the callbacks you pass to map and filter
  • Music Theory — turn note arrays into scales, keys, and chords