Skip to content

Stage 1: mixer drawer (12 channel strips: fader, pan, FX sends, mute/solo, meter, mini-scope)#2

Merged
astrobyte-dev merged 2 commits into
mainfrom
stage-1-mixer
May 25, 2026
Merged

Stage 1: mixer drawer (12 channel strips: fader, pan, FX sends, mute/solo, meter, mini-scope)#2
astrobyte-dev merged 2 commits into
mainfrom
stage-1-mixer

Conversation

@astrobyte-dev

@astrobyte-dev astrobyte-dev commented May 25, 2026

Copy link
Copy Markdown
Owner

Stage 1: mixer drawer (12 channel strips)

A bottom-drawer mixer alongside the cards: 12 orbit-backed strips (inactive ones ghosted), each with a dB-linear gain fader, pan, FX1 (reverb) and FX2 (delay) sends, mute/solo, a meter, and a live mini-scope. Rebased onto main after PR #1; single commit 15338c3.

What changed

  • Mixer (window.AbxMixer) — bottom drawer toggled from the transport (CSS height transition, no re-render). 12 strips = the 12 SuperDirt orbits (d1-d12); d13-d16 have no orbit and are not shown (in-file CONSTRAINT comment so nobody "fixes" it back to 16).
    • Fader: dB-linear, unity at 0.75, top = 2.0 (matches the card gain knob's max so the two agree), 2% deadband = hard silence. Writes # gain via the existing cmd:set path.
    • Pan / FX1 / FX2: # pan, # room, # delay — the FX sends are SuperDirt's native per-orbit effects, so zero SuperCollider changes and no orbit-routing.
    • Mute/solo: routed through the cards' data-cmd dispatcher, so they stay in sync both ways for free.
    • Meter + mini-scope: per-channel from /state.scopes; the mini-scope reuses AbxScope.draw (no reimplementation).
  • Pure math single-sourced in dashboard-mixer.js (faderToGain/gainToFader/panPositionToString/panStringToPosition), unit-tested in src/mixer.test.ts (12 cases). DOM/UI guarded by typeof document so the Node test imports the math cleanly. No separate src/mixer.ts to drift (same single-source pattern as dashboard-dsp.js).
  • Dispatcher guards propagated: the new AbxMixer.handleClick/handleInput calls carry the same window.AbxMixer && guard as the Stage 0 dispatchers. This is the Stage 0 resilience fix applied to new code, not a new concern.
  • Test floor 19 → 31 in run-tests.mjs (track 7 + dsp 12 + mixer 12), per the "raise the floor in the commit that adds tests" convention.

Standing review

  1. Karpathy/process. The four design decisions (dock, FX architecture, strip count, fader law) and then the test cases + verification approach were each proposed and confirmed before any code. Surgical: one feature module + one test + wiring.
  2. Tests. 12 new pure-math cases (deadband silence, unity at 0.75, top = exactly 2.0, inverse round-trip, out-of-range parking, pan round-trip). npm test green before and after. The new wiring-assertion test closes the Stage 0 gap (see Verification).
  3. Architecture. dashboard-mixer.js is one feature module (math + UI behind AbxMixer); reuses Abx.* and AbxScope.draw; no duplication. The 12-not-16 reality is a load-bearing in-file constraint tied to sc/superdirt_startup.scd.
  4. Security. No new user-controllable strings reach SC/Tidal: sends/fader/pan go through the same cmd:set -> applyParam path as the card knobs. No new endpoints, no new persisted state.
  5. Concurrency/lifecycle. One new rAF (mixer meter + mini-scope) that is idle while the drawer is closed. No new timers/intervals: row edits reuse the existing cmd:set send. Cross-namespace calls guarded.
  6. Performance. Mixer tick does nothing when closed; mini-scopes reuse AbxScope.draw; no new poll endpoints or SSE streams.
  7. Comments. The hard-won/why bits are documented: the deadband rationale, the FLOOR convention, the 12-not-16 constraint, and the guard propagation.
  8. UX regressions. Mute/solo on a strip syncs with the card buttons (shared data-cmd). Dispatch branches stay mutually exclusive, so adding the guarded AbxMixer delegate changes nothing for existing controls. No keyboard shortcut or debounce threshold changed.
  9. README/docs. Pure additive feature; the README feature list does not yet mention the mixer. Recommend a one-line bullet in a follow-up (flagging rather than expanding this PR's scope).
  10. Next-stage readiness. Stage 2 (effect racks) can build on the per-channel #-param model and the strip UI; the per-orbit (not shared-bus) send choice is documented should Stage 5 want true aux sends. Harder than expected: nothing major; the rebase reconciliation against the Stage 0 guards was the only fiddly bit, handled in one pass.

Verification

  • Unit: 31/31 via run-tests.mjs, floor 31 enforced (a discovery regression now fails loudly).
  • Smoke (headless): step grid / cheats / curves / samples panels open, #steps/#cheats deep-links work, zero console errors, with the dispatcher guards in place.
  • Wiring-assertion (closes the Stage 0 gap): injects a known active slot, intercepts /cmd at the fetch layer (no boot/audio), actuates every interactive control, asserts each fires the correct command. A dead or mis-wired control fails. Result (exit 0):
fader (gain)          PASS  -> set d1 gain  (faderToGain(0.5))
pan                   PASS  -> set d1 pan   (0.3)
FX1 (room)            PASS  -> set d1 room  (0.40)
FX2 (delay)           PASS  -> set d1 delay (0.30)
mute                  PASS  -> mute d1
solo                  PASS  -> solo d1
ghost strip d12 inert PASS  -> disabled, 0 cmd leak
errors: []

Manual checklist (needs your ears + hands)

  • Open the mixer drawer from the transport; all 12 strips render, inactive ones ghosted
  • Play a beat; per-strip meters move and mini-scopes draw on active channels
  • Drag a fader -> the layer's volume changes audibly within the debounce window
  • Pan, FX1 (reverb), FX2 (delay) each audibly affect the channel
  • Mute/solo on a strip stays in sync with that channel's card buttons (and vice versa)
  • Drawer open/close is instant, no re-render flash; fader feel is acceptable for v1

…se test floor

Mixer drawer (window.AbxMixer): 12 orbit-backed channel strips with a dB-linear gain
fader (unity at 75%, top = 2.0 to match the card gain knob), pan, FX1=room / FX2=delay
sends (native per-orbit SuperDirt effects, no SC changes), mute/solo (shared data-cmd
path with the cards so they stay in sync), a meter, and a mini-scope (reuses
AbxScope.draw). Pure math single-sourced in dashboard-mixer.js, unit-tested via
src/mixer.test.ts (12 cases). In-file CONSTRAINT: 12 strips, not 16 (SuperDirt numOrbits=12).

Rebased onto main after PR #1. Resolved the dashboard.js dispatcher region by keeping
main's window.AbxX guards AND adding the same guard to the new AbxMixer.handleClick /
handleInput call-sites: propagation of the Stage 0 fix to new code, not a new concern.

Raised run-tests.mjs FLOOR 19 -> 31 (track 7 + dsp 12 + mixer 12), applying the
"raise the floor in the same commit that adds tests" convention so a discovery regression
cannot silently drop the mixer tests.
Copilot AI review requested due to automatic review settings May 25, 2026 13:08
The standing review checks that new features appear in the feature list,
and the Stage 1 mixer was missing. One bullet, neutral wording (no FL
references), matching the existing voice.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “mixer drawer” UI to the dashboard, providing 12 orbit-backed channel strips (fader/pan/FX sends/mute/solo/meter/mini-scope) and unit-tested, single-sourced parameter mapping math shared between browser and Node tests.

Changes:

  • Introduces dashboard-mixer.js (window.AbxMixer) + wiring in dashboard.js to handle mixer inputs/clicks and rerender when state changes.
  • Extends dashboard.html with mixer drawer markup/CSS and adds the mixer module script.
  • Adds mixer.test.ts for fader/pan math and raises the test-count floor in run-tests.mjs.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
mcp/src/mixer.test.ts New unit tests for mixer fader/pan math (single-source import of dashboard module).
mcp/run-tests.mjs Raises enforced test-count floor to account for new mixer tests.
mcp/dashboard.js Hooks mixer delegate handlers into existing input/click dispatch and render rerender path.
mcp/dashboard.html Adds mixer drawer UI markup, CSS, and loads dashboard-mixer.js.
mcp/dashboard-mixer.js Implements mixer module (math + UI + render/tick loop).
mcp/dashboard-mixer.d.ts Adds ambient typings for the mixer math surface exposed on globalThis.AbxMixer.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mcp/dashboard-mixer.js
Comment on lines +92 to +105
// write a param via the same cmd:set path the card knobs use; ghost strips have nothing to write to.
function setParam(slot, param, value){
if (!((Abx.state().slots || {})[slot])) return;
Abx.send({ cmd: "set", slot: slot, param: param, value: value });
}
// delegates (mirror the AbxSeq/AbxCurves contract). M/S go through the core data-cmd dispatcher
// (shared with the cards), so mute/solo stay in sync both ways for free.
function handleInput(e){
var el = e.target, slot = el.dataset && el.dataset.slot; if (!slot) return false;
if (el.classList.contains("fader")) { setParam(slot, "gain", +faderToGain(+el.value).toFixed(GAIN_DP)); return true; }
if (el.classList.contains("pan")) { setParam(slot, "pan", panStringToPosition(el.value)); return true; }
if (el.classList.contains("fx1")) { setParam(slot, "room", +(+el.value).toFixed(2)); return true; }
if (el.classList.contains("fx2")) { setParam(slot, "delay", +(+el.value).toFixed(2)); return true; }
return false;
Comment thread mcp/dashboard-mixer.js
Comment on lines +109 to +115
// re-render strips when the live slot set / mute / solo changes (called from dashboard.js render()).
// Keyed on structure only (not per-value), so dragging a fader does not yank itself mid-gesture.
var lastSig = "";
function maybeRerender(){
if (!isOpen()) return;
var st = Abx.state();
var sig = JSON.stringify({ s: Object.keys(st.slots || {}), m: st.muted, so: st.solo });
Comment thread mcp/dashboard.html
Comment on lines 261 to 268
<main><div class="grid" id="grid"></div></main>
<div id="mixwrap">
<div class="mixbar"><span class="seqlabel">&#127898; MIXER</span>
<span class="seqhint">12 channels (orbits) · fader = volume (dB, unity at 75%) · FX1 = reverb send · FX2 = delay send · pan · M/S. Empty channels are ghosted.</span>
</div>
<div id="mixrows"></div>
</div>
<div id="console">
@astrobyte-dev
astrobyte-dev merged commit bb0ad1c into main May 25, 2026
1 check passed
@astrobyte-dev
astrobyte-dev deleted the stage-1-mixer branch May 25, 2026 13:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants