feat(editor): DAW mixer console — vertical channel strips, live meters, +10 dB - #295
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (12)
📝 WalkthroughWalkthroughThe mixer is replaced with a bottom-drawer channel-strip interface. Audio routing adds a post-limiter master bus, 0..110 dB fader headroom, analyser-based live metering, dB labels, host meter hooks, and updated lifecycle and regression tests. ChangesMixer audio and metering
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Editor
participant MixerPanel
participant AudioMixer
participant Host
Editor->>MixerPanel: open bottom-drawer mixer
MixerPanel->>AudioMixer: update fader level
AudioMixer->>AudioMixer: route audio through masterGain
MixerPanel->>Host: request mixerMeterLevels()
Host->>AudioMixer: read analyser levels and peaks
AudioMixer-->>Host: return bus and track meters
Host-->>MixerPanel: provide live meter data
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…ack order
Three Tracks/mixer fixes surfaced by dogfooding:
1. The Master row and tempo guide could vanish ("No guide"): sources were
derived from S.audioUrl, which (a) still points at the previous song at
install time and (b) active-source switching reassigns to a focused
stem. Derive the master from the stable S.masterAudioUrl instead, pinned
at install.
2. "Master Mix" naming: the master track now defaults to the SONG name (its
recording), and the guide button follows the track's DISPLAY name so an
inline rename wins instead of reverting to a generic label.
3. Mixer strips now reorder to match a drag-reorder of the Tracks column
(and rename with it): the mixer reads host.mixerTrackOrder ->
trackSessionOrderedMixKeys() and reorders via _mixerOrderedPartsPure; the
tree commit refreshes the mixer.
Tests: _mixerOrderedPartsPure ordering (tree order, tail-stable, empty);
master-name default/override. 184 JS suites, lint 0 errors.
Fixes behaviour in the guide (#290), Tracks UI (#292), and mixer (#295)
features; carried on the stack tip. Verified live in the desktop build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
9c0a04f to
f7f6f85
Compare
dc09c19 to
2d71828
Compare
Clicking an audio track in the Tracks column now focuses it as the active reference: its decoded buffer becomes S.audioBuffer, so the main waveform shows that source and onset tools (Suggest, snap) analyze it. Playback is NOT rerouted — the newly-active source plays via the reference path and every OTHER live source keeps playing through the scheduler, so what you hear is unchanged; only what you see and analyze follows the click. This generalizes the additive engine to #285's model without adopting its full unified scheduler: _liveAudioSources() now returns master + stems, _startStemSources schedules every live source EXCEPT the active one (exposed as the pure _scheduledSourceIdsPure), and the active one rides the existing S.audioSource → _refGain path. activateTrackAudioSource decodes on demand (reusing the source cache), installs the buffer, recomputes the waveform, and restarts playback split if playing. The master's URL is held in S.masterAudioUrl so it survives while a stem is active (S.audioUrl then points at the stem). Onsets self-invalidate on the buffer swap; syncStemAudio adopts the already-decoded active buffer for free rather than re-fetching. Wired: host.selectTrackSessionSource → activateTrackAudioSource; the Tracks-column audio-row click focuses the source. resetStemAudioCache and the load/create paths reset activeAudioSourceId to 'master' and seed S.masterAudioUrl. The last parity gap from #285 — completes the re-cut. Tests: stem_engine.test.mjs pins the active-vs-scheduled split (_scheduledSourceIdsPure). 184 JS suites, 291 pytest, lint 0 errors. Stacked on feat/editor-mixer-meters (#295). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…ack order
Three Tracks/mixer fixes surfaced by dogfooding:
1. The Master row and tempo guide could vanish ("No guide"): sources were
derived from S.audioUrl, which (a) still points at the previous song at
install time and (b) active-source switching reassigns to a focused
stem. Derive the master from the stable S.masterAudioUrl instead, pinned
at install.
2. "Master Mix" naming: the master track now defaults to the SONG name (its
recording), and the guide button follows the track's DISPLAY name so an
inline rename wins instead of reverting to a generic label.
3. Mixer strips now reorder to match a drag-reorder of the Tracks column
(and rename with it): the mixer reads host.mixerTrackOrder ->
trackSessionOrderedMixKeys() and reorders via _mixerOrderedPartsPure; the
tree commit refreshes the mixer.
Tests: _mixerOrderedPartsPure ordering (tree order, tail-stable, empty);
master-name default/override. 184 JS suites, lint 0 errors.
Fixes behaviour in the guide (#290), Tracks UI (#292), and mixer (#295)
features; carried on the stack tip. Verified live in the desktop build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
f7f6f85 to
2ca32ac
Compare
2d71828 to
85395ef
Compare
Clicking an audio track in the Tracks column now focuses it as the active reference: its decoded buffer becomes S.audioBuffer, so the main waveform shows that source and onset tools (Suggest, snap) analyze it. Playback is NOT rerouted — the newly-active source plays via the reference path and every OTHER live source keeps playing through the scheduler, so what you hear is unchanged; only what you see and analyze follows the click. This generalizes the additive engine to #285's model without adopting its full unified scheduler: _liveAudioSources() now returns master + stems, _startStemSources schedules every live source EXCEPT the active one (exposed as the pure _scheduledSourceIdsPure), and the active one rides the existing S.audioSource → _refGain path. activateTrackAudioSource decodes on demand (reusing the source cache), installs the buffer, recomputes the waveform, and restarts playback split if playing. The master's URL is held in S.masterAudioUrl so it survives while a stem is active (S.audioUrl then points at the stem). Onsets self-invalidate on the buffer swap; syncStemAudio adopts the already-decoded active buffer for free rather than re-fetching. Wired: host.selectTrackSessionSource → activateTrackAudioSource; the Tracks-column audio-row click focuses the source. resetStemAudioCache and the load/create paths reset activeAudioSourceId to 'master' and seed S.masterAudioUrl. The last parity gap from #285 — completes the re-cut. Tests: stem_engine.test.mjs pins the active-vs-scheduled split (_scheduledSourceIdsPure). 184 JS suites, 291 pytest, lint 0 errors. Stacked on feat/editor-mixer-meters (#295). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…ack order
Three Tracks/mixer fixes surfaced by dogfooding:
1. The Master row and tempo guide could vanish ("No guide"): sources were
derived from S.audioUrl, which (a) still points at the previous song at
install time and (b) active-source switching reassigns to a focused
stem. Derive the master from the stable S.masterAudioUrl instead, pinned
at install.
2. "Master Mix" naming: the master track now defaults to the SONG name (its
recording), and the guide button follows the track's DISPLAY name so an
inline rename wins instead of reverting to a generic label.
3. Mixer strips now reorder to match a drag-reorder of the Tracks column
(and rename with it): the mixer reads host.mixerTrackOrder ->
trackSessionOrderedMixKeys() and reorders via _mixerOrderedPartsPure; the
tree commit refreshes the mixer.
Tests: _mixerOrderedPartsPure ordering (tree order, tail-stable, empty);
master-name default/override. 184 JS suites, lint 0 errors.
Fixes behaviour in the guide (#290), Tracks UI (#292), and mixer (#295)
features; carried on the stack tip. Verified live in the desktop build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
2ca32ac to
dc58021
Compare
85395ef to
ff7807b
Compare
Clicking an audio track in the Tracks column now focuses it as the active reference: its decoded buffer becomes S.audioBuffer, so the main waveform shows that source and onset tools (Suggest, snap) analyze it. Playback is NOT rerouted — the newly-active source plays via the reference path and every OTHER live source keeps playing through the scheduler, so what you hear is unchanged; only what you see and analyze follows the click. This generalizes the additive engine to #285's model without adopting its full unified scheduler: _liveAudioSources() now returns master + stems, _startStemSources schedules every live source EXCEPT the active one (exposed as the pure _scheduledSourceIdsPure), and the active one rides the existing S.audioSource → _refGain path. activateTrackAudioSource decodes on demand (reusing the source cache), installs the buffer, recomputes the waveform, and restarts playback split if playing. The master's URL is held in S.masterAudioUrl so it survives while a stem is active (S.audioUrl then points at the stem). Onsets self-invalidate on the buffer swap; syncStemAudio adopts the already-decoded active buffer for free rather than re-fetching. Wired: host.selectTrackSessionSource → activateTrackAudioSource; the Tracks-column audio-row click focuses the source. resetStemAudioCache and the load/create paths reset activeAudioSourceId to 'master' and seed S.masterAudioUrl. The last parity gap from #285 — completes the re-cut. Tests: stem_engine.test.mjs pins the active-vs-scheduled split (_scheduledSourceIdsPure). 184 JS suites, 291 pytest, lint 0 errors. Stacked on feat/editor-mixer-meters (#295). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…ack order
Three Tracks/mixer fixes surfaced by dogfooding:
1. The Master row and tempo guide could vanish ("No guide"): sources were
derived from S.audioUrl, which (a) still points at the previous song at
install time and (b) active-source switching reassigns to a focused
stem. Derive the master from the stable S.masterAudioUrl instead, pinned
at install.
2. "Master Mix" naming: the master track now defaults to the SONG name (its
recording), and the guide button follows the track's DISPLAY name so an
inline rename wins instead of reverting to a generic label.
3. Mixer strips now reorder to match a drag-reorder of the Tracks column
(and rename with it): the mixer reads host.mixerTrackOrder ->
trackSessionOrderedMixKeys() and reorders via _mixerOrderedPartsPure; the
tree commit refreshes the mixer.
Tests: _mixerOrderedPartsPure ordering (tree order, tail-stable, empty);
master-name default/override. 184 JS suites, lint 0 errors.
Fixes behaviour in the guide (#290), Tracks UI (#292), and mixer (#295)
features; carried on the stack tip. Verified live in the desktop build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Three real bugs in the new DAW mixer console (PR #295): - The MASTER strip was dead. Its fader called editorSetMixLevel('master'), which early-returned for any bus but ref/guide/click — and even past that, _mixSetBusGain mapped anything-not-ref/guide to the CLICK key + node. So a master move silently retuned the click bus, never trimmed the output, and never persisted editorMixMaster. Accept 'master' in both, routing it to masterGain + its own pref. - Bus fader labels reverted to '%'. editorSetMixLevel wrote 'p%', but the panel now shows dB everywhere. Live drags flipped the label back to a percent. Emit the same dB label the strips use. - Stem meter taps leaked on teardown. teardownAudio → _stemGainsReset disconnected the stem gains but left their AnalyserNode taps in the map (only resetStemAudioCache cleaned them, via an over-broad _detachTrackMeters that ALSO nuked live part meters). A survivor keeps reading a dead node and blocks the re-attach guard, so the stem's meter goes dead on the next song. _stemGainsReset now detaches each tap with its gain (mirroring _partGainsReset); _detachTrackMeters is gone. Regression tests fail pre-fix, pass after: master routing/persist (slice env), editorSetMixLevel master persist + dB label (full import), and the stem tap-teardown lifecycle against the real audioMixerMeterLevels read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@assets/v3-theme.css`:
- Around line 602-613: Update the opening animation behavior for `#plugin-editor`
.editor-mixer-drawer so reduced-motion users do not run editor-mixer-rise when
the drawer opens. Add an appropriate prefers-reduced-motion override while
preserving the existing closing-animation behavior and normal-motion
transitions.
In `@screen.html`:
- Around line 389-390: Update screen.html lines 389-390 so the
`#editor-mixer-play-all` button uses a dedicated full-text class instead of
.editor-mix-ms. In assets/v3-theme.css lines 619-633, scope the 18px titlebar
font-size rule to the close button and style the All tracks class with automatic
width and appropriate padding.
In `@src/mixer-panel.js`:
- Around line 200-202: Expose the formatted dB value through aria-valuetext for
every fader. In src/mixer-panel.js at lines 200-202, initialize generated track
sliders from _mixerFaderLabelPure(st.vol); at lines 292-296, set it when bus
sliders are seeded; and at lines 356-363, update it whenever the visible track
label changes. In screen.html at lines 397-424, add initial dB aria-valuetext
values for SOURCE, GUIDE, CLICK, and MASTER and keep them synchronized in the
bus input handler.
- Around line 215-228: Update _meterPeakForKey to mirror _mixerMeterInputPure:
return levels.ref for the active audio key and levels.guide for the active
transcription key, using the relevant active identifiers in its arguments or
call sites. Preserve existing bus and track peak handling, and apply the same
fallback behavior to the additional peak-readout path noted in the comment.
In `@tests/mixer_meter_teardown.test.js`:
- Line 24: Update the `_stemGainsReset` extraction in the test to capture the
complete function body, including the `stemGainNodes.clear()` call after the
loop. Replace the current brace-limited regex approach with parsing or matching
that accounts for nested braces, while preserving the existing function-size
assertion.
In `@tests/mixer_panel.test.mjs`:
- Around line 189-198: Update the test “bus faders seed from host.mixUiState on
open” to wrap the stub assignment and assertions in a try/finally block. In the
finally block, always restore the saved host.mixUiState value and close the
mixer panel with editorToggleMixerPanel(false, true), preserving the existing
assertions and setup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 07540a8c-0f33-4d86-bf5d-805fc202ef26
📒 Files selected for processing (12)
CHANGELOG.mdassets/v3-theme.cssscreen.htmlsrc/audio.jssrc/host.jssrc/main.jssrc/mixer-panel.jstests/audio_mixer.test.jstests/loop_ab.test.jstests/mixer_meter_teardown.test.jstests/mixer_meters.test.mjstests/mixer_panel.test.mjs
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Product ruling applied (decision item 3 from the review): +10 dB of headroom on EVERY fader — the SOURCE/GUIDE/CLICK buses and MASTER now ride the same 0–110 scale as the channel strips (one pct→gain curve everywhere, no inert fader zones). |
|
@coderabbitai review |
✅ Action performedReview finished.
|
5c48b4d to
e199123
Compare
Correction PR (parity item) — the mixer/meters #285 features I'd deferred, ported faithfully to #285's design at Christian's direction (vertical channel strips in a bottom drawer, not my earlier docked-panel adaptation).
What changed
The docked mixer side-panel becomes #285's bottom mixer drawer: a horizontal row of vertical channel strips (one per stem + transcription part), each a live level meter beside a rotated fader, then the SOURCE / GUIDE / CLICK utility buses and a dedicated MASTER output strip. It rises on open, falls on close (reduced-motion aware), and opens closed each session (a per-screen view toggle, not a persisted pref).
audioMixerMeterLevelsreads RMS→(−60..0 dBFS) levels + peak dB each frame; the panel's rAF tick fills the bars with instant-attack/gravity-decay ballistics and a peak-hold clip readout.limiter → masterGain → destination). The reference recording joins there too, so it's metered and master-trimmed but still never through the limiter — a hot recording is never colored.partStripState/clapStateresolve volume through_mixerGainForFaderPure, so stems and synth parts alike can be pushed to +6 dB; every fader reads its level in dB.Tests
tests/mixer_meters.test.mjs(new, 4): the sample→level/peak math, the meter ballistics, and the dB readout label.tests/mixer_panel.test.mjs: reconciled to the +6 dB clamp, dB fader labels, the master bus, the animated close (instant in the unit env), and the always-closed init; a new fader-gain pure case.tests/audio_mixer.test.js+tests/loop_ab.test.js: the sliced-source envs inject the meter-tap stub, and the ref-routing assertion now expectsrefGain → masterGain → destination(still bypassing the limiter).Gates: 184 JS suites, pytest 291 / 2 skipped, lint 0 errors / 3 baseline warnings.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Summary by CodeRabbit
New Features
Bug Fixes