Middleware daemon and GUI for DiGiCo S21/S31 mixing consoles. Sits between the console and your workflow tools to add snapshot/cue management, macros, channel palettes, smart ganging, pan link, personal monitoring, and QLab integration — features the console doesn't natively provide.
Communicates with the console over GP OSC (the documented open protocol) and optionally the iPad protocol (reverse-engineered) for parameters that GP OSC doesn't cover.
- Capture the live console state as a named snapshot, scoped to specific channels and parameter sections
- Organize snapshots into a numbered cue list with Go Next / Go Previous / Fire by number
- Scope templates — reusable channel/section selections for consistent captures
- Fade interpolation — continuous parameters (faders, gains, EQ) crossfade over a configurable time; discrete parameters (mute, solo) fire instantly at the start
- One-level undo — every recall stashes the prior live values so an accidental fire can be rolled back
- Inter-message pacing — configurable microsecond delay between sends during large recalls to avoid flooding the console's ARM chip
- Auto-save on recall — optional: when advancing to a new snapshot, dirty parameters within the previous snapshot's scope template are automatically written back into it. Mid-show tweaks ride along without manual re-capture
- Console memory link — each app snapshot can optionally reference a console memory row. On recall, the daemon fires the console's native memory load via the iPad protocol, waits for the console echo flood to settle (~250 ms), then writes the app's captured parameters on top. State-aware dedup means re-recalling the same snapshot does not re-fire the console memory
- Follow mode — when the operator drives the console's own snapshot list, the daemon mirrors the change by recalling the first matching app snapshot in cue order. Self-fired echoes are suppressed so there is no feedback loop
- Renumbering helper — bulk-shift every snapshot's console memory reference after inserting or deleting a row on the console (rows are positional; inserts shift everything below)
- Top-bar toggle that suspends all OSC traffic in both directions: outbound sends are dropped before they hit the socket, inbound packets are dropped before parsing, the state mirror freezes, gangs and pan link stop propagating
- Lets the operator edit show data — snapshots, scopes, palettes, gangs, pan links, console memory refs — without touching the live desk
- A persistent banner across every tab makes the state unmistakable
- Defaults to OFF on every startup; not persisted
- Per-cell parameter scope editor with channel × parameter matrix, grouped by signal-flow section
- "Select modified" + auto-preselect — the dirty tracker watches every parameter that changes since the last recall so you can snapshot exactly what you've been touching
- Visual recall scope and per-channel recall safes that mirror the console's surface (visualization aid; the console doesn't expose these over OSC)
- Both round-trip in the show file
- Record a sequence of console parameter changes in learn mode
- Three step modes: Fixed (absolute value), Relative (delta from current), Toggle (flip on/off)
- Execute macros on demand or via OSC trigger
- Quick-trigger slots for frequently used macros
- Capture the EQ, Dynamics 1 (compressor), or Dynamics 2 (gate) section of a channel as a reusable palette
- Link palettes to snapshots — when a palette is updated, all linked snapshots inherit the new values on recall
- Useful for maintaining consistent processing across multiple scenes without re-saving every snapshot
- Link channels together with selective parameter sections (e.g. gang faders but not EQ)
- Two propagation modes: Relative (apply the delta to each member) and Absolute (snap members to the source value)
- Mixed channel types supported (e.g. inputs + auxes in the same gang); routing sections automatically restrict to same-type members
- Anti-feedback suppression prevents infinite loops from console echo-back
- Enable/disable gangs on the fly, or pause individual gangs without breaking them for mid-show edits
- Bind an input channel's main pan to selected stereo aux send pans — useful when an effects send needs to follow the channel pan automatically
- Per-input configuration; any number of stereo auxes can be linked to the same input
- Absolute, unidirectional propagation: moving the input pan snaps every linked aux send pan to the same value; moving an aux send pan manually acts as a temporary override and does not propagate back
- Adapts to the discovered console shape — only stereo aux buses are linkable, and the UI greys out mono auxes
- Skipped during snapshot/cue/macro recalls so memory recalls are never altered
- Bindings are persisted in the show file
- Define named monitor clients with permitted aux sends and visible input channels
- Performers connect via OSC from any device and can only adjust their own mix
- Permission validation — each client can only touch the auxes they're assigned to
- FOH changes automatically echo to connected clients
- A companion Flutter monitor app (
monitor_app/) ships alongside for Android/iOS performers — see its own README for build instructions
- Inbound (trigger listener): OSC trigger listener accepts
/cue/go,/cue/previous,/cue/fire,/cue/current,/macro/fire,/snapshot/recall, and/snapshot/recall_full. Drive cue recall from QLab, companion apps, Stream Deck, or any OSC sender — see the OSC Trigger Commands table for argument types. - Outbound (QLab cue export): build QLab cue lists from app snapshots — emits
/new+/cue/selected/*sequences that QLab consumes to create network cues (one per snapshot or per parameter), grouped under a parent cue. Each network cue fires/snapshot/recallback at the daemon. Useful for building a QLab show that drives the console mix without re-keying snapshot names.
- Mode 1: GP OSC only (default)
- Mode 2: Direct iPad protocol connection to the console — accesses parameters that GP OSC doesn't expose (phantom power, insert slots, stereo mode, etc.)
- Mode 3: iPad proxy — sits between a real iPad and the console, forwarding traffic while maintaining the state mirror
- OSC Log tab: live tail of every inbound and outbound OSC message with filtering, useful for protocol debugging and reverse-engineering
- Inspector tab: searchable view of the full state mirror — every (channel, parameter) → value the daemon currently knows about
- Save/load the entire show (snapshots, cue list, macros, palettes, monitor clients, gang groups, pan links, recall safes, console memory refs, workflow toggles, console layout) as a single JSON file
- Console variant (S21 / S21+) and channel layout are restored on load, so offline editing reflects the saved console shape until you reconnect
- Backward-compatible — older show files load cleanly with sensible defaults
- Rust toolchain 1.85 or newer (pinned via
rust-versionin Cargo.toml — required by edition 2024). Install via rustup:curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - A DiGiCo S21 or S31 console on the same network, or use the included mock console for testing
git clone https://github.com/pob31/S21_HiJack.git
cd S21_HiJack
cargo build --releaseThe binary is at target/release/s21_hijack (Linux/macOS) or target/release/s21_hijack.exe (Windows).
cargo run --release -- --console-ip 192.168.1.1This opens the native desktop UI with tabs for Setup, Snapshots, Macros, Live, Gangs, Pan Link, Monitor, OSC Log, and Inspector.
cargo run --release -- --headless --console-ip 192.168.1.1Runs without a GUI — useful for Raspberry Pi or server deployments. All features are available via the OSC trigger interface and show files.
A mock console simulator is included:
# Terminal 1 — start the mock console
cargo run --bin mock_console -- --port 8000
# Terminal 2 — connect to it
cargo run --release -- --console-ip 127.0.0.1 --console-port 8000| Argument | Default | Description |
|---|---|---|
--console-ip |
192.168.1.1 |
Console IP address |
--console-port |
8000 |
Console GP OSC port |
--local-port |
8001 |
Local UDP port to bind |
--trigger-port |
53001 |
QLab/OSC trigger listener port |
--mode |
mode1 |
Operating mode: mode1, mode2, mode3 |
--ipad-send-port |
— | Console's iPad protocol port (send target) |
--ipad-receive-port |
— | Local port to receive iPad protocol messages |
--ipad-port |
0 |
Legacy: single port for both send/receive |
--ipad-ip |
— | iPad device IP (for Mode 3 proxy) |
--monitor-port |
0 |
Personal monitor server port (0 = disabled) |
--headless |
false |
Run without the GUI |
Send these to the trigger port (default 53001) from QLab, Stream Deck, or any OSC source:
| OSC Path | Args | Action |
|---|---|---|
/cue/go |
— | Advance to next cue and recall |
/cue/previous |
— | Go back to previous cue and recall |
/cue/fire |
int or float | Fire a specific cue by number |
/cue/current |
— | Query current cue number (replies to sender on /cue/current) |
/macro/fire |
string or int | Execute a macro by name or ID |
/snapshot/recall |
string | Recall a snapshot by name or UUID, honouring its scope |
/snapshot/recall_full |
string | Recall a snapshot by name or UUID, ignoring its scope (full state) |
Performers connect to the monitor port with these messages. {name} is the client's configured monitor profile name.
| OSC Path | Args | Action |
|---|---|---|
/monitor/discover |
— | Broadcast discovery — server replies with its address |
/monitor/{name}/connect |
— | Register/reconnect a client |
/monitor/{name}/state |
— | Request full current state for this client |
/monitor/{name}/send/{input}/{aux}/level |
float | Set send level (input → aux) |
/monitor/{name}/send/{input}/{aux}/pan |
float | Set send pan (input → aux) |
/monitor/{name}/send/{input}/{aux}/on |
bool / int (0/1) | Set send on/off |
/monitor/{name}/aux/{n}/fader |
float | Set the aux master fader |
/monitor/{name}/aux/{n}/mute |
bool / int (0/1) | Mute/unmute the aux master |
/status/console |
— | Query console connection status (replies to sender) |
/status/clients |
— | Query connected client count (replies to sender) |
Permission validation: each client can only touch the auxes assigned to its profile and the inputs the profile makes visible.
src/
main.rs Entry point (CLI args, headless/UI modes)
model/ Data model — channels, parameters, snapshots, macros,
gangs, pan_link, palettes, recall_scope, dirty_tracker,
config (incl. PlusMode), monitor profiles
osc/ OSC protocol — GP OSC + iPad encode/parse/client,
trigger listener, monitor server
console/ Business logic — connection manager, snapshot/macro/
monitor/gang/pan_link engines, fade controller,
palette manager, iPad handshake & proxy
persistence/ Show file save/load (versioned, backward-compatible)
ui/ Native egui desktop UI (one file per tab + scope editor)
bin/mock_console.rs Mock console simulator for testing without hardware
Documentation/
PRD.md Full product requirements document
GP_OSC_help.jpeg GP OSC protocol reference image
iPad_commands.png, iPad_handshake.txt Reverse-engineered iPad protocol notes
DiGiCo S OSC Commandset_*.csv Console parameter address tables
cargo test346 tests covering the data model, OSC protocol parsing/encoding, engine logic, persistence backward compatibility, and UI parsing utilities. Continuous integration runs cargo check, cargo test, cargo fmt --check, cargo clippy, and cargo audit on a Linux/macOS/Windows matrix on every push and PR — see .github/workflows/ci.yml.
- Raspberry Pi (Linux ARM) — primary deployment target, runs headless as a daemon
- macOS / Windows / Linux desktop — development and GUI use
- Cross-compile for Pi with
cross build --release --target armv7-unknown-linux-gnueabihfor build natively on the Pi - Linux desktop integration (icon, .desktop entry) is provided by
install-linux.sh
Dual-licensed under either of:
- MIT License — see LICENSE-MIT
- Apache License, Version 2.0 — see LICENSE-APACHE
at your option. This is the standard Rust-ecosystem dual-license — pick whichever fits your downstream needs.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work shall be dual-licensed as above, without any additional terms or conditions.