Skip to content

feat(editor): add source-aware DAW track session - #285

Closed
ChrisBeWithYou wants to merge 21 commits into
mainfrom
feat/editor-daw-track-session
Closed

ChrisBeWithYou wants to merge 21 commits into
mainfrom
feat/editor-daw-track-session

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • adds one DAW-style Tracks area for master/stem audio, transcription lanes, and optional folders, with shared selection, resizing, reordering, inline rename, M/S/faders, pairing, and a traditional bottom mixer drawer
  • persists the track tree, pairings, guide mode, and non-destructive audio-track removals; double-click opens a transcription's native string, piano-roll, or drum editor
  • lets an audio row become a locked Metronome Guide, maps its pulses from the chosen anchor through every remaining authored barline and the open final measure (including low-confidence extrapolation), and exposes Accept Whole Fit as one undoable tempo-map edit
  • makes Tempo Map marquee selection reliable while preserving full-height marker dragging, allows uncapped selection (including Ctrl/Cmd+A), and exposes an explicit undoable Lock N barlines action
  • makes G propose through the final authored barline for ordinary audio too: the trusted detected prefix stays intact and any post-confidence-break tail is visibly low-confidence, editable, and never auto-committed
  • guarantees G analyzes the locked Metronome Guide even after another audio or transcription row becomes active; guide activation is awaited and revalidated across the async load
  • restarts G from the active tempo marker, locked or unlocked, and proposes only the remaining markers; stale multi-selections can no longer send analysis back toward the beginning
  • keeps the authored click-pulse phase across downstream locked barlines and prevents unsaved projects from inheriting another new song's blank-key locks
  • schedules all decoded audio sources together, keeps transcription instruments live by default, drives calibrated post-fader meters, and gives live audio faders real +6 dB headroom

Compatibility and scope

  • old packs normalize into a Master Mix plus generated audio/transcription leaves
  • deleting audio is non-destructive: source media stays in the project while the row, playback, and mixer strip are tombstoned
  • the final transcription arrangement remains protected because the feedpak format requires one
  • retains the legacy stems response for existing consumers; does not use or merge external PR feat(editor): stem mixer — isolate any instrument while editing #275

Validation

  • npm test: 160/160 test files passing
  • python -m pytest: 262 passed, 2 skipped
  • npm run lint: 0 errors, 3 pre-existing warnings
  • hosted CI: JavaScript, Python, lint, and CodeRabbit green at ecbd8bd
  • combined all-PR desktop build: 179/179 JavaScript files and 291 Python tests passing; installed as local integration commit 2c014b2

UI verification

  • Combined build is installed in the desktop app and its bundled backend responds on port 18000.
  • Real multistem audibility, moving meters, same-song locked-guide fitting, and the new selection/delete/open interactions still require maintainer desktop testing; they are not claimed visually confirmed yet.
  • Supplied Moises click analysis finds 615 stable pulses through 250.92 s, while the imported GP grid contains 556 beats through 226.94 s; tempo fitting cannot invent the missing authored tail.

Summary by CodeRabbit

  • New Features
    • Added a source-aware DAW-style left track session with audio/transcription pairing, folders, drag reordering/rename, and tempo-guide lock.
    • Introduced a bottom-drawer mixer with per-track and master faders, ref/guide/click utilities, and live meters; added waveform/source switching support.
    • Refreshed parts view to use the unified track-session layout and improved wheel scrolling behavior while in parts view.
  • Bug Fixes
    • Normalize legacy Guitar Pro XML to UTF-8 before parsing to prevent import issues.
    • Made note/pitch edits silent by disabling preview blips.
  • Documentation
    • Updated the User Guide for the new Mixer and track-session workflow.
  • Tests
    • Added/expanded regression coverage for encoding normalization, track-session persistence, and mixer behavior.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change adds persisted source-aware track sessions with audio-source selection, transcription pairing, folder organization, and tempo-guide controls. It also introduces cached reference switching, multitrack playback, master mixer routing, a bottom mixer drawer, silent edit previews, XML encoding normalization, and related tests and documentation.

