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 noteslet cc = #{"cutoff": 74, "reso": 71}; // a dictionary
PRINT notes.length(); // 4PRINT cc["cutoff"]; // 74Arrays
Section titled “Arrays”Creating arrays
Section titled “Creating arrays”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;Accessing elements
Section titled “Accessing elements”Elements are zero-indexed. Use bracket notation, or .get() for the same thing:
let nums = #[1, 2, 3, 4, 5];PRINT nums[0]; // 1PRINT nums[2]; // 3PRINT nums.get(4); // 5Built-in functions
Section titled “Built-in functions”| Function | Description | Example |
|---|---|---|
length(arr) | Number of elements | length(#[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 arrays | concat(#[1,2], #[3,4]) → [1,2,3,4] |
range(n) | Array 0 … n-1 | range(5) → [0,1,2,3,4] |
range(start, end) | Array start … end-1 | range(2, 5) → [2,3,4] |
Methods
Section titled “Methods”.push() and .pop() mutate the array in place; everything else returns a new value and leaves
the original untouched.
| Method | Mutates? | Description |
|---|---|---|
.push(item) | yes | Append to the end |
.pop() | yes | Remove and return the last |
.get(index) | no | Element at index |
.length() | no | Number of elements |
.reverse() | no | Reversed copy |
.slice(start, end) | no | Sub-array [start, end) |
.concat(other) | no | Concatenated copy |
.contains(value) | no | true 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); // trueHigher-order methods
Section titled “Higher-order methods”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)| Method | Returns | Description |
|---|---|---|
.map(fn(x)) | Array | Apply fn to every element |
.filter(fn(x)) | Array | Keep elements whose fn is truthy |
.reduce(fn(acc, x), init) | Value | Fold into a single accumulated value |
Nested arrays
Section titled “Nested arrays”let matrix = #[ #[1, 2, 3], #[4, 5, 6], #[7, 8, 9],];PRINT matrix[0][1]; // 2PRINT matrix[2][2]; // 9A musical example
Section titled “A musical example”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 setlet melody = scale.map(fn(n) { return n; }); // (transform here if you like)
lead << Sequence(melody); // play the array as a patternPLAY;Dictionaries
Section titled “Dictionaries”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"];Key types
Section titled “Key types”Keys must be strings or numbers, and the two are distinct — 200 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"Accessing values
Section titled “Accessing values”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");// NULMutating
Section titled “Mutating”let config = #{"volume": 80, "tempo": 120};config["volume"] = 100; // index assignmentconfig.set("swing", 0.6); // .set(key, value)config["volume"] += 5; // compound assignmentlet old = config.remove("tempo"); // .remove returns the removed valuePRINT old; // 120PRINT config;Inspection methods
Section titled “Inspection methods”| Method | Description |
|---|---|
.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(); // 3PRINT d.contains_key("a"); // truePRINT 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 5PRINT defaults; // unchangedMerges chain, later values winning:
let result = #{"a": 1, "b": 2}.merge(#{"b": 20}).merge(#{"c": 30});PRINT result; // a:1, b:20, c:30Higher-order methods
Section titled “Higher-order methods”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 transformedlet top = scores.filter(fn(k, v) { return v > 90; }); // keep entries where fn is truthylet total = scores.reduce(fn(acc, k, v) { return acc + v; }, 0); // fold to one valuePRINT top; // {"bob": 92}PRINT total; // 255Iteration
Section titled “Iteration”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]}";}Nesting
Section titled “Nesting”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"]; // 36PRINT kit["snare"]["velocity"][2]; // 110Sharing & cloning
Section titled “Sharing & cloning”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; // shareslet copy = original.clone(); // independentoriginal.push(4);PRINT alias; // [1, 2, 3, 4] — saw the changePRINT copy; // [1, 2, 3] — did notSee Sharing & Cloning for the full rule.
Lazy iteration
Section titled “Lazy iteration”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.
Next Steps
Section titled “Next Steps”- Iterators — lazy pipelines over collections and patterns
- Functions — the callbacks you pass to
mapandfilter - Music Theory — turn note arrays into scales, keys, and chords