Files
companion-module-digico-s31/tools/README.md
T
michaelandClaude Opus 5.5 90dee14444 Add a feedback for every controllable state, matrix send presets
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>
2026-10-19 15:44:43 -04:00

10 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/?, /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. Matrix inputs have no name address
/{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/? 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) or /Input_Channels/N/Dynamics/GR_meter ,ii [id, 1|4] (gate / compressor gain reduction), where the client chooses id. Every subscribed id then gets a random value 0..127 about five times a second, as /Meters/values ,ii... [id, v, id, v, ...] (only ids whose value changed). /Meters/clear drops every subscription, like the real console.

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/pong counts 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/Name reply 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.