Skip to content

feat(editor): multitrack stem ingest — import, rename, reorder, and pair studio tracks with chart tracks - #283

Merged
byrongamatos merged 3 commits into
mainfrom
feat/editor-stem-tracks
Jul 15, 2026
Merged

feat(editor): multitrack stem ingest — import, rename, reorder, and pair studio tracks with chart tracks#283
byrongamatos merged 3 commits into
mainfrom
feat/editor-stem-tracks

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

⚠ STACKED on #282 (drum kit) — the audio-stack chain is now #280#281#282 → this. Same rebase-retarget-ancestry ritual after each squash.

What

Bring in a whole studio session. Christian's ask: more than demucs's five — take N tracks from an actual session, ingest, rename, rearrange, and transcribe/chart against each as the user wishes.

  • File ▸ Audio tracks… — a manager for the session's multitrack: Import any number of audio files (wav/ogg/opus/mp3/flac; ids derive from filenames, sanitized + deduped), rename (click the name — the file moves with it), reorder (the manifest's stems list is order-authoritative, so the mixer strips follow), delete.
  • Pair each audio track with the chart track that transcribes it. Pairings persist as the editor_stem_links manifest extension key (the audio_shift retention contract, third use now) and key chart tracks by the id-or-name rule — never a bare index, so links survive part reordering. Renames follow the link; deletes drop it.
  • Solo my source track (registry + Transport menu): one command isolates the current track's paired audio via the stem-mixer's S.stemMix contract — the transcription move. Audible the moment feat(editor): stem mixer — isolate any instrument while editing #275's strips land; the pairing itself is stored, shown, and shipped today.
  • Works on both session kinds: loaded sloppaks (files land in stems/ + manifest; zip-form persists on Save — the replace-audio rule) and fresh GP/MIDI imports (session-held, packed by Build into the new feedpak's stems/ + manifest).
  • /load's stems gate relaxed ≥2 → ≥1 (a lone imported track must reach the manager; the mixer strips keep their own ≥2 gate).

Backend hard edges (all pinned)

_stems_reorder_pure demands a full permutation — a partial order gets no authority to drop entries. _stems_rename_pure refuses collisions and never mutates its input. _stems_manifest_list preserves unknown entry fields — another tool's stem metadata survives our edits. Path-traversal guards mirror _safe_stem_path on every file op.

Testing

tests/stem_tracks.test.mjs (4, fails on main) + tests/test_stem_tracks.py (5). Full suites green: 162 JS / 0 fail, pytest 264, lint at baseline. Live-verified end-to-end on the testbed: imported two synthesized "studio tracks" (Guitar L (DI).wav, Bass DI.wav) through the real endpoint → renamed → reordered → paired with Lead → saved → confirmed stems/GuitarMain.wav + stems/Bass_DI.wav and editor_stem_links: {lead: GuitarMain} inside the pak's manifest on disk, alongside the untouched demucs stems → then deleted/reordered/unlinked and confirmed the pak restored exactly. Zero page errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

Summary by CodeRabbit

  • New Features
    • Import multiple audio tracks into a session.
    • Rename, reorder, delete, and pair audio tracks with chart tracks.
    • Preserve track pairings when rearranging songs and when saving or building sessions.
    • Added an Audio tracks manager for reviewing and updating imported tracks.
    • Added Solo my source track to isolate the audio paired with the selected chart track.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c7e435f-ee10-4cb2-b2b2-07f925e45e32

📥 Commits

Reviewing files that changed from the base of the PR and between a3fdcc3 and f2819f8.

📒 Files selected for processing (14)
  • CHANGELOG.md
  • routes.py
  • screen.html
  • src/create.js
  • src/file-ops.js
  • src/input.js
  • src/main.js
  • src/menu-bar.js
  • src/shortcuts.js
  • src/state.js
  • src/stem-tracks.js
  • tests/screen_markup.test.mjs
  • tests/stem_tracks.test.mjs
  • tests/test_stem_tracks.py

📝 Walkthrough

Walkthrough

Adds multitrack audio-stem import and management, chart-track pairing, persistence in sloppaks, editor commands, stem-mixer soloing, and frontend/backend tests.

Changes

Multitrack stem management

Layer / File(s) Summary
Stem manifest and persistence contracts
routes.py, tests/test_stem_tracks.py
Adds stem ID normalization, manifest handling, link parsing, absent-versus-empty semantics, load/save persistence, and backend validation tests.
Stem import and operation endpoints
routes.py
Adds endpoints for importing stems and atomically renaming, reordering, deleting, and pairing them across session types; create builds include stem files and links.
Frontend stem state and controls
src/state.js, src/file-ops.js, screen.html, src/stem-tracks.js
Adds stem state, the Audio tracks modal, import and pairing controls, authoritative response adoption, persistence wiring, and paired-stem solo behavior.
Editor command and lifecycle integration
src/input.js, src/main.js, src/menu-bar.js, src/shortcuts.js
Registers stem commands, initializes the stem module, exposes handlers, and gates soloing on stem-mixer availability.
Frontend behavior validation
tests/stem_tracks.test.mjs, tests/screen_markup.test.mjs, src/create.js, CHANGELOG.md
Tests UI, operations, pairing, soloing, dirty-state behavior, and save/build payloads; documents the feature.

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

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Editor as stem-tracks.js
  participant API as routes.py
  participant Storage as manifest or session
  Editor->>API: Import audio files with stem_links
  API->>Storage: Store stems and apply pairings
  Storage-->>API: Return authoritative stem state
  API-->>Editor: Return stems, stem_links, and persistence status
Loading

Suggested reviewers: byrongamatos

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-stem-tracks

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

ChrisBeWithYou pushed a commit that referenced this pull request Jul 15, 2026
…ource gating + isolation (review #283 items 15-18)

Item 15: every stem op (pairing sync via new op 'links', rename, reorder,
delete, import) ships the CURRENT S.stemLinks, the backend session seeds
from the manifest on load and honors the submitted snapshot, and pairings
ride the same manifest write as the op — so the authoritative response can
never resurrect stale links over an unsaved pairing. Failed POSTs
half-apply nothing.

Item 16: stem responses carry a persisted verdict (dir-form sloppak =
durable now; zip/create = Save/Build) and _adopt marks the session dirty
on non-durable changes so the lifecycle guard can't silently discard them.
Create-mode Build now ships stem_links too (the third save path).

Item 17: editorSoloMyStem is gated on a real host.stemMixChanged consumer
(stemMixerAvailable); with none wired it reports unavailable instead of
flipping state nothing reads, and the menu greys it via needs:'stemMixer'.

Item 18: solo-my-source is an exclusive isolate — enabling clears every
other stem's solo, toggle-off restores the no-solo state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
@byrongamatos
byrongamatos changed the base branch from feat/editor-drum-kit to main July 15, 2026 20:28
ChrisBeWithYou and others added 3 commits July 15, 2026 22:42
…air studio tracks with chart tracks

Christian's ask: more than demucs's five — take N tracks from an actual
studio session, ingest, rename, rearrange, and transcribe/chart against
each as the user wishes.

Backend (routes.py):
- POST /import-stems (multipart, any count): filename-derived ids
  (_stem_safe_id: sanitized, deduped, 60-char cap), files into the
  sloppak session's stems/ + manifest stems list (zip-form persists on
  Save — the replace-audio rule) or, for create-mode GP/MIDI imports,
  into the session's stem_files which Build packs into the new pak
  (staging/stems/ + manifest entries in _write_sloppak_pak).
- POST /stem-op rename|reorder|delete: pure list ops
  (_stems_rename_pure refuses collisions; _stems_reorder_pure demands a
  full permutation — partial orders get no authority to drop entries;
  _stems_manifest_list preserves unknown entry fields so another tool's
  stem metadata survives our edits); renames MOVE the file and follow
  the links; deletes drop their links.
- editor_stem_links manifest extension key (chart-track key → stem id,
  the audio_shift absent/empty/garbage contract) at all three save
  paths + /load; /load's stems gate relaxed >=2 → >=1 (a lone imported
  track must reach the manager; the mixer strips keep their own gate).

