Skip to content

The Arrangement Model

A looping pattern is the right shape for live coding, but a finished piece has a form — an intro, a verse, a chorus. Resonon’s arrangement model is the arrangement view of a DAW written as values: a timeline is one lane on a ruler, and a section is a slice of the whole piece — every track’s clips, its automation and its picture under one length. This page explains the rules those values follow, which are chosen so that a song can be edited while it plays without anything being lost.

A timeline isn’t sound on its own; it is a pattern source. You send it to a track with << like any other pattern, and when it reaches its end it loops. Each slot queries its pattern one cycle at a time across the slot’s duration, so a one-cycle figure repeats and a multi-cycle pattern advances through its own cycles. Any cycle no slot covers is silent — gaps are how breaks and drops are written, not an oversight.

A section adds the thing a set of timelines can’t guarantee: one length for every lane. Because the length belongs to the section and not to each track’s lane, the tracks cannot drift out of alignment, and a section boundary is a boundary for the whole piece.

A timeline’s length is a ruler in whole cycles, but nothing is placed by the ruler: a slot may begin and end anywhere. .at(2.5, 1.25) means exactly that. Positions a musician means stay exact — a third of a cycle is a third, not 0.3333 — so a run of triplet placements cannot drift.

This matters most for material that was recorded. A take lands where latency compensation measured it, which is almost never on a cycle line; an arrangement that only knew the grid would have to round that measurement away.

When two entries cover the same cycle, the later one wins — but the entry underneath is masked, not cut. It is still there, hidden for exactly as long as the mask lasts, and when the mask ends it resumes in its own phase rather than restarting. show() marks that with an offset:

timeline — 16 cycles, 2 entries
layers: base, main, fills
0..4 [bd bd sd bd] main
4..6 [hh hh] fills
6..16 [bd bd sd bd] +6c main

After the fill, the groove picks up at its own cycle 6 instead of starting over. The same holds across a section boundary: a fill punched from the end of a verse into a chorus plays through the seam, and the chorus resumes where it would have been.

Nothing is destroyed, which is what makes this safe to live-code. Lay down a groove, punch variations over it, and take them away again without retyping the groove.

A continuous lane needs the edges spelled out. On an automation lane, the curve beneath a mask holds its level right up to the mask edge and steps there; it doesn’t slide towards the mask across the preceding cycles. Clear the mask and the curve underneath comes back exactly as authored.

Re-running a line must not stack another copy of what it wrote. A write therefore upserts by start: editing .at(4, 2) to .at(4, 1) and running it again reshapes that same mask. .clear() on a position lifts the mask and brings back exactly what it was hiding.

Masks live on layers, stacked bottom to top:

  • base is the structure. arrange() lays sections down here, and a bare song[drums] << clip replaces it.
  • main is where a plain .at() write goes.
  • Named layers — .layer("fills") — are stacked on top when first written to, and group punches you want to lift together with .mute() and bring back, in phase, with .unmute().

Because the structure and the punches live on different layers, rewriting the song underneath leaves your punches alone, and .clear() removes what you punched while keeping what arrange() built. A hand-built timeline() has an empty base — nothing laid a structure down — so every .at() write lands on main, and .clear() empties it outright.

Only a write creates a layer. .layer("fills") on its own is a name and a target, so naming a layer that was never written to is an error rather than a silent no-op: a typo fails where you can see it. The stack order is declarative — .set_layer_order(#[...]) states it, and running the line again changes nothing.

show() renders the resolved stack — one row per audible interval, tagged with the layer that won it. That is the answer to “what will this sound like”, which the list of writes cannot give once masks are involved. A muted layer contributes no rows, and an entry that is completely hidden is not an interval at all.

.entries() answers the other question — “what did I write here” — in authoring order, and flags an entry that is completely hidden as masked. That is the one thing the resolved view cannot tell you, and the reason a covered entry stays findable instead of mysterious.

Every lane edit on a playing song lands live, with no second play() and no rewind. “Live” means a different moment on each kind of lane:

LaneTakes effectWhy
Notes (song[drums])next cycle boundarythe lane swaps its pattern once per cycle, so notes already sounding are never cut off mid-cycle
Automation (.volume)next audio blockthe merged curve re-binds and the engine picks it up on its next callback
Screen (song[screen])next framethe renderer re-selects the covering span every frame

So a note edit — including .mute() and .clear() on a note layer — is quantized to the cycle, and you hear the rest of the current cycle first. Automation and visuals are effectively immediate.