- Every set-level action (input, bus, matrix and control group faders, aux and matrix sends) has a fade time and a curve: fader travel (natural), fader travel with ease in/out, or linear in dB. Fades step at 20 Hz, land exactly on the target, never overshoot, and are cancelled by any new move on the same fader; new "Stop all fades" action, fades_running variable and fade_running feedback. Verified on the S31 with a switched-off matrix send: 20 writes in 1 s, ending exactly on target, then restored. - Matrix sends now look like mutes in the presets: red when the send is off, black when it is on (new matrix_send_off feedback, true only when the send is known to be off). - Matrix send knobs carry a meter column on the right. The S31 has no meter for matrix inputs, so it shows the destination output's meter. Meter gauges use a faint track so an empty meter reads as empty. - Test fixes: the meter-resubscribe check no longer depends on a silent channel changing value, plus back-to-back /Meters/clear coverage. npm test: 504 checks pass on Node 25 and on Companion's Node 22. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
278 lines
21 KiB
Markdown
278 lines
21 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 and output 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 (inputs, busses, matrix inputs and outputs, control groups, graphic EQs), 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`. The console pads names
|
||
with trailing spaces and wraps long ones with runs of spaces; every name is trimmed and its whitespace collapsed to single
|
||
spaces when it arrives, so dropdowns, variables and presets show `Studio TB`, not `Studio TB `. A matrix input with
|
||
no name reads `Matrix In N`.
|
||
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 and matrix inputs, phase also on matrix inputs) |
|
||
| Snapshots | fire (pick from the list or type an index), next, previous |
|
||
| Console | reload, raw OSC, relay: forget all iPads, stop all fades |
|
||
|
||
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.
|
||
|
||
**Fades.** Every *set level* action (input, buss, matrix output and control group faders, aux send level, matrix send
|
||
level) has a **Fade time (ms, 0 = instant)**, 0 to 60000 ms. With 0 the level jumps to the target at once, as before; above 0
|
||
the module moves the level there smoothly, starting from where it is now (it reads the level first when it is not known
|
||
yet; if the console does not answer, the target is set at once and a warning is logged). A new level is written 20 times a
|
||
second at most (every 50 ms, steps that would change the level by less than 0.05 dB are left out), and the last write is
|
||
exactly the target. The **Fade curve** (shown once the time is above 0) is *Fader travel (natural)*, which moves the way a
|
||
fader's travel does (the same taper as the gauges, so the level change sounds even; the default), *Fader travel, ease in/out*
|
||
(the same with a slow start and a slow end), or *Linear in dB* (the same number of dB every step; a fade from or to −∞ runs between −90 dB, where sound
|
||
stops, and the target, so no part of it is a silent stretch; a fade to −∞ still ends at exactly −∞). A matrix send never goes below −90 dB.
|
||
There is one fade per level: starting another fade, a plain *set* or an *adjust* on the same level ends the running one
|
||
where it is, and the new one takes over from that level. The action returns at once and the fade runs on in the
|
||
background; the **Stop all fades** action ends every fade where it is. Fades end too when the connection to the console is
|
||
closed or reconfigured, and while one runs the module keeps writing its level, so a fader moved at the console in the
|
||
meantime is overwritten. The variable `fades_running` is the number of fades running and the feedback **A fade is
|
||
running** is on while there is one.
|
||
|
||
**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 numeric value for a gauge 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**; **matrix send is off** (red like a mute; false while the state is not yet known); **graphic EQ in**; snapshot is current; fader level above/below
|
||
a threshold; aux send level above/below; console link alive; relay has an iPad; a level fade is running; 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; phase also
|
||
per matrix input); and the meter feedbacks (signal present, peak hold) for inputs, busses and matrix outputs.
|
||
* 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 and matrix input); current snapshot name; meter level
|
||
text (see **Meters**). Levels read `-inf` at or below -90 dB.
|
||
* Numeric, for gauges (see **Gauges** below): the `*_pos` position feedbacks, `input_pan_num` / `send_pan_num` and
|
||
`geq_band_db`.
|
||
* Advanced image bars (**legacy image**, deprecated by Companion 5.1, kept so existing buttons keep working): input
|
||
fader, aux send level, buss / matrix output / control group fader, matrix send level (-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. Use a gauge on new buttons.
|
||
|
||
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.
|
||
|
||
## Gauges
|
||
|
||
Companion 5.x has a native **Gauge** element, and every knob and meter preset uses one. Each position feedback is a
|
||
number you feed to a gauge through a button local variable:
|
||
|
||
| Feedback | Range | Meaning |
|
||
| --- | --- | --- |
|
||
| `input_fader_pos`, `buss_fader_pos`, `matrix_fader_pos`, `cg_fader_pos`, `send_level_pos`, `matrix_send_level_pos` | 0-100 | fader / send position on a fader taper; unknown reads 0 |
|
||
| `input_pan_num`, `send_pan_num` | 0-1 | the console's pan, 0.5 = centre (unknown reads 0.5) |
|
||
| `geq_band_db` | -12 to +12 | graphic EQ band gain in dB (unknown reads 0) |
|
||
| `input_meter_pos`, `buss_meter_pos`, `matrix_meter_pos` | 0-100 | meter position: levels over -60..0 dBFS (-inf = 0), gain reduction over 0..20 dB; the input meter has the *Meter* option level / gate / compressor reduction |
|
||
|
||
The fader taper maps +10 dB = 100, +5 = 87.5, 0 = 75, -5 = 65, -10 = 55, -20 = 40, -30 = 28, -40 = 18, -50 = 11 and
|
||
-60 = 6 (straight lines in between), falls to about 1 just above -90 dB, and reads 0 at or below -90 dB (off).
|
||
|
||
To add a gauge to your own button in Companion 5.1:
|
||
|
||
1. **Local Variables** tab: add a feedback variable, name it `pos`, and pick the position feedback (for example
|
||
`input_fader_pos` with its input).
|
||
2. **Style** tab: **+**, then **Gauge**. Set *Value* to the expression `$(local:pos)`, *Min* 0, *Max* 100
|
||
(fader and meter positions).
|
||
3. For pan use `input_pan_num` / `send_pan_num` with *Min* 0, *Max* 1, **Symmetric** on and *Origin* 0.5. For a graphic EQ
|
||
band use `geq_band_db` with *Min* -12, *Max* 12, symmetric, *Origin* 0.
|
||
|
||
## Variables
|
||
|
||
`input_N_name|fader|mute|pan`, `buss_N_name|mode|stereo|fader|mute`, `matrix_N_name|fader|mute`, `matrix_in_N_name`,
|
||
`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`, `fades_running` (number of level fades running) 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. The knob and meter presets are *layered*
|
||
(black background, name, dB text, gauge) and a muted / on state tints their background; the plain toggles are simple
|
||
buttons.
|
||
|
||
| 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 gauge along the bottom |
|
||
| 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 gauge) |
|
||
| Group *group name* | One section per named group buss: per input an **Assign** toggle, green when assigned |
|
||
| Matrix, Control groups | Mute and fader knob (with a gauge) per output / group |
|
||
| Matrix sends to *output name* | One section per matrix output: per matrix input an **On/off** button (black when the send is on, red when it is off, like a mute; shows the matrix input's name and the level) and a **Level (knob)** (turn = send level, press = on/off, turns red when the send is off, with a level gauge along the bottom and a meter column on the right; the S31 has no meter for matrix inputs, so that column shows the destination matrix output's meter) |
|
||
| 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, each with a vertical gauge at the right edge (green below 70, amber 70-90, red above 90) |
|
||
| Buss meters, Matrix meters | One level meter button per named buss and per matrix output, same vertical gauge |
|
||
| 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
|
||
3200 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
|
||
|
||
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: input meters 5000-5260, buss meters 5400 + N, matrix output meters 5450 + N). The console then streams
|
||
integer values 0-127 several times a second, only for the ones that changed, and the module re-checks the meter
|
||
feedbacks at most 10 times a second, and only the buttons showing the meter that moved.
|
||
|
||
**The scale is inverted and approximate.** A level meter value is about the **dB below full scale**: 0 = full scale,
|
||
42 = about -42 dBFS, **127 = silence (-inf)**. A gain reduction meter value is about the **dB of reduction** (0-12 is
|
||
typical, 127 = inactive). It has not been calibrated against a reference tone, so treat the numbers as roughly 1 dB per
|
||
step, not as a measurement.
|
||
|
||
| Feedback | What it shows |
|
||
| --- | --- |
|
||
| `input_meter_value`, `buss_meter_value`, `matrix_meter_value` | text: `-42` or `-inf` (dBFS, approx.); gain reduction meters: `3` (dB) |
|
||
| `input_meter_pos`, `buss_meter_pos`, `matrix_meter_pos` | gauge position 0-100: levels over -60..0 dBFS, gain reduction over 0..20 dB |
|
||
| `input_signal`, `buss_signal`, `matrix_signal` | signal present: level above a dBFS threshold (option, default -50; gain reduction meters: any reduction) |
|
||
| `input_peak`, `buss_peak`, `matrix_peak` | level at or above a dBFS value (option, default -1) held for the *Hold* time; not for gain reduction meters |
|
||
|
||
Input meters are level (post-fader), gate gain reduction and compressor gain reduction. Output meters exist for aux
|
||
busses, group busses (the address follows the buss's current aux / group side) and matrix outputs; they are level meters.
|
||
Control groups and matrix inputs have no meter. 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 (and after a
|
||
reconnect). 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`, `/Matrix_Inputs/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 | names arrive padded; the module cleans them |
|
||
| `/{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 | a matrix send floors at -90 dB |
|
||
| `/Matrix_Inputs/N/Channel_Input/{trim,phase}` | 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`, `/{Aux_Outputs,Group_Outputs,Matrix_Outputs}/N/Output/meter` | none | send `,ii [id, kind]` (kind 1 level and gate and outputs, 4 compressor) to subscribe |
|
||
| `/Meters/values` | `ii...` | pushed pairs `[id, value]`, value 0..127: inverted scale, 127 = silence / inactive |
|
||
| `/Meters/clear` | none | clears every subscription on the console; sent by iPad apps, never by this module |
|