MIDI In & CC
The last guide sent notes out. This one brings MIDI in — route a keyboard into a track, read a controller’s knobs as live signals, and send CC automation back to your gear. By the end you’ll have MIDI flowing both directions.
Listing Input Ports
Section titled “Listing Input Ports”midi_input_ports() returns the MIDI input ports your system exposes:
let in_ports = midi_input_ports();print(in_ports); // ["IAC Driver Resonon", "USB MIDI Keyboard"]Connecting an Input Port
Section titled “Connecting an Input Port”midi_input_connect(port, alias) opens a connection and gives it an alias — a
short name you choose and reuse when routing input to tracks:
midi_input_connect("IAC Driver Resonon", "iac");Substring matching
Section titled “Substring matching”The port name doesn’t have to be exact. Resonon tries an exact match first, then falls back to a substring match:
// Full port name is "Arturia KeyStep 37"midi_input_connect("KeyStep", "kb");If several ports contain the substring, the first match wins — use a more specific substring or the full name to disambiguate.
Routing Input to Tracks
Section titled “Routing Input to Tracks”A track pulls MIDI from an input with .input(alias).
All channels
Section titled “All channels”let keys = MidiTrack(1, 100).input("iac");A specific channel
Section titled “A specific channel”Pass a channel to filter to just that one:
let bass = MidiTrack(2, 100).input("iac", 1);Every connected port
Section titled “Every connected port”The special alias "all" listens to every connected input port at once:
let omni = MidiTrack(3, 80).input("all");Monitor Modes
Section titled “Monitor Modes”Monitor mode decides whether incoming MIDI is passed through to the track’s output:
| Mode | Behavior |
|---|---|
"off" | No pass-through (default) |
"in" | Always pass input through to output |
"auto" | Pass through only when no pattern is playing |
// Always hear the keyboardlet keys = MidiTrack(1, 100).input("iac").monitor("in");
// Hear the keyboard only when the track is idlelet keys_auto = MidiTrack(1, 100).input("iac").monitor("auto");
// Silent monitoring (record only)let keys_off = MidiTrack(1, 100).input("iac").monitor("off");Thru-play behavior
Section titled “Thru-play behavior”When monitor mode is "in" or "auto", incoming events are forwarded to the
track’s output, with a few things handled automatically:
- Channel remapping — forwarded notes are remapped to the track’s output channel, so thru-play stays consistent with pattern output. A keyboard on channel 1 driving a channel-5 track arrives on channel 5.
- Feedback detection — if an input and output form a loop (both on the same IAC
bus, say), events could circulate forever. Resonon detects this and briefly
suppresses thru-play, logging a warning, then re-enables after a cooldown. To
avoid loops entirely, use separate ports or set monitor to
"off". - CC capture — incoming CC messages are captured and made available as
Cc()signal sources (covered below).
Full input chain
Section titled “Full input chain”The track methods return the track, so a complete input-to-output route reads as one expression:
let thru = MidiTrack(1, 100) .input("iac") .monitor("in") .output("iac");Disconnecting
Section titled “Disconnecting”midi_input_disconnect(alias) closes a named input connection:
midi_input_disconnect("iac");On disconnect Resonon sends All Notes Off (CC 123) to any outputs that were receiving thru-play, so unplugging a controller mid-performance won’t leave stuck notes.
Reading CC as Signals
Section titled “Reading CC as Signals”Controller knobs, faders, and wheels arrive as Control Change (CC) messages.
Cc() turns one into a live signal — a continuous value from 0.0 to 1.0
you can route anywhere a signal is accepted.
Any channel
Section titled “Any channel”Cc(cc_num) reads the given CC from any channel — convenient when you have a
single controller and don’t care which channel it sends on:
let cutoff_signal = Cc(74);Specific channel
Section titled “Specific channel”Cc(channel, cc_num) isolates one channel (0–15) — use it when several
controllers share CC numbers and you need to tell them apart:
let cutoff_signal = Cc(0, 74);Both forms need an active MIDI input connection.
MIDI learn
Section titled “MIDI learn”Don’t know a controller’s CC number? Cc_learn() waits up to 10 seconds for you to
move a knob, then returns a signal bound to exactly that channel and CC — and prints
the equivalent Cc(...) call so you can hardcode it later:
let knob = Cc_learn();// Console: "Waiting for CC input... (10s timeout)"// Move a knob on your controller...// Console: "Learned: Cc(0, 74)"If nothing arrives within 10 seconds it raises an error. The typical workflow is to
learn interactively, then replace the call with the hardcoded Cc():
let knob = Cc(0, 74);Smoothing
Section titled “Smoothing”CC values arrive as discrete 7-bit steps (0–127). Apply .smooth(time_ms) to
eliminate zipper noise when modulating audio — a one-pole lowpass interpolates
between incoming values into a continuous signal:
let cutoff = Cc(74).smooth(50);Modulating With CC Signals
Section titled “Modulating With CC Signals”CC signals work anywhere a signal is accepted. Route one into a parameter with <<:
let synth = AudioTrack("synth");
synth.filter.param("Cutoff") << Cc(74).smooth(50); // knob → cutoffsynth.param("Gain") << Cc(11).smooth(20); // expression pedal → volumesynth.reverb.param("Mix") << Cc(1).smooth(30); // mod wheel → reverb depthBecause CC signals are just signals, all the standard signal methods apply — see Signals & Automation for the full set. The ones you’ll reach for most:
| Method | Description |
|---|---|
.smooth(time_ms) | One-pole lowpass smoothing |
.range(min, max) | Scale output to a linear range |
.range_exp(min, max) | Scale output to an exponential range |
Frequency-like targets feel more natural on a logarithmic curve, so reach for
.range_exp() there:
let filter_freq = Cc(74).smooth(50).range_exp(200, 8000);Common CC assignments, for reference:
| CC | Name | Typical use |
|---|---|---|
| 1 | Mod Wheel | Vibrato, filter modulation |
| 7 | Volume | Channel volume |
| 10 | Pan | Stereo panning |
| 11 | Expression | Dynamic volume |
| 64 | Sustain | On/off (≥64 = on) |
| 74 | Brightness | Filter cutoff |
Sending CC Output
Section titled “Sending CC Output”The other direction: send CC to external synths and DAWs. Call .cc(number) on a
MIDI track to make a CC output, then route a signal, pattern, or value into it with
<<.
Signal-driven
Section titled “Signal-driven”The signal is sampled (~30 Hz by default) and sent as CC messages, skipping identical consecutive values:
let synth = MidiTrack(1);synth << [C4 E4 G4 C5];
synth.cc(74) << Sine(0.5).range(0, 127);Pattern-driven
Section titled “Pattern-driven”Send discrete values on the beat grid:
synth.cc(1) << [0 64 127 64];Static
Section titled “Static”Send a fixed value once per cycle:
synth.cc(7) << 100;Custom send rate
Section titled “Custom send rate”Change the sampling rate of a signal-driven CC with .send_rate(hz) — higher is
smoother but uses more bandwidth (standard DIN MIDI handles ~1000 three-byte
messages per second; USB is far faster):
synth.cc(74).send_rate(60) << Sine(0.5).range(0, 127);A CC output is a value like any other, so you can store it in a variable:
let cutoff = synth.cc(74);cutoff << Sine(0.5).range(0, 127);Inspecting Routing
Section titled “Inspecting Routing”midi_routing() prints a summary of every connection, active slot, and the clock
state — the fastest way to see what’s wired to what:
midi_routing();MIDI Routing
Output Ports: "daw" -> "IAC Driver Bus 1" "synth" -> "Prophet Rev2"
Input Ports: "kb" -> "Arturia KeyStep 37"
Active Slots: ch1 -> "daw" (ch 1) synth:ch1 -> "synth" (ch 1)
Clock: mode: follower (from "kb")If nothing is connected it prints (no MIDI connections).
Common Errors
Section titled “Common Errors”| Error | Cause | Fix |
|---|---|---|
Input port alias '...' already connected | Alias already in use | Disconnect it first, or pick another alias |
| Port not found | Port name not recognized | Check midi_input_ports() for the exact name |
MIDI feedback loop detected on ch... | Thru-play forming a loop | Use separate in/out ports, or set monitor to "off" |
Complete Example
Section titled “Complete Example”List ports, connect with substring matching, build a pass-through track, inspect the routing, play, and clean up:
print(midi_input_ports());print(midi_ports());
midi_input_connect("KeyStep", "kb");midi_connect("IAC Driver Bus 1", "daw");
let keys = MidiTrack(1, 100) .input("kb") .monitor("in") .output("daw");
midi_routing();
PLAY;// ... play your keyboard ...PAUSE;
midi_input_disconnect("kb");midi_disconnect("daw");Next Steps
Section titled “Next Steps”With MIDI flowing both ways, lock Resonon’s clock to your other gear — or write your notes out to a file.
- Clock Sync — sync tempo and transport with DAWs and hardware
- Export & MPE — write Standard MIDI Files
- Signals & Automation — the full signal method reference