The .swproj format
A Stagewright project file is plain JSON on your disk. Sync it with git, Dropbox, iCloud, or a USB stick, the file is the canonical project, no sidecar database, no cloud lockin.
Schema
{
"schemaVersion": 3,
"activeRackId": "rack-1",
"script": "// TypeScript source\n…",
"audio": {
"inputDeviceName": "Aggregate Device",
"outputDeviceName": "Aggregate Device",
"sampleRate": 192000,
"bufferSize": 192
},
"midi": {
"inputs": [
{ "nodeId": "midi-in", "deviceName": "MIDI Mix" },
{ "nodeId": "midi-in-2", "deviceName": "MPK mini" }
]
},
"racks": [
{
"id": "rack-1",
"name": "Live Rig",
"plugins": [...],
"meters": [...],
"connections": [...],
"positions": {...}, // UI-only: card x/y
"performer": {...}, // UI-only: widget layout
"canvasExtras": { // UI-only: mixer strip labels, etc.
"mixerStripLabels": {
"plugin-2": ["Piano", "Guitar L", "Guitar R", "Drums"]
}
}
}
],
"bypassGates": [...] // Boolean-control → plugin bypass wires
}Schema versioning
schemaVersion: 3 is the current. The engine loader handles older versions transparently:
- v1: flat plugins + connections, no rack concept. Migrated to a single default rack.
- v2: introduced racks but no
canvasExtras. Loads cleanly; new cosmetic state defaults to empty. - v3 (current): adds
canvasExtrasround-trip.
A loaded project is always re-saved at the current version when the user hits Cmd-S. Editing the JSON in a text editor and reopening it is safe, Stagewright validates the schema and warns about unknown fields.
What's in each rack
plugins[], list of{ nodeId, identifier, bypassed, state }.identifieris the engine's stable plugin descriptor id ("AudioUnit-Tuner-v1.0" etc.);stateis the plugin's own serialised state (base64-encoded). Renames live inpositions[nodeId].name(UI-only) so the engine identifier stays stable.meters[], vumeter / true-peak / utility nodes that aren't full plugins. Same shape as plugins, narrower.connections[],{ fromNode, fromPort, toNode, toPort }wires.positions,{ nodeId: { x, y } }. Canvas layout.performer, the full front-panel layout (strips, widgets, bindings, groups). Opaque to the engine, round-trips verbatim.canvasExtras, back-canvas cosmetics that don't fit elsewhere (mixer strip labels, more to come). Same opaque round-trip pattern asperformer.
Bypass gates
bypassGates[] is project-scoped, not per-rack, because they wire arbitrary plugin pairs (a BooleanControl driving N plugins' bypass state). Each entry is { targetNodeId, sourceNodeId }.
Logical control names, hardware abstraction
The MIDI input cards' deviceName is the logical name; the engine resolves it to the matching macOS / Windows MIDI device at load time. If a device isn't connected, the card stays in the graph with a warning, re-plug the controller, click "Refresh devices" in the inspector, and the binding restores itself.
Editing externally
.swproj is plain JSON, open it in any editor, your IDE, git diff tools, whatever. Stagewright re-saves on Cmd-S so any external edits round-trip cleanly. The script field is a raw TypeScript string; some people prefer to edit the script in their external editor and re-paste, others prefer Monaco inside Stagewright, both work.