Skip to content

Patterns & Tracks

Patterns are the core music type in Resonon. They describe sequences of notes, rests, and samples that play over time. This page covers how patterns divide time, how to nest and layer them, and how to send them to MIDI tracks.

A pattern is enclosed in square brackets [...] and contains musical elements. Elements divide the cycle evenly:

[C4] // One note fills the whole cycle
[C4 D4] // Each note gets 1/2 of the cycle
[C4 D4 E4] // Each note gets 1/3 of the cycle
[C4 D4 E4 F4] // Each note gets 1/4 of the cycle

The more elements in a pattern, the faster they play:

PatternElementsDuration per element
[C4]1Full cycle
[C4 D4]21/2 cycle each
[C4 D4 E4]31/3 cycle each
[C4 D4 E4 F4]41/4 cycle each

With setbpm(120) and 4 beats per cycle, each cycle lasts 2 seconds. A 4-element pattern gives each element one beat (0.5 seconds).

Wrap elements in [...] to subdivide their time slot:

[[C4 D4] E4] // First half subdivided into two notes, second half is one note

This creates a triplet-like feel — C4 and D4 split the first half, E4 takes the second half. Nesting can go arbitrarily deep:

[[[C4 D4] [E4 F4 G4]] A4]

Here the first half contains five notes (2 + 3), while the second half is a single note.

Use commas to play elements simultaneously:

[C4, E4, G4] // C major chord — all three notes at once

Commas create a stack — all layers play at the same time. This is useful for chords and for layering rhythmic parts:

[bd _ sd _, ch ch ch ch] // Kick-snare rhythm with steady hi-hats

Use angle brackets <...> to alternate one element per cycle:

<C4 D4 E4> // Cycle 1: C4, Cycle 2: D4, Cycle 3: E4, then repeats

This is useful for creating variation across cycles without writing long patterns.

So far you’ve used AudioTrack to play samples. MIDI tracks send note data to external synthesizers, DAWs, or hardware:

let melody = MidiTrack(1); // Channel 1
melody << [C4 E4 G4 E4];
PLAY;

MidiTrack(channel) creates a track on a MIDI channel (1–16). The << operator works the same way — send a pattern and it loops.

MidiTrack(1) // Channel only (velocity defaults to 100)
MidiTrack(2, 80) // Channel 2, softer default velocity
MidiTrack("lead", 1, 110) // Named track with custom velocity

Build up arrangements by creating several tracks on different channels:

let melody = MidiTrack(1);
let bass = MidiTrack(2);
melody << [C4 E4 G4 E4];
bass << [C2 _ C2 G2];
PLAY;

Each track plays its pattern independently. Changes to one don’t affect the others.

Audio tracks and MIDI tracks work side by side:

use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");
drums.load_instrument(Sampler(Kit("cr78")));
drums << [bd _ sd _, ch ch ch ch];
let melody = MidiTrack(1);
melody << [C4 E4 G4 E4];
PLAY;

Send a new pattern to replace the current one. The change takes effect at the next cycle boundary:

melody << [C4 E4 G4 E4]; // Start with this
melody << [D4 F4 A4 F4]; // Evaluate again — switches on next cycle

This is the core of live coding: keep playback running and re-evaluate individual lines to evolve your piece.

To clear a track, send an empty pattern:

bass << []; // Silence the bass

Control how loud each note plays with the ^ operator:

melody << [C4^120 D4^80 E4^60 F4^40];

Values range from 0 to 127. Notes without ^ use the track’s default velocity (100, or whatever you set in the constructor).

Mix accented and default notes:

let soft = MidiTrack(2, 50); // Default velocity 50
soft << [C4 D4^110 E4 F4^110]; // Accents on D4 and F4

Patterns are values — store them, pass them to functions, compose them:

let kick = [bd _ bd _];
let snare = [_ _ sd _];
let hats = [ch ch ch ch];
drums << [kick, snare, hats]; // Layer all three

You can now write patterns, layer tracks, and code live. Next, learn how to organize your code into a project: