Found on the live S31 (read-only, plus meter subscriptions):
- Matrix inputs are named at /Matrix_Inputs/N/Channel_Input/name (an
address missing from DiGiCo's 2014 list). Their names now appear in
every matrix-send dropdown, preset and the new matrix_in_N_name
variables, falling back to "Matrix In N" when empty. Matrix inputs also
get trim and phase.
- Console names are space-padded where the surface wraps them; every name
is now trimmed and its whitespace runs collapsed ("LEAD V RESI").
- Meter values are inverted: 127 is silence and lower is louder, about
1 dB per step below full scale (silent channels read 127, the room mic
36-48). The module treated 127 as peak. Level meters now read dBFS
(approximate), gauges span -60..0 dBFS, signal and peak thresholds are
in dBFS; gain reduction reads dB of reduction, 127 meaning none.
- Bus and matrix outputs have meters (/Aux_Outputs, /Group_Outputs and
/Matrix_Outputs/N/Output/meter): new signal, peak, value and gauge
feedbacks and "Buss meters" / "Matrix meters" presets.
3002 presets, 2.7 MB. npm test: 425 checks pass on Node 25 and on
Companion's Node 22. Deployed and checked on the booth Companion 5.1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 KiB
Developer tools
Two small, dependency-free Node.js tools (Node 22+) for developing the S31 module
without a console. They share osc-lite.js, a tiny OSC encoder/decoder.
tools/package.json pins this folder to CommonJS, because the module's own
package.json is "type": "module" and would otherwise make Node load these
files as ESM. Keep it.
Both tools speak two OSC dialects, chosen with --dialect:
| Dialect | What it is |
|---|---|
sseries (default) |
The lowercase S-Series command list: /console/ping, /channel/N/fader, ... |
sd |
The "DiGiCo Pad" dialect the real S31 iPad extension actually speaks: /Console/Name/?, /Input_Channels/N/fader/?, ... Verified against a live console (tools/catalog/s31-catalog.md); this is the dialect the Companion module uses. |
mock-s31.js - fake S31 console
Emulates the console's OSC extension. It listens on one UDP port and sends all feedback to one configured controller IP:port, whoever asked and from whichever source port, like the real desk.
node tools/mock-s31.js [--dialect sseries|sd] [--listen 8000] [--reply-to IP:PORT]
[--channels N] [--auxes N] [--bool-int] [--live]
[--delay MS] [--types] [--quiet]
| Flag | Meaning |
|---|---|
--dialect |
sseries (default) or sd. |
--listen |
UDP port to bind (the console's receive port). Default 8000. |
--reply-to |
Fixed feedback destination, e.g. 127.0.0.1:9000. If omitted, locks onto the first sender's IP with port 9000 and prints that. |
--channels |
Input count, 1-60. Default 48 (sseries) or 60 (sd). |
--auxes |
Aux busses / sends per channel. Default 16, max 16 (sseries); default 25, max 25 (sd). |
--bool-int |
sseries only: send booleans as int 0/1 instead of OSC T/F. |
--delay MS |
sd: answer and echo every message MS milliseconds late (0-5000, default 0), to test a slow console and stale echoes. |
--live |
Every 3 s, change a random send level or send on/off and emit it. |
--types |
Also print each message's OSC type tags, e.g. /Input_Channels/1/fader ,f [-1.4375]. |
--quiet |
Don't print every message. |
Traffic is printed as <- ADDRESS [args] from IP:PORT and -> ADDRESS [args].
Whole-number floats print with a decimal (-10.0) so you can tell int from float.
sseries dialect
No args at a readable address = query (reply with the value); with an arg =
set (store, then echo to the controller). /console/resend dumps the whole
state at ~2 ms per packet. Values are clamped (fader -150..10, send level
-90..10, pan -1..1). Addresses: /console/{ping,channel/counts,resend},
/channel/N/{name,fader,mute,solo,pan}, /channel/N/total/gain (send-only,
sent with fader changes), /channel/N/send/M/{level,enabled,pan}, and
/digico/snapshots/fire[/next|/previous] (logged only). Inputs are 1-60, aux
busses 70..70+auxes-1.
sd dialect
A query is the address plus a trailing /? and no args; the reply is the same address without /?
plus the value. A set is the address without /? and one arg; it is stored and echoed back in the
console's reply types. The mock answers the addresses listed in tools/catalog/s31-catalog.md (the real
console's answers) with the catalog's reply types. Entity numbers are 1-based, control groups
included (/Control_Groups/1/name is the first); snapshot indexes are 0-based. --channels N limits the
inputs, --auxes N the busses (default 25), and the matrix (8 outputs, 10 inputs), 10 control groups and
16 graphic EQs are fixed.
| Address | Reply type |
|---|---|
/Console/Name/?, /Console/Session/Filename/? |
s |
/Console/{Matrix_Outputs,Matrix_Inputs,Control_Groups,Graphic_EQ,Talkback_Outputs,Multis}/? |
i |
/Console/{Input_Channels,Aux_Outputs,Group_Outputs}/? |
silent, like the real console. All nine counts are pushed once, ~200 ms after the first datagram it receives |
/Console/Aux_Outputs/types/? |
one i per buss, 1 = aux, 0 = group (a buss named only on the Group side is a group) |
/Console/{Aux_Outputs,Group_Outputs}/modes/?, /Console/Input_Channels/modes/? |
one i per buss / input (2 = stereo, 1 = mono, captured) |
/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. Only input names accept a write here (not verified on the real console); the others are read-only. The 25 busses appear under both Aux_Outputs and Group_Outputs; a buss reports its name on the side matching its mode and "" on the other. Names are padded with trailing spaces and runs of spaces, as the surface reports them (matrix input names are read-only here) |
/{Input_Channels,Aux_Outputs,Group_Outputs,Matrix_Outputs,Control_Groups}/N/fader/? |
f, dB, -150 = -inf, max +10 |
/{...}/N/mute/?, /{...}/N/solo/? |
f 0/1 (a float, not an int); a SET must be a float too, see below |
/Input_Channels/N/Panner/pan/? |
f, 0..1, 0.5 = centre |
/Input_Channels/N/Aux_Send/M/{send_level,send_on,send_pan}/? |
f (level dB -150..10, on 0/1, pan 0..1) |
/Input_Channels/N/Aux_Send/M/send_pre-post/? |
i (captured as an int, default 0; settable 0..1, which value means "pre" is an assumption; the Companion module does not use it) |
/Input_Channels/N/Group_Send/M/group/? |
f 0/1 group assign. Default: inputs 1-8 and 18-21 on group 24. The wire type is the constant SD_GROUP_ASSIGN_WIRE_TYPE |
/Matrix_Inputs/N/Matrix_Send/M/{send_level,send_on}/? |
f (default -90 and 0, captured). A matrix send floors at -90 dB (verified on an S31): level writes are clamped to -90..10, never -150 as for faders and aux sends |
/Graphic_EQ/N/{geq_in,geq_gain_1..32}/? |
f |
/Input_Channels/N/{Channel_Input/{trim,phase,phantom,analog_gain,input_pad},Filters/{hi,lo}_filter_{in,freq},Channel_Delay/{delay_on,delay},EQ/eq_in,Dynamics/{comp_in,gate_in},Insert/insert_{A,B}_in}/? |
f (switches 0/1) |
/{Aux_Outputs,Group_Outputs}/N/{Buss_Trim/{trim,phase},EQ/eq_in,Dynamics/{comp_in,gate_in},Channel_Delay/{delay_on,delay},Insert/insert_{A,B}_in}/?, /Matrix_Outputs/N/Buss_Trim/trim/?, /Matrix_Inputs/N/Channel_Input/{trim,phase}/? |
f |
/Snapshots/Current_Snapshot/?, /Snapshots/count/? |
i |
/Snapshots/names/? |
one /Snapshots/name ,iiis [index, cue*100, 0, "name"] per snapshot. /Snapshots/name/? without an argument stays silent (unreliable on the real console) |
/Snapshots/Fire_Snapshot_number [i], /Snapshots/Fire_Next_Snapshot, /Snapshots/Fire_Prev_Snapshot |
write-only triggers; the mock moves its current snapshot and pushes /Snapshots/Current_Snapshot |
/Macros/Buttons/press [i] |
logged only (no macro query is answered) |
Meters. Subscribe with /Input_Channels/N/Channel_Input/post_meter ,ii [id, 1] (post-fader level),
/Input_Channels/N/Dynamics/GR_meter ,ii [id, 1|4] (gate / compressor gain reduction) or
/{Aux_Outputs,Group_Outputs,Matrix_Outputs}/N/Output/meter ,ii [id, 1] (output level), where the client chooses id.
The scale is inverted, as on the real console (verified on an S31, approximate): a level meter reads about the dB
below full scale, so silence is 127 and the mock's live channels (inputs 1-3, aux 1, group 9, matrix output 1) read
20-60; a gain reduction meter reads the dB of reduction, 0-12, now and then 127 (inactive). Every subscribed id gets
its value about five times a second, as /Meters/values ,ii... [id, v, id, v, ...] (only ids whose value changed, so a
silent channel reports 127 once). /Meters/clear drops every subscription, like the real console. Control groups and
matrix inputs have no meter address.
A 0/1 switch (mute, solo, send on, group assign, matrix send on, geq_in and every channel-strip switch) is both
reported and written as a float 0.0 / 1.0, verified on a real S31 on 2026-10-15 (the 2014 DiGiCo list says
int). Exactly like the console, the mock accepts only a float write (stored, echoed as ,f) and silently
ignores an int i or T/F write: no echo, no state change. Levels, pans and values are clamped to
their ranges. Like the real console the mock stays silent on everything else: all lowercase S-Series
addresses, /Console/Resend, /Console/Channels/?, whole-send queries, out-of-range entity numbers,
sets of read-only names. (--bool-int has no effect in sd.)
fake-ipad.js - stand-in for the DiGiCo iPad app
Binds the receive port, sends its opening requests, repeats a keepalive every 5 s, prints everything it receives and a packet-count summary every 10 s.
node tools/fake-ipad.js --console IP --send-port 8000 --receive-port 9000
[--dialect sseries|sd] [--name A] [--types] [--quiet]
sseries: sends/console/ping, then/console/resend; keepalive/console/ping;/console/pongcounts as a pong.sd: sends/Console/Name/?,/Console/Input_Channels/?,/Console/Aux_Outputs/?,/Console/Aux_Outputs/modes/?, names of inputs 1-8 and aux outputs 1-4, then/Snapshots/names/?(20 ms apart); keepalive/Console/Name/?; a/Console/Namereply counts as a pong.
Type ADDRESS [arg ...] lines on stdin to send your own messages
(true/false = boolean, 5 = int, 5.0 or -12.5 = float, anything else or
"quoted" = string; quit exits):
/channel/3/send/2/level -12.5 # sseries set
/Input_Channels/3/Aux_Send/2/send_on 1 # sd set (echo comes back as ,f [1])
/Input_Channels/3/Aux_Send/2/send_level/? # sd query
/channel/3/name "Lead Vox"
End-to-end loops on one machine
The module sits between the console and the iPad: the console's feedback goes
to the module (port 9000), and the module relays to and from the iPad. Use the
ports the module is actually configured with; 9100 and 9200 below are just
examples for "the relay's iPad-facing port" and "where the relay forwards
feedback to the iPad".
S-Series dialect
# 1. Mock console: listens on 8000, replies to whatever listens on 9000
node tools/mock-s31.js --listen 8000 --reply-to 127.0.0.1:9000
# 2. Start the Companion module (or any relay) listening on 9000, sending to 127.0.0.1:8000
# 3. Fake iPad pointed at the relay (sends to 9100, receives the relayed feedback on 9200)
node tools/fake-ipad.js --console 127.0.0.1 --send-port 9100 --receive-port 9200
SD dialect (what the real S31 speaks)
# 1. Mock console in SD mode: listens on 8000, replies to 127.0.0.1:9000
node tools/mock-s31.js --dialect sd --listen 8000 --reply-to 127.0.0.1:9000 --types
# 2. Start the Companion module (or any relay) listening on 9000, sending to 127.0.0.1:8000
# 3. Fake iPad (SD) pointed at the relay
node tools/fake-ipad.js --dialect sd --console 127.0.0.1 --send-port 9100 --receive-port 9200 --types
No module yet? Skip step 2 and point the iPad straight at the mock to check the
tools themselves, e.g.
node tools/fake-ipad.js --dialect sd --console 127.0.0.1 --send-port 8000 --receive-port 9000.