Tracks & Master
Every sound you make in resonon flows through a track. A track is a channel for audio — think of it like a channel strip in a regular DAW: it hosts one instrument, has its own volume and pan, carries a chain of effects, and sends its output downstream. Several tracks playing together sum at the master, the final output that reaches your speakers.
This chapter builds a track from scratch and gets it playing. Here’s the whole
thing — write it into a .non file, select it, and press Cmd+Enter:
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.instrument(Sampler(Kit("CR-78")));
drums << [bd sd bd sd];
PLAY;A four-on-the-floor beat. Now the line-by-line.
Creating a Track
Section titled “Creating a Track”let drums = AudioTrack("drums");The name — "drums" — is how you’ll recognise the track in printouts, in the
live-coding server, and when you render to disk. It’s also, more importantly,
what the track is. AudioTrack("drums") makes the channel the first time you
run it, and every time after that it hands you back the same one: same fader,
same instrument, same effect chain. You don’t import AudioTrack; it’s always
available.
That matters more than it looks. It’s what lets you re-run a line while the music
plays — you’re talking to the track that’s already there, not stacking up a
second one. The flip side: two AudioTrack("bass") calls mean one track. Two
tracks need two names.
A fresh track is silent. It has no instrument yet, so there’s nothing to make sound.
Loading an Instrument
Section titled “Loading an Instrument”drums.instrument(Sampler(Kit("CR-78")));instrument attaches an instrument to the track. Here it’s a Sampler
loaded with the built-in CR-78 drum kit — resonon’s sample engine playing back
classic drum-machine samples. A track holds exactly one instrument.
Run that same line again and nothing is torn down. instrument compares what you
asked for against what’s already loaded, and when they’re the same thing — the
same plugin, the same kit — it keeps the live instance, along with its
parameter edits, its modulation, and any notes still sounding. Ask for something
different and it replaces the instrument instead. remove_instrument() takes it
off entirely, letting the outgoing sound ring out and leaving the effect chain
and routing alone:
drums.remove_instrument();The Samplers chapter covers the sample engine in depth, and Plugins shows how to load a VST3 or CLAP instrument instead.
Sending a Pattern with <<
Section titled “Sending a Pattern with <<”drums << [bd sd bd sd];The << operator sends a pattern to a track. [bd sd bd sd] is
mini-notation: four samples — bass drum, snare, bass drum, snare — spread evenly
across one cycle. The track plays that pattern, looping it every cycle, until you
send it a new one.
Sending again replaces the pattern outright:
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.instrument(Sampler(Kit("CR-78")));
drums << [bd sd bd sd];drums << [bd*4]; // replaces the previous pattern
PLAY;Mini-notation is a language of its own — rests, subdivisions, repeats, chords. The Patterns chapters are the place to learn it.
Volume & Pan
Section titled “Volume & Pan”Two controls live on every track: volume (in decibels) and pan (from -1
fully left to 1 fully right). You set them with <<, the same operator you use
for patterns:
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.instrument(Sampler(Kit("CR-78")));drums << [bd sd bd sd];
drums.volume << -6; // 6 dB quieterdrums.pan << -0.5; // halfway to the left
PLAY;Pan uses a constant-power law: a source keeps a steady perceived loudness as
it moves across the stereo field. Center is unity (0 dB, unchanged), and each
side rises to +3 dB at the hard-panned extreme so the total acoustic power stays
constant — the same behaviour you’d expect from a DAW pan pot. Because the
extremes are +3 dB hotter, a near-full-scale source can clip when hard-panned;
trim its volume if that happens.
<< here does more than set a number once — it can also take a moving signal,
so volume and pan become automation lanes. That’s the subject of
Signals & Automation.
Multiple Tracks
Section titled “Multiple Tracks”Real arrangements use several tracks, each with its own instrument, pattern, and mix. Give every track a distinct name:
use "std/instruments" { Sampler, Kit };
let kick = AudioTrack("kick");let snare = AudioTrack("snare");let hats = AudioTrack("hats");
kick.instrument(Sampler(Kit("CR-78")));snare.instrument(Sampler(Kit("CR-78")));hats.instrument(Sampler(Kit("CR-78")));
kick << [bd*4];snare << [_ sd _ sd];hats << [hh*8];
PLAY;Each track is independent — mute one, repan another, swap a pattern — and all three play together.
The Master Output
Section titled “The Master Output”You never created a master, yet sound still reached your speakers. That’s because
every track routes to master by default. master is the final mixing point:
all tracks (and any buses) sum there before the audio leaves resonon. It’s the
master fader of the session.
You can ride it like any track, using <<:
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.instrument(Sampler(Kit("CR-78")));drums << [bd sd bd sd];
master.volume << -2; // trim the whole mix by 2 dBmaster.pan << 0; // keep it centered
PLAY;Building a Track in One Chain
Section titled “Building a Track in One Chain”Every setter you’ve seen returns the track it acted on, so a whole channel strip can be one expression instead of five statements:
use "std/instruments" { Sampler, Kit };use "std/effects" { Lowpass };
let drums = AudioTrack("drums") .instrument(Sampler(Kit("CR-78"))) .fx(#[Lowpass("lowpass", 4000)]) .out(master) .volume(-6);
drums << [bd sd bd sd];
PLAY;Read top to bottom, that’s the strip: what makes the sound, what processes it,
where it goes, how loud. fx sets the effect chain
(Effects); out sets the track’s single output
destination — the chainable spelling of >>, covered in
Routing, Buses & Sends. volume(-6) is
the method form of drums.volume << -6; the operator form is what you want when
the value is a moving signal.
Not every method chains. The ones that answer a question hand you the answer
instead of the track — effects(), effect(name), send(destination),
take(), and instrument() called with no argument. Those end a chain.
Layouts
Section titled “Layouts”Nothing above is special to a single track, and nothing about it is one-shot. Put
the whole rig in a function and you have a layout — no new syntax, just a
fn that builds tracks and returns their handles:
use "std/instruments" { Sampler, Kit };use "std/effects" { Lowpass, Reverb };
fn stage() { let verb = AudioTrack("verb") .fx(#[Reverb("room", 0.8, 0.3)]) .out(master);
let drums = AudioTrack("drums") .instrument(Sampler(Kit("CR-78"))) .fx(#[Lowpass("lowpass", 4000)]) .out(master) .send_to(verb, 0.3);
return (drums, verb);}
let (drums, verb) = stage();drums << [bd sd bd sd];
PLAY;The layout is declarative: it describes the rig you want, and running it makes
the live graph match. Run stage() a second time and nothing is rebuilt — the
names line up, so you get the same tracks, the same reverb, the same everything,
still playing.
Which is what makes editing it live worth doing. Add an effect to the
.fx(#[...]) array and re-evaluate — it’s inserted in place. Delete one and it’s
removed. Move one and the chain reorders, which of course changes the sound,
because order is what an effect chain is. Change .instrument(...) and the
instrument swaps.
What survives all of that is the effects themselves. Every name still in the list
keeps its live instance — including one you just moved — so parameter values and
running modulation carry across the edit untouched. You don’t RESET; and you
don’t start the piece over; the code you’re looking at simply becomes true.
Because a layout is an ordinary function, it takes arguments, returns partial
rigs you compose into bigger ones, and travels between projects through
use like any other code — see Modules.
Transport
Section titled “Transport”Three statements drive playback:
PLAY;starts the clock and begins playing.PAUSE;halts playback where it is; the nextPLAY;resumes from that point.STOP;halts playback, returns to the start of the cycle, and mutes the output.
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.instrument(Sampler(Kit("CR-78")));drums << [bd sd bd sd];
PLAY;In the VSCode extension you don’t usually type these — F5 toggles play/pause
and Shift+F5 stops. You’ll reach for the statements mostly in rendered files
and scripts.
Next Steps
Section titled “Next Steps”You can build tracks, load instruments, mix them, write a layout, and drive transport. Next, go deeper on the instrument that made all that sound.
- Samplers — drum kits, melodic sampling, and per-sample control
- Effects — shaping a track’s sound with delays, filters, and reverb
- Routing, Buses & Sends — wiring tracks
together with
>>and sends - Live Workflow — editing a layout while it plays