Recipes & patterns
Real-world script patterns pulled from working rigs. Copy-paste, adapt to your hardware, ship.
AKAI MIDI Mix, full mute / solo / rec with LED feedback
The MIDI Mix has three rows of 8 latching buttons (mute / solo / rec) and a knob / fader column per channel. The widget grid on the front panel mirrors that with MUTE_1..MUTE_8, SOLO_1..SOLO_8, BPS_1..BPS_8.
// Hardware layout (channel 1 throughout):
// Per column N (1..8):
// mute LED note = 1 + (N-1)*3 (1, 4, 7, ..., 22)
// solo LED note = 2 + (N-1)*3 (2, 5, 8, ..., 23)
// rec LED note = 3 + (N-1)*3 (3, 6, 9, ..., 24)
// Mute/Solo/Rec BUTTONS send CCs on channels 1/2/3 respectively,
// controller 22..29 = columns 1..8 (AKAI MIDI Mix editor remap).
const akai = midi.input('MIDI Mix');
const out = midi.output('MIDI Mix');
const mute = [1, 2, 3, 4, 5, 6, 7, 8].map((i) => widget(`MUTE_${i}`));
const solo = [1, 2, 3, 4, 5, 6, 7, 8].map((i) => widget(`SOLO_${i}`));
const rec = [1, 2, 3, 4, 5, 6, 7, 8].map((i) => widget(`BPS_${i}`));
const ROW_BASE = { mute: 1, solo: 2, rec: 3 } as const;
const noteFor = (col: number, kind: keyof typeof ROW_BASE) =>
ROW_BASE[kind] + col * 3;
const setLed = (n: number, on: boolean) =>
out.sendNoteOn(1, n, on ? 127 : 0);
// Widget → LED for every column. Same template across rows.
([
[mute, 'mute'],
[solo, 'solo'],
[rec, 'rec'],
] as const).forEach(([widgets, kind]) => {
widgets.forEach((w, i) =>
w.on('change', (v) => setLed(noteFor(i, kind), v > 0.5)),
);
});
// LED sync at boot + on rack change (LEDs reset between racks).
const syncLeds = () => {
([
['mute', mute],
['solo', solo],
['rec', rec],
] as const).forEach(([kind, ws]) =>
ws.forEach((w, i) => setLed(noteFor(i, kind), w.get() > 0.5)),
);
};
onInit(syncLeds);
onRackChange(syncLeds);
// Incoming CCs: button press (value = 127) toggles the matching
// widget. Release (value = 0) is ignored, these are latching.
akai.on('cc', (m) => {
if (m.value !== 127) return;
const col = m.controller - 22;
if (col < 0 || col > 7) return;
if (m.channel === 1) mute[col]?.toggle();
else if (m.channel === 2) solo[col]?.toggle();
else if (m.channel === 3) rec[col]?.toggle();
});Source switch (audio vs MIDI) with one toggle + icons
A toggle widget named PIANO_SRC with iconOn = piano-keys and iconOff = speaker-high from the inspector, flips between the Roland's analog audio and its MIDI sound on the same panel.
const src = widget('PIANO_SRC');
const audioMute = widget('PIANO_AUDIO_MUTE'); // bound to mixer strip 1 Mute
const midiMute = widget('PIANO_MIDI_MUTE'); // bound to mixer strip 2 Mute
const apply = (toMidi: boolean) => {
// toMidi = true means MIDI source live → audio strip muted.
audioMute.set(toMidi);
midiMute.set(!toMidi);
};
src.on('change', (v) => apply(v > 0.5));
onInit(() => apply(src.get() > 0.5));(For TWO toggles in a radio group with no script, see Front panel → Groups. This script form is the single-toggle alternative.)
Tempo-relative timer (blink an LED on the beat)
const bpm = () => widget('BPM').get() * 200 + 40; // 40..240 BPM mapping
const led = widget('BEAT_LED');
let on = false;
function tick() {
on = !on;
led.set(on);
const periodMs = 60_000 / bpm() / 2; // half-beat = visible flash
setTimeout(tick, periodMs);
}
onInit(tick);(Prefer this hand-rolled setTimeout chain over onTimer(ms, ...) when the period depends on a value that changes, onTimer ms is fixed at registration.)
Hardware knob → multiple plugin parameters
// "Macro" knob on MIDI Mix CC 14 channel 1.
const akai = midi.input('MIDI Mix');
const reverbMix = widget('REVERB_MIX'); // bound to plugin A
const delayMix = widget('DELAY_MIX'); // bound to plugin B
const cabMix = widget('CAB_MIX'); // bound to plugin C
akai.on('cc', (m) => {
if (m.channel !== 1 || m.controller !== 14) return;
const v = m.value / 127;
reverbMix.set(v);
delayMix.set(v * 0.7);
cabMix.set(1 - v * 0.5);
});(For a flat ganged value with no curves, set all three widgets to the same controlGroupId in the inspector, no script needed. Use a script when each target wants its own scaling.)
Foot-controller scene cycling
Stomp a foot-controller pedal to cycle scenes 1 → 2 → 3 → 1.
const foot = midi.input('Foot Controller');
const scenes = [widget('SCENE_1'), widget('SCENE_2'), widget('SCENE_3')];
let i = 0;
foot.on('note-on', (m) => {
if (m.note !== 60) return; // C4 = bank-up
scenes.forEach((s, j) => s.set(j === i));
i = (i + 1) % scenes.length;
});
onInit(() => scenes.forEach((s, j) => s.set(j === 0)));(Or use a radio group on all three scene widgets and hardware-MIDI- learn each scene to a different foot pedal, no script needed for exclusivity.)
A "ready" LED
const out = midi.output('Foot Controller');
onInit(() => out.sendCC(1, 100, 127)); // ready LED on
onShutdown(() => out.sendCC(1, 100, 0)); // ready LED offLogging only when something interesting happens
let last = -1;
widget('GAIN').on('change', (v) => {
const bucket = Math.round(v * 10);
if (bucket === last) return;
last = bucket;
console.log(`gain bucket ${bucket}`);
});(Don't console.log on every value change, the engine log fills up fast and you can't see the interesting lines through the noise.)
More to come
If you have a recipe you wish was documented here, open an issue on GitHub with the rig context, we'll add it.