Skip to content

MIDI Tracks

MIDI tracks send pattern data to external synthesizers, DAWs, and hardware over MIDI. Each track targets a specific channel and output port, and can optionally receive MIDI input for live play-through.

MidiTrack(...) creates a track. The first argument can be a name (string) or a channel (number). If the first argument is a number, the track is named "unnamed".

ParameterTypeDescription
nameString (optional)Track name, defaults to "unnamed"
channelNumberMIDI channel (1—16)
velocityNumber (optional)Default velocity (0—127), defaults to 100
port_aliasString (optional)Output port alias, defaults to "default"

Without a name:

MidiTrack(channel)
MidiTrack(channel, velocity)
MidiTrack(channel, velocity, port_alias)

With a name:

MidiTrack(name, channel)
MidiTrack(name, channel, velocity)
MidiTrack(name, channel, velocity, port_alias)
// Channel only — unnamed, velocity 100, default port
let melody = MidiTrack(1);
// Channel + velocity
let bass = MidiTrack(2, 80);
// Named track with velocity
let lead = MidiTrack("lead", 1, 110);
// Full form — named, routed to a specific port alias
let synth = MidiTrack("pad", 3, 90, "hardware");

Use the output operator << to send a pattern to a track:

melody << [C4 E4 G4 E4];

You can change the pattern at any time. The new pattern takes effect at the next cycle boundary:

melody << [C4 D4 E4 F4 G4];

MIDI tracks expose methods for routing, velocity, input, and monitoring. Each method has a mutating variant that modifies the track in place and a pure variant (prefixed with with_) that returns a new track, leaving the original unchanged.

MethodMutatingDescription
.output(alias)yesRoute to output port
.output(alias, channel)yesRoute to port on specific channel
.with_output(...)noPure variant of .output()
.velocity(n)yesSet default velocity (0—127)
.velocity(signal)yesSet signal-driven velocity modulation
.velocity(pattern)yesSet pattern-driven velocity modulation
.with_velocity(n)noPure variant of .velocity()
.input(alias)yesReceive from input port (all channels)
.input(alias, channel)yesReceive from specific channel
.with_input(...)noPure variant of .input()
.monitor(mode)yesSet monitor mode ("off", "in", "auto")
.with_monitor(mode)noPure variant of .monitor()

.output(alias) routes the track to a named port alias. .output(alias, channel) also changes the MIDI channel:

let track = MidiTrack("keys", 1, 100);
// Route to the "daw" port alias
track.output("daw");
// Route to "synth" on channel 5
track.output("synth", 5);

If the alias is not currently connected, Resonon logs a warning but does not error — you can connect the port later.

.velocity(n) sets the default velocity for the track. Notes without an explicit velocity use this value:

let soft = MidiTrack("pad", 4, 60);
soft.velocity(40); // even softer
// Pure variant for chaining
let loud = soft.with_velocity(120);

.velocity() also accepts a signal for dynamic velocity modulation. Each note’s velocity is sampled from the signal at event time:

let melody = MidiTrack("synth", 1);
melody << [C4 D4 E4 F4];
melody.velocity(sine(0.5).range(60, 127));

.velocity() also accepts a pattern. Each note’s velocity is determined by the velocity pattern event active at that note’s start time:

let melody = MidiTrack("synth", 1);
melody << [C4 D4 E4 F4];
melody.velocity([100 60 80 120]);

Per-note @ velocity always takes precedence over signal or pattern:

// C4 stays at 100, F4 stays at 50, D4/E4 get signal values
melody << [C4@100 D4 E4 F4@50];
melody.velocity(sine(1).range(60, 127));

The last .velocity() call wins — calling .velocity(n) clears any active signal or pattern, and .velocity(signal) or .velocity(pattern) overrides the static default. Signal and pattern are mutually exclusive.

.input(alias) configures a track to receive MIDI from an input port. .monitor(mode) controls whether incoming MIDI is passed through to the track’s output:

ModeBehavior
"off"Input is received but not passed through (default)
"in"Always pass input through to output
"auto"Pass through when no pattern is playing
let keys = MidiTrack("keys", 1, 100);
keys.input("keyboard");
keys.monitor("in");

The special alias "all" receives from all connected input ports:

keys.input("all");

See MIDI Input for details on connecting input ports and input routing.

Port aliases let you send different tracks to different MIDI destinations — for example, one track to your DAW and another to a hardware synthesizer.

