Skip to content

feat(editor): the piano roll stretches, compacts and scrolls - #330

Merged
ChrisBeWithYou merged 2 commits into
mainfrom
feat/editor-roll-vertical-zoom
Jul 19, 2026
Merged

feat(editor): the piano roll stretches, compacts and scrolls#330
ChrisBeWithYou merged 2 commits into
mainfrom
feat/editor-roll-vertical-zoom

Conversation

@ChrisBeWithYou

Copy link
Copy Markdown
Contributor

⚠️ Stacked on #327 — it needs the shared lane-scroll model that PR introduces, so this targets fix/editor-lane-vertical-scroll, not main. Merge #327 first, then this retargets to main cleanly. The diff below is only this change.

After merging, please confirm the merge commit is an ancestor of main — a stacked editor PR once showed MERGED while having merged into its dead stack-parent, and sat unnoticed for two days.

The problem

The roll never overflowed because it squashed instead:

PIANO_LANE_H = Math.max(4, Math.min(14, 350 / (nhi - nlo + 1)));

Derived, untouchable, and it packs any pitch range into ~350px. A wide range collapses to four pixels per semitone — passable for reading, useless for editing, and with no way out.

That's exactly why #327 fixed only the drum grid's reachability and deliberately left the roll alone.

The fix

That derived value is now the default. S.rollLaneH is a user override, and the vertical scrolling shared with the drum grid is what makes a taller-than-viewport roll usable. The two halves only work together, which is why they land together — stretching without scrolling would just move content off-screen.

gesture result
Alt+wheel stretch / compact the lanes
plain wheel (when it overflows) scroll vertically
drag the right-edge bar scroll
song load back to automatic height

Nothing changes until you reach for it. The derived height is still the default, a roll that fits still shows no scrollbar, and a plain wheel still pans horizontally.

Alt is Live's binding for key-track zoom (Live 12 manual p.153, p.240); Logic exposes the same as a Vertical Zoom slider (Logic Pro guide, p.297). It's checked before the ctrl branch so Alt+Ctrl can't land on horizontal zoom while the user means vertical, and gated on isKeysMode() so every other view keeps today's gestures.

Decisions worth reviewing

_beatBarTopY() follows the scroll. The anchor, handshape and beat-bar strips annotate the chart, so they ride with the note content rather than floating over it. The string view has no vertical scroll — laneScrollY() is 0 there and the expression is unchanged.

The clip is conditional. Pushed only when the view actually scrolls, so the string view's pixels are untouched; clipping unconditionally would be a silent behaviour change for the common case. Released in the finally, not after the last painter — a throw in between would leave the context one save deep and the outer restore() would pop the wrong level.

Song load resets both the override and the offset, before updatePianoRange so it re-derives the auto height rather than honouring a stale override from the previous arrangement's range.

Verification

Browser, real song, roll view. The scrollbar goes from absent (0px) to 158px of thumb after stretching — and it only ever paints when content exceeds the viewport, so its appearance is the proof the lanes grew. Plain wheel then scrolls (content shifts). Waveform and ruler stay clean above the clip. Status reads Roll lane height 23.6px — Alt+scroll to stretch/compact. No page errors.

A note on method: my first metric was the painted content's vertical span, which looked like it shrank. That metric is wrong once clipping bounds the visible column — the rail is the honest signal. Recording it here because the span number is misleading if anyone re-runs the probe.

tests/roll_vertical_zoom.test.mjs (16 cases) against the real keys.js: auto-fit unchanged for narrow and wide ranges (the old pathology is preserved as the default); override wins, clamps both ends, and degrades to auto-fit on garbage; stretch→compact round-trips; clamping is silent at both stops; a nonsense factor is a no-op; reset restores auto; the override survives a range recompute (updatePianoRange runs on every extent-changing edit and must not stomp it); unscrolled geometry is byte-identical; scroll shifts by exactly the offset; scrolled y→midi→y round-trips; and a fully stretched roll genuinely exceeds a real canvas — the test that ties the two halves together.

249 JS, 346 pytest, lint clean.

This closes the navigation ask

Horizontal (#325) → drum vertical (#327) → roll vertical + stretch/compact (here). Track list already scrolled and resized; the tempo map needs no vertical, as Christian called.

🤖 Generated with Claude Code

https://claude.ai/code/session_017xGPjDBF8NTwTK7VQvizix

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@ChrisBeWithYou, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 18 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 565d0c13-98f0-43b5-9f40-212bc84e634c

📥 Commits

Reviewing files that changed from the base of the PR and between 3a4be1d and 32b74c7.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • src/draw.js
  • src/file-ops.js
  • src/keys.js
  • src/lane-scroll.js
  • src/main.js
  • src/mouse.js
  • src/state.js
  • tests/roll_vertical_zoom.test.mjs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-roll-vertical-zoom

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@ChrisBeWithYou
ChrisBeWithYou force-pushed the fix/editor-lane-vertical-scroll branch from 5fa1515 to 9ac57e3 Compare July 19, 2026 21:24
@ChrisBeWithYou
ChrisBeWithYou changed the base branch from fix/editor-lane-vertical-scroll to main July 19, 2026 21:28
ChrisBeWithYou and others added 2 commits July 19, 2026 16:28
STACKED ON #327 (fix/editor-lane-vertical-scroll) — it needs the shared
lane-scroll model that PR introduces. Merge #327 first; this diff shows only
its own changes against that branch.

The roll never overflowed because it SQUASHED instead. Lane height was derived
and nothing could touch it — max(4, min(14, 350 / range)) — packing any pitch
range into ~350px. A wide range collapsed to 4px per semitone: passable for
reading, useless for editing, with no way out. That is exactly why #327 left
the roll alone and fixed only the drum grid's reachability.

That derived value is now the DEFAULT. S.rollLaneH is a user override, and the
vertical scrolling shared with the drum grid is what makes a taller-than-
viewport roll usable — the two halves only work together, which is why they
land together.

Alt+wheel is Live's binding for key-track zoom (Live 12 manual p.153, p.240);
Logic exposes the same as a Vertical Zoom slider (p.297). Checked BEFORE the
ctrl branch so Alt+Ctrl can't land on horizontal zoom while the user means
vertical, and gated on isKeysMode() so every other view keeps today's gestures
untouched.

midiToY/yToMidi take the scroll term, mirroring _drumLaneIdxToY's pair — every
painter and hit-test funnels through them, so one edit reaches all of them.

_beatBarTopY() follows the roll's scroll: the anchor, handshape and beat-bar
strips annotate the chart, so they ride with the note content rather than
floating over it. The string view has no vertical scroll, so laneScrollY() is
0 there and that expression is unchanged.

The lane-band clip is pushed only when the view actually scrolls, so the
string view's pixels are untouched — clipping it unconditionally would be a
silent behaviour change for the common case. It is released in the `finally`,
not after the last painter: a throw in between would otherwise leave the
context one save deep and the outer restore would pop the wrong level.

Song load resets both the override and the scroll offset, before
updatePianoRange so it re-derives the auto height instead of honouring a stale
override from the previous arrangement's range.

Verified in a browser on a real song, roll view: the scrollbar goes from
ABSENT to 158px of thumb after stretching — and it only ever paints when
content exceeds the viewport, so its appearance is proof the lanes grew. Plain
wheel then scrolls. Waveform and ruler stay clean above the clip. Status reads
"Roll lane height 23.6px — Alt+scroll to stretch/compact".

249 JS tests, 346 pytest, lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xGPjDBF8NTwTK7VQvizix
@ChrisBeWithYou
ChrisBeWithYou force-pushed the feat/editor-roll-vertical-zoom branch from 591a26c to 32b74c7 Compare July 19, 2026 21:29
@ChrisBeWithYou
ChrisBeWithYou merged commit 29f73df into main Jul 19, 2026
4 checks passed
@ChrisBeWithYou
ChrisBeWithYou deleted the feat/editor-roll-vertical-zoom branch July 19, 2026 21:31
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.

1 participant