Skip to content

MIDI CC

Read MIDI Control Change (CC) values as signals and use them to modulate parameters in real time.

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);

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).

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.

Cc_learn() provides interactive MIDI learn. It blocks for up to 10 seconds, waiting for you to move a knob or fader:

  1. Displays Waiting for CC input... (10s timeout) in the console
  2. Once a CC message arrives, returns a signal bound to that exact channel and CC number
  3. 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.

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 number
let knob = Cc_learn();
// Console: "Learned: Cc(0, 74)"
// Step 2: replace with hardcoded call
let knob = Cc(0, 74);

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.

CC signals work anywhere a signal is accepted. Use << to route a signal into a parameter:

midi_input_connect("USB Controller", "ctrl");
let synth = AudioTrack("synth");
synth.filter.Cutoff << Cc(74).smooth(50);
let track = AudioTrack("keys");
track.Gain << Cc(11).smooth(20);
let track = AudioTrack("pad");
track.reverb.Mix << Cc(1).smooth(30);
CCNameTypical Use
1Mod WheelVibrato, filter modulation
7VolumeChannel volume
10PanStereo panning
11ExpressionDynamic volume
64SustainOn/off (≥64 = on)
74BrightnessFilter cutoff

Use multiple CCs to control different parameters simultaneously:

midi_input_connect("USB Controller", "ctrl");
let synth = AudioTrack("synth");
// Mod wheel controls vibrato depth
synth.vibrato.Depth << Cc(1).smooth(30).range(0, 1);
// CC 74 controls filter cutoff
synth.filter.Cutoff << Cc(74).smooth(50).range(200, 8000);
// Expression pedal controls volume
synth.Gain << Cc(11).smooth(20);

CC signals support all standard signal methods. See Signal Methods for the full list. Common ones:

MethodDescription
.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);

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.