Multi-Deck#218
Draft
BuffMcBigHuge wants to merge 17 commits into
Draft
Conversation
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
…sions, major work on deck system reliability. Signed-off-by: BuffMcBigHuge <marco@bymar.co>
Closed
Signed-off-by: BuffMcBigHuge <marco@bymar.co>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR replaces the single "now playing" input with a four-deck DJ-style mixer in the realtime performance UI. Each deck owns one track, can play full / vocals / instruments, routes to bus A or bus B, and blends through a crossfader. The mixed result drives live inference, not just local preview.
Session persistence, custom track stores, and local saved sessions are inherited from the session-mods line of work; this writeup covers only the deck mixer, bus/crossfader model, and server-side parametric mix path.
Replaces #206 and builds on #150 improvements. Draft / WIP.
What changed in the performance surface
The old primary input controls (
AudioSourceCrate,TrackPicker, standalone reference controls) are removed from the main performance shell. Decks in the Advanced drawer (DecksPanel) are now the primary way to:Deck runtime lives in
PerformanceShellviauseDeckRuntime, so mixing and server sync continue when the drawer tab is closed.Deck model
useDeckStoreis the browser source of truth. Each slot (A–D) holds:trackNamesourcePartfull,vocals, orinstrumentsvolume,muted,solocrossfadeSideleft→ bus A,right→ bus Bplaying,positionSec,cueSecGlobal state: active
deckIds,crossfade(0 = full A, 1 = full B),timbreDeckId/structureDeckId,monitorEnabled,inferenceEnabled, andmixRevision(bumps on mix-relevant edits for sync).Invariant: a deck is always a loaded track slot. Adding a deck requires choosing or uploading a track first; removing a deck clears that slot but never drops below one active deck. Deck 1 seeds from the current/default track.
Bus system and crossfader
Decks assign to one of two buses:
crossfadeSide = "left"crossfadeSide = "right"The crossfader is linear:
0.0→ full bus A weight for A-side decks0.5→ equal A/B bus weighting1.0→ full bus B weight for B-side decksPer-deck effective gain (shared by monitor and server):
where
bus_gain = (1 - crossfade)on bus A andcrossfadeon bus B. Muted decks contribute0. Active deck gains are normalized on the server so partial crossfades do not collapse latent energy toward zero.The UI crossfader shows which decks sit on each side. Multiple decks on the same bus sum through normalized weighting after per-deck gain.
Two audio paths (monitor vs inference)
1. Browser monitor (
useDeckMonitor)AudioBufferSourceNodeper playing deck from loaded assets (useDeckAssets+deckAssets.ts)requirePlaying: true)2. Server inference (
useDeckServerSync→deck_mix_state)id,track_name,source_part,volume,muted,playing(wire parity only),side, plus globalcrossfadews_adapter.py→StreamingSession.set_deck_mix_state()Important behavior: inference is decoupled from monitor transport. A loaded, unmuted deck contributes to the mix whether or not its monitor is playing. That fixes the earlier bug where inference stayed on the default track until every deck was "playing."
Server-side parametric deck mix (core architecture)
Problem with the first approach
An earlier implementation rendered mixed PCM in the browser (
renderDeckMixindeckMixer.ts) and pushed it through the heavyswap_sourcepath (useDeckInferenceSync). That made crossfader, volume, and bus changes feel like full source swaps: VAE prep, stale queued snapshots, and lag compared to knobs like strength.That path remains in the tree but is no longer wired from
useDeckRuntime.Current approach: blend prepared latents, not PCM
Each distinct
(track_name, source_part)is prepared once into a session cache entry:cond_pair_bwhere applicable)On each
deck_mix_stateupdate the server:gain > 0(playingignored)stream.source/ conditioning and marks hint blending dirtyParametric edits (crossfade, volume, mute, bus assignment, stem part) hit the cache and re-blend — no
swap_sourceafter the first prepare for that(track, mode).Safety invariants
Unit coverage:
tests/unit/test_deck_mix.py(16 tests) for_deck_gainand_resize_latent_tensor.Track assets and stems (deck-facing only)
Decks consume HTTP asset routes on the demo server (
/api/track_asset,/api/track_stem) for fixtures and uploads. Upload persistence and stem packets are part of the broader upload/session work; from the deck perspective, the UI only needs manifests and WAV sidecars for monitor loading and server prepare.Per-deck full / vocals / instruments buttons switch
sourcePart, which changes both monitor buffers and the cache key on the server.Timbre and structure references
Timbre and structure are chosen per deck (replacing standalone ref controls). Fixture refs still use server fixture messages; custom tracks can send PCM from loaded deck assets. These paths still use the existing timbre/structure commands and are not yet fully folded into the parametric latent mixer.
Architecture
flowchart LR subgraph UI["Browser"] Store["useDeckStore"] Panel["DecksPanel"] Runtime["useDeckRuntime"] Monitor["useDeckMonitor\n(Web Audio)"] Sync["useDeckServerSync"] Store --> Panel Store --> Runtime Runtime --> Monitor Runtime --> Sync end subgraph Server["StreamingSession"] WS["deck_mix_state"] Cache["_deck_cache\n(track, part)"] Mix["set_deck_mix_state\nblend latents + cond"] Stream["active stream.source"] WS --> Mix Cache --> Mix Mix --> Stream end Sync -->|debounced params| WS Monitor -->|assets only| Assets["/api/track_*"] Mix -->|prepare on miss| AssetsKnown limitations (WIP)
solodeck_mix_stateyetplaying, server does notdeck_mix_stateor asset HTTP yetuseDeckInferenceSync.tsunused but still presentBranch technical notes:
docs/DECK_SYSTEM_BRANCH_TECHNICAL.md.Test plan
full/vocals/instrumentsper deck when sidecars existuv run pytest tests/unit/test_deck_mix.py -qKey files (deck system)
Frontend:
useDeckStore.ts,DecksPanel.tsx,useDeckRuntime.ts,useDeckAssets.ts,useDeckMonitor.ts,useDeckServerSync.ts,deckAssets.ts,deckMixer.ts,protocol.tsBackend:
session.py(set_deck_mix_state,_deck_cache,_deck_gain),ws_adapter.py, demoserver.pyasset routes