/** * The label column beside a group's own automation lanes. * * The designs draw a group lane as `▤ Volume 0.42` on an accent rail — and * the rail is load-bearing, not decoration: "Scope is carried by colour, not by * depth — a lane the group owns has an accent rail and names the group; a * clip's lane is neutral." Two lanes both called Volume, doing entirely * different things, otherwise sit eight pixels apart with nothing to tell them * apart. * * The number is the value AT THE PLAYHEAD, not the stored seed: a readout that * showed the seed would stand still while the curve is audibly working. */ import { sampleAutomationLane } from "@hyperframes/core/audio-automation"; import { automationLaneLabelParts, elementAutomation, elementFxChain } from "./automationLaneData"; import { AUTOMATION_LANE_H } from "./automationLaneHeight"; import type { TimelineElement } from "../store/playerStore"; import { useLivePlayheadTime } from "../../hooks/useLivePlayheadTime"; export function TimelineGroupLaneLabels({ groupElement, groupLabel, top, columnWidth, gutterBackground, accentColor, }: { /** The group wearing a clip's shape — see `groupAutomationElement`. */ groupElement: TimelineElement; groupLabel: string; /** y of the first lane, matching the canvas slot's own offset. */ top: number; columnWidth: number; gutterBackground: string; accentColor: string; }) { // The LIVE playhead, not the row's `currentTime` prop — that one only moves // on seek, so the readout sat frozen while the curve was audibly working, // which is precisely the failure this number exists to prevent. const currentTime = useLivePlayheadTime(); const chain = elementFxChain(groupElement); const lanes = elementAutomation(groupElement).lanes; return ( <> {lanes.map((lane, index) => { const parts = automationLaneLabelParts(lane.target, chain); if (!parts) return null; // A group's clock is composition time (§1.3), so the playhead needs no // clip-local rebase here — unlike a clip's lane. const value = sampleAutomationLane(lane, currentTime); return (