Skip to content

fix: data-volume multiplies the volume lane in preview and render - #6068

Queued
miguel-heygen wants to merge 6 commits into
mainfrom
fix/clip-gain-times-lane
Queued

miguel-heygen wants to merge 6 commits into
mainfrom
fix/clip-gain-times-lane

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

What

data-volume is clip gain that multiplies the volume lane. Projects setting both now sound at gain × lane. Reset gain to 1 to retain a lane's former absolute level.

Clip gain plays before clip effects; the lane plays after them. Dynamics effects therefore react to the gained signal. Group gain stays after group effects. Studio exposes separate Gain and Volume controls: Gain edits data-volume; Volume edits a lane point at the playhead, creating a lane when needed.

Why

A lane previously discarded data-volume. The shared contract now caps their product at the existing +12 dB authored-gain ceiling. It preserves the ramp below that ceiling: gain 2 × lane 0→3 over 10 seconds is 3 at 5 seconds and reaches the ceiling at 6.6351195092 seconds. Clamping endpoints alone incorrectly made the midpoint 1.9905358528.

How

  • gainTimesLane owns the capped product. gainedVolumePoints inserts ceiling-crossing breakpoints for straight ramps and bounds sampling for bent crossings. Preview clips, groups and render consume that envelope. A pre-FX clip gain leaves only the lane's share of the capped product downstream.
  • Gain edits rebook the lane. Removing a playing lane clears its held AudioParam events and restores the static clip or group baseline. Zero gain stays muted without division. Preview and render share the check for exponent and via-point bends.
  • The PCM baker and FX pass receive the same envelope. Existing GSAP volume tweens keep their absolute-level behavior.
  • Core's unpacked package budget changes from 3,936,240 on the base to 3,942,104 bytes, matching the built artifact. The gain stages and shared envelope helpers account for the increase. Packed core is 1,633,047 bytes, within its unchanged budget; engine remains within both budgets.

Before

The same synthetic audio fixture, with gain 0.5 and lane 0.8 at the playhead. Only Volume is exposed.

Before panel

After

Gain shows −6.0 dB independently of the lane's −1.9 dB Volume. Captures use the pre-change and current panel sources while holding the audio fixture fixed.

After panel

Test plan

  • Failing first against the previous audio implementation: transport exit 1, 4 failed / 95 passed; render envelope exit 1, 3 failed / 8 passed; mixer exit 1, 1 failed / 73 passed.
  • Three serial runs of each changed audio test file: automation 40, transport 99, render envelope 11, mixer 74; every run exit 0, no skips. Covers over-ceiling products, rising/falling crossings, zero gain, gain edits and pre-FX render input.
  • Deliberately removing crossings, removing the upstream-gain correction, and replacing zero gain with unity each made the new witnesses fail. Mutations restored.
  • Lane-removal regression: 2 failed / 99 passed without the reset; restored implementation passed transport 101 and FX runtime 37 in three serial rounds. Real browser OfflineAudioContext PCM returned from 0.5 to 2 for both clip and group after removing the lane.
  • Three runs of each changed Studio test file: panel 46 and volume automation 21, every run exit 0, no skips.
  • Neighbor checks: media 153, FX runtime 37, AudioParam scheduling 23, each exit 0. Core/engine typechecks, lint, format, comment checks and commit hooks passed.
  • Browser fixture: Gain changed from −6 to −3 dB without changing the lane; Volume changed the first lane point to −6 dB without changing gain. Both changes persisted in the saved HTML and after a full reload.

What I measured

Recorded the real AudioParam schedule, native browser PCM for lane removal, mixer envelope inputs, packed artifacts, and fixture control edits with saved-file and reload checks. Unit tests and builds ran on Linux; an isolated browser drove the fixture Studio server.

What I did NOT exercise

The ceiling delta was not auditioned through a physical audio device or measured from a new encoded output. The scheduling/mixer tests record collaborators; they do not prove codec or device behavior. Touch interaction, screen-reader output, invalid text, undo/redo, and first-time-user comprehension were not exercised in this pass.

UX checklist

Job: adjust a clip's gain independently of its volume keyframes.
One action: edit Gain while retaining the lane, or edit Volume at the playhead.

# Question Result Evidence
1 First-time purpose clear Not exercised Labels captured; comprehension not tested.
2 Editable controls recognizable Y Two labeled sliders and editable dB values.
3 User-facing words Y Gain, Volume, dB.
4 Equivalent editor model Y Clip gain offsets the lane, the requested model.
5 Guidance at the control Not exercised No novice guidance study.
6 Action reachable Y Both controls visible in Media.
7 Clear action hierarchy Y Paired property edits; no competing submit action.
8 Display matches data Y −3 dB gain and −6 dB lane checked after reload.
9 Excluded items announced Not exercised Hidden/deleted selection not tested.
10 Long names recoverable Not exercised Short fixture filename only.
11 Keyboard path Y Text edits committed with Enter.
12 Controls discoverable Y Gain and Volume visible without hover.
13 Helpful refusals Not exercised Invalid text not tested.
14 Refusals placed nearby Not exercised No refusal triggered.
15 Invalid options prevented Not exercised Out-of-range UI entry not tested.
16 Recover without losing work Not exercised Undo/redo not walked.
17 Destructive actions separated Not exercised No destructive edit walked.
18 Empty states explained Not exercised Selected audio only.
19 Visible feedback Y Value and preview attributes updated after Enter; latency not measured.
20 Wait progress Not exercised No long operation in this flow.
21 State survives reload Y Saved HTML and fresh preview retained both values.

Choice: the two existing gain layers are exposed as two controls; neither is hidden behind a mode.
End: the panel shows the saved gain and lane value after reload.

Base automatically changed from fix/preview-volume-lane-one-writer to main October 22, 2026 15:33
@mintlify

mintlify Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

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

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 22, 2026, 3:47 PM

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

@miguel-heygen
miguel-heygen force-pushed the fix/clip-gain-times-lane branch from 7f4eed9 to e8e01b4 Compare October 22, 2026 15:39

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at e8e01b4, after #6057 landed and the branch was rebased onto main cfff449. The audio implementation and tests are byte-identical to the first requested head, 7f4eed9. I did not re-audit the unrelated CLI deadline changes that came in with the rebase. Both failure probes were re-run at this head.

Zero gain and ordinary multiplication work. The documented behavior change for existing projects is called out explicitly. Two parity defects remain in the boost range.

P2: a routed clip's preview goes above the ceiling render uses (packages/core/src/runtime/media.ts:461-463, webAudioTransport.ts:102-108).

  • A routed clip now sends data-volume to its gain node, and the lane node schedules the raw lane separately.
  • Probe through the real syncRuntimeMedia → WebAudioTransport: data-volume=2 with a constant lane of 3 gives a graph gain of 2×3 = 6.
  • The shared gainTimesLane and render cap that product at MAX_AUDIO_GAIN ≈ 3.981.
  • Each authored value is valid on its own; it is only the product that goes over.

Fix: apply the same combined-gain and ceiling rule on the routed path, and add tests for products above the ceiling. Clamping each factor separately does not enforce the ceiling on the product.

P2: clamping the breakpoint products bends a ramp that used to be linear (packages/core/src/runtime/audioFx.ts:145-146, packages/engine/src/services/audioMixer.ts:514-515).

  • Case: gain 2 with a lane ramping 0→3 over 10 s.
  • These paths scale and clamp the endpoints to 0→3.981, then interpolate between them.
  • At 5 s, the group schedule and the render envelope give 1.9905, while gainTimesLane(2, sampleLane(5)) gives 3.
  • So the ramp is quieter than it should be well before the product actually reaches the ceiling, and it also differs from the routed clip preview.

Fix: keep the product-then-clamp rule at every point through interpolation, including where the ramp crosses the ceiling, instead of clipping only the authored breakpoints. Add ramps that cross the ceiling to the preview-group and render tests.

How this was checked:

  • Preview probes ran the current syncRuntimeMedia, WebAudioTransport and scheduleVolume with recording AudioContext/GainNode stubs.
  • Render probes ran the current volumeLaneKeyframes and interpolation code, extracted from source, with the actual gain and automation modules.
  • No real audio device, generated WAV or audible distortion was tested.
  • The standard core test command stopped before collecting tests: its audio-runtime prebuild couldn't resolve a parser export from the borrowed dependencies. That is a limit of this validation, not a failure in the PR.

Review by Rames (hf-review)

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 159 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 (0)

Unstable (1)

  • seqplainkeys-none-center-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Fixed both ceiling findings in 1f41ecf.

  • Routed clips schedule only the lane's share of the capped product after the pre-FX gain. Gain 2 with lane 3 now produces 3.9810717055 across the two gain stages, rather than 6.
  • Preview groups and render share gainedVolumePoints. A straight ramp receives a breakpoint where its product reaches the ceiling, so gain 2 × lane 0→3 over 10 seconds remains 3 at 5 seconds and holds the ceiling after 6.6351195092 seconds. Falling ramps are covered too.
  • Gain edits rebook the ceiling-aware lane. Zero gain stays zero without division. Bent crossings use bounded samples, and the render baker now recognizes via-point bends with the same predicate as preview.

Failing first against the preceding committed implementation: transport exit 1, 4 failed / 95 passed; render envelope exit 1, 3 failed / 8 passed; mixer exit 1, 1 failed / 73 passed. With the fix, all four changed test files passed three serial runs: automation 40, transport 99, render envelope 11, mixer 74, each exit 0 and no skips. Three deliberate mutations also failed: removing crossing breakpoints, removing the upstream-gain correction, and replacing zero gain with unity. Core and engine typechecks passed; commit hooks passed.

These checks execute the real scheduling and mixer code with recorded AudioParams and mixer collaborators. This ceiling delta has not been checked through an audio device or an encoded output file.

This push also preserves the separate Studio Gain control change. Its two changed test files passed three runs (46 and 21 tests, exit 0); fixture panel captures are still pending before the UI part is ready for review.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed at 1f41ecf. Both ceiling findings from my request for changes at e8e01b4 are fixed in the scheduling and envelope paths I checked.

  • Routed preview: running the real syncRuntimeMedia → WebAudioTransport with recording GainNodes, gain 2 × a constant lane of 3 now gives 3.9810717055 instead of 6. Zero gain and 0.5 gain also match render.
  • Straight ramps: gainedVolumePoints now inserts the point where the ramp crosses the ceiling. With gain 2 on a lane ramping 0→3 over 10 s, both the group schedule and the current render-envelope function (extracted from source) give 3 at 5 s and reach the ceiling at 6.6351195092 s. The ramp is no longer bent toward a clipped endpoint. Curved crossings use bounded sampling, so this doesn't claim exact parity in continuous time.

The added clip-gain change looks right:

  • The preview gain node sits before FX, and the new node has matching teardown paths.
  • Render applies input gain in both the whole-file and windowed paths.
  • Studio keeps the static Gain separate from the lane Volume, and new volume lanes start at unity.
  • Studio's production caller supplies the lane commit callback.

Non-blocking, pre-existing: removing a live volume lane leaves its dedicated AudioParam at its last value instead of resetting it to unity (packages/core/src/runtime/audioFx.ts:232-243; scheduleVolume, lines 142-148). A replay of lane removal on a recording param keeps 0.25 after the lane is removed. With clip gain 2, preview stays at 0.5 while an export with no lane uses 2. The previous implementation had the same gap, so it doesn't reopen the ceiling findings. Worth tracking separately.

What was run:

  • 139 focused tests pass (40 automation, 99 transport), against current-source aliases with borrowed dependencies.
  • I re-ran the original preview and render-envelope probes, plus the lane-removal replay.

What was not run:

  • The Engine and Studio suites couldn't collect locally, because generated runtime and dependency exports were unavailable.
  • Real audio-device playback, an encoded WAV, compressor output and a full render were not verified.

The panel screenshots are still pending from the author, so this approves the code, not the UI.

Review by Rames (hf-review)

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

The lane-removal finding is fixed at bfe42fd. The shared FX scheduler now clears the removed lane's held events and schedules the static baseline: unity downstream of clip gain, or the group's static gain.

The two new transport witnesses fail without the reset (2 failed / 99 passed). Restored: transport 101 and FX runtime 37 pass in three serial runs. A native browser OfflineAudioContext rendered PCM at 0.5 before removal and 2 afterward for both the clip and group paths. Core typecheck, lint, format and commit hooks passed.

The PR body now includes separate Before and After capture sections and the saved-file/reload control checks. The panel captures are unchanged by this audio-only follow-up.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed at bfe42fd. The lane-removal follow-up I raised when approving at 1f41ecf is fixed.

packages/core/src/runtime/audioFx.ts:151-158,251-260 now clears the removed lane's held events and resets the gain after clip gain to unity, or to the clamped static group gain. One shared scheduler covers both clips and groups. My original replay on a recording param now resets 0.25 → 1, which restores a total gain of 2 for the clip. Both new transport regression tests fail on the previous scheduler and pass at this head.

What ran

  • 178 focused tests pass: 40 automation, 101 transport, 37 FX runtime. These ran against current-source aliases with borrowed dependencies.
  • The earlier ceiling fix is unchanged in source, and its regression tests still pass. Curved crossings are still bounded approximations.

What changed besides that: the budget JSON only raises core's unpacked allowance, from 3,938,103 to 3,942,104 bytes. I didn't rebuild the package to check its measured size.

Not verified: native audio output, encoded output, the full Engine and Studio suites, and the panel screenshots. Recording AudioParam calls proves the scheduling behaviour, not what a device plays.

Review by Rames (hf-review)

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 22, 2026
Any commits made after this event will not be merged.
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 22, 2026
@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 22, 2026
Any commits made after this event will not be merged.

This branch was successfully deployed

1 active (outdated) deployment
staging - docs — de06906d Deployed Oct 22, 2026 by mintlify[bot]
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