Skip to content

Arrangements

So far every pattern you’ve sent to a track loops forever. That’s perfect for live coding, but a finished piece usually has a shape: an intro, a verse, a chorus, a breakdown. A timeline gives you that shape. Think of it like the arrangement view in a DAW — a fixed-length ruler where you drop patterns at specific positions, and silence wherever you don’t.

Here’s a two-section drum arrangement from start to finish:

use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");
drums.load_instrument(Sampler(Kit("CR-78")));
let song = timeline(16);
song.at(0, 8) << [bd _ sd _]; // verse: cycles 0–7
song.at(8, 8) << [bd*4]; // chorus: cycles 8–15
drums << song;
PLAY;

You build a 16-cycle timeline, place a sparse beat in the first half and a four-on-the-floor in the second, then send the whole thing to a track with << just like any other pattern. Let’s take it apart.

timeline(length) makes an empty timeline that’s length cycles long. The length is the total runtime of the arrangement, in cycles.

let t = timeline(16); // 16 cycles long
let short = timeline(4); // 4 cycles long

The length must be greater than zero.

A timeline is empty until you put something in it. Carve out a slot with .at(start, duration), then assign a pattern to that slot with <<:

let t = timeline(8);
t.at(0, 4) << [bd _ sd _]; // starts at cycle 0, lasts 4 cycles
t.at(4, 4) << [bd bd sd bd]; // starts at cycle 4, lasts 4 cycles

start is the cycle the slot begins on (zero or greater); duration is how many cycles it lasts (greater than zero). Anything that’s a pattern can go in a slot — mini-notation, a euclidean rhythm, a note sequence, or any combinator:

let t = timeline(12);
t.at(0, 4) << [bd sd]; // mini-notation
t.at(4, 4) << euclid(3, 8, [bd]); // euclidean rhythm
t.at(8, 4) << [c4 e4 g4 c5]; // a melody

A timeline isn’t sound on its own — it’s a pattern source. Send it to a track with <<, exactly as you would a plain pattern:

use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");
drums.load_instrument(Sampler(Kit("CR-78")));
let t = timeline(8);
t.at(0, 4) << [bd _ sd _];
t.at(4, 4) << [bd*4];
drums << t;
PLAY;

When the timeline reaches its end it loops back to the start, so an arrangement repeats just like any other pattern would.

A slot queries its pattern one cycle at a time across the slot’s whole duration. A single-cycle pattern — most mini-notation — simply repeats every cycle. A multi-cycle pattern like one built with .cat() advances through its own cycles and wraps around naturally when the slot outlasts it:

let t = timeline(16);
// single-cycle: the same figure plays every cycle of the slot
t.at(0, 8) << [bd sd hh cp];
// two-cycle pattern over an 8-cycle slot: A B A B A B A B
t.at(8, 8) << [bd sd].cat([hh cp]);

Any cycle with no slot covering it is silent. Gaps aren’t an oversight — they’re how you write breaks, drops, and breathing room into a piece:

let t = timeline(16);
t.at(0, 4) << [bd sd bd sd]; // cycles 0–3: drums
// cycles 4–7: silence
t.at(8, 4) << [bd _ _ sd]; // cycles 8–11: drums
// cycles 12–15: silence

When two slots cover the same cycle, last start wins: the slot with the higher start cycle takes over in the overlap. This makes it easy to lay down a background figure and then punch a variation over part of it.

let t = timeline(8);
t.at(0, 8) << [bd*4]; // background for all 8 cycles
t.at(4, 4) << [cp cp cp cp]; // overrides cycles 4–7
// cycles 0–3 play [bd*4], cycles 4–7 play [cp cp cp cp]

If two slots start on the same cycle, the one you added last wins.

Real arrangements use one timeline per track, all the same length, so the sections line up. Here a kick, snare, and hi-hat play a sparse verse for eight cycles and then open up into a busier chorus:

use "std/instruments" { Sampler, Kit };
let len = 16;
let kick_tl = timeline(len);
kick_tl.at(0, 8) << [bd _ bd _]; // verse
kick_tl.at(8, 8) << [bd*4]; // chorus
let kicks = AudioTrack("kicks");
kicks.load_instrument(Sampler(Kit("CR-78")));
kicks << kick_tl;
let snare_tl = timeline(len);
snare_tl.at(0, 8) << [_ _ sd _]; // verse
snare_tl.at(8, 8) << [_ sd _ sd]; // chorus
let snares = AudioTrack("snares");
snares.load_instrument(Sampler(Kit("CR-78")));
snares << snare_tl;
let hat_tl = timeline(len);
hat_tl.at(0, 16) << [_ hh]; // throughout
let hats = AudioTrack("hats");
hats.load_instrument(Sampler(Kit("CR-78")));
hats << hat_tl;
PLAY;

Two accessors let you check what you’ve built. .length() returns the timeline’s length in cycles, and .entries() returns how many slots you’ve filled:

let t = timeline(16);
show(t.length()); // 16
show(t.entries()); // 0
t.at(0, 8) << [bd sd];
show(t.entries()); // 1

With an arrangement in place, render it to an audio file.

  • Rendering — bounce tracks and stems to .wav