feat(editor): swing quantization on the snap grid (workspace-shell D2) - #197
Conversation
Add swing to grid snap as a BEAT-DOMAIN phase offset fed through the tempo-map converter — snap(t) = timeOf(_swingQuantizeBeatPure(beatOf(t), subs, pct)) — never a seconds nudge (charrette $1.6). Expressed as a beat coordinate, a swung note keeps its groove ratio through a tempo flex exactly like a straight one. Model: swing displaces the OFF subdivision of each consecutive pair; a pair spans 2/subs beats, so pairs align to the beat for every straight even grid (1/8 swing at subs=2, 1/16 swing at subs=4, ...). Presets: Straight / 54% / 58% / 62% (a select next to Snap). Guard rails: - 50% is BIT-IDENTICAL to the old rounding path (pinned across grids). - Triplet grids are a no-op — and triplet-ness is divisibility by 3, not parity (1/6T and 1/12T are even; the first cut got this wrong and the suite caught it). Odd grids can't pair; also a no-op. - Out-of-band values (<=50, >75, NaN, corrupt pref) fall back to straight rather than flinging notes. - Snap placement only: playback, existing notes and the drawn grid are unchanged. S.swingPct is an editor pref (localStorage), never the pack. The legacy sliced-source onset suite injects a straight-path stand-in for the new symbol (its fixture carries no swingPct, so the real quantizer reduces to plain rounding there); swing behavior has its own real-import suite: tests/swing_snap.test.mjs (8) — the pct%-through-the-pair placement (would-fail-on-main), pair starts and downbeats fixed, nearest-candidate rounding, triplet/odd no-ops, corruption fallback, and the groove-ratio- survives-a-flex property. Suite 91/91, ESLint 0 errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds swing presets to snap-grid placement, with beat-domain quantization, editor state persistence in ChangesSwing quantization
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Editor
participant snapTime
participant SwingQuantizer
participant TempoMapping
Editor->>snapTime: Request snap placement
snapTime->>SwingQuantizer: Quantize beat with swing percentage
SwingQuantizer-->>snapTime: Return quantized beat
snapTime->>TempoMapping: Convert beat to time
TempoMapping-->>Editor: Return snapped time
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/main.js (1)
1242-1252: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winGuard-band duplicated across files.
The
(50,75]swing bound is hand-mirrored here and in_swingQuantizeBeatPure(src/snap.js). Exporting it as a constant (e.g.SWING_MIN/SWING_MAX) from snap.js and importing here would remove the drift risk the current comment is compensating for.♻️ Proposed refactor
+// src/snap.js +export const SWING_MIN = 50; +export const SWING_MAX = 75;-window.editorSetSwing = (pct) => { - const n = Number(pct); - // Same guard band as the quantizer: outside (50,75] means straight. - S.swingPct = Number.isFinite(n) && n > 50 && n <= 75 ? n : 50; +window.editorSetSwing = (pct) => { + const n = Number(pct); + S.swingPct = Number.isFinite(n) && n > SWING_MIN && n <= SWING_MAX ? n : SWING_MIN;🤖 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 `@src/main.js` around lines 1242 - 1252, Centralize the swing bounds instead of duplicating the (50,75] guard in window.editorSetSwing. Export shared constants such as SWING_MIN and SWING_MAX from _swingQuantizeBeatPure’s module in src/snap.js, import them in src/main.js, and use them in the Number.isFinite range check while preserving the existing fallback to 50.
🤖 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 `@src/main.js`:
- Around line 1242-1252: Centralize the swing bounds instead of duplicating the
(50,75] guard in window.editorSetSwing. Export shared constants such as
SWING_MIN and SWING_MAX from _swingQuantizeBeatPure’s module in src/snap.js,
import them in src/main.js, and use them in the Number.isFinite range check
while preserving the existing fallback to 50.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d3e5c1ea-1cb4-48ea-81bc-90f916295121
📒 Files selected for processing (8)
CHANGELOG.mdscreen.htmlsrc/loop.jssrc/main.jssrc/snap.jssrc/state.jstests/onset_snap.test.jstests/swing_snap.test.mjs
What
Swing on the snap grid (charrette §1.6): a Swing select next to Snap — Straight · 54% · 58% · 62% — that displaces the off subdivision of each pair toward the next beat.
The design rule
Swing is a beat-domain phase offset fed through the tempo-map converter:
snap(t) = timeOf(_swingQuantizeBeatPure(beatOf(t), subs, pct))— never a seconds nudge. Expressed as a beat coordinate, a swung note keeps its groove ratio through a tempo flex exactly like a straight one (pinned by test: 62% stays 62% through the pair after the grid times change under it).
Guard rails
S.swingPctis an editor pref (localStorage), never written to the pack.The legacy sliced-source onset suite injects a straight-path stand-in for the new symbol (its fixture carries no
swingPct, so the real quantizer reduces to plain rounding there); swing behavior has its own real-import suite.Tests
tests/swing_snap.test.mjs(8): pct%-through-the-pair placement (would-fail-on-main), pair starts/downbeats fixed, nearest-candidate rounding, triplet/odd no-ops, corruption fallback, the flex-survival property, and a no-drift check that the toolbar options equalSWING_PRESETS. Full suite 91/91 · ESLint 0 errors · CHANGELOG updated.🤖 Generated with Claude Code
https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Summary by CodeRabbit