Skip to content

MIDI I/O

Resonon sends and receives MIDI. This page covers connecting to output ports, routing to a DAW, receiving input from a controller, and mapping knobs to parameters with MIDI learn.

List available output ports and connect:

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

On macOS, the IAC Driver is the standard way to route MIDI to a DAW. Enable it in Audio MIDI Setup > MIDI Studio.

Create a MIDI track and send a pattern:

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

The notes arrive in your DAW on MIDI channel 1. Set up a software instrument on that channel to hear sound.

Connect multiple ports with aliases and route tracks to specific destinations:

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

Re-route a track on the fly:

bass.output("daw", 1); // Move bass to DAW, channel 1
midi_routing();

This prints a summary of all output ports, input ports, active track routing, and clock state.

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

Route input to a track with .input() and set a monitor mode:

midi_input_connect("KeyStep", "kb");
midi_connect("IAC Driver Bus 1", "daw");
let keys = MidiTrack("keys", 1, 100)
.with_input("kb")
.with_monitor("in")
.with_output("daw");
PLAY;

Now playing the KeyStep sends notes through Resonon to the DAW. Monitor modes:

ModeBehavior
"off"No pass-through (default)
"in"Always pass input to output
"auto"Pass through only when no pattern is playing
let bass = MidiTrack(2, 100).input("kb", 1); // Only channel 1 from input
let omni = MidiTrack(3, 80).input("all"); // All ports, all channels

MIDI CC messages (knob/fader values from controllers) are available as signals. Use them to control any parameter.

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

CC values are normalized to 0.0–1.0. Apply them to effect parameters with <<:

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.Cutoff << Cc(74).smooth(50).range_exp(200, 8000);
delay.Feedback << Cc(71).smooth(30).range(0.1, 0.6);

.smooth(ms) removes zipper noise from 7-bit CC values. .range() and .range_exp() map the 0–1 signal to a useful range (exponential is better for frequencies).

Don’t know the CC number for a knob? Use Cc_learn():

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

Cc_learn() blocks for up to 10 seconds, waiting for the first CC message. It returns a signal bound to that CC. Use the printed output to hardcode it later:

// Step 1: Discover interactively
let knob = Cc_learn();
// Step 2: Hardcode for your script
let knob = Cc(0, 74);
CCNameTypical Use
1Mod WheelVibrato, filter modulation
7VolumeChannel volume
10PanStereo panning
11ExpressionDynamic volume control
64SustainSustain pedal (on/off)
71ResonanceFilter resonance
74BrightnessFilter cutoff

Wire up multiple knobs to control a live performance:

use "std/instruments" { Sampler, Kit };
use "std/effects" { Lowpass, Delay };
midi_input_connect("USB Controller", "ctrl");
let drums = AudioTrack("drums");
drums.load_instrument(Sampler(Kit("cr78")));
drums << [bd sd [bd bd] sd, ch ch ch ch];
let filter = Lowpass(2000);
let delay = Delay(0.25, 0.4);
drums.load_effect(filter);
drums.load_effect(delay);
// Map controller knobs to effect parameters
filter.Cutoff << Cc(74).smooth(50).range_exp(300, 4000);
filter.Resonance << Cc(71).smooth(30).range(0.5, 4);
delay.Feedback << Cc(1).smooth(30).range(0.1, 0.6);
PLAY;

For sample-accurate CC control inside custom DSP blocks, use the cc() builtin:

dsp effect CcFilter {
fn process(left, right) -> (out_l, out_r) {
let cutoff = cc(74) * 10000.0 + 200.0;
let reso = cc(71) * 0.9 + 0.1;
return (svf_lp(left, cutoff, reso),
svf_lp(right, cutoff, reso));
}
}

Additional MIDI builtins available in DSP blocks:

FunctionDescription
cc(n)CC value (0–1), any channel
cc(channel, n)CC on specific channel
aftertouch()Channel pressure (0–1)
pitchbend()Pitch bend (-1 to 1)

Send or follow MIDI clock for tempo sync with external gear:

// Send clock to all connected outputs
midi_clock_send(true);
setbpm(120);
PLAY;
// Follow external clock
midi_input_connect("DAW Output", "daw");
midi_clock_follow("daw");

Fine-tune timing:

midi_delay(15); // Delay all MIDI output by 15ms
midi_clock_offset(-5); // Send clock 5ms earlier than notes