Skip to content

fix(studio): the Volume control changes the whole clip, fades included - #7587

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/studio-volume-whole-clip
Nov 12, 2026
Merged

miguel-heygen merged 1 commit into
mainfrom
fix/studio-volume-whole-clip

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

What was wrong

A creator reported that raising a sound effect's volume in Studio only changed the level around the playhead, as if a keyframe had been added.

That is what the media panel's Volume slider did on main: it wrote a point into the clip's volume automation at the playhead, starting a lane when the clip had none. A separate Gain slider held the whole-clip level. Agents often write a fade on sound effects, so clips with a volume lane are the normal case.

What changes

  • Volume now sets the whole clip's level (data-volume). If the clip has a fade or volume keyframes, the whole curve moves together: the fade keeps its shape and every moment gets louder or quieter by the same amount. Preview and render already multiply data-volume by the lane, so no level math changes.
  • The separate Gain slider is folded into Volume. The slider that wrote a point at the playhead is removed.
  • The keyframe toggle beside Volume still turns automation on and off. To add or move a single point, use the lane in the timeline.
  • The Audio Gain dialog (G) no longer refuses a clip that has a volume lane. It writes the same data-volume. Normalize loudness still refuses such clips, unchanged.

Existing projects with a fade now get whole-clip changes from the control. Nothing in a saved file changes until a person edits it.

Before

Real Studio in Chrome on main: a 6 s sound effect with a fade (0 at 0 s, 1 at 1 s, 1 at 5 s, 0 at 6 s), playhead at 3 s, Volume dragged to +6 dB. Two sliders, and only the moment at the playhead changes.

before

After

Same project and drag on this branch. One Volume slider; the whole clip is 200 %.

after

What Studio saved:

data-volume volume lane
main 1 new point t=3, v=1.995, the other points untouched
this branch 1.995262 unchanged

A clip with no lane: main created a two-point lane it was never asked for; this branch writes only data-volume.

Rendered level of the fade clip (RMS over 0.2 s windows, same project rendered unedited, with main's edit and with this branch's edit):

window unedited main this branch
0.4 s -30.05 dB -30.05 dB (no change) -24.05 dB (+6.0)
2.9 s -24.08 dB -18.20 dB (+5.9) -18.09 dB (+6.0)
5.4 s -30.05 dB -30.05 dB (no change) -24.05 dB (+6.0)

Tests

Fails without the fix: propertyPanelFlatMediaSection.test.tsx > "Volume sets the whole clip's gain and never writes a keyframe (fade: true)" and "(fade: false)" fail on main with expected [ 'Gain', 'Volume' ] to deeply equal [ 'Volume' ] (main shows a Gain slider and a Volume slider that writes a keyframe); audioGainApply.test.ts > "writes data-volume on a clip with a volume lane, which the gain stacks on" fails on main with Error: This clip's volume is automated. Remove its volume automation to normalize it.; the existing "commits +12 dB of boost from the upper half of the gain fader" test, now aimed at the Volume slider, fails on main with expected "vi.fn()" to be called with arguments: [ 'volume', '3.981072' ]. All three pass on this branch (checked against main's source of the three changed files, 4 failed | 46 passed).

  • propertyPanelFlatMediaSection.test.tsx: Volume writes data-volume and no keyframe, with and without a fade, and there is one Volume slider. Red on main, green here.
  • audioGainApply.test.ts: writeClipGains writes data-volume for a clip with a volume lane. Red on main, green here.
  • useVolumeAutomation.test.tsx: tests of the removed point writer are gone with it.
  • src/components/editor and src/player/components: 305 files passed, 1 skipped. tsc --noEmit and oxlint clean.

Search first: the level math already lives in gainTimesLane (packages/core/src/audioGain.ts) and the runtime and render paths that call it (#6068); no new helper is added.

The media panel's Volume slider wrote a keyframe at the playhead, so a clip with a fade (or none) changed only around where the playhead sat. Volume now sets the clip's gain, the whole fade moves with it, and the keyframe toggle still adds automation on purpose. The separate Gain slider is folded into it, and the Audio Gain dialog no longer refuses clips that have a volume lane.
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 118 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

@heygengenesis heygengenesis Bot 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.

Requesting changes at 802c5029. The only blocker is the description, not the code.

Blocker: no Fails without the fix: line. A fix PR in this lane has to name, on a line starting Fails without the fix:, a test that fails without the fix. The Tests section says "Red on main, green here", but it never names a failing test and its error on that line. I checked locally, so the line can be added as is:

Fails without the fix:
- propertyPanelFlatMediaSection.test.tsx › "Volume sets the whole clip's gain and never writes a keyframe (fade: true)" — expected [ 'Gain', 'Volume' ] to deeply equal [ 'Volume' ]
- audioGainApply.test.ts › "writes data-volume on a clip with a volume lane, which the gain stacks on" — throws "This clip's volume is automated. Remove its volume automation to normalize it."

Once the body has this line I'll approve. The code itself has no blockers:

  • Volume now writes data-volume through onSetAttribute. The removed Gain slider wrote the same attribute through the same call, so nothing new is written. The lane toggle is unchanged.
  • writeClipGains no longer refuses a clip with a volume lane. normalizeClipsLoudness still refuses one on its own check (audioGainApply.ts:43), and volumeLaneOwnsGain and VOLUME_LANE_REFUSAL are still used there.
  • The speed slider still uses the shared lane hook's onCommitAt and automatedValue. Only the volume part of that binding was removed.
  • I ran the three changed test files locally: 65 out of 65 pass. With main's three source files put back, 4 tests fail, including the two above. The newest CI, PR captures and regression runs at this head are green.

@heygengenesis heygengenesis Bot 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 802c5029. The head is the same and the code hasn't changed. The description now has the Fails without the fix: line, and it matches what I reproduced locally. With main's three source files put back, 4 tests fail and 46 pass, with the same errors the line quotes. All 65 tests pass on this branch. The code review from my previous review still holds, and the newest CI at this head is green.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 12, 2026
Merged via the queue into main with commit 245904d Nov 12, 2026
262 of 291 checks passed
@miguel-heygen
miguel-heygen deleted the fix/studio-volume-whole-clip branch November 12, 2026 05:13
@miguel-heygen miguel-heygen mentioned this pull request Nov 12, 2026
4 of 6 tasks
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.

1 participant