From 45fa9c8e876c2456e9a3606e0a6e917d1ce1f179 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Sat, 15 Aug 2026 18:58:12 -0700 Subject: [PATCH] feat(studio,core): mute groups, and hear-only-this that cannot reach the export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B5: mute and solo, on groups and tracks (track mute already shipped by A2 — nothing to build there). Group mute — persisted as data-hidden on the element itself (never written onto members, per design doc §2.1's state-restoration warning). Studio action reuses B7's generic setAudioGroupAttribute (setQuiet/setLive split) rather than duplicating toggleTimelineTrackHidden's shape — same one-atomic-patch/one-undo-entry contract, already built for exactly this purpose. Render: B4 already drops every member of a data-hidden group (confirmed by a new audioMixer.test.ts case — no production change needed there). Preview: a dedicated muteGain node (groupInput -> [fx] -> muteGain -> output -> master) so a mute toggle never fights scheduleVolumeLane's ramps on the same param — the same hazard B7's volume fader was split out to avoid. Mid-playback toggles sync via a new syncAudioGroupMute pass in init.ts (a group carries no data-start, so it's invisible to the existing visibility-node query). Members of a muted group render the strikethrough label treatment (TimelineTrackPlainHeader's isGroupMuted, sourced from TimelineElement.audioGroupHidden) — display only, no attribute touched. Solo — "Hear only this": a new session-only store slice (audioSoloSlice, soloed: ReadonlySet of clip/group ids, never track numbers, never serialized). Predicate (isAudibleUnderSolo, packages/core/src/audioGroups.ts so both the store and the preview transport share one definition): an element is audible while any solo is active only if it or its own group is soloed. "Siblings, never ancestors" lives in the graph, not the predicate — solo gain is a per-element stage only; group buses are never attenuated by solo, so a soloed member's path through its group stays open by construction. Preview: a dedicated per-element soloGain in webAudioTransport.ts (parallel to the mute mechanics), pushed via window.__hf.setAudioSolo — a direct call, not an attribute write, so it can't ride the visibility-diff path mute uses. media.ts's HTMLMedia fallback folds the same predicate into its per-tick volume computation (the same seam A2 used for data-hidden). Half-lit group indicator (isGroupHalfLitUnderSolo) for "not soloed itself, but a member is". Exclusive-by-default toggle, ⌘/Ctrl-click to add/remove, TimelineSoloButton (⌗) beside mute on both track and group headers. Transport-bar banner ("Hearing only