Name matrix inputs, fix the meter scale, add output meters
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>
This commit is contained in:
+11
-7
@@ -68,7 +68,7 @@ inputs, `--auxes N` the busses (default 25), and the matrix (8 outputs, 10 input
|
||||
| `/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/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 |
|
||||
@@ -78,17 +78,21 @@ inputs, `--auxes N` the busses (default 25), and the matrix (8 outputs, 10 input
|
||||
| `/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` |
|
||||
| `/{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) 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.
|
||||
**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
|
||||
|
||||
+39
-14
@@ -584,6 +584,8 @@ const SD_GROUP_NAMES = [
|
||||
'', '', '', '', '', '', '', '', 'BAND', 'VOCALS', 'MISC', 'DRUMS', 'DRUM CRUSH', '', '', '', '', 'Broadcast mix',
|
||||
'', '', '', '', 'CAFE', 'Studio', 'Master',
|
||||
];
|
||||
/** Matrix input names as the console reports them: padded with trailing spaces, with runs of spaces inside (captured). */
|
||||
const SD_MATRIX_IN_NAMES = ['TB ', 'Studio L', 'Studio R', 'Master L', 'Master R', 'GUIDE ', 'Studio TB ', 'Dante In 63 ', 'Dante In 64 ', ''];
|
||||
const SD_MATRIX_NAMES = ['MAINS L', 'MAINS R', 'SUB L', 'SUB R', 'ProP LR', 'Matrix 6', 'Matrix 7', 'Studio'];
|
||||
const SD_CG_NAMES = ['DRUMS', 'GTR', 'KEYS', 'TRKS', 'VOCALS', 'FX', '', 'ProP', 'MC & SPEAKER', ''];
|
||||
/** Snapshot list: index n has cue number (n+1)*100 (reported as iiis [index, cue*100, 0, name]). */
|
||||
@@ -644,6 +646,10 @@ const SD_PARAM_SPECS = {
|
||||
matrix: {
|
||||
'Buss_Trim/trim': { spec: sdValue(-40, 40), def: 0 },
|
||||
},
|
||||
minput: {
|
||||
'Channel_Input/trim': { spec: sdValue(-40, 40), def: 0 },
|
||||
'Channel_Input/phase': { spec: SD_SWITCH, def: 0 },
|
||||
},
|
||||
};
|
||||
const SD_SEND_PROPS = { send_level: 'level', send_on: 'on', send_pan: 'pan', 'send_pre-post': 'prepost' };
|
||||
|
||||
@@ -655,11 +661,13 @@ const sdInputs = [null]; // 1-based: { name, fader, mute, solo, pan, sends:[null
|
||||
const sdAuxes = [null]; // 1-based: { name, fader, mute, solo }
|
||||
const sdGroups = [null]; // 1-based: { name, fader, mute, solo }
|
||||
const sdMatrix = [null]; // 1-based matrix outputs: { name, fader, mute, solo }
|
||||
const sdMatrixIn = [null]; // 1-based matrix inputs: { sends:[null,{level,on}...] }
|
||||
const sdMatrixIn = [null]; // 1-based matrix inputs: { name, sends:[null,{level,on}...] }
|
||||
const sdCgs = [null]; // 1-based control groups: { name, fader, mute, solo }
|
||||
const sdGeqs = [null]; // 1-based graphic EQs: { name, on, gains:[null, ...] }
|
||||
let sdSnapshot = 3; // /Snapshots/Current_Snapshot
|
||||
const sdMeterSubs = new Map(); // meter id -> last value sent
|
||||
/** meter id -> { last, kind: 'level' | 'gr', live }: what was last sent and what to send. Silence is 127; live channels read 20-60. */
|
||||
const sdMeterSubs = new Map();
|
||||
const SD_LIVE_INPUTS = 3; // inputs 1-3 carry signal; aux 1, group 9 and matrix output 1 do too
|
||||
let sdMeterTimer = null;
|
||||
const SD_METER_INTERVAL_MS = 200; // ~5 Hz
|
||||
|
||||
@@ -705,7 +713,7 @@ function buildStateSd() {
|
||||
for (let k = 0; k < SD_MATRIX_IN; k++) {
|
||||
const sends = [null];
|
||||
for (let m = 1; m <= SD_MATRIX_OUT; m++) sends.push({ level: -90, on: 0 }); // captured: -90 / 0
|
||||
sdMatrixIn.push({ sends });
|
||||
sdMatrixIn.push({ name: SD_MATRIX_IN_NAMES[k], sends });
|
||||
}
|
||||
for (let k = 0; k < SD_CGS; k++) sdCgs.push({ name: SD_CG_NAMES[k], fader: tidy16(-20 + rng() * 20), mute: 0, solo: 0 });
|
||||
for (let k = 1; k <= SD_GEQS; k++) {
|
||||
@@ -738,8 +746,9 @@ const SD_RE_MATRIX_SEND = /^\/Matrix_Inputs\/(\d+)\/Matrix_Send\/(\d+)\/(send_le
|
||||
const SD_RE_CG_NAME = /^\/Control_Groups\/(\d+)\/name$/;
|
||||
const SD_RE_CG_PROP = /^\/Control_Groups\/(\d+)\/(fader|mute|solo)$/;
|
||||
const SD_RE_GEQ = /^\/Graphic_EQ\/(\d+)\/(name|geq_in|geq_gain_(\d+))$/;
|
||||
const SD_RE_PARAM = /^\/(Input_Channels|Aux_Outputs|Group_Outputs|Matrix_Outputs)\/(\d+)\/((?:Channel_Input|Filters|Channel_Delay|EQ|Dynamics|Insert|Buss_Trim)\/\w+)$/;
|
||||
const SD_RE_METER_SUB = /^\/Input_Channels\/(\d+)\/(?:Channel_Input\/post_meter|Dynamics\/GR_meter)$/;
|
||||
const SD_RE_MINPUT_NAME = /^\/Matrix_Inputs\/(\d+)\/Channel_Input\/name$/;
|
||||
const SD_RE_PARAM = /^\/(Input_Channels|Aux_Outputs|Group_Outputs|Matrix_Outputs|Matrix_Inputs)\/(\d+)\/((?:Channel_Input|Filters|Channel_Delay|EQ|Dynamics|Insert|Buss_Trim)\/\w+)$/;
|
||||
const SD_RE_METER_SUB = /^\/(Input_Channels|Aux_Outputs|Group_Outputs|Matrix_Outputs)\/(\d+)\/(Channel_Input\/post_meter|Dynamics\/GR_meter|Output\/meter)$/;
|
||||
|
||||
/** Map an SD leaf address to { target, prop, spec } or null when there is no such thing. */
|
||||
function sdResolve(address) {
|
||||
@@ -776,11 +785,13 @@ function sdResolve(address) {
|
||||
const prop = fixedProp || m[2];
|
||||
return { target, prop, spec: SD_PROPS[prop] };
|
||||
}
|
||||
m = SD_RE_MINPUT_NAME.exec(address);
|
||||
if (m) return sdMatrixIn[Number(m[1])] ? { target: sdMatrixIn[Number(m[1])], prop: 'name', spec: SD_PROPS.name } : null;
|
||||
m = SD_RE_PARAM.exec(address);
|
||||
if (m) {
|
||||
const kind = { Input_Channels: 'input', Matrix_Outputs: 'matrix' }[m[1]] || 'buss';
|
||||
const kind = { Input_Channels: 'input', Matrix_Outputs: 'matrix', Matrix_Inputs: 'minput' }[m[1]] || 'buss';
|
||||
const entry = SD_PARAM_SPECS[kind][m[3]];
|
||||
const owner = { Input_Channels: sdInputs, Aux_Outputs: sdAuxes, Group_Outputs: sdGroups, Matrix_Outputs: sdMatrix }[m[1]][Number(m[2])];
|
||||
const owner = { Input_Channels: sdInputs, Aux_Outputs: sdAuxes, Group_Outputs: sdGroups, Matrix_Outputs: sdMatrix, Matrix_Inputs: sdMatrixIn }[m[1]][Number(m[2])];
|
||||
if (!entry || !owner) return null;
|
||||
if (!owner.params) owner.params = {};
|
||||
if (!(m[3] in owner.params)) owner.params[m[3]] = entry.def;
|
||||
@@ -888,8 +899,16 @@ function sdSet(address, args, types) {
|
||||
}
|
||||
const sub = SD_RE_METER_SUB.exec(address);
|
||||
if (sub) {
|
||||
if (!sdInputs[Number(sub[1])] || typeof args[0] !== 'number') return ignore(`meter subscription ${address} ${formatArgs(args)}`);
|
||||
sdMeterSubs.set(Math.trunc(args[0]), -1);
|
||||
const [, tree, numText, path] = sub;
|
||||
const owner = { Input_Channels: sdInputs, Aux_Outputs: sdAuxes, Group_Outputs: sdGroups, Matrix_Outputs: sdMatrix }[tree];
|
||||
const input = tree === 'Input_Channels';
|
||||
if (!owner[Number(numText)] || (input && path === 'Output/meter') || (!input && path !== 'Output/meter') || typeof args[0] !== 'number') {
|
||||
return ignore(`meter subscription ${address} ${formatArgs(args)}`);
|
||||
}
|
||||
const num = Number(numText);
|
||||
const kind = path === 'Dynamics/GR_meter' ? 'gr' : 'level';
|
||||
const live = kind === 'level' && ((input && num <= SD_LIVE_INPUTS) || (tree === 'Aux_Outputs' && num === 1) || (tree === 'Group_Outputs' && num === 9) || (tree === 'Matrix_Outputs' && num === 1));
|
||||
sdMeterSubs.set(Math.trunc(args[0]), { last: -1, kind, live });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -906,14 +925,20 @@ function sdSet(address, args, types) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** ~5 Hz: random 0..127 for every subscribed id whose value changed, as /Meters/values [id, v, id, v ...]. */
|
||||
/**
|
||||
* ~5 Hz: /Meters/values [id, v, id, v ...] for every subscribed id whose value changed. The scale is inverted, as on the
|
||||
* real console: a level meter reads about the dB below full scale, so silence is 127 and live channels read 20-60;
|
||||
* a gain reduction meter reads the dB of reduction (0-12, now and then 127 = inactive).
|
||||
*/
|
||||
function sdMeterTick() {
|
||||
if (!sdMeterSubs.size || !replyTarget) return;
|
||||
const pairs = [];
|
||||
for (const [id, last] of sdMeterSubs) {
|
||||
const v = Math.random() < 0.15 ? 0 : Math.floor(Math.random() * 128);
|
||||
if (v === last) continue;
|
||||
sdMeterSubs.set(id, v);
|
||||
for (const [id, meter] of sdMeterSubs) {
|
||||
let v;
|
||||
if (meter.kind === 'gr') v = Math.random() < 0.1 ? 127 : Math.floor(Math.random() * 13);
|
||||
else v = meter.live ? 20 + Math.floor(Math.random() * 41) : 127;
|
||||
if (v === meter.last) continue;
|
||||
meter.last = v;
|
||||
pairs.push(int(id), int(v));
|
||||
}
|
||||
for (let i = 0; i < pairs.length; i += 40) send('/Meters/values', pairs.slice(i, i + 40));
|
||||
|
||||
Reference in New Issue
Block a user