Skip to content

MIDI I/O

Resonon isn’t a closed world — it speaks MIDI, so it can drive a hardware synth, sequence a DAW, or take notes and knob-turns from a controller. In this chapter you’ll connect an output port, send a pattern to it, read a controller, and map a knob to a parameter with MIDI learn.

First, see what’s out there. midi_ports() returns the output ports your system exposes; midi_connect opens one:

let ports = midi_ports();
PRINT ports; // ["IAC Driver Bus 1", "Prophet Rev2", ...]
midi_connect("IAC Driver Bus 1");

On macOS the IAC Driver is the usual way to pipe MIDI into a DAW — enable it in Audio MIDI Setup → MIDI Studio. On Windows, a tool like loopMIDI gives you the same kind of virtual port.

A MidiTrack is the MIDI counterpart to the AudioTrack you’ve been using — but instead of feeding an instrument, its notes go out the port on a chosen channel. Send it a pattern with <<, exactly like an audio track:

midi_connect("IAC Driver Bus 1");
let melody = MidiTrack(1); // channel 1
melody << [C4 E4 G4 E4];
PLAY;

Point a software or hardware instrument at channel 1 and you’ll hear your pattern. Everything you know about patterns — chords, rests, Euclidean rhythms — works unchanged; only the destination is different.

Give each connection an alias and you can fan tracks out to different ports and channels — your DAW on one, a hardware synth on another:

midi_connect("IAC Driver Bus 1", "daw");
midi_connect("Prophet Rev2", "synth");
let drums = MidiTrack(10, 110, "daw"); // to the DAW, channel 10, velocity 110
let bass = MidiTrack(1, 90, "synth"); // to hardware, channel 1
let pad = MidiTrack(3, 80, "synth"); // to hardware, channel 3
drums << [bd _ sd _, ch ch ch ch];
bass << [C2 _ C2 G2];
pad << [C4, E4, G4];
PLAY;

MidiTrack(channel, velocity, alias) — the velocity and alias are optional and default to 100 and "default". Re-route a track at any time with .output:

bass.output("daw", 1); // move bass onto the DAW, channel 1

When you lose track of what’s going where, midi_routing() prints the whole picture — every output, input, track route, and the clock state.

Input mirrors output. midi_input_ports() lists controllers; midi_input_connect opens one under an alias:

let in_ports = midi_input_ports();
PRINT in_ports; // ["Arturia KeyStep 37", ...]
midi_input_connect("KeyStep", "kb");

Route that input into a track with .input, and choose how it monitors — whether incoming notes pass straight through to the output:

midi_input_connect("KeyStep", "kb");
midi_connect("IAC Driver Bus 1", "daw");
let keys = MidiTrack(1, 100)
.input("kb")
.monitor("in")
.output("daw");
PLAY;

Now the KeyStep plays through Resonon and out to your DAW. The monitor modes:

ModeBehavior
"off"No pass-through (the default)
"in"Always pass input straight to output
"auto"Pass through only while no pattern is playing on the track

You can narrow input down to one channel, or open it up to everything:

let bass = MidiTrack(2, 100).input("kb", 1); // only channel 1 from "kb"
let omni = MidiTrack(3, 80).input("all"); // every port, every channel

Controller knobs and faders send CC (control-change) messages, and Resonon hands them to you as signals — the same signals you can route into any parameter. Cc(n) reads CC n from any channel; Cc(channel, n) pins it to one channel (channels are 015):

let cutoff = Cc(74); // CC 74, any channel
let mod_wheel = Cc(0, 1); // CC 1 on channel 0

A CC arrives normalized to 0.01.0. Shape it and send it into a parameter with <<, just like an LFO:

use "std/effects" { Lowpass, Delay };
let filter = Lowpass(2000);
let delay = Delay(0.25, 0.5);
drums.load_effect(filter);
drums.load_effect(delay);
filter.param("Cutoff") << Cc(74).smooth(50).range_exp(200, 8000);
delay.param("Feedback") << Cc(71).smooth(30).range(0.1, 0.6);

.smooth(ms) irons out the zipper noise of stepped 7-bit values, and .range / .range_exp map the 01 signal onto a useful span — reach for range_exp on anything frequency-like, where your ear hears ratios rather than steps.

Don’t know which CC a knob sends? Let Resonon listen. Cc_learn() waits up to ten seconds for the first CC message, then returns a signal bound to it:

let knob = Cc_learn();
// "Waiting for CC input... (10s timeout)"
// ...twist a knob on your controller...
// "Learned: Cc(0, 74)"

Once you know the number, hard-code it so your script doesn’t pause to learn every time:

let knob = Cc(0, 74);

To lock tempo with external gear, either send MIDI clock to your outputs or follow a clock coming in:

midi_clock_send(true); // become the clock master
setbpm(120);
PLAY;
midi_input_connect("DAW Output", "daw");
midi_clock_follow("daw"); // follow an incoming clock instead

Sending and following are two ends of the same wire — a session is either the master or a follower, not both. If notes and clock drift against your gear, nudge them into line:

midi_delay(15); // delay all MIDI output by 15 ms
midi_clock_offset(-5); // send clock 5 ms ahead of the notes

That’s the whole round trip — out to your gear, back from a controller, and locked to a shared clock. The MIDI chapters go deeper on each piece: