feat(studio): audio gain on G, speed in clip names, additive selection and partner clamp (8/8) - #4821
vanceingalls wants to merge 18 commits into
Conversation
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Reviewed the #4821 delta at exact 49a5b9b0e2341e69d63d83eed3e65552b43aef23 against stacked #4820 ca563f775fcd9f0adba2c468f538f1dca6d6d037. Two inline holds: SDK linked-duration edits can push an offset audio partner past the video endpoint; and Studio's detached-audio containment runs before collision placement (and only for the grabbed element), so the final moved audio can escape the video. Current-head Comments is also red on five raised-comment-share files. #4820 and earlier stack layers remain held; this review is of this incremental delta only. No local browser, tests, merge, or release action. — Jerrai
| const endsTogether = | ||
| Math.abs(pStart + (partner.duration ?? 0) - (gStart + (grabbed.duration ?? 0))) < | ||
| ALIGN_EPSILON_S; | ||
| if (edit.duration !== undefined && endsTogether) timing.duration = edit.duration; |
There was a problem hiding this comment.
When linked clips share an end but have different starts, copying the grabbed clip's duration moves the partner's end. Example: video [2,8] (duration 6) and audio [3,8] (duration 5), both linked. session.setTiming(video, {duration:4}) applies video [2,6] and then this sets audio duration 4 → [3,7], so the audio extends one second past its video even though this delta promises it stays inside. The new session test checks linked start movement; the duration test uses equal starts, which cannot catch the offset. Preserve the common end (derive partner duration from its own start and the new end) and add an unequal-start test.
| const previewStart = video | ||
| ? clampStartIntoBounds(floored, drag.element.duration, video) | ||
| : floored; | ||
| const placement = resolveDropPlacement(drag, clientY, previewStart, nextMove.track, ctx, group); |
There was a problem hiding this comment.
The detached-audio/video bound is applied to previewStart, but resolveDropPlacement can move it outside that bound to avoid another clip, and line 200 returns placement.start without reclamping or rejecting it. Concrete same-lane case: video [10,30], its audio [12,20] (duration 8), obstacle [20,30]; dragging audio toward 22 clamps to 22 (the last bounded start), but nearest-free placement prefers 30 over 12, and timelineClipDragCommit.ts:240–244 persists audio [30,38]. Also, the bound is computed only for drag.element, not audio carried as a passenger of a multi-selection; dragging selected text and audio moves the audio via resolveMultiSelection without its own bounds. Please constrain final placement and each held audio mover, and test collision and non-audio-grabbed multi-select cases.
49a5b9b to
fb5c6b6
Compare
Edit accuracy: 530 passing here, 530 on the base branchThe gate passes. |
fb5c6b6 to
a64a9ad
Compare
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Re-reviewed #4821 at exact a64a9ad1a50bb07930492362c083ba1a9d77f3f4 against current #4820. The unequal-start/shared-end duration repair and the Studio collision/passenger clamp address the previous examples; Comments and SDK/Studio checks are green. One boundary in the duration repair still breaks the linked containment promise (inline): a valid short video-duration edit can end before the audio starts, the computed partner duration becomes nonpositive and is silently skipped, leaving the old audio span beyond the video. Please define and test the crossing behavior. Current-head Studio/player captures and file-size checks also fail; no live UI or audio validation was performed. Other stack layers remain separately held. No merge or release action. — Jerrai
| const partnerEnd = (partner.start ?? 0) + (partner.duration ?? 0); | ||
| if (Math.abs(partnerEnd - grabbedEnd) >= ALIGN_EPSILON_S) return undefined; | ||
| const duration = (edit.start ?? grabbedStart) + edit.duration - partnerStart; | ||
| return duration > 0 ? duration : undefined; |
There was a problem hiding this comment.
This repairs unequal-start durations only while the new shared endpoint remains after the partner's start. With linked video [2,8] and audio [3,8], setTiming(video,{duration:0.5}) makes the video [2,2.5], computes partner duration -0.5, and silently returns undefined; applySetTiming commits the video but leaves the audio [3,8] outside it. No SDK rejection runs before the first mutation. Please reject this edit atomically, remove/retime the partner under an explicit policy, or otherwise preserve the linked/containment invariant, with a regression at this crossing boundary.
a64a9ad to
73fc900
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dges Grey with no effects, white with any. The tooltip lists look, voice, other fx, crop and ducking; a click opens the clip menu. The volume badge stays when volume is off 100%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Set Gain to, Adjust Gain by, Normalize Max Peak to, Normalize All Peaks to and Normalize loudness to -16 LUFS, with the selection's peak amplitude read from the peak map. Writes data-volume on every selected clip with sound as one undo step. The clip menu's Normalize loudness becomes Audio Gain... (G). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… recording Keys pressed inside an aria-modal dialog no longer reach the global hotkeys, so Delete on the focused OK button cannot delete the clips the dialog edits. The multi-clip write refuses during a recording like the single-clip one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Link no longer requires the same file and identical timing, matching Premiere. Same-file pairs get a sync origin so a trimmed or moved audio shows its offset; different files link without one. Merge back is disabled until the pair is in sync. Linked moves keep the offset, and a trim only carries to a partner whose edge is at the same time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A modifier click on a clip replaced the selection, so a video and an audio could never both be selected and Link never appeared. Shift, Cmd and Ctrl now toggle the clip (and its link partners unless Alt) in the selection; Shift no longer turns a clip click into a time-range popover. Right-clicking a lone unlinked video or audio offers Link when exactly one opposite-kind clip from the same file sits on another track. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dd in every host A modifier click passed the toggle as an argument to an optional onSelectElement call, so hosts without that handler (desktop) never ran it. An audio clip linked to a video, or sharing its sync origin, now stays inside that video's span while dragged or trimmed, Alt edits included, unless the video moves or trims in the same gesture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dragged audio's ghost followed the pointer past the video's end while the landing stayed clamped; it now stops where the clip will land. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Unlink both clips stayed selected, so the next edge trim ran the multi-select group resize and the former partner still followed. Unlink from the menu, the shortcut or an Alt edit now leaves nothing selected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… duration Copying the grabbed clip's duration to a partner that starts later pushed the partner's end past the clip both ended on. The partner's duration is now the shared new end minus its own start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… placement too The containment clamp ran before collision placement and only for the grabbed clip, so a drop pushed past an obstacle, or audio carried along in a multi-selection, could land outside its video. The allowed shift is now the intersection over every moving audio, and a placement outside it is rejected. Link partners are matched within their source file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ain dialog Normalize moved from the clip menu into the Audio Gain dialog here, so the volume-lane refusal and the saved/refused/failed checks go with it: a lane owned clip is refused, and a failed single or multi-clip save rejects instead of reporting success. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s start
With linked video [2,8] and audio [3,8], setTiming(video, {duration: 0.5})
computed a partner duration of -0.5, skipped it, and committed the video alone,
leaving the audio outside its video. The partner edits are now planned before
any mutation; a non-positive partner duration throws ("Linked audio would start
after the new end — unlink or trim the audio first.") and can() reports
E_LINKED_PARTNER_CROSSED, so nothing is written and the undo stack is
untouched. In Studio the refusal fails the cutover (never declines into the
legacy path) and the timeline resize toast shows the reason.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73fc900 to
e8968fb
Compare
Summary
The Premiere polish pass:
[150%]/[ramp]Changes
[NN%]/[ramp]clip names, and a single fx badge in place of the per-tool and link badges.AudioGainDialogon G (one undo step). Shortcuts are kept out of the modal, and it refuses while recording.Testing
Static gates on the layer tip:
bun install,bun run build,tsc --noEmitfor every touched package (0 errors),oxlintandoxfmt --checkon files changed vs main (clean),gen:skills-manifest --check(in sync),scripts/comment-ratchet.mjs(ok).clearSelectionfails the new "unlink leaves nothing selected" test.Notes
setResizingClipseeds the trim gesture from the whole selection. After Unlink both clips stayed selected, so upstream's multi-select group resize trimmed both. Unlink now clears the selection. Trimming with both clips deliberately selected still trims both, by design (upstream's restored multi-select group resize).🤖 Generated with Claude Code