Frontend (src/stem-tracks.js + modal):
- File ▸ Audio tracks…: import (multi-file), rename (click the name),
  reorder (▴▾ — the manifest is order-authoritative), delete, and a
  per-track pairing select over the chart tracks. Chart keys use the
  _partViewKeyPure id-or-name rule — never a bare index, so links
  survive part reordering.
- 'Solo my source track' (registry + Transport menu): solos the current
  track's paired stem via S.stemMix — the transcription move; audible
  once the stem-mixer strips (#275) land, honest status either way.

tests/stem_tracks.test.mjs (4, fails on main): row model + key rule,
one-stem-per-track pairing, solo verb exactness, persistence wire.
tests/test_stem_tracks.py (5): id sanitize/dedupe, rename/reorder pure
hard-edges, unknown-field preservation, the links contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…ource gating + isolation (review #283 items 15-18)

Item 15: every stem op (pairing sync via new op 'links', rename, reorder,
delete, import) ships the CURRENT S.stemLinks, the backend session seeds
from the manifest on load and honors the submitted snapshot, and pairings
ride the same manifest write as the op — so the authoritative response can
never resurrect stale links over an unsaved pairing. Failed POSTs
half-apply nothing.

Item 16: stem responses carry a persisted verdict (dir-form sloppak =
durable now; zip/create = Save/Build) and _adopt marks the session dirty
on non-durable changes so the lifecycle guard can't silently discard them.
Create-mode Build now ships stem_links too (the third save path).

Item 17: editorSoloMyStem is gated on a real host.stemMixChanged consumer
(stemMixerAvailable); with none wired it reports unavailable instead of
flipping state nothing reads, and the menu greys it via needs:'stemMixer'.

Item 18: solo-my-source is an exclusive isolate — enabling clears every
other stem's solo, toggle-off restores the no-solo state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
… mix

The stem manager hides the `full` combined-mix entry (both /load and
_stem_state_payload skip id=="full"), so S.stems — and the `order` the UI
sends on a reorder — never contains it. But the stem-op reorder validated
that order against the WHOLE manifest stems list via _stems_reorder_pure,
which still includes `full`, so `sorted(order) != sorted(ids)` and every
reorder on a real (stem-separated / replace-audio'd) sloppak returned 400
"order must be a full permutation".

Root cause: the permutation was checked against the full manifest list
instead of the surfaced subset. Fix: _reorder_with_hidden reorders only the
entries the manager surfaced (the ids `order` names), keeps hidden entries
(`full`, plus any payload-dropped missing-file/path-escape stems) at the
front, and still fails closed on a foreign or duplicate id. Wired into both
the sloppak and create-mode reorder branches.

Regression: test_reorder_with_hidden_keeps_full_and_reorders_managed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos
byrongamatos force-pushed the feat/editor-stem-tracks branch from 7b667de to f2819f8 Compare July 15, 2026 20:43
@byrongamatos
byrongamatos merged commit 18f06b4 into main Jul 15, 2026
3 of 4 checks passed
@byrongamatos
byrongamatos deleted the feat/editor-stem-tracks branch July 15, 2026 20:43
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