Descriptions & Slots
In a DAW you drag a device onto a track and it is there. In resonon you write the rig as code and run it — and then run it again, and again, while it plays. For that to be safe, running a line must make the session become what the line says, not add another copy of it. Two ideas make that work: a constructor is a description, and a device on a track is identified by its slot name.
A constructor is a description
Section titled “A constructor is a description”Delay("delay", 0.25, 0.5) does not build a delay. It writes down three things — the slot name
"delay", what kind of device to build, and the settings to give it — and hands you a
handle. drums.set_effects(#[...]) is what builds it. Until that line runs, nothing is compiled, nothing
is loaded, and nothing makes a sound. The same holds for instruments: Sampler(Kit("CR-78"))
describes a sampler, and track.set_instrument(...) builds it.
That splits a handle’s methods in two:
- Writes are recorded.
set_parameter,set_programandload_stateon a handle that isn’t placed yet remember what you asked for and write it when the track places the handle. Constructor arguments are recorded writes too:Delay("delay", 0.25)means set Time to 0.25. - Reads need an instance.
parameter,parameters,programs,save_state,connect_inputand the GUI methods have nothing to read until the device is on a track, and the error says so.
let echo = Delay("delay", 0.25, 0.5);echo.set_parameter("Time", 0.4); // fine — recorded, written when it is placedprint(echo.parameters()); // error: "delay" is not on a track yet
drums.set_effects(#[echo]); // built hereprint(echo.parameters()); // now reads the live effect.name() is the exception — it is the one thing a description already knows.
Once placed, the handle stops being a description and becomes a reference to “the slot called
delay on that track”, re-resolved on every call. It keeps working across re-runs, even when the
slot underneath is rebuilt.
A plugin has two moments rather than one. Its name is resolved on the constructor line, so a typo or a bundle the scanner skipped is reported where you typed it. The expensive half — opening the binary, activating it, enumerating its parameters — waits for placement, so a plugin that resolves but fails to load reports on the placement line.
A chain is matched by name
Section titled “A chain is matched by name”set_effects(#[...]) sets a track’s entire chain in one call. The obvious worry is that re-running it
throws away what is running. It doesn’t: the name you gave each effect is its slot identity,
and set_effects matches the list you passed against the chain that is already there, name by name.
- A name already in the chain keeps its live effect. Modulation running on it survives, and
the writes the line records land on the live effect: change
Lowpass("lp", 1200)toLowpass("lp", 12000), re-run, and the cutoff moves to 12000. - A name not yet in the chain is built and inserted.
- A name you removed from the list is taken out.
- Moving a name reorders the chain around it.
So you edit the list and re-run, and the chain becomes what you wrote while the music keeps playing.
Parameter values follow one rule: the last write executed wins. Running a constructor writes
the arguments you gave it — and only those, so Lowpass("lp") leaves the cutoff where it is. A
set_parameter or a turn of the plugin’s own knob is a write too, and it stands until the next one.
Placing a handle you bound earlier doesn’t run its constructor again, so
drums.set_effects(#[lp, delay]) with a let lp = Lowpass("lp", 250) from before leaves lp’s cutoff
alone; re-running the let line is what writes 250 again.
The one thing that does start a slot over is changing what kind of effect it is. Put a
Lowpass("delay", 800) where a Delay("delay", …) used to be and the name no longer describes the
same effect, so the slot is rebuilt from the arguments you just gave — there are no old parameters
to carry across.
Two rules follow. Names must be unique within a chain: two slots called "delay" on one track is
an error, not a guess (different tracks may reuse a name). And set_effects(#[]) clears the chain, because
an empty list is still a description of what you want.
An instrument follows the same rule with a single slot. set_instrument(...) compares what you asked
for against what is loaded; when they are the same — the same plugin, the same kit — it keeps the
live instance, with its modulation and any notes still sounding, and writes the parameters the line
records onto it, by the same last-write-wins rule. Ask for
something different and it replaces the instrument. Replacing it, or taking it off with
remove_instrument(), fades the outgoing instrument out over a few milliseconds so it doesn’t click;
the track’s effects, and their tails, stay.
A layout is declarative
Section titled “A layout is declarative”Put together, those rules mean a rig written as code is a description of the rig you want, and running it makes the live graph match. A function that builds tracks and returns their handles — a layout — is no new syntax, and running it a second time rebuilds nothing: the names line up, so you get the same tracks and the same effects, still playing.
That is why editing a layout live is worth doing. Add an effect to a .set_effects(#[...]) list and re-run
— it is inserted in place. Delete one and it is removed. Move one and the chain reorders. Every
name still in the list keeps its live instance, so running modulation and every value you didn’t
rewrite carry across the edit. You don’t reset and you don’t start the piece over; the code you are looking at
simply becomes true.
Related
Section titled “Related”- Your First Mix — write a rig as a layout, step by step
- Build an Effect Chain — add, reorder and dial effects
- Names & Live Editing — the same idea for functions and modules
- The Audio Graph — where the devices sit