266 lines
11 KiB
JavaScript
266 lines
11 KiB
JavaScript
import { COLORS } from './feedbacks.js'
|
|||
|
|
|
||
|
|
/** Hard limits agreed for the preset list (Companion keeps all of it in memory and sends it to the web UI). */
|
||
|
|
export const MAX_PRESETS = 3000
|
||
|
|
export const MAX_PRESET_BYTES = 4 * 1024 * 1024
|
||
|
|
|
||
|
|
const { white: WHITE, dark: DARK, red: RED, green: GREEN, amber: AMBER, bar: BAR } = COLORS
|
||
|
|
|
||
|
|
const fb = (feedbackId, options, bg, color = WHITE) => ({ feedbackId, options, style: { bgcolor: bg, color } })
|
||
|
|
const act = (actionId, options) => ({ actionId, options })
|
||
|
|
|
||
|
|
/** Build every preset from the live state. `withSendKnobs` is the first thing dropped when the list is too big. */
|
||
|
|
function build(inst, withSendKnobs) {
|
||
|
|
const s = inst.state
|
||
|
|
const L = inst.label
|
||
|
|
const presets = {}
|
||
|
|
const structure = []
|
||
|
|
const nameVar = (ent, n) => `$(${L}:${ent}_${n}_name)`
|
||
|
|
const localFeedback = (variableName, feedbackId, options) => ({ variableName, variableType: 'feedback', feedbackId, options })
|
||
|
|
|
||
|
|
const simple = (name, text, down, feedbacks = [], extra = {}) => ({
|
||
|
|
type: 'simple',
|
||
|
|
name,
|
||
|
|
style: { text, size: 'auto', color: WHITE, bgcolor: DARK },
|
||
|
|
steps: [{ down, up: [] }],
|
||
|
|
feedbacks,
|
||
|
|
...extra,
|
||
|
|
})
|
||
|
|
/** Rotary preset: press = `press`, turning = `adjust` with ∓1 (the action's delta option). */
|
||
|
|
const knob = (name, text, press, adjustActionId, adjustOptions, localVariables, feedbacks) => ({
|
||
|
|
type: 'simple',
|
||
|
|
name,
|
||
|
|
style: { text, size: 'auto', color: WHITE, bgcolor: DARK },
|
||
|
|
steps: [
|
||
|
|
{
|
||
|
|
down: press,
|
||
|
|
up: [],
|
||
|
|
rotate_left: [act(adjustActionId, { ...adjustOptions, delta: -1 })],
|
||
|
|
rotate_right: [act(adjustActionId, { ...adjustOptions, delta: 1 })],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
feedbacks,
|
||
|
|
localVariables,
|
||
|
|
})
|
||
|
|
const group = (id, name, ids) => ({ id, type: 'simple', name, presets: ids })
|
||
|
|
|
||
|
|
// ---- Inputs
|
||
|
|
{
|
||
|
|
const muteIds = []
|
||
|
|
const knobIds = []
|
||
|
|
const soloIds = []
|
||
|
|
const meterIds = { level: [], gate: [], comp: [] }
|
||
|
|
for (const n of s.numbers('input')) {
|
||
|
|
const name = s.displayName('input', n)
|
||
|
|
const tag = `${n} · ${name}`
|
||
|
|
presets[`input_mute_${n}`] = simple(`Mute: ${tag}`, nameVar('input', n), [act('input_mute', { channels: [n], state: 'toggle' })], [fb('input_muted', { channel: n }, RED)])
|
||
|
|
presets[`input_knob_${n}`] = knob(
|
||
|
|
`Fader knob: ${tag}`,
|
||
|
|
`${nameVar('input', n)}\\n$(local:level) dB`,
|
||
|
|
[act('input_mute', { channels: [n], state: 'toggle' })],
|
||
|
|
'input_fader_adjust',
|
||
|
|
{ channel: n },
|
||
|
|
[localFeedback('level', 'input_fader_value', { channel: n })],
|
||
|
|
[fb('input_muted', { channel: n }, RED), { feedbackId: 'input_fader_bar', options: { channel: n, color: BAR } }],
|
||
|
|
)
|
||
|
|
presets[`input_solo_${n}`] = simple(`Solo: ${tag}`, `${nameVar('input', n)}\\nSOLO`, [act('input_solo', { channels: [n], state: 'toggle' })], [fb('input_solo', { channel: n }, AMBER, COLORS.black)])
|
||
|
|
for (const [meter, prefix, label] of [
|
||
|
|
['level', 'input_meter', 'Meter'],
|
||
|
|
['gate', 'input_gate_gr', 'Gate reduction'],
|
||
|
|
['comp', 'input_comp_gr', 'Compressor reduction'],
|
||
|
|
]) {
|
||
|
|
presets[`${prefix}_${n}`] = simple(
|
||
|
|
`${label}: ${tag}`,
|
||
|
|
nameVar('input', n),
|
||
|
|
[],
|
||
|
|
[
|
||
|
|
{ feedbackId: 'input_meter_bar', options: { channel: n, meter } },
|
||
|
|
fb('input_signal', { channel: n, meter, threshold: 5 }, GREEN),
|
||
|
|
fb('input_peak', { channel: n, meter, hold: 2 }, RED),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
meterIds[meter].push(`${prefix}_${n}`)
|
||
|
|
}
|
||
|
|
muteIds.push(`input_mute_${n}`)
|
||
|
|
knobIds.push(`input_knob_${n}`)
|
||
|
|
soloIds.push(`input_solo_${n}`)
|
||
|
|
}
|
||
|
|
if (muteIds.length) {
|
||
|
|
structure.push({
|
||
|
|
id: 'inputs',
|
||
|
|
name: 'Inputs',
|
||
|
|
definitions: [group('inputs_mute', 'Mute', muteIds), group('inputs_knob', 'Fader (knob)', knobIds), group('inputs_solo', 'Solo', soloIds)],
|
||
|
|
})
|
||
|
|
structure.push({
|
||
|
|
id: 'meters',
|
||
|
|
name: 'Input meters',
|
||
|
|
description: 'Meters subscribe on the console when the button first renders.',
|
||
|
|
definitions: [
|
||
|
|
group('meters_input', 'Level', meterIds.level),
|
||
|
|
group('meters_gate', 'Gate gain reduction', meterIds.gate),
|
||
|
|
group('meters_comp', 'Compressor gain reduction', meterIds.comp),
|
||
|
|
],
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---- Busses
|
||
|
|
{
|
||
|
|
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}`] = knob(
|
||
|
|
`Master fader (knob): ${tag}`,
|
||
|
|
`${nameVar('buss', n)}\\n$(local:level) dB`,
|
||
|
|
[act('buss_mute', { busses: [n], state: 'toggle' })],
|
||
|
|
'buss_fader_adjust',
|
||
|
|
{ buss: n },
|
||
|
|
[localFeedback('level', 'buss_fader_value', { buss: n })],
|
||
|
|
[fb('buss_muted', { buss: n }, RED), { feedbackId: 'buss_fader_bar', options: { buss: n, color: BAR } }],
|
||
|
|
)
|
||
|
|
muteIds.push(`buss_mute_${n}`)
|
||
|
|
knobIds.push(`buss_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)] })
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---- Sends to each aux buss
|
||
|
|
const inputs = s.numbers('input')
|
||
|
|
for (const b of s.namedBusses('aux')) {
|
||
|
|
const bname = s.displayName('buss', b)
|
||
|
|
const onIds = []
|
||
|
|
const knobIds = []
|
||
|
|
for (const ch of inputs) {
|
||
|
|
const cname = s.displayName('input', ch)
|
||
|
|
const tag = `${ch} · ${cname} → ${b} · ${bname}`
|
||
|
|
const text = `${nameVar('input', ch)}\\n$(local:level) dB`
|
||
|
|
const lv = [localFeedback('level', 'send_level_value', { channel: ch, buss: b })]
|
||
|
|
presets[`send_on_${ch}_${b}`] = simple(`Send on/off: ${tag}`, text, [act('send_on', { channel: ch, buss: b, state: 'toggle' })], [fb('send_on', { channel: ch, buss: b }, GREEN)], { localVariables: lv })
|
||
|
|
onIds.push(`send_on_${ch}_${b}`)
|
||
|
|
if (withSendKnobs) {
|
||
|
|
presets[`send_knob_${ch}_${b}`] = knob(
|
||
|
|
`Send level (knob): ${tag}`,
|
||
|
|
text,
|
||
|
|
[act('send_on', { channel: ch, buss: b, state: 'toggle' })],
|
||
|
|
'send_level_adjust',
|
||
|
|
{ channel: ch, buss: b },
|
||
|
|
lv,
|
||
|
|
[fb('send_on', { channel: ch, buss: b }, GREEN), { feedbackId: 'send_level_bar', options: { channel: ch, buss: b, color: BAR } }],
|
||
|
|
)
|
||
|
|
knobIds.push(`send_knob_${ch}_${b}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const defs = [group(`sends_${b}_on`, 'On/off', onIds)]
|
||
|
|
if (withSendKnobs) defs.push(group(`sends_${b}_knob`, 'Level (knob)', knobIds))
|
||
|
|
if (onIds.length) structure.push({ id: `sends_${b}`, name: `Sends to ${bname}`, definitions: defs })
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---- Assign to each group buss
|
||
|
|
for (const b of s.namedBusses('group')) {
|
||
|
|
const bname = s.displayName('buss', b)
|
||
|
|
const ids = []
|
||
|
|
for (const ch of inputs) {
|
||
|
|
presets[`group_${ch}_${b}`] = simple(
|
||
|
|
`Assign: ${ch} · ${s.displayName('input', ch)} → ${b} · ${bname}`,
|
||
|
|
nameVar('input', ch),
|
||
|
|
[act('group_assign', { channel: ch, buss: b, state: 'toggle' })],
|
||
|
|
[fb('group_assigned', { channel: ch, buss: b }, GREEN)],
|
||
|
|
)
|
||
|
|
ids.push(`group_${ch}_${b}`)
|
||
|
|
}
|
||
|
|
if (ids.length) structure.push({ id: `group_${b}`, name: `Group ${bname}`, definitions: [group(`group_${b}_assign`, 'Assign', ids)] })
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---- Matrix and control groups
|
||
|
|
{
|
||
|
|
const muteIds = []
|
||
|
|
const knobIds = []
|
||
|
|
for (const n of s.named('matrix')) {
|
||
|
|
const tag = `${n} · ${s.displayName('matrix', n)}`
|
||
|
|
presets[`matrix_mute_${n}`] = simple(`Mute: ${tag}`, nameVar('matrix', n), [act('matrix_mute', { matrix: n, state: 'toggle' })], [fb('matrix_muted', { matrix: n }, RED)])
|
||
|
|
presets[`matrix_knob_${n}`] = knob(
|
||
|
|
`Fader (knob): ${tag}`,
|
||
|
|
`${nameVar('matrix', n)}\\n$(local:level) dB`,
|
||
|
|
[act('matrix_mute', { matrix: n, state: 'toggle' })],
|
||
|
|
'matrix_fader_adjust',
|
||
|
|
{ matrix: n },
|
||
|
|
[localFeedback('level', 'matrix_fader_value', { matrix: n })],
|
||
|
|
[fb('matrix_muted', { matrix: n }, RED)],
|
||
|
|
)
|
||
|
|
muteIds.push(`matrix_mute_${n}`)
|
||
|
|
knobIds.push(`matrix_knob_${n}`)
|
||
|
|
}
|
||
|
|
if (muteIds.length) structure.push({ id: 'matrix', name: 'Matrix', definitions: [group('matrix_mute', 'Mute', muteIds), group('matrix_knob', 'Fader (knob)', knobIds)] })
|
||
|
|
}
|
||
|
|
{
|
||
|
|
const muteIds = []
|
||
|
|
const knobIds = []
|
||
|
|
for (const n of s.named('cg')) {
|
||
|
|
const tag = `${n} · ${s.displayName('cg', n)}`
|
||
|
|
presets[`cg_mute_${n}`] = simple(`Mute: ${tag}`, nameVar('cg', n), [act('cg_mute', { cg: n, state: 'toggle' })], [fb('cg_muted', { cg: n }, RED)])
|
||
|
|
presets[`cg_knob_${n}`] = knob(
|
||
|
|
`Fader (knob): ${tag}`,
|
||
|
|
`${nameVar('cg', n)}\\n$(local:level) dB`,
|
||
|
|
[act('cg_mute', { cg: n, state: 'toggle' })],
|
||
|
|
'cg_fader_adjust',
|
||
|
|
{ cg: n },
|
||
|
|
[localFeedback('level', 'cg_fader_value', { cg: n })],
|
||
|
|
[fb('cg_muted', { cg: n }, RED)],
|
||
|
|
)
|
||
|
|
muteIds.push(`cg_mute_${n}`)
|
||
|
|
knobIds.push(`cg_knob_${n}`)
|
||
|
|
}
|
||
|
|
if (muteIds.length) structure.push({ id: 'cgs', name: 'Control groups', definitions: [group('cgs_mute', 'Mute', muteIds), group('cgs_knob', 'Fader (knob)', knobIds)] })
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---- Snapshots
|
||
|
|
{
|
||
|
|
const fireIds = []
|
||
|
|
for (const [index, snap] of [...s.snapshots.list].sort((a, b) => a[0] - b[0])) {
|
||
|
|
presets[`snap_fire_${index}`] = simple(`Fire ${index} · ${snap.name}`, `${index}\\n${snap.name}`, [act('snapshot_fire', { snapshot: index })], [fb('snapshot_current', { snapshot: index }, GREEN)])
|
||
|
|
fireIds.push(`snap_fire_${index}`)
|
||
|
|
}
|
||
|
|
presets.snap_next = simple('Next snapshot', 'NEXT ▶', [act('snapshot_next', {})])
|
||
|
|
presets.snap_prev = simple('Previous snapshot', '◀ PREV', [act('snapshot_prev', {})])
|
||
|
|
presets.snap_current = simple('Current snapshot (display)', `$(${L}:snapshot_index)\\n$(local:snap)`, [], [], {
|
||
|
|
localVariables: [localFeedback('snap', 'snapshot_name_value', {})],
|
||
|
|
})
|
||
|
|
const defs = []
|
||
|
|
if (fireIds.length) defs.push(group('snapshots_fire', 'Fire', fireIds))
|
||
|
|
defs.push(group('snapshots_nav', 'Next / previous / current', ['snap_next', 'snap_prev', 'snap_current']))
|
||
|
|
structure.push({ id: 'snapshots', name: 'Snapshots', definitions: defs })
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---- Console
|
||
|
|
{
|
||
|
|
presets.console_reload = simple('Reload from console', `$(${L}:console_name)\\nReload`, [act('reload', {})], [fb('connected', {}, GREEN)])
|
||
|
|
presets.console_status = simple('Connection status', 'Console', [], [fb('connected', {}, GREEN), { ...fb('connected', {}, RED), isInverted: true }])
|
||
|
|
presets.console_relay = simple('Relay status', `iPads\\n$(${L}:relay_clients)`, [], [fb('relay_clients', {}, GREEN)])
|
||
|
|
structure.push({ id: 'console', name: 'Console', definitions: [group('console_all', 'Console', ['console_reload', 'console_status', 'console_relay'])] })
|
||
|
|
}
|
||
|
|
|
||
|
|
return { structure, presets }
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Presets for the current state: { structure, presets, stats }. If the list would exceed the agreed
|
||
|
|
* limits the per-input send level knobs (the largest group) are left out and stats.droppedSendKnobs is set.
|
||
|
|
*/
|
||
|
|
export function getPresetDefinitions(inst) {
|
||
|
|
let built = build(inst, true)
|
||
|
|
let count = Object.keys(built.presets).length
|
||
|
|
let bytes = JSON.stringify(built.presets).length
|
||
|
|
let droppedSendKnobs = false
|
||
|
|
if (count > MAX_PRESETS || bytes > MAX_PRESET_BYTES) {
|
||
|
|
built = build(inst, false)
|
||
|
|
droppedSendKnobs = true
|
||
|
|
count = Object.keys(built.presets).length
|
||
|
|
bytes = JSON.stringify(built.presets).length
|
||
|
|
}
|
||
|
|
return { ...built, stats: { count, bytes, droppedSendKnobs } }
|
||
|
|
}
|