Skip to content

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.

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.

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.

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.

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 quieter
drums.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.

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.

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 dB
master.pan << 0; // keep it centered
PLAY;

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.

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.

Three statements drive playback:

  • PLAY; starts the clock and begins playing.
  • PAUSE; halts playback where it is; the next PLAY; 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.

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