Use midi_connect(port_name, alias) to open a connection with a named alias:

midi_connect("IAC Driver Bus 1", "daw");
midi_connect("Prophet Rev2", "synth");

Without an alias, the port is connected as "default":

midi_connect("IAC Driver Bus 1");
// equivalent to midi_connect("IAC Driver Bus 1", "default")

Route tracks via the constructor or the .output() method:

// Via constructor
let drums = MidiTrack("drums", 10, 110, "daw");
let pad = MidiTrack("pad", 1, 80, "synth");
// Via method
let lead = MidiTrack("lead", 2, 100);
lead.output("daw");
// List available ports
PRINT midi_ports();
// Check which ports are connected
PRINT midi_connected();
// Disconnect a specific alias
midi_disconnect("synth");
// Disconnect the default port
midi_disconnect();
// Connect to DAW for recording and hardware synth for monitoring
midi_connect("IAC Driver Bus 1", "daw");
midi_connect("Prophet Rev2", "synth");
let bass = MidiTrack("bass", 1, 90, "daw");
let lead = MidiTrack("lead", 2, 100, "daw");
let pad = MidiTrack("pad", 1, 80, "synth");
bass << [C2 _ E2 _ G2 _ E2 _];
lead << [C4 E4 G4 C5];
pad << <Stack(chord(C4, "maj7"))>;
PLAY;

When multiple tracks are playing, they share the same cycle clock. Patterns sent to different tracks stay aligned:

let bass = MidiTrack(2, 80);
bass << [C3 _ C3 G2];
let chords = MidiTrack(3, 90);
chords << <Stack(chord(C4, "major")) Stack(chord(G3, "major"))>;

Both tracks loop in sync.

Start and stop playback with PLAY and PAUSE:

PLAY;
// ... music plays ...
PAUSE;

Playback resumes from where it left off. All active tracks respond to the same transport.

Pure methods (with_*) return new tracks, so you can chain them for a complete setup in one expression:

let track = MidiTrack("lead", 1, 100)
.with_output("daw")
.with_input("keyboard")
.with_monitor("auto");

This is equivalent to:

let track = MidiTrack("lead", 1, 100);
track.output("daw");
track.input("keyboard");
track.monitor("auto");
PatternTechniqueMusical effectUse case
Multi-timbralTracks on different channelsLayer instrumentsOrchestration
Multi-portTracks to different aliasesMultiple destinationsDAW + hardware
Velocity layersDifferent default velocitiesDynamic contrastExpressive parts
Input thru.input() + .monitor("in")Live play-throughPerformance
Builder chain.with_*() methodsClean setupComplex routing

Assign separate channels to each instrument. Most DAWs and multi-timbral synths map channels to different sounds:

let lead = MidiTrack("lead", 1, 100);
let bass = MidiTrack("bass", 2, 80);
let drums = MidiTrack("drums", 10, 110);
lead << [E5 D5 C5 D5];
bass << [C3 _ C3 G2];
drums << [bd _ sn _];
PLAY;

Channel 10 is conventionally used for drums in General MIDI.

Record to a DAW while simultaneously monitoring through a hardware synth:

midi_connect("IAC Driver Bus 1", "daw");
midi_connect("Minilogue", "synth");
let track = MidiTrack("lead", 1, 100, "daw");
let monitor = MidiTrack("monitor", 1, 100, "synth");
let melody = [C4 E4 G4 C5];
track << melody;
monitor << melody;
PLAY;

Combine keyboard input with a backing pattern:

midi_connect("IAC Driver Bus 1", "daw");
let backing = MidiTrack("backing", 1, 80, "daw");
backing << [C3 _ E3 _ G3 _ E3 _];
let live = MidiTrack("live", 2, 100)
.with_output("daw")
.with_input("keyboard")
.with_monitor("in");
PLAY;

The live track passes keyboard input directly to the DAW while the backing track loops its pattern.

ErrorCauseFix
MIDI channel must be 1-16Channel out of rangeUse a channel between 1 and 16
MIDI velocity must be 0-127Velocity out of rangeUse a velocity between 0 and 127
Invalid monitor modeUnrecognized mode stringUse "off", "in", or "auto"
Port alias warning in logAlias not yet connectedCall midi_connect() with the alias first
// List all available output ports
PRINT midi_ports();
// Check which ports are connected (returns dict or false)
PRINT midi_connected();
// List available input ports
PRINT midi_input_ports();