Add Group and Aux master actions, feedbacks and preset sections
Groups were only reachable through the generic "Buss:" actions, whose dropdown mixes all 25 busses, so users could not find group fader control. - New "Group:" and "Aux master:" mute, solo, set fader (with fade time) and adjust fader actions; their dropdowns list only busses of that type. Matching muted, solo, fader text/position, meter, signal and peak feedbacks. - The old buss_* actions and feedbacks keep working for existing buttons and are now labelled "Any buss (aux or group)". - Presets: "Busses" is split into "Groups" and "Aux masters", and the bus meters into "Group meters" and "Aux meters". A bus that changes type on the console moves section on the next rebuild. npm test: 542 checks pass on Node 25 and on Companion's Node 22. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+26
-16
@@ -73,7 +73,10 @@ 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.
|
||||
fader, mute and solo are on the Aux_Outputs or Group_Outputs side that matches its mode. The module has
|
||||
separate **Group** and **Aux master** actions, feedbacks and presets for them (their dropdowns list only busses of
|
||||
that type); a buss that changes type on the console moves to the other dropdowns and preset sections when the
|
||||
definitions are rebuilt.
|
||||
|
||||
## Actions
|
||||
|
||||
@@ -88,7 +91,9 @@ is not known yet.
|
||||
| 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 |
|
||||
| Groups | **Group:** mute, solo (several), fader set (with fade) / adjust; input assign on/off is *Group: assign input* above |
|
||||
| Aux masters | **Aux master:** mute, solo (several), fader set (with fade) / adjust (the master fader of an aux buss; the sends to it are under Aux sends) |
|
||||
| Any buss | **Any buss (aux or group):** mute, solo (several), fader set / adjust, with one dropdown of all 25 busses. These are the original buss actions, kept so existing buttons keep working; use the Group and Aux master actions for new buttons |
|
||||
| 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) |
|
||||
@@ -101,8 +106,8 @@ own naming `lo_filter` is the high-pass filter and `hi_filter` the low-pass filt
|
||||
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
|
||||
**Fades.** Every *set level* action (input, group, aux master, any-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
|
||||
@@ -132,19 +137,23 @@ Every action that changes something the console reports has a feedback for it: a
|
||||
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 /
|
||||
* Boolean (button colour): input muted / solo; aux send on; group assigned; group and aux master muted / solo (and the older any-buss versions); 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.
|
||||
per matrix input); and the meter feedbacks (signal present, peak hold) for inputs, group and aux master outputs, 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
|
||||
and pan; group / aux master / 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.
|
||||
* Group and aux master feedbacks: `group_muted`, `group_solo`, `group_fader_value`, `group_fader_pos`, `group_meter_value`,
|
||||
`group_meter_pos`, `group_signal`, `group_peak`, and the same eight with the prefix `aux_` for aux masters. They have the
|
||||
same options and behave exactly like the older `buss_*` ones (which stay, named "Any buss (aux or group): ...") with
|
||||
the dropdown limited to busses of that type. There are no image-bar versions of the new ones.
|
||||
* 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
|
||||
fader, aux send level, any-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.
|
||||
|
||||
@@ -158,10 +167,10 @@ 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_fader_pos`, `group_fader_pos`, `aux_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 |
|
||||
| `input_meter_pos`, `group_meter_pos`, `aux_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).
|
||||
@@ -193,7 +202,8 @@ buttons.
|
||||
| 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 |
|
||||
| Groups | Per named group buss: a **Mute** button (red when muted) and a **Fader (knob)** (turn = fader, press = mute, with a gauge) |
|
||||
| Aux masters | The same for each named aux buss's master fader (the sends to an aux are in *Sends to aux name*) |
|
||||
| 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 |
|
||||
@@ -201,7 +211,7 @@ buttons.
|
||||
| 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 |
|
||||
| Group meters, Aux meters, Matrix meters | One level meter button per named group buss, per named aux 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
|
||||
@@ -222,10 +232,10 @@ 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_meter_value`, `group_meter_value`, `aux_meter_value`, `buss_meter_value`, `matrix_meter_value` | text: `-42` or `-inf` (dBFS, approx.); gain reduction meters: `3` (dB) |
|
||||
| `input_meter_pos`, `group_meter_pos`, `aux_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`, `group_signal`, `aux_signal`, `buss_signal`, `matrix_signal` | signal present: level above a dBFS threshold (option, default -50; gain reduction meters: any reduction) |
|
||||
| `input_peak`, `group_peak`, `aux_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.
|
||||
|
||||
@@ -23,7 +23,9 @@ export const opt = {
|
||||
const choices = kind === 'aux' ? inst.state.choices.auxChoices() : kind === 'group' ? inst.state.choices.groupChoices() : inst.state.choices.busses()
|
||||
return opt.dropdown(id, label || (kind === 'aux' ? 'Aux buss' : kind === 'group' ? 'Group buss' : 'Buss'), choices)
|
||||
},
|
||||
busses: (inst, id = 'busses', label = 'Busses') => opt.multi(id, label, inst.state.choices.busses()),
|
||||
/** Several busses; `kind` 'aux' or 'group' lists only busses of that type (without the suffix), 'all' lists every buss. */
|
||||
busses: (inst, id = 'busses', label = 'Busses', kind = 'all') =>
|
||||
opt.multi(id, label, kind === 'aux' ? inst.state.choices.auxChoices() : kind === 'group' ? inst.state.choices.groupChoices() : inst.state.choices.busses()),
|
||||
matrix: (inst, id = 'matrix', label = 'Matrix output') => opt.dropdown(id, label, inst.state.choices.matrix()),
|
||||
matrixIn: (inst, id = 'matrixIn', label = 'Matrix input') => opt.dropdown(id, label, inst.state.choices.matrixInputs()),
|
||||
cg: (inst, id = 'cg', label = 'Control group') => opt.dropdown(id, label, inst.state.choices.cgs()),
|
||||
@@ -147,6 +149,36 @@ function quiet(inst, defs) {
|
||||
export function getActionDefinitions(inst) {
|
||||
const each = (list, fn) => Promise.all(numbers(list).map(fn))
|
||||
const n = (v) => Number(v)
|
||||
/**
|
||||
* Mute, solo and fader actions for one type of buss (`prefix` 'group' or 'aux'). They are thin wrappers over the same
|
||||
* buss operations as the buss_* actions (the address follows the buss's aux / group side), with the dropdowns
|
||||
* filtered by type. The option ids are the same as the buss_* ones.
|
||||
*/
|
||||
const bussActions = (prefix, title, one, many) => ({
|
||||
[`${prefix}_mute`]: {
|
||||
name: `${title}: mute`,
|
||||
description: 'Mute, unmute or toggle one or more.',
|
||||
options: [opt.busses(inst, 'busses', many, prefix), opt.state()],
|
||||
callback: async ({ options }) => void (await each(options.busses, (b) => inst.setSwitch('buss', 'mute', b, options.state))),
|
||||
},
|
||||
[`${prefix}_solo`]: {
|
||||
name: `${title}: solo`,
|
||||
options: [opt.busses(inst, 'busses', many, prefix), opt.state()],
|
||||
callback: async ({ options }) => void (await each(options.busses, (b) => inst.setSwitch('buss', 'solo', b, options.state))),
|
||||
},
|
||||
[`${prefix}_fader_set`]: {
|
||||
name: `${title}: set fader`,
|
||||
description: FADE_NOTE,
|
||||
options: [opt.buss(inst, prefix, 'buss', one), opt.level(), opt.duration(), opt.curve()],
|
||||
callback: async ({ options }) => inst.setFader('buss', n(options.buss), n(options.level), ...fadeArgs(options)),
|
||||
},
|
||||
[`${prefix}_fader_adjust`]: {
|
||||
name: `${title}: adjust fader (relative)`,
|
||||
description: `For rotary encoders. ${ADJUST_NOTE}`,
|
||||
options: [opt.buss(inst, prefix, 'buss', one), opt.delta()],
|
||||
callback: async ({ options }) => inst.adjustFader('buss', n(options.buss), n(options.delta)),
|
||||
},
|
||||
})
|
||||
return quiet(inst, {
|
||||
...getParamActions(inst),
|
||||
input_mute: {
|
||||
@@ -221,24 +253,27 @@ export function getActionDefinitions(inst) {
|
||||
callback: async ({ options }) => inst.setGroup(n(options.channel), n(options.buss), options.state),
|
||||
},
|
||||
|
||||
...bussActions('group', 'Group', 'Group', 'Groups'),
|
||||
...bussActions('aux', 'Aux master', 'Aux master', 'Aux masters'),
|
||||
buss_mute: {
|
||||
name: 'Buss: mute',
|
||||
name: 'Any buss (aux or group): mute',
|
||||
description: 'Mute, unmute or toggle one or more busses, aux and group alike (one list of all busses).',
|
||||
options: [opt.busses(inst), opt.state()],
|
||||
callback: async ({ options }) => void (await each(options.busses, (b) => inst.setSwitch('buss', 'mute', b, options.state))),
|
||||
},
|
||||
buss_solo: {
|
||||
name: 'Buss: solo',
|
||||
name: 'Any buss (aux or group): solo',
|
||||
options: [opt.busses(inst), opt.state()],
|
||||
callback: async ({ options }) => void (await each(options.busses, (b) => inst.setSwitch('buss', 'solo', b, options.state))),
|
||||
},
|
||||
buss_fader_set: {
|
||||
name: 'Buss: set fader',
|
||||
name: 'Any buss (aux or group): set fader',
|
||||
description: FADE_NOTE,
|
||||
options: [opt.buss(inst), opt.level(), opt.duration(), opt.curve()],
|
||||
callback: async ({ options }) => inst.setFader('buss', n(options.buss), n(options.level), ...fadeArgs(options)),
|
||||
},
|
||||
buss_fader_adjust: {
|
||||
name: 'Buss: adjust fader (relative)',
|
||||
name: 'Any buss (aux or group): adjust fader (relative)',
|
||||
description: `For rotary encoders. ${ADJUST_NOTE}`,
|
||||
options: [opt.buss(inst), opt.delta()],
|
||||
callback: async ({ options }) => inst.adjustFader('buss', n(options.buss), n(options.delta)),
|
||||
|
||||
@@ -129,16 +129,30 @@ export function getFeedbackDefinitions(inst) {
|
||||
return at > 0 && Date.now() - at <= n(o.hold ?? holdOption.default) * 1000
|
||||
}
|
||||
const meterText = (m, gr) => (gr ? String(meterGrDb(m?.value)) : fmtMeterLevel(m?.value))
|
||||
const outputMeters = (ent, id, entOption, who) => ({
|
||||
[`${ent}_signal`]: bool(`${who} meter: signal present`, GREEN, [entOption(), signalOption], outputKey(ent, id), ({ options }) => levelSignal(outputMeterOf(ent, id, options), options)),
|
||||
[`${ent}_peak`]: bool(`${who} meter: peak hold (dBFS, approx.)`, RED, [entOption(), peakOption, holdOption], outputKey(ent, id), ({ options }) => {
|
||||
const outputMeters = (prefix, ent, id, entOption, who) => ({
|
||||
[`${prefix}_signal`]: bool(`${who} meter: signal present`, GREEN, [entOption(), signalOption], outputKey(ent, id), ({ options }) => levelSignal(outputMeterOf(ent, id, options), options)),
|
||||
[`${prefix}_peak`]: bool(`${who} meter: peak hold (dBFS, approx.)`, RED, [entOption(), peakOption, holdOption], outputKey(ent, id), ({ options }) => {
|
||||
outputMeterOf(ent, id, options)
|
||||
return levelPeak(outputMeterId(ent, n(options[id])), options)
|
||||
}),
|
||||
[`${ent}_meter_value`]: value(`${who} meter: level text (dBFS, approx.)`, [entOption()], outputKey(ent, id), ({ options }) => fmtMeterLevel(outputMeterOf(ent, id, options)?.value)),
|
||||
[`${ent}_meter_pos`]: value(`${who} meter: position (0-100 over -60..0 dBFS, for gauges)`, [entOption()], outputKey(ent, id), ({ options }) => meterPosition(outputMeterOf(ent, id, options)?.value, 'level')),
|
||||
[`${prefix}_meter_value`]: value(`${who} meter: level text (dBFS, approx.)`, [entOption()], outputKey(ent, id), ({ options }) => fmtMeterLevel(outputMeterOf(ent, id, options)?.value)),
|
||||
[`${prefix}_meter_pos`]: value(`${who} meter: position (0-100 over -60..0 dBFS, for gauges)`, [entOption()], outputKey(ent, id), ({ options }) => meterPosition(outputMeterOf(ent, id, options)?.value, 'level')),
|
||||
})
|
||||
const entityKey = (ent, id) => (o) => watchKey.entity(ent, n(o[id]))
|
||||
/**
|
||||
* Muted / solo / fader level text / fader position for a buss. `prefix` is the feedback id prefix: 'buss' lists every
|
||||
* buss, 'group' and 'aux' only busses of that type (the dropdown is the only difference; the watch key is the buss's).
|
||||
*/
|
||||
const bussFeedbacks = (prefix, kind, title, label) => {
|
||||
const one = () => opt.buss(inst, kind, 'buss', label)
|
||||
const key = entityKey('buss', 'buss')
|
||||
return {
|
||||
[`${prefix}_muted`]: bool(`${title}: muted`, RED, [one()], key, ({ options }) => s.getMute('buss', n(options.buss)) === true),
|
||||
[`${prefix}_solo`]: bool(`${title}: solo on`, AMBER, [one()], key, ({ options }) => s.getSolo('buss', n(options.buss)) === true),
|
||||
[`${prefix}_fader_value`]: value(`${title}: fader level (dB text)`, [one()], key, ({ options }) => fmtDb(s.getFader('buss', n(options.buss))) || '--'),
|
||||
[`${prefix}_fader_pos`]: value(`${title}: fader position (0-100, for gauges)`, [one()], key, ({ options }) => faderPosition(s.getFader('buss', n(options.buss)))),
|
||||
}
|
||||
}
|
||||
const sendKey = (o) => watchKey.send(n(o.channel), n(o.buss))
|
||||
const msendKey = (o) => watchKey.msend(n(o.matrixIn), n(o.matrixOut))
|
||||
const msendOf = (o) => {
|
||||
@@ -201,8 +215,9 @@ export function getFeedbackDefinitions(inst) {
|
||||
inst.ensureGroup(n(options.channel), n(options.buss))
|
||||
return s.getGroup(n(options.channel), n(options.buss)) === true
|
||||
}),
|
||||
buss_muted: bool('Buss: muted', RED, [opt.buss(inst)], entityKey('buss', 'buss'), ({ options }) => s.getMute('buss', n(options.buss)) === true),
|
||||
buss_solo: bool('Buss: solo on', AMBER, [opt.buss(inst)], entityKey('buss', 'buss'), ({ options }) => s.getSolo('buss', n(options.buss)) === true),
|
||||
...bussFeedbacks('buss', 'all', 'Any buss (aux or group)', 'Buss'),
|
||||
...bussFeedbacks('group', 'group', 'Group', 'Group'),
|
||||
...bussFeedbacks('aux', 'aux', 'Aux master', 'Aux master'),
|
||||
matrix_muted: bool('Matrix output: muted', RED, [opt.matrix(inst)], entityKey('matrix', 'matrix'), ({ options }) => s.getMute('matrix', n(options.matrix)) === true),
|
||||
cg_muted: bool('Control group: muted', RED, [opt.cg(inst)], entityKey('cg', 'cg'), ({ options }) => s.getMute('cg', n(options.cg)) === true),
|
||||
matrix_solo: bool('Matrix output: solo on', AMBER, [opt.matrix(inst)], entityKey('matrix', 'matrix'), ({ options }) => s.getSolo('matrix', n(options.matrix)) === true),
|
||||
@@ -270,8 +285,10 @@ export function getFeedbackDefinitions(inst) {
|
||||
return !isGr(options) && levelPeak(meterId(n(options.channel), 'level'), options)
|
||||
},
|
||||
),
|
||||
...outputMeters('buss', 'buss', () => opt.buss(inst), 'Buss'),
|
||||
...outputMeters('matrix', 'matrix', () => opt.matrix(inst), 'Matrix output'),
|
||||
...outputMeters('buss', 'buss', 'buss', () => opt.buss(inst), 'Any buss (aux or group)'),
|
||||
...outputMeters('group', 'buss', 'buss', () => opt.buss(inst, 'group', 'buss', 'Group'), 'Group'),
|
||||
...outputMeters('aux', 'buss', 'buss', () => opt.buss(inst, 'aux', 'buss', 'Aux master'), 'Aux master'),
|
||||
...outputMeters('matrix', 'matrix', 'matrix', () => opt.matrix(inst), 'Matrix output'),
|
||||
|
||||
input_fader_value: value('Input: fader level (dB text)', [opt.channel(inst)], entityKey('input', 'channel'), ({ options }) => fmtDb(s.getFader('input', n(options.channel))) || '--'),
|
||||
input_pan_value: value('Input: pan (text)', [opt.channel(inst)], entityKey('input', 'channel'), ({ options }) => fmtPan(s.getPan(n(options.channel))) || '--'),
|
||||
@@ -284,7 +301,6 @@ export function getFeedbackDefinitions(inst) {
|
||||
const on = s.getSend(n(options.channel), n(options.buss)).on
|
||||
return on === undefined ? '--' : on ? 'On' : 'Off'
|
||||
}),
|
||||
buss_fader_value: value('Buss: fader level (dB text)', [opt.buss(inst)], entityKey('buss', 'buss'), ({ options }) => fmtDb(s.getFader('buss', n(options.buss))) || '--'),
|
||||
matrix_fader_value: value('Matrix output: fader level (dB text)', [opt.matrix(inst)], entityKey('matrix', 'matrix'), ({ options }) => fmtDb(s.getFader('matrix', n(options.matrix))) || '--'),
|
||||
cg_fader_value: value('Control group: fader level (dB text)', [opt.cg(inst)], entityKey('cg', 'cg'), ({ options }) => fmtDb(s.getFader('cg', n(options.cg))) || '--'),
|
||||
send_pan_value: value('Aux send: pan (text)', [opt.channel(inst), opt.buss(inst, 'aux')], sendKey, ({ options }) => {
|
||||
@@ -295,7 +311,6 @@ export function getFeedbackDefinitions(inst) {
|
||||
geq_band_value: value('Graphic EQ: band gain (dB text)', [opt.geq(inst), opt.band()], geqGainKey, ({ options }) => fmtDb(geqGainOf(options)) || '--'),
|
||||
// Numbers for Companion's native Gauge element (use as a button local variable, see the Gauges section of the help)
|
||||
input_fader_pos: value('Input: fader position (0-100, for gauges)', [opt.channel(inst)], entityKey('input', 'channel'), ({ options }) => faderPosition(s.getFader('input', n(options.channel)))),
|
||||
buss_fader_pos: value('Buss: fader position (0-100, for gauges)', [opt.buss(inst)], entityKey('buss', 'buss'), ({ options }) => faderPosition(s.getFader('buss', n(options.buss)))),
|
||||
matrix_fader_pos: value('Matrix output: fader position (0-100, for gauges)', [opt.matrix(inst)], entityKey('matrix', 'matrix'), ({ options }) => faderPosition(s.getFader('matrix', n(options.matrix)))),
|
||||
cg_fader_pos: value('Control group: fader position (0-100, for gauges)', [opt.cg(inst)], entityKey('cg', 'cg'), ({ options }) => faderPosition(s.getFader('cg', n(options.cg)))),
|
||||
send_level_pos: value('Aux send: level position (0-100, for gauges)', [opt.channel(inst), opt.buss(inst, 'aux')], sendKey, ({ options }) => {
|
||||
@@ -328,7 +343,7 @@ export function getFeedbackDefinitions(inst) {
|
||||
const v = s.getSend(n(fb.options.channel), n(fb.options.buss)).level
|
||||
return v === undefined ? {} : horizontalBar({ ...image, fraction: levelFraction(v), color: fb.options.color })
|
||||
}),
|
||||
buss_fader_bar: bar('Buss: fader bar (legacy image)', [opt.buss(inst)], entityKey('buss', 'buss'), (fb, image) => {
|
||||
buss_fader_bar: bar('Any buss (aux or group): fader bar (legacy image)', [opt.buss(inst)], entityKey('buss', 'buss'), (fb, image) => {
|
||||
const v = s.getFader('buss', n(fb.options.buss))
|
||||
return v === undefined ? {} : horizontalBar({ ...image, fraction: levelFraction(v), color: fb.options.color })
|
||||
}),
|
||||
@@ -392,14 +407,14 @@ export function watchKeyForEvent(ev) {
|
||||
export function feedbackIdsForEvent(ev) {
|
||||
switch (ev.kind) {
|
||||
case 'mute':
|
||||
return { input: ['input_muted'], buss: ['buss_muted'], matrix: ['matrix_muted'], cg: ['cg_muted'] }[ev.ent] || []
|
||||
return { input: ['input_muted'], buss: ['buss_muted', 'group_muted', 'aux_muted'], matrix: ['matrix_muted'], cg: ['cg_muted'] }[ev.ent] || []
|
||||
case 'solo':
|
||||
return { input: ['input_solo'], buss: ['buss_solo'], matrix: ['matrix_solo'], cg: ['cg_solo'] }[ev.ent] || []
|
||||
return { input: ['input_solo'], buss: ['buss_solo', 'group_solo', 'aux_solo'], matrix: ['matrix_solo'], cg: ['cg_solo'] }[ev.ent] || []
|
||||
case 'fader':
|
||||
return (
|
||||
{
|
||||
input: ['input_fader_value', 'input_fader_pos', 'input_fader_bar', 'level_compare'],
|
||||
buss: ['buss_fader_value', 'buss_fader_pos', 'buss_fader_bar', 'level_compare'],
|
||||
buss: ['buss_fader_value', 'buss_fader_pos', 'buss_fader_bar', 'group_fader_value', 'group_fader_pos', 'aux_fader_value', 'aux_fader_pos', 'level_compare'],
|
||||
matrix: ['matrix_fader_value', 'matrix_fader_pos', 'matrix_fader_bar', 'level_compare'],
|
||||
cg: ['cg_fader_value', 'cg_fader_pos', 'cg_fader_bar', 'level_compare'],
|
||||
}[ev.ent] || []
|
||||
|
||||
+33
-25
@@ -19,6 +19,12 @@ const ON_GREEN = 0x00a050 // a send is on / an input is assigned
|
||||
const MUTED_RED = 0xb01010 // muted, peak
|
||||
const SIGNAL_BG = 0x00501e // signal present on a meter button
|
||||
|
||||
/** The two kinds of buss, each with its own actions, feedbacks and preset sections (the console's aux or group side of a buss). */
|
||||
const BUSS_KINDS = [
|
||||
{ kind: 'group', prefix: 'group', section: 'groups', meterSection: 'meters_groups', title: 'Group', meterTitle: 'Group' },
|
||||
{ kind: 'aux', prefix: 'aux', section: 'aux_masters', meterSection: 'meters_aux', title: 'Aux master', meterTitle: 'Aux' },
|
||||
]
|
||||
|
||||
const fb = (feedbackId, options, bg, color = WHITE) => ({ feedbackId, options, style: { bgcolor: bg, color } })
|
||||
const act = (actionId, options) => ({ actionId, options })
|
||||
|
||||
@@ -209,27 +215,27 @@ const METER_TRACK_AMOUNT = 20
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Busses
|
||||
{
|
||||
// ---- Groups and aux masters: per named buss of that type, a mute button and a fader knob
|
||||
for (const { kind, prefix, section, title } of BUSS_KINDS) {
|
||||
const muteIds = []
|
||||
const knobIds = []
|
||||
for (const n of s.named('buss')) {
|
||||
const tag = `${n} · ${s.displayName('buss', n)} (${s.bussMode(n)})`
|
||||
presets[`buss_mute_${n}`] = simple(`Master mute: ${tag}`, nameVar('buss', n), [act('buss_mute', { busses: [n], state: 'toggle' })], [fb('buss_muted', { buss: n }, RED)])
|
||||
presets[`buss_knob_${n}`] = layeredKnob({
|
||||
name: `Master fader (knob): ${tag}`,
|
||||
for (const n of s.namedBusses(kind)) {
|
||||
const tag = `${n} · ${s.displayName('buss', n)}`
|
||||
presets[`${prefix}_mute_${n}`] = simple(`${title} mute: ${tag}`, nameVar('buss', n), [act(`${prefix}_mute`, { busses: [n], state: 'toggle' })], [fb(`${prefix}_muted`, { buss: n }, RED)])
|
||||
presets[`${prefix}_knob_${n}`] = layeredKnob({
|
||||
name: `${title} fader (knob): ${tag}`,
|
||||
title: nameVar('buss', n),
|
||||
press: [act('buss_mute', { busses: [n], state: 'toggle' })],
|
||||
adjust: ['buss_fader_adjust', { buss: n }],
|
||||
level: ['buss_fader_value', { buss: n }],
|
||||
pos: ['buss_fader_pos', { buss: n }],
|
||||
feedbacks: [tint('buss_muted', { buss: n }, MUTED_RED)],
|
||||
press: [act(`${prefix}_mute`, { busses: [n], state: 'toggle' })],
|
||||
adjust: [`${prefix}_fader_adjust`, { buss: n }],
|
||||
level: [`${prefix}_fader_value`, { buss: n }],
|
||||
pos: [`${prefix}_fader_pos`, { buss: n }],
|
||||
feedbacks: [tint(`${prefix}_muted`, { buss: n }, MUTED_RED)],
|
||||
})
|
||||
muteIds.push(`buss_mute_${n}`)
|
||||
knobIds.push(`buss_knob_${n}`)
|
||||
muteIds.push(`${prefix}_mute_${n}`)
|
||||
knobIds.push(`${prefix}_knob_${n}`)
|
||||
}
|
||||
if (muteIds.length) {
|
||||
structure.push({ id: 'busses', name: 'Busses', definitions: [group('busses_mute', 'Master mute', muteIds), group('busses_knob', 'Master fader (knob)', knobIds)] })
|
||||
structure.push({ id: section, name: `${title}s`, definitions: [group(`${section}_mute`, 'Mute', muteIds), group(`${section}_knob`, 'Fader (knob)', knobIds)] })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,19 +368,21 @@ const METER_TRACK_AMOUNT = 20
|
||||
|
||||
// ---- Output meters: one layered meter per buss and per matrix output
|
||||
{
|
||||
const bussIds = []
|
||||
for (const n of s.named('buss')) {
|
||||
presets[`buss_meter_${n}`] = layeredMeter({
|
||||
name: `Buss meter: ${n} · ${s.displayName('buss', n)} (${s.bussMode(n)})`,
|
||||
for (const { kind, prefix, meterSection, meterTitle } of BUSS_KINDS) {
|
||||
const ids = []
|
||||
for (const n of s.namedBusses(kind)) {
|
||||
presets[`${prefix}_meter_${n}`] = layeredMeter({
|
||||
name: `${meterTitle} meter: ${n} · ${s.displayName('buss', n)}`,
|
||||
title: nameVar('buss', n),
|
||||
posFeedback: ['buss_meter_pos', { buss: n }],
|
||||
signal: ['buss_signal', { buss: n, thresholdDb: -50 }],
|
||||
peak: ['buss_peak', { buss: n, peakDb: -1, hold: 2 }],
|
||||
posFeedback: [`${prefix}_meter_pos`, { buss: n }],
|
||||
signal: [`${prefix}_signal`, { buss: n, thresholdDb: -50 }],
|
||||
peak: [`${prefix}_peak`, { buss: n, peakDb: -1, hold: 2 }],
|
||||
})
|
||||
bussIds.push(`buss_meter_${n}`)
|
||||
ids.push(`${prefix}_meter_${n}`)
|
||||
}
|
||||
if (ids.length) {
|
||||
structure.push({ id: meterSection, name: `${meterTitle} meters`, description: 'Meters subscribe on the console when the button first renders.', definitions: [group(`${meterSection}_level`, 'Level', ids)] })
|
||||
}
|
||||
if (bussIds.length) {
|
||||
structure.push({ id: 'buss_meters', name: 'Buss meters', description: 'Meters subscribe on the console when the button first renders.', definitions: [group('buss_meters_level', 'Level', bussIds)] })
|
||||
}
|
||||
const matrixIds = []
|
||||
for (const n of s.numbers('matrix')) {
|
||||
|
||||
+4
-2
@@ -417,11 +417,13 @@ export class State {
|
||||
const entity = (ent, suffix) => () => this.numbers(ent).map((n) => ({ id: n, label: `${n} · ${this.displayName(ent, n)}${suffix ? suffix(n) : ''}` }))
|
||||
const bussSuffix = (n) => ` (${this.bussMode(n)})`
|
||||
const busses = entity('buss', bussSuffix)
|
||||
const plainBusses = entity('buss')
|
||||
return {
|
||||
inputs: entity('input'),
|
||||
busses,
|
||||
auxChoices: () => busses().filter((c) => this.bussMode(c.id) === 'aux'),
|
||||
groupChoices: () => busses().filter((c) => this.bussMode(c.id) === 'group'),
|
||||
// filtered by type, so the "(aux)" / "(group)" suffix would only repeat what the dropdown already says
|
||||
auxChoices: () => plainBusses().filter((c) => this.bussMode(c.id) === 'aux'),
|
||||
groupChoices: () => plainBusses().filter((c) => this.bussMode(c.id) === 'group'),
|
||||
matrix: entity('matrix'),
|
||||
matrixInputs: entity('minput'),
|
||||
cgs: entity('cg'),
|
||||
|
||||
+127
-9
@@ -425,12 +425,12 @@ check('input_60_pan' in ctx.variableDefs && 'cg_10_mute' in ctx.variableDefs &&
|
||||
check(entityOptions.length > 40 && entityOptions.every((o) => (o.type === 'dropdown' || o.type === 'multidropdown') && o.minChoicesForSearch === 0 && o.choices.every((c) => typeof c.id === 'number')), `all ${entityOptions.length} entity options are searchable dropdowns with numeric ids`)
|
||||
const levels = all.flatMap((d) => d.options).filter((o) => o.id === 'level')
|
||||
const thresholds = all.flatMap((d) => d.options).filter((o) => o.id === 'threshold' && o.min === -150)
|
||||
check(levels.length === 6 && levels.every((o) => o.type === 'number' && o.showMinAsNegativeInfinity === true && (o.min === -150 || o.min === -90)) && thresholds.every((o) => o.showMinAsNegativeInfinity === true), `level options use showMinAsNegativeInfinity (${levels.length} level + ${thresholds.length} threshold options)`)
|
||||
check(levels.length === 8 && levels.every((o) => o.type === 'number' && o.showMinAsNegativeInfinity === true && (o.min === -150 || o.min === -90)) && thresholds.every((o) => o.showMinAsNegativeInfinity === true), `level options use showMinAsNegativeInfinity (${levels.length} level + ${thresholds.length} threshold options)`)
|
||||
check(ctx.actions.matrix_send_level_set.options[2].min === -90 && ctx.actions.matrix_send_level_set.options[2].showMinAsNegativeInfinity === true && ctx.actions.send_level_set.options[2].min === -150 && ctx.actions.input_fader_set.options[1].min === -150 && ctx.actions.cg_fader_set.options[1].min === -150, 'per-kind floor: a matrix send level goes down to -90, aux sends and faders to -150')
|
||||
const adjusts = Object.entries(ctx.actions).filter(([id]) => /(fader|send_level)_adjust$/.test(id))
|
||||
check(adjusts.length === 6 && adjusts.every(([, a]) => a.description.includes('From \u2212\u221e the first step up jumps to \u221260 dB')), `all ${adjusts.length} level adjust actions say that the first step up from -inf jumps to -60 dB`)
|
||||
check(adjusts.length === 8 && adjusts.every(([, a]) => a.description.includes('From \u2212\u221e the first step up jumps to \u221260 dB')), `all ${adjusts.length} level adjust actions say that the first step up from -inf jumps to -60 dB`)
|
||||
check(ctx.actions.input_mute.options[0].choices[0].label === '1 · KICK In' && ctx.actions.send_on.options[1].choices.every((c) => S.bussMode(c.id) === 'aux'), 'action dropdowns show the live labels; send actions list aux busses only')
|
||||
const idsExpected = ['input_mute', 'input_solo', 'input_fader_set', 'input_fader_adjust', 'input_pan_set', 'input_pan_adjust', 'input_name_set', 'send_on', 'send_level_set', 'send_level_adjust', 'send_pan_set', 'send_pan_adjust', 'group_assign', 'buss_mute', 'buss_solo', 'buss_fader_set', 'buss_fader_adjust', 'matrix_mute', 'matrix_solo', 'matrix_fader_set', 'matrix_fader_adjust', 'matrix_send_on', 'matrix_send_level_set', 'matrix_send_level_adjust', 'cg_mute', 'cg_solo', 'cg_fader_set', 'cg_fader_adjust', 'geq_band_set', 'geq_band_adjust', 'geq_on', 'snapshot_fire', 'snapshot_next', 'snapshot_prev', 'reload', 'raw_osc', 'relay_forget', 'fade_stop']
|
||||
const idsExpected = ['input_mute', 'input_solo', 'input_fader_set', 'input_fader_adjust', 'input_pan_set', 'input_pan_adjust', 'input_name_set', 'send_on', 'send_level_set', 'send_level_adjust', 'send_pan_set', 'send_pan_adjust', 'group_assign', 'buss_mute', 'buss_solo', 'buss_fader_set', 'buss_fader_adjust', 'matrix_mute', 'matrix_solo', 'matrix_fader_set', 'matrix_fader_adjust', 'matrix_send_on', 'matrix_send_level_set', 'matrix_send_level_adjust', 'cg_mute', 'cg_solo', 'cg_fader_set', 'cg_fader_adjust', 'geq_band_set', 'geq_band_adjust', 'geq_on', 'snapshot_fire', 'snapshot_next', 'snapshot_prev', 'reload', 'raw_osc', 'relay_forget', 'fade_stop', 'group_mute', 'group_solo', 'group_fader_set', 'group_fader_adjust', 'aux_mute', 'aux_solo', 'aux_fader_set', 'aux_fader_adjust']
|
||||
// per-channel switches and values, generated from the PARAMS table
|
||||
for (const [param, p] of Object.entries(PARAMS)) {
|
||||
for (const ent of Object.keys(p.paths)) idsExpected.push(...(p.type === 'switch' ? [`${ent}_${param}`] : [`${ent}_${param}_set`, `${ent}_${param}_adjust`]))
|
||||
@@ -438,6 +438,7 @@ check('input_60_pan' in ctx.variableDefs && 'cg_10_mute' in ctx.variableDefs &&
|
||||
check(idsExpected.every((id) => ctx.actions[id]) && Object.keys(ctx.actions).length === idsExpected.length, `all ${idsExpected.length} designed actions exist (${Object.keys(ctx.actions).length} registered)`)
|
||||
check(!('macro_press' in ctx.actions), 'no first-class macro action (the console never answered a macro query)')
|
||||
const fbExpected = ['input_muted', 'input_solo', 'send_on', 'group_assigned', 'buss_muted', 'buss_solo', 'matrix_muted', 'cg_muted', 'snapshot_current', 'level_compare', 'send_level_compare', 'connected', 'relay_clients', 'fade_running', 'input_signal', 'input_peak', 'input_fader_value', 'input_pan_value', 'send_level_value', 'send_on_value', 'buss_fader_value', 'matrix_fader_value', 'cg_fader_value', 'snapshot_name_value', 'input_meter_value', 'input_fader_bar', 'send_level_bar', 'buss_fader_bar', 'input_meter_bar']
|
||||
for (const k of ['group', 'aux']) fbExpected.push(`${k}_muted`, `${k}_solo`, `${k}_fader_value`, `${k}_fader_pos`, `${k}_meter_value`, `${k}_meter_pos`, `${k}_signal`, `${k}_peak`)
|
||||
fbExpected.push('buss_signal', 'buss_peak', 'buss_meter_value', 'buss_meter_pos', 'matrix_signal', 'matrix_peak', 'matrix_meter_value', 'matrix_meter_pos')
|
||||
fbExpected.push('input_fader_pos', 'buss_fader_pos', 'matrix_fader_pos', 'cg_fader_pos', 'send_level_pos', 'matrix_send_level_pos', 'input_pan_num', 'send_pan_num', 'geq_band_db', 'input_meter_pos')
|
||||
fbExpected.push('matrix_solo', 'cg_solo', 'matrix_send_on', 'matrix_send_off', 'geq_on', 'send_pan_value', 'matrix_send_level_value', 'geq_band_value', 'matrix_fader_bar', 'cg_fader_bar', 'matrix_send_level_bar', 'geq_band_bar')
|
||||
@@ -456,6 +457,8 @@ check('input_60_pan' in ctx.variableDefs && 'cg_10_mute' in ctx.variableDefs &&
|
||||
geq_on: ['geq_on'], geq_band_set: ['geq_band_value', 'geq_band_bar'], geq_band_adjust: ['geq_band_value', 'geq_band_bar'],
|
||||
snapshot_fire: ['snapshot_current', 'snapshot_name_value'], snapshot_next: ['snapshot_current', 'snapshot_name_value'], snapshot_prev: ['snapshot_current', 'snapshot_name_value'],
|
||||
reload: ['connected'], relay_forget: ['relay_clients'], fade_stop: ['fade_running'],
|
||||
group_mute: ['group_muted'], group_solo: ['group_solo'], group_fader_set: ['group_fader_value'], group_fader_adjust: ['group_fader_value'],
|
||||
aux_mute: ['aux_muted'], aux_solo: ['aux_solo'], aux_fader_set: ['aux_fader_value'], aux_fader_adjust: ['aux_fader_value'],
|
||||
input_name_set: [], // the name is the variable input_N_name
|
||||
raw_osc: [], // free-form
|
||||
}
|
||||
@@ -465,18 +468,18 @@ check('input_60_pan' in ctx.variableDefs && 'cg_10_mute' in ctx.variableDefs &&
|
||||
else PARITY[`${ent}_${param}_set`] = PARITY[`${ent}_${param}_adjust`] = [`${ent}_${param}_value`]
|
||||
}
|
||||
}
|
||||
const GAUGE = { input_fader: 'input_fader_pos', send_level: 'send_level_pos', buss_fader: 'buss_fader_pos', matrix_fader: 'matrix_fader_pos', matrix_send_level: 'matrix_send_level_pos', cg_fader: 'cg_fader_pos', input_pan: 'input_pan_num', send_pan: 'send_pan_num', geq_band: 'geq_band_db' }
|
||||
const GAUGE = { input_fader: 'input_fader_pos', send_level: 'send_level_pos', buss_fader: 'buss_fader_pos', group_fader: 'group_fader_pos', aux_fader: 'aux_fader_pos', matrix_fader: 'matrix_fader_pos', matrix_send_level: 'matrix_send_level_pos', cg_fader: 'cg_fader_pos', input_pan: 'input_pan_num', send_pan: 'send_pan_num', geq_band: 'geq_band_db' }
|
||||
for (const id of Object.keys(PARITY)) {
|
||||
const g = GAUGE[id.replace(/_(set|adjust)$/, '')]
|
||||
if (g && /_(set|adjust)$/.test(id)) PARITY[id] = [...PARITY[id], g]
|
||||
}
|
||||
const noGauge = Object.keys(ctx.actions).filter((id) => /^(input_fader|send_level|buss_fader|matrix_fader|matrix_send_level|cg_fader|input_pan|send_pan|geq_band)_(set|adjust)$/.test(id) && !(PARITY[id] || []).some((f) => /_(pos|num|db)$/.test(f)))
|
||||
const noGauge = Object.keys(ctx.actions).filter((id) => /^(input_fader|send_level|buss_fader|group_fader|aux_fader|matrix_fader|matrix_send_level|cg_fader|input_pan|send_pan|geq_band)_(set|adjust)$/.test(id) && !(PARITY[id] || []).some((f) => /_(pos|num|db)$/.test(f)))
|
||||
check(noGauge.length === 0 && Object.values(GAUGE).every((f) => ctx.feedbacks[f]?.type === 'value'), `every level and pan action has a numeric value feedback for gauges (${Object.values(GAUGE).join(', ')})`)
|
||||
const noFeedback = Object.keys(ctx.actions).filter((id) => !(id in PARITY))
|
||||
const missing = Object.entries(PARITY).filter(([id, fbs]) => id in ctx.actions && fbs.some((f) => !ctx.feedbacks[f])).map(([id]) => id)
|
||||
const levelActions = Object.keys(ctx.actions).filter((id) => /_(set|adjust)$/.test(id) && /(fader|level|pan|band|trim|gain|freq)/.test(id))
|
||||
const lacksValue = levelActions.filter((id) => !(PARITY[id] || []).some((f) => /_value$/.test(f)))
|
||||
const lacksBar = levelActions.filter((id) => /(fader|send_level)/.test(id) && !(PARITY[id] || []).some((f) => /_bar$/.test(f)))
|
||||
const lacksBar = levelActions.filter((id) => /(fader|send_level)/.test(id) && !/^(group|aux)_/.test(id) && !(PARITY[id] || []).some((f) => /_bar$/.test(f)))
|
||||
check(noFeedback.length === 0 && missing.length === 0 && lacksValue.length === 0 && lacksBar.length === 0, `every action has its feedback (${Object.keys(ctx.actions).length} actions; no feedback listed: ${noFeedback.join(', ') || 'none'}; unknown feedback: ${missing.join(', ') || 'none'}; level without value: ${lacksValue.join(', ') || 'none'}; fader/send level without bar: ${lacksBar.join(', ') || 'none'})`)
|
||||
check(Object.values(ctx.feedbacks).filter((f) => f.type === 'advanced').every((f) => Array.isArray(f.affectedProperties)), 'advanced feedbacks declare affectedProperties')
|
||||
}
|
||||
@@ -529,7 +532,7 @@ check('input_60_pan' in ctx.variableDefs && 'cg_10_mute' in ctx.variableDefs &&
|
||||
const knobs = ids.filter((id) => /_knob_/.test(id))
|
||||
check(knobs.length > 100 && knobs.every((id) => P[id].steps[0].rotate_left?.length && P[id].steps[0].rotate_right?.length), `all ${knobs.length} knob presets have rotate_left and rotate_right`)
|
||||
const sections = ctx.presetStructure.map((s) => s.id)
|
||||
check(['inputs', 'busses', 'cgs', 'matrix', 'snapshots', 'console', 'meters', 'geqs'].every((s) => sections.includes(s)) && sections.some((s) => s.startsWith('sends_')) && sections.some((s) => s.startsWith('group_')) && sections.some((s) => s.startsWith('msends_')), `preset sections cover inputs, busses, sends, groups, matrix, matrix sends, graphic EQs, control groups, snapshots, meters, console (${sections.length} sections)`)
|
||||
check(['inputs', 'groups', 'aux_masters', 'cgs', 'matrix', 'snapshots', 'console', 'meters', 'geqs'].every((s) => sections.includes(s)) && sections.some((s) => s.startsWith('sends_')) && sections.some((s) => s.startsWith('group_')) && sections.some((s) => s.startsWith('msends_')), `preset sections cover inputs, busses, sends, groups, matrix, matrix sends, graphic EQs, control groups, snapshots, meters, console (${sections.length} sections)`)
|
||||
// matrix sends: one section per matrix output, per matrix input an on/off button and a knob
|
||||
const msendSections = ctx.presetStructure.filter((x) => x.id.startsWith('msends_'))
|
||||
const msendOn = ids.filter((id) => id.startsWith('msend_on_'))
|
||||
@@ -553,7 +556,7 @@ check('input_60_pan' in ctx.variableDefs && 'cg_10_mute' in ctx.variableDefs &&
|
||||
const layered = ids.filter((id) => P[id].type === 'layered')
|
||||
const knobsLayered = layered.filter((id) => /_knob_/.test(id))
|
||||
const meterPresets = layered.filter((id) => /^input_(meter|gate_gr|comp_gr)_/.test(id))
|
||||
check(knobsLayered.length === ids.filter((id) => /_knob_/.test(id)).length && meterPresets.length === 180 && layered.filter((id) => /^(buss|matrix)_meter_/.test(id)).length === 32 && layered.length === knobsLayered.length + 180 + 32 && !ids.some((id) => /_(mute|solo|on)_/.test(id) && P[id].type === 'layered'), `every knob (${knobsLayered.length}) and meter (${meterPresets.length} input + 32 buss / matrix output) preset is layered; plain toggles stay simple`)
|
||||
check(knobsLayered.length === ids.filter((id) => /_knob_/.test(id)).length && meterPresets.length === 180 && layered.filter((id) => /^(group|aux|matrix)_meter_/.test(id)).length === 32 && layered.length === knobsLayered.length + 180 + 32 && !ids.some((id) => /_(mute|solo|on)_/.test(id) && P[id].type === 'layered'), `every knob (${knobsLayered.length}) and meter (${meterPresets.length} input + 32 buss / matrix output) preset is layered; plain toggles stay simple`)
|
||||
const ELEMENT_TYPES = new Set(['group', 'composite', 'text', 'image', 'box', 'line', 'circle', 'gauge'])
|
||||
const LAYERED_PROPS = { box: ['id', 'type', 'x', 'y', 'width', 'height', 'color'], text: ['id', 'type', 'x', 'y', 'width', 'height', 'text', 'fontsize', 'fontsizeAllowShrink', 'color', 'halign', 'valign'], gauge: ['id', 'type', 'x', 'y', 'width', 'height', 'value', 'min', 'max', 'orientation', 'trackStyle', 'fillEnabled', 'multiColour', 'stops', 'trackAmount'] }
|
||||
const isExpr = (v) => !!v && typeof v === 'object' && v.isExpression === true && typeof v.value === 'string'
|
||||
@@ -1527,9 +1530,12 @@ const cb = (feedbackId, options, image) => ctx.feedbacks[feedbackId].callback(fb
|
||||
await sleep(inst.timing.rebuildDebounceMs + 400)
|
||||
const label3 = ctx.actions.buss_mute.options[0].choices.find((c) => c.id === 3)?.label
|
||||
check(wasAux && label3 === '3 · Group 3 (group)' && ctx.pushes.actions === JSON.parse(p0).actions + 1 && ctx.pushes.presets === JSON.parse(p0).presets + 1, `a changed buss type rebuilds and pushes once (${label3})`)
|
||||
const optIds = (id, o) => ctx.actions[id].options.find((x) => x.id === o).choices.map((c) => c.id)
|
||||
check(optIds('group_fader_set', 'buss').includes(3) && !optIds('aux_fader_set', 'buss').includes(3) && optIds('group_mute', 'busses').includes(3) && !optIds('aux_mute', 'busses').includes(3) && ctx.actions.group_fader_set.options[0].choices.find((c) => c.id === 3).label === '3 · Group 3' && ctx.feedbacks.group_muted.options[0].choices.some((c) => c.id === 3) && !ctx.feedbacks.aux_muted.options[0].choices.some((c) => c.id === 3), 'the buss moved from the aux dropdowns to the group dropdowns (actions and feedbacks)')
|
||||
check(!ctx.presets.aux_mute_3 && !ctx.presets.aux_knob_3 && !ctx.presets.aux_meter_3 && ctx.presetStructure.find((x) => x.id === 'aux_masters').definitions[0].presets.length === 14, 'and its aux master presets are gone from "Aux masters" (it has no group-side name, so no group presets either)')
|
||||
inst.onConsoleMessage(decode(encode('/Console/Aux_Outputs/types', types.map((i) => ({ i })))), { address: '127.0.0.1', port: P.mock })
|
||||
await sleep(inst.timing.rebuildDebounceMs + 400)
|
||||
check(ctx.actions.buss_mute.options[0].choices.find((c) => c.id === 3)?.label === '3 · TRACKS (aux)', 'and back again')
|
||||
check(ctx.actions.buss_mute.options[0].choices.find((c) => c.id === 3)?.label === '3 · TRACKS (aux)' && optIds('aux_fader_set', 'buss').includes(3) && !optIds('group_fader_set', 'buss').includes(3) && !!ctx.presets.aux_mute_3 && ctx.presetStructure.find((x) => x.id === 'aux_masters').definitions[0].presets.includes('aux_mute_3'), 'and back again (dropdowns and the "Aux masters" presets)')
|
||||
|
||||
// a changed snapshot list rebuilds
|
||||
const p1 = ctx.pushes.actions
|
||||
@@ -1618,6 +1624,22 @@ const cb = (feedbackId, options, image) => ctx.feedbacks[feedbackId].callback(fb
|
||||
run1(lc)
|
||||
check(inst.watchers.get('cg:2')?.has('inst-L'), 'level_compare registers under the entity kind it compares')
|
||||
ctx.feedbacks.level_compare.unsubscribe(lc, {})
|
||||
// the group and aux master feedbacks watch the buss's own key, like the buss_* ones
|
||||
const bm = mk('buss_muted', { buss: 24 }, 'inst-BM')
|
||||
const gm = mk('group_muted', { buss: 24 }, 'inst-GM')
|
||||
const gf = mk('group_fader_pos', { buss: 24 }, 'inst-GF')
|
||||
const gx = mk('group_fader_value', { buss: 25 }, 'inst-GX')
|
||||
const ax = mk('aux_fader_value', { buss: 3 }, 'inst-AX')
|
||||
const gp = mk('group_signal', { buss: 24, thresholdDb: -50 }, 'inst-GP')
|
||||
for (const f of [bm, gm, gf, gx, ax, gp]) run1(f)
|
||||
check(['inst-BM', 'inst-GM', 'inst-GF'].every((id) => inst.watchers.get('buss:24')?.has(id)) && inst.watchers.get('buss:25')?.has('inst-GX') && inst.watchers.get('buss:3')?.has('inst-AX') && inst.watchers.get(`meter:${outputMeterId('buss', 24)}`)?.has('inst-GP'), 'group_* and aux_* feedbacks register under the buss key (meters under the buss meter id)')
|
||||
await sleep(150)
|
||||
ctx.byId.length = 0
|
||||
inst.onConsoleMessage(decode(encode('/Group_Outputs/24/fader', [-12.25])), { address: '127.0.0.1', port: P.mock })
|
||||
await sleep(80)
|
||||
const idsG = ctx.byId.flatMap(([, x]) => x)
|
||||
check(idsG.includes('inst-BM') && idsG.includes('inst-GM') && idsG.includes('inst-GF') && !idsG.includes('inst-GX') && !idsG.includes('inst-AX') && !ctx.checks.some(([, t]) => /^(group|aux|buss)_/.test(String(t))), 'a group fader change re-checks only the instances watching that buss, by id and with no per-type check')
|
||||
for (const f of [bm, gm, gf, gx, ax, gp]) ctx.feedbacks[f.feedbackId].unsubscribe(f, {})
|
||||
|
||||
// every option referenced by an isVisibleExpression must not become an expression
|
||||
const bad = []
|
||||
@@ -1873,6 +1895,102 @@ const idle = () => waitFor(() => inst.fades.size === 0, 3000)
|
||||
}
|
||||
check(inst.fades.size === 0 && inst.fadeStarts.size === 0, 'no fade or fade start is left over after the fade tests')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 16c. Group and aux master actions, feedbacks and presets
|
||||
// ---------------------------------------------------------------------------
|
||||
{
|
||||
const GROUPS = [9, 10, 11, 12, 13, 18, 23, 24, 25]
|
||||
const allIds = S.choices.busses().map((c) => c.id)
|
||||
const AUXES = allIds.filter((id) => !GROUPS.includes(id))
|
||||
const ids = (opts, id) => opts.find((o) => o.id === id).choices.map((c) => c.id)
|
||||
const labels = (opts, id) => opts.find((o) => o.id === id).choices.map((c) => c.label)
|
||||
check(S.choices.groupChoices().map((c) => c.id).join() === GROUPS.join() && AUXES.length === 16, `the mock has ${GROUPS.length} group busses and ${AUXES.length} aux busses`)
|
||||
let ok = true
|
||||
for (const [prefix, want] of [['group', GROUPS], ['aux', AUXES]]) {
|
||||
for (const a of ['fader_set', 'fader_adjust']) ok = ok && ids(ctx.actions[`${prefix}_${a}`].options, 'buss').join() === want.join()
|
||||
for (const a of ['mute', 'solo']) ok = ok && ids(ctx.actions[`${prefix}_${a}`].options, 'busses').join() === want.join() && ctx.actions[`${prefix}_${a}`].options[0].type === 'multidropdown'
|
||||
for (const f of ['muted', 'solo', 'fader_value', 'fader_pos', 'meter_value', 'meter_pos', 'signal', 'peak']) ok = ok && ids(ctx.feedbacks[`${prefix}_${f}`].options, 'buss').join() === want.join()
|
||||
}
|
||||
check(ok, 'every group_* dropdown (actions and feedbacks) lists exactly the nine group busses, every aux_* one exactly the sixteen aux busses')
|
||||
const gl = labels(ctx.actions.group_fader_set.options, 'buss')
|
||||
const al = labels(ctx.actions.aux_fader_set.options, 'buss')
|
||||
check(gl.includes('24 · Studio') && al.includes('4 · CLK-GD-MD') && !gl.concat(al, labels(ctx.actions.group_mute.options, 'busses'), labels(ctx.actions.aux_mute.options, 'busses')).some((l) => /\((aux|group)\)/.test(l)), 'their labels carry no "(group)" / "(aux)" suffix')
|
||||
check(ids(ctx.actions.buss_fader_set.options, 'buss').length === 25 && labels(ctx.actions.buss_fader_set.options, 'buss').includes('24 · Studio (group)') && ids(ctx.actions.buss_mute.options, 'busses').length === 25, 'the buss_* dropdowns still list all 25 busses with the suffix')
|
||||
check(ctx.actions.group_fader_set.options.map((o) => o.id).join() === 'buss,level,duration,curve' && ctx.actions.aux_fader_set.options.map((o) => o.id).join() === 'buss,level,duration,curve' && ctx.actions.group_fader_adjust.options.map((o) => o.id).join() === 'buss,delta' && ctx.actions.aux_mute.options.map((o) => o.id).join() === 'busses,state', 'option ids are the same as the buss_* ones (fade options on the set actions)')
|
||||
check(ctx.actions.group_mute.name === 'Group: mute' && ctx.actions.group_solo.name === 'Group: solo' && ctx.actions.group_fader_set.name === 'Group: set fader' && ctx.actions.group_fader_adjust.name === 'Group: adjust fader (relative)' && ctx.actions.aux_mute.name === 'Aux master: mute' && ctx.actions.aux_solo.name === 'Aux master: solo' && ctx.actions.aux_fader_set.name === 'Aux master: set fader' && ctx.actions.aux_fader_adjust.name === 'Aux master: adjust fader (relative)' && ctx.actions.group_assign.name === 'Group: assign input on/off', 'action names: "Group: ..." and "Aux master: ..." (group_assign keeps its name)')
|
||||
check(/fade time above 0/.test(ctx.actions.group_fader_set.description) && /fade time above 0/.test(ctx.actions.aux_fader_set.description), 'the two set actions describe the fade')
|
||||
// the old ids stay, with names that sort apart
|
||||
const oldActions = ['buss_mute', 'buss_solo', 'buss_fader_set', 'buss_fader_adjust']
|
||||
const oldFeedbacks = ['buss_muted', 'buss_solo', 'buss_fader_value', 'buss_fader_pos', 'buss_fader_bar', 'buss_signal', 'buss_peak', 'buss_meter_value', 'buss_meter_pos']
|
||||
check(oldActions.every((id) => ctx.actions[id]?.name.startsWith('Any buss (aux or group): ')) && oldFeedbacks.every((id) => ctx.feedbacks[id]?.name.startsWith('Any buss (aux or group)')), `the old buss_* ids still exist and their names start with "Any buss (aux or group)" (${oldActions.length} actions, ${oldFeedbacks.length} feedbacks)`)
|
||||
check(ctx.feedbacks.group_muted.name === 'Group: muted' && ctx.feedbacks.aux_fader_pos.name === 'Aux master: fader position (0-100, for gauges)' && ctx.feedbacks.group_peak.name.startsWith('Group meter: ') && ctx.feedbacks.aux_signal.name.startsWith('Aux master meter: ') && !('group_fader_bar' in ctx.feedbacks) && !('aux_fader_bar' in ctx.feedbacks), 'feedback names, and no legacy image-bar variants for group / aux masters')
|
||||
|
||||
// writes: the group side for a group buss, the aux side for an aux
|
||||
let r = await run('group_fader_set', { buss: 24, level: -7 })
|
||||
expectWrite(r, '/Group_Outputs/24/fader', ',f', -7, 'group_fader_set')
|
||||
r = await run('aux_fader_set', { buss: 1, level: -9 })
|
||||
expectWrite(r, '/Aux_Outputs/1/fader', ',f', -9, 'aux_fader_set')
|
||||
r = await run('group_mute', { busses: [9, 24], state: 'on' })
|
||||
expectWrite(r, '/Group_Outputs/9/mute', ',f', 1, 'group_mute (first)')
|
||||
expectWrite(r, '/Group_Outputs/24/mute', ',f', 1, 'group_mute (second)')
|
||||
r = await run('aux_mute', { busses: [3, 4], state: 'on' })
|
||||
expectWrite(r, '/Aux_Outputs/3/mute', ',f', 1, 'aux_mute (first)')
|
||||
expectWrite(r, '/Aux_Outputs/4/mute', ',f', 1, 'aux_mute (second)')
|
||||
r = await run('group_solo', { busses: [24], state: 'on' })
|
||||
expectWrite(r, '/Group_Outputs/24/solo', ',f', 1, 'group_solo')
|
||||
r = await run('aux_solo', { busses: [3], state: 'off' })
|
||||
expectWrite(r, '/Aux_Outputs/3/solo', ',f', 0, 'aux_solo off')
|
||||
r = await run('group_mute', { busses: [9], state: 'toggle' })
|
||||
expectWrite(r, '/Group_Outputs/9/mute', ',f', 0, 'group_mute toggle (from muted)')
|
||||
await run('group_fader_set', { buss: 23, level: -20 })
|
||||
r = await run('group_fader_adjust', { buss: 23, delta: 3 })
|
||||
expectWrite(r, '/Group_Outputs/23/fader', ',f', -17, 'group_fader_adjust')
|
||||
await run('aux_fader_set', { buss: 2, level: -20 })
|
||||
r = await run('aux_fader_adjust', { buss: 2, delta: -2.5 })
|
||||
expectWrite(r, '/Aux_Outputs/2/fader', ',f', -22.5, 'aux_fader_adjust')
|
||||
// feedbacks read the same state as the buss_* ones
|
||||
check(cb('group_muted', { buss: 24 }) === true && cb('buss_muted', { buss: 24 }) === true && cb('aux_muted', { buss: 3 }) === true && cb('aux_muted', { buss: 1 }) === false && cb('group_solo', { buss: 24 }) === true && cb('aux_solo', { buss: 3 }) === false && cb('group_fader_value', { buss: 24 }) === '-7' && cb('aux_fader_value', { buss: 1 }) === '-9' && near(cb('group_fader_pos', { buss: 24 }), cb('buss_fader_pos', { buss: 24 })) && near(cb('aux_fader_pos', { buss: 2 }), faderPosition(-22.5)), 'group_* / aux_* feedbacks follow the buss state (muted, solo, fader text and position)')
|
||||
|
||||
// fades through the same engine
|
||||
const mark = sent.length
|
||||
await go('group_fader_set', { buss: 25, level: -40 })
|
||||
const m2 = sent.length
|
||||
await go('group_fader_set', { buss: 25, level: -10, duration: 300, curve: 'db' })
|
||||
check(inst.fades.size === 1, 'a group fader fade is running')
|
||||
check(await idle(), 'the group fade finishes')
|
||||
const gw = writesTo('/Group_Outputs/25/fader', m2).map((x) => x.db)
|
||||
check(gw.length === 6 && gw.at(-1) === -10 && gw.every((v, k) => near(v, -40 + 5 * (k + 1), 0.01)), `group_fader_set fades on /Group_Outputs/25/fader (${gw.map((v) => Math.round(v * 100) / 100).join(', ')})`)
|
||||
await go('aux_fader_set', { buss: 5, level: -40 })
|
||||
const m3 = sent.length
|
||||
await go('aux_fader_set', { buss: 5, level: -10, duration: 200, curve: 'db' })
|
||||
check(await idle(), 'the aux master fade finishes')
|
||||
const aw = writesTo('/Aux_Outputs/5/fader', m3).map((x) => x.db)
|
||||
check(aw.length === 4 && aw.at(-1) === -10 && !sent.slice(m3).some((m) => m.address.startsWith('/Group_Outputs/5/')), `aux_fader_set fades on /Aux_Outputs/5/fader (${aw.map((v) => Math.round(v * 100) / 100).join(', ')})`)
|
||||
void mark
|
||||
|
||||
// presets: Groups and Aux masters sections, group / aux meter sections, no "Busses" section
|
||||
const P2 = ctx.presets
|
||||
const secs = ctx.presetStructure
|
||||
const sec = (id) => secs.find((x) => x.id === id)
|
||||
const idsOf = (section) => section.definitions.flatMap((d) => d.presets)
|
||||
const namedGroups = S.namedBusses('group')
|
||||
const namedAux = S.namedBusses('aux')
|
||||
check(namedGroups.join() === GROUPS.join() && namedAux.length === 15 && sec('groups')?.name === 'Groups' && sec('aux_masters')?.name === 'Aux masters' && !sec('busses') && !sec('buss_meters'), `sections "Groups" (${namedGroups.length} named group busses) and "Aux masters" (${namedAux.length} named aux busses) replace "Busses"`)
|
||||
check(idsOf(sec('groups')).length === 2 * namedGroups.length && idsOf(sec('aux_masters')).length === 2 * namedAux.length && sec('groups').definitions.map((d) => d.name).join() === 'Mute,Fader (knob)' && sec('aux_masters').definitions.map((d) => d.name).join() === 'Mute,Fader (knob)', 'each has a Mute and a Fader (knob) preset per named buss of that type')
|
||||
const gMute = P2.group_mute_24
|
||||
const gKnob = P2.group_knob_24
|
||||
const aMute = P2.aux_mute_4
|
||||
const aKnob = P2.aux_knob_4
|
||||
check(gMute.name === 'Group mute: 24 · Studio' && gMute.steps[0].down[0].actionId === 'group_mute' && JSON.stringify(gMute.steps[0].down[0].options) === JSON.stringify({ busses: [24], state: 'toggle' }) && gMute.feedbacks[0].feedbackId === 'group_muted' && gMute.style.text === '$(S31:buss_24_name)', 'group mute preset uses group_mute / group_muted')
|
||||
check(gKnob.type === 'layered' && gKnob.name === 'Group fader (knob): 24 · Studio' && gKnob.steps[0].rotate_right[0].actionId === 'group_fader_adjust' && gKnob.steps[0].down[0].actionId === 'group_mute' && gKnob.feedbacks[0].feedbackId === 'group_muted' && gKnob.localVariables.map((v) => `${v.variableName}=${v.feedbackId}`).join() === 'level=group_fader_value,pos=group_fader_pos', 'group fader knob preset uses group_fader_adjust / group_fader_value / group_fader_pos / group_muted')
|
||||
check(aMute.name === 'Aux master mute: 4 · CLK-GD-MD' && aMute.steps[0].down[0].actionId === 'aux_mute' && aMute.feedbacks[0].feedbackId === 'aux_muted' && aKnob.steps[0].rotate_left[0].actionId === 'aux_fader_adjust' && aKnob.localVariables.map((v) => v.feedbackId).join() === 'aux_fader_value,aux_fader_pos' && aKnob.feedbacks[0].feedbackId === 'aux_muted', 'aux master mute and knob presets use aux_*')
|
||||
check(!Object.keys(P2).some((id) => /^buss_(mute|knob|meter)_/.test(id)) && !JSON.stringify(P2).includes('"buss_') , 'no preset refers to a buss_* action or feedback any more')
|
||||
const gMeters = sec('meters_groups')
|
||||
const aMeters = sec('meters_aux')
|
||||
check(gMeters?.name === 'Group meters' && aMeters?.name === 'Aux meters' && idsOf(gMeters).length === 9 && idsOf(aMeters).length === 15 && P2.group_meter_24.localVariables.some((v) => v.feedbackId === 'group_meter_pos') && P2.group_meter_24.feedbacks.map((f) => f.feedbackId).join() === 'group_signal,group_peak' && P2.aux_meter_4.feedbacks.map((f) => f.feedbackId).join() === 'aux_signal,aux_peak' && !sec('buss_meters'), 'group meter and aux meter sections (9 and 15 meters) use group_* / aux_* meter feedbacks')
|
||||
check(idsOf(gMeters).concat(idsOf(aMeters)).every((id) => P2[id].type === 'layered'), 'the group and aux meter presets are layered gauges')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 17. A slow console (every answer and echo 150 ms late): bind failures, encoder clicks, lazy loads
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user