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:
+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