feat(core): route grouped audio through a group bus in preview - #3287
Open
vanceingalls wants to merge 1 commit into
Open
feat(core): route grouped audio through a group bus in preview#3287vanceingalls wants to merge 1 commit into
vanceingalls wants to merge 1 commit into
Conversation
An audio element carrying `data-audio-group` no longer lands its gain on the master bus directly — it feeds a per-group `GainNode` (built lazily on first use, one per group id) which itself feeds master, so members of the same group sum before the ear, ready for a group-level FX chain and volume/mute in later steps. An id with no matching `<hf-audio-group>` element still gets a plain, unprocessed bus rather than losing the track. The group's own chain and volume lane are wired through the same `attachElementFxChain`/`scheduleVolumeLane` every element already uses, against the group's clock — composition time (design doc §1.3), since a group has no `data-start` and a missing one parses as 0. The bus persists across `stopAll()` (mirroring `_masterGain`'s own lifecycle) so replaying a group does not rebuild its chain; only `destroy()` disposes it. Render is untouched — stays flat until B4; `audio-groups` is still a 0% canary so nothing ships this to a real composition without hand-authoring `data-audio-group`. Also: `audioGroupOf` (B1) crashed on any element lacking a real `tagName`/ `getAttribute` — exactly the shape of most `HTMLMediaElement` test doubles in this suite, including this file's own `mockEl`. Made it tolerant, same style as `readChain`'s existing guard in `runtime/audioFx.ts`. `schedulePlayback` was already 110 lines pre-existing before this diff; extracted `resolveDestination` and `handleSourceEnded` to shrink it to 92, then suppressed the remainder (inherently sequential graph wiring, not a decision tree) per the same precedent B2 used on `TimelineLogicalRow`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Aug 15, 2026
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.
Summary
Step B3 of the audio-mixer-groups execution runbook (
plans/audio-execution/B3-group-preview.md). Preview-engine-only: an audio element carryingdata-audio-groupnow feeds a per-groupGainNode(built lazily on first use) instead of landing on master directly, so grouped members sum before the ear and have a place for a group-level FX chain / volume / mute in later steps (B4-B7).attachElementFxChain/scheduleVolumeLanemachinery unchanged — just pointed at the<hf-audio-group>element — against composition time (design doc §1.3), since a group has nodata-startand a missing one parses as 0.<hf-audio-group>element still gets a flat, unprocessed bus (graceful degradation, same posture as B1'sresolveAudioGroups).stopAll()(mirrors_masterGain's own lifecycle: created lazily, onlydestroy()disposes it) so replaying a group doesn't rebuild its chain.audio-groupsis still a 0% canary.audioGroupOf: it calledel.tagName.toLowerCase()unconditionally, which crashes on the many{ muted: false } as HTMLMediaElementtest doubles used throughout this suite (no realtagName/getAttribute). Made it tolerant, mirroringreadChain's existing guard style inruntime/audioFx.ts.schedulePlaybackwas already 110 lines before this diff; extractedresolveDestinationandhandleSourceEnded(92 lines now), then suppressed the CRAP/complexity finding on the remainder — inherently sequential graph wiring, not a decision tree — per the same// fallow-ignore-next-line complexityprecedent B2 used onTimelineLogicalRow.Stack
Depends on (merge after all): #3286 (B2) → #3278 (B1) → #3277 (P2) → #3276 (P1) → #3275 (A2) → #3274 (A1).
Test plan
bun run buildcleanpackages/corefull suite: 2359/2359 (7 new group-routing tests inwebAudioTransport.test.ts)oxfmt/oxlintcleanhyperframes preview(needs a running dev server — left unchecked, same as B2's PR)🤖 Generated with Claude Code