openmixer — generated API reference
    Preparing search index...

    Class MixerEngine

    Reads are a push observer (not promises): desks stream state after subscribe. One fat interface -- anything wanting fader updates wants mute/name/meter too.

    Implements

    Index
    cueMode: "pfl" | "afl" | "sip" = 'pfl'

    Console-wide cue mode (cue/solo-to-monitor spec): PFL (default) / AFL / SIP.

    headAmpOwner?: (channel: ChannelId) => boolean

    Where the analog trio is OWNED when the console declares a head-amp entity: the re-assert for one channel, answering true when it took the job (audit D4). Set by the composition root that wires the entity; absent on a console whose ONE store is the strip.

    The trio belongs to the PHYSICAL INPUT, and the entity keys it that way and knows which of the three it has actually read off the box. The strip knows neither, so running both means two independently-ordered queues onto one preamp, the second asserting values nobody measured — a defaulted phantom reaching a live condenser. One owner answers, or the strip does; never both.

    monitorDim: boolean = false

    Whether monitor DIM is engaged (an extra attenuation on the cue master).

    monitorLevel: number = 1

    Monitor LEVEL (0..1) — the cue-bus master fader (drives the monitor output loudness).

    monitorOutput?: ChannelId

    The designated monitor destination, if any (the sink the cue bus routes to).

    monitorTakeover: boolean = false

    Whether the monitor destination is in TAKEOVER mode (B): the sink normally carries MAIN and switches to cue on solo (the native is_monitor crossfade). Default false = pure cue (A).

    selectedChannel?: ChannelId

    The console's globally-selected channel (SEL), if any surface has selected one.

    sipEnabled: boolean = false

    SIP safety guard (#192): whether the destructive solo-in-place mode is UNLOCKED in Setup. Default DISARMED — while false, setCueMode to sip is rejected upstream.

    topology?: MixerTopology

    The bound device topology (undefined until the engine reports connected).

    • get monitorTakeoverAvailable(): boolean | undefined

      Whether mode B (takeover) could actually be ESTABLISHED for the current monitor destination — see MixerAdapter.monitorTakeoverAvailable. undefined = this desk cannot say.

      Three facts blur around the monitor, and only the first two are state. They are named here together because reading one for another is how takeover came to look broken:

      • monitorOutput — WHICH channel's routed sink the monitor listens at (cue/1 means "wherever the cue bus lands");
      • that channel's own output PATCH (/patch/output/{kind}/{index}) — WHERE that lands physically. It belongs to the patch row, not to this engine, and mode A needs nothing beyond it: cue_L/cue_R → the sink, and solo is audible;
      • monitorTakeover — WHETHER that destination idles on MAIN (mode B) or on silence (mode A). Mode B is a flag on a MAIN OUT_ROUTE, so it presupposes that MAIN is also fanned out to that same sink; the flag alone conjures no such route. This getter is none of the three: it is the readback saying whether the third one reached anything at all, which is the only way takeover: true avoids claiming work it did not do.

      Returns boolean | undefined

    • Does the console DECLARE this channel — the allocation's own answer, read off the topology.

      2026-08-07-one-summing-bus.md §1 step 4: rows exist for exactly the allocated instances and refuse outside them. The allocation is the one authored decision; the topology is that decision as a channel list (MixerTopology.channels), and it is what every membership answer below is built on.

      Before a topology there is nothing authoritative to ask — an offline desk, a unit rig with no adapter — so the strip map stands in for it, which is the honest answer when nothing has been declared at all.

      Parameters

      Returns boolean

    • EXTEND the known topology with channels created after connect (a per-gig allocation.apply building new buses at runtime — the console GROWS; nothing is ever removed). Channels already known (and repeats within channels itself) are skipped, so re-applying an allocation is idempotent. When anything was actually added the whole topology is replaced (a fresh object — MixerTopology.channels is readonly, so holders of the old snapshot are never mutated under their feet) and a connected event re-fans the NEW topology out, exactly like a device (re)connect: every subscribed surface / server client converges on the grown channel list over the same wire frame the greeting uses.

      A no-op (returns [], emits nothing) before onConnected — with no authoritative topology there is nothing to extend (the offline-desk degraded path).

      Parameters

      • channels: readonly ChannelId[]

        the channel ids the console now also carries

      Returns ChannelId[]

      the channels actually added (empty when every one was already known)

    • The strip this console holds for id, or undefined when the console does not declare it.

      MEMBERSHIP IS THE ALLOCATION'S, NEVER THE FIRST TOUCH'S. strip creates on first touch, which is what the control intents want (a verb for a channel the topology declared must not fail on a cold map) but is useless as a membership question: it answers for aux/97 as readily as for input/1. Reading the MAP instead was the same defect wearing the other face — measured on the rig 2026-09-14, /channel declared 16 DCAs while /channel/dca/{2..16}/mute answered no-such-instance, because nothing had written to them yet (an internal spec §2b).

      So the question is asked of the DECLARATION, and the strip then fetched from the map that adoptDeclared has already filled for every declared channel. A strip minted by a stray touch outside the allocation is invisible here, which is what makes no-such-instance reachable again.

      Parameters

      Returns ChannelStrip | undefined

    • A channel's head-amp just became actuable — its physical input only now DECLARED itself (a box that establishes mid-show, or one whose transport node the adapter observes after sessions.autoload has already replayed the saved head-amp into the strips). See import('./adapter.js').MixerReceiver.onHeadAmpAvailable.

      The adapter reports only that a path OPENED; the values that must travel down it live here, on the canonical strip. So we re-assert them (reassertHeadAmp) — otherwise the box would keep its own state while the strip claims the operator's, and a strip that says 48 V ON over a box that is OFF is a safety lie, not a cosmetic drift.

      Parameters

      Returns Promise<void>

    • The HEAD-AMP GAIN (dB) pushed by the device — the box telling us what it already did. Mirrored into the strip's gain display on a head-amp channel for the same reason setHeadAmpGain does it: the encoder and the head-amp row are ONE physical control and must never disagree. State-only in both directions — nothing is written back to the device.

      Parameters

      Returns void

    • −20 dB analog PAD pushed by the device. State-only, mirroring onPhantom, and — like setPad — it touches nothing else: the pad is an attenuator ahead of an unmoved gain scale, so the stored gain is unaffected by it in either direction.

      Parameters

      Returns void

    • 48 V phantom pushed BY the device — someone turned it at the desk, or the desk reported its state after subscribe. Records + fans out exactly like onMute, and pointedly does NOT call back into the adapter: this is the device telling us what it already did.

      Until this existed the MixerReceiver.onPhantom hook was declared and fired (the Midas adapter pushes it on every phantom node change) into an engine that did not implement it, so a desk-side 48 V change never reached the model and the strip went on showing the last value openmixer itself had written. That is the same safety lie reassertHeadAmp exists to prevent, arriving from the other direction.

      Parameters

      Returns void

    • Drive the channel's STORED head-amp (phantom → pad → SENS) back onto the device, so engine state and box state agree. The re-assert for a late-declaring box (the sessions.autoload race), and the only path that carries saved head-amp onto hardware that wasn't there at boot — on a console with no headAmpOwner, which takes the job wherever it is set.

      Never clobbers a concurrent operator edit, by construction rather than by locking:

      • every write is READ FROM THE STRIP AT DISPATCH (inside the queued task), so a re-assert can never carry a snapshot that an edit has since superseded — it re-reads the one home;
      • it rides the same per-channel queueHeadAmp FIFO as the operator's own writes, so the two can never interleave out of order on the wire.

      Together those give the invariant: whatever lands LAST on the box is the strip's current value — an operator edit mid-heal wins, and a heal that dispatches after an edit re-reads it and re-asserts the same thing.

      Idempotent: re-asserting an unchanged strip re-writes the same values. Ordered phantom → pad → SENS deliberately: analog SENS is PAD-RELATIVE (head-amp.ts — the BOX applies the 20 dB shift), so the pad must be settled before the SENS byte is encoded against it. Re-asserting out of that order lands SENS 15 dB too hot on a padded input.

      A head-amp the strip never carried is left alone (undefined = the operator never dialled it) — the heal re-asserts stored truth, it does not invent a default.

      Parameters

      • channel: ChannelId

        the channel to re-assert onto its device

      Returns Promise<void>

    • Select a channel (the console's global SEL). Pure surface state — no device call (no adapter vocabulary carries a selection); the fan-out is the whole point: every subscribed surface follows to the same strip.

      Parameters

      Returns Promise<void>

    • Set the channel's INPUT ALIGNMENT delay (ms) — the input-stage latency that pushes one microphone back to meet another on the same source.

      NOT the FX delay (a musical echo) and not an output route's (a loudspeaker zone). On the software console it reaches the native strip's own alignment line, applied beside trim and polarity so every tap point and every downstream stage see the corrected signal.

      Parameters

      • channel: ChannelId
      • delayMs: number
      • reference: string = ''

      Returns Promise<void>

    • Set the channel's scribble-strip colour (a token hue key). Pure surface metadata, like select: recorded + fanned out so every surface (web strips, X-Touch scribbles) converges, persisted by the session — no adapter vocabulary carries it.

      Parameters

      Returns Promise<void>

    • Parameters

      • mode: "pfl" | "afl" | "sip"

      Returns Promise<void>

    • Set the channel's HEAD-AMP GAIN in dB — the BOX's analog gain ahead of the A/D, never the console's own digital stage (operator ruling, 2026-08-12: "We must distinguish digital gain that belongs to the console, from headAmp gain that belongs to the box."). Records + fans out, then forwards to the adapter's OPTIONAL MixerAdapter.setHeadAmpGain, which is where — and only where — the fabric's own encoding is applied.

      DISTINCT from setTrim: this buys signal-to-noise ahead of the converter, trim is the digital stage, and they never compose into one value.

      Parameters

      Returns Promise<void>

    • Say what this input IS — a factory template id, or null to unassign it. Metadata on the same footing as setColour: recorded + fanned out so every surface agrees, persisted by the session, carried by no adapter vocabulary and reaching no lane.

      Parameters

      • channel: ChannelId
      • instrument: string | null

      Returns Promise<void>

    • Engage / release monitor DIM — an extra attenuation on the cue master, audible on the routed monitor output (#191 operator feedback: "DIM must actually attenuate").

      Parameters

      • dim: boolean

      Returns Promise<void>

    • Set the monitor LEVEL (0..1) — the cue-bus master fader. Drives the routed monitor output in both mode A (pure cue) and mode B (takeover audition).

      Parameters

      • level: number

      Returns Promise<void>

    • Parameters

      • output: ChannelId | undefined
      • Optionaltakeover: boolean

      Returns Promise<void>

    • Rename the strip — the same record + emit + adapter shape as every other intent, and the ONE door a live rename and a session restore both drive. Until this existed each writer assigned strip.name directly and broadcast for itself (announceName, the session provider's broadcastName option) — the pattern task #72 retires: a fact's fan-out must follow from the write, never from every writer remembering to say it. The name event already existed for the device-push direction (onName); this is its intent twin.

      Parameters

      Returns Promise<void>

    • Engage / release the channel's −20 dB analog PAD (a REAC box control). Records + fans out (every surface converges, the session persists it), then forwards to the adapter's OPTIONAL MixerAdapter.setPad — real only on a REAC-backed adapter; absent (a no-op) on the software console, which has no analog pad.

      The pad touches no other state, and that is the whole shape of it (operator ruling, 2026-08-12: "pad and sens do not change their range … What changes is the actuator, pad subtracts 20 dB, that is all."). It is a fixed attenuation ahead of a gain scale that does not move, so the stored gain stays exactly where it is while the real analog level genuinely drops 20 dB — the entire point of a pad, and hiding it behind a compensating bump would lie about the signal.

      This method used to REBASE the stored value across the toggle. That existed only because the desk stored the Roland dBu SENS, in which the same physical setting is two different numbers at the two pad settings — so the rebase was defending a dB invariant through a dBu store. The store speaks dB now, and the round trip deleted itself.

      Parameters

      Returns Promise<void>

    • Set 48 V phantom on the channel's source. Records + fans out always (every surface converges, the session persists it); the device call is OPTIONAL — most adapters (incl. the software console: a PipeWire node has no preamp to power) carry no setPhantom, so until real preamp hardware is bound this is deliberately STATE-ONLY: persisted + broadcast, no +48 V actually switched.

      Parameters

      Returns Promise<void>

    • Set the channel's polarity (phase) invert. Records + fans out, then forwards to the adapter's optional setPolarity — on the software console that reaches the native strip DSP as a real sign flip (see SoftwareMixerAdapter.setPolarity).

      Parameters

      Returns Promise<void>

    • Arm / disarm the SIP safety guard (#192). Console-wide surface state; the SIP-mode gate is enforced by the server (a disarmed guard refuses cue.setMode('sip')).

      Parameters

      • enabled: boolean

      Returns void

    • Parameters

      Returns Promise<void>

    • Set the channel's digital input TRIM (dB) — the fine head-amp offset that composes with setGain's coarse encoder value. Records trimDb on the canonical strip, fans out a trim event, and drives the adapter's ONE head-amp stage with the composed gain + trim (see applyHeadAmp) — the two controls never clobber each other, and the device hears the sum a real desk's gain staging would produce.

      Parameters

      Returns Promise<void>

    • The console-wide half of the solo-in-place condition — the cue mode, and whether ANY strip anywhere is soloed (mixer_rt.c's any_solo).

      Derived from the strips on every call, exactly as the native fan-out derives it before pushing setCueGlobal: a stored copy would go stale the moment a solo landed through a door that forgot to refresh it. One implementation, so the value the RT graph acts on and the value /channel/{kind}/{index}/mute reports as bySip are the same reading.

      Returns SipCondition

    • Every strip the console holds — the DECLARED roster, in the allocation's own order, so this list and /channel's (which reads topology.channels directly) cannot disagree.

      Without a topology it is the touched map, for the reason declares falls back to it: nothing has been declared, so there is nothing else to be honest about.

      Returns ChannelStrip[]

    • Clear every soloed strip in ONE pass, emitting a single soloClear event — not N individual solo events, so the UI doesn't visually cascade-clear one tile at a time.

      Returns Promise<void>