Skip to content

fix(audio): two things the envelopes needed to be heard at all - #3017

Open
vanceingalls wants to merge 2 commits into
wa-4-automation-playbackfrom
wa-5-automation-runtime-fixes
Open

fix(audio): two things the envelopes needed to be heard at all#3017
vanceingalls wants to merge 2 commits into
wa-4-automation-playbackfrom
wa-5-automation-runtime-fixes

Conversation

@vanceingalls

Copy link
Copy Markdown
Collaborator

Stacked on #3016. Both found by listening rather than by testing.

1. The volume lane was inaudible in preview. It was scheduled onto the
transport's gain AudioParam — but the runtime rewrites that gain every tick
from data-volume and the GSAP-seeked value, so the envelope was erased within
a frame. Volume automation was correct in the render and silent in preview.

The lane now feeds the per-tick path where the probed volume keyframes already
sit, checked ahead of them so the two cannot fight, and the transport no longer
schedules volume at all: one mechanism instead of two racing.

The cost is stated plainly: in preview the level now steps per tick rather than
per sample, exactly as the existing keyframe path does. The render still bakes it
sample-accurately, and FX parameters are still scheduled on their own
AudioParams, since nothing rewrites those. Measured on a real duck —
0.550 open, 0.150 ducked, clean ramps at both ends.

2. Structural chain edits never applied to the running graph.
buildFxChain's update reports false when a change is structural rather than
new values, and the attribute observer ignored that. So adding, removing or
bypassing an effect did nothing until the persisting write reloaded the
composition — and that reload restarted every playing track, which is what was
audible as the chop.

The graph is now swapped in place: old effects detached, new ones built and
connected between the same source and gain, lanes re-scheduled onto the new
nodes. The source node is never touched, so playback does not restart. A track
with no chain is watched too, so adding its first effect is heard the same way —
which means the attach function now always returns a disposer instead of null
for the empty case.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Fallow audit report

Found 50 findings.

Duplication (49)
Severity Rule Location Description
minor fallow/code-duplication packages/core/scripts/build-audio-fx-runtime.ts:3 Code clone group 1 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/scripts/build-audio-fx-runtime.ts:13 Code clone group 2 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/scripts/build-position-edits-render.ts:3 Code clone group 1 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/scripts/build-position-edits-render.ts:13 Code clone group 2 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/audio/audioFxAutomation.test.ts:130 Code clone group 3 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/audio/audioFxAutomation.test.ts:182 Code clone group 3 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/audio/audioFxGraph.ts:225 Code clone group 4 (15 lines, 2 instances)
minor fallow/code-duplication packages/core/src/audio/audioFxGraph.ts:258 Code clone group 4 (15 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:11 Code clone group 5 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:22 Code clone group 5 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:391 Code clone group 6 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:478 Code clone group 6 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:491 Code clone group 7 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:513 Code clone group 8 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:544 Code clone group 8 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:576 Code clone group 9 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:611 Code clone group 9 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:775 Code clone group 10 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:783 Code clone group 11 (21 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:803 Code clone group 7 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:863 Code clone group 12 (6 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:873 Code clone group 13 (11 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:887 Code clone group 13 (11 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:892 Code clone group 12 (6 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:900 Code clone group 15 (14 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:900 Code clone group 14 (15 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:940 Code clone group 16 (24 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:940 Code clone group 17 (11 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:943 Code clone group 18 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:962 Code clone group 18 (14 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:977 Code clone group 15 (14 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:995 Code clone group 16 (24 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:995 Code clone group 17 (11 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:1034 Code clone group 17 (11 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:1067 Code clone group 10 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:1076 Code clone group 11 (21 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:1103 Code clone group 15 (14 lines, 3 instances)
minor fallow/code-duplication packages/core/src/runtime/media.test.ts:1103 Code clone group 14 (15 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/components/editor/PropertyPanelEmptyState.test.tsx:11 Code clone group 19 (12 lines, 7 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx:17 Code clone group 19 (12 lines, 7 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFlatLayoutSection.test.tsx:16 Code clone group 19 (12 lines, 7 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFlatPrimitives.test.tsx:16 Code clone group 19 (12 lines, 7 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFlatTextSection.test.tsx:13 Code clone group 19 (12 lines, 7 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFlatTextSection.test.tsx:13 Code clone group 20 (36 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFlatToggle.test.tsx:10 Code clone group 19 (12 lines, 7 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFxSection.test.tsx:14 Code clone group 20 (36 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFxSection.test.tsx:14 Code clone group 19 (12 lines, 7 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFxSection.test.tsx:89 Code clone group 21 (6 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelFxSection.test.tsx:201 Code clone group 21 (6 lines, 2 instances)
Health (1)
Severity Rule Location Description
minor fallow/high-crap-score packages/core/stubs/audio-fx-runtime-entry.ts:74 'render' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)

Generated by fallow.

vanceingalls and others added 2 commits August 4, 2026 14:41
The envelope was scheduled onto the transport's gain AudioParam, but the
runtime rewrites that gain every tick from `data-volume` and the GSAP-seeked
value — so it was erased within a frame. Volume automation was correct in the
render and inaudible while previewing.

The lane now feeds the per-tick path where the probed volume keyframes already
sit, checked ahead of them so the two cannot fight, and the transport no
longer schedules volume at all: one mechanism instead of two racing.

The cost is honest — in preview the level steps per tick rather than per
sample, exactly as the existing keyframe path does. The render still bakes it
into the PCM sample-accurately, and FX parameters are still scheduled on their
own AudioParams, since nothing rewrites those.

Parsed lanes are cached by attribute text: the runtime asks once per tick per
track, and parsing there would run the JSON parser 60 times a second for a
value that only changes on an edit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A structural edit — an effect added, removed, bypassed, or a filter's pole
count switched — was dropped. `buildFxChain`'s update reports false when the
change is not merely new values, and the attribute observer ignored that, so
the edit only took hold when the persisting write reloaded the composition.
That reload restarted every playing track, which is what was heard as the
audio chopping.

The graph is now swapped in place: the old effects are detached, the new ones
built and connected between the same source and gain, and any lanes
re-scheduled onto the new nodes. The source node is never touched, so playback
does not restart.

A track with no chain is watched too, rather than wired through and forgotten,
so adding its first effect is heard the same way. That means the function
always returns a disposer instead of null for the empty case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vanceingalls
vanceingalls force-pushed the wa-5-automation-runtime-fixes branch from f092e79 to ea66fad Compare August 4, 2026 21:43
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