Skip to content

MIDI Out

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(channel, velocity?, port_alias?, name?) creates a track. All arguments after channel are optional.

ParameterTypeDescription
channelNumberMIDI channel (1—16)
velocityNumber (optional)Default velocity (0—127), defaults to 100
port_aliasString (optional)Output port alias, defaults to "default"
nameString (optional)Track name for logging, defaults to "unnamed"
// Channel only — unnamed, velocity 100, default port
let melody = MidiTrack(1);
// Channel + velocity
let bass = MidiTrack(2, 80);
// Channel + velocity + port alias
let lead = MidiTrack(1, 110, "daw");
// Full form — with name
let synth = MidiTrack(3, 90, "hardware", "pad");

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 modifies the track in place and returns the same track, so calls can be chained.

MethodDescription
.output(alias)Route to output port
.output(alias, channel)Route to port on specific channel
.velocity(n)Set default velocity (0—127)
.velocity(signal)Set signal-driven velocity modulation
.velocity(pattern)Set pattern-driven velocity modulation
.input(alias)Receive from input port (all channels)
.input(alias, channel)Receive from specific channel
.monitor(mode)Set monitor mode ("off", "in", "auto")

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

let track = MidiTrack(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(4, 60);
soft.velocity(40); // even softer

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

let melody = MidiTrack(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(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(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.

.scale(scale, root) sets a track-level scale. Degree notation (^1, ^3, ^5) resolves to MIDI notes through this scale, and absolute notes are auto-quantized to the nearest scale tone.

let t = MidiTrack(1);
t.scale(major, C4);
t << <^1 ^3 ^5>; // resolves to C4, E4, G4
t << [C4 Db4 E4 F#4]; // Db4 quantized to C4 or D4

Accepts Scale objects, string names, or custom scales:

t.scale("minor", A3);
t.scale(Scale(#[0, 2, 4, 5, 7, 9, 11]), C4);
t.scale(load_scala("just/5-limit"), C4);

.tuning(range?) enables microtonal pitch bend output. Notes with fractional semitones (e.g., from a non-equal-tempered scale) are split into the nearest integer MIDI note plus a pitch bend message.

let t = MidiTrack(1);
t.tuning(2); // ±2 semitone bend range (default)
t.scale(load_scala("just/5-limit"), C4);
t << <^1 ^3 ^5>; // just-intonation thirds/fifths via pitch bend

The range argument sets the pitch bend sensitivity in semitones (default: 2). Your synthesizer’s pitch bend range must match.

.mpe(range?, zone_lo?, zone_hi?) enables per-note channel allocation. Each note gets its own MIDI channel, so overlapping microtonal notes don’t share a single pitch bend wheel.

let t = MidiTrack(1);
t.mpe(); // ±48 semitones, channels 2-16
t.mpe(24); // custom bend range
t.mpe(48, 2, 9); // range, zone channels 2-9
ParameterDefaultDescription
range48Pitch bend range in semitones
zone_lo2First member channel (1-16)
zone_hi16Last member channel (1-16)

On the first MPE note, Resonon sends the MPE Configuration Message (MCM) and per-channel RPN 0 (pitch bend range) so the synth configures itself automatically.

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(10, 110, "daw");
let pad = MidiTrack(1, 80, "synth");
// Via method
let lead = MidiTrack(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(1, 90, "daw");
let lead = MidiTrack(2, 100, "daw");
let pad = MidiTrack(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.

Track methods return the track, so you can chain them for a complete setup in one expression:

let track = MidiTrack(1, 100)
.output("daw")
.input("keyboard")
.monitor("auto");

This is equivalent to:

let track = MidiTrack(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 chainChained track methodsClean setupComplex routing

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

let lead = MidiTrack(1, 100);
let bass = MidiTrack(2, 80);
let drums = MidiTrack(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(1, 100, "daw");
let monitor = MidiTrack(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(1, 80, "daw");
backing << [C3 _ E3 _ G3 _ E3 _];
let live = MidiTrack(2, 100)
.output("daw")
.input("keyboard")
.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();