Logic panel, Script
The Logic panel is where the show's behaviour lives. Open the Logic view from the top toolbar. The right side of the screen is Monaco (VS Code's editor in a browser) with TypeScript IntelliSense and hover docs against Stagewright's runtime API.
Scripts run inside the engine sidecar on a sandboxed QuickJS runtime. They survive rack switches, project loads, and engine restarts. A runaway loop can't take the engine down, the sandbox terminates the script if it spins for longer than the configured tick budget.
One script per project
Stagewright follows the one script per project model: a single TypeScript file, project-scoped, hooks into everything. There's no folder of separate scripts to manage. Multi-file authoring is mentioned on the corporate product page but lives behind the v2 roadmap, see Roadmap.
What scripts can do
- React to incoming MIDI (CCs, notes, program change).
- Drive Stagewright widgets by name (
widget('PIANO_VOL').set(0.7)). - Send MIDI out to hardware controllers (LED feedback on an AKAI MIDI Mix, for example).
- React to rack / variation changes (
onRackChange,onVariation). - Schedule work on timers (
onTimer(50, () => ...)). - Persist small values across reloads (key-value store, project- scoped).
What scripts cannot do
- Make audio. Audio runs entirely through the wired graph; the script can change parameters but never produces samples.
- Block the audio thread. Hooks run on the message thread; the audio path is isolated.
- Access the file system, the network,
eval, or any host API outside the sandbox's documented surface. The runtime exposes only the bindings listed in the Scripting reference.
A tiny script
// AKAI MIDI Mix: 8 mute buttons in a row.
// CC 22..29 toggle widgets MUTE_1..MUTE_8.
const akai = midi.input('MIDI Mix');
const mutes = [1, 2, 3, 4, 5, 6, 7, 8].map((i) => widget(`MUTE_${i}`));
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();
});Three things to notice:
- Hardware is addressed by logical name (
'MIDI Mix'), not by USB port. The project maps the logical name to whatever's plugged in. - Widgets are addressable by their name (the user-typed
namein the inspector, NOT the label). Names must be unique per project; labels can repeat ("GAIN" on every strip). - Press / release semantics are explicit. AKAI sends 127 on press, 0 on release, the script picks which it cares about.
Hot reload, dirty state, save
The Logic editor saves your script with the project (Cmd-S). A dirty editor shows the standard "unsaved" dot in the tab. Stagewright hot-reloads the script on every save, no need to restart the engine. The script keeps its persistent KV store across reloads but loses ephemeral local variables (that's the point of hot reload).
To bring up the API:
widget(name),widgets(), the Widgets API.midi.input(name),midi.output(name), the MIDI API.onInit,onCC,onProgramChange,onTimer,onRackChange,onVariation,onSongPart, the Hooks reference.- A growing list of recipes in Recipes & patterns.
Errors and the diagnostics panel
Script errors surface in two places:
- Inline in Monaco as red squiggles (TypeScript) or as a script- runtime error overlay (QuickJS exceptions).
- In the Diagnostics panel at the bottom of the app, on the Engine log tab. Filter the log by typing the script name or by clicking the
scripttag.
Use console.log(...) from the script, output lands in the engine log under the script: prefix.
Going further
Start with Scripting overview, then drill into the specific APIs you need.