Changes

Track session and mixer integration

Layer / File(s) Summary
Backend persistence and import contracts
routes.py, tests/test_gp_xml_encoding.py, tests/test_track_session_persist.py
Track-session data and audio-source metadata are sanitized during load/save, while converter-generated XML is normalized to UTF-8 and covered by regression tests.
Track-session model and editor wiring
src/state.js, src/track-session.js, src/file-ops.js, src/host.js, src/main.js, tests/track_session.test.mjs
The editor normalizes, renders, edits, loads, saves, and synchronizes source-aware tracks, transcription pairings, folders, tempo guides, and host selections.
Audio-source caching and mixer routing
src/audio.js, src/mixer-panel.js, tests/audio_mixer.test.js, tests/mixer_panel.test.mjs, tests/loop_ab.test.js, tests/multitrack_audio.test.mjs
Reference buffers can be cached and switched by source ID; multitrack playback, master routing, metering, extended faders, and silent edit previews are implemented.
Track and mixer interface
screen.html, assets/v3-theme.css, src/parts-view.js, docs/USER-GUIDE.md, CHANGELOG.md
The layout adds a track-session sidebar and bottom mixer drawer, updates unified lane rendering and styling, and documents the updated behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant TrackSession
  participant Host
  participant Audio
  participant Mixer
  Editor->>TrackSession: Initialize and render session
  TrackSession->>Host: Select target or source
  Host->>Audio: Activate cached audio source
  TrackSession->>Mixer: Update strip state
  Mixer->>Host: Notify mix change
  Host->>Audio: Apply track mix and reference gain
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: byrongamatos

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a source-aware DAW track session to the editor.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-daw-track-session

Comment @coderabbitai help to get the list of available commands.

@ChrisBeWithYou
ChrisBeWithYou marked this pull request as ready for review July 15, 2026 08:48

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 8

🤖 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 538-543: Update the `#plugin-editor` .editor-mixer-drawer styles so
the hidden class remains effective: remove the explicit display: flex
declaration or add a more-specific .editor-mixer-drawer.hidden rule with
display: none, preserving the drawer’s collapsed state when closed.

In `@routes.py`:
- Line 4005: Extend the create/conversion flows to parse track_session like the
/save flow and pass it through to _write_sloppak_pak in both save_as_sloppak and
/build. Ensure newly created or converted sessions preserve folders, pairings,
and tempo-guide settings.
- Around line 3804-3817: Update the stem cache filename construction that
populates _stem_urls so each cached path includes a unique per-source index or
collision suffix, preventing sanitized IDs from overwriting one another. Keep
the source ID generation and audio_sources mapping in the shown loop consistent
with the resulting cached URLs.

In `@src/audio.js`:
- Around line 73-76: Update the cached-source branches in the audio handling
flow, including the branch around cached and _installDecodedAudio, to call
cancelAudioLoad() before installing either cached master or stem audio. Ensure
both cache-hit paths invalidate pending loads and advance the generation before
_installDecodedAudio runs, preventing an older decode from replacing the newly
selected source.

In `@src/file-ops.js`:
- Around line 177-180: Update the flow before installTrackSession to populate
the synthesized Master Mix source with data.audio_url when normalization creates
it with an empty URL. Ensure activateTrackAudioSource can validate the Master
Mix against the cached audio, while preserving existing audio_sources handling
for servers that provide a populated master source.

In `@src/track-session.js`:
- Around line 288-293: Update the select action in the track-session control
handler so an unpaired transcription falls back to the Master Mix source rather
than S.trackSession.tempoGuideSourceId. Preserve the pairedSourceId selection
when an explicit pairing exists, and continue passing the resolved
S.focusedSourceId to host.selectTrackSessionSource.
- Around line 61-87: Update the track normalization logic around audioTrackId
and transcriptionTrackId so persisted folders or unrelated leaves cannot cause
canonical source/target tracks to be omitted via seen.has(id). Reserve canonical
IDs only for their matching leaf types and source/target identifiers, or assign
a collision-free ID to conflicting persisted items, while preserving
deduplication for valid matching leaves.

