mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 19:06:04 +00:00
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>
100 lines
4.0 KiB
TypeScript
100 lines
4.0 KiB
TypeScript
// fallow-ignore-file code-duplication
|
|
// fallow-ignore-file dead-code
|
|
import type { TimelineElement } from "../store/playerStore";
|
|
import type { TimelineMoveOperation } from "../../hooks/timelineMoveAdapter";
|
|
import type { BlockedTimelineEditIntent } from "./timelineEditing";
|
|
import type { PropertyGroupName } from "@hyperframes/core/gsap-parser";
|
|
import type { TimelineKeyframeTarget } from "./timelineKeyframeIdentity";
|
|
|
|
export interface TimelinePropertyGroupKeyframeToggle {
|
|
animationId: string;
|
|
propertyGroup: PropertyGroupName;
|
|
tweenPercentage: number;
|
|
properties: Record<string, number | string>;
|
|
remove: boolean;
|
|
}
|
|
|
|
/**
|
|
* Shared callback signatures for timeline editing operations.
|
|
* Used by NLELayout, Timeline, and any component that passes through
|
|
* the standard set of timeline mutation handlers.
|
|
*/
|
|
export interface TimelineDropCallbacks {
|
|
onFileDrop?: (
|
|
files: File[],
|
|
placement?: { start: number; track: number },
|
|
) => Promise<void> | void;
|
|
onAssetDrop?: (
|
|
assetPath: string,
|
|
placement: { start: number; track: number },
|
|
) => Promise<void> | void;
|
|
onBlockDrop?: (
|
|
blockName: string,
|
|
placement: { start: number; track: number },
|
|
) => Promise<void> | void;
|
|
onCompositionDrop?: (
|
|
sourcePath: string,
|
|
placement: { start: number; track: number },
|
|
) => Promise<void> | void;
|
|
}
|
|
|
|
export interface TimelineEditCallbacks {
|
|
onMoveElement?: (
|
|
element: TimelineElement,
|
|
updates: Pick<TimelineElement, "start" | "track">,
|
|
) => Promise<void> | void;
|
|
/** Atomic multi-clip move (single undo) for main-track ripple + track-insert.
|
|
* `coalesceKey` (drag-commit gesture id) merges the move history entry with a
|
|
* lane change's follow-up z-reorder entry into one undo step; `coalesceMs`
|
|
* widens that entry's fold window when a server round-trip separates the
|
|
* gesture's records (per-gesture-unique keys keep the fold gesture-scoped). */
|
|
onMoveElements?: (
|
|
edits: Array<{ element: TimelineElement; updates: Pick<TimelineElement, "start" | "track"> }>,
|
|
coalesceKey?: string,
|
|
operation?: TimelineMoveOperation,
|
|
coalesceMs?: number,
|
|
) => Promise<void> | void;
|
|
onResizeElement?: (
|
|
element: TimelineElement,
|
|
updates: Pick<TimelineElement, "start" | "duration" | "playbackStart">,
|
|
) => Promise<void> | void;
|
|
onResizeElements?: (
|
|
changes: Array<{
|
|
element: TimelineElement;
|
|
start: number;
|
|
duration: number;
|
|
playbackStart?: number;
|
|
}>,
|
|
options?: { coalesceKey?: string },
|
|
) => Promise<void> | void;
|
|
onToggleTrackHidden?: (track: number, hidden: boolean) => Promise<void> | void;
|
|
/** B7's bus strip: live-write the group's own attribute while dragging. */
|
|
onSetAudioGroupAttributeLive?: (groupId: string, attr: string, value: string | null) => void;
|
|
/** ...and persist one undo entry on release. */
|
|
onSetAudioGroupAttributeQuiet?: (
|
|
groupId: string,
|
|
attr: string,
|
|
value: string | null,
|
|
label: string,
|
|
) => Promise<void>;
|
|
onBlockedEditAttempt?: (element: TimelineElement, intent: BlockedTimelineEditIntent) => void;
|
|
onSplitElement?: (element: TimelineElement, splitTime: number) => Promise<void> | void;
|
|
onRazorSplit?: (element: TimelineElement, splitTime: number) => Promise<void> | void;
|
|
onRazorSplitAll?: (splitTime: number) => Promise<void> | void;
|
|
onDeleteKeyframe?: (elementId: string, keyframe: TimelineKeyframeTarget) => void;
|
|
onDeleteAllKeyframes?: (element: TimelineElement, animationId?: string) => void;
|
|
onMoveKeyframeToPlayhead?: (element: TimelineElement, keyframe: TimelineKeyframeTarget) => void;
|
|
/** Drag-to-retime: `keyframe` identifies the dragged keyframe (its percentage
|
|
* is clip-relative), `toClipPercentage` is the neighbour-clamped drop. */
|
|
onMoveKeyframe?: (
|
|
elementId: string,
|
|
keyframe: TimelineKeyframeTarget,
|
|
toClipPercentage: number,
|
|
) => Promise<boolean>;
|
|
onToggleKeyframeAtPlayhead?: (element: TimelineElement) => void;
|
|
onTogglePropertyGroupKeyframe?: (
|
|
element: TimelineElement,
|
|
target: TimelinePropertyGroupKeyframeToggle,
|
|
) => Promise<void> | void;
|
|
}
|