MIDI In & CC
Route MIDI from external keyboards, controllers, and instruments into Resonon tracks.
Listing Input Ports
Section titled “Listing Input Ports”midi_input_ports() returns an array of available MIDI input port names:
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 assigns it an alias for later reference:
midi_input_connect("IAC Driver Resonon", "iac");The alias is a short name you choose. Use it when routing input to tracks.
Substring matching
Section titled “Substring matching”The port name does not need to be exact. Resonon tries an exact match first, then falls back to substring matching:
// Full port name is "Arturia KeyStep 37"midi_input_connect("KeyStep", "kb");If multiple ports contain the substring, the first match is used. Use a more specific substring or the full name to avoid ambiguity.
Alias uniqueness
Section titled “Alias uniqueness”Each alias must be unique. Connecting a second port with the same alias produces an error:
Input port alias 'kb' already connectedDisconnect the existing alias first if you need to reuse it.
Routing Input to Tracks
Section titled “Routing Input to Tracks”All channels
Section titled “All channels”.input(alias) routes all MIDI channels from the named input to the track:
let keys = MidiTrack(1, 100).input("iac");Specific channel
Section titled “Specific channel”.input(alias, channel) filters to a single MIDI channel:
let bass = MidiTrack(2, 100).input("iac", 1);All connected ports
Section titled “All connected ports”The special alias "all" listens to every connected input port:
let omni = MidiTrack(3, 80).input("all");Monitor Modes
Section titled “Monitor Modes”Monitor mode controls 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 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 MIDI events are forwarded to the track’s output port. Several things happen during thru-play:
Channel remapping
Section titled “Channel remapping”Incoming MIDI is remapped to the track’s output channel. If your keyboard sends on channel 1 but the track outputs on channel 5, the forwarded notes arrive on channel 5. This keeps thru-play consistent with pattern output.
Feedback detection
Section titled “Feedback detection”If an input port and output port form a loop (for example, both connected to the same IAC bus), events can circulate endlessly. Resonon detects this automatically: if thru-play exceeds the rate limit, it is temporarily suppressed. You will see a warning in the console:
[resonon] MIDI feedback loop detected on ch1 — thru-play suppressed (will retry after 1000ms cooldown)After a brief cooldown, thru-play re-enables automatically. To avoid feedback loops, use separate ports for input and output, or set monitor mode to "off".
CC value capture
Section titled “CC value capture”Incoming CC messages are automatically captured and made available as Cc() signal sources. For example, if your controller sends CC 1 (mod wheel), that value is immediately usable in patterns and modulation. See MIDI CC for details on using CC signals.
Full Chain
Section titled “Full Chain”The track methods return the track, so you can chain .input(), .monitor(), and .output() in a single expression for a complete input-to-output routing:
let thru = MidiTrack(1, 100) .input("iac") .monitor("in") .output("iac");This creates a track that receives from the "iac" input, monitors everything, and sends to the "iac" output.
MIDI Clock & Timing
Section titled “MIDI Clock & Timing”Clock Send (Master Mode)
Section titled “Clock Send (Master Mode)”midi_clock_send(true) enables 24 PPQ clock output to all connected MIDI output ports. Resonon becomes the clock master, sending Start, Stop, and Continue transport messages alongside clock ticks.
midi_connect("IAC Driver Bus 1", "daw");midi_clock_send(true);setbpm(120);PLAY;Disable with midi_clock_send(false).
Clock send and clock follow are mutually exclusive — enabling one disables the other.
Clock Follow (Follower Mode)
Section titled “Clock Follow (Follower Mode)”midi_clock_follow(alias) syncs Resonon’s tempo and transport to an external MIDI clock source arriving on the named input port:
midi_input_connect("DAW Output", "daw");midi_clock_follow("daw");When clock follow is active:
- Tempo is derived by averaging 24 ticks (one quarter note)
- Start resets playback to the beginning
- Stop pauses playback
- Continue resumes from the current position
- A soft PLL applies phase correction (up to ~1ms per quarter note) to stay tightly locked
Disable clock follow by passing false:
midi_clock_follow(false);MIDI Output Delay
Section titled “MIDI Output Delay”midi_delay(ms) adds a global delay (in milliseconds) to all outgoing MIDI — notes and clock. This is useful for compensating audio output latency so that MIDI and audio stay aligned.
midi_delay(15); // delay all MIDI output by 15msPRINT midi_delay(); // getter: prints 15Clock Offset
Section titled “Clock Offset”midi_clock_offset(ms) adds an offset (in milliseconds) applied only to clock ticks. Positive values delay ticks, negative values send them earlier.
midi_clock_offset(-5); // send clock ticks 5ms earlyPRINT midi_clock_offset(); // getter: prints -5Clock offset is independent of midi_delay(). The total delay on clock ticks is midi_delay() + midi_clock_offset():
midi_delay(15); // all MIDI delayed 15msmidi_clock_offset(-5); // clock ticks shifted 5ms earlier// Effective clock delay: 15 + (-5) = 10ms// Effective note delay: 15msClock & Timing Summary
Section titled “Clock & Timing Summary”| Function | Description |
|---|---|
midi_clock_send(true) | Enable 24 PPQ clock output (master mode) |
midi_clock_send(false) | Disable clock output |
midi_clock_follow(alias) | Sync to external clock on input port |
midi_clock_follow(false) | Disable clock follow |
midi_delay(ms) | Set global MIDI output delay (notes + clock) |
midi_delay() | Get current output delay in ms |
midi_clock_offset(ms) | Set clock-only offset (positive = later, negative = earlier) |
midi_clock_offset() | Get current clock offset in ms |
Inspecting Routing
Section titled “Inspecting Routing”midi_routing() prints a summary of all MIDI connections, active slots, and clock state:
midi_routing();Example output:
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")In master mode, the clock section shows the send state:
Clock: mode: master (sending)If nothing is connected, it shows (no MIDI connections).
Disconnecting
Section titled “Disconnecting”midi_input_disconnect(alias) closes the named input connection:
midi_input_disconnect("iac");On disconnect, Resonon sends All Notes Off (CC 123) to any output ports that were receiving thru-play from the disconnected input. This prevents stuck notes when you unplug or disconnect a controller mid-performance.
Complete Example
Section titled “Complete Example”A full workflow: list ports, connect with substring matching, create a track with input and monitoring, inspect routing, play, and clean up:
// List available portsPRINT midi_input_ports();PRINT midi_ports();
// Connect ports (substring match)midi_input_connect("KeyStep", "kb");midi_connect("IAC Driver Bus 1", "daw");
// Create a pass-through tracklet keys = MidiTrack(1, 100) .input("kb") .monitor("in") .output("daw");
// Check the routingmidi_routing();
PLAY;
// ... play your MIDI keyboard ...
PAUSE;
// Clean upmidi_input_disconnect("kb");midi_disconnect("daw");Debugging and Introspection
Section titled “Debugging and Introspection”Common errors
Section titled “Common errors”| Error | Cause | Fix |
|---|---|---|
Input port alias '...' already connected | Alias already in use | Disconnect the alias first, or choose a different alias |
No MIDI input connected with alias '...' | Clock follow with unrecognized alias | Connect the input port with midi_input_connect() first |
| Port not found | Port name not recognized | Check midi_input_ports() for available names |
MIDI feedback loop detected on ch... | Thru-play forming a loop | Use separate ports for input and output, or set monitor to "off" |
Introspection calls
Section titled “Introspection calls”// List available input portsPRINT midi_input_ports();
// Show full MIDI routing statemidi_routing();See Also
Section titled “See Also”- MIDI Tracks — send patterns to MIDI outputs
- MIDI CC — use CC messages as modulation sources
- MIDI Setup — configure MIDI ports
Read MIDI Control Change (CC) values as signals and send CC automation to external devices.
CC Input
Section titled “CC Input”Reading CC Values
Section titled “Reading CC Values”Any channel
Section titled “Any channel”Cc(cc_num) creates a signal that reads the given CC number from any MIDI channel. The signal outputs a value from 0.0 to 1.0:
let cutoff_signal = Cc(74);Specific channel
Section titled “Specific channel”Cc(channel, cc_num) reads from a specific MIDI channel (0—15):
let cutoff_signal = Cc(0, 74);Both forms require an active MIDI input connection (see MIDI Input).
Channel behavior
Section titled “Channel behavior”Cc(cc_num) reads the last CC value received on any MIDI channel. This is convenient when you have a single controller and don’t care which channel it sends on.
Cc(channel, cc_num) isolates to a single channel (0—15). Use this when multiple controllers share the same CC numbers and you need to distinguish between them.
MIDI Learn
Section titled “MIDI Learn”Cc_learn() provides interactive MIDI learn. It blocks for up to 10 seconds, waiting for you to move a knob or fader:
- Displays
Waiting for CC input... (10s timeout)in the console - Once a CC message arrives, returns a signal bound to that exact channel and CC number
- Prints
Learned: Cc(channel, cc_num)— use this to hardcode the call later
let knob = Cc_learn();// Console: "Waiting for CC input... (10s timeout)"// Move a knob on your controller...// Console: "Learned: Cc(0, 74)"If no CC message is received within 10 seconds, an error is raised. An active MIDI input connection is required.
Workflow
Section titled “Workflow”Use Cc_learn() interactively to discover the right CC number, then replace it with a hardcoded Cc() call for your script:
// Step 1: discover the CC numberlet knob = Cc_learn();// Console: "Learned: Cc(0, 74)"
// Step 2: replace with hardcoded calllet knob = Cc(0, 74);Smoothing
Section titled “Smoothing”CC values arrive as discrete 7-bit steps (0—127). To eliminate zipper noise when modulating audio parameters, apply .smooth(time_ms):
let cutoff = Cc(74).smooth(50);The argument is the smoothing time in milliseconds. A one-pole lowpass filter interpolates between incoming values, producing a continuous signal.
Using CC Signals for Modulation
Section titled “Using CC Signals for Modulation”CC signals work anywhere a signal is accepted. Use << to route a signal into a parameter:
Filter cutoff
Section titled “Filter cutoff”midi_input_connect("USB Controller", "ctrl");
let synth = AudioTrack("synth");synth.filter.param("Cutoff") << Cc(74).smooth(50);Expression pedal for volume
Section titled “Expression pedal for volume”let track = AudioTrack("keys");track.param("Gain") << Cc(11).smooth(20);Modulation wheel for effect depth
Section titled “Modulation wheel for effect depth”let track = AudioTrack("pad");track.reverb.param("Mix") << Cc(1).smooth(30);Common CC Numbers
Section titled “Common CC Numbers”| 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 |
Multiple CC Sources
Section titled “Multiple CC Sources”Use multiple CCs to control different parameters simultaneously:
midi_input_connect("USB Controller", "ctrl");
let synth = AudioTrack("synth");
// Mod wheel controls vibrato depthsynth.vibrato.param("Depth") << Cc(1).smooth(30).range(0, 1);
// CC 74 controls filter cutoffsynth.filter.param("Cutoff") << Cc(74).smooth(50).range(200, 8000);
// Expression pedal controls volumesynth.param("Gain") << Cc(11).smooth(20);Signal Methods
Section titled “Signal Methods”CC signals support all standard signal methods. See Signal Methods for the full list. Common ones:
| 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 |
Example — map a CC to a frequency range with linear scaling:
let filter_freq = Cc(74).smooth(50).range(200, 8000);For parameters that feel more natural on a logarithmic scale (like frequency), use .range_exp():
let filter_freq = Cc(74).smooth(50).range_exp(200, 8000);Thread Safety
Section titled “Thread Safety”CC values are stored atomically and updated from the MIDI input thread. They are safe to read from the audio thread without locks, making them suitable for real-time parameter control.
CC Output
Section titled “CC Output”Send MIDI CC messages to external synths and DAWs. Use .cc(number) on a MIDI track to create a CC output, then route a value, signal, or pattern into it with <<.
Signal-driven CC
Section titled “Signal-driven CC”Route an LFO or other signal to a CC parameter. The signal is sampled at ~30 Hz by default and sent as discrete CC messages with deduplication (identical consecutive values are skipped):
let synth = MidiTrack(1);synth << [C4 E4 G4 C5];
synth.cc(74) << Sine(0.5).range(0, 127);Pattern-driven CC
Section titled “Pattern-driven CC”Send discrete CC values on the beat grid:
let synth = MidiTrack(1);synth.cc(1) << [0 64 127 64];Static CC value
Section titled “Static CC value”Send a fixed value once per cycle:
let synth = MidiTrack(1);synth.cc(7) << 100;Multiple CCs
Section titled “Multiple CCs”Assign different sources to multiple CC parameters on the same track:
let synth = MidiTrack(1);synth << [C4 E4 G4];
synth.cc(74) << Sine(0.25).range(20, 100);synth.cc(1) << [0 32 64 127];synth.cc(7) << 100;Custom send rate
Section titled “Custom send rate”By default, signal-driven CC is sampled at ~30 Hz. Use .send_rate(hz) on the CC output to change the rate:
let synth = MidiTrack(1);synth.cc(74).send_rate(60) << lfo;Higher rates produce smoother automation but use more MIDI bandwidth. Standard DIN MIDI supports about 1000 three-byte messages per second; USB MIDI is much faster.
Storing CC outputs
Section titled “Storing CC outputs”The CC output is a value that can be stored in a variable:
let synth = MidiTrack(1);let cutoff = synth.cc(74);cutoff << Sine(0.5).range(0, 127);See Also
Section titled “See Also”- MIDI Input — connect MIDI input devices
- MIDI Tracks — send patterns to MIDI outputs
- Signal Methods — full signal method reference