Skip to content

Widgets API

Every front-panel widget is addressable from script by its name (the script identifier, NOT the user-visible label). Two top-level helpers expose them:

typescript
widget(name: string): WidgetHandle
widgets(): WidgetHandle[]   // every widget on every strip

widget(name) returns null when the name isn't known. If you expect the widget to always exist, assert at script load:

typescript
onInit(() => {
  const piano = widget('PIANO_VOL');
  if (!piano) throw new Error('script expects a PIANO_VOL widget');
});

WidgetHandle

typescript
interface WidgetHandle {
  // ── Reading ─────────────────────────────────────────────────────
  get(): number;                          // 0..1 for numeric, 0/1 for boolean
  type: WidgetType;                       // 'knob' | 'fader' | 'toggle' | …
  label: string;                          // user-visible label
  bindingKind: BindingKind | null;        // 'plugin-param' | 'plugin-bypass' | …

  // ── Writing ─────────────────────────────────────────────────────
  set(value: number | boolean): void;
  toggle(): void;                         // boolean kinds only; flips current value

  // ── Events ──────────────────────────────────────────────────────
  on(event: 'change', fn: (value: number, prev: number) => void): void;
  off(event: 'change', fn?: (value: number, prev: number) => void): void;

  // ── Mutating the widget itself (rare) ───────────────────────────
  setLabel(text: string): void;           // for song-driven labels
  setColor(hex: string): void;            // overrides the inspector colour
}

get() and set()

Continuous widgets (knob, fader, drawbar, expression-pedal): get() returns 0..1; set(v) accepts the same range. Linear in the UI sense, your widget's scaling curve still applies to what the binding sees (see Concepts → Scaling curves).

Boolean widgets (button, toggle, led, led-button, sustain-pedal): get() returns 0 or 1; set(true|false) accepts the matching type. toggle() flips the current value.

typescript
widget('GAIN').set(0.5);            // half
widget('MUTE_1').toggle();          // flip
const tempoOn = widget('TEMPO_LED').get(); // 0 | 1

on('change', fn)

Fires when the widget's value changes for ANY reason, mouse, MIDI, OSC, script writes to its bound parameter via another path. The callback receives the new value and the previous one.

typescript
const muteOne = widget('MUTE_1');
muteOne.on('change', (v) => {
  console.log(`MUTE_1 is now ${v >= 0.5 ? 'ON' : 'OFF'}`);
});

Loop guard

A handler that writes to its own widget (widget.set(...) from its on('change')) re-fires the same handler. The engine's widget bridge has a dedupe guard for exact-value writes, but a sloppy handler can still ping-pong. If you find yourself doing this, restructure, write to a DIFFERENT widget, or use onTimer to throttle.

bindingKind and binding-aware logic

When you want to know what a widget is bound to:

typescript
const w = widget('SCENE_A');
if (w.bindingKind === 'plugin-bypass') {
  // widget directly flips a plugin's bypass
} else if (w.bindingKind === 'plugin-param') {
  // widget writes a plugin parameter
}

Useful in generic-script patterns where one script handles many widgets and the binding decides behaviour.

Common patterns

Reading every widget on the panel

typescript
onInit(() => {
  for (const w of widgets()) {
    console.log(`${w.label || '(no label)'} → ${w.get()}`);
  }
});

A mute panel driven by hardware

typescript
const mutes = [1, 2, 3, 4, 5, 6, 7, 8].map((i) => widget(`MUTE_${i}`));
const akai = midi.input('MIDI Mix');

akai.on('cc', (m) => {
  if (m.value !== 127) return; // ignore release
  const i = m.controller - 22;
  if (i >= 0 && i < mutes.length) mutes[i].toggle();
});

LED feedback to the controller

typescript
const out = midi.output('MIDI Mix');

const mutes = [1, 2, 3, 4, 5, 6, 7, 8].map((i) => widget(`MUTE_${i}`));

const setLed = (i: number, on: boolean) =>
  out.sendNoteOn(1, 1 + i * 3, on ? 127 : 0);

mutes.forEach((w, i) => {
  w.on('change', (v) => setLed(i, v > 0.5));
});

onInit(() => {
  mutes.forEach((w, i) => setLed(i, w.get() > 0.5));
});

Source-switcher (single-toggle audio / MIDI source)

typescript
// One toggle named PIANO_SRC: ON = audio, OFF = MIDI.
// Mutes the matching strips on the Piano Channel Mixer.
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

src.on('change', (v) => {
  audioMute.set(v > 0.5);   // audio source → midi strip muted
  midiMute.set(v <= 0.5);   // midi source → audio strip muted
});

onInit(() => {
  audioMute.set(src.get() > 0.5);
  midiMute.set(src.get() <= 0.5);
});

(For a radio-group source switch using TWO toggles in the inspector's radio-group field, no script is needed, the radio- group cascade handles exclusivity automatically. See Front panel → Groups.)

Next: MIDI input & output.

Proprietary software, used under the Stagewright Software Licence.