Skip to content

feat(editor): tempo ramps + fit-from-drift + Tempo List (P2-7) - #279

Merged
byrongamatos merged 3 commits into
mainfrom
feat/editor-tempo-ramps-lane
Jul 15, 2026
Merged

feat(editor): tempo ramps + fit-from-drift + Tempo List (P2-7)#279
byrongamatos merged 3 commits into
mainfrom
feat/editor-tempo-ramps-lane

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

⚠ STACKED (top of the P2 chain): #276 (P2-5) → #277 (P2-6) → #278 (P2-8) → this. Base is feat/editor-feel-marker. After each parent squash-merges: rebase --onto, retarget, ancestry check (the #230 ritual).

What

P2-7 — a ritardando is ONE thing. Select a run of barlines, right-click ▸ Ramp the range (accel/rit)…, give it start → end BPM: the whole gesture becomes a single authored ramp object whose span re-spaces smoothly along a curve, with notes riding and one undo restoring everything.

  • Curve presets, never hand-tuned béziers (piano seat): linear / ease-in / ease-out; a rit defaults to ease-out (it releases).
  • Locks win: the compile runs through the existing _respaceWithLocksPure post-pass — a locked (human-verified) barline inside the span holds its exact time and the ramp compiles around it. Pinned by test showing the raw compile would have moved it.
  • One chip, not N: the marker lane shows rit. 140→120; derived tempo chips are suppressed inside the span (the smooth drift is the point) while honest step-chips at the boundaries stay.
  • Fit-from-drift: right-click ▸ Fit ramp to the recording reads the onset drift across the selection, least-squares fits the linear ramp, and offers it only when its residual beats a flat reading — Map Health's red rubato bar resolves to authored-green instead of nagging forever (provenance detected).
  • Tempo List (Tempo/Grid menu, UX G3): the numeric ledger — one row per authored mark (Bar · Type · Value · Source), click jumps to the bar. Chips are paint; this is text + provenance.
  • ramp joins the backend persistence vocabulary (same contract as feat(editor): authored marks I — hold/fermata bars, meter groupings, provenance (P2-5) #276).

The one command rule

A ramp edit is ONE TempoMapCmd carrying {markerDelta, compiledGridDelta}TempoMapCmd gains the same optional marks-snapshot contract TempoGridCmd got in P2-5, so undo restores marks and beats together (pinned).

Deferred (documented in the commit)

The graphical Tempo-lane strip (UX D1: derived-height points, vertical drag as sugar over the horizontal re-space, corner curve-node gesture, Snap pop-up) needs live mouse-drag verification and its own PR — this PR lands its entire foundation (engine, verbs, list, persistence). DAW divergences held: tempo points remain DERIVED, no tempo sets.

Testing

tests/tempo_ramps.test.mjs (8 cases, fails on main): validation hard-edges, monotone accel compile + anchored start + block-shifted tail, ease-out ≠ linear, the one-chip contract (inside-span silence + honest boundary steps), locks-win, exec → rollback → redo deep equality on marks + beats, fit-from-drift on a synthesized 120→90 rit (falling ramp, residual beats flat), Tempo List rows for all four kinds. Backend ramp round-trip in pytest. Full suites green: 162 files / 0 fail, pytest 268, lint at baseline.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

Summary by CodeRabbit

  • New Features

    • Added tempo ramps for smooth accelerando and ritardando changes across selected bars.
    • Added “Fit ramp to the recording” to automatically estimate tempo changes from performance drift.
    • Added an authored Tempo List showing tempo, grid, hold, feel, and ramp marks with clickable navigation.
    • Added ramp markers and improved tempo-map display behavior.
  • Bug Fixes

    • Improved validation, persistence, remapping, and undo/redo handling for tempo ramps.
  • Tests

    • Expanded coverage for ramp editing, validation, rendering, fitting, and history behavior.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c0260d83-a68f-4cbd-91d7-38a706cf36f4

📥 Commits

Reviewing files that changed from the base of the PR and between 1783a47 and dfa4a3b.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • routes.py
  • screen.html
  • src/main.js
  • src/menu-bar.js
  • src/ruler.js
  • src/tempo-list.js
  • src/tempo-marks.js
  • src/tempo.js
  • tests/screen_markup.test.mjs
  • tests/tempo_ramp_review.test.mjs
  • tests/tempo_ramps.test.mjs
  • tests/test_tempo_marks.py

📝 Walkthrough

Walkthrough

Adds authored tempo ramps with validation, curve compilation, drift fitting, marker rendering, and undo/redo persistence. Adds a Tempo List overlay for viewing and navigating authored tempo marks, with menu, startup, redraw, and markup-test integration.

Changes

Tempo authoring and inspection

Layer / File(s) Summary
Ramp mark contracts and persistence
routes.py, src/tempo-marks.js, tests/test_tempo_marks.py, tests/tempo_ramp_review.test.mjs
Adds ramp mark validation, curve presets, endpoint remapping, persistence coercion, and malformed-input coverage.
Ramp editing, compilation, and undo
src/tempo.js, src/ruler.js, tests/tempo_ramp_review.test.mjs, tests/tempo_ramps.test.mjs
Adds range authoring, drift fitting, curve compilation, lock-aware re-spacing, ramp marker behavior, strict prompt parsing, and undo/redo snapshots for tempo marks.
Tempo List UI
screen.html, src/main.js, src/menu-bar.js, src/tempo-list.js, CHANGELOG.md, tests/screen_markup.test.mjs, tests/tempo_ramps.test.mjs
Adds the hidden Tempo List panel, menu and lifecycle wiring, formatted authored-mark rows, row navigation, close handling, and structural/rendering tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: byrongamatos

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-tempo-ramps-lane

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

ChrisBeWithYou pushed a commit that referenced this pull request Jul 15, 2026
…asureEnd validation (review #279 items 4-7)

- item 4: _marksRemapPure remaps BOTH ramp endpoints atomically through a
  renumber; a ramp whose end bar was deleted or whose span collapses drops
  whole (never measureEnd <= measure).
- item 5: editorRampRange now REPLACES-from-baseline — overlapping ramp
  marks are stripped and their spans neutralized (_rampNeutralizePure:
  endpoints held, interior uniform) before the new mark compiles, making
  same-range reapply bit-identical and a BPM edit equivalent to compiling
  against the original grid. Endpoint-touching ramps still coexist.
- item 6: the ramp prompt parses strictly (_rampPromptParsePure — complete
  finite numbers, 0 < bpm <= 1000) and the mark is _markNormPure-validated
  BEFORE any compile or history mutation; invalid input leaves beats,
  marks, and history reference-identical.
- item 7: routes.py parses measureEnd with _exact_int instead of int() —
  fractions/bools/strings drop instead of truncating, inf no longer
  crashes the load (OverflowError), at both wire boundaries.

Regression suites: tests/tempo_ramp_review.test.mjs (10 of 15 cases fail
pre-fix) + two pytest cases (both fail pre-fix).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
byrongamatos added a commit that referenced this pull request Jul 15, 2026
Review #279: the ramp compile is monotonic by construction inside the
span (positive BPM), but nothing pinned the trailing block-shift at the
span END — the exact seam where a regression would write a backwards or
duplicate beat time and silently corrupt beatOf/timeOf's binary search.
Add a whole-grid strict-monotonicity assertion over every curve preset,
both accel and rit, plus the lock re-space path. No production change:
the current compile already holds the invariant; this is the guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos
byrongamatos changed the base branch from feat/editor-feel-marker to main July 15, 2026 20:28
ChrisBeWithYou and others added 3 commits July 15, 2026 22:34
The expressive centerpiece: a rit/accel is ONE authored object, never N
shrinking bars painted as noisy chips.

- ramp marker kind: {measure, measureEnd, bpmStart, bpmEnd, curve} with
  hard-edged validation (curve presets linear/ease-in/ease-out — a rit
  defaults to ease-out; never hand-tuned beziers).
- _rampCompilePure: the span's beat times re-space along the tempo
  curve (midpoint-sampled integral, beat granularity), span start
  anchored, tail shifts as a block — the _tempoSetMeasureBpmPure rule.
  Locks WIN via the existing _respaceWithLocksPure post-pass.
- editorRampRange (context menu on a multi-selection): ONE TempoMapCmd
  carries mark + compiled grid — notes ride the reproject, undo
  restores marks and beats together (TempoMapCmd gains the same marks
  snapshot contract TempoGridCmd got in P2-5).
- editorRampFitFromDrift: least-squares linear ramp over the observed
  per-bar onset BPMs; offered only when its residual beats a flat
  reading; lands through the same verb with provenance 'detected'.
- Chips: ONE ramp chip; derived tempo chips suppressed INSIDE the span
  (honest step chips at the boundaries stay).
- src/tempo-list.js + panel: the numeric ledger (UX G3) — one row per
  authored mark (Bar/Type/Value/Source), click jumps to the bar;
  identity-keyed render riding drawNow.
- routes.py: ramp joins the persistence vocabulary.

DEFERRED (documented): the graphical Tempo-lane strip with vertical
drag + curve-node gestures (UX D1) — it needs live drag verification
and its own PR; the engine, verbs, list, and persistence here are its
foundation.

tests/tempo_ramps.test.mjs (8 cases, fails on main): validation,
monotone accel compile + anchored start + block-shifted tail, ease-out
vs linear, ONE-chip contract, locks-win, exec/rollback/redo on marks +
beats, fit-from-drift on a synthesized rit, Tempo List rows. Backend
ramp round-trip in pytest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…asureEnd validation (review #279 items 4-7)

- item 4: _marksRemapPure remaps BOTH ramp endpoints atomically through a
  renumber; a ramp whose end bar was deleted or whose span collapses drops
  whole (never measureEnd <= measure).
- item 5: editorRampRange now REPLACES-from-baseline — overlapping ramp
  marks are stripped and their spans neutralized (_rampNeutralizePure:
  endpoints held, interior uniform) before the new mark compiles, making
  same-range reapply bit-identical and a BPM edit equivalent to compiling
  against the original grid. Endpoint-touching ramps still coexist.
- item 6: the ramp prompt parses strictly (_rampPromptParsePure — complete
  finite numbers, 0 < bpm <= 1000) and the mark is _markNormPure-validated
  BEFORE any compile or history mutation; invalid input leaves beats,
  marks, and history reference-identical.
- item 7: routes.py parses measureEnd with _exact_int instead of int() —
  fractions/bools/strings drop instead of truncating, inf no longer
  crashes the load (OverflowError), at both wire boundaries.

Regression suites: tests/tempo_ramp_review.test.mjs (10 of 15 cases fail
pre-fix) + two pytest cases (both fail pre-fix).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Review #279: the ramp compile is monotonic by construction inside the
span (positive BPM), but nothing pinned the trailing block-shift at the
span END — the exact seam where a regression would write a backwards or
duplicate beat time and silently corrupt beatOf/timeOf's binary search.
Add a whole-grid strict-monotonicity assertion over every curve preset,
both accel and rit, plus the lock re-space path. No production change:
the current compile already holds the invariant; this is the guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos
byrongamatos force-pushed the feat/editor-tempo-ramps-lane branch from 77dd43e to dfa4a3b Compare July 15, 2026 20:35
@byrongamatos
byrongamatos merged commit 40ef5d0 into main Jul 15, 2026
3 of 4 checks passed
@byrongamatos
byrongamatos deleted the feat/editor-tempo-ramps-lane branch July 15, 2026 20:35
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.

2 participants