feat(studio): fade handles on every clip, not just audio - #3351
Open
miguel-heygen wants to merge 2 commits into
Open
feat(studio): fade handles on every clip, not just audio#3351miguel-heygen wants to merge 2 commits into
miguel-heygen wants to merge 2 commits into
Conversation
A fade had to be animated: an opacity tween positioned against the clip's
own start and duration, which then had to be kept in sync every time the
clip moved or was trimmed. This makes it declarative — the author says how
long the fade lasts and the runtime attenuates the clip across that stretch
of its own window, so the fade follows the clip wherever it goes.
<video class="clip" data-start="0" data-duration="4"
data-fade-in="0.5" data-fade-out="0.75" data-fade-curve="smooth">
Applied as `filter: opacity()`, not `opacity`. Opacity is the property
animation engines drive, and a runtime that rewrites it every frame fights
them for it; a filter multiplies with whatever they set instead. The
element's authored filter is captured on first touch and composed onto, and
restored exactly whenever the clip is not attenuating — so a clip outside
its fades carries only what its author gave it.
Three curves: linear, smooth (eases both ends) and sharp. Fades too long for
the clip share it in proportion rather than overlapping into a dip, so a
trimmed clip still resolves to a clean in and out.
Visual only. A fade on a sound is volume automation, it already has
`data-automation` to live in, and putting it there keeps it editable as
breakpoints instead of as one number.
The grips only appeared on audio, because the volume envelope was the only place a fade had to live. Now that the runtime reads `data-fade-in` and `data-fade-out`, every clip has somewhere to keep one — so every clip gets the grips. One gesture, two storages, chosen by what the clip is: audio still rides its volume envelope, so a fade drawn with the grip is there as breakpoints to refine afterwards; everything else carries the attributes. Each is a projection that reads back exactly what it wrote, and the wedge is drawn with the sampler of whichever thing will actually play it — the runtime's easing for a visual fade, the envelope's own curvature for an audio one. The attribute write reaches the preview and the file, but the timeline's element list is only re-derived on a refresh the quiet commit skips, so the fade is also applied to the store — on commit only. Applying it during the drag moved the value each write compares against, and by release "nothing changed" and the persisted write never happened: the preview faded and the file did not.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The fade grips now appear on every clip, not just audio. Drag one in to set the
fade, drag it back to the corner to remove it, double-click to step the shape
through linear, smooth and sharp — same gesture whatever the clip is.
Why
The grips were audio-only because the volume envelope was the only place a fade
had somewhere to live. #3350 gives the runtime
data-fade-in/data-fade-out,so now everything does.
How
One gesture, two storages, chosen by what the clip is. Each is a projection
that reads back exactly what it wrote:
data-fade-in/data-fade-out/data-fade-curveruntime's easing for a visual fade, the envelope's own curvature for an audio
one. Both are named
smooth; they are not the same numbers, and drawing onewith the other's shape would be a picture of the wrong fade.
the preview and the file, but the timeline's element list is only re-derived
on a refresh the quiet commit skips.
updateElement's writable surface is now a namedEditableTimelineFieldstype rather than a Pick list re-spelled at the store.
Test plan
Unit tests added/updated — 19 in the binding (which storage each clip
gets, preview vs commit, only-the-end-that-moved, attribute removal, the
curve default staying off the markup, the read-only cases, the store
apply) and 23 in the fade math, including that the audio and visual
samplers draw different shapes for the same curve name.
Manual testing performed — drove a real Chrome and read the project
file back after each gesture, on a
divclip (0–8s) and an audio clip:data-fade-in="2"data-fade-out="1.5", fade-in keptdata-fade-curve="smooth", both fades keptdata-automationvolume[{t:0,v:0},{t:2,v:1}]And the runtime attenuates it, read out of the live preview:
opacity(0)att=0,
opacity(0.5)at t=1, no filter at t=4,opacity(0.2593)at t=7.5 —which is
smoothstepa third of the way into the 1.5s tail.The same grips, each clip fading in the way its own medium does:
Full
packages/studiosuite green (4353 tests), typecheck, oxlint, oxfmt.Two bugs this pass caught, both only visible by looking
which moved the value each write compared against — so by release "nothing
changed" and the persisted write never happened. The preview faded and the
file did not. It applies on commit only now, and the probe above is what
caught it.
the probe for that was the level at the midpoint — where every symmetric
easing sits exactly on the straight line. Smooth fades drew as straight ramps.
There is no shortcut now, and the test samples a quarter in.
Not covered
data-fade-inon a<video>fades the picture; duckingits sound is still the envelope's job, and a video has no automation lane in
the timeline yet.