v2: generic S-Series module with live discovery, dynamic options and meters
Rewrite of the module around what the console itself reports: - Settings cut to console address/ports, the iPad relay and debug logging; nothing site-specific remains. An upgrade script carries v1 configs and remaps v1 actions/feedbacks whose ids were reused. - On connect the module reads every name, buss type (aux/group), stereo mode, fader/mute/solo/pan and the snapshot list; entities that never answer are hidden. Definitions rebuild (debounced, skipped when identical) when names, buss types or snapshots change. - Every entity option is a searchable dropdown of live names. 70 actions cover inputs, aux sends, group assigns, busses, matrix outputs and sends, control groups, graphic EQs, channel-strip switches, trim/gain/filters and snapshots, each level with set and encoder-friendly adjust variants. - Boolean, value and bar feedbacks for every state, input level and gain-reduction meters via the console's meter subscription protocol, and about 2800 presets including rotary knob presets with live level text. - A write guard keeps fast encoder turns from being undone by delayed echoes; feedbacks are re-checked per instance. - Relay: the client table survives config changes, idle iPads are kept for an hour by default (0 = forever), bind failures retry and show in status. npm test: 326 checks pass on Node 25 and on Companion's Node 22. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+165
-68
@@ -1,96 +1,193 @@
|
||||
# DiGiCo S31 (S-Series) — Studio Mon control + iPad relay
|
||||
# DiGiCo S-Series (S21 / S31): control and iPad relay
|
||||
|
||||
This connection does two things:
|
||||
|
||||
1. **Controls which channels feed one buss** — by default the *Studio* buss — plus any aux send
|
||||
level/on/pan, channel mute/fader, buss master fader/mute and snapshot recall, with feedbacks,
|
||||
variables and ready-made presets.
|
||||
2. **Relays the DiGiCo iPad app.** An S-Series console talks to exactly one iPad at a time.
|
||||
Companion takes that slot; the iPad apps point at the Companion computer instead of the
|
||||
console. Everything an app sends is forwarded to the console unchanged and every console
|
||||
message is copied to every app seen recently, so several iPads can be used at once.
|
||||
|
||||
## What the console speaks (verified on "S31 AB", 2026-10-15)
|
||||
|
||||
The S31's **iPad** extension speaks the DiGiCo Pad / SD-style OSC tree over UDP:
|
||||
|
||||
| Address | Reply | Notes |
|
||||
| --- | --- | --- |
|
||||
| `/Console/Name/?` | `/Console/Name "S31 AB"` | used as keepalive |
|
||||
| `/Console/Input_Channels/?`, `/Console/Aux_Outputs/?` | int counts (60, 25) | 25 busses, each in aux *or* group mode |
|
||||
| `/Console/Aux_Outputs/modes/?` | 25 ints, 2 = stereo, 1 = mono | |
|
||||
| `/Input_Channels/N/Channel_Input/name/?` | string | |
|
||||
| `/Input_Channels/N/fader/?`, `/mute/?` | float dB (−150 = −∞), float 0/1 | |
|
||||
| `/Input_Channels/N/Aux_Send/M/send_level` | float dB, −150 = off | write a float |
|
||||
| `/Input_Channels/N/Aux_Send/M/send_on` | float 0/1 | write int 0/1 |
|
||||
| `/Input_Channels/N/Aux_Send/M/send_pan` | float 0..1, 0.5 centre | |
|
||||
| `/Input_Channels/N/Group_Send/M/group` | float 0/1 | group assign; write int 0/1 |
|
||||
| `/Aux_Outputs/M/Buss_Trim/name/?`, `/Group_Outputs/M/Buss_Trim/name/?` | string | a buss is named on one side only |
|
||||
| `/Aux_Outputs/M/fader`, `/mute` (and `/Group_Outputs/M/…`) | float | buss masters |
|
||||
| `/Snapshots/Current_Snapshot/?`, `/Snapshots/names/?` | int; `/Snapshots/name [i, cue×100, 0, name]` | |
|
||||
|
||||
A query is the address plus `/?` with no argument; the reply is the same address without `/?`.
|
||||
Every reply goes to the controller IP and **Send Port** configured on the console, never to the
|
||||
asking socket, which is why a fixed local port is bound. The lowercase S-Series *OSC Control*
|
||||
command list (`/channel/N/…`, `/console/resend`) is **not** answered on the iPad leg; it belongs
|
||||
to the separate *OSC Control* extension and is available here as an alternative command set.
|
||||
1. **Controls the console over OSC.** On connect it asks the console what it has (inputs, busses,
|
||||
matrix outputs, control groups, graphic EQs, snapshots) and builds its dropdowns, variables and
|
||||
presets from the live names. Faders, mutes, solos, pans, aux sends, group assigns, matrix sends,
|
||||
control groups, graphic EQs, per-channel switches (phantom, EQ in, filters, inserts ...) and
|
||||
snapshots are covered, with feedbacks, variables, rotary-encoder presets and input meters.
|
||||
2. **Relays the DiGiCo iPad app.** An S-Series console talks to exactly one iPad at a time. Companion
|
||||
takes that slot and the iPad apps connect to this computer instead; everything an app sends is
|
||||
forwarded to the console unchanged, and everything the console sends is copied to every app, so
|
||||
several iPads can be used together with Companion.
|
||||
|
||||
## Console setup (once, at the surface)
|
||||
|
||||
Main Menu → **Extensions → iPad**:
|
||||
Main Menu, **Extensions > iPad**:
|
||||
|
||||
| Console field | Value |
|
||||
| --- | --- |
|
||||
| iPad IP address | the Companion computer (AB-ProPresenter: `10.3.11.152`) |
|
||||
| Send port *(console → iPad)* | this connection's **Local receive port** (default `9000`) |
|
||||
| Receive port *(iPad → console)* | this connection's **Console receive port** (default `8000`) |
|
||||
| iPad connection enabled | yes |
|
||||
| iPad IP address | the computer running Companion |
|
||||
| Send port *(console to this computer)* | this connection's **Local receive port** (default `9000`) |
|
||||
| Receive port *(this computer to the console)* | this connection's **Console receive port** (default `8000`) |
|
||||
| iPad connection | enabled |
|
||||
|
||||
The console sends every reply and every change to that one IP address and Send port, never to the
|
||||
port a query came from. That is why the connection binds a fixed local port.
|
||||
|
||||
## iPad app setup
|
||||
|
||||
In the DiGiCo S app connect page, for every iPad:
|
||||
In the DiGiCo S app, for every iPad:
|
||||
|
||||
| iPad field | Value |
|
||||
| --- | --- |
|
||||
| Console IP | the Companion computer, **not** the console |
|
||||
| Console IP | the computer running Companion, **not** the console |
|
||||
| Send port | this connection's **Relay port** (default `8000`) |
|
||||
| Receive port | this connection's **iPad receive port** (default `9000`) |
|
||||
|
||||
An iPad that goes quiet for *Forget an iPad after* seconds is dropped from the fan-out list.
|
||||
`$(S31:relay_clients)` shows how many are live.
|
||||
An iPad that stays quiet for *Forget an iPad after* seconds (0 = never) is dropped from the fan-out list, and at most
|
||||
16 are kept. `$(label:relay_clients)` shows how many are live. Changing only the idle timeout, the iPad receive port or
|
||||
Debug logging does not interrupt the relay or the console link; changing the relay port reopens the relay and keeps the
|
||||
list of iPads. If the relay port is already in use the connection shows the warning "iPad relay: port N in use" and tries
|
||||
again every 10 seconds (a console-link port that cannot be bound shows a connection failure and is retried the same way). Other controllers share the console through the relay,
|
||||
and anything they change reaches this module too, so feedbacks follow moves made on the surface or on an iPad.
|
||||
|
||||
## Studio Mon buss
|
||||
## Settings
|
||||
|
||||
Set **Studio Mon buss number** (1-25, the console's buss order). The module reads the buss
|
||||
names and works out whether it is an **aux** (send level + on/off per channel) or a **group**
|
||||
(assign on/off per channel); override with *Studio Mon buss type* if needed. On this console:
|
||||
|
||||
| Buss | Aux name | Group name |
|
||||
| Setting | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| 1-8 | VOCAL VERB, VOCAL SLAP, TRACKS, CLK-GD-MD, VOCAL DELAY, VOCALS, TB, MC/SpkrBcastOut | |
|
||||
| 9-13 | | BAND, VOCALS, MISC, DRUMS, DRUM CRUSH |
|
||||
| 14-17 | DRUM PLATE, AG VERB, FOH Spkr, Studio TB | |
|
||||
| 18 | | Broadcast mix |
|
||||
| 19-21 | FX, Prop & Spot, Drum Room Verb | |
|
||||
| 23-25 | | CAFE, **Studio**, Master |
|
||||
| Console IP | | The console's address |
|
||||
| Console receive port | 8000 | UDP port the module sends to |
|
||||
| Local receive port | 9000 | UDP port the module listens on (the console's Send port) |
|
||||
| Enable iPad relay | on | Turns the relay on |
|
||||
| Relay port | 8000 | Where iPad apps send |
|
||||
| iPad receive port | 9000 | Where relayed console traffic goes (0 = the app's own source port) |
|
||||
| Forget an iPad after | 3600 s | Idle time before an iPad is dropped (0 = never; an iPad that only watches sends nothing). At most 16 iPads are kept; the least recently seen is dropped first |
|
||||
| Debug logging | off | Logs every new address pattern once and all traffic at debug level |
|
||||
|
||||
The default is buss **24 "Studio"** (stereo group). Presets under *Studio Mon* give one
|
||||
on/off button per input (green when assigned/on, label = channel name); for an aux buss the
|
||||
+1 dB / −1 dB nudge presets appear as well.
|
||||
## What it discovers
|
||||
|
||||
On every connect (and on the **Reload** action, and automatically after the console was silent for
|
||||
20 seconds and comes back) the module asks for the console name, the matrix, control group and graphic EQ
|
||||
counts, the buss types and modes, every name, the session name, the snapshot list, and the fader, mute,
|
||||
solo and pan of everything that answered. The console does not answer the input and buss counts, so an
|
||||
entity exists when its **name** answered; entities that never answer are hidden from dropdowns, variables
|
||||
and presets. Dropdown entries read `1 · KICK In`, `24 · Monitors (group)`, `3 · Message · cue 4`.
|
||||
When a name, a buss mode or the snapshot list changes, the definitions are rebuilt a moment later.
|
||||
Sends, group assigns, matrix sends, graphic EQ gains and the per-channel switches are **not** bulk-loaded:
|
||||
each is read the first time a button or action needs it.
|
||||
|
||||
The 25 busses are each an **aux** or a **group**, according to `/Console/Aux_Outputs/types` (1 = aux,
|
||||
0 = group). Aux busses take sends (level, on, pan); group busses take input assigns. A buss's
|
||||
fader, mute and solo are on the Aux_Outputs or Group_Outputs side that matches its mode.
|
||||
|
||||
## Actions
|
||||
|
||||
Level actions take dB (`-inf` at the bottom of the number field). Every level has a **set** and a
|
||||
relative **adjust** version for rotary encoders; adjusting from -inf lands at -60 dB on the first step up.
|
||||
Toggles read the current state first when it is not known yet.
|
||||
|
||||
| Group | Actions |
|
||||
| --- | --- |
|
||||
| Inputs | mute, solo (several inputs at once), fader set/adjust, pan set/adjust, set name |
|
||||
| Aux sends | on/off, level set/adjust, pan set/adjust |
|
||||
| Groups | assign input on/off |
|
||||
| Busses | mute, solo (several), fader set/adjust |
|
||||
| Matrix | output mute, fader set/adjust; matrix send on/off, level set/adjust |
|
||||
| Control groups | mute, solo, fader set/adjust |
|
||||
| Graphic EQ | in/out, band gain set/adjust (bands 1-32) |
|
||||
| Channel strip | per input and per buss: phantom, phase, pad, EQ in, compressor in, gate in, high-pass in, low-pass in, delay in, insert A/B in (on/off); trim (-40 to +40 dB), analog gain (0 to 60 dB), filter frequencies (20 to 20000 Hz) (set/adjust; trim also on matrix outputs) |
|
||||
| Snapshots | fire (pick from the list or type an index), next, previous |
|
||||
| Console | reload, raw OSC, relay: forget all iPads |
|
||||
|
||||
The channel-strip values are what the console reports (trim and gain in dB, filters in Hz). In the console's
|
||||
own naming `lo_filter` is the high-pass filter and `hi_filter` the low-pass filter; the actions are named for
|
||||
what they do. Delay times and the send pre/post fader switch are not offered: their units and meaning were
|
||||
never verified.
|
||||
|
||||
**Encoders and writes.** Every write updates the module's state at once, so a fast encoder turn accumulates. For
|
||||
0.4 seconds after the last write to a value, a different value arriving for it is treated as a late echo of an
|
||||
earlier step and ignored; then one query confirms what the console has. The snapshot *Fire* action sends the
|
||||
snapshot's list position (0 = the first); verify it once on your console before using it live.
|
||||
|
||||
**Macros:** the console never answered a macro query, so there is no macro action. If your console supports it, the
|
||||
*Send raw OSC* action can send `/Macros/Buttons/press` with an integer argument (0-255).
|
||||
|
||||
## Feedbacks
|
||||
|
||||
* Boolean (button colour): input muted / solo, aux send on, group assigned, buss muted /
|
||||
solo, matrix muted, control group muted, snapshot is current, fader level above/below a threshold, send
|
||||
level above/below, console link alive, relay has an iPad, every channel-strip switch above, and the meter
|
||||
feedbacks below.
|
||||
* Value (use with a preset's local variable to put live numbers in button text): input fader, input pan,
|
||||
send level, send On/Off, buss / matrix / control group fader, current snapshot name, input meter value.
|
||||
* Advanced bars: input fader, aux send level and buss fader draw a horizontal bar along the bottom of the
|
||||
button (-60 to +10 dB); the input meter draws a vertical bar at the right edge (green, amber, red).
|
||||
|
||||
## Variables
|
||||
|
||||
`$(S31:ch_N_name)`, `$(S31:aux_N_name)`, `$(S31:group_N_name)`, `$(S31:buss_N_mode)`,
|
||||
`$(S31:mon_N_on)`, `$(S31:mon_N_level)`, `$(S31:mon_name)`, `$(S31:mon_mode)`,
|
||||
`$(S31:snapshot_name)`, `$(S31:connected)`, `$(S31:relay_clients)`, `$(S31:last_send_touched)`,
|
||||
`$(S31:last_rx)`.
|
||||
`input_N_name|fader|mute|pan`, `buss_N_name|mode|stereo|fader|mute`, `matrix_N_name|fader|mute`,
|
||||
`cg_N_name|fader|mute` (only for entities that exist), `snapshot_index|name|cue|count`, `console_name`,
|
||||
`session_name`, `connected`, `relay_clients`, `relay_client_ips` and `last_change`. Use them as
|
||||
`$(label:input_1_name)` with the connection's label. There are no per-send variables: use the value feedbacks.
|
||||
|
||||
## Presets
|
||||
|
||||
Presets are named with the live entity names and rebuilt when names load.
|
||||
|
||||
| Preset group | What the buttons do |
|
||||
| --- | --- |
|
||||
| Inputs: Mute | One button per input; red when muted |
|
||||
| Inputs: Fader (knob) | Rotary: turn = fader -/+1 dB, press = mute; shows name and dB, with a level bar |
|
||||
| Inputs: Solo | One button per input |
|
||||
| Busses | Master mute, and master fader knob (turn = fader, press = mute) per named buss |
|
||||
| Sends to *aux name* | One section per named aux buss: per input an **On/off** button (green when on, shows the level) and a **Level (knob)** (turn = send level, press = on/off, with a bar) |
|
||||
| Group *group name* | One section per named group buss: per input an **Assign** toggle, green when assigned |
|
||||
| Matrix, Control groups | Mute and fader knob per output / group |
|
||||
| Snapshots | **Fire** per snapshot (green when current), next, previous, current (display) |
|
||||
| Input meters | Level, gate gain reduction and compressor gain reduction buttons per input |
|
||||
| Console | Reload, connection status, relay status |
|
||||
|
||||
Busses and control groups with no name are unused slots and get no presets. If the preset list would exceed
|
||||
3000 entries or 4 MB, the per-input send level knobs are left out and a warning is logged.
|
||||
|
||||
## Meters
|
||||
|
||||
Input meters are subscribed on the console the first time a meter feedback renders (ids start at 5000 so
|
||||
they never collide with an iPad's). The console then streams values 0-127 several times a second; the
|
||||
module re-checks the meter feedbacks at most 10 times a second. Level is the post-fader input meter; gate and
|
||||
compressor gain reduction are the two reduction meters. There is no known buss meter address, so busses
|
||||
have no meters. The console keeps one subscription table: an iPad connecting clears it (`/Meters/clear`),
|
||||
and the module re-subscribes half a second after it sees that pass through the relay. Two iPads watching
|
||||
different banks both receive the mixed stream. The module never sends `/Meters/clear` itself.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **No reply from console**: the console only sends to the iPad IP + Send Port set on the
|
||||
console. Check those, and that nothing else on this computer owns the local receive port
|
||||
(on AB-ProPresenter UDP 8002 belongs to Dante Via; 8000 and 9000 are free).
|
||||
- **Learn mode** logs every new address pattern once (Connections → log). Touch a control on
|
||||
the surface and the matching address appears; `$(S31:last_send_touched)` shows the last
|
||||
send/assign that changed.
|
||||
- Press **Reload from console** after a session change to refresh names and levels.
|
||||
* **No reply from the console.** The console only answers to the iPad IP address and Send port set on the
|
||||
console. Check them, and that nothing else on this computer owns the local receive port.
|
||||
* **Dropdowns show generic names.** The load has not finished or the console did not answer the names;
|
||||
press **Reload**. The log shows `load complete` with the counts when it is done.
|
||||
* **Something changed on the console is not shown.** Press Reload after a session change; sends and
|
||||
per-channel values are only read when first needed.
|
||||
* **Debug logging** lists every new address pattern once (Connections log), so touching a control on the
|
||||
surface shows its address; `$(label:last_change)` shows the last change reported.
|
||||
|
||||
## Protocol (condensed)
|
||||
|
||||
A query is the address plus `/?` with no argument; the reply is the same address without `/?`. All 0/1
|
||||
switches are **reported as floats** but written as integers 0/1; levels, sends and pans are floats. Numbers
|
||||
are 1-based, snapshot indexes 0-based.
|
||||
|
||||
| Address | Reply | Notes |
|
||||
| --- | --- | --- |
|
||||
| `/Console/Name` | s | also the keepalive (every 5 s) |
|
||||
| `/Console/{Matrix_Outputs,Matrix_Inputs,Control_Groups,Graphic_EQ}` | i | counts that answer |
|
||||
| `/Console/{Input_Channels,Aux_Outputs,Group_Outputs}` | i | pushed once after first contact, never answered |
|
||||
| `/Console/Aux_Outputs/types` | 25 x i | 1 = aux, 0 = group |
|
||||
| `/Console/{Aux_Outputs,Group_Outputs,Input_Channels}/modes` | i per buss / input | 2 = stereo, 1 = mono |
|
||||
| `/Console/Session/Filename` | s | |
|
||||
| `/Input_Channels/N/Channel_Input/name`, `/Aux_Outputs/N/Buss_Trim/name`, `/Group_Outputs/N/Buss_Trim/name`, `/Matrix_Outputs/N/Buss_Trim/name`, `/Control_Groups/N/name`, `/Graphic_EQ/N/name` | s | matrix inputs have no name address |
|
||||
| `/{Input_Channels,Aux_Outputs,Group_Outputs,Matrix_Outputs,Control_Groups}/N/{fader,mute,solo}` | f | fader in dB, -150 = -inf |
|
||||
| `/Input_Channels/N/Panner/pan` | f | 0..1, 0.5 = centre |
|
||||
| `/Input_Channels/N/Aux_Send/M/{send_level,send_on,send_pan}` | f | |
|
||||
| `/Input_Channels/N/Group_Send/M/group` | f | |
|
||||
| `/Matrix_Inputs/N/Matrix_Send/M/{send_level,send_on}` | f | |
|
||||
| `/Graphic_EQ/N/{geq_in,geq_gain_1..32}` | f | |
|
||||
| `/Input_Channels/N/{Channel_Input,Filters,Channel_Delay,EQ,Dynamics,Insert}/...` and the buss equivalents | f | the channel-strip switches and values above |
|
||||
| `/Snapshots/Current_Snapshot`, `/Snapshots/count` | i | |
|
||||
| `/Snapshots/names` | one `/Snapshots/name` `iiis` per snapshot: index, cue x 100, ?, name | |
|
||||
| `/Snapshots/Fire_Snapshot_number`, `/Snapshots/Fire_Next_Snapshot`, `/Snapshots/Fire_Prev_Snapshot` | none | write-only int |
|
||||
| `/Input_Channels/N/Channel_Input/post_meter`, `/Input_Channels/N/Dynamics/GR_meter` | none | send `,ii [id, kind]` (kind 1 level and gate, 4 compressor) to subscribe |
|
||||
| `/Meters/values` | `ii...` | pushed pairs `[id, value]`, value 0..127 |
|
||||
| `/Meters/clear` | none | clears every subscription on the console; sent by iPad apps, never by this module |
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"id": "digico-s31",
|
||||
"name": "digico-s31",
|
||||
"shortname": "DiGiCo S31",
|
||||
"description": "DiGiCo S21/S31 aux-send control over OSC with a relay that lets several DiGiCo iPad apps share the console's single remote slot",
|
||||
"version": "0.1.0",
|
||||
"description": "DiGiCo S21/S31 control over OSC: discovers the console, builds dropdowns, variables and presets from its names, and relays the DiGiCo iPad app so several iPads can share the console's single remote slot",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"repository": "http://chronicles-ab.waterschurch.org:3000/Waters-Church/companion-module-digico-s31",
|
||||
"bugs": "http://chronicles-ab.waterschurch.org:3000/Waters-Church/companion-module-digico-s31/issues",
|
||||
|
||||
Reference in New Issue
Block a user