In `@tests/test_gp_xml_encoding.py`:
- Around line 22-30: Update test_generated_utf8_xml_is_left_byte_identical to
read the file bytes after _normalize_generated_xml_encoding([path]) and assert
they exactly match the original payload, preserving the valid UTF-8 byte
identity guarantee.
🪄 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: 0a696eba-09b5-4420-8d99-63de005d913b

📥 Commits

Reviewing files that changed from the base of the PR and between d7c5e3c and 2d7b82f.

📒 Files selected for processing (18)
  • CHANGELOG.md
  • assets/v3-theme.css
  • docs/USER-GUIDE.md
  • routes.py
  • screen.html
  • src/audio.js
  • src/file-ops.js
  • src/host.js
  • src/main.js
  • src/mixer-panel.js
  • src/state.js
  • src/track-session.js
  • tests/audio_mixer.test.js
  • tests/loop_ab.test.js
  • tests/mixer_panel.test.mjs
  • tests/test_gp_xml_encoding.py
  • tests/test_track_session_persist.py
  • tests/track_session.test.mjs

Comment thread assets/v3-theme.css
Comment thread routes.py
Comment thread routes.py
Comment thread src/audio.js
Comment thread src/file-ops.js Outdated
Comment thread src/track-session.js
Comment thread src/track-session.js
Comment thread tests/test_gp_xml_encoding.py

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
tests/mixer_panel.test.mjs (1)

163-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for live meter routing and lifecycle.

These assertions cover pure ballistics and rendered markup, but not _meterInputForKey, host.mixerMeterLevels(), or meter start/stop behavior. A routing or lifecycle regression could therefore pass the suite; add a focused fake-RAF integration test for active audio, master, guide, and panel hide behavior.

Also applies to: 216-217

🤖 Prompt for 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.

In `@tests/mixer_panel.test.mjs` around lines 163 - 169, Add a focused fake-RAF
integration test alongside the existing meter tests that exercises
_meterInputForKey and host.mixerMeterLevels() for active audio, master, and
guide routing, then verifies meter updates stop when the panel is hidden and
resume or initialize correctly when shown. Keep the existing pure ballistics
assertions unchanged and use the test’s lifecycle controls to cover meter
start/stop behavior.
🤖 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.

Nitpick comments:
In `@tests/mixer_panel.test.mjs`:
- Around line 163-169: Add a focused fake-RAF integration test alongside the
existing meter tests that exercises _meterInputForKey and
host.mixerMeterLevels() for active audio, master, and guide routing, then
verifies meter updates stop when the panel is hidden and resume or initialize
correctly when shown. Keep the existing pure ballistics assertions unchanged and
use the test’s lifecycle controls to cover meter start/stop behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e622ad9-4cd9-4dc9-a982-6442cfb39a5a

📥 Commits

Reviewing files that changed from the base of the PR and between 5234633 and 0b14dbb.

📒 Files selected for processing (8)
  • assets/v3-theme.css
  • screen.html
  • src/audio.js
  • src/host.js
  • src/main.js
  • src/mixer-panel.js
  • tests/audio_mixer.test.js
  • tests/mixer_panel.test.mjs
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/audio_mixer.test.js
  • screen.html
  • src/host.js
  • assets/v3-theme.css
  • src/audio.js

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/audio.js (1)

1275-1297: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider caching per-analyser sample buffers instead of allocating on every meter poll.

audioMixerMeterLevels() allocates a new Float32Array per bus/track analyser on every call. If this is polled per animation frame for live meters (per PR objective), that's repeated allocation on a hot path. Reusing a Map<analyser, Float32Array> keyed by analyser (sized once) would avoid the churn.

