Skip to content

Recipes & patterns

Real-world script patterns pulled from working rigs. Copy-paste, adapt to your hardware, ship. Every example uses only the runtime API, no placeholder helpers.

AKAI MIDI Mix: mute / solo / rec with LED feedback

The MIDI Mix has three rows of 8 latching buttons (mute / solo / rec). The widget grid in the Stage view mirrors that with MUTE_1..MUTE_8, SOLO_1..SOLO_8, REC_1..REC_8.

typescript
// Hardware layout (channel 1 throughout):
//   Per column N (0..7):
//     mute LED note = 1 + N*3   (1, 4, 7, ...)
//     solo LED note = 2 + N*3   (2, 5, 8, ...)
//     rec  LED note = 3 + N*3   (3, 6, 9, ...)
//   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(`REC_${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(
  ([ws, kind]) => {
    ws.forEach((w, i) => w.on('change', (v) => setLed(noteFor(i, kind), v > 0.5)));
  },
);

// LED sync at boot and on every rack activation (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);
onActivate(syncLeds);

// Incoming CCs: a press (value 127) toggles the matching widget.
// Release (value 0) is ignored, these are latching.
const toggle = (w: SwWidget) => w.set(w.get() > 0.5 ? 0 : 1);

akai.on('cc', (m) => {
  if (m.value !== 127) return;
  const col = m.controller - 22;
  if (col < 0 || col > 7) return;
  if (m.channel === 1) toggle(mute[col]);
  else if (m.channel === 2) toggle(solo[col]);
  else if (m.channel === 3) toggle(rec[col]);
});

Source switch (audio vs MIDI) with one toggle

A toggle widget named PIANO_SRC flips between the Roland's analog audio and its MIDI sound on the same panel.

typescript
const src       = widget('PIANO_SRC');
const audioMute = widget('PIANO_AUDIO_MUTE'); // mixer strip 1 mute
const midiMute  = widget('PIANO_MIDI_MUTE');  // mixer strip 2 mute

const apply = (toMidi: boolean) => {
  // toMidi = true means MIDI source live -> audio strip muted.
  audioMute.set(toMidi ? 1 : 0);
  midiMute.set(toMidi ? 0 : 1);
};

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 Stage → Groups. This is the single-toggle alternative.

setInterval fires at a fixed period, so when the period depends on a value that changes, chain setTimeout and recompute each tick:

typescript
const bpm = () => widget('BPM').get() * 200 + 40; // maps 0..1 -> 40..240 BPM
const led = widget('BEAT_LED');

let on = false;
function tick() {
  on = !on;
  led.set(on ? 1 : 0);
  const periodMs = 60_000 / bpm() / 2; // half-beat = visible flash
  setTimeout(tick, periodMs);
}
onInit(tick);

Hardware knob → multiple plugin parameters

typescript
// "Macro" knob on MIDI Mix CC 14, channel 1.
const akai = midi.input('MIDI Mix');
const reverbMix = widget('REVERB_MIX');
const delayMix  = widget('DELAY_MIX');
const cabMix    = widget('CAB_MIX');

akai.on('cc', (m) => {
  if (m.channel !== 1 || m.controller !== 14) return;
  const v = m.normalized;      // 0..1 already
  reverbMix.set(v);
  delayMix.set(v * 0.7);
  cabMix.set(1 - v * 0.5);
});

For a flat ganged value with no curves, put all three widgets in the same control group 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 scene widgets 1 → 2 → 3 → 1.

typescript
const foot = midi.input('Foot Controller');
const scenes = [widget('SCENE_1'), widget('SCENE_2'), widget('SCENE_3')];

let i = 0;
foot.on('note', (m) => {
  if (!m.on || m.note !== 60) return;         // C4 press = advance
  scenes.forEach((s, j) => s.set(j === i ? 1 : 0));
  i = (i + 1) % scenes.length;
});

onInit(() => scenes.forEach((s, j) => s.set(j === 0 ? 1 : 0)));

Radio group and bidirectional sync in one line

The stdlib helpers cover the two most common gestures without hand- rolling guards:

typescript
// Turning one scene on sets the others to 0.
exclusive([widget('SCENE_1'), widget('SCENE_2'), widget('SCENE_3')]);

// Keep two widgets mirrored (a stage fader and its FOH twin).
bind(widget('MASTER'), widget('MASTER_FOH'));

Logging only when something interesting happens

typescript
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 the interesting lines get lost in 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.

Proprietary software, used under the Stagewright Software Licence.