mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
feat(studio,core): a volume and a living meter on the group row
B7: the group bus strip — droppable, and deliberately minimal per the casual-user design constraints (groups doc §5): a volume slider, a level bar that moves with the sound, and the words "Too loud" when it clips. No dB numbers, no peak-hold readout, no routing row. Transport (core): groupInput() now routes each group through input -> [FX chain or dry passthrough] -> output -> master, with one AnalyserNode per group tapped off `output` (post-FX, so the meter reads what the bus actually outputs) — fftSize 256, level not spectrum. groupLevel(groupId) returns RMS-ish level 0..1 + a clipped flag off a reused per-group buffer (no per-frame allocation), or null when the group is idle/unknown. The runtime posts group-levels messages only while playing, piggybacking the existing message channel rather than adding a new poll loop. Studio: groupLevels.ts is a plain pub-sub store (mirrors liveTime.ts's shape) fed by useTimelinePlayer's message handler via parseGroupLevelsMessage; useGroupLevel throttles re-renders to ~33ms. TimelineGroupBusStrip renders in the group row's own `∿` lane area (STRIP_H, already sized in B2's row-height pipeline) — drag writes live via onSetAudioGroupAttributeLive, release commits one undo entry via onSetAudioGroupAttributeQuiet (packages/studio/src/hooks/ timelineAudioGroupVolume.ts, extracted from timelineTrackVisibility.ts to stay under the 600-line cap; mirrors FxParamRow's live/commit split). "Too loud" holds for ~2s after the last clipped block, tracked in the component, not the transport. volumeByGroup mirrors labelByGroup in useTimelineTrackDerivations.ts so the strip's slider round-trips the group's own data-volume. Fixed two pre-existing group-routing tests in webAudioTransport.test.ts that hardcoded gain-node creation order/count — B7 inserts an extra `output` gain node between the group's input and master (for the meter to tap), which shifted node indices the tests asserted on directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6e7c189e14
commit
a11883e6d1
@@ -2140,6 +2140,25 @@ export function initSandboxRuntimeModular(): void {
|
||||
scheduleRootStageLayoutDiagnostics();
|
||||
};
|
||||
|
||||
/** One meter reading per group with an active member — polled from the
|
||||
* transport's analyser, not the DOM, so an idle group (never played, no
|
||||
* matching `<hf-audio-group>`) is simply absent rather than reported as
|
||||
* zero. Cheap when nothing is grouped: `groupIds()` is empty. */
|
||||
const postGroupLevels = () => {
|
||||
const groupIds = webAudio.groupIds();
|
||||
if (groupIds.length === 0) return;
|
||||
const levels = groupIds
|
||||
.map((groupId) => {
|
||||
const reading = webAudio.groupLevel(groupId);
|
||||
return reading ? { groupId, ...reading } : null;
|
||||
})
|
||||
.filter(
|
||||
(entry): entry is { groupId: string; level: number; clipped: boolean } => entry !== null,
|
||||
);
|
||||
if (levels.length === 0) return;
|
||||
postRuntimeMessage({ source: "hf-preview", type: "group-levels", levels });
|
||||
};
|
||||
|
||||
const finitePositiveDuration = (value: number | null | undefined): number =>
|
||||
typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
||||
|
||||
@@ -2897,6 +2916,9 @@ export function initSandboxRuntimeModular(): void {
|
||||
if (transportTickCount % 30 === 0) {
|
||||
bindMediaMetadataListeners();
|
||||
}
|
||||
if (clock.isPlaying()) {
|
||||
postGroupLevels();
|
||||
}
|
||||
|
||||
// Sync clock duration with the resolved timeline each tick (catches async
|
||||
// rebinds, live data-duration edits). Never shrink while playing — transient
|
||||
|
||||
Reference in New Issue
Block a user