♻️ Proposed direction
+const _meterSampleBufs = new WeakMap();
+function _samplesFor(analyser) {
+    let buf = _meterSampleBufs.get(analyser);
+    if (!buf || buf.length !== (analyser.fftSize || 256)) {
+        buf = new Float32Array(analyser.fftSize || 256);
+        _meterSampleBufs.set(analyser, buf);
+    }
+    return buf;
+}
 export function audioMixerMeterLevels() {
     ...
-        const samples = new Float32Array(analyser.fftSize || 256);
+        const samples = _samplesFor(analyser);
         analyser.getFloatTimeDomainData(samples);
🤖 Prompt for 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.

In `@src/audio.js` around lines 1275 - 1297, Update audioMixerMeterLevels() to
reuse cached Float32Array sample buffers per analyser instead of allocating a
new buffer on each poll. Add or use a Map keyed by analyser, create or resize
each buffer only when needed to match analyser.fftSize (defaulting to 256), and
use the cached buffer for both bus and track analyser reads.
🤖 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 `@tests/multitrack_audio.test.mjs`:
- Line 69: Update the final assertion-count log in the multitrack audio test to
report 8 passed instead of 7, matching the eight assert calls in the file.

---

Nitpick comments:
In `@src/audio.js`:
- Around line 1275-1297: Update audioMixerMeterLevels() to reuse cached
Float32Array sample buffers per analyser instead of allocating a new buffer on
each poll. Add or use a Map keyed by analyser, create or resize each buffer only
when needed to match analyser.fftSize (defaulting to 256), and use the cached
buffer for both bus and track analyser reads.
🪄 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: fdbd5fb6-8a8a-4b14-957d-0359809bad4b

📥 Commits

Reviewing files that changed from the base of the PR and between 0b14dbb and 7d6c311.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • assets/v3-theme.css
  • docs/USER-GUIDE.md
  • screen.html
  • src/audio.js
  • src/host.js
  • src/main.js
  • src/mixer-panel.js
  • src/track-session.js
  • tests/audio_mixer.test.js
  • tests/mixer_panel.test.mjs
  • tests/multitrack_audio.test.mjs
  • tests/track_session.test.mjs
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/host.js
  • tests/track_session.test.mjs
  • assets/v3-theme.css
  • screen.html
  • docs/USER-GUIDE.md
  • CHANGELOG.md
  • tests/audio_mixer.test.js
  • src/mixer-panel.js

Comment thread tests/multitrack_audio.test.mjs

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 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 `@src/mouse.js`:
- Around line 651-654: Update the S.partsViewMode gesture handling around
host.scrollTrackArea so horizontal-dominant input is routed through e.deltaX
rather than e.deltaY. Preserve the existing vertical-dominant condition and
ensure the fall-through pan receives horizontal trackpad swipes so the timeline
moves.

In `@src/parts-view.js`:
- Around line 223-249: Update _partsViewOnMouseDown’s transcription-row
selection branch to resolve the row’s pairedSourceId, falling back to the Master
Mix source when no pairing exists, before selecting the transcription. Switch
the audible source through the existing source-selection mechanism, while
preserving the arrangement selection and status updates.
🪄 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: 320d10a1-a9b8-45c8-997e-9eca60a0030c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d6c311 and ea2f4b0.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • assets/v3-theme.css
  • docs/USER-GUIDE.md
  • screen.html
  • src/audio.js
  • src/host.js
  • src/main.js
  • src/mixer-panel.js
  • src/mouse.js
  • src/parts-view.js
  • src/state.js
  • src/track-session.js
  • tests/mixer_panel.test.mjs
  • tests/parts_view.test.js
  • tests/track_session.test.mjs
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/state.js
  • docs/USER-GUIDE.md
  • src/host.js
  • assets/v3-theme.css
  • screen.html
  • CHANGELOG.md
  • tests/mixer_panel.test.mjs
  • src/mixer-panel.js
  • src/audio.js

Comment thread src/mouse.js
Comment thread src/parts-view.js
ChrisBeWithYou and others added 13 commits July 15, 2026 11:31
resetTrackAudioCache disconnected and cleared the per-track gain nodes but
left their meter analysers in _meterAnalysers. _attachMeterTap dedupes on
that key, so the freshly-created gain for each audio source never got a tap
on the next song — every non-focused audio-track VU meter read the dead old
node (the focused one only survived via the ref-bus fallback).

Drop the track: meter taps in the reset so _ensureTrackGain re-taps fresh
gains. Bus taps (ref/guide/click/master) persist with their long-lived nodes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- routes.py: give each cached stem a per-source index in its filename so
  distinct source ids that sanitize alike (drums/kit vs drums:kit) no longer
  overwrite one another and share a URL.
- audio.js: cancelAudioLoad() before both cached-source installs so a slower
  in-flight stem decode can't finish and replace the just-installed source.
- file-ops.js: seed the synthesized Master Mix with data.audio_url when an
  older server omits audio_sources, so clicking Master Mix resolves against
  the cached audio instead of reporting "unavailable".
- mouse.js: pan the timeline by the dominant wheel axis so a horizontal
  trackpad swipe moves it instead of being consumed with no effect.
- tests: assert valid UTF-8 XML is byte-identical after normalize; add
  regression tests for the four code fixes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChrisBeWithYou

Copy link
Copy Markdown
Contributor Author

Status: this PR is being re-cut on top of the merged main rather than merged as-is. It branched off d7c5e3c before the #270–288 wave landed, and its mixer/tracks plumbing competes with the band-mode mixer + stem ingest that merged via #280–283/#286 (different host hooks, different mixer panel, a parallel pairing store) — a mechanical merge would ship half of each.

The decision (Christian): tracks DO become persistent first-class objects — this PR's core model wins — but rebuilt to speak the landed vocabulary. The salvage lands as a PR sequence:

  1. feat(editor): tracks become first-class — the persistent track-session tree #289 — the persistent track-session tree (model + editor_track_session persistence + the installCreatedTrackSession seam), with pairing delegated to editor_stem_links and sources derived from S.stems. ← open now
  2. Metronome Guide (lock an audio row as the tempo reference, Accept Whole Fit as one undo)
  3. Tempo-suggest continuation pack (analyze the locked guide, restart from active marker, honest low-confidence tail)
  4. Tempo Map marquee selection + bulk barline locks
  5. The unified Tracks surface (lanes, folders, inline rename, drag reorder, non-destructive removal UX)
  6. Engine/meters (evaluate against landed band-mode playback first)

Leaving this PR open as the reference implementation until the salvage sequence exists; it closes when the last slice is up. (Its Ctrl/Cmd+A work already landed via #287, and parts of the fit behavior via #279.)

ChrisBeWithYou pushed a commit that referenced this pull request Jul 16, 2026
…s, +6 dB

Replaces the docked mixer side-panel with #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, followed by the
SOURCE/GUIDE/CLICK utility buses and a dedicated MASTER output strip.
Rises on open, falls on close (reduced-motion aware); opens closed each
session (a per-screen view toggle, not a saved pref).

Metering: an AnalyserNode taps each bus + per-stem gain (a zero-gain sink
keeps the browser processing them without a second audible copy);
audioMixerMeterLevels reads 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. A master gain sits post-limiter
(limiter → masterGain → destination); the reference joins there too, so
the recording is metered and master-trimmed but STILL never through the
limiter (a hot recording is never colored). Per-stem taps drop on song
switch; bus taps persist.

Headroom: the fader range is 0..106 — 0..100 linear to unity, 100..106 a
log map to +6 dB. partStripState/clapState resolve vol through
_mixerGainForFaderPure, so stems and synth parts alike can be pushed to
+6 dB; every fader reads dB. The selected Tracks-column row lights its
matching strip. Removed the vestigial edit-blip toggle.

Faithful port of #285's mixer design onto the current main.

Tests: mixer_meters.test.mjs (level/peak/ballistics/dB pures); mixer_panel
reconciled to the +6 dB clamp, dB labels, master bus, animated close, and
always-closed init; audio_mixer/loop_ab sliced envs inject the meter-tap
stub and assert the new post-limiter ref routing. 184 JS suites, 291
pytest, lint 0 errors.

Stacked on fix/editor-beat-lock-guard (#294) → the meters/+6dB correction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
ChrisBeWithYou pushed a commit that referenced this pull request Jul 16, 2026
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
ChrisBeWithYou pushed a commit that referenced this pull request Jul 16, 2026
…s, +6 dB

Replaces the docked mixer side-panel with #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, followed by the
SOURCE/GUIDE/CLICK utility buses and a dedicated MASTER output strip.
Rises on open, falls on close (reduced-motion aware); opens closed each
session (a per-screen view toggle, not a saved pref).

Metering: an AnalyserNode taps each bus + per-stem gain (a zero-gain sink
keeps the browser processing them without a second audible copy);
audioMixerMeterLevels reads 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. A master gain sits post-limiter
(limiter → masterGain → destination); the reference joins there too, so
the recording is metered and master-trimmed but STILL never through the
limiter (a hot recording is never colored). Per-stem taps drop on song
switch; bus taps persist.

Headroom: the fader range is 0..106 — 0..100 linear to unity, 100..106 a
log map to +6 dB. partStripState/clapState resolve vol through
_mixerGainForFaderPure, so stems and synth parts alike can be pushed to
+6 dB; every fader reads dB. The selected Tracks-column row lights its
matching strip. Removed the vestigial edit-blip toggle.

Faithful port of #285's mixer design onto the current main.

Tests: mixer_meters.test.mjs (level/peak/ballistics/dB pures); mixer_panel
reconciled to the +6 dB clamp, dB labels, master bus, animated close, and
always-closed init; audio_mixer/loop_ab sliced envs inject the meter-tap
stub and assert the new post-limiter ref routing. 184 JS suites, 291
pytest, lint 0 errors.

Stacked on fix/editor-beat-lock-guard (#294) → the meters/+6dB correction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
ChrisBeWithYou pushed a commit that referenced this pull request Jul 16, 2026
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
@ChrisBeWithYou

Copy link
Copy Markdown
Contributor Author

Closing this superseded monolithic PR now that the reviewed stack in #289#296 contains the salvaged work and fixes. The stack was rebased and re-reviewed through its final tip; local JS/Python/lint suites pass, and #289's hosted JS, Python, lint, and CodeRabbit checks are green.

ChrisBeWithYou pushed a commit that referenced this pull request Jul 16, 2026
…s, +6 dB

Replaces the docked mixer side-panel with #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, followed by the
SOURCE/GUIDE/CLICK utility buses and a dedicated MASTER output strip.
Rises on open, falls on close (reduced-motion aware); opens closed each
session (a per-screen view toggle, not a saved pref).

Metering: an AnalyserNode taps each bus + per-stem gain (a zero-gain sink
keeps the browser processing them without a second audible copy);
audioMixerMeterLevels reads 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. A master gain sits post-limiter
(limiter → masterGain → destination); the reference joins there too, so
the recording is metered and master-trimmed but STILL never through the
limiter (a hot recording is never colored). Per-stem taps drop on song
switch; bus taps persist.

Headroom: the fader range is 0..106 — 0..100 linear to unity, 100..106 a
log map to +6 dB. partStripState/clapState resolve vol through
_mixerGainForFaderPure, so stems and synth parts alike can be pushed to
+6 dB; every fader reads dB. The selected Tracks-column row lights its
matching strip. Removed the vestigial edit-blip toggle.

Faithful port of #285's mixer design onto the current main.

Tests: mixer_meters.test.mjs (level/peak/ballistics/dB pures); mixer_panel
reconciled to the +6 dB clamp, dB labels, master bus, animated close, and
always-closed init; audio_mixer/loop_ab sliced envs inject the meter-tap
stub and assert the new post-limiter ref routing. 184 JS suites, 291
pytest, lint 0 errors.

Stacked on fix/editor-beat-lock-guard (#294) → the meters/+6dB correction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
ChrisBeWithYou pushed a commit that referenced this pull request Jul 16, 2026
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
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