Skip to content

Hooks

The script reacts to events through hook functions registered at top level. All hooks live as global functions; calling them multiple times registers multiple handlers (they don't replace).

HookWhen it fires
onInit(fn)Once at script load / engine ready; once again on every hot reload
onCC(fn)Every CC from any midi-in (use midi.input(name).on('cc', ...) to filter)
onProgramChange(fn)Every program change from any midi-in
onTimer(ms, fn)Every ms milliseconds; minimum 10 ms
onRackChange(fn)Active rack switches (manual or programmatic)
onVariation(fn)Variation switches within a rack
onSongPart(fn)Song-part advance (v2, not in v1)
onShutdown(fn)Engine is about to stop; last chance to flush state

onInit(fn)

Runs once when the script is first parsed AND once on every hot reload. Use it to:

  • Compute and cache widget handles.
  • Push initial LED feedback to controllers.
  • Restore state from the per-project KV store.
typescript
onInit(() => {
  console.log('script ready');
  refreshAllLeds();
});

Top-level statements ALSO run at load time. onInit is for code that needs to assume widgets / midi devices are bound (some are not available before the engine has hydrated the project).

onCC / onProgramChange / per-device handlers

There are TWO ways to listen for CCs:

  • Global: onCC((m) => ...) fires for every CC from every midi-in. The event has a device field with the source name.
  • Per-device: midi.input(name).on('cc', (m) => ...) fires only for that midi-in. Same event shape minus the device field.

Prefer per-device unless your script genuinely treats every CC the same, it's clearer and a touch faster (no name comparison per event).

typescript
// Global: log everything for debugging.
onCC((m) => console.log(`${m.device}: cc${m.controller}=${m.value}`));

// Per-device: act on AKAI MIDI Mix CCs specifically.
midi.input('MIDI Mix').on('cc', (m) => { /* ... */ });

onTimer(ms, fn)

typescript
onTimer(500, () => {
  console.log(`tick, ${new Date().toISOString()}`);
});

Timers fire on the message thread; the same ~50 ms tick budget applies. They survive across rack switches but are cancelled when the script reloads.

Return false (or call the supplied stop()) to cancel:

typescript
let n = 0;
onTimer(1000, ({ stop }) => {
  console.log(`heartbeat ${++n}`);
  if (n >= 10) stop();
});

onRackChange(fn) and onVariation(fn)

Fire when the user (or another script call) switches the active rack or variation.

typescript
onRackChange((rack) => {
  console.log(`switched to rack: ${rack.name}`);
  refreshAllLeds();
});

onVariation((v) => {
  // v.id, v.name, v.rackId
});

Use onRackChange to:

  • Re-emit LED feedback to controllers (LEDs reset on each rack).
  • Reset any state your script holds that's rack-specific.
  • Log into the diagnostics panel so future-you knows why a setting changed.

onSongPart(fn), v2

Not in v1

Songs / Song Parts / Setlists are advertised on the corporate page but not in code. The hook exists on the typings as a placeholder; the engine won't ever fire it in v1. See Roadmap.

typescript
// v2 only:
onSongPart((part) => {
  widget('PART_LABEL').setLabel(part.name);
});

onShutdown(fn)

Last call before the engine stops. Use it to flush LEDs to off, write final state to kv, or close hardware-specific connections.

typescript
onShutdown(() => {
  // dark every LED so the controller doesn't sit lit between sessions
  for (let i = 0; i < 24; i++) out.sendNoteOn(1, 1 + i, 0);
});

Has a 100 ms hard timeout, if your shutdown work hasn't finished by then, the engine exits anyway. Keep it small.

Persistent state: kv

typescript
kv.get(key: string): unknown | null
kv.set(key: string, value: unknown): void
kv.delete(key: string): void
kv.keys(): string[]

JSON-serialisable values only. Persists with the project file. 4 KB total cap per project, for richer state, build it into the project itself.

typescript
onInit(() => {
  const lastTempo = kv.get('tempo') as number | null;
  if (lastTempo != null) widget('BPM').set(lastTempo);
});

widget('BPM').on('change', (v) => kv.set('tempo', v));

Next: Recipes & patterns.

Proprietary software, used under the Stagewright Software Licence.