feat(core): fade any clip in and out with data-fade-in / data-fade-out - #3350
Open
miguel-heygen wants to merge 1 commit into
Open
feat(core): fade any clip in and out with data-fade-in / data-fade-out#3350miguel-heygen wants to merge 1 commit into
miguel-heygen wants to merge 1 commit 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.
3 tasks
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
Any clip can now declare its own fades, and the runtime plays them:
Three curves —
linear(default),smooth(eases both ends),sharp(holds,then moves late). Either end works on its own.
Targets
maindirectly: it is the framework half of Studio's fade handles, anduseful on its own to anyone writing a composition by hand.
Why
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. Declaring it instead means the fade follows the clip — trim it,
slide it, ripple the track, and the fade is still a fade.
It also gives Studio's fade grips something to write for visual clips. Audio
already had somewhere to put a fade (
data-automation); nothing else did.How
filter: opacity(), notopacity. Opacity is the property animationengines drive, and a runtime that rewrites it every frame fights them for it —
the repo has scar tissue from exactly that. A filter multiplies with whatever
they set instead, so a fade composes with an author's opacity tween rather
than racing it.
fade is composed onto it (
blur(2px)→blur(2px) opacity(0.5)), thenrestored exactly whenever the clip is not attenuating — including the frames
where it is outside its own window. A clip that is not fading carries only
what its author gave it.
isTimedElementVisibleAtis split into aresolveTimedElementWindowthat both visibility and the fade read, because afade running on a different window than the clip is visible for is a fade that
clips or hangs.
into a dip, so a clip trimmed shorter than its own fades still resolves to a
clean in and out. A clip with no end can only fade in.
hasAttributecalls before parsing anything;it runs for every timed element on every frame and almost none declare a fade.
Test plan
Unit tests added/updated — 15 covering parsing (missing, partial, bad
values, unknown curve), the three curve shapes, level at every interesting
point of the window, the proportional split, the unbounded window, and
filter composition.
Manual testing performed — seeked a real preview and read each element's
computed inline filter back out of the page.
clip-ais 0-8s with a 2ssmooth fade at each end;
clip-bstarts at 8s with a 1s fade and anauthored
blur(2px):opacity(0)blur(2px)opacity(0.1563)blur(2px)opacity(0.5)blur(2px)blur(2px)opacity(0.5)blur(2px)blur(2px) opacity(0)blur(2px) opacity(0.5)blur(2px)0.1563issmoothstep(0.25), so the curve is the one it claims to be; theauthored blur survives throughout and is what remains once each clip is past
its fade.
Full
packages/coresuite green (2443 tests), typecheck, oxlint, oxfmt.Not covered
data-automationto live in, and putting it there keeps it editable asbreakpoints rather than as one number. A
<video>withdata-fade-infadesthe picture; ducking its sound is the envelope's job. Folding the clip fade
into media gain would mean matching it in the producer's PCM bake too, or
preview and render would disagree — worth doing, worth doing separately.
hyperframes lintrule for a fade longer than itsclip (the runtime resolves it sensibly), and the attribute is not in the docs
site or the skills yet. Both follow once the shape is agreed.