feat(editor): GM roll — a piano-roll layout for the drum editor - #317
Conversation
|
Warning Review limit reached
Next review available in: 38 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe drum editor adds a persisted GM roll density mode with one General MIDI percussion piece per pitch-ordered row, GM-number labels, updated controls, documentation, changelog coverage, and tests for mapping and cycling. ChangesDrum GM roll mode
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant DensityButton
participant DrumEditor
participant LocalStorage
DensityButton->>DrumEditor: Toggle drum density
DrumEditor->>DrumEditor: Compute next density
DrumEditor->>LocalStorage: Persist editorDrumDensity
DrumEditor->>DrumEditor: Rebuild lane table and render labels
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Planned follow-up (Christian's call, 07-18): surface this in the view dropdown. This PR puts the GM roll behind the Rows button (Full → Compact → GM roll). #316 separately turns the view switcher into a dropdown that, for drum tracks, offers Once both this and #316 are merged, a small follow-up commit lands on whichever merged second: the drum dropdown becomes Drum grid · Piano roll · Notation ( Nothing to change in this PR — noting it so the two don't stay disconnected. |
The drum 'Piano roll' dropdown entry needs GM-roll density to exist. Merge #317 first, then this PR. Signed-off-by: ChrisBeWithYou <chris@rifflarr.local> # Conflicts: # CHANGELOG.md
…tion With the GM roll (#317, merged into this branch) and the view dropdown both present, the drum piano roll was reachable only from the Rows button while fretted tracks advertised "Piano roll" in the dropdown — so a user checking the dropdown for "what layouts does this track have?" concluded drums had none. Christian's call: fold the unification in rather than defer it, since he controls the merge order. - The drum dropdown becomes Drum grid · Piano roll · Notation, where Piano roll selects GM-roll density. Full/Compact stay on the Rows button: they are densities of the same KIT-ordered grid, whereas the GM roll is a different axis (pitch). - Returning to Drum grid restores the density you came from (_drumKitDensityBack), so a Compact user isn't silently reset to Full by a trip through the roll. - Both controls route through ONE setter (_editorSetDrumDensity, which _editorToggleDrumDensity now delegates to), so the Rows button and the dropdown cannot drift apart — the dropdown reads _drumDensityMode(). - No import cycle: key-view.js -> drum.js is one-directional. Drums now match fretted tracks' vocabulary: instrument geometry (String view / Drum grid) -> pitch-ordered (Piano roll / GM rows) -> engraved (Tab-Notation / Notation). Tests: +4 in view_switch_lens (density-driven value derivation, Piano roll setting GM density with both controls agreeing, the Compact/Full round-trip restore, and the Rows cycle still wrapping through the same setter). Full suite 210/210, lint 0 errors. Runtime-verified in the host: the dropdown lists all three; Rows -> Compact then dropdown -> Piano roll flips the Rows button to GM roll; returning to Drum grid restores COMPACT (not Full); Notation engraves without disturbing the density; leaving it returns to the grid. Merge order: #317 first, then this PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01929LgKdJMyPGLf8N1WpEVW Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
|
Update — the follow-up landed early, in #316. Christian is merging these himself after a Codex review, which removes the merge-order risk that had me deferring this. This PR is unchanged and still independently mergeable. #316 now has this branch merged into it and adds the unification on top: the drum dropdown becomes Merge order: this PR first, then #316. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/drum_density.test.mjs (1)
106-112: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUpdate test description to match assertion behavior.
The test description mentions
junk recovers to compact-first, but the assertion_drumDensityNextPure('banana') === 'full'verifies that junk input correctly recovers to'full'.♻️ Proposed fix
-t('density cycle: Full → Compact → GM roll → Full; junk recovers to compact-first', () => { +t('density cycle: Full → Compact → GM roll → Full; junk recovers to full', () => { assert.strictEqual(_drumDensityNextPure('full'), 'compact'); assert.strictEqual(_drumDensityNextPure('compact'), 'midi');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/drum_density.test.mjs` around lines 106 - 112, Update the test description for the `_drumDensityNextPure` cycle test to state that junk input recovers to “full,” matching the existing `'banana'` assertion. Leave the assertions and test behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/drum_density.test.mjs`:
- Around line 106-112: Update the test description for the
`_drumDensityNextPure` cycle test to state that junk input recovers to “full,”
matching the existing `'banana'` assertion. Leave the assertions and test
behavior unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 57b45e27-0dc1-4ffd-90b3-3eb3d3cd6316
📒 Files selected for processing (4)
CHANGELOG.mddocs/USER-GUIDE.mdsrc/drum.jstests/drum_density.test.mjs
Tester ask ("no piano roll for drum MIDI"; view-modality open question
3): drummers coming from a DAW expect the GM percussion-row layout, not
only the physical-kit piece order. The Rows toggle gains a third density
after Full and Compact.
- DRUM_PIECE_GM: the canonical General-MIDI note per chart piece (the
primary note only — the pad strip's GM_DRUM_MAP keeps the aliases;
defining it here avoids a drum <- pad-strip import cycle).
- _drumLaneTablePure grows a 'midi' branch: one row per piece on its GM
note, pitch-DESCENDING top->bottom (piano-roll convention), no-GM
pieces (stack) sink last. Same grid, same piece-ids, same editing and
colors — V6's one-data-path rule holds; only row order and labels
change. Lane labels gain the GM number prefix ("38 Snare").
- _drumDensityNextPure: Full -> Compact -> GM roll -> Full; the pref
round-trips 'midi' through localStorage; button + status copy updated.
Tests: drum_density suite extended (all new assertions fail on main) —
every piece exactly once, strict descending order with crash_r on top /
kick lowest / stack last, gm numbers agree with the map, map hygiene
(exactly the chart pieces), and the 3-way cycle with junk recovery.
Full suite 209/209, lint 0 errors. Runtime-verified through the host
with Playwright: Rows cycles Full -> Compact -> GM roll -> Full, the GM
rows render pitch-ordered with numbered labels, the pref persists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01929LgKdJMyPGLf8N1WpEVW
Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
8d8dcee to
3ee0940
Compare
…tion With the GM roll (#317, merged into this branch) and the view dropdown both present, the drum piano roll was reachable only from the Rows button while fretted tracks advertised "Piano roll" in the dropdown — so a user checking the dropdown for "what layouts does this track have?" concluded drums had none. Christian's call: fold the unification in rather than defer it, since he controls the merge order. - The drum dropdown becomes Drum grid · Piano roll · Notation, where Piano roll selects GM-roll density. Full/Compact stay on the Rows button: they are densities of the same KIT-ordered grid, whereas the GM roll is a different axis (pitch). - Returning to Drum grid restores the density you came from (_drumKitDensityBack), so a Compact user isn't silently reset to Full by a trip through the roll. - Both controls route through ONE setter (_editorSetDrumDensity, which _editorToggleDrumDensity now delegates to), so the Rows button and the dropdown cannot drift apart — the dropdown reads _drumDensityMode(). - No import cycle: key-view.js -> drum.js is one-directional. Drums now match fretted tracks' vocabulary: instrument geometry (String view / Drum grid) -> pitch-ordered (Piano roll / GM rows) -> engraved (Tab-Notation / Notation). Tests: +4 in view_switch_lens (density-driven value derivation, Piano roll setting GM density with both controls agreeing, the Compact/Full round-trip restore, and the Rows cycle still wrapping through the same setter). Full suite 210/210, lint 0 errors. Runtime-verified in the host: the dropdown lists all three; Rows -> Compact then dropdown -> Piano roll flips the Rows button to GM roll; returning to Drum grid restores COMPACT (not Full); Notation engraves without disturbing the density; leaving it returns to the grid. Merge order: #317 first, then this PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01929LgKdJMyPGLf8N1WpEVW Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
) * feat(editor): Tab and Notation join the view switcher The live engraved score (tab-view-live.js) existed but was reachable only through the view-cycle shortcut and the View menu — the top-right switcher showed just String | Piano roll, and its active state ignored the lens entirely (String stayed lit while the engraving was showing). - The switcher becomes String · Piano roll · Tab · Notation. Tab opens the engraved tablature; Notation the standard-notation staff — both drive the existing lens (editorToggleTabView) with the matching staff profile (editorSetTabViewStaff). - A user whose Score-staff preference is "notation + tab" keeps it: _tabStaffForClickPure preserves 'both' on either click, and _viewSwitchActivePure lights Tab AND Notation together for it (the view genuinely shows both). - The active state is lens-aware; the roll-lock pill hides while the lens owns the timeline; Tab/Notation hide on drum tracks (which have no tab — same refusal the lens itself makes); keys stay piano-locked. - Returning to String/Piano roll drops the lens before writing the per-part view pref (mirrors the cycle's ordering). - User Guide: the view list documents all four + the switcher. Tests: tests/view_switch_lens.test.mjs — active-set derivation (including 'both' lighting two buttons and junk-staff degradation), 'both'-preserving staff-for-click, lens entry via editorSetViewMode('tab'/'notation'), String exit dropping the lens, and keys/drums refusal. All fail on main. Existing tab_view_cycle suite unchanged and green; full suite 210/210, lint 0 errors. Runtime-verified through the host with Playwright: 4 buttons, Tab renders the engraving, Notation switches staff + persists the pref, String hides the mount. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01929LgKdJMyPGLf8N1WpEVW Signed-off-by: ChrisBeWithYou <chris@rifflarr.local> * refactor(editor): view switcher becomes a dropdown (Christian's call) The pill toggle from the previous commit becomes a per-track view dropdown: String view · Piano roll · Tab · Notation · Notation + Tab. With a dropdown every staff profile is an explicit option, so the selected value is always a truthful readback of state (lens included) and the 'preserve the both pref on click' dance disappears — _tabStaffForClickPure is gone, _viewSwitchValuePure derives the single value. Drum tracks lose the engraved options (disabled + hidden) instead of buttons that refuse. Tests reworked to the value model; 'both' now pinned as an explicit choice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01929LgKdJMyPGLf8N1WpEVW Signed-off-by: ChrisBeWithYou <chris@rifflarr.local> * feat(editor): drum parity — Notation for drum tracks in the view dropdown Christian's parity call: drums should get Notation too. In the drum editor the same dropdown now offers Drum grid / Notation, and the live lens engraves the drum tab on a real percussion staff. - src/alphatex.js: _alphaTexFromDrumHitsPure, the percussion flavor of the generator. The bar walker (bucketing, gap durations, rest fill, beatMap) is factored into a shared _alphaTexAssemblePure so drums and fretted parts keep ONE quantization contract. DRUM_TEX_ARTICULATIONS maps each piece to its alphaTab articulation id; `stack` has none, so its hits are skipped and COUNTED, never silently dropped. - Three things the browser taught us, each now pinned by a test: * every drum beat is parenthesized — a bare `36.4` lexes as the FLOAT 36.4, not articulation 36 with a quarter duration (fretted notes dodge this because `3.6.4` has two dots). This was a hard parse error: the engraving came up blank. * `\clef neutral` is BAR metadata and load-bearing — without it alphaTab engraves the kit under a TREBLE clef. * the percussion staff renders under StaveProfile.Default; forcing Score (as fretted parts do) engraves it as a pitched staff. - src/tab-view-live.js: a `_texSource` ('chart' | 'drums') selects the generator, keys the render + api-rebuild, and routes click-to-select into S.drumSel instead of S.sel. Entering the lens from the drum editor keeps S.drumEditMode ON underneath (the lens paints over it), so leaving Notation restores the grid with its state intact; the draw pass drops the lens if the drum tab or mode goes away. - src/key-view.js + screen.html: the dropdown shows the drum pair in drum mode and the five standard views otherwise. Tests: alphatex suite +5 (percussion header/clef/articulations, chord grouping with same-piece dedupe, unmapped counting, clef only on bar 1, and the fretted generator NEVER emitting a clef); view-switch suite +2 (drum value derivation, and drum-notation not leaving the drum editor). All fail on main. Full suite 210/210, lint 0 errors. Runtime-verified in the host: the dropdown offers Drum grid / Notation, the engraving renders with a PERCUSSION clef, kick on the bottom space, snare on the middle line, X noteheads for hats; switching back restores the grid. Verified via alphaTab's parsed model too: isPercussion=true, channel 9, articulations resolving to MIDI 36/38/42. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01929LgKdJMyPGLf8N1WpEVW Signed-off-by: ChrisBeWithYou <chris@rifflarr.local> * feat(editor): drums reach view parity — Drum grid · Piano roll · Notation With the GM roll (#317, merged into this branch) and the view dropdown both present, the drum piano roll was reachable only from the Rows button while fretted tracks advertised "Piano roll" in the dropdown — so a user checking the dropdown for "what layouts does this track have?" concluded drums had none. Christian's call: fold the unification in rather than defer it, since he controls the merge order. - The drum dropdown becomes Drum grid · Piano roll · Notation, where Piano roll selects GM-roll density. Full/Compact stay on the Rows button: they are densities of the same KIT-ordered grid, whereas the GM roll is a different axis (pitch). - Returning to Drum grid restores the density you came from (_drumKitDensityBack), so a Compact user isn't silently reset to Full by a trip through the roll. - Both controls route through ONE setter (_editorSetDrumDensity, which _editorToggleDrumDensity now delegates to), so the Rows button and the dropdown cannot drift apart — the dropdown reads _drumDensityMode(). - No import cycle: key-view.js -> drum.js is one-directional. Drums now match fretted tracks' vocabulary: instrument geometry (String view / Drum grid) -> pitch-ordered (Piano roll / GM rows) -> engraved (Tab-Notation / Notation). Tests: +4 in view_switch_lens (density-driven value derivation, Piano roll setting GM density with both controls agreeing, the Compact/Full round-trip restore, and the Rows cycle still wrapping through the same setter). Full suite 210/210, lint 0 errors. Runtime-verified in the host: the dropdown lists all three; Rows -> Compact then dropdown -> Piano roll flips the Rows button to GM roll; returning to Drum grid restores COMPACT (not Full); Notation engraves without disturbing the density; leaving it returns to the grid. Merge order: #317 first, then this PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01929LgKdJMyPGLf8N1WpEVW Signed-off-by: ChrisBeWithYou <chris@rifflarr.local> --------- Signed-off-by: ChrisBeWithYou <chris@rifflarr.local> Co-authored-by: ChrisBeWithYou <chris@rifflarr.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Why
Kisscool's "no piano roll for drum MIDI" report (recorded in the dogfood handoff as a known design gap, and as view-modality open question 3) + Christian's 07-18 commitment to ship it for the nightly. Drummers arriving from DAWs read drums on General-MIDI percussion rows; the editor offered only the physical-kit piece order (Full) and the community 7-row family shape (Compact).
What
The Rows toggle gains a third density: GM roll.
stacksinking last). Labels carry the GM number:38 Snare,42 HH cl.'midi'branch in_drumLaneTablePure. Same grid, same piece-ids, same editing/selection/colors in every mode — only row order and labels change.DRUM_PIECE_GMdefines the canonical piece→note map indrum.js(primary notes only; the pad strip'sGM_DRUM_MAPkeeps the 35/40/41/47/59 aliases — and importing it from there would create adrum ← drum-pad-stripcycle).Full → Compact → GM roll → Full(_drumDensityNextPure); the preference round-trips'midi'through localStorage like the existing modes; button text + status copy + User Guide updated.Tests
drum_density.test.mjsextended — all new assertions fail on main: every piece appears exactly once; strict pitch-descending order (crash_r top, kick lowest GM row, stack last withgm: null); per-rowgmagrees with the map; map hygiene (exactly the chart pieces, nothing extra); the 3-way cycle incl. junk recovery.npm test209/209 · lint 0 errors.Runtime verification (real host + Playwright)
Loaded a pack with a drum tab, entered the drum editor: Rows cycles Full → Compact → GM roll → Full; the GM rows render pitch-ordered with numbered labels and the fixture's hits land on their rows; the status line describes the mode; the preference persists (
editorDrumDensity = midi).🤖 Generated with Claude Code
https://claude.ai/code/session_01929LgKdJMyPGLf8N1WpEVW
Summary by CodeRabbit
New Features
Documentation