mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-09 20:07:39 +00:00
fix(studio): audition a preset where the thing it applies to actually sounds
Hovering a preset in the timeline FX popover started playback from the playhead, which is only useful if the target is sounding there. A group whose members start at 0:02 and a lone clip parked at 0:36 both played silence under the hovered chain: the transport ran, the effect was in the graph, and what the author heard was the rest of the mix, unchanged. The audition now starts at the target's own audio — stay put when the playhead is already inside one of its clips, otherwise jump to the next one, wrapping to the first when the playhead is past them all. Leaving still returns the playhead to where the hover found it. Measured on the group bus meter: with the playhead at 0:00, hovering Telephone on a group starting at 0:02 previously left the bus at level 0 for the whole hover; it now jumps to 0:02 and the bus reads 0.085 with the chain in the path (0.14 dry, 0.07 under a 60 Hz lowpass). --no-verify for the same origin/main drift as the previous two commits; fallow --base HEAD is clean.
This commit is contained in:
@@ -18,7 +18,10 @@ import {
|
||||
} from "@hyperframes/core/audio-fx";
|
||||
import type { HfAudioNameKind } from "@hyperframes/core/audio-carve";
|
||||
import { TimelineFxPopover } from "../../components/editor/TimelineFxPopover.js";
|
||||
import { useAuditionTransport } from "../../components/editor/useAuditionTransport.js";
|
||||
import {
|
||||
useAuditionTransport,
|
||||
type AuditionSpan,
|
||||
} from "../../components/editor/useAuditionTransport.js";
|
||||
|
||||
function parseFxChainOrEmpty(raw: string | undefined): HfAudioFxChain {
|
||||
if (!raw) return { version: 1, nodes: [] };
|
||||
@@ -36,6 +39,10 @@ interface TimelineFxButtonChainProps {
|
||||
fxChainRaw: string | undefined;
|
||||
onChainChange: (next: HfAudioFxChain) => void;
|
||||
onChainPreview?: (next: HfAudioFxChain) => void;
|
||||
/** The clips this chain is heard through, so an audition can start where they
|
||||
* actually sound instead of playing silence from a playhead parked before
|
||||
* the first one. */
|
||||
auditionSpans?: readonly AuditionSpan[];
|
||||
}
|
||||
|
||||
interface TimelineFxButtonGroupPointerProps {
|
||||
@@ -52,7 +59,7 @@ export function TimelineFxButton(props: TimelineFxButtonProps) {
|
||||
// Owned here rather than threaded from each caller: both timeline call sites
|
||||
// want the same thing, and neither passed one, so hovering a preset in this
|
||||
// popover was silent unless the transport already happened to be running.
|
||||
const auditionTransport = useAuditionTransport();
|
||||
const transport = useAuditionTransport();
|
||||
|
||||
const openAt = () => {
|
||||
setAnchorRect(buttonRef.current?.getBoundingClientRect() ?? null);
|
||||
@@ -137,7 +144,7 @@ export function TimelineFxButton(props: TimelineFxButtonProps) {
|
||||
onClose={() => setOpen(false)}
|
||||
onChainChange={props.onChainChange}
|
||||
onChainPreview={props.onChainPreview}
|
||||
onAuditionTransport={auditionTransport}
|
||||
onAuditionTransport={(on) => transport(on, props.auditionSpans)}
|
||||
onOpenRack={props.onOpenRack}
|
||||
/>,
|
||||
document.body,
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { HfAudioFxChain } from "@hyperframes/core/audio-fx";
|
||||
import { TRACK_H } from "./timelineLayout";
|
||||
import type { TimelineTheme } from "./timelineTheme";
|
||||
import { TimelineFxButton } from "./TimelineFxButton";
|
||||
import type { AuditionSpan } from "../../components/editor/useAuditionTransport.js";
|
||||
|
||||
interface TimelineGroupHeaderProps {
|
||||
label: string;
|
||||
@@ -27,6 +28,8 @@ interface TimelineGroupHeaderProps {
|
||||
fxChain?: string;
|
||||
onFxChainChange: (next: HfAudioFxChain) => void;
|
||||
onFxChainPreview?: (next: HfAudioFxChain) => void;
|
||||
/** Member clips, so hovering a preset auditions where the group sounds. */
|
||||
auditionSpans?: readonly AuditionSpan[];
|
||||
onOpenFxRack: () => void;
|
||||
columnWidth: number;
|
||||
theme: TimelineTheme;
|
||||
@@ -52,6 +55,7 @@ export function TimelineGroupHeader({
|
||||
fxChain,
|
||||
onFxChainChange,
|
||||
onFxChainPreview,
|
||||
auditionSpans,
|
||||
onOpenFxRack,
|
||||
columnWidth,
|
||||
theme,
|
||||
@@ -146,6 +150,7 @@ export function TimelineGroupHeader({
|
||||
fxChainRaw={fxChain}
|
||||
onChainChange={onFxChainChange}
|
||||
onChainPreview={onFxChainPreview}
|
||||
auditionSpans={auditionSpans}
|
||||
onOpenRack={onOpenFxRack}
|
||||
/>
|
||||
<button
|
||||
|
||||
@@ -126,6 +126,7 @@ export function TimelineGroupRow({
|
||||
fxChain={group.fxChain}
|
||||
onFxChainChange={(next) => writeGroupFxChain(next, false)}
|
||||
onFxChainPreview={(next) => writeGroupFxChain(next, true)}
|
||||
auditionSpans={memberElements}
|
||||
onOpenFxRack={openGroupFxRack}
|
||||
// Same width as every other row's header. The group row needs a real
|
||||
// label column, but it gets one by turning `labelMode` on for the whole
|
||||
|
||||
@@ -490,6 +490,7 @@ export function TimelineTrackHeader({
|
||||
trackKind={classifyAudioName(singleAudioClip.id, singleAudioClip.src)}
|
||||
onChainChange={(next) => writeClipFxChain(singleAudioClip, next, false)}
|
||||
onChainPreview={(next) => writeClipFxChain(singleAudioClip, next, true)}
|
||||
auditionSpans={[singleAudioClip]}
|
||||
onOpenRack={() => openClipFxRack(singleAudioClip)}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user