Skip to content

feat(studio): edit a clip's speed ramp with a rate lane and presets - #4153

Merged
miguel-heygen merged 7 commits into
mainfrom
feat/speed-ramp-studio
Sep 20, 2026
Merged

miguel-heygen merged 7 commits into
mainfrom
feat/speed-ramp-studio

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

Speed ramps, end to end: an editor can ramp a clip's speed in Studio, an agent can write the same ramp in the format, and snapshots and the docs agree with the preview and the render. (This folds the former #4170 and #4171 into one PR, one feature, one review.)

What a user can do now

Editor, in Studio

  • A log-scale Speed slider (0.1x-10x) replaces the plain constant-rate slider.
  • An "A" toggle turns the speed into a curve over the clip's timeline, drawn as a lane like volume automation, editable point by point.
  • A Speed preset picker applies one of six ready-made curves (montage, hero, bullet, jump cut, flash in, flash out).

Agent or anyone reading a project, from the CLI

  • hyperframes snapshot now shows the right frame for a clip with a speed ramp, and for a clip at a constant rate above 5x. It used to clamp at 5x and ignore the ramp, so those snapshots disagreed with the preview and the render.

Anyone learning the format, in the docs

  • One reference page, speed-ramps, teaches the rate lane in data-automation (fields, presets, what Studio shows, pitch, limits, and which speed limits belong to the viewer instead of the clip).
  • The data-playback-rate range in the HTML schema now says 0.1 to 10, and the skills no longer tell agents that speed ramps are unsupported.

Shape

  • Studio reuses the volume-lane binding and geometry helpers through one shared lane binding, rather than a second implementation, and adds a @hyperframes/core/speed-ramp subpath mirroring the existing subpath shape.
  • Fixes a bug in the shared attribute-commit path found by review: the optimistic live write and the on-disk write could disagree for any attribute whose "off" value is the literal string "false". The live write now matches the file write for exactly the attributes the patcher treats as booleans, with a regression test.
  • Snapshot reads the rate through the runtime's own reader and maps time with the shared sourceTimeAt / timeAtSourceTime, so there is no second copy of the clamp.
  • The viewer's transport speed is a separate limit from a clip's rate (0.1 to 10, owned by packages/core/src/playbackRateBounds.ts); they are not unified.

Before

Only a constant Rate slider, no way to automate it:

before.mp4

After

Speed slider, automate toggle, preset picker; applying a preset draws the curve as a lane on the timeline:

after-v2.mp4

Verification

  • Devbox (exit codes checked): Studio propertyPanelFlatMediaSection, useVolumeAutomation, useDomEditAttributeCommits tests 40 passed; snapshot.test.ts 38 passed; tsc clean for packages/cli. Studio tsc on the devbox lists only four errors in files that do not exist in this branch (stale leftovers on that copy).
  • Not exercised: a real hyperframes snapshot of a ramped clip, a docs preview build.
  • Independent review of the combined diff: see the table below.

Pre-review pass

Each part was reviewed independently before folding, and the combined diff was reviewed again at this head (results in the table). Findings that were fixed along the way (the last two came from the combined review): a Speed preset on a clip with no data-duration was stretched over a made-up one second, so the panel now hides presets until the duration is known; one stale 'not in the format' line left in the editing recipes; a snapshot regression where an invalid authored rate ignored the browser default; two docs wording claims (clip length needs data-duration; the render approximates the curve in quarter-second slices); the docs now state transport speed per surface (Studio menu 0.25 to 2, player menu up to 4, runtime clamps a request to 0.1 to 5).

Area Status
Rate lane write/read round-trip to runtime Audited
Log-scale slider math, presets, duration refresh on reapply Audited
Attribute-commit fix, with a mutation that fails the new test Audited
Snapshot reader parity with the runtime on 11 attribute cases Audited
Every number on the docs page against core and engine source Audited
Undo/redo of a rate-lane edit, concurrent writers on one lane Trusting (shared infra)
Real snapshot render of a ramped clip, docs preview build Not exercised

before

after

Current-head verification

  • Head 59dffa7 is rebased onto current origin/main.
  • Devbox focused suites: Studio 222 files passed, 2,308 tests passed, 16 todo; CLI snapshot 38 passed; core speedRamp 10 passed.
  • Devbox typechecks: packages/cli clean; packages/studio clean.
  • Fresh Before and After captures below were recorded from the current head. The After state shows the selected clip's Speed control and preset picker.

The focused suites and typechecks above supersede the earlier narrower test note.

@somanshreddy somanshreddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed at 9def196f. The code is sound — including the defect fix — and every blocker is downstack.

Verified (this PR's own code)

  • The keep-pitch fix is correct and fixes the class, not just the symptom. The optimistic live-preview path treated any value === "false" as "remove the attribute," but persist() only removes "false" for HTML_BOOLEAN_ATTRIBUTES. So data-preserve-pitch="false" (keep-pitch OFF) was dropped from the preview node while written to disk — preview/persist divergence. resolveOptimisticAttributeValue(attr, value) aligns them by reusing HTML_BOOLEAN_ATTRIBUTES (data-preserve-pitch isn't one → "false" is kept, matching persist). Root-level in the shared commit path, covered by a new test. It also closes the same divergence for any other non-boolean "false" attribute, not only keep-pitch.
  • The Speed UI consumes the shared format consistentlypropertyPanelFlatMediaSection imports RATE_RANGE/RATE_TARGET/SpeedPresetId from @hyperframes/core, reads the rate lane + playback-rate constant + preserve-pitch, and maps the slider through the shared RATE_RANGE. useVolumeAutomation is cleanly generalized to bind both the volume and rate lanes by target. The new ./speed-ramp core subpath (src for bun, dist published) is the right way to expose it to Studio.

Why I'm not stamping — all blockers are downstack

  • Merge-order: stacked on #4143 (tai's, engine) → #4129 (core), and #4129 is a draft I'm holding (its own CI is red and it's coupled to #4143). #4153 can't merge before both, and #4129 must be marked ready first.
  • The red Preflight here is inherited from #4129, not this PR: it fails format:check on packages/core/src/{speedRamp,clock,runtime/media,runtime/playbackRate}.test.ts#4129's files, which live in this PR's base branch; #4153's own files pass (oxlint 0/0). It clears when #4129 is bun run format-ed. player-perf/preview-regression/regression (2-4s) are the usual gating cascade.

Net: #4153 itself is good — the defect fix is well-reasoned and the UI is on the shared format. Unblock it by getting #4129 ready + formatted and #4143 landed; it'll rebase and need a fresh stamp then (the head moves). I'll re-verify the UI wiring end-to-end once it's not sitting on a draft.

Codex disabled per workspace cap — manual pass only.

@miguel-heygen
miguel-heygen force-pushed the feat/speed-ramp-engine branch 2 times, most recently from 6fd34f5 to 1b6f5a5 Compare September 19, 2026 07:53
@miguel-heygen
miguel-heygen force-pushed the feat/speed-ramp-studio branch 2 times, most recently from e973061 to f484ba0 Compare September 19, 2026 08:46
somanshreddy
somanshreddy previously approved these changes Sep 19, 2026

@somanshreddy somanshreddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving at f484ba0e — pure replay on the fixed stack, CI green (mergeStateStatus CLEAN, 0 failure conclusions, 0 pending).

My earlier review carries: the keep-pitch fix is a correct root-level preview/persist alignment (resolveOptimisticAttributeValue reusing HTML_BOOLEAN_ATTRIBUTES, so data-preserve-pitch="false" is no longer dropped from the live preview), and the Speed UI consumes the shared core format (RATE_RANGE/RATE_TARGET/speedPresetLane, useVolumeAutomation generalized to both volume + rate lanes). Range-diff identical to what I reviewed.

Stacked on #4143 (@tai's engine half) → merges after it per the stack order. Approving on merit.

Codex disabled per workspace cap — manual pass only.

@miguel-heygen miguel-heygen reopened this Sep 19, 2026
@miguel-heygen
miguel-heygen force-pushed the feat/speed-ramp-studio branch 2 times, most recently from c4ac1b5 to 3b5559c Compare September 19, 2026 11:06
@miguel-heygen miguel-heygen changed the title feat(studio): edit a clip's speed ramp with a rate lane, presets and a pitch toggle feat(studio): edit a clip's speed ramp with a rate lane and presets Sep 19, 2026
Base automatically changed from feat/speed-ramp-engine to main September 19, 2026 12:01
@miguel-heygen
miguel-heygen dismissed somanshreddy’s stale review September 19, 2026 12:01

The base branch was changed.

@miguel-heygen
miguel-heygen force-pushed the feat/speed-ramp-studio branch 2 times, most recently from b7aa702 to 67187a6 Compare September 19, 2026 16:32
@mintlify

mintlify Bot commented Sep 19, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Sep 20, 2026, 3:27 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

…to 10x

hyperframes snapshot clamped the constant playback rate at 5 and ignored a
rate lane in data-automation, so a sped-up or ramped clip showed the wrong
frame. It now reads the rate through the runtime's own reader and maps time
with the shared source-time function.
Adds the speed-ramps reference, corrects the 0.1 to 5 rate range to 0.1 to 10
and the 'speed ramps are not supported' lines in the skills, and regenerates
the skills manifest.
A preset on a clip with no data-duration was stretched over a made-up one
second. The panel now offers presets only when the duration is known.
@miguel-heygen
miguel-heygen merged commit 0c158be into main Sep 20, 2026
138 of 160 checks passed
@miguel-heygen
miguel-heygen deleted the feat/speed-ramp-studio branch September 20, 2026 16:21
miguel-heygen added a commit that referenced this pull request Sep 22, 2026
#4153 added supported speed ramps as rate lanes in data-automation. The
previously unselected capability tests still required preprocessing.
Require the documented rate-lane representation and its precedence over
the constant playback rate, matching the runtime resolver.
miguel-heygen added a commit that referenced this pull request Sep 22, 2026
#4153 added supported speed ramps as rate lanes in data-automation. The
previously unselected capability tests still required preprocessing.
Require the documented rate-lane representation and its precedence over
the constant playback rate, matching the runtime resolver.
miguel-heygen added a commit that referenced this pull request Sep 22, 2026
* ci: select the remaining orphan tests in their owning runners

* ci: provide FFmpeg for the catalog preview witness

* ci: pass the installed Chrome path to catalog rendering

* test: align creator capability checks with speed ramp support

#4153 added supported speed ramps as rate lanes in data-automation. The
previously unselected capability tests still required preprocessing.
Require the documented rate-lane representation and its precedence over
the constant playback rate, matching the runtime resolver.
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