A matrix-send knob looked dead on the booth Companion although the console followed it (verified through Companion's rotate API: -90 -> -60 -> -59): the module had matrix send actions but no matrix send feedbacks. - Every action with a readable state now has a matching feedback: matrix send on/level text/level bar, matrix and control group solo and fader bars, graphic EQ in and band gain text/bar, aux send pan text, and value text for trim, gain and filter frequencies. A test checks the parity. - New action: matrix output solo. - Matrix sends use the console's -90 dB floor: adjusting down from -inf no longer writes -150 there. - Presets: "Matrix sends to <output>" (on/off and knob per matrix input) and graphic EQ in/out. 2970 presets, 1.8 MB. npm test: 368 checks pass on Node 25 and on Companion's Node 22. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
212 lines
14 KiB
Markdown
212 lines
14 KiB
Markdown
# DiGiCo S-Series (S21 / S31): control and iPad relay
|
||
|
||
This connection does two things:
|
||
|
||
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**:
|
||
|
||
| Console field | Value |
|
||
| --- | --- |
|
||
| 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, for every iPad:
|
||
|
||
| iPad field | Value |
|
||
| --- | --- |
|
||
| 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 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.
|
||
|
||
## Settings
|
||
|
||
| Setting | Default | Meaning |
|
||
| --- | --- | --- |
|
||
| 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 |
|
||
|
||
## 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. A relative step cannot start from -inf: from −∞ the first
|
||
step up jumps to −60 dB. A matrix send has its floor at **-90 dB** (the console reports off matrix sends as -90, and
|
||
the module never writes lower); faders and aux sends go down to -150 dB. 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
|
||
|
||
Every action that changes something the console reports has a feedback for it: a boolean for every switch, a value
|
||
(text for a button's local variable) and a bar for every level. The state a feedback needs (sends, matrix sends,
|
||
graphic EQ bands, channel-strip values) is loaded the first time one is shown, and each button re-checks only when its
|
||
own value changes.
|
||
|
||
* Boolean (button colour): input muted / solo; aux send on; group assigned; buss muted / solo; matrix output muted /
|
||
solo; control group muted / solo; **matrix send on**; **graphic EQ in**; snapshot is current; fader level above/below
|
||
a threshold; aux send level above/below; console link alive; relay has an iPad; every channel-strip switch
|
||
(phantom, phase, pad, EQ in, compressor in, gate in, high-pass in, low-pass in, delay in, insert A/B in); and the meter
|
||
feedbacks below.
|
||
* Value (put live numbers in button text with a preset's local variable): input fader and pan; aux send level, On/Off
|
||
and pan; buss / matrix / control group fader; **matrix send level**; **graphic EQ band gain**; trim, analog gain and
|
||
filter frequency per input and buss (trim also per matrix output); current snapshot name; input meter value. Levels
|
||
read `-inf` at or below -90 dB.
|
||
* Advanced bars: input fader, aux send level, buss / matrix output / control group fader, **matrix send level** (all
|
||
-60 to +10 dB) and **graphic EQ band gain** (-12 to +12 dB) draw a horizontal bar along the bottom of the button;
|
||
the input meter draws a vertical bar at the right edge (green, amber, red).
|
||
|
||
Not covered by a feedback: free-form *Send raw OSC*, and *Set name* (the name is the variable `input_N_name`). There is
|
||
no matrix-output solo action, only its feedback.
|
||
|
||
## Variables
|
||
|
||
`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 |
|
||
| Matrix sends to *output name* | One section per matrix output: per matrix input an **On/off** button (green when on; shows `Matrix In N` and the level) and a **Level (knob)** (turn = send level, press = on/off, with a bar) |
|
||
| Graphic EQs | One **In/out** toggle per graphic EQ, green when in |
|
||
| 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 gate / compressor reduction meter presets are left out first, then the
|
||
per-input aux send level knobs, 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 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 and written as floats** (0.0 / 1.0), verified on an S31 on 2026-10-15: integer and
|
||
OSC `T`/`F` writes to a switch are silently ignored (no echo, no change), unlike the 2014 DiGiCo list.
|
||
Levels, sends and pans are floats too. 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 |
|