Commit Graph
4 Commits
Author SHA1 Message Date
KeelrLeek 98d11a2228 previews: the album button opens the record the previewed track came from
The third action in the preview modal's control row has been a disabled
stub since the modal landed -- there was nowhere for it to go until the
album page existed. It goes there now.

The target is the item's own provider `albumId`, never a title match, so a
top-tracks preview lands on the exact record the modal's metadata line is
already naming: Elephant Gym's "Light" opens Angle, and AC/DC's "Highway
to Hell" the album rather than any of the compilations carrying it. The
manifest has carried `albumId` and an `actions.openAlbum` flag from the
start; all 20 rows of a Deezer top-track payload declare one.

The navigation lands on the previewed TRACK, not just the record -- the
album page's existing pending-track flash does the scrolling and the
highlight, the same landing the now-playing song link already gets.

Three details that are not obvious:

- The overlay must not import state.svelte.js (the module comment on
  previewSession explains the cycle), so the navigator is injected by the
  page exactly as `playFull` already is.
- Both ids are read out of previewUi and the snapshot BEFORE the close, not
  after: closePreview() nulls the callback and drops the snapshot the
  active item came from. And the close happens first, because the session
  holds the audio focus and navigating under an open dialog would land the
  new page behind the scrim.
- contracts.js freezes every manifest id as a String, while `catalog_album`
  takes a u64 -- a JSON string fails to deserialize -- and the album page's
  flash compares track ids with ===. Both cross back to numbers in
  openAlbumFromPreviewItem, once, rather than at each call site.

An album session is already standing on the record it would open, so the
button greys out there with "You're already on this album" rather than
re-navigating to the page underneath. The decision is made from the
manifest's own contextKey, so it holds for any future surface that opens an
album-kind session. The album page still supplies the navigator, so a row
that ever declares a different album goes somewhere instead of being
silently dead.

Verified in the running app over CDP: artist top-tracks preview -> click ->
lands on Highway to Hell (id 9410086), pendingTrackId 92719900 as a Number,
matching track found on the page, .trow.flash present, modal gone, and the
glass bar trail reading Search -> AC/DC -> Highway to Hell. From an album
session the button is disabled and a click changes nothing. 85 JS tests
pass.
2026-07-28 17:13:54 -05:00
KeelrLeek 691fdcdc9a fix: previews follow the app volume; volume bar fill always accent
The preview backend never received a volume — clips played at the crate
default (0.7) regardless of the slider, even muted. The session now sets
the preview volume from the app store (mute → 0) before the first load and
mirrors it live while the modal is open; the debug overlay's PREVIEW row
exposes the backend's resting volume as proof.

The player-bar volume (and fallback seek) fill is now always the accent
color — matching the waveform scrubber's played region — stepping up to
the hover shade on hover instead of white-until-hover.

Also syncs package-lock to the soulstream name + alpha version.
2026-07-23 16:25:42 -05:00
KeelrLeek 793f78370e feat: crossfade preview suite, loudness normalization, debug overlay
Preview modal audio, five rounds:
- True track-change crossfade: scheduleSettle no longer silences the
  outgoing clip at t=0; preview_load's equal-power fade owns both ramps,
  gated on the incoming voice's first Playing event (audible_marks), with
  failure/overstay fallbacks. CROSSFADE_MS = 340 — locked to the reel's
  card-slide so audio and visuals land together.
- Two-neighbour lease prefetch + a 15-min on-disk clip byte-cache
  (recent_clips): steps in BOTH directions crossfade in ~15 ms; revisits
  never re-download.
- Adaptive settle (isolated steps fire immediately, bursts coalesce) and
  anticipatory auto-advance (step at clip end minus 400 ms) so auto-play
  crossfades over the tail instead of gapping.
- Session resume memory: revisited tracks continue where they left off
  (decoder-level start offset via play_local_from/resume_at); finished
  tracks restart from the top.
- Open/close crossfades with the MAIN player: fade-pause with the
  desired-paused latch landing at ramp end, fade-resume from the paused
  position; MAIN_FADE_GEN guards; focus handshake relaxation documented
  in focusBridge.

Volume normalization (Settings > Playback, default on):
- Dependency-free BS.1770/EBU R128 meter in the player crate (K-weighting,
  400 ms gated blocks, incremental) + analyzer that reads the same growing
  file playback streams from — first plays are measured at download speed.
- Gain layered under user volume; audible start GATED on a preliminary
  measurement (10 s of audio or 800 ms budget) so the level is right from
  the first sample and never changes mid-track; prelim boosts capped 3 dB,
  peak-aware clipping guard, persistent loudness-cache.json.
- Gapless boundaries get their own sample-accurate gain via
  SetNextNormGainDb, fed by preload analysis.

Debug overlay (Ctrl+Shift+D): process-tree CPU / PDH GPU / working-set +
private-commit RAM / network rates / FPS / current-track download progress
read from the backend's own StreamProgress / live normalization state /
preview session phase. Polls only while visible.

131 JS + player + app tests green; every path verified live over CDP.
2026-07-23 16:05:14 -05:00
KeelrLeek d051f0db28 Phase 2C WIP: PreviewOverlay modal + root integration (reel centering bug)
Working:
- PreviewOverlay.svelte mounted at app root; opens over a fixture manifest
  (window.__previewFixture(n) in dev), inert background, player bar hidden,
  focus trap, keyboard nav, F3 rail, T3 swaps, R1b capsule, accent extraction.
- previewSession.svelte.js: store + service wiring (production + fixture).
- Root integration in App.svelte; revision-gateway bumps + notePlaybackGeneration
  threaded through state.svelte.js player entry points; api.win guarded so plain
  vite browser mounts.
- Launcher on the artist page builds a real artist manifest and opens the modal
  with a play-full starter.
- 70 JS tests still green.

Two bugs found and fixed during bring-up:
- effect_update_depth_exceeded: bleed-crossfade and bump effects read AND wrote
  the same $state; wrapped writes in untrack(). This loop was breaking Svelte's
  scheduler globally (even ui.* stopped reacting).
- accentFromImage now sets crossOrigin for remote covers.

KNOWN BROKEN (handoff): reel vertical centering. The active card stays centred
at index 0 but drifts by one stride per step. Root cause isolated — see
TODO-Feature-Docs/scrollable-track-previews/HANDOFF-PHASE2C.md.
2026-07-22 19:26:41 -05:00