Skip to content

Visualization gallery + Melody's Path - #16

Merged
rainmana merged 6 commits into
mainfrom
feat/viz-gallery-melody-path
Jul 8, 2026
Merged

rainmana merged 6 commits into
mainfrom
feat/viz-gallery-melody-path

Conversation

@rainmana

Copy link
Copy Markdown
Owner

Adds a switchable visualization gallery and the first structure-revealing visualization, Melody's Path — a scrolling per-track melodic contour. Closes #2, closes #3.

What's in it

  • Contract: extends Visualization with optional loadTimeline(notes, durationSec) so structure-revealing vises see the whole song (not just live events). cosmic-aurora ignores it.
  • Gallery: registry.ts + VizPicker.svelte (mirrors ThemePicker) + persisted viz setting (restored on startup). VizCanvas rewritten to a single (vizId, midi)-driven effect that rebuilds the viz + RAF loop on switch/song-change — head is read only inside the loop, so there's no per-frame rebuild.
  • Melody's Path: scrolling contour, fixed playhead at 30%, ~3s past / 7s future window, per-track ribbons in the cosmic hues, leaps emphasized (segment brightness/width ∝ interval), notes bloom as they cross the playhead.
  • Tests: vitest for the pure geometry helpers (pitchToY, windowSlice, leapIntensity).

Verification

  • npm run check (0 errors), npm test (3/3), npm run build (clean). Frontend-only — no backend/Rust changes.
  • Manual (GUI) still needed: load a MIDI → open the Visualization picker → switch to Melody's Path → confirm the contour scrolls past the playhead, leaps pop, notes bloom on crossing, and switching back to Cosmic Aurora + persistence-across-restart work.

Spec: docs/superpowers/specs/2026-06-27-viz-gallery-melody-path-design.md · Plan: docs/superpowers/plans/2026-06-27-viz-gallery-melody-path.md

🤖 Generated with Claude Code

rainmana and others added 5 commits June 27, 2026 14:50
Sub-project 1 of the structure-revealing viz roadmap (#2, #3): a switchable
visualization gallery (registry + VizPicker + persisted viz setting + the
loadTimeline contract extension) plus the first new viz, Melody's Path — a
scrolling per-track melodic contour. Frontend-only; vitest for geometry helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@socket-security

socket-security Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​vitest@​4.1.9981007998100

View full report

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9252a9d386

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/VizCanvas.svelte
ro.observe(host);
viz.resize(host.clientWidth, host.clientHeight);

let ptr = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Seed the scheduler when switching mid-song

Switching vizId while a song is already playing rebuilds this effect with ptr back at 0. On the first RAF at a nonzero head.time, the loop below emits onNoteOn for every historical note with start_sec <= time; in Melody's Path those all become blooms (and Cosmic still processes/caps hundreds of orbs), so switching visualizations mid-song can flash or stall on dense MIDI files. Initialize the new scheduler from the current playhead instead of replaying the whole past.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 420fce3 — good catch. The scheduler now seeds at the current playhead without firing events on the first frame after a viz/song switch, and the same guard covers time discontinuities in both directions (backward seek — which had this replay-flood pre-PR — and >1s forward jumps, same root cause Codex didn't flag). In-flight notes get their balancing onNoteOff before the reset, then ptr fast-forwards past the skipped span silently.

Comment thread src/lib/visualizations/geometry.ts Outdated

/** Notes whose start_sec is within [t0, t1] (inclusive). `sorted` must be ascending by start_sec. */
export function windowSlice(sorted: Note[], t0: number, t1: number): Note[] {
return sorted.filter((nn) => nn.start_sec >= t0 && nn.start_sec <= t1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound the per-frame note window

Melody's Path calls windowSlice for every track on every animation frame, but this helper ignores its sorted precondition and scans/allocates over the entire track with filter. For long or dense MIDI files, rendering cost becomes O(total notes) at ~60Hz even though only a 10-second window is visible; use the sorted start times to binary-search/advance window bounds and only materialize visible notes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 420fce3. windowSlice now actually uses its sorted precondition: binary-searches the lower and upper bounds and returns slice(from, to) — O(log n + visible) per track per frame instead of filtering the entire track at 60Hz. Signature and inclusive [t0, t1] semantics unchanged; added an equivalence-vs-naive-filter test on a 500-note array plus boundary/empty-window cases (vitest 5/5).

…y-search windowSlice

1. VizCanvas: on the first frame after a viz/song switch AND on any time
   discontinuity (backward seek, or >1s forward jump), position the note
   scheduler at the playhead WITHOUT firing events, instead of replaying the
   whole skipped span as an onNoteOn flood (hundreds of blooms/orbs in one
   frame on dense MIDI). Also cures the pre-existing backward-seek replay.
2. geometry: windowSlice now honors its sorted precondition — binary-searches
   both bounds and slices, O(log n + visible) per track per frame instead of
   filtering every note at 60Hz. Same signature + inclusive semantics; added
   equivalence and boundary tests (5/5 passing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rainmana

rainmana commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

Triage of the Socket report: informational only, no action needed — the single added dependency is vitest@4.1.9 (dev-only) with green scores across the board (supply chain 98, vulnerabilities 100, license 100). Both Codex P2 findings are addressed in 420fce3 (see inline replies); CI re-running on the fix.

@rainmana
rainmana merged commit 0b11b17 into main Jul 8, 2026
3 checks passed
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.

Visualization: Melody's Path (scrolling contour) Visualization Gallery foundation (switcher + loadTimeline contract)

1 participant