@waveform-playlist/browser 15.2.0 → 15.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx","../src/WaveformPlaylistContext.tsx","../src/playout/resolvePlayoutAdapter.ts","../src/waveformDataLoader.ts","../src/soundFontSync.ts","../src/hooks/useTimeFormat.ts","../src/hooks/useZoomControls.ts","../src/hooks/useMasterVolume.ts","../src/hooks/useSelectionState.ts","../src/hooks/useLoopState.ts","../src/hooks/useSelectedTrack.ts","../src/hooks/useUndoState.ts","../src/hooks/useClipDragHandlers.ts","../src/utils/boundaryTrim.ts","../src/hooks/useAnnotationDragHandlers.ts","../src/hooks/useDragSensors.ts","../src/hooks/useClipSplitting.ts","../src/hooks/useKeyboardShortcuts.ts","../src/hooks/usePlaybackShortcuts.ts","../src/hooks/useAnnotationKeyboardControls.ts","../src/hooks/useAnimationFrameLoop.ts","../src/hooks/useWaveformDataCache.ts","../src/workers/peaksWorker.ts","../src/MediaElementPlaylistContext.tsx","../src/playout/resolveMediaElementPlayout.ts","../src/components/PlaybackControls.tsx","../src/components/ZoomControls.tsx","../src/components/ContextualControls.tsx","../src/AnnotationIntegrationContext.tsx","../src/components/AnnotationControls.tsx","../src/contexts/ClipInteractionContext.tsx","../src/components/PlaylistVisualization.tsx","../src/components/AnimatedPlayhead.tsx","../src/components/ChannelWithProgress.tsx","../src/SpectrogramIntegrationContext.tsx","../src/components/PlaylistAnnotationList.tsx","../src/components/Waveform.tsx","../src/components/MediaElementPlaylist.tsx","../src/plugins/noDropAnimationPlugins.ts","../src/components/AnimatedMediaElementPlayhead.tsx","../src/components/ChannelWithMediaElementProgress.tsx","../src/components/MediaElementAnnotationList.tsx","../src/components/MediaElementWaveform.tsx","../src/components/KeyboardShortcuts.tsx","../src/components/ClipInteractionProvider.tsx","../src/modifiers/ClipCollisionModifier.ts","../src/modifiers/SnapToGridModifier.ts"],"sourcesContent":["// Export types from playout\nexport type { EffectsFunction, TrackEffectsFunction } from '@waveform-playlist/playout';\n\n// Export new flexible/headless API\nexport {\n WaveformPlaylistProvider,\n usePlaybackAnimation,\n usePlaylistState,\n usePlaylistControls,\n usePlaylistData,\n usePlaylistDataOptional,\n} from './WaveformPlaylistContext';\nexport type { WaveformTrack, TrackState, FrameData } from './WaveformPlaylistContext';\n\n// Export MediaElement-based provider (single-track with playback rate control)\nexport {\n MediaElementPlaylistProvider,\n useMediaElementAnimation,\n useMediaElementState,\n useMediaElementControls,\n useMediaElementData,\n} from './MediaElementPlaylistContext';\nexport type {\n MediaElementTrackConfig,\n MediaElementAnimationContextValue,\n MediaElementStateContextValue,\n MediaElementControlsContextValue,\n MediaElementDataContextValue,\n} from './MediaElementPlaylistContext';\nexport {\n useClipDragHandlers,\n useAnnotationDragHandlers,\n useAnnotationKeyboardControls,\n useDragSensors,\n useClipSplitting,\n useKeyboardShortcuts,\n getShortcutLabel,\n usePlaybackShortcuts,\n useZoomControls,\n useTimeFormat,\n useMasterVolume,\n} from './hooks';\nexport type {\n UsePlaybackShortcutsOptions,\n UsePlaybackShortcutsReturn,\n ZoomControls,\n TimeFormatControls,\n MasterVolumeControls,\n} from './hooks';\n\nexport {\n PlayButton,\n PauseButton,\n StopButton,\n RewindButton,\n FastForwardButton,\n SkipBackwardButton,\n SkipForwardButton,\n LoopButton,\n SetLoopRegionButton,\n ClearAllButton,\n ZoomInButton,\n ZoomOutButton,\n MasterVolumeControl,\n TimeFormatSelect,\n AudioPosition,\n SelectionTimeInputs,\n AutomaticScrollCheckbox,\n ContinuousPlayCheckbox,\n LinkEndpointsCheckbox,\n EditableCheckbox,\n DownloadAnnotationsButton,\n Waveform,\n MediaElementWaveform,\n MediaElementPlaylist,\n MediaElementAnnotationList,\n PlaylistVisualization,\n PlaylistAnnotationList,\n KeyboardShortcuts,\n} from './components';\nexport type { KeyboardShortcutsProps } from './components/KeyboardShortcuts';\nexport type { ClearAllButtonProps } from './components/PlaybackControls';\nexport type { WaveformProps } from './components/Waveform';\nexport type { MediaElementWaveformProps } from './components/MediaElementWaveform';\nexport type { MediaElementPlaylistProps } from './components/MediaElementPlaylist';\nexport type { MediaElementAnnotationListProps } from './components/MediaElementAnnotationList';\nexport type { PlaylistVisualizationProps } from './components/PlaylistVisualization';\nexport type { PlaylistAnnotationListProps } from './components/PlaylistAnnotationList';\n\n// Re-export TimeFormat type from ui-components for convenience\nexport type { TimeFormat } from '@waveform-playlist/ui-components';\n\n// Re-export core types for convenience\nexport type { ClipTrack, AudioClip, Fade, AnnotationData } from '@waveform-playlist/core';\n\n// Export annotation callback types for Waveform components\nexport type { GetAnnotationBoxLabelFn, OnAnnotationUpdateFn } from './types/annotations';\n\n// Annotation integration context (for optional annotation support)\nexport {\n AnnotationIntegrationProvider,\n useAnnotationIntegration,\n} from './AnnotationIntegrationContext';\nexport type { AnnotationIntegration } from './AnnotationIntegrationContext';\n\n// Spectrogram integration context (for optional spectrogram support)\nexport {\n SpectrogramIntegrationProvider,\n useSpectrogramIntegration,\n} from './SpectrogramIntegrationContext';\nexport type {\n SpectrogramIntegration,\n SpectrogramCanvasRegistration,\n} from './SpectrogramIntegrationContext';\n\n// Export ClipInteractionProvider for encapsulated drag/trim/snap/collision setup\nexport { ClipInteractionProvider } from './components/ClipInteractionProvider';\nexport type { ClipInteractionProviderProps } from './components/ClipInteractionProvider';\nexport { useClipInteractionEnabled } from './contexts/ClipInteractionContext';\n\n// Export modifiers and plugins for drag-and-drop\nexport { ClipCollisionModifier } from './modifiers/ClipCollisionModifier';\nexport { SnapToGridModifier } from './modifiers/SnapToGridModifier';\nexport { noDropAnimationPlugins } from './plugins/noDropAnimationPlugins';\n\n// Export waveform-data.js utilities\nexport {\n loadWaveformData,\n waveformDataToPeaks,\n loadPeaksFromWaveformData,\n getWaveformDataMetadata,\n} from './waveformDataLoader';\n","import React, {\n createContext,\n useContext,\n useState,\n useEffect,\n useRef,\n useCallback,\n useMemo,\n type ReactNode,\n} from 'react';\nimport { ThemeProvider } from 'styled-components';\nimport type {\n EffectsFunction,\n TrackEffectsFunction,\n SoundFontCache,\n} from '@waveform-playlist/playout';\nimport { resolvePlayoutAdapter } from './playout/resolvePlayoutAdapter';\nimport { PlaylistEngine, type EngineState, type PlayoutAdapter } from '@waveform-playlist/engine';\nimport {\n type ClipTrack,\n type Fade,\n type AnnotationAction,\n type MidiNoteData,\n} from '@waveform-playlist/core';\nimport {\n type TimeFormat,\n type WaveformPlaylistTheme,\n defaultTheme,\n} from '@waveform-playlist/ui-components';\nimport { extractPeaksFromWaveformDataFull } from './waveformDataLoader';\nimport { syncSoundFontCacheToAdapter } from './soundFontSync';\nimport type WaveformData from 'waveform-data';\nimport type { PeakData } from '@waveform-playlist/core';\nimport type { AnnotationData } from '@waveform-playlist/core';\nimport {\n useTimeFormat,\n useZoomControls,\n useMasterVolume,\n useSelectionState,\n useLoopState,\n useSelectedTrack,\n useUndoState,\n useAnimationFrameLoop,\n useWaveformDataCache,\n} from './hooks';\n\n// Types\nexport interface ClipPeaks {\n clipId: string;\n trackName: string;\n peaks: PeakData;\n startSample: number;\n durationSamples: number;\n fadeIn?: Fade;\n fadeOut?: Fade;\n midiNotes?: MidiNoteData[];\n sampleRate?: number;\n offsetSamples?: number;\n}\n\nexport type TrackClipPeaks = ClipPeaks[];\n\n// Legacy WaveformTrack type - kept for reference but deprecated\n// @deprecated Use ClipTrack from @waveform-playlist/core instead\nexport interface WaveformTrack {\n src: string | AudioBuffer;\n name?: string;\n effects?: TrackEffectsFunction;\n}\n\nexport interface TrackState {\n name: string;\n muted: boolean;\n soloed: boolean;\n volume: number;\n pan: number;\n}\n\n// Split contexts for performance optimization\n// Animation context contains playback state and timing refs — no per-frame state updates\n\n/** Per-frame data passed to registered animation callbacks. */\nexport interface FrameData {\n /** Raw engine time (for state/logic — NOT for visual positioning). */\n readonly time: number;\n /** Visually-aligned time for DOM positioning: engine.getAudibleTime() while\n * playing (matches speaker output), raw time when resting. */\n readonly visualTime: number;\n readonly sampleRate: number;\n readonly samplesPerPixel: number;\n}\n\nexport interface PlaybackAnimationContextValue {\n isPlaying: boolean;\n currentTime: number;\n currentTimeRef: React.RefObject<number>;\n /**\n * Visually-aligned playback time (engine.getAudibleTime() while playing;\n * raw resting time otherwise). Kept current by the animation loop during playback\n * and by pause/seek/stop paths when stopped. Read from this for any visual\n * positioning that should match the audible output.\n */\n visualTimeRef: React.RefObject<number>;\n // Refs for direct time calculation in animated components (avoids timing drift)\n playbackStartTimeRef: React.RefObject<number>; // context.currentTime when playback started\n audioStartPositionRef: React.RefObject<number>; // Audio position when playback started\n /** Returns raw playback time from engine (auto-wraps at loop boundaries). */\n getPlaybackTime: () => number;\n /** Current time of the adapter's AudioContext, in seconds. */\n getAudioContextTime: () => number;\n /**\n * Returns the current adapter scheduler lookahead (Tone ~0.1s, native 0).\n * Use this for any audible-latency calculation that must match the playhead\n * (e.g., recording live-preview peak slicing).\n */\n getLookAhead: () => number;\n /**\n * Returns the adapter AudioContext's output latency in seconds.\n * Use this for audible-latency calculations (e.g., recording live-preview peak slicing).\n */\n getOutputLatency: () => number;\n /** Register a per-frame callback driven by the single animation loop. */\n registerFrameCallback: (id: string, cb: (data: FrameData) => void) => void;\n /** Unregister a per-frame callback. */\n unregisterFrameCallback: (id: string) => void;\n}\n\nexport interface PlaylistStateContextValue {\n continuousPlay: boolean;\n linkEndpoints: boolean;\n annotationsEditable: boolean;\n isAutomaticScroll: boolean;\n isLoopEnabled: boolean;\n annotations: AnnotationData[];\n activeAnnotationId: string | null;\n selectionStart: number;\n selectionEnd: number;\n selectedTrackId: string | null; // ID of currently selected track for editing operations\n // Loop region (separate from selection) - Audacity-style loop points\n loopStart: number;\n loopEnd: number;\n /** Whether playback continues past the end of loaded audio */\n indefinitePlayback: boolean;\n /** Whether undo is available */\n canUndo: boolean;\n /** Whether redo is available */\n canRedo: boolean;\n}\n\nexport interface PlaylistControlsContextValue {\n // Playback controls\n play: (startTime?: number, playDuration?: number) => Promise<void>;\n pause: () => void;\n stop: () => void;\n seekTo: (time: number) => void;\n setCurrentTime: (time: number) => void;\n\n // Track controls\n setTrackMute: (trackIndex: number, muted: boolean) => void;\n setTrackSolo: (trackIndex: number, soloed: boolean) => void;\n setTrackVolume: (trackIndex: number, volume: number) => void;\n setTrackPan: (trackIndex: number, pan: number) => void;\n\n // Selection\n setSelection: (start: number, end: number) => void;\n setSelectedTrackId: (trackId: string | null) => void;\n\n // Time format\n setTimeFormat: (format: TimeFormat) => void;\n formatTime: (seconds: number) => string;\n\n // Zoom\n zoomIn: () => void;\n zoomOut: () => void;\n\n // Master volume\n setMasterVolume: (volume: number) => void;\n\n // Automatic scroll\n setAutomaticScroll: (enabled: boolean) => void;\n setScrollContainer: (element: HTMLDivElement | null) => void;\n scrollContainerRef: React.RefObject<HTMLDivElement | null>;\n\n // Annotation controls\n setContinuousPlay: (enabled: boolean) => void;\n setLinkEndpoints: (enabled: boolean) => void;\n setAnnotationsEditable: (enabled: boolean) => void;\n setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>>;\n setActiveAnnotationId: (id: string | null) => void;\n\n // Loop controls\n setLoopEnabled: (enabled: boolean) => void;\n setLoopRegion: (start: number, end: number) => void;\n setLoopRegionFromSelection: () => void;\n clearLoopRegion: () => void;\n\n // Undo/redo\n undo: () => void;\n redo: () => void;\n}\n\nexport interface PlaylistDataContextValue {\n duration: number;\n audioBuffers: AudioBuffer[];\n peaksDataArray: TrackClipPeaks[]; // Array of tracks, each containing array of clip peaks\n trackStates: TrackState[];\n tracks: ClipTrack[]; // Original tracks array with IDs\n sampleRate: number;\n waveHeight: number;\n timeScaleHeight: number;\n minimumPlaylistHeight: number;\n controls: { show: boolean; width: number };\n playoutRef: React.RefObject<PlaylistEngine | null>;\n samplesPerPixel: number;\n timeFormat: TimeFormat;\n masterVolume: number;\n canZoomIn: boolean;\n canZoomOut: boolean;\n barWidth: number;\n barGap: number;\n /** Draw bars with pill-shaped rounded caps (radius barWidth/2). */\n roundedBars: boolean;\n /** Width in pixels of progress bars. Defaults to barWidth + barGap (fills gaps). */\n progressBarWidth: number;\n /** Whether the playlist has finished loading all tracks */\n isReady: boolean;\n /** Whether tracks are rendered in mono mode */\n mono: boolean;\n /** Ref set by useClipDragHandlers during boundary trim drags.\n * When true, loadAudio skips engine rebuild — visual updates flow via React state only. */\n isDraggingRef: React.MutableRefObject<boolean>;\n onTracksChange: ((tracks: ClipTrack[]) => void) | undefined;\n}\n\n// Create the 4 separate contexts\nconst PlaybackAnimationContext = createContext<PlaybackAnimationContextValue | null>(null);\nconst PlaylistStateContext = createContext<PlaylistStateContextValue | null>(null);\nconst PlaylistControlsContext = createContext<PlaylistControlsContextValue | null>(null);\nconst PlaylistDataContext = createContext<PlaylistDataContextValue | null>(null);\n\nexport interface WaveformPlaylistProviderProps {\n tracks: ClipTrack[]; // Updated to use clip-based model\n timescale?: boolean;\n mono?: boolean;\n waveHeight?: number;\n samplesPerPixel?: number;\n zoomLevels?: number[]; // Array of zoom levels in samples per pixel (lower = more zoomed in)\n automaticScroll?: boolean;\n theme?: Partial<WaveformPlaylistTheme>;\n controls?: {\n show: boolean;\n width: number;\n };\n annotationList?: {\n annotations?: AnnotationData[];\n editable?: boolean;\n isContinuousPlay?: boolean;\n linkEndpoints?: boolean;\n controls?: AnnotationAction[];\n };\n effects?: EffectsFunction;\n onReady?: () => void;\n /** Called when audio/engine initialization fails (e.g. a missing optional peer,\n * a throwing `createAdapter`, or an invalid `zoomLevels`/`samplesPerPixel`). The\n * provider already logs the error; use this to surface it in your UI. */\n onError?: (err: Error) => void;\n /** @deprecated Use onAnnotationsChange instead */\n onAnnotationUpdate?: (annotations: AnnotationData[]) => void;\n /** Callback when annotations are changed (drag, edit, etc.) */\n onAnnotationsChange?: (annotations: AnnotationData[]) => void;\n /** Width in pixels of waveform bars. Default: 1 */\n barWidth?: number;\n /** Spacing in pixels between waveform bars. Default: 0 */\n barGap?: number;\n /** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */\n roundedBars?: boolean;\n /** Width in pixels of progress bars. Default: barWidth + barGap (fills gaps). */\n progressBarWidth?: number;\n /** Callback when engine clip operations (move, trim, split) change tracks.\n * The provider calls this so the parent can update its tracks state without\n * triggering a full engine rebuild.\n *\n * **Important:** The parent must pass the received `tracks` reference back as\n * the `tracks` prop (i.e. `setState(tracks)`). The provider uses reference\n * identity (`tracks === engineTracksRef.current`) to detect engine-originated\n * updates and skip the expensive `loadAudio` rebuild. */\n onTracksChange?: (tracks: ClipTrack[]) => void;\n /** SoundFont cache for sample-based MIDI playback. When provided, MIDI clips\n * use SoundFont samples instead of PolySynth synthesis. */\n soundFontCache?: SoundFontCache;\n /** When true, tracks render visually but the engine build is deferred.\n * Use this during progressive loading to avoid rebuilding the engine for\n * each track — flip to false when all tracks are ready for a single build. */\n deferEngineRebuild?: boolean;\n /** Disable automatic stop when the cursor reaches the end of the longest\n * track. Useful for DAW-style recording beyond existing audio. */\n indefinitePlayback?: boolean;\n /** Desired AudioContext sample rate. Creates a cross-browser AudioContext at\n * this rate via standardized-audio-context. Pre-computed peaks (.dat files)\n * render instantly when they match. On mismatch, falls back to worker. */\n sampleRate?: number;\n /** Factory for a custom PlayoutAdapter. When omitted, the Tone.js engine\n * (@waveform-playlist/playout) is dynamically imported — so a custom adapter\n * lets a consumer use neither @waveform-playlist/playout nor tone. Called once\n * per engine rebuild; the provider owns and disposes the returned instance.\n * Pass a stable reference (module-level or useCallback) — it is read directly\n * inside the curated `loadAudio` effect, not via its dependency array. */\n createAdapter?: () => PlayoutAdapter;\n children: ReactNode;\n}\n\nexport const WaveformPlaylistProvider: React.FC<WaveformPlaylistProviderProps> = ({\n tracks,\n timescale = false,\n mono = false,\n waveHeight = 80,\n samplesPerPixel: initialSamplesPerPixel = 1024,\n zoomLevels,\n automaticScroll = false,\n theme: userTheme,\n controls = { show: false, width: 0 },\n annotationList,\n effects,\n onReady,\n onError,\n onAnnotationUpdate: _onAnnotationUpdate,\n onAnnotationsChange,\n barWidth = 1,\n barGap = 0,\n roundedBars = false,\n progressBarWidth: progressBarWidthProp,\n onTracksChange,\n soundFontCache,\n deferEngineRebuild = false,\n indefinitePlayback = false,\n sampleRate: sampleRateProp,\n createAdapter,\n children,\n}) => {\n // Default progressBarWidth to barWidth + barGap (fills gaps)\n const progressBarWidth = progressBarWidthProp ?? barWidth + barGap;\n\n // Ref for animation loop access (avoids adding prop to useCallback deps)\n const indefinitePlaybackRef = useRef(indefinitePlayback);\n indefinitePlaybackRef.current = indefinitePlayback;\n\n // Stabilize zoomLevels reference — inline arrays (e.g. zoomLevels={[256, 512]})\n // create a new reference every render, which would trigger engine rebuild via\n // loadAudio deps. Content-based comparison avoids spurious rebuilds.\n const stableZoomLevels = useMemo(\n () => zoomLevels,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [zoomLevels?.join(',')]\n );\n\n // Annotations are derived from prop (single source of truth in parent)\n // In v6, annotations must be pre-parsed (numeric start/end). Use parseAeneas() from @waveform-playlist/annotations before passing.\n const annotations = useMemo(() => {\n if (!annotationList?.annotations) return [];\n if (process.env.NODE_ENV !== 'production' && annotationList.annotations.length > 0) {\n const first = annotationList.annotations[0] as unknown as Record<string, unknown>;\n if (typeof first.start !== 'number' || typeof first.end !== 'number') {\n console.error(\n '[waveform-playlist] Annotations must have numeric start/end values. ' +\n 'In v6, use parseAeneas() from @waveform-playlist/annotations before passing annotations. ' +\n 'Received start type: ' +\n typeof first.start\n );\n return [];\n }\n }\n return annotationList.annotations;\n }, [annotationList?.annotations]);\n\n // Ref for animation loop (avoids restarting loop on annotation change)\n const annotationsRef = useRef<AnnotationData[]>(annotations);\n annotationsRef.current = annotations;\n\n // State\n const [activeAnnotationId, setActiveAnnotationIdState] = useState<string | null>(null);\n const [isPlaying, setIsPlaying] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [audioBuffers, setAudioBuffers] = useState<AudioBuffer[]>([]);\n const [peaksDataArray, setPeaksDataArray] = useState<TrackClipPeaks[]>([]); // Updated for clip-based peaks\n const [trackStates, setTrackStates] = useState<TrackState[]>([]);\n const [isAutomaticScroll, setIsAutomaticScroll] = useState(automaticScroll);\n const [continuousPlay, setContinuousPlayState] = useState(\n annotationList?.isContinuousPlay ?? false\n );\n const [linkEndpoints, setLinkEndpoints] = useState(annotationList?.linkEndpoints ?? false);\n const [annotationsEditable, setAnnotationsEditable] = useState(annotationList?.editable ?? false);\n const [isReady, setIsReady] = useState(false);\n\n // Refs\n // Engine owns selection, loop, selectedTrackId, zoom, and masterVolume state.\n // React subscribes to engine statechange and mirrors into useState/refs.\n // Playback timing (currentTime, isPlaying) remains in React for animation loop.\n const engineRef = useRef<PlaylistEngine | null>(null);\n const adapterRef = useRef<PlayoutAdapter | null>(null);\n const audioInitializedRef = useRef<boolean>(false);\n const isPlayingRef = useRef<boolean>(false);\n isPlayingRef.current = isPlaying;\n const playStartPositionRef = useRef<number>(0);\n const currentTimeRef = useRef<number>(0);\n // Visually-aligned playback time (see toVisualTime / getAudibleTime). The\n // animation loop updates this per frame. Pause/seek/stop paths sync it\n // explicitly so the static playhead reflects the commanded resting position.\n const visualTimeRef = useRef<number>(0);\n const tracksRef = useRef<ClipTrack[]>(tracks);\n const soundFontCacheRef = useRef(soundFontCache);\n soundFontCacheRef.current = soundFontCache;\n const trackStatesRef = useRef<TrackState[]>(trackStates);\n const playbackStartTimeRef = useRef<number>(0); // context.currentTime when playback started\n const audioStartPositionRef = useRef<number>(0); // Audio position when playback started\n const playbackEndTimeRef = useRef<number | null>(null); // Audio position where playback should stop (for selections)\n const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n const isAutomaticScrollRef = useRef<boolean>(false);\n // Animation frame callback registry — components register per-frame DOM update\n // callbacks instead of running their own rAF loops. Single loop drives all.\n const frameCallbacksRef = useRef<Map<string, (data: FrameData) => void>>(new Map());\n const continuousPlayRef = useRef<boolean>(annotationList?.isContinuousPlay ?? false);\n const activeAnnotationIdRef = useRef<string | null>(null);\n // Engine clip operations guard: tracks reference from the last engine statechange.\n // When engine methods (moveClip, trimClip, splitClip) mutate tracks, the statechange\n // handler stores state.tracks here and calls onTracksChange with the same reference.\n // loadAudio checks tracks === engineTracksRef.current to skip the full rebuild.\n const engineTracksRef = useRef<ClipTrack[] | null>(null);\n // Monotonic counter from engine.getState().tracksVersion — used to detect\n // track mutations (move/trim/split/add/remove) vs other statechange events.\n const lastTracksVersionRef = useRef(0);\n // Flag set during render to prevent the previous effect cleanup from disposing the\n // engine when we're skipping the rebuild. React runs previous cleanup before the\n // current effect body, so the flag must be set during render (which precedes cleanup).\n const skipEngineDisposeRef = useRef(false);\n // Set by useClipDragHandlers during boundary trim drags. When true,\n // loadAudio skips the full engine rebuild — visual updates flow via React\n // state only. On drag end, engine.trimClip() commits the final delta.\n const isDraggingRef = useRef(false);\n // Snapshot of tracks from the previous loadAudio run, used to detect\n // additive-only changes (new tracks appended, existing unchanged).\n const prevTracksRef = useRef<ClipTrack[]>([]);\n // Provider-level ref for scroll-position math and animation loop pixel\n // calculation. Distinct from useZoomControls's internal ref (statechange guard).\n const samplesPerPixelRef = useRef<number>(initialSamplesPerPixel);\n // Provisional sample rate until the adapter (which owns the AudioContext) is\n // created in loadAudio. Reconciled from adapter.audioContext.sampleRate there.\n // (#510: avoids a static @waveform-playlist/playout import at mount.)\n const [initialSampleRate] = useState<number>(() => sampleRateProp ?? 48000);\n const sampleRateRef = useRef<number>(initialSampleRate);\n\n // Custom hooks — engine-owned state delegated to hooks with onEngineState() pattern\n const { timeFormat, setTimeFormat, formatTime } = useTimeFormat();\n const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });\n const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;\n const volume = useMasterVolume({ engineRef, initialVolume: 1.0 });\n const {\n masterVolume,\n setMasterVolume,\n masterVolumeRef,\n onEngineState: onVolumeEngineState,\n } = volume;\n const {\n selectionStart,\n selectionEnd,\n setSelection: setSelectionEngine,\n selectionStartRef,\n selectionEndRef,\n onEngineState: onSelectionEngineState,\n } = useSelectionState({ engineRef });\n const {\n isLoopEnabled,\n loopStart,\n loopEnd,\n setLoopEnabled,\n setLoopRegion,\n clearLoopRegion,\n isLoopEnabledRef,\n loopStartRef,\n loopEndRef,\n onEngineState: onLoopEngineState,\n } = useLoopState({ engineRef });\n const {\n selectedTrackId,\n setSelectedTrackId: setSelectedTrackIdControl,\n onEngineState: onSelectedTrackEngineState,\n selectedTrackIdRef,\n } = useSelectedTrack({ engineRef });\n const {\n canUndo,\n canRedo,\n undo,\n redo,\n onEngineState: onUndoEngineState,\n } = useUndoState({ engineRef });\n const { animationFrameRef, startAnimationFrameLoop, stopAnimationFrameLoop } =\n useAnimationFrameLoop();\n\n // Worker-based WaveformData cache for fast zoom resampling\n const baseScale = useMemo(\n () => Math.min(...(stableZoomLevels ?? [256, 512, 1024, 2048, 4096, 8192])),\n [stableZoomLevels]\n );\n const { cache: waveformDataCache } = useWaveformDataCache(tracks, baseScale);\n\n // Custom setter for continuousPlay that updates BOTH state and ref synchronously\n // This ensures the ref is updated immediately, before the animation loop can read it\n const setContinuousPlay = useCallback((value: boolean) => {\n continuousPlayRef.current = value; // Update ref synchronously\n setContinuousPlayState(value); // Update state (triggers re-render)\n }, []);\n\n // Custom setter for activeAnnotationId that updates BOTH state and ref synchronously\n const setActiveAnnotationId = useCallback((value: string | null) => {\n activeAnnotationIdRef.current = value; // Update ref synchronously\n setActiveAnnotationIdState(value); // Update state (triggers re-render)\n }, []);\n\n // Cross-hook concern: reads selection refs to set loop region\n const setLoopRegionFromSelection = useCallback(() => {\n const start = selectionStartRef.current ?? 0;\n const end = selectionEndRef.current ?? 0;\n if (start !== end && end > start) {\n setLoopRegion(start, end);\n }\n }, [setLoopRegion, selectionStartRef, selectionEndRef]);\n\n // Keep refs in sync with state\n useEffect(() => {\n isAutomaticScrollRef.current = isAutomaticScroll;\n }, [isAutomaticScroll]);\n\n useEffect(() => {\n trackStatesRef.current = trackStates;\n }, [trackStates]);\n\n tracksRef.current = tracks;\n\n // Render-phase guard: detect when tracks prop came from an engine clip operation.\n // Must be set before effects run so the previous effect cleanup can skip disposal.\n // Also skip disposal during active boundary trim drags — onDragMove updates React\n // tracks per-frame, triggering effect re-runs whose cleanup would dispose the engine.\n const isEngineTracks = tracks === engineTracksRef.current;\n\n // Detect additive-only track changes: new tracks appended, existing tracks unchanged.\n // Uses reference equality on individual tracks — if any existing track object changed\n // (e.g., clip added to it), this is false and triggers a full rebuild.\n const prevTracks = prevTracksRef.current;\n const isIncrementalAdd =\n engineRef.current !== null &&\n tracks.length > prevTracks.length &&\n prevTracks.every((pt) => {\n const current = tracks.find((t) => t.id === pt.id);\n return current === pt; // reference equality — existing tracks must be untouched\n });\n // Note: prevTracks may be empty here (e.g. recording's first track on a\n // persistent empty-timeline engine) — that's a valid 0→1 incremental add, so\n // it must NOT require prevTracks.length > 0. The engineRef !== null guard keeps\n // it correct: a lazy/no-engine consumer still takes the full build path.\n\n skipEngineDisposeRef.current = isEngineTracks || isDraggingRef.current || isIncrementalAdd;\n\n // Adjust scroll position proportionally when zoom changes\n useEffect(() => {\n if (!scrollContainerRef.current || duration === 0) return;\n\n const container = scrollContainerRef.current;\n const oldSamplesPerPixel = samplesPerPixelRef.current;\n const newSamplesPerPixel = samplesPerPixel;\n\n if (oldSamplesPerPixel === newSamplesPerPixel) return;\n\n // Calculate the current center time in the viewport\n const containerWidth = container.clientWidth;\n const currentScrollLeft = container.scrollLeft;\n const centerPixel = currentScrollLeft + containerWidth / 2;\n const sr = sampleRateRef.current;\n const centerTime = (centerPixel * oldSamplesPerPixel) / sr;\n\n // Calculate new scroll position to keep the same center time\n const newCenterPixel = (centerTime * sr) / newSamplesPerPixel;\n const newScrollLeft = Math.max(0, newCenterPixel - containerWidth / 2);\n\n container.scrollLeft = newScrollLeft;\n samplesPerPixelRef.current = newSamplesPerPixel;\n }, [samplesPerPixel, duration]);\n\n // Track pending playback resume after tracks change\n const pendingResumeRef = useRef<{ position: number } | null>(null);\n\n // Load audio from clips (only when tracks change)\n useEffect(() => {\n // Guard: skip full rebuild when tracks came from an engine clip operation\n // (moveClip, trimClip, splitClip). The engine and adapter already have the\n // correct tracks — we just need to update duration for the timeline.\n // Also skip during active boundary trim drags — visual updates flow via\n // React state only; engine.trimClip() commits the final delta on drag end.\n if (isEngineTracks || isDraggingRef.current) {\n if (isEngineTracks) {\n engineTracksRef.current = null;\n }\n\n // Recalculate duration from updated clip positions\n let maxDuration = 0;\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n const clipEnd = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n maxDuration = Math.max(maxDuration, clipEnd);\n });\n });\n setDuration(maxDuration);\n prevTracksRef.current = tracks;\n return;\n }\n\n // Guard: incremental track addition — only new tracks appended, existing\n // tracks unchanged. Add each new track to the existing engine without\n // tearing down the playout. This avoids disposing and recreating all\n // audio nodes when dropping a file or finishing a recording.\n if (isIncrementalAdd && engineRef.current) {\n try {\n // Use prevTracks captured during render (not prevTracksRef.current) to ensure\n // the guard condition and inner logic operate on the same snapshot.\n const prevIds = new Set(prevTracks.map((t) => t.id));\n const addedTracks = tracks.filter((t) => !prevIds.has(t.id));\n\n // Merge current UI state into new tracks before adding to engine\n const currentTrackStates = trackStatesRef.current;\n for (const track of addedTracks) {\n const trackIndex = tracks.indexOf(track);\n const trackState = currentTrackStates[trackIndex];\n const trackWithState = {\n ...track,\n volume: trackState?.volume ?? track.volume,\n muted: trackState?.muted ?? track.muted,\n soloed: trackState?.soloed ?? track.soloed,\n pan: trackState?.pan ?? track.pan,\n };\n engineRef.current!.addTrack(trackWithState);\n }\n\n // Update duration from all tracks (including newly added)\n let maxDuration = 0;\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n const clipEnd = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n maxDuration = Math.max(maxDuration, clipEnd);\n });\n });\n setDuration(maxDuration);\n\n // Initialize track states for the new tracks (preserve existing)\n setTrackStates((prev) => {\n if (prev.length === tracks.length) return prev;\n const newStates = [...prev];\n for (const track of addedTracks) {\n newStates.push({\n name: track.name,\n muted: track.muted,\n soloed: track.soloed,\n volume: track.volume,\n pan: track.pan,\n });\n }\n return newStates;\n });\n\n // Extract audio buffers from all clips across all tracks\n const buffers: AudioBuffer[] = [];\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n if (clip.audioBuffer) {\n buffers.push(clip.audioBuffer);\n }\n });\n });\n setAudioBuffers(buffers);\n\n prevTracksRef.current = tracks;\n return;\n } catch (err) {\n console.warn(\n '[waveform-playlist] Incremental add failed, falling through to full rebuild:',\n String(err)\n );\n // Fall through to full loadAudio path\n }\n }\n\n // Defer engine rebuild during progressive loading — tracks render visually\n // (controls, clip shapes, peaks via worker) but the engine isn't built yet.\n // When deferEngineRebuild flips to false, this effect re-runs and builds once.\n if (deferEngineRebuild) {\n // Still update duration so the timeline renders at the correct width\n let maxDuration = 0;\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n const clipEnd = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n maxDuration = Math.max(maxDuration, clipEnd);\n });\n });\n setDuration(maxDuration);\n return;\n }\n\n // Reset ready state for full rebuild\n setIsReady(false);\n\n if (tracks.length === 0) {\n // Clear content state when all tracks are removed.\n setAudioBuffers([]);\n setDuration(0);\n setTrackStates([]);\n setPeaksDataArray([]);\n\n // Always keep a persistent engine alive — even with no tracks — so the\n // Transport is ready before playback/recording starts. Without this the\n // engine is rebuilt asynchronously when the first track is added, and\n // play() races that rebuild — intermittently no-opping (frozen\n // playhead/time) and paying first-record latency. The engine is still\n // created via dynamic import (lazy module load preserved, #510); only its\n // lifecycle is eager.\n if (engineRef.current) {\n // Keep the existing engine; just clear its tracks.\n engineRef.current.setTracks([]);\n prevTracksRef.current = tracks;\n return;\n }\n // No engine yet (initial mount with no tracks): fall through to the\n // loadAudio path below, which builds a persistent engine for 0 tracks.\n }\n\n // Capture playback state before rebuilding playout (read from ref, not state,\n // so isPlaying can be excluded from the dep array — play/pause must not trigger rebuilds)\n const wasPlaying = isPlayingRef.current;\n const resumePosition = currentTimeRef.current;\n\n // Stop current playback and animation before disposing\n if (engineRef.current && wasPlaying) {\n engineRef.current.stop();\n stopAnimationFrameLoop();\n // Mark that we need to resume playback after playout is rebuilt\n pendingResumeRef.current = { position: resumePosition };\n }\n\n let cancelled = false;\n\n const loadAudio = async () => {\n try {\n // Extract all audio buffers from all clips across all tracks\n const buffers: AudioBuffer[] = [];\n\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n if (clip.audioBuffer) {\n buffers.push(clip.audioBuffer);\n }\n });\n });\n\n // Calculate total timeline duration from all clips across all tracks\n // Use clip.sampleRate which is always defined (works for peaks-only clips too)\n let maxDuration = 0;\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n const sampleRate = clip.sampleRate;\n const clipEndSample = clip.startSample + clip.durationSamples;\n const clipEnd = clipEndSample / sampleRate;\n maxDuration = Math.max(maxDuration, clipEnd);\n });\n });\n\n setAudioBuffers(buffers);\n setDuration(maxDuration);\n\n // Initialize or update track states, preserving existing UI state (mute/solo/volume/pan)\n // Only initialize from ClipTrack props when trackStates is empty or track count changes\n setTrackStates((prevStates) => {\n if (prevStates.length === tracks.length) {\n // Same number of tracks - preserve existing UI state, just update names\n return prevStates.map((state, i) => ({\n ...state,\n name: tracks[i].name,\n }));\n }\n // Track count changed - reinitialize from ClipTrack properties\n return tracks.map((track) => ({\n name: track.name,\n muted: track.muted,\n soloed: track.soloed,\n volume: track.volume,\n pan: track.pan,\n }));\n });\n\n // Dispose old engine before creating new one\n if (engineRef.current) {\n engineRef.current.dispose();\n }\n\n // Reset tracks version tracking for the new engine\n lastTracksVersionRef.current = 0;\n engineTracksRef.current = null;\n\n // Create engine adapter — custom if provided, else Tone.js (dynamically imported).\n // Reset init flag — new adapter needs AudioContext resume on first play.\n audioInitializedRef.current = false;\n const adapter = await resolvePlayoutAdapter({\n createAdapter,\n effects,\n soundFontCache: soundFontCacheRef.current,\n sampleRate: sampleRateProp,\n });\n if (cancelled) {\n adapter.dispose();\n return;\n }\n // Reconcile sample rate from the adapter's own AudioContext. The setState\n // calls later in loadAudio trigger a re-render that propagates this via\n // `const sampleRate = sampleRateRef.current` at render.\n sampleRateRef.current = adapter.audioContext.sampleRate;\n adapterRef.current = adapter;\n const engine = new PlaylistEngine({\n adapter,\n samplesPerPixel: samplesPerPixelRef.current,\n zoomLevels: stableZoomLevels,\n });\n\n // Seed engine with current UI state so a fresh engine doesn't\n // reset selection/loop/selectedTrack to defaults on the first statechange emission.\n engine.setSelection(selectionStartRef.current ?? 0, selectionEndRef.current ?? 0);\n engine.setLoopRegion(loopStartRef.current ?? 0, loopEndRef.current ?? 0);\n if (isLoopEnabledRef.current) engine.setLoopEnabled(true);\n engine.setMasterVolume(masterVolumeRef.current ?? 1.0);\n if (selectedTrackIdRef.current) engine.selectTrack(selectedTrackIdRef.current);\n\n // Merge current UI state into tracks before passing to engine\n const currentTrackStates = trackStatesRef.current;\n const tracksWithState = tracks.map((track, index) => {\n const trackState = currentTrackStates[index];\n return {\n ...track,\n volume: trackState?.volume ?? track.volume,\n muted: trackState?.muted ?? track.muted,\n soloed: trackState?.soloed ?? track.soloed,\n pan: trackState?.pan ?? track.pan,\n };\n });\n\n // Subscribe to engine statechange BEFORE setTracks() so the first\n // emission (which carries correct canZoomIn/canZoomOut) is captured.\n // Seeding statechanges fired above are harmless — they contain values\n // that already match the hooks' current state, so onEngineState()\n // ref guards skip them.\n // Suppress tracks mirroring during the initial setTracks — the parent\n // already has these tracks. The flag is local so it becomes false\n // after setTracks returns (statechange fires synchronously).\n let suppressTracksMirroring = true;\n engine.on('statechange', (state: EngineState) => {\n onSelectionEngineState(state);\n onLoopEngineState(state);\n onSelectedTrackEngineState(state);\n onZoomEngineState(state);\n onVolumeEngineState(state);\n onUndoEngineState(state);\n\n // Mirror engine tracks changes to parent via onTracksChange.\n // tracksVersion only increments on structural track mutations (move,\n // trim, split, setTracks, addTrack, removeTrack), not on\n // selection/zoom/volume changes. Per-track mixer edits bump the\n // engine's separate mixerVersion (#501) and are persisted here via\n // trackStates, so they intentionally do not flow through onTracksChange.\n if (!suppressTracksMirroring && state.tracksVersion !== lastTracksVersionRef.current) {\n lastTracksVersionRef.current = state.tracksVersion;\n engineTracksRef.current = state.tracks;\n if (onTracksChange) {\n onTracksChange(state.tracks);\n } else {\n console.warn(\n '[waveform-playlist] Engine tracks changed but onTracksChange prop is not set — ' +\n 'UI will revert on next render. Pass onTracksChange to WaveformPlaylistProvider.'\n );\n }\n }\n });\n\n engine.setTracks(tracksWithState);\n suppressTracksMirroring = false;\n lastTracksVersionRef.current = engine.getState().tracksVersion;\n engineRef.current = engine;\n\n setIsReady(true);\n\n // Dispatch custom event for external listeners\n const event = new CustomEvent('waveform-playlist:ready', {\n detail: {\n trackCount: tracks.length,\n duration: maxDuration,\n },\n });\n window.dispatchEvent(event);\n\n prevTracksRef.current = tracks;\n\n onReady?.();\n } catch (error) {\n // Dispose an adapter created before the engine failed to build (e.g. invalid\n // zoomLevels make `new PlaylistEngine` throw) — the cleanup skips it because\n // engineRef was never assigned, so it would otherwise leak a live AudioContext.\n if (!engineRef.current && adapterRef.current) {\n try {\n adapterRef.current.dispose();\n } catch (disposeErr) {\n console.warn(\n '[waveform-playlist] adapter dispose after load failure threw: ' + String(disposeErr)\n );\n }\n adapterRef.current = null;\n }\n console.warn('[waveform-playlist] Error loading audio: ' + String(error));\n onError?.(error instanceof Error ? error : new Error(String(error)));\n }\n };\n\n loadAudio();\n\n return () => {\n cancelled = true;\n // Skip disposal when the next render's guard will keep the engine alive.\n // skipEngineDisposeRef is set during the render phase (before this cleanup runs).\n if (skipEngineDisposeRef.current) {\n skipEngineDisposeRef.current = false;\n return;\n }\n stopAnimationFrameLoop();\n if (engineRef.current) {\n engineRef.current.dispose();\n adapterRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n tracks,\n // isEngineTracks is intentionally excluded — it's a render-phase guard read\n // inside the effect body, not a trigger. Including it causes a spurious re-run\n // when it flips from true→false after engineTracksRef is cleared.\n // isPlaying is intentionally excluded — read from isPlayingRef inside the\n // effect body. Including it causes a full engine+playout rebuild on every\n // play/pause/stop, destroying and recreating all audio Players.\n // soundFontCache is deliberately excluded — late cache changes are forwarded\n // to the live adapter by the sync effect below; only adapter creation reads it\n // (via soundFontCacheRef).\n onReady,\n onError,\n effects,\n stopAnimationFrameLoop,\n onSelectionEngineState,\n onLoopEngineState,\n onSelectedTrackEngineState,\n onZoomEngineState,\n onVolumeEngineState,\n onUndoEngineState,\n onTracksChange,\n masterVolumeRef,\n selectionStartRef,\n selectionEndRef,\n loopStartRef,\n loopEndRef,\n isLoopEnabledRef,\n stableZoomLevels,\n deferEngineRebuild,\n ]);\n\n // Forward late-arriving / swapped SoundFontCache to the live adapter so\n // MIDI tracks upgrade from PolySynth without an engine rebuild. On mount\n // this may run against a just-created adapter (the loadAudio effect is\n // declared earlier, runs first, and assigns adapterRef synchronously) or\n // against null (empty tracks). Both are safe: the helper no-ops on null,\n // and the adapter skips MIDI tracks whose routing is unchanged.\n useEffect(() => {\n syncSoundFontCacheToAdapter(adapterRef.current, soundFontCache);\n }, [soundFontCache]);\n\n // Regenerate peaks when zoom, mono, or waveformDataCache changes (without reloading audio)\n // Peak sources in priority order:\n // A) clip.waveformData — external pre-computed peaks (e.g. from BBC audiowaveform)\n // B) waveformDataCache — worker-generated WaveformData (fast resample on zoom)\n // C) empty peaks — clip is still loading or has no audio data\n useEffect(() => {\n if (tracks.length === 0) return;\n\n const allTrackPeaks: TrackClipPeaks[] = tracks.map((track) => {\n const clipPeaks: ClipPeaks[] = track.clips.map((clip) => {\n let peaks: PeakData | undefined;\n\n // Path A: External pre-computed waveform data (e.g. from audiowaveform .dat file)\n // When sample rates match, use clip offsets directly.\n // When rates mismatch (e.g., waveformData at 48000 Hz, clip rebuilt at 44100 Hz\n // after audio decode), convert offsets to the waveformData's sample space and\n // adjust samplesPerPixel so peaks fill the correct container width.\n // Path B (worker) replaces these on next render once cache is populated.\n if (clip.waveformData) {\n try {\n const wdRate = clip.waveformData.sample_rate;\n const clipRate = clip.sampleRate;\n let peakOffset = clip.offsetSamples;\n let peakDuration = clip.durationSamples;\n let peakSpp = samplesPerPixel;\n if (wdRate !== clipRate && clipRate > 0 && wdRate > 0) {\n const ratio = wdRate / clipRate;\n peakOffset = Math.round(clip.offsetSamples * ratio);\n peakDuration = Math.round(clip.durationSamples * ratio);\n peakSpp = Math.max(1, Math.round(samplesPerPixel * ratio));\n }\n peaks = extractPeaksFromWaveformDataFull(\n clip.waveformData as WaveformData,\n peakSpp,\n mono,\n peakOffset,\n peakDuration\n );\n } catch (err) {\n console.warn(\n '[waveform-playlist] Failed to extract peaks from waveformData: ' + String(err)\n );\n }\n }\n\n // Path B: Worker-generated WaveformData cache (fast resample on zoom)\n if (!peaks) {\n const cached = waveformDataCache.get(clip.id);\n if (cached) {\n try {\n peaks = extractPeaksFromWaveformDataFull(\n cached,\n samplesPerPixel,\n mono,\n clip.offsetSamples,\n clip.durationSamples\n );\n } catch (err) {\n console.warn(\n '[waveform-playlist] Failed to extract peaks from cache: ' + String(err)\n );\n }\n }\n }\n\n // Path C: No peaks data available yet — render empty while worker processes.\n // Use correct channel count from audioBuffer to prevent track height shift\n // when peaks arrive (mono mode collapses to 1 channel).\n if (!peaks) {\n if (!deferEngineRebuild && !clip.audioBuffer && !clip.waveformData && !clip.midiNotes) {\n console.warn(\n `[waveform-playlist] Clip \"${clip.id}\" has no audio data or waveform data`\n );\n }\n const numChannels = mono ? 1 : (clip.audioBuffer?.numberOfChannels ?? 1);\n // MIDI clips: derive pixel width from sample duration so the clip renders\n // at the correct width instead of zero-width blank tracks.\n const pixelLength = clip.midiNotes\n ? Math.ceil(clip.durationSamples / samplesPerPixel)\n : 0;\n peaks = {\n length: pixelLength,\n data: Array.from({ length: numChannels }, () => new Int16Array(pixelLength * 2)),\n bits: 16,\n };\n }\n\n return {\n clipId: clip.id,\n trackName: track.name,\n peaks,\n startSample: clip.startSample,\n durationSamples: clip.durationSamples,\n fadeIn: clip.fadeIn,\n fadeOut: clip.fadeOut,\n midiNotes: clip.midiNotes,\n sampleRate: clip.sampleRate,\n offsetSamples: clip.offsetSamples,\n };\n });\n\n return clipPeaks;\n });\n\n setPeaksDataArray(allTrackPeaks);\n }, [tracks, samplesPerPixel, mono, waveformDataCache, deferEngineRebuild]);\n\n // Audio clock from the adapter's own AudioContext (#510 — replaces tone's\n // getContext()). Stable; reads a ref so identity never changes.\n const getAudioContextTime = useCallback(\n () => adapterRef.current?.audioContext.currentTime ?? 0,\n []\n );\n\n // Returns current playback time from engine (auto-wraps at loop boundaries).\n // Falls back to manual calculation when engine is unavailable.\n const getPlaybackTimeFallbackWarnedRef = useRef(false);\n const getPlaybackTime = useCallback(() => {\n if (engineRef.current) {\n return engineRef.current.getCurrentTime();\n }\n // Fallback: manual calculation (does not handle loop wrapping)\n if (!getPlaybackTimeFallbackWarnedRef.current) {\n getPlaybackTimeFallbackWarnedRef.current = true;\n console.warn(\n '[waveform-playlist] getPlaybackTime called without engine. ' +\n 'Falling back to manual elapsed time (loop wrapping will not work).'\n );\n }\n const elapsed = getAudioContextTime() - (playbackStartTimeRef.current ?? 0);\n return (audioStartPositionRef.current ?? 0) + elapsed;\n }, [getAudioContextTime]);\n\n // Resting (non-playing) cursor positions display the raw time unchanged.\n // Latency compensation (outputLatency + lookAhead) models the gap between\n // the Transport's scheduling position and audible output — a property of\n // *playing*, not of position. A seeked/paused/stopped cursor has no audible\n // counterpart; the commanded position is the truth. During playback the\n // animation loop uses engine.getAudibleTime() instead.\n const toVisualTime = useCallback((rawTime: number): number => {\n return Number.isFinite(rawTime) ? Math.max(0, rawTime) : 0;\n }, []);\n\n // Pair-write helper for the dual-ref pattern. Storage stays raw on\n // `currentTimeRef`; display gets the guard-clamped raw version on `visualTimeRef`\n // (compensation happens in the animation loop via engine.getAudibleTime() while playing).\n // Use this at every pause/seek/stop/loop site — never assign one ref alone\n // (browser/CLAUDE.md: \"Display uses visualTimeRef\").\n const setCurrentTimeRefs = useCallback(\n (rawTime: number) => {\n currentTimeRef.current = rawTime;\n visualTimeRef.current = toVisualTime(rawTime);\n },\n [toVisualTime]\n );\n\n // Read the engine's scheduler lookahead (Tone adapter ~0.1s, native 0).\n // Exposed through PlaybackAnimationContextValue so consumers (e.g., recording\n // live preview) compute audible-latency from the same source as the playhead.\n const getLookAhead = useCallback((): number => {\n return engineRef.current?.lookAhead ?? 0;\n }, []);\n\n const getOutputLatency = useCallback((): number => {\n const audioCtx = adapterRef.current?.audioContext;\n return audioCtx && 'outputLatency' in audioCtx ? (audioCtx as AudioContext).outputLatency : 0;\n }, []);\n\n const registerFrameCallback = useCallback((id: string, cb: (data: FrameData) => void) => {\n frameCallbacksRef.current.set(id, cb);\n }, []);\n\n const unregisterFrameCallback = useCallback((id: string) => {\n frameCallbacksRef.current.delete(id);\n }, []);\n\n // Animation loop\n const startAnimationLoop = useCallback(() => {\n const updateTime = () => {\n // Get current time from engine (auto-wraps at loop boundaries via Transport.seconds)\n const time = getPlaybackTime();\n currentTimeRef.current = time;\n\n // Compute visual time once — all visual consumers use this same value.\n // engine.getAudibleTime() subtracts outputLatency (hardware DAC delay)\n // and adapter.lookAhead (Tone.js Transport runs ~100ms ahead of audible),\n // holding at the play-start position during the pre-roll window. Native\n // adapters report lookAhead 0, so this is a near no-op there.\n const visualRaw = engineRef.current ? engineRef.current.getAudibleTime() : time;\n const visualTime = Number.isFinite(visualRaw) ? Math.max(0, visualRaw) : 0;\n visualTimeRef.current = visualTime;\n\n // Drive registered per-frame callbacks BEFORE stop checks so the\n // final frame renders at the correct stop position (not one frame behind).\n const sr = sampleRateRef.current;\n const spp = samplesPerPixelRef.current;\n const frameData: FrameData = {\n time,\n visualTime,\n sampleRate: sr,\n samplesPerPixel: spp,\n };\n for (const cb of frameCallbacksRef.current.values()) {\n cb(frameData);\n }\n\n // Handle annotation playback based on continuous play mode\n const currentAnnotations = annotationsRef.current;\n if (currentAnnotations.length > 0) {\n const currentAnnotation = currentAnnotations.find(\n (ann) => time >= ann.start && time < ann.end\n );\n\n if (continuousPlayRef.current) {\n // Continuous play ON: update active annotation, let audio play to the end\n if (currentAnnotation && currentAnnotation.id !== activeAnnotationIdRef.current) {\n setActiveAnnotationId(currentAnnotation.id);\n } else if (!currentAnnotation && activeAnnotationIdRef.current !== null) {\n // Clear the active annotation when we're past it, but don't stop playback\n // Let playback continue until the audio actually ends (handled by duration check)\n setActiveAnnotationId(null);\n }\n } else {\n // Continuous play OFF: stop at end of current annotation\n if (activeAnnotationIdRef.current) {\n const activeAnnotation = currentAnnotations.find(\n (ann) => ann.id === activeAnnotationIdRef.current\n );\n if (activeAnnotation && time >= activeAnnotation.end) {\n // Stop playback at end of current annotation\n if (engineRef.current) {\n engineRef.current.stop();\n }\n setIsPlaying(false);\n setCurrentTimeRefs(playStartPositionRef.current);\n setCurrentTime(playStartPositionRef.current);\n return;\n }\n } else {\n // If no active annotation ID is set, use the current annotation\n if (currentAnnotation) {\n setActiveAnnotationId(currentAnnotation.id);\n }\n }\n }\n }\n\n // Handle automatic scroll — uses shared visualTime for alignment with playhead\n if (isAutomaticScrollRef.current && scrollContainerRef.current && duration > 0) {\n const container = scrollContainerRef.current;\n const pixelPosition = (visualTime * sr) / spp;\n const containerWidth = container.clientWidth;\n\n // Continuously scroll to keep playhead centered.\n // Math.round prevents sub-pixel jitter from browser integer rounding.\n const targetScrollLeft = Math.round(Math.max(0, pixelPosition - containerWidth / 2));\n container.scrollLeft = targetScrollLeft;\n }\n\n // Check if we've reached the playback end time (for selection playback)\n if (playbackEndTimeRef.current !== null && time >= playbackEndTimeRef.current) {\n // Stop playback at selection end (selection playback is separate from looping)\n if (engineRef.current) {\n engineRef.current.stop();\n }\n setIsPlaying(false);\n setCurrentTimeRefs(playbackEndTimeRef.current);\n setCurrentTime(playbackEndTimeRef.current);\n playbackEndTimeRef.current = null; // Clear the end time\n return;\n }\n\n // Loop is handled natively by Tone.js Transport (Transport.loop/loopStart/loopEnd).\n // Transport.seconds auto-wraps at loop boundaries, so getPlaybackTime() returns\n // the correct position without manual detection here.\n\n if (time >= duration && !indefinitePlaybackRef.current) {\n // Stop playback - inline to avoid circular dependency\n if (engineRef.current) {\n engineRef.current.stop();\n }\n setIsPlaying(false);\n setCurrentTimeRefs(playStartPositionRef.current);\n setCurrentTime(playStartPositionRef.current);\n setActiveAnnotationId(null);\n return;\n }\n startAnimationFrameLoop(updateTime);\n };\n startAnimationFrameLoop(updateTime);\n }, [\n duration,\n setActiveAnnotationId,\n startAnimationFrameLoop,\n getPlaybackTime,\n setCurrentTimeRefs,\n ]);\n\n const stopAnimationLoop = stopAnimationFrameLoop;\n\n // Restart animation loop and reschedule playout when continuousPlay changes during playback\n // This ensures the loop always has the current continuousPlay value\n // and removes duration limits when switching to continuous play\n useEffect(() => {\n const reschedulePlayback = async () => {\n if (isPlaying && animationFrameRef.current && engineRef.current) {\n // When toggling continuous play ON, reschedule playout without duration limit\n // so audio continues past the current annotation boundary\n if (continuousPlay) {\n const currentPos = currentTimeRef.current;\n\n // Stop current playout (which may have duration limit + pause callback)\n engineRef.current.stop();\n stopAnimationLoop();\n\n const timeNow = getAudioContextTime();\n playbackStartTimeRef.current = timeNow;\n audioStartPositionRef.current = currentPos;\n\n // Play without duration - will play to end of track\n engineRef.current.play(currentPos);\n startAnimationLoop();\n } else {\n // Just restart animation loop for continuous play OFF\n stopAnimationLoop();\n startAnimationLoop();\n }\n }\n };\n\n reschedulePlayback().catch((err) => {\n console.warn('[waveform-playlist] Failed to reschedule playback:', err);\n setIsPlaying(false);\n stopAnimationLoop();\n });\n }, [\n continuousPlay,\n isPlaying,\n startAnimationLoop,\n stopAnimationLoop,\n animationFrameRef,\n getAudioContextTime,\n ]);\n\n // Resume playback after tracks change (e.g., after splitting a clip during playback)\n useEffect(() => {\n const resumePlayback = async () => {\n if (pendingResumeRef.current && engineRef.current) {\n const { position } = pendingResumeRef.current;\n pendingResumeRef.current = null;\n\n const timeNow = getAudioContextTime();\n playbackStartTimeRef.current = timeNow;\n audioStartPositionRef.current = position;\n\n if (!audioInitializedRef.current) {\n await engineRef.current.init();\n audioInitializedRef.current = true;\n }\n\n engineRef.current.play(position);\n setIsPlaying(true);\n startAnimationLoop();\n }\n };\n\n resumePlayback().catch((err) => {\n console.warn('[waveform-playlist] Failed to resume playback after track change:', err);\n setIsPlaying(false);\n stopAnimationLoop();\n });\n }, [tracks, startAnimationLoop, stopAnimationLoop, getAudioContextTime]);\n\n // Playback controls\n const play = useCallback(\n async (startTime?: number, playDuration?: number) => {\n if (!engineRef.current) return;\n\n const actualStartTime = startTime ?? currentTimeRef.current;\n playStartPositionRef.current = actualStartTime;\n\n // Update currentTimeRef to match the actual start position\n // This ensures the animation loop starts from the correct position\n setCurrentTimeRefs(actualStartTime);\n\n // Stop existing playback and animation loop before restarting.\n // engine.stop() resets engine._currentTime to _playStartPosition (could be 0)\n // and sets _isPlaying=false. AnimatedPlayhead's RAF may fire during this\n // window and call engine.getCurrentTime() which would return 0.\n // The seek() immediately sets _currentTime to the intended start position,\n // preventing the cursor flash.\n engineRef.current.stop();\n engineRef.current.seek(actualStartTime);\n stopAnimationLoop();\n\n // Record timing for accurate position tracking using the adapter's AudioContext\n const startTimeNow = getAudioContextTime();\n playbackStartTimeRef.current = startTimeNow;\n audioStartPositionRef.current = actualStartTime;\n\n // Set playback end time if playing with duration (e.g., selection playback)\n playbackEndTimeRef.current =\n playDuration !== undefined ? actualStartTime + playDuration : null;\n\n // Don't set up playback complete callback for annotations\n // The animation loop handles stopping at annotation boundaries\n // This avoids callback timing issues when switching between annotations\n\n // Ensure AudioContext is resumed (requires user gesture, no-op after first call).\n // Guarded by ref so subsequent plays skip the await entirely.\n if (!audioInitializedRef.current) {\n await engineRef.current.init();\n audioInitializedRef.current = true;\n }\n\n const endTime = playDuration !== undefined ? actualStartTime + playDuration : undefined;\n try {\n engineRef.current.play(actualStartTime, endTime);\n } catch (err) {\n console.warn('[waveform-playlist] Playback failed to start:', err);\n stopAnimationLoop();\n return;\n }\n setIsPlaying(true);\n startAnimationLoop();\n },\n [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs, getAudioContextTime]\n );\n\n const pause = useCallback(() => {\n if (!engineRef.current) return;\n\n // Get current position from engine (auto-wraps at loop boundaries)\n const pauseTime = getPlaybackTime();\n\n engineRef.current.pause();\n setIsPlaying(false);\n stopAnimationLoop();\n\n // Update to the calculated pause position\n setCurrentTimeRefs(pauseTime);\n setCurrentTime(pauseTime);\n }, [stopAnimationLoop, getPlaybackTime, setCurrentTimeRefs]);\n\n const stop = useCallback(() => {\n if (!engineRef.current) return;\n\n engineRef.current.stop();\n setIsPlaying(false);\n stopAnimationLoop();\n\n // Return cursor to where playback started (Audacity-style)\n setCurrentTimeRefs(playStartPositionRef.current);\n setCurrentTime(playStartPositionRef.current);\n setActiveAnnotationId(null);\n }, [stopAnimationLoop, setActiveAnnotationId, setCurrentTimeRefs]);\n\n // Seek to a specific time - works whether playing or stopped\n const seekTo = useCallback(\n (time: number) => {\n // Clamp time to valid range\n const clampedTime = Math.max(0, Math.min(time, duration));\n\n // Update the current time state\n setCurrentTimeRefs(clampedTime);\n setCurrentTime(clampedTime);\n\n // If currently playing, restart at the new position.\n // play() handles stop+seek+restart internally.\n if (isPlaying && engineRef.current) {\n play(clampedTime);\n }\n },\n [duration, isPlaying, play, setCurrentTimeRefs]\n );\n\n // Track controls\n const setTrackMute = useCallback(\n (trackIndex: number, muted: boolean) => {\n const trackId = tracksRef.current[trackIndex]?.id;\n if (!trackId) return;\n\n const newStates = [...trackStates];\n newStates[trackIndex] = { ...newStates[trackIndex], muted };\n setTrackStates(newStates);\n\n if (engineRef.current) {\n engineRef.current.setTrackMute(trackId, muted);\n }\n },\n [trackStates]\n );\n\n const setTrackSolo = useCallback(\n (trackIndex: number, soloed: boolean) => {\n const trackId = tracksRef.current[trackIndex]?.id;\n if (!trackId) return;\n\n const newStates = [...trackStates];\n newStates[trackIndex] = { ...newStates[trackIndex], soloed };\n setTrackStates(newStates);\n\n if (engineRef.current) {\n engineRef.current.setTrackSolo(trackId, soloed);\n }\n },\n [trackStates]\n );\n\n const setTrackVolume = useCallback(\n (trackIndex: number, volume: number) => {\n const trackId = tracksRef.current[trackIndex]?.id;\n if (!trackId) return;\n\n const newStates = [...trackStates];\n newStates[trackIndex] = { ...newStates[trackIndex], volume };\n setTrackStates(newStates);\n\n if (engineRef.current) {\n engineRef.current.setTrackVolume(trackId, volume);\n }\n },\n [trackStates]\n );\n\n const setTrackPan = useCallback(\n (trackIndex: number, pan: number) => {\n const trackId = tracksRef.current[trackIndex]?.id;\n if (!trackId) return;\n\n const newStates = [...trackStates];\n newStates[trackIndex] = { ...newStates[trackIndex], pan };\n setTrackStates(newStates);\n\n if (engineRef.current) {\n engineRef.current.setTrackPan(trackId, pan);\n }\n },\n [trackStates]\n );\n\n // Selection — wraps hook setter with playback side-effects (provider concern).\n const setSelection = useCallback(\n (start: number, end: number) => {\n setSelectionEngine(start, end);\n setCurrentTimeRefs(start);\n setCurrentTime(start);\n\n if (isPlaying && engineRef.current) {\n // Stop + seek before restarting playback from selection start.\n // engine.stop() resets _currentTime and _isPlaying, creating a window\n // where getCurrentTime() returns wrong value. seek() immediately\n // corrects _currentTime so any RAF firing in between reads the\n // intended position. engine.play() then restarts everything.\n engineRef.current.stop();\n engineRef.current.seek(start);\n engineRef.current.play(start);\n }\n },\n [isPlaying, setSelectionEngine, setCurrentTimeRefs]\n );\n\n // Memoize setScrollContainer callback\n const setScrollContainer = useCallback((element: HTMLDivElement | null) => {\n scrollContainerRef.current = element;\n }, []);\n\n // Stable callback ref for onAnnotationsChange to avoid re-creating controls context\n const onAnnotationsChangeRef = useRef(onAnnotationsChange);\n onAnnotationsChangeRef.current = onAnnotationsChange;\n\n const setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>> = useCallback(\n (action) => {\n const updated = typeof action === 'function' ? action(annotationsRef.current) : action;\n if (!onAnnotationsChangeRef.current) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n 'waveform-playlist: setAnnotations was called but no onAnnotationsChange callback is provided. ' +\n 'Annotation edits will not persist. Pass onAnnotationsChange to WaveformPlaylistProvider to handle annotation updates.'\n );\n }\n return;\n }\n onAnnotationsChangeRef.current(updated);\n },\n []\n );\n\n const sampleRate = sampleRateRef.current;\n const timeScaleHeight = timescale ? 30 : 0;\n const minimumPlaylistHeight = tracks.length * waveHeight + timeScaleHeight;\n\n // Split context values for performance optimization\n // Animation context only re-renders consumers on discrete events\n // (play/pause/stop/seek), never during the animation loop itself\n\n const animationValue: PlaybackAnimationContextValue = useMemo(\n () => ({\n isPlaying,\n currentTime,\n currentTimeRef,\n visualTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n getPlaybackTime,\n getAudioContextTime,\n getLookAhead,\n getOutputLatency,\n registerFrameCallback,\n unregisterFrameCallback,\n }),\n [\n isPlaying,\n currentTime,\n currentTimeRef,\n visualTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n getPlaybackTime,\n getAudioContextTime,\n getLookAhead,\n getOutputLatency,\n registerFrameCallback,\n unregisterFrameCallback,\n ]\n );\n\n const stateValue: PlaylistStateContextValue = useMemo(\n () => ({\n continuousPlay,\n linkEndpoints,\n annotationsEditable,\n isAutomaticScroll,\n isLoopEnabled,\n annotations,\n activeAnnotationId,\n selectionStart,\n selectionEnd,\n selectedTrackId,\n loopStart,\n loopEnd,\n indefinitePlayback,\n canUndo,\n canRedo,\n }),\n [\n continuousPlay,\n linkEndpoints,\n annotationsEditable,\n isAutomaticScroll,\n isLoopEnabled,\n annotations,\n activeAnnotationId,\n selectionStart,\n selectionEnd,\n selectedTrackId,\n loopStart,\n loopEnd,\n indefinitePlayback,\n canUndo,\n canRedo,\n ]\n );\n\n const setCurrentTimeControl = useCallback(\n (time: number) => {\n setCurrentTimeRefs(time);\n setCurrentTime(time);\n },\n [setCurrentTimeRefs]\n );\n\n const setAutomaticScrollControl = useCallback((enabled: boolean) => {\n setIsAutomaticScroll(enabled);\n }, []);\n\n const controlsValue: PlaylistControlsContextValue = useMemo(\n () => ({\n // Playback controls\n play,\n pause,\n stop,\n seekTo,\n setCurrentTime: setCurrentTimeControl,\n\n // Track controls\n setTrackMute,\n setTrackSolo,\n setTrackVolume,\n setTrackPan,\n\n // Selection\n setSelection,\n setSelectedTrackId: setSelectedTrackIdControl,\n\n // Time format\n setTimeFormat,\n formatTime,\n\n // Zoom\n zoomIn: zoom.zoomIn,\n zoomOut: zoom.zoomOut,\n\n // Master volume\n setMasterVolume,\n\n // Automatic scroll\n setAutomaticScroll: setAutomaticScrollControl,\n setScrollContainer,\n scrollContainerRef,\n\n // Annotation controls\n setContinuousPlay,\n setLinkEndpoints,\n setAnnotationsEditable,\n setAnnotations,\n setActiveAnnotationId,\n\n // Loop controls\n setLoopEnabled,\n setLoopRegion,\n setLoopRegionFromSelection,\n clearLoopRegion,\n\n // Undo/redo\n undo,\n redo,\n }),\n [\n play,\n pause,\n stop,\n seekTo,\n setCurrentTimeControl,\n setTrackMute,\n setTrackSolo,\n setTrackVolume,\n setTrackPan,\n setSelection,\n setSelectedTrackIdControl,\n setTimeFormat,\n formatTime,\n zoom.zoomIn,\n zoom.zoomOut,\n setMasterVolume,\n setAutomaticScrollControl,\n setScrollContainer,\n scrollContainerRef,\n setContinuousPlay,\n setLinkEndpoints,\n setAnnotationsEditable,\n setAnnotations,\n setActiveAnnotationId,\n setLoopEnabled,\n setLoopRegion,\n setLoopRegionFromSelection,\n clearLoopRegion,\n undo,\n redo,\n ]\n );\n\n const dataValue: PlaylistDataContextValue = useMemo(\n () => ({\n duration,\n audioBuffers,\n peaksDataArray,\n trackStates,\n tracks,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n minimumPlaylistHeight,\n controls,\n playoutRef: engineRef,\n samplesPerPixel,\n timeFormat,\n masterVolume,\n canZoomIn: zoom.canZoomIn,\n canZoomOut: zoom.canZoomOut,\n barWidth,\n barGap,\n roundedBars,\n progressBarWidth,\n isReady,\n mono,\n isDraggingRef,\n onTracksChange,\n }),\n [\n duration,\n audioBuffers,\n peaksDataArray,\n trackStates,\n tracks,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n minimumPlaylistHeight,\n controls,\n engineRef,\n samplesPerPixel,\n timeFormat,\n masterVolume,\n zoom.canZoomIn,\n zoom.canZoomOut,\n barWidth,\n barGap,\n roundedBars,\n progressBarWidth,\n isReady,\n mono,\n isDraggingRef,\n onTracksChange,\n ]\n );\n\n // Merge user theme with default theme\n const mergedTheme = { ...defaultTheme, ...userTheme };\n\n return (\n <ThemeProvider theme={mergedTheme}>\n <PlaybackAnimationContext.Provider value={animationValue}>\n <PlaylistStateContext.Provider value={stateValue}>\n <PlaylistControlsContext.Provider value={controlsValue}>\n <PlaylistDataContext.Provider value={dataValue}>\n {children}\n </PlaylistDataContext.Provider>\n </PlaylistControlsContext.Provider>\n </PlaylistStateContext.Provider>\n </PlaybackAnimationContext.Provider>\n </ThemeProvider>\n );\n};\n\n// Individual hooks for each context - use these for optimal performance\n// Components only re-render when their specific context data changes\n\nexport const usePlaybackAnimation = () => {\n const context = useContext(PlaybackAnimationContext);\n if (!context) {\n throw new Error('usePlaybackAnimation must be used within WaveformPlaylistProvider');\n }\n return context;\n};\n\nexport const usePlaylistState = () => {\n const context = useContext(PlaylistStateContext);\n if (!context) {\n throw new Error('usePlaylistState must be used within WaveformPlaylistProvider');\n }\n return context;\n};\n\nexport const usePlaylistControls = () => {\n const context = useContext(PlaylistControlsContext);\n if (!context) {\n throw new Error('usePlaylistControls must be used within WaveformPlaylistProvider');\n }\n return context;\n};\n\nexport const usePlaylistData = () => {\n const context = useContext(PlaylistDataContext);\n if (!context) {\n throw new Error('usePlaylistData must be used within WaveformPlaylistProvider');\n }\n return context;\n};\n\n/**\n * Like {@link usePlaylistData} but returns `null` instead of throwing when there\n * is no WaveformPlaylistProvider ancestor. Use this in hooks/components that may\n * also run in the MediaElement path (MediaElementPlaylistProvider), where the\n * WebAudio playlist context is absent.\n */\nexport const usePlaylistDataOptional = () => useContext(PlaylistDataContext);\n","import type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport type { EffectsFunction, SoundFontCache } from '@waveform-playlist/playout';\n\n/** Options for {@link resolvePlayoutAdapter}. */\nexport interface ResolvePlayoutAdapterOptions {\n /** Consumer-supplied factory. When present, the default engine is never imported. */\n createAdapter?: () => PlayoutAdapter;\n /** Master effects function — forwarded to the default Tone.js adapter only. */\n effects?: EffectsFunction;\n /** SoundFont cache — forwarded to the default Tone.js adapter only. */\n soundFontCache?: SoundFontCache;\n /** Desired AudioContext sample rate — configures the global Tone context (default path only). */\n sampleRate?: number;\n}\n\nconst INSTALL_HINT =\n '@waveform-playlist/playout (and its peer `tone`) is required for the default WebAudio engine. ' +\n 'Install with: npm install @waveform-playlist/playout tone — or pass a custom `createAdapter`.';\n\n/**\n * Resolve a {@link PlayoutAdapter}. With `createAdapter`, returns its result and\n * never touches the default engine — so consumers with a custom adapter install\n * neither `@waveform-playlist/playout` nor `tone`. Otherwise dynamically imports\n * the Tone.js engine, rethrowing a friendly install hint (and console.warn-ing the\n * original error) when the optional peer is absent.\n */\nexport async function resolvePlayoutAdapter(\n opts: ResolvePlayoutAdapterOptions\n): Promise<PlayoutAdapter> {\n if (opts.createAdapter) {\n return opts.createAdapter();\n }\n\n let mod: typeof import('@waveform-playlist/playout');\n try {\n mod = await import('@waveform-playlist/playout');\n } catch (originalErr) {\n console.warn(\n '[waveform-playlist] @waveform-playlist/playout dynamic import failed: ' + String(originalErr)\n );\n throw new Error(INSTALL_HINT);\n }\n\n if (opts.sampleRate !== undefined) {\n // Non-fatal: a failed context configuration falls back to the default rate\n // (restores pre-#510 behavior — it was caught in the old mount-time initializer).\n try {\n mod.configureGlobalContext({ sampleRate: opts.sampleRate });\n } catch (ctxErr) {\n console.warn(\n '[waveform-playlist] configureGlobalContext failed (continuing with default rate): ' +\n String(ctxErr)\n );\n }\n }\n return mod.createToneAdapter({ effects: opts.effects, soundFontCache: opts.soundFontCache });\n}\n","/**\n * Waveform Data Loader\n *\n * Utilities for loading pre-computed waveform data in waveform-data.js format.\n * Supports both binary (.dat) and JSON formats from BBC's audiowaveform tool.\n */\n\nimport WaveformData from 'waveform-data';\nimport type { PeakData, Peaks } from '@waveform-playlist/core';\n\n/**\n * Load waveform data from a .dat or .json file\n *\n * @param src - URL to waveform data file (.dat or .json)\n * @returns WaveformData instance\n */\nexport async function loadWaveformData(src: string): Promise<WaveformData> {\n const response = await fetch(src);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch waveform data: ${response.statusText}`);\n }\n\n // Detect binary format from pathname (ignores query string and fragment)\n const { pathname } = new URL(src, globalThis.location?.href ?? 'http://localhost');\n const isBinary = pathname.toLowerCase().endsWith('.dat');\n\n if (isBinary) {\n const arrayBuffer = await response.arrayBuffer();\n return WaveformData.create(arrayBuffer);\n } else {\n const json = await response.json();\n return WaveformData.create(json);\n }\n}\n\n/**\n * Convert WaveformData to our internal Peaks format\n *\n * @param waveformData - WaveformData instance from waveform-data.js\n * @param channelIndex - Channel index (0 for mono/left, 1 for right)\n * @returns Peaks data with alternating min/max values, preserving original bit depth\n */\nexport function waveformDataToPeaks(\n waveformData: WaveformData,\n channelIndex: number = 0\n): { data: Int8Array | Int16Array; bits: 8 | 16; length: number; sampleRate: number } {\n const channel = waveformData.channel(channelIndex);\n const bits = waveformData.bits as 8 | 16;\n\n // Get the min/max arrays to determine length\n const minArray = channel.min_array();\n const maxArray = channel.max_array();\n const length = minArray.length;\n\n // Use appropriate typed array based on source file bit depth\n // 8-bit: values range from -128 to 127\n // 16-bit: values range from -32768 to 32767\n const peaks = bits === 8 ? new Int8Array(length * 2) : new Int16Array(length * 2);\n\n // Interleave min/max pairs\n for (let i = 0; i < length; i++) {\n peaks[i * 2] = minArray[i];\n peaks[i * 2 + 1] = maxArray[i];\n }\n\n return {\n data: peaks,\n bits,\n length,\n sampleRate: waveformData.sample_rate,\n };\n}\n\n/**\n * Load waveform data file and convert to Peaks format in one step\n *\n * @param src - URL to waveform data file (.dat or .json)\n * @param channelIndex - Channel index (default: 0)\n * @returns Peaks data ready for rendering\n */\nexport async function loadPeaksFromWaveformData(\n src: string,\n channelIndex: number = 0\n): Promise<{ data: Int8Array | Int16Array; bits: 8 | 16; length: number; sampleRate: number }> {\n const waveformData = await loadWaveformData(src);\n return waveformDataToPeaks(waveformData, channelIndex);\n}\n\n/**\n * Get metadata from waveform data file without converting to peaks\n *\n * @param src - URL to waveform data file\n * @returns Metadata (sample rate, channels, duration, bits, etc.)\n */\nexport async function getWaveformDataMetadata(src: string): Promise<{\n sampleRate: number;\n channels: number;\n duration: number;\n samplesPerPixel: number;\n length: number;\n bits: 8 | 16;\n}> {\n const waveformData = await loadWaveformData(src);\n\n return {\n sampleRate: waveformData.sample_rate,\n channels: waveformData.channels,\n duration: waveformData.duration,\n samplesPerPixel: waveformData.scale,\n length: waveformData.length,\n bits: waveformData.bits as 8 | 16,\n };\n}\n\n/**\n * Slice and resample WaveformData with aligned source indices.\n *\n * Handles three cases:\n * 1. Scale differs + offset/duration given → aligned slice then resample\n * 2. Same scale + offset/duration given → direct slice\n * 3. No offset/duration → full-file resample (or identity)\n *\n * @returns Processed WaveformData ready for peak extraction\n */\nfunction sliceAndResample(\n waveformData: WaveformData,\n samplesPerPixel: number,\n offsetSamples?: number,\n durationSamples?: number\n): WaveformData | null {\n let processedData = waveformData;\n\n if (offsetSamples !== undefined && durationSamples !== undefined) {\n if (processedData.scale !== samplesPerPixel) {\n const sourceScale = waveformData.scale;\n const ratio = samplesPerPixel / sourceScale;\n\n // Compute clip's pixel range in target-scale bins\n const targetStart = Math.floor(offsetSamples / samplesPerPixel);\n const targetEnd = Math.ceil((offsetSamples + durationSamples) / samplesPerPixel);\n\n // Convert to source indices using floor/ceil for inclusive slicing.\n // For integer ratios (power-of-two scales), this gives exact alignment.\n // For non-integer ratios, floor/ceil ensures we always include all source\n // bins that contribute to the target bins — never underrepresenting peaks.\n const sourceStart = Math.floor(targetStart * ratio);\n const sourceEnd = Math.min(waveformData.length, Math.ceil(targetEnd * ratio));\n\n if (sourceStart >= sourceEnd) {\n return null; // Clip offset beyond waveform data\n }\n\n processedData = processedData.slice({\n startIndex: sourceStart,\n endIndex: sourceEnd,\n });\n processedData = processedData.resample({ scale: samplesPerPixel });\n } else {\n // No resampling needed — slice directly at base scale\n const startIndex = Math.floor(offsetSamples / samplesPerPixel);\n const endIndex = Math.ceil((offsetSamples + durationSamples) / samplesPerPixel);\n processedData = processedData.slice({ startIndex, endIndex });\n }\n } else if (processedData.scale !== samplesPerPixel) {\n processedData = processedData.resample({ scale: samplesPerPixel });\n }\n\n return processedData;\n}\n\n/**\n * Extract peaks from a WaveformData object at a specific scale (samplesPerPixel)\n * and optionally slice to a sample range.\n *\n * @param waveformData - WaveformData instance from waveform-data.js\n * @param samplesPerPixel - Target samples per pixel (will resample if different)\n * @param channelIndex - Channel index (default: 0)\n * @param offsetSamples - Optional start offset in samples (for clip trimming)\n * @param durationSamples - Optional duration in samples (for clip trimming)\n * @returns Peaks data ready for rendering\n */\nexport function extractPeaksFromWaveformData(\n waveformData: WaveformData,\n samplesPerPixel: number,\n channelIndex: number = 0,\n offsetSamples?: number,\n durationSamples?: number\n): { data: Int8Array | Int16Array; bits: 8 | 16; length: number } {\n const processedData = sliceAndResample(\n waveformData,\n samplesPerPixel,\n offsetSamples,\n durationSamples\n );\n\n if (processedData === null) {\n const bits = waveformData.bits as 8 | 16;\n const emptyPeaks = bits === 8 ? new Int8Array(0) : new Int16Array(0);\n return { data: emptyPeaks, bits, length: 0 };\n }\n\n // Convert to our peaks format\n const channel = processedData.channel(channelIndex);\n const bits = processedData.bits as 8 | 16;\n const minArray = channel.min_array();\n const maxArray = channel.max_array();\n const length = minArray.length;\n\n const peaks = bits === 8 ? new Int8Array(length * 2) : new Int16Array(length * 2);\n\n for (let i = 0; i < length; i++) {\n peaks[i * 2] = minArray[i];\n peaks[i * 2 + 1] = maxArray[i];\n }\n\n return { data: peaks, bits, length };\n}\n\n/**\n * Extract peaks from a WaveformData object, handling ALL channels, mono merging,\n * slicing, and resampling.\n *\n * Bit depth is determined by the WaveformData source — all typed arrays match\n * the source's bit depth for consistent data/metadata.\n *\n * @param waveformData - WaveformData instance (should be generated with split_channels: true)\n * @param samplesPerPixel - Target samples per pixel\n * @param isMono - Whether to merge channels to mono\n * @param offsetSamples - Optional start offset in samples (for clip trimming)\n * @param durationSamples - Optional duration in samples (for clip trimming)\n * @returns PeakData matching the interface from @waveform-playlist/core\n */\nexport function extractPeaksFromWaveformDataFull(\n waveformData: WaveformData,\n samplesPerPixel: number,\n isMono: boolean,\n offsetSamples?: number,\n durationSamples?: number\n): PeakData {\n const processedData = sliceAndResample(\n waveformData,\n samplesPerPixel,\n offsetSamples,\n durationSamples\n );\n\n if (processedData === null) {\n const bits = waveformData.bits as 8 | 16;\n return { length: 0, data: [], bits };\n }\n\n const numChannels = processedData.channels;\n const bits = processedData.bits as 8 | 16;\n\n // Extract peaks for all channels\n const channelPeaks: Peaks[] = [];\n for (let c = 0; c < numChannels; c++) {\n const channel = processedData.channel(c);\n const minArray = channel.min_array();\n const maxArray = channel.max_array();\n const len = minArray.length;\n\n const peaks: Peaks = bits === 8 ? new Int8Array(len * 2) : new Int16Array(len * 2);\n\n for (let i = 0; i < len; i++) {\n peaks[i * 2] = minArray[i];\n peaks[i * 2 + 1] = maxArray[i];\n }\n channelPeaks.push(peaks);\n }\n\n // Handle mono merging (same algorithm as makeMono in webaudio-peaks)\n if (isMono && channelPeaks.length > 1) {\n const weight = 1 / channelPeaks.length;\n const numPeaks = channelPeaks[0].length / 2;\n const monoPeaks: Peaks =\n bits === 8 ? new Int8Array(numPeaks * 2) : new Int16Array(numPeaks * 2);\n\n for (let i = 0; i < numPeaks; i++) {\n let min = 0;\n let max = 0;\n for (let c = 0; c < channelPeaks.length; c++) {\n min += weight * channelPeaks[c][i * 2];\n max += weight * channelPeaks[c][i * 2 + 1];\n }\n monoPeaks[i * 2] = min;\n monoPeaks[i * 2 + 1] = max;\n }\n\n return {\n length: numPeaks,\n data: [monoPeaks],\n bits,\n };\n }\n\n const peakLength = channelPeaks.length > 0 ? channelPeaks[0].length / 2 : 0;\n\n return {\n length: peakLength,\n data: channelPeaks,\n bits,\n };\n}\n","import type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport type { SoundFontCache } from '@waveform-playlist/playout';\n\n/** Adapters that accept a SoundFontCache (e.g. the Tone.js adapter). */\ninterface SoundFontCapableAdapter {\n setSoundFontCache(cache: SoundFontCache | undefined): void;\n}\n\n/**\n * Structural check for soundfont support — avoids a runtime import of\n * `@waveform-playlist/playout`. `isToneAdapter` from playout does the same\n * structural check (via optional chaining); our local `supportsSoundFont`\n * adds an explicit `adapter != null` guard. Keeping it structural lets the\n * core barrel stay engine-free (#510) and works for any custom adapter that\n * exposes `setSoundFontCache`.\n */\nfunction supportsSoundFont(\n adapter: PlayoutAdapter | null\n): adapter is PlayoutAdapter & SoundFontCapableAdapter {\n return (\n adapter != null &&\n typeof (adapter as Partial<SoundFontCapableAdapter>).setSoundFontCache === 'function'\n );\n}\n\n/**\n * Forward a (possibly late-loaded or swapped) SoundFontCache to the live\n * adapter. Safe no-op when the adapter is absent or doesn't support\n * soundfonts. The adapter itself skips MIDI tracks whose routing is\n * unchanged, so redundant calls (e.g. on mount) cause no rebuild churn.\n */\nexport function syncSoundFontCacheToAdapter(\n adapter: PlayoutAdapter | null,\n cache: SoundFontCache | undefined\n): void {\n if (!supportsSoundFont(adapter)) return;\n adapter.setSoundFontCache(cache);\n}\n","import { useState } from 'react';\nimport {\n formatTime as formatTimeUtil,\n parseTime as parseTimeUtil,\n type TimeFormat,\n} from '@waveform-playlist/ui-components';\n\nexport interface TimeFormatControls {\n timeFormat: TimeFormat;\n setTimeFormat: (format: TimeFormat) => void;\n formatTime: (seconds: number) => string;\n parseTime: (timeString: string) => number;\n}\n\n/**\n * Hook to manage time format state\n *\n * @example\n * ```tsx\n * const { timeFormat, setTimeFormat, formatTime, parseTime } = useTimeFormat();\n *\n * <TimeFormatSelect\n * value={timeFormat}\n * onChange={setTimeFormat}\n * />\n * <span>{formatTime(currentTime)}</span>\n * <input onChange={(e) => seekTo(parseTime(e.target.value))} />\n * ```\n */\nexport function useTimeFormat(): TimeFormatControls {\n const [timeFormat, setTimeFormat] = useState<TimeFormat>('hh:mm:ss.uuu');\n\n const formatTime = (seconds: number) => {\n return formatTimeUtil(seconds, timeFormat);\n };\n\n const parseTime = (timeString: string) => {\n return parseTimeUtil(timeString, timeFormat);\n };\n\n return {\n timeFormat,\n setTimeFormat,\n formatTime,\n parseTime,\n };\n}\n","import { useState, useCallback, useRef, startTransition, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface ZoomControls {\n samplesPerPixel: number;\n zoomIn: () => void;\n zoomOut: () => void;\n canZoomIn: boolean;\n canZoomOut: boolean;\n}\n\nexport interface UseZoomControlsProps {\n engineRef: RefObject<PlaylistEngine | null>;\n initialSamplesPerPixel: number;\n}\n\n/**\n * Hook for managing zoom controls via PlaylistEngine delegation.\n *\n * zoomIn/zoomOut delegate to the engine. State is mirrored back from\n * the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n *\n * samplesPerPixel updates use startTransition so React treats them as\n * non-urgent — during playback, animation RAF callbacks interleave\n * with the zoom re-render instead of being blocked.\n */\nexport function useZoomControls({\n engineRef,\n initialSamplesPerPixel,\n}: UseZoomControlsProps): ZoomControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [samplesPerPixel, setSamplesPerPixel] = useState(initialSamplesPerPixel);\n const [canZoomIn, setCanZoomIn] = useState(true);\n const [canZoomOut, setCanZoomOut] = useState(true);\n\n // Internal refs for statechange guards — prevent redundant setState\n // calls during high-frequency engine events (clip drags, play/pause).\n const canZoomInRef = useRef(true);\n const canZoomOutRef = useRef(true);\n const samplesPerPixelRef = useRef(initialSamplesPerPixel);\n\n const zoomIn = useCallback(() => {\n engineRef.current?.zoomIn();\n }, [engineRef]);\n\n const zoomOut = useCallback(() => {\n engineRef.current?.zoomOut();\n }, [engineRef]);\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.samplesPerPixel !== samplesPerPixelRef.current) {\n samplesPerPixelRef.current = state.samplesPerPixel;\n startTransition(() => {\n setSamplesPerPixel(state.samplesPerPixel);\n });\n }\n if (state.canZoomIn !== canZoomInRef.current) {\n canZoomInRef.current = state.canZoomIn;\n setCanZoomIn(state.canZoomIn);\n }\n if (state.canZoomOut !== canZoomOutRef.current) {\n canZoomOutRef.current = state.canZoomOut;\n setCanZoomOut(state.canZoomOut);\n }\n }, []);\n\n return {\n samplesPerPixel,\n zoomIn,\n zoomOut,\n canZoomIn,\n canZoomOut,\n onEngineState,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseMasterVolumeProps {\n engineRef: RefObject<PlaylistEngine | null>;\n initialVolume?: number; // 0-1.0 (linear gain, consistent with Web Audio API)\n}\n\nexport interface MasterVolumeControls {\n masterVolume: number;\n setMasterVolume: (volume: number) => void;\n /** Ref holding the current masterVolume for seeding a fresh engine. */\n masterVolumeRef: React.RefObject<number>;\n}\n\n/**\n * Hook for managing master volume via PlaylistEngine delegation.\n *\n * setMasterVolume delegates to the engine. State is mirrored back from\n * the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n */\nexport function useMasterVolume({\n engineRef,\n initialVolume = 1.0,\n}: UseMasterVolumeProps): MasterVolumeControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [masterVolume, setMasterVolumeState] = useState(initialVolume);\n\n // Internal ref for statechange guard + engine seeding on rebuild.\n const masterVolumeRef = useRef(initialVolume);\n\n const setMasterVolume = useCallback(\n (volume: number) => {\n if (engineRef.current) {\n // Engine exists — delegate; statechange will update ref + React state.\n engineRef.current.setMasterVolume(volume);\n } else {\n // No engine yet — persist locally so loadAudio() seeds correctly.\n masterVolumeRef.current = volume;\n setMasterVolumeState(volume);\n }\n },\n [engineRef]\n );\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.masterVolume !== masterVolumeRef.current) {\n masterVolumeRef.current = state.masterVolume;\n setMasterVolumeState(state.masterVolume);\n }\n }, []);\n\n return {\n masterVolume,\n setMasterVolume,\n masterVolumeRef,\n onEngineState,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseSelectionStateProps {\n engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface SelectionControls {\n selectionStart: number;\n selectionEnd: number;\n setSelection: (start: number, end: number) => void;\n selectionStartRef: React.RefObject<number>;\n selectionEndRef: React.RefObject<number>;\n}\n\n/**\n * Hook for managing selection state via PlaylistEngine delegation.\n *\n * setSelection delegates to the engine. State is mirrored back from\n * the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n *\n * Note: The provider wraps this setter with playback side-effects\n * (updating currentTime, restarting playback). This hook only handles\n * the engine delegation + state mirroring.\n */\nexport function useSelectionState({ engineRef }: UseSelectionStateProps): SelectionControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [selectionStart, setSelectionStartState] = useState(0);\n const [selectionEnd, setSelectionEndState] = useState(0);\n\n // Internal refs for statechange guard + engine seeding on rebuild.\n const selectionStartRef = useRef(0);\n const selectionEndRef = useRef(0);\n\n const setSelection = useCallback(\n (start: number, end: number) => {\n engineRef.current?.setSelection(start, end);\n },\n [engineRef]\n );\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.selectionStart !== selectionStartRef.current) {\n selectionStartRef.current = state.selectionStart;\n setSelectionStartState(state.selectionStart);\n }\n if (state.selectionEnd !== selectionEndRef.current) {\n selectionEndRef.current = state.selectionEnd;\n setSelectionEndState(state.selectionEnd);\n }\n }, []);\n\n return {\n selectionStart,\n selectionEnd,\n setSelection,\n selectionStartRef,\n selectionEndRef,\n onEngineState,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseLoopStateProps {\n engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface LoopControls {\n isLoopEnabled: boolean;\n loopStart: number;\n loopEnd: number;\n setLoopEnabled: (value: boolean) => void;\n setLoopRegion: (start: number, end: number) => void;\n clearLoopRegion: () => void;\n isLoopEnabledRef: React.RefObject<boolean>;\n loopStartRef: React.RefObject<number>;\n loopEndRef: React.RefObject<number>;\n}\n\n/**\n * Hook for managing loop state via PlaylistEngine delegation.\n *\n * setLoopEnabled, setLoopRegion, and clearLoopRegion delegate to the\n * engine. State is mirrored back from the engine via onEngineState(),\n * which the provider's statechange handler calls on every engine event.\n */\nexport function useLoopState({ engineRef }: UseLoopStateProps): LoopControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [isLoopEnabled, setIsLoopEnabledState] = useState(false);\n const [loopStart, setLoopStartState] = useState(0);\n const [loopEnd, setLoopEndState] = useState(0);\n\n // Internal refs for statechange guard + animation loop reads.\n const isLoopEnabledRef = useRef(false);\n const loopStartRef = useRef(0);\n const loopEndRef = useRef(0);\n\n const setLoopEnabled = useCallback(\n (value: boolean) => {\n engineRef.current?.setLoopEnabled(value);\n },\n [engineRef]\n );\n\n const setLoopRegion = useCallback(\n (start: number, end: number) => {\n engineRef.current?.setLoopRegion(start, end);\n },\n [engineRef]\n );\n\n const clearLoopRegion = useCallback(() => {\n engineRef.current?.setLoopRegion(0, 0);\n }, [engineRef]);\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.isLoopEnabled !== isLoopEnabledRef.current) {\n isLoopEnabledRef.current = state.isLoopEnabled;\n setIsLoopEnabledState(state.isLoopEnabled);\n }\n if (state.loopStart !== loopStartRef.current) {\n loopStartRef.current = state.loopStart;\n setLoopStartState(state.loopStart);\n }\n if (state.loopEnd !== loopEndRef.current) {\n loopEndRef.current = state.loopEnd;\n setLoopEndState(state.loopEnd);\n }\n }, []);\n\n return {\n isLoopEnabled,\n loopStart,\n loopEnd,\n setLoopEnabled,\n setLoopRegion,\n clearLoopRegion,\n isLoopEnabledRef,\n loopStartRef,\n loopEndRef,\n onEngineState,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseSelectedTrackProps {\n engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface SelectedTrackControls {\n selectedTrackId: string | null;\n setSelectedTrackId: (trackId: string | null) => void;\n}\n\n/**\n * Hook for managing selected track state via PlaylistEngine delegation.\n *\n * setSelectedTrackId delegates to the engine's selectTrack method.\n * State is mirrored back from the engine via onEngineState(), which\n * the provider's statechange handler calls on every engine event.\n */\nexport function useSelectedTrack({ engineRef }: UseSelectedTrackProps): SelectedTrackControls & {\n onEngineState: (state: EngineState) => void;\n selectedTrackIdRef: React.RefObject<string | null>;\n} {\n const [selectedTrackId, setSelectedTrackIdState] = useState<string | null>(null);\n\n // Internal ref for statechange guard.\n const selectedTrackIdRef = useRef<string | null>(null);\n\n const setSelectedTrackId = useCallback(\n (trackId: string | null) => {\n engineRef.current?.selectTrack(trackId);\n },\n [engineRef]\n );\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.selectedTrackId !== selectedTrackIdRef.current) {\n selectedTrackIdRef.current = state.selectedTrackId;\n setSelectedTrackIdState(state.selectedTrackId);\n }\n }, []);\n\n return {\n selectedTrackId,\n setSelectedTrackId,\n onEngineState,\n selectedTrackIdRef,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseUndoStateProps {\n engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface UndoControls {\n canUndo: boolean;\n canRedo: boolean;\n undo: () => void;\n redo: () => void;\n}\n\n/**\n * Hook for managing undo/redo state via PlaylistEngine delegation.\n *\n * undo/redo delegate to the engine. canUndo/canRedo are mirrored back\n * from the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n *\n * No refs are exposed for engine seeding — undo history intentionally\n * resets on engine rebuild (setTracks calls clearHistory).\n */\nexport function useUndoState({ engineRef }: UseUndoStateProps): UndoControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [canUndo, setCanUndo] = useState(false);\n const [canRedo, setCanRedo] = useState(false);\n\n // Internal refs for statechange guard (same pattern as useSelectionState etc.)\n const canUndoRef = useRef(false);\n const canRedoRef = useRef(false);\n\n const undo = useCallback(() => {\n if (!engineRef.current) {\n console.warn('[waveform-playlist] undo: engine not ready, call ignored');\n return;\n }\n engineRef.current.undo();\n }, [engineRef]);\n\n const redo = useCallback(() => {\n if (!engineRef.current) {\n console.warn('[waveform-playlist] redo: engine not ready, call ignored');\n return;\n }\n engineRef.current.redo();\n }, [engineRef]);\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.canUndo !== canUndoRef.current) {\n canUndoRef.current = state.canUndo;\n setCanUndo(state.canUndo);\n }\n if (state.canRedo !== canRedoRef.current) {\n canRedoRef.current = state.canRedo;\n setCanRedo(state.canRedo);\n }\n }, []);\n\n return {\n canUndo,\n canRedo,\n undo,\n redo,\n onEngineState,\n };\n}\n","import React from 'react';\nimport type {\n DragStartEvent as DragStartCallback,\n DragMoveEvent as DragMoveCallback,\n DragEndEvent as DragEndCallback,\n} from '@dnd-kit/abstract';\nimport type { ClipTrack } from '@waveform-playlist/core';\nimport type { PlaylistEngine } from '@waveform-playlist/engine';\nimport { calculateBoundaryTrim } from '../utils/boundaryTrim';\nimport { usePlaylistData } from '../WaveformPlaylistContext';\n\ninterface UseClipDragHandlersOptions {\n tracks: ClipTrack[];\n onTracksChange: (tracks: ClipTrack[]) => void;\n samplesPerPixel: number;\n engineRef: React.RefObject<PlaylistEngine | null>;\n /** Ref toggled during boundary trim drags. When true, the provider's loadAudio\n * skips engine rebuilds so engine keeps original clip positions. On drag end,\n * engine.trimClip() commits the final delta. Obtain from usePlaylistData(). */\n isDraggingRef: React.MutableRefObject<boolean>;\n /** Optional function that snaps a sample position to the nearest grid position.\n * Used for boundary trim snapping (move snapping is handled by the SnapToGridModifier). */\n snapSamplePosition?: (samplePosition: number) => number;\n}\n\n/**\n * Custom hook for handling clip drag operations (movement and trimming)\n *\n * Provides drag handlers for use with @dnd-kit/react DragDropProvider.\n * Handles both clip movement (dragging entire clips) and boundary trimming (adjusting clip edges).\n *\n * Collision detection for clip moves is handled by `ClipCollisionModifier` (passed to DragDropProvider).\n *\n * **Move:** `onDragEnd` delegates to `engine.moveClip()` in one shot.\n *\n * **Trim:** `onDragMove` updates React state per-frame via `onTracksChange` for smooth\n * visual feedback (using cumulative deltas from the original clip snapshot). `isDraggingRef`\n * prevents loadAudio from rebuilding the engine during the drag, so the engine keeps the\n * original clip positions. On drag end, `engine.trimClip()` commits the final delta.\n *\n * @example\n * ```tsx\n * const { onDragStart, onDragMove, onDragEnd } = useClipDragHandlers({\n * tracks,\n * onTracksChange: setTracks,\n * samplesPerPixel,\n * engineRef: playoutRef,\n * isDraggingRef,\n * });\n *\n * return (\n * <DragDropProvider\n * onDragStart={onDragStart}\n * onDragMove={onDragMove}\n * onDragEnd={onDragEnd}\n * modifiers={[RestrictToHorizontalAxis, ClipCollisionModifier.configure({ tracks, samplesPerPixel })]}\n * >\n * <Waveform showClipHeaders={true} />\n * </DragDropProvider>\n * );\n * ```\n */\nexport function useClipDragHandlers({\n tracks,\n onTracksChange,\n samplesPerPixel,\n engineRef,\n isDraggingRef,\n snapSamplePosition,\n}: UseClipDragHandlersOptions) {\n const { sampleRate } = usePlaylistData();\n // Store snap function in ref so onDragMove doesn't need it as a dependency\n const snapSamplePositionRef = React.useRef(snapSamplePosition);\n snapSamplePositionRef.current = snapSamplePosition;\n // Store original clip state when drag starts (for cumulative delta application)\n const originalClipStateRef = React.useRef<{\n offsetSamples: number;\n durationSamples: number;\n startSample: number;\n } | null>(null);\n\n // Store the last boundary trim delta (samples) so onDragEnd uses the same value\n // as the last onDragMove visual update, avoiding stale-position mismatches.\n const lastBoundaryDeltaRef = React.useRef(0);\n\n const onDragStart = React.useCallback(\n (event: Parameters<DragStartCallback>[0]) => {\n const data = event.operation.source?.data as\n | {\n boundary?: 'left' | 'right';\n trackIndex: number;\n clipIndex: number;\n }\n | undefined;\n\n if (!data) return;\n\n // Only store state for boundary trimming operations\n if (!data.boundary) {\n originalClipStateRef.current = null;\n // Group move into one undo step\n if (engineRef.current) {\n engineRef.current.beginTransaction();\n } else {\n console.warn(\n '[waveform-playlist] onDragStart: engine not ready, move will not be grouped for undo'\n );\n }\n return;\n }\n\n const track = tracks[data.trackIndex];\n const clip = track?.clips[data.clipIndex];\n\n if (clip) {\n // Store original clip state for cumulative delta application\n originalClipStateRef.current = {\n offsetSamples: clip.offsetSamples,\n durationSamples: clip.durationSamples,\n startSample: clip.startSample,\n };\n // Signal provider to skip loadAudio rebuilds during the drag\n isDraggingRef.current = true;\n // Group the drag into one undo step\n if (engineRef.current) {\n engineRef.current.beginTransaction();\n } else {\n console.warn(\n '[waveform-playlist] onDragStart: engine not ready, trim will not be grouped for undo'\n );\n }\n }\n },\n [tracks, isDraggingRef, engineRef]\n );\n\n const onDragMove = React.useCallback(\n (event: Parameters<DragMoveCallback>[0]) => {\n const data = event.operation.source?.data as\n | {\n boundary?: 'left' | 'right';\n trackIndex: number;\n clipIndex: number;\n }\n | undefined;\n\n if (!data) return;\n\n // Only update for boundary trimming operations (not clip movement)\n if (!data.boundary) return;\n\n // Need original clip state to apply cumulative delta\n if (!originalClipStateRef.current) return;\n\n const { boundary, trackIndex, clipIndex } = data;\n\n // The dragmove event is dispatched BEFORE position.current is updated (happens\n // in a microtask), so the snapshot's position.current is stale. Use event.to\n // (the pointer's current coordinates from the sensor) for the correct position.\n const currentX = event.to?.x ?? event.operation.position.current.x;\n const rawDeltaX = currentX - event.operation.position.initial.x;\n const sampleDelta = rawDeltaX * samplesPerPixel;\n\n // Get original clip state (stored on drag start)\n const originalClip = originalClipStateRef.current;\n const snapFn = snapSamplePositionRef.current;\n\n // Track the effective delta after snapping (for engine.trimClip on drag end)\n let effectiveDelta = sampleDelta;\n\n // Update tracks in real-time during drag\n const newTracks = tracks.map((track, tIdx) => {\n if (tIdx !== trackIndex) return track;\n\n const sortedClips = [...track.clips].sort((a, b) => a.startSample - b.startSample);\n const sortedIndex = sortedClips.findIndex((clip) => clip === track.clips[clipIndex]);\n\n const newClips = track.clips.map((clip, cIdx) => {\n if (cIdx !== clipIndex) return clip;\n\n let trimResult = calculateBoundaryTrim({\n originalClip,\n clip,\n pixelDelta: rawDeltaX,\n samplesPerPixel,\n sampleRate,\n boundary,\n sortedClips,\n sortedIndex,\n });\n\n // Snap the boundary position to the grid if a snap function is provided\n if (snapFn) {\n if (boundary === 'left') {\n const snappedStart = snapFn(trimResult.startSample);\n const delta = snappedStart - trimResult.startSample;\n trimResult = {\n startSample: snappedStart,\n durationSamples: trimResult.durationSamples - delta,\n offsetSamples: trimResult.offsetSamples + delta,\n };\n // Effective delta = how far the start moved from original\n effectiveDelta = snappedStart - originalClip.startSample;\n } else {\n const endSample = trimResult.startSample + trimResult.durationSamples;\n const snappedEnd = snapFn(endSample);\n trimResult = {\n ...trimResult,\n durationSamples: snappedEnd - trimResult.startSample,\n };\n // Effective delta = how much the duration changed from original\n effectiveDelta = trimResult.durationSamples - originalClip.durationSamples;\n }\n }\n\n return {\n ...clip,\n startSample: trimResult.startSample,\n durationSamples: trimResult.durationSamples,\n offsetSamples: trimResult.offsetSamples,\n };\n });\n\n return { ...track, clips: newClips };\n });\n\n lastBoundaryDeltaRef.current = effectiveDelta;\n onTracksChange(newTracks);\n },\n [tracks, onTracksChange, samplesPerPixel, sampleRate]\n );\n\n const onDragEnd = React.useCallback(\n (event: Parameters<DragEndCallback>[0]) => {\n // Handle canceled drags (focus loss, Escape key, component unmount).\n // Without this, isDraggingRef stays true and loadAudio skips rebuilds permanently.\n if (event.canceled) {\n // Revert React state for boundary trims — onDragMove updated tracks per-frame,\n // but the engine still has original positions (isDraggingRef blocked rebuilds).\n // For clip moves, originalClipStateRef is null so this block is skipped —\n // no React state revert needed because the Feedback plugin handles the visual\n // position (CSS translate removed on cancel) and engine was never mutated.\n if (originalClipStateRef.current) {\n const cancelData = event.operation.source?.data as\n | { trackIndex: number; clipIndex: number }\n | undefined;\n if (cancelData) {\n const { trackIndex, clipIndex } = cancelData;\n const original = originalClipStateRef.current;\n const revertedTracks = tracks.map((track, tIdx) => {\n if (tIdx !== trackIndex) return track;\n const newClips = track.clips.map((clip, cIdx) => {\n if (cIdx !== clipIndex) return clip;\n return {\n ...clip,\n offsetSamples: original.offsetSamples,\n durationSamples: original.durationSamples,\n startSample: original.startSample,\n };\n });\n return { ...track, clips: newClips };\n });\n onTracksChange(revertedTracks);\n }\n }\n isDraggingRef.current = false;\n originalClipStateRef.current = null;\n lastBoundaryDeltaRef.current = 0;\n // Cancel aborts the transaction — no undo step pushed\n engineRef.current?.abortTransaction();\n return;\n }\n\n const data = event.operation.source?.data as\n | {\n clipId: string;\n trackIndex: number;\n boundary?: 'left' | 'right';\n }\n | undefined;\n\n if (!data) {\n // Transaction was opened in onDragStart — abort to prevent leak\n isDraggingRef.current = false;\n engineRef.current?.abortTransaction();\n return;\n }\n\n const { trackIndex, clipId, boundary } = data;\n\n // Boundary trims: use the last delta stored by onDragMove (avoids stale snapshot position).\n // Clip moves: use transform.x (modifier-constrained, cached as plain object in snapshot).\n const sampleDelta = boundary\n ? lastBoundaryDeltaRef.current\n : event.operation.transform.x * samplesPerPixel;\n\n const trackId = tracks[trackIndex]?.id;\n\n // Boundary trim: onDragMove updated React state per-frame for visuals.\n // isDraggingRef prevented loadAudio from rebuilding the engine, so the\n // engine still has the original (pre-drag) clip positions. Commit the\n // final delta via engine.trimClip() so the adapter has correct positions.\n if (boundary) {\n isDraggingRef.current = false;\n if (!trackId) {\n console.warn(\n `[waveform-playlist] onDragEnd: track at index ${trackIndex} not found — trim not synced to adapter`\n );\n engineRef.current?.abortTransaction();\n } else if (!engineRef.current) {\n console.warn('[waveform-playlist] engineRef is null — trim not synced to adapter');\n } else {\n engineRef.current.trimClip(trackId, clipId, boundary, Math.floor(sampleDelta));\n engineRef.current.commitTransaction();\n }\n originalClipStateRef.current = null;\n lastBoundaryDeltaRef.current = 0;\n return;\n }\n\n // Clip move: delegate to engine in one shot\n if (!trackId) {\n console.warn(\n `[waveform-playlist] onDragEnd: track at index ${trackIndex} not found — move not synced to adapter`\n );\n engineRef.current?.abortTransaction();\n } else if (!engineRef.current) {\n console.warn('[waveform-playlist] engineRef is null — move not synced to adapter');\n } else {\n engineRef.current.moveClip(trackId, clipId, Math.floor(sampleDelta));\n engineRef.current.commitTransaction();\n }\n },\n [tracks, onTracksChange, samplesPerPixel, engineRef, isDraggingRef]\n );\n\n return {\n onDragStart,\n onDragMove,\n onDragEnd,\n };\n}\n","import type { AudioClip } from '@waveform-playlist/core';\nimport { constrainBoundaryTrim } from '@waveform-playlist/engine';\n\n/**\n * Result of a boundary trim calculation — the new clip dimensions\n * after dragging a left or right boundary by a given pixel delta.\n */\nexport interface BoundaryTrimResult {\n startSample: number;\n durationSamples: number;\n offsetSamples: number;\n}\n\nexport interface CalculateBoundaryTrimParams {\n /** The original clip state captured at drag start */\n originalClip: Pick<AudioClip, 'startSample' | 'durationSamples' | 'offsetSamples'>;\n /** The current clip with full AudioClip shape (used for constraint calculation) */\n clip: AudioClip;\n /** Raw pixel delta from drag start (positive = rightward) */\n pixelDelta: number;\n /** Samples per pixel (zoom level) */\n samplesPerPixel: number;\n /** Audio sample rate (used to compute minimum duration) */\n sampleRate: number;\n /** Which boundary is being dragged */\n boundary: 'left' | 'right';\n /** Clips on the same track, sorted by startSample (for collision detection) */\n sortedClips: AudioClip[];\n /** Index of the clip being trimmed within sortedClips */\n sortedIndex: number;\n}\n\n/**\n * Pure function that calculates new clip dimensions after a boundary trim drag.\n *\n * Converts a pixel delta to sample delta, constrains it via the engine's\n * `constrainBoundaryTrim`, and applies the constrained delta to the original\n * clip snapshot to produce new startSample, durationSamples, and offsetSamples.\n *\n * For left boundary: startSample and offsetSamples increase by the constrained delta,\n * durationSamples decreases by the same amount.\n *\n * For right boundary: only durationSamples changes (increases by the constrained delta).\n */\nexport function calculateBoundaryTrim({\n originalClip,\n clip,\n pixelDelta,\n samplesPerPixel,\n sampleRate,\n boundary,\n sortedClips,\n sortedIndex,\n}: CalculateBoundaryTrimParams): BoundaryTrimResult {\n const sampleDelta = pixelDelta * samplesPerPixel;\n const MIN_DURATION_SAMPLES = Math.floor(0.1 * sampleRate);\n\n // Build a temporary clip with original state for constraint calculation\n const tempClip: AudioClip = {\n ...clip,\n startSample: originalClip.startSample,\n offsetSamples: originalClip.offsetSamples,\n durationSamples: originalClip.durationSamples,\n };\n\n const constrainedDelta = constrainBoundaryTrim(\n tempClip,\n Math.floor(sampleDelta),\n boundary,\n sortedClips,\n sortedIndex,\n MIN_DURATION_SAMPLES\n );\n\n if (boundary === 'left') {\n return {\n startSample: originalClip.startSample + constrainedDelta,\n durationSamples: originalClip.durationSamples - constrainedDelta,\n offsetSamples: originalClip.offsetSamples + constrainedDelta,\n };\n } else {\n return {\n startSample: originalClip.startSample,\n durationSamples: originalClip.durationSamples + constrainedDelta,\n offsetSamples: originalClip.offsetSamples,\n };\n }\n}\n","import React from 'react';\nimport type {\n DragStartEvent as DragStartCallback,\n DragMoveEvent as DragMoveCallback,\n DragEndEvent as DragEndCallback,\n} from '@dnd-kit/abstract';\nimport type { AnnotationData } from '@waveform-playlist/core';\n\nconst LINK_THRESHOLD = 0.01; // Consider edges \"linked\" if within 10ms\n\ninterface UseAnnotationDragHandlersOptions {\n annotations: AnnotationData[];\n onAnnotationsChange: (annotations: AnnotationData[]) => void;\n samplesPerPixel: number;\n /** Sample rate for pixel-to-time conversion. Providers pass the real rate from\n * context; defaults to 48000 (the engine default) for out-of-provider usage. */\n sampleRate?: number;\n duration: number;\n linkEndpoints: boolean;\n}\n\n/**\n * Custom hook for handling annotation drag operations (boundary trimming)\n *\n * Provides drag handlers for use with @dnd-kit/react DragDropProvider.\n * Handles annotation boundary resizing with linked endpoints support.\n *\n * @example\n * ```tsx\n * const { onDragStart, onDragMove, onDragEnd } = useAnnotationDragHandlers({\n * annotations,\n * onAnnotationsChange: setAnnotations,\n * samplesPerPixel,\n * duration,\n * linkEndpoints,\n * });\n *\n * return (\n * <DragDropProvider\n * onDragStart={onDragStart}\n * onDragMove={onDragMove}\n * onDragEnd={onDragEnd}\n * modifiers={[RestrictToHorizontalAxis]}\n * >\n * {renderAnnotations()}\n * </DragDropProvider>\n * );\n * ```\n */\nexport function useAnnotationDragHandlers({\n annotations,\n onAnnotationsChange,\n samplesPerPixel,\n // Default mirrors the engine default; the providers always pass the real rate from context (#510).\n sampleRate = 48000,\n duration,\n linkEndpoints,\n}: UseAnnotationDragHandlersOptions) {\n // Store original annotation state when drag starts (for cumulative delta application)\n const originalAnnotationStateRef = React.useRef<{\n start: number;\n end: number;\n annotationIndex: number;\n } | null>(null);\n\n const onDragStart = React.useCallback(\n (event: Parameters<DragStartCallback>[0]) => {\n const data = event.operation.source?.data as\n | {\n annotationId: string;\n annotationIndex: number;\n edge: 'start' | 'end';\n }\n | undefined;\n\n if (!data || data.annotationIndex === undefined) {\n originalAnnotationStateRef.current = null;\n return;\n }\n\n const annotation = annotations[data.annotationIndex];\n if (annotation) {\n originalAnnotationStateRef.current = {\n start: annotation.start,\n end: annotation.end,\n annotationIndex: data.annotationIndex,\n };\n }\n },\n [annotations]\n );\n\n const onDragMove = React.useCallback(\n (event: Parameters<DragMoveCallback>[0]) => {\n if (!originalAnnotationStateRef.current) {\n return;\n }\n\n const data = event.operation.source?.data as\n | {\n annotationId: string;\n annotationIndex: number;\n edge: 'start' | 'end';\n }\n | undefined;\n\n if (!data) return;\n\n const { edge, annotationIndex } = data;\n const originalState = originalAnnotationStateRef.current;\n\n // The dragmove event is dispatched BEFORE position.current is updated (happens\n // in a microtask), so the snapshot's position.current is stale. Use event.to\n // (the pointer's current coordinates from the sensor) for the correct position.\n const currentX = event.to?.x ?? event.operation.position.current.x;\n const rawDeltaX = currentX - event.operation.position.initial.x;\n const timeDelta = (rawDeltaX * samplesPerPixel) / sampleRate;\n\n // Apply delta to original state\n const newTime =\n edge === 'start' ? originalState.start + timeDelta : originalState.end + timeDelta;\n\n // Update annotations using the boundary logic\n const updatedAnnotations = updateAnnotationBoundaries({\n annotationIndex,\n newTime,\n isDraggingStart: edge === 'start',\n annotations,\n duration,\n linkEndpoints,\n });\n\n onAnnotationsChange(updatedAnnotations);\n },\n [annotations, onAnnotationsChange, samplesPerPixel, sampleRate, duration, linkEndpoints]\n );\n\n const onDragEnd = React.useCallback(\n (event: Parameters<DragEndCallback>[0]) => {\n // Handle canceled drags — revert annotations to pre-drag state\n if (event.canceled && originalAnnotationStateRef.current) {\n const { annotationIndex, start, end } = originalAnnotationStateRef.current;\n const revertedAnnotations = annotations.map((annotation, idx) => {\n if (idx !== annotationIndex) return annotation;\n return { ...annotation, start, end };\n });\n onAnnotationsChange(revertedAnnotations);\n }\n originalAnnotationStateRef.current = null;\n },\n [annotations, onAnnotationsChange]\n );\n\n return {\n onDragStart,\n onDragMove,\n onDragEnd,\n };\n}\n\n/**\n * Updates annotation boundaries based on drag operations.\n * Handles linked endpoints and collision detection.\n */\nfunction updateAnnotationBoundaries({\n annotationIndex,\n newTime,\n isDraggingStart,\n annotations,\n duration,\n linkEndpoints: shouldLinkEndpoints,\n}: {\n annotationIndex: number;\n newTime: number;\n isDraggingStart: boolean;\n annotations: AnnotationData[];\n duration: number;\n linkEndpoints: boolean;\n}): AnnotationData[] {\n const updatedAnnotations = [...annotations];\n const annotation = annotations[annotationIndex];\n\n if (isDraggingStart) {\n // Dragging start edge\n const constrainedStart = Math.min(annotation.end - 0.1, Math.max(0, newTime));\n const delta = constrainedStart - annotation.start;\n\n updatedAnnotations[annotationIndex] = {\n ...annotation,\n start: constrainedStart,\n };\n\n if (shouldLinkEndpoints && annotationIndex > 0) {\n // Link Endpoints mode: handle both already-linked and collision scenarios\n const prevAnnotation = updatedAnnotations[annotationIndex - 1];\n\n if (Math.abs(prevAnnotation.end - annotation.start) < LINK_THRESHOLD) {\n // Already linked: move previous annotation's end together with this start\n updatedAnnotations[annotationIndex - 1] = {\n ...prevAnnotation,\n end: Math.max(prevAnnotation.start + 0.1, prevAnnotation.end + delta),\n };\n } else if (constrainedStart <= prevAnnotation.end) {\n // Dragged past previous annotation: snap to link them together\n updatedAnnotations[annotationIndex] = {\n ...updatedAnnotations[annotationIndex],\n start: prevAnnotation.end,\n };\n }\n } else if (\n !shouldLinkEndpoints &&\n annotationIndex > 0 &&\n constrainedStart < updatedAnnotations[annotationIndex - 1].end\n ) {\n // Collision detection: push previous annotation's end back\n updatedAnnotations[annotationIndex - 1] = {\n ...updatedAnnotations[annotationIndex - 1],\n end: constrainedStart,\n };\n }\n } else {\n // Dragging end edge\n const constrainedEnd = Math.max(annotation.start + 0.1, Math.min(newTime, duration));\n const delta = constrainedEnd - annotation.end;\n\n updatedAnnotations[annotationIndex] = {\n ...annotation,\n end: constrainedEnd,\n };\n\n if (shouldLinkEndpoints && annotationIndex < updatedAnnotations.length - 1) {\n // Link Endpoints mode: handle both already-linked and collision scenarios\n const nextAnnotation = updatedAnnotations[annotationIndex + 1];\n\n if (Math.abs(nextAnnotation.start - annotation.end) < LINK_THRESHOLD) {\n // Already linked: move next annotation's start together with this end\n const newStart = nextAnnotation.start + delta;\n updatedAnnotations[annotationIndex + 1] = {\n ...nextAnnotation,\n start: Math.min(nextAnnotation.end - 0.1, newStart),\n };\n\n // Cascade linked endpoints\n let currentIndex = annotationIndex + 1;\n while (currentIndex < updatedAnnotations.length - 1) {\n const current = updatedAnnotations[currentIndex];\n const next = updatedAnnotations[currentIndex + 1];\n\n if (Math.abs(next.start - current.end) < LINK_THRESHOLD) {\n const nextDelta = current.end - annotations[currentIndex].end;\n updatedAnnotations[currentIndex + 1] = {\n ...next,\n start: Math.min(next.end - 0.1, next.start + nextDelta),\n };\n currentIndex++;\n } else {\n break; // No more linked endpoints\n }\n }\n } else if (constrainedEnd >= nextAnnotation.start) {\n // Dragged past next annotation: snap to link them together\n updatedAnnotations[annotationIndex] = {\n ...updatedAnnotations[annotationIndex],\n end: nextAnnotation.start,\n };\n }\n } else if (\n !shouldLinkEndpoints &&\n annotationIndex < updatedAnnotations.length - 1 &&\n constrainedEnd > updatedAnnotations[annotationIndex + 1].start\n ) {\n // Collision detection: push next annotation's start forward\n const nextAnnotation = updatedAnnotations[annotationIndex + 1];\n\n updatedAnnotations[annotationIndex + 1] = {\n ...nextAnnotation,\n start: constrainedEnd,\n };\n\n // Cascade collisions\n let currentIndex = annotationIndex + 1;\n while (currentIndex < updatedAnnotations.length - 1) {\n const current = updatedAnnotations[currentIndex];\n const next = updatedAnnotations[currentIndex + 1];\n\n if (current.end > next.start) {\n updatedAnnotations[currentIndex + 1] = {\n ...next,\n start: current.end,\n };\n currentIndex++;\n } else {\n break; // No more collisions\n }\n }\n }\n }\n\n return updatedAnnotations;\n}\n","/**\n * Hook for configuring @dnd-kit sensors for clip dragging\n *\n * Provides consistent drag activation behavior across all examples.\n * Always overrides PointerSensor defaults with custom activation constraints:\n * - Default mode: distance-based activation (1px) for all pointer types\n * - Touch-optimized mode: delay-based activation for touch (250ms),\n * distance-based for mouse/pen\n */\n\nimport { useMemo } from 'react';\nimport { PointerSensor, PointerActivationConstraints } from '@dnd-kit/dom';\nimport type { PluginDescriptor } from '@dnd-kit/abstract';\n\nexport interface DragSensorOptions {\n /**\n * Enable mobile-optimized touch handling with delay-based activation.\n * When true, touch events get delay-based activation while mouse/pen get distance-based.\n * When false (default), all pointer types use distance-based activation (1px).\n */\n touchOptimized?: boolean;\n /**\n * Delay in milliseconds before touch drag activates (only when touchOptimized is true).\n * Default: 250ms - long enough to distinguish from scroll intent\n */\n touchDelay?: number;\n /**\n * Distance tolerance during touch delay (only when touchOptimized is true).\n * If finger moves more than this during delay, drag is cancelled.\n * Default: 5px - allows slight finger movement\n */\n touchTolerance?: number;\n /**\n * Distance in pixels before mouse drag activates.\n * Default: 1px for immediate feedback on desktop\n */\n mouseDistance?: number;\n}\n\n/**\n * Returns configured sensors for @dnd-kit drag operations\n *\n * @param options - Configuration options for drag sensors\n * @returns Array of sensor constructors/descriptors for DragDropProvider's sensors prop\n *\n * @example\n * // Desktop-optimized (default — 1px distance activation for all pointer types)\n * const sensors = useDragSensors();\n *\n * @example\n * // Mobile-optimized with custom touch delay\n * const sensors = useDragSensors({ touchOptimized: true, touchDelay: 300 });\n */\nexport function useDragSensors(\n options: DragSensorOptions = {}\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (typeof PointerSensor | PluginDescriptor<any, any, any>)[] {\n const {\n touchOptimized = false,\n touchDelay = 250,\n touchTolerance = 5,\n mouseDistance = 1,\n } = options;\n\n return useMemo(() => {\n if (touchOptimized) {\n // Custom activation constraints for touch-optimized mode\n return [\n PointerSensor.configure({\n activationConstraints(event) {\n // Touch events get delay-based activation\n if (event.pointerType === 'touch') {\n return [\n new PointerActivationConstraints.Delay({\n value: touchDelay,\n tolerance: touchTolerance,\n }),\n ];\n }\n // Mouse/pen get distance-based activation\n return [new PointerActivationConstraints.Distance({ value: mouseDistance })];\n },\n }),\n ];\n }\n\n // Default: PointerSensor with distance-based activation\n return [\n PointerSensor.configure({\n activationConstraints: [\n new PointerActivationConstraints.Distance({ value: mouseDistance }),\n ],\n }),\n ];\n }, [touchOptimized, touchDelay, touchTolerance, mouseDistance]);\n}\n","import React, { useCallback } from 'react';\nimport type { ClipTrack } from '@waveform-playlist/core';\nimport { calculateSplitPoint, canSplitAt } from '@waveform-playlist/engine';\nimport type { PlaylistEngine } from '@waveform-playlist/engine';\nimport {\n usePlaybackAnimation,\n usePlaylistData,\n usePlaylistState,\n} from '../WaveformPlaylistContext';\n\nexport interface UseClipSplittingOptions {\n tracks: ClipTrack[];\n samplesPerPixel: number;\n engineRef: React.RefObject<PlaylistEngine | null>;\n}\n\nexport interface UseClipSplittingResult {\n splitClipAtPlayhead: () => boolean;\n splitClipAt: (trackIndex: number, clipIndex: number, splitTime: number) => boolean;\n}\n\n/**\n * Hook for splitting clips at the playhead or at a specific time\n *\n * Splitting delegates to `engine.splitClip()` — the engine handles clip creation,\n * adapter sync, and emits statechange. The provider's statechange handler propagates\n * the updated tracks to the parent via `onTracksChange`.\n *\n * @param options - Configuration options\n * @returns Object with split functions\n *\n * @example\n * ```tsx\n * const { splitClipAtPlayhead } = useClipSplitting({\n * tracks,\n * samplesPerPixel,\n * engineRef: playoutRef,\n * });\n *\n * // In keyboard handler\n * const handleKeyPress = (e: KeyboardEvent) => {\n * if (e.key === 's' || e.key === 'S') {\n * splitClipAtPlayhead();\n * }\n * };\n * ```\n */\nexport const useClipSplitting = (options: UseClipSplittingOptions): UseClipSplittingResult => {\n const { tracks, engineRef } = options;\n const { sampleRate } = usePlaylistData();\n const { currentTimeRef } = usePlaybackAnimation();\n const { selectedTrackId } = usePlaylistState();\n\n /**\n * Split a specific clip at a given time\n *\n * @param trackIndex - Index of the track containing the clip\n * @param clipIndex - Index of the clip within the track\n * @param splitTime - Timeline position where to split (in seconds)\n * @returns true if pre-validation passed and the engine was called, false otherwise.\n * Note: engine.splitClip() returns void so a true here assumes the engine accepted\n * the split (it performs its own canSplitAt check internally).\n */\n const splitClipAt = useCallback(\n (trackIndex: number, clipIndex: number, splitTime: number): boolean => {\n const { samplesPerPixel } = options;\n\n const track = tracks[trackIndex];\n if (!track) return false;\n\n const clip = track.clips[clipIndex];\n if (!clip) return false;\n\n // Convert split time to sample position, snapped to pixel boundary\n const splitSample = Math.round(splitTime * sampleRate);\n const snappedSplitSample = calculateSplitPoint(splitSample, samplesPerPixel);\n\n // Pre-flight validation before sending to engine (engine also validates internally).\n // Duplicated here to provide early user feedback via console.warn.\n const MIN_DURATION_SAMPLES = Math.floor(0.1 * sampleRate);\n if (!canSplitAt(clip, snappedSplitSample, MIN_DURATION_SAMPLES)) {\n console.warn('Split point is invalid (outside bounds or too close to edge)');\n return false;\n }\n\n // Delegate to engine — handles clip creation, adapter sync, and emits statechange\n const engine = engineRef.current;\n if (!engine) {\n console.warn('[waveform-playlist] engineRef is null — split not synced to adapter');\n return false;\n }\n\n engine.splitClip(track.id, clip.id, snappedSplitSample);\n return true;\n },\n [tracks, options, engineRef, sampleRate]\n );\n\n /**\n * Split clip at the current playhead position on the selected track\n * If no track is selected, does nothing\n *\n * @returns true if a clip was split, false otherwise\n */\n const splitClipAtPlayhead = useCallback((): boolean => {\n // If no track is selected, cannot split\n if (!selectedTrackId) {\n console.warn('[waveform-playlist] No track selected — click a clip to select a track first');\n return false;\n }\n\n // Find the selected track\n const trackIndex = tracks.findIndex((track) => track.id === selectedTrackId);\n if (trackIndex === -1) {\n console.warn('Selected track not found');\n return false;\n }\n\n const track = tracks[trackIndex];\n\n // Use ref for real-time position during playback (state updates are throttled)\n const currentTime = currentTimeRef.current ?? 0;\n\n // Find clip at current time on the selected track\n for (let clipIndex = 0; clipIndex < track.clips.length; clipIndex++) {\n const clip = track.clips[clipIndex];\n const clipStartTime = clip.startSample / sampleRate;\n const clipEndTime = (clip.startSample + clip.durationSamples) / sampleRate;\n\n // Check if currentTime is within this clip (not at boundaries)\n if (currentTime > clipStartTime && currentTime < clipEndTime) {\n // Found a clip! Split it\n return splitClipAt(trackIndex, clipIndex, currentTime);\n }\n }\n\n console.warn(`[waveform-playlist] No clip found at playhead position on track \"${track.name}\"`);\n return false;\n }, [tracks, currentTimeRef, selectedTrackId, splitClipAt, sampleRate]);\n\n return {\n splitClipAtPlayhead,\n splitClipAt,\n };\n};\n","import { useEffect, useCallback } from 'react';\nimport { handleKeyboardEvent } from '@waveform-playlist/core';\nimport type { KeyboardShortcut } from '@waveform-playlist/core';\n\n// Re-export from core for backwards compatibility\nexport type { KeyboardShortcut } from '@waveform-playlist/core';\nexport { handleKeyboardEvent, getShortcutLabel } from '@waveform-playlist/core';\n\nexport interface UseKeyboardShortcutsOptions {\n shortcuts: KeyboardShortcut[];\n enabled?: boolean;\n}\n\n/**\n * Hook for managing keyboard shortcuts\n *\n * @param options - Configuration options\n *\n * @example\n * ```tsx\n * useKeyboardShortcuts({\n * shortcuts: [\n * {\n * key: ' ',\n * action: togglePlayPause,\n * description: 'Play/Pause',\n * preventDefault: true,\n * },\n * {\n * key: 's',\n * action: splitClipAtPlayhead,\n * description: 'Split clip at playhead',\n * preventDefault: true,\n * },\n * ],\n * });\n * ```\n */\nexport const useKeyboardShortcuts = (options: UseKeyboardShortcutsOptions): void => {\n const { shortcuts, enabled = true } = options;\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent) => handleKeyboardEvent(event, shortcuts, enabled),\n [shortcuts, enabled]\n );\n\n useEffect(() => {\n if (!enabled) return;\n\n window.addEventListener('keydown', handleKeyDown);\n\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n };\n }, [handleKeyDown, enabled]);\n};\n","import { useCallback } from 'react';\nimport { usePlaybackAnimation, usePlaylistControls } from '../WaveformPlaylistContext';\nimport { useKeyboardShortcuts, type KeyboardShortcut } from './useKeyboardShortcuts';\n\nexport interface UsePlaybackShortcutsOptions {\n /**\n * Enable the shortcuts. Defaults to true.\n */\n enabled?: boolean;\n /**\n * Additional shortcuts to include alongside the default playback shortcuts.\n */\n additionalShortcuts?: KeyboardShortcut[];\n /**\n * Override default shortcuts. If provided, only these shortcuts will be used.\n */\n shortcuts?: KeyboardShortcut[];\n}\n\nexport interface UsePlaybackShortcutsReturn {\n /** Rewind to the beginning (time = 0) */\n rewindToStart: () => void;\n /** Toggle play/pause */\n togglePlayPause: () => void;\n /** Stop playback and return to start position */\n stopPlayback: () => void;\n /** The list of active keyboard shortcuts */\n shortcuts: KeyboardShortcut[];\n}\n\n/**\n * Hook that provides common playback keyboard shortcuts for the playlist.\n *\n * Default shortcuts:\n * - `Space` - Toggle play/pause\n * - `Escape` - Stop playback\n * - `0` - Rewind to start (seek to time 0)\n *\n * @example\n * ```tsx\n * // Basic usage - enables default shortcuts\n * usePlaybackShortcuts();\n *\n * // With additional custom shortcuts\n * usePlaybackShortcuts({\n * additionalShortcuts: [\n * { key: 's', action: splitClipAtPlayhead, description: 'Split clip' },\n * ],\n * });\n *\n * // Completely override shortcuts\n * usePlaybackShortcuts({\n * shortcuts: [\n * { key: 'Home', action: rewindToStart, description: 'Go to start' },\n * ],\n * });\n * ```\n */\nexport const usePlaybackShortcuts = (\n options: UsePlaybackShortcutsOptions = {}\n): UsePlaybackShortcutsReturn => {\n const { enabled = true, additionalShortcuts = [], shortcuts: overrideShortcuts } = options;\n\n const { isPlaying } = usePlaybackAnimation();\n const { setCurrentTime, play, pause, stop } = usePlaylistControls();\n\n /**\n * Toggle between play and pause.\n */\n const togglePlayPause = useCallback(() => {\n if (isPlaying) {\n pause();\n } else {\n play();\n }\n }, [isPlaying, play, pause]);\n\n /**\n * Stop playback and return to start position.\n */\n const stopPlayback = useCallback(() => {\n stop();\n }, [stop]);\n\n /**\n * Rewind to the beginning of the timeline.\n * If playing, stops and restarts playback from the beginning.\n */\n const rewindToStart = useCallback(() => {\n setCurrentTime(0);\n\n if (isPlaying) {\n play(0);\n }\n }, [isPlaying, setCurrentTime, play]);\n\n // Default playback shortcuts\n const defaultShortcuts: KeyboardShortcut[] = [\n {\n key: ' ',\n action: togglePlayPause,\n description: 'Play/Pause',\n preventDefault: true,\n },\n {\n key: 'Escape',\n action: stopPlayback,\n description: 'Stop',\n preventDefault: true,\n },\n {\n key: '0',\n action: rewindToStart,\n description: 'Rewind to start',\n preventDefault: true,\n },\n ];\n\n // Use override shortcuts if provided, otherwise combine defaults with additional\n const activeShortcuts = overrideShortcuts ?? [...defaultShortcuts, ...additionalShortcuts];\n\n // Register the keyboard shortcuts\n useKeyboardShortcuts({\n shortcuts: activeShortcuts,\n enabled,\n });\n\n return {\n rewindToStart,\n togglePlayPause,\n stopPlayback,\n shortcuts: activeShortcuts,\n };\n};\n","import { useCallback, useMemo, useEffect } from 'react';\nimport type { AnnotationData } from '@waveform-playlist/core';\nimport { usePlaylistDataOptional } from '../WaveformPlaylistContext';\nimport { useKeyboardShortcuts } from './useKeyboardShortcuts';\n\nconst LINK_THRESHOLD = 0.01; // Consider edges \"linked\" if within 10ms\nconst TIME_DELTA = 0.01; // 10ms adjustment per keypress\n\ninterface UseAnnotationKeyboardControlsOptions {\n annotations: AnnotationData[];\n activeAnnotationId: string | null;\n onAnnotationsChange: (annotations: AnnotationData[]) => void;\n /** Callback to set the active annotation ID for selection */\n onActiveAnnotationChange?: (id: string | null) => void;\n duration: number;\n linkEndpoints: boolean;\n /** Whether continuous play is enabled (affects playback duration) */\n continuousPlay?: boolean;\n enabled?: boolean;\n /** Optional: scroll container ref for auto-scrolling to annotation */\n scrollContainerRef?: React.RefObject<HTMLDivElement | null>;\n /** Optional: callback to start playback at a time with optional duration */\n onPlay?: (startTime: number, duration?: number) => void;\n /**\n * Pixels-per-sample for auto-scroll positioning. Falls back to the\n * WaveformPlaylistProvider context when omitted (WebAudio path). Pass\n * explicitly when used outside that provider (e.g. the MediaElement path).\n */\n samplesPerPixel?: number;\n /** Sample rate for auto-scroll positioning. Falls back to context. */\n sampleRate?: number;\n}\n\n/**\n * Hook for keyboard-based annotation navigation and boundary editing\n *\n * Navigation Shortcuts:\n * - ArrowUp / ArrowLeft = Select previous annotation\n * - ArrowDown / ArrowRight = Select next annotation\n * - Home = Select first annotation\n * - End = Select last annotation\n * - Escape = Deselect annotation\n * - Enter = Play selected annotation\n *\n * Boundary Editing Shortcuts (requires active annotation):\n * - [ = Move start boundary earlier (left)\n * - ] = Move start boundary later (right)\n * - Shift+[ = Move end boundary earlier (left)\n * - Shift+] = Move end boundary later (right)\n *\n * Respects linkEndpoints and continuousPlay settings.\n *\n * @example\n * ```tsx\n * useAnnotationKeyboardControls({\n * annotations,\n * activeAnnotationId,\n * onAnnotationsChange: setAnnotations,\n * onActiveAnnotationChange: setActiveAnnotationId,\n * duration,\n * linkEndpoints,\n * });\n * ```\n */\nexport function useAnnotationKeyboardControls({\n annotations,\n activeAnnotationId,\n onAnnotationsChange,\n onActiveAnnotationChange,\n duration,\n linkEndpoints,\n continuousPlay = false,\n enabled = true,\n scrollContainerRef,\n onPlay,\n samplesPerPixel: samplesPerPixelProp,\n sampleRate: sampleRateProp,\n}: UseAnnotationKeyboardControlsOptions) {\n // Auto-scroll positioning needs samplesPerPixel/sampleRate. Prefer explicit\n // props (works in the MediaElement path); otherwise read them from the\n // WaveformPlaylistProvider context when present. usePlaylistDataOptional()\n // returns null instead of throwing, so this hook is safe outside that provider.\n const playlistData = usePlaylistDataOptional();\n const samplesPerPixel = samplesPerPixelProp ?? playlistData?.samplesPerPixel;\n const sampleRate = sampleRateProp ?? playlistData?.sampleRate;\n\n const activeIndex = useMemo(() => {\n if (!activeAnnotationId) return -1;\n return annotations.findIndex((a) => a.id === activeAnnotationId);\n }, [annotations, activeAnnotationId]);\n\n // Scroll waveform to show a specific annotation\n const scrollToAnnotation = useCallback(\n (annotationId: string) => {\n if (!scrollContainerRef?.current || !samplesPerPixel || !sampleRate) return;\n\n const annotation = annotations.find((a) => a.id === annotationId);\n if (!annotation) return;\n\n const container = scrollContainerRef.current;\n const containerWidth = container.clientWidth;\n\n // Calculate pixel positions for annotation start and center\n const startPixel = (annotation.start * sampleRate) / samplesPerPixel;\n const endPixel = (annotation.end * sampleRate) / samplesPerPixel;\n const annotationCenter = (startPixel + endPixel) / 2;\n\n // Check if annotation is currently visible\n const scrollLeft = container.scrollLeft;\n const visibleStart = scrollLeft;\n const visibleEnd = scrollLeft + containerWidth;\n\n // If annotation is not fully visible, scroll to center it\n if (startPixel < visibleStart || endPixel > visibleEnd) {\n const targetScrollLeft = Math.max(0, annotationCenter - containerWidth / 2);\n container.scrollTo({\n left: targetScrollLeft,\n behavior: 'smooth',\n });\n }\n },\n [annotations, scrollContainerRef, samplesPerPixel, sampleRate]\n );\n\n // Auto-scroll when active annotation changes via keyboard navigation\n useEffect(() => {\n if (activeAnnotationId && scrollContainerRef?.current && samplesPerPixel && sampleRate) {\n scrollToAnnotation(activeAnnotationId);\n }\n }, [activeAnnotationId, scrollToAnnotation, scrollContainerRef, samplesPerPixel, sampleRate]);\n\n const moveStartBoundary = useCallback(\n (delta: number) => {\n if (activeIndex < 0) return;\n\n const annotation = annotations[activeIndex];\n const newStart = Math.max(0, Math.min(annotation.end - 0.1, annotation.start + delta));\n const actualDelta = newStart - annotation.start;\n\n const updatedAnnotations = [...annotations];\n updatedAnnotations[activeIndex] = {\n ...annotation,\n start: newStart,\n };\n\n // Handle linked endpoints\n if (linkEndpoints && activeIndex > 0) {\n const prevAnnotation = updatedAnnotations[activeIndex - 1];\n if (Math.abs(prevAnnotation.end - annotation.start) < LINK_THRESHOLD) {\n // Already linked: move previous annotation's end together\n updatedAnnotations[activeIndex - 1] = {\n ...prevAnnotation,\n end: Math.max(prevAnnotation.start + 0.1, prevAnnotation.end + actualDelta),\n };\n }\n } else if (!linkEndpoints && activeIndex > 0) {\n // Non-linked mode: don't overlap previous annotation\n const prevAnnotation = updatedAnnotations[activeIndex - 1];\n if (newStart < prevAnnotation.end) {\n // Push back previous annotation's end\n updatedAnnotations[activeIndex - 1] = {\n ...prevAnnotation,\n end: newStart,\n };\n }\n }\n\n onAnnotationsChange(updatedAnnotations);\n },\n [annotations, activeIndex, linkEndpoints, onAnnotationsChange]\n );\n\n const moveEndBoundary = useCallback(\n (delta: number) => {\n if (activeIndex < 0) return;\n\n const annotation = annotations[activeIndex];\n const newEnd = Math.max(annotation.start + 0.1, Math.min(duration, annotation.end + delta));\n const actualDelta = newEnd - annotation.end;\n\n const updatedAnnotations = [...annotations];\n updatedAnnotations[activeIndex] = {\n ...annotation,\n end: newEnd,\n };\n\n // Handle linked endpoints\n if (linkEndpoints && activeIndex < annotations.length - 1) {\n const nextAnnotation = updatedAnnotations[activeIndex + 1];\n if (Math.abs(nextAnnotation.start - annotation.end) < LINK_THRESHOLD) {\n // Already linked: move next annotation's start together\n const newNextStart = Math.min(\n nextAnnotation.end - 0.1,\n nextAnnotation.start + actualDelta\n );\n updatedAnnotations[activeIndex + 1] = {\n ...nextAnnotation,\n start: newNextStart,\n };\n\n // Cascade linked endpoints\n let currentIndex = activeIndex + 1;\n while (currentIndex < updatedAnnotations.length - 1) {\n const current = updatedAnnotations[currentIndex];\n const next = updatedAnnotations[currentIndex + 1];\n\n if (Math.abs(next.start - annotations[currentIndex].end) < LINK_THRESHOLD) {\n const nextDelta = current.end - annotations[currentIndex].end;\n updatedAnnotations[currentIndex + 1] = {\n ...next,\n start: Math.min(next.end - 0.1, next.start + nextDelta),\n };\n currentIndex++;\n } else {\n break;\n }\n }\n }\n } else if (!linkEndpoints && activeIndex < annotations.length - 1) {\n // Non-linked mode: don't overlap next annotation\n const nextAnnotation = updatedAnnotations[activeIndex + 1];\n if (newEnd > nextAnnotation.start) {\n // Push forward next annotation's start\n updatedAnnotations[activeIndex + 1] = {\n ...nextAnnotation,\n start: newEnd,\n };\n\n // Cascade collisions\n let currentIndex = activeIndex + 1;\n while (currentIndex < updatedAnnotations.length - 1) {\n const current = updatedAnnotations[currentIndex];\n const next = updatedAnnotations[currentIndex + 1];\n\n if (current.end > next.start) {\n updatedAnnotations[currentIndex + 1] = {\n ...next,\n start: current.end,\n };\n currentIndex++;\n } else {\n break;\n }\n }\n }\n }\n\n onAnnotationsChange(updatedAnnotations);\n },\n [annotations, activeIndex, duration, linkEndpoints, onAnnotationsChange]\n );\n\n // Navigation functions\n const selectPrevious = useCallback(() => {\n if (!onActiveAnnotationChange || annotations.length === 0) return;\n\n if (activeIndex <= 0) {\n // If no selection or at first, select last annotation\n onActiveAnnotationChange(annotations[annotations.length - 1].id);\n } else {\n onActiveAnnotationChange(annotations[activeIndex - 1].id);\n }\n }, [annotations, activeIndex, onActiveAnnotationChange]);\n\n const selectNext = useCallback(() => {\n if (!onActiveAnnotationChange || annotations.length === 0) return;\n\n if (activeIndex < 0 || activeIndex >= annotations.length - 1) {\n // If no selection or at last, select first annotation\n onActiveAnnotationChange(annotations[0].id);\n } else {\n onActiveAnnotationChange(annotations[activeIndex + 1].id);\n }\n }, [annotations, activeIndex, onActiveAnnotationChange]);\n\n const selectFirst = useCallback(() => {\n if (!onActiveAnnotationChange || annotations.length === 0) return;\n onActiveAnnotationChange(annotations[0].id);\n }, [annotations, onActiveAnnotationChange]);\n\n const selectLast = useCallback(() => {\n if (!onActiveAnnotationChange || annotations.length === 0) return;\n onActiveAnnotationChange(annotations[annotations.length - 1].id);\n }, [annotations, onActiveAnnotationChange]);\n\n const clearSelection = useCallback(() => {\n if (!onActiveAnnotationChange) return;\n onActiveAnnotationChange(null);\n }, [onActiveAnnotationChange]);\n\n // Play the currently selected annotation\n const playActiveAnnotation = useCallback(() => {\n if (activeIndex < 0 || !onPlay) return;\n\n const annotation = annotations[activeIndex];\n // If continuous play is off, play just this annotation's duration\n const playDuration = !continuousPlay ? annotation.end - annotation.start : undefined;\n onPlay(annotation.start, playDuration);\n }, [annotations, activeIndex, continuousPlay, onPlay]);\n\n // Shortcuts that require an active annotation (boundary editing + playback)\n const activeAnnotationShortcuts = useMemo(\n () => [\n {\n key: '[',\n action: () => moveStartBoundary(-TIME_DELTA),\n description: 'Move annotation start earlier',\n preventDefault: true,\n },\n {\n key: ']',\n action: () => moveStartBoundary(TIME_DELTA),\n description: 'Move annotation start later',\n preventDefault: true,\n },\n {\n key: '{',\n shiftKey: true,\n action: () => moveEndBoundary(-TIME_DELTA),\n description: 'Move annotation end earlier',\n preventDefault: true,\n },\n {\n key: '}',\n shiftKey: true,\n action: () => moveEndBoundary(TIME_DELTA),\n description: 'Move annotation end later',\n preventDefault: true,\n },\n {\n key: 'Enter',\n action: playActiveAnnotation,\n description: 'Play selected annotation',\n preventDefault: true,\n },\n ],\n [moveStartBoundary, moveEndBoundary, playActiveAnnotation]\n );\n\n // Navigation shortcuts (always active when enabled and there are annotations)\n const navigationShortcuts = useMemo(\n () => [\n {\n key: 'ArrowUp',\n action: selectPrevious,\n description: 'Select previous annotation',\n preventDefault: true,\n },\n {\n key: 'ArrowLeft',\n action: selectPrevious,\n description: 'Select previous annotation',\n preventDefault: true,\n },\n {\n key: 'ArrowDown',\n action: selectNext,\n description: 'Select next annotation',\n preventDefault: true,\n },\n {\n key: 'ArrowRight',\n action: selectNext,\n description: 'Select next annotation',\n preventDefault: true,\n },\n {\n key: 'Home',\n action: selectFirst,\n description: 'Select first annotation',\n preventDefault: true,\n },\n {\n key: 'End',\n action: selectLast,\n description: 'Select last annotation',\n preventDefault: true,\n },\n {\n key: 'Escape',\n action: clearSelection,\n description: 'Deselect annotation',\n preventDefault: true,\n },\n ],\n [selectPrevious, selectNext, selectFirst, selectLast, clearSelection]\n );\n\n // Active annotation shortcuts only work when an annotation is selected\n useKeyboardShortcuts({\n shortcuts: activeAnnotationShortcuts,\n enabled: enabled && activeIndex >= 0,\n });\n\n // Navigation shortcuts work whenever there are annotations\n useKeyboardShortcuts({\n shortcuts: navigationShortcuts,\n enabled: enabled && annotations.length > 0 && !!onActiveAnnotationChange,\n });\n\n return {\n moveStartBoundary,\n moveEndBoundary,\n selectPrevious,\n selectNext,\n selectFirst,\n selectLast,\n clearSelection,\n scrollToAnnotation,\n playActiveAnnotation,\n };\n}\n","import { useCallback, useEffect, useRef } from 'react';\n\nexport interface AnimationFrameLoopControls {\n animationFrameRef: React.MutableRefObject<number | null>;\n startAnimationFrameLoop: (callback: () => void) => void;\n stopAnimationFrameLoop: () => void;\n}\n\n/**\n * Shared RAF loop controller used by playlist providers.\n * Always guarantees a single in-flight animation frame.\n */\nexport const useAnimationFrameLoop = (): AnimationFrameLoopControls => {\n const animationFrameRef = useRef<number | null>(null);\n\n const stopAnimationFrameLoop = useCallback(() => {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n }, []);\n\n const startAnimationFrameLoop = useCallback(\n (callback: () => void) => {\n stopAnimationFrameLoop();\n animationFrameRef.current = requestAnimationFrame(callback);\n },\n [stopAnimationFrameLoop]\n );\n\n useEffect(() => {\n return () => {\n stopAnimationFrameLoop();\n };\n }, [stopAnimationFrameLoop]);\n\n return {\n animationFrameRef,\n startAnimationFrameLoop,\n stopAnimationFrameLoop,\n };\n};\n","/**\n * useWaveformDataCache\n *\n * Manages a Map<clipId, WaveformData> that stores pre-computed WaveformData\n * objects generated by a web worker. These enable near-instant zoom changes\n * via WaveformData.resample() instead of re-scanning raw audio samples.\n *\n * Behavior:\n * - Creates the worker lazily on first clip needing generation\n * - Generates waveform data once per unique AudioBuffer\n * - Fans out cached/generated waveform data to all clips sharing that buffer\n * - Uses a cancelled flag to ignore state updates after effect cleanup\n * - Terminates the worker on unmount\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { ClipTrack } from '@waveform-playlist/core';\nimport type WaveformData from 'waveform-data';\nimport { createPeaksWorker, type PeaksWorkerApi } from '../workers/peaksWorker';\n\nexport interface UseWaveformDataCacheReturn {\n cache: ReadonlyMap<string, WaveformData>;\n isGenerating: boolean;\n}\n\nexport function useWaveformDataCache(\n tracks: ClipTrack[],\n baseScale: number\n): UseWaveformDataCacheReturn {\n const [cache, setCache] = useState<Map<string, WaveformData>>(() => new Map());\n const [isGenerating, setIsGenerating] = useState(false);\n\n const workerRef = useRef<PeaksWorkerApi | null>(null);\n const generatedByBufferRef = useRef<WeakMap<AudioBuffer, WaveformData>>(new WeakMap());\n const inflightByBufferRef = useRef<WeakMap<AudioBuffer, Promise<void>>>(new WeakMap());\n const subscribersByBufferRef = useRef<WeakMap<AudioBuffer, Set<string>>>(new WeakMap());\n const pendingCountRef = useRef(0);\n\n // Stable callback to get or create the worker\n const getWorker = useCallback(() => {\n if (!workerRef.current) {\n workerRef.current = createPeaksWorker();\n }\n return workerRef.current;\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n const generatedByBuffer = generatedByBufferRef.current;\n const inflightByBuffer = inflightByBufferRef.current;\n const subscribersByBuffer = subscribersByBufferRef.current;\n const immediateAssignments: Array<{ clipId: string; waveformData: WaveformData }> = [];\n const buffersToProcess = new Map<AudioBuffer, Set<string>>();\n\n for (const track of tracks) {\n for (const clip of track.clips) {\n if (!clip.audioBuffer || clip.waveformData) {\n continue;\n }\n\n const existingWaveformData = generatedByBuffer.get(clip.audioBuffer);\n if (existingWaveformData) {\n immediateAssignments.push({\n clipId: clip.id,\n waveformData: existingWaveformData,\n });\n continue;\n }\n\n if (inflightByBuffer.get(clip.audioBuffer)) {\n const existingSubscribers = subscribersByBuffer.get(clip.audioBuffer);\n if (existingSubscribers) {\n existingSubscribers.add(clip.id);\n } else {\n subscribersByBuffer.set(clip.audioBuffer, new Set([clip.id]));\n }\n continue;\n }\n\n const clipsForBuffer = buffersToProcess.get(clip.audioBuffer);\n if (clipsForBuffer) {\n clipsForBuffer.add(clip.id);\n } else {\n buffersToProcess.set(clip.audioBuffer, new Set([clip.id]));\n }\n }\n }\n\n if (immediateAssignments.length > 0) {\n setCache((prev) => {\n const next = new Map(prev);\n for (const assignment of immediateAssignments) {\n next.set(assignment.clipId, assignment.waveformData);\n }\n return next;\n });\n }\n\n if (buffersToProcess.size === 0) return;\n\n pendingCountRef.current += buffersToProcess.size;\n setIsGenerating(true);\n\n const worker = getWorker();\n\n for (const [audioBuffer, clipIds] of buffersToProcess) {\n subscribersByBuffer.set(audioBuffer, new Set(clipIds));\n\n // .slice() channel buffers to avoid detaching the original AudioBuffer views\n const channels: ArrayBuffer[] = [];\n for (let c = 0; c < audioBuffer.numberOfChannels; c++) {\n channels.push(audioBuffer.getChannelData(c).slice().buffer as ArrayBuffer);\n }\n\n const id = `buffer-${Math.random().toString(36).slice(2, 11)}`;\n const job = worker\n .generate({\n id,\n channels,\n length: audioBuffer.length,\n sampleRate: audioBuffer.sampleRate,\n scale: baseScale,\n bits: 16,\n splitChannels: true,\n })\n .then((waveformData) => {\n generatedByBuffer.set(audioBuffer, waveformData);\n const subscribers = subscribersByBuffer.get(audioBuffer) ?? new Set<string>();\n subscribersByBuffer.delete(audioBuffer);\n inflightByBuffer.delete(audioBuffer);\n if (cancelled) return;\n\n setCache((prev) => {\n const next = new Map(prev);\n for (const clipId of subscribers) {\n next.set(clipId, waveformData);\n }\n return next;\n });\n\n pendingCountRef.current--;\n if (pendingCountRef.current <= 0) {\n pendingCountRef.current = 0;\n setIsGenerating(false);\n }\n })\n .catch((err) => {\n subscribersByBuffer.delete(audioBuffer);\n inflightByBuffer.delete(audioBuffer);\n if (cancelled) return;\n console.warn('[waveform-playlist] Worker peak generation failed:', err);\n pendingCountRef.current--;\n if (pendingCountRef.current <= 0) {\n pendingCountRef.current = 0;\n setIsGenerating(false);\n }\n });\n\n inflightByBuffer.set(audioBuffer, job);\n }\n\n return () => {\n cancelled = true;\n inflightByBufferRef.current = new WeakMap();\n subscribersByBufferRef.current = new WeakMap();\n pendingCountRef.current = 0;\n setIsGenerating(false);\n };\n }, [tracks, baseScale, getWorker]);\n\n // Terminate worker on unmount\n useEffect(() => {\n return () => {\n workerRef.current?.terminate();\n workerRef.current = null;\n };\n }, []);\n\n return { cache, isGenerating };\n}\n","/**\n * Inline Web Worker for generating WaveformData binary format from AudioBuffer channels.\n *\n * Uses a Blob URL approach (portable across all bundlers) with the generateWaveformData\n * algorithm adapted from BBC's waveform-data.js (MIT licensed, adapted from Audacity).\n *\n * The worker generates peaks at a base scale (finest zoom level). The main thread\n * then uses WaveformData.resample() for near-instant zoom changes.\n */\n\nimport WaveformData from 'waveform-data';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Worker code (runs inside the Blob URL worker)\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Self-contained worker source.\n * Contains the generateWaveformData function from waveform-data.js/src/waveform-generator.js\n * (MIT License, BBC). Pure JS, zero dependencies.\n */\nconst workerSource = `\n\"use strict\";\n\nvar INT8_MAX = 127;\nvar INT8_MIN = -128;\nvar INT16_MAX = 32767;\nvar INT16_MIN = -32768;\n\nfunction calculateWaveformDataLength(audio_sample_count, scale) {\n var data_length = Math.floor(audio_sample_count / scale);\n var samples_remaining = audio_sample_count - (data_length * scale);\n if (samples_remaining > 0) {\n data_length++;\n }\n return data_length;\n}\n\nfunction generateWaveformData(options) {\n var scale = options.scale;\n var amplitude_scale = options.amplitude_scale;\n var split_channels = options.split_channels;\n var length = options.length;\n var sample_rate = options.sample_rate;\n var channels = options.channels.map(function(channel) {\n return new Float32Array(channel);\n });\n var output_channels = split_channels ? channels.length : 1;\n var header_size = 24;\n var data_length = calculateWaveformDataLength(length, scale);\n var bytes_per_sample = options.bits === 8 ? 1 : 2;\n var total_size = header_size + data_length * 2 * bytes_per_sample * output_channels;\n var buffer = new ArrayBuffer(total_size);\n var data_view = new DataView(buffer);\n\n var scale_counter = 0;\n var offset = header_size;\n\n var min_value = new Array(output_channels);\n var max_value = new Array(output_channels);\n\n for (var channel = 0; channel < output_channels; channel++) {\n min_value[channel] = Infinity;\n max_value[channel] = -Infinity;\n }\n\n var range_min = options.bits === 8 ? INT8_MIN : INT16_MIN;\n var range_max = options.bits === 8 ? INT8_MAX : INT16_MAX;\n\n data_view.setInt32(0, 2, true);\n data_view.setUint32(4, options.bits === 8, true);\n data_view.setInt32(8, sample_rate, true);\n data_view.setInt32(12, scale, true);\n data_view.setInt32(16, data_length, true);\n data_view.setInt32(20, output_channels, true);\n\n for (var i = 0; i < length; i++) {\n var sample = 0;\n\n if (output_channels === 1) {\n for (var ch = 0; ch < channels.length; ++ch) {\n sample += channels[ch][i];\n }\n sample = Math.floor(range_max * sample * amplitude_scale / channels.length);\n\n if (sample < min_value[0]) {\n min_value[0] = sample;\n if (min_value[0] < range_min) {\n min_value[0] = range_min;\n }\n }\n if (sample > max_value[0]) {\n max_value[0] = sample;\n if (max_value[0] > range_max) {\n max_value[0] = range_max;\n }\n }\n }\n else {\n for (var ch2 = 0; ch2 < output_channels; ++ch2) {\n sample = Math.floor(range_max * channels[ch2][i] * amplitude_scale);\n\n if (sample < min_value[ch2]) {\n min_value[ch2] = sample;\n if (min_value[ch2] < range_min) {\n min_value[ch2] = range_min;\n }\n }\n if (sample > max_value[ch2]) {\n max_value[ch2] = sample;\n if (max_value[ch2] > range_max) {\n max_value[ch2] = range_max;\n }\n }\n }\n }\n\n if (++scale_counter === scale) {\n for (var ch3 = 0; ch3 < output_channels; ch3++) {\n if (options.bits === 8) {\n data_view.setInt8(offset++, min_value[ch3]);\n data_view.setInt8(offset++, max_value[ch3]);\n }\n else {\n data_view.setInt16(offset, min_value[ch3], true);\n data_view.setInt16(offset + 2, max_value[ch3], true);\n offset += 4;\n }\n min_value[ch3] = Infinity;\n max_value[ch3] = -Infinity;\n }\n scale_counter = 0;\n }\n }\n\n if (scale_counter > 0) {\n for (var ch4 = 0; ch4 < output_channels; ch4++) {\n if (options.bits === 8) {\n data_view.setInt8(offset++, min_value[ch4]);\n data_view.setInt8(offset++, max_value[ch4]);\n }\n else {\n data_view.setInt16(offset, min_value[ch4], true);\n data_view.setInt16(offset + 2, max_value[ch4], true);\n }\n }\n }\n\n return buffer;\n}\n\nself.onmessage = function(e) {\n var msg = e.data;\n try {\n var result = generateWaveformData({\n scale: msg.scale,\n bits: msg.bits,\n amplitude_scale: msg.amplitude_scale,\n split_channels: msg.split_channels,\n length: msg.length,\n sample_rate: msg.sample_rate,\n channels: msg.channels\n });\n self.postMessage({ id: msg.id, buffer: result }, [result]);\n } catch (err) {\n self.postMessage({ id: msg.id, error: err.message || String(err) });\n }\n};\n`;\n\n// ────────────────────────────────────────────────────────────────────────────\n// Promise-based worker API (runs on the main thread)\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface PeaksWorkerApi {\n generate(params: {\n id: string;\n channels: ArrayBuffer[];\n length: number;\n sampleRate: number;\n scale: number;\n bits: 8 | 16;\n splitChannels: boolean;\n }): Promise<WaveformData>;\n terminate(): void;\n}\n\ninterface PendingEntry {\n resolve: (value: WaveformData) => void;\n reject: (reason: unknown) => void;\n}\n\nlet idCounter = 0;\n\nexport function createPeaksWorker(): PeaksWorkerApi {\n let worker: Worker;\n try {\n const blob = new Blob([workerSource], { type: 'application/javascript' });\n const url = URL.createObjectURL(blob);\n worker = new Worker(url);\n URL.revokeObjectURL(url);\n } catch (err) {\n // Worker creation can fail in CSP-restricted environments that block blob: URLs.\n // Return a no-op API that rejects all generate calls gracefully.\n console.warn('[waveform-playlist] Failed to create peaks worker (CSP restriction?):', err);\n return {\n generate() {\n return Promise.reject(new Error('Worker creation failed'));\n },\n terminate() {\n /* no-op */\n },\n };\n }\n\n const pending = new Map<string, PendingEntry>();\n let terminated = false;\n\n worker.onmessage = (e: MessageEvent) => {\n const msg = e.data;\n const entry = pending.get(msg.id);\n if (!entry) return;\n pending.delete(msg.id);\n\n if (msg.error) {\n entry.reject(new Error(msg.error));\n } else {\n try {\n const waveformData = WaveformData.create(msg.buffer);\n entry.resolve(waveformData);\n } catch (err) {\n entry.reject(err);\n }\n }\n };\n\n worker.onerror = (e: ErrorEvent) => {\n terminated = true;\n worker.terminate();\n for (const [, entry] of pending) {\n entry.reject(e.error ?? new Error(e.message));\n }\n pending.clear();\n };\n\n return {\n generate(params) {\n if (terminated) return Promise.reject(new Error('Worker terminated'));\n const messageId = String(++idCounter);\n\n return new Promise<WaveformData>((resolve, reject) => {\n pending.set(messageId, { resolve, reject });\n\n worker.postMessage(\n {\n id: messageId,\n scale: params.scale,\n bits: params.bits,\n amplitude_scale: 1.0,\n split_channels: params.splitChannels,\n length: params.length,\n sample_rate: params.sampleRate,\n channels: params.channels,\n },\n params.channels // Transfer ownership\n );\n });\n },\n\n terminate() {\n terminated = true;\n worker.terminate();\n for (const [, entry] of pending) {\n entry.reject(new Error('Worker terminated'));\n }\n pending.clear();\n },\n };\n}\n","import React, {\n createContext,\n useContext,\n useState,\n useEffect,\n useRef,\n useCallback,\n useMemo,\n type ReactNode,\n} from 'react';\nimport { ThemeProvider } from 'styled-components';\nimport type { MediaElementPlayout, FadeConfig } from '@waveform-playlist/media-element-playout';\nimport { resolveMediaElementPlayout } from './playout/resolveMediaElementPlayout';\nimport { type WaveformDataObject } from '@waveform-playlist/core';\nimport { type WaveformPlaylistTheme, defaultTheme } from '@waveform-playlist/ui-components';\nimport type { AnnotationData } from '@waveform-playlist/core';\nimport { extractPeaksFromWaveformData } from './waveformDataLoader';\nimport type WaveformData from 'waveform-data';\nimport type { PeakData } from '@waveform-playlist/core';\nimport type { ClipPeaks, TrackClipPeaks } from './WaveformPlaylistContext';\nimport { useAnimationFrameLoop } from './hooks/useAnimationFrameLoop';\n\n// Configuration for a single media element track\nexport interface MediaElementTrackConfig {\n /** Audio source URL or Blob URL */\n source: string;\n /** Pre-computed waveform data (required for visualization) */\n waveformData: WaveformDataObject;\n /** Track name for display */\n name?: string;\n /** Fade in configuration (requires audioContext on provider) */\n fadeIn?: FadeConfig;\n /** Fade out configuration (requires audioContext on provider) */\n fadeOut?: FadeConfig;\n}\n\n// Context values for animation (high-frequency updates)\nexport interface MediaElementAnimationContextValue {\n isPlaying: boolean;\n currentTime: number;\n currentTimeRef: React.RefObject<number>;\n}\n\n// Context values for playlist state\nexport interface MediaElementStateContextValue {\n continuousPlay: boolean;\n annotations: AnnotationData[];\n activeAnnotationId: string | null;\n playbackRate: number;\n isAutomaticScroll: boolean;\n}\n\n// Context values for controls\nexport interface MediaElementControlsContextValue {\n play: (startTime?: number) => void;\n pause: () => void;\n stop: () => void;\n seekTo: (time: number) => void;\n setPlaybackRate: (rate: number) => void;\n setContinuousPlay: (enabled: boolean) => void;\n setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>>;\n setActiveAnnotationId: (id: string | null) => void;\n setAutomaticScroll: (enabled: boolean) => void;\n setScrollContainer: (element: HTMLDivElement | null) => void;\n scrollContainerRef: React.RefObject<HTMLDivElement | null>;\n}\n\n// Context values for playlist data\nexport interface MediaElementDataContextValue {\n duration: number;\n peaksDataArray: TrackClipPeaks[];\n sampleRate: number;\n waveHeight: number;\n timeScaleHeight: number;\n samplesPerPixel: number;\n playoutRef: React.RefObject<MediaElementPlayout | null>;\n controls: { show: boolean; width: number };\n barWidth: number;\n barGap: number;\n /** Draw bars with pill-shaped rounded caps (radius barWidth/2). */\n roundedBars: boolean;\n progressBarWidth: number;\n fadeIn?: FadeConfig;\n fadeOut?: FadeConfig;\n}\n\n// Create contexts\nconst MediaElementAnimationContext = createContext<MediaElementAnimationContextValue | null>(null);\nconst MediaElementStateContext = createContext<MediaElementStateContextValue | null>(null);\nconst MediaElementControlsContext = createContext<MediaElementControlsContextValue | null>(null);\nconst MediaElementDataContext = createContext<MediaElementDataContextValue | null>(null);\n\nexport interface MediaElementPlaylistProviderProps {\n /** Single track configuration with source URL and waveform data */\n track: MediaElementTrackConfig;\n /** Initial samples per pixel (zoom level) */\n samplesPerPixel?: number;\n /** Height of each waveform track */\n waveHeight?: number;\n /** Show timescale */\n timescale?: boolean;\n /** Initial playback rate (0.5 to 2.0) */\n playbackRate?: number;\n /** Whether to preserve pitch when changing playback rate (default: true).\n * Set to false when using an external pitch processor like SoundTouch. */\n preservesPitch?: boolean;\n /** Enable automatic scroll to keep playhead centered */\n automaticScroll?: boolean;\n /** Theme configuration */\n theme?: Partial<WaveformPlaylistTheme>;\n /** Track controls configuration */\n controls?: { show: boolean; width: number };\n /** Annotations */\n annotationList?: {\n annotations?: AnnotationData[];\n isContinuousPlay?: boolean;\n };\n /** Width of waveform bars */\n barWidth?: number;\n /** Gap between waveform bars */\n barGap?: number;\n /** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */\n roundedBars?: boolean;\n /** Width of progress bars */\n progressBarWidth?: number;\n /** Callback when annotations are changed (drag, edit, etc.) */\n onAnnotationsChange?: (annotations: AnnotationData[]) => void;\n /**\n * AudioContext for Web Audio routing (fades, effects).\n * When provided, audio routes through Web Audio nodes:\n * HTMLAudioElement → MediaElementSourceNode → fadeGain → volumeGain → destination\n *\n * Without this, playback uses HTMLAudioElement directly (no fades or effects).\n * Each provider instance should use its own AudioContext or share one —\n * createMediaElementSource() is called once per audio element.\n */\n audioContext?: AudioContext;\n /** Callback when audio is ready */\n onReady?: () => void;\n /** Called when playout initialization fails (e.g. a missing optional peer,\n * a throwing `createPlayout`, or an `addTrack` error). The provider already\n * logs the error; use this to surface it in your UI. */\n onError?: (err: Error) => void;\n /** Factory for a custom MediaElement playout. When omitted, the bundled engine\n * (@waveform-playlist/media-element-playout) is dynamically imported. */\n createPlayout?: () => MediaElementPlayout;\n children: ReactNode;\n}\n\n/**\n * MediaElementPlaylistProvider\n *\n * A simplified playlist provider for single-track playback using HTMLAudioElement.\n * Key features:\n * - Pitch-preserving playback rate (0.5x - 2.0x)\n * - Pre-computed peaks visualization (no AudioBuffer needed)\n * - Simpler API than full WaveformPlaylistProvider\n *\n * Use this for:\n * - Language learning apps (speed control)\n * - Podcast players\n * - Single-track audio viewers\n *\n * For multi-track editing, use WaveformPlaylistProvider instead.\n */\nexport const MediaElementPlaylistProvider: React.FC<MediaElementPlaylistProviderProps> = ({\n track,\n samplesPerPixel: initialSamplesPerPixel = 1024,\n waveHeight = 100,\n timescale = false,\n playbackRate: initialPlaybackRate = 1,\n preservesPitch = true,\n automaticScroll = false,\n theme: userTheme,\n controls = { show: false, width: 0 },\n annotationList,\n barWidth = 1,\n barGap = 0,\n roundedBars = false,\n progressBarWidth: progressBarWidthProp,\n audioContext,\n onAnnotationsChange,\n onReady,\n onError,\n createPlayout,\n children,\n}) => {\n const progressBarWidth = progressBarWidthProp ?? barWidth + barGap;\n\n // State\n const [isPlaying, setIsPlaying] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [peaksDataArray, setPeaksDataArray] = useState<TrackClipPeaks[]>([]);\n const [playbackRate, setPlaybackRateState] = useState(initialPlaybackRate);\n // Annotations are derived from prop (single source of truth in parent)\n // In v6, annotations must be pre-parsed (numeric start/end). Use parseAeneas() from @waveform-playlist/annotations before passing.\n const annotations = useMemo(() => {\n if (!annotationList?.annotations) return [];\n if (process.env.NODE_ENV !== 'production' && annotationList.annotations.length > 0) {\n const first = annotationList.annotations[0] as unknown as Record<string, unknown>;\n if (typeof first.start !== 'number' || typeof first.end !== 'number') {\n console.error(\n '[waveform-playlist] Annotations must have numeric start/end values. ' +\n 'In v6, use parseAeneas() from @waveform-playlist/annotations before passing annotations. ' +\n 'Received start type: ' +\n typeof first.start\n );\n return [];\n }\n }\n return annotationList.annotations;\n }, [annotationList?.annotations]);\n\n // Ref for animation loop (avoids restarting loop on annotation change)\n const annotationsRef = useRef<AnnotationData[]>(annotations);\n annotationsRef.current = annotations;\n\n const [activeAnnotationId, setActiveAnnotationIdState] = useState<string | null>(null);\n const [continuousPlay, setContinuousPlayState] = useState(\n annotationList?.isContinuousPlay ?? false\n );\n const [samplesPerPixel] = useState(initialSamplesPerPixel);\n const [isAutomaticScroll, setIsAutomaticScroll] = useState(automaticScroll);\n\n // Refs\n const playoutRef = useRef<MediaElementPlayout | null>(null);\n const currentTimeRef = useRef<number>(0);\n const continuousPlayRef = useRef<boolean>(continuousPlay);\n const activeAnnotationIdRef = useRef<string | null>(null);\n const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n const isAutomaticScrollRef = useRef<boolean>(automaticScroll);\n const samplesPerPixelRef = useRef<number>(initialSamplesPerPixel);\n const { startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();\n\n // Sync refs\n useEffect(() => {\n continuousPlayRef.current = continuousPlay;\n }, [continuousPlay]);\n\n useEffect(() => {\n isAutomaticScrollRef.current = isAutomaticScroll;\n }, [isAutomaticScroll]);\n\n // Custom setter for activeAnnotationId\n const setActiveAnnotationId = useCallback((value: string | null) => {\n activeAnnotationIdRef.current = value;\n setActiveAnnotationIdState(value);\n }, []);\n\n const setContinuousPlay = useCallback((value: boolean) => {\n continuousPlayRef.current = value;\n setContinuousPlayState(value);\n }, []);\n\n // Memoize setScrollContainer callback\n const setScrollContainer = useCallback((element: HTMLDivElement | null) => {\n scrollContainerRef.current = element;\n }, []);\n\n // Get sample rate from waveform data\n const sampleRate = track.waveformData.sample_rate;\n\n // Initialize playout and load track\n useEffect(() => {\n let cancelled = false;\n let createdPlayout: MediaElementPlayout | null = null;\n\n (async () => {\n try {\n const playout = await resolveMediaElementPlayout({\n createPlayout,\n playbackRate: initialPlaybackRate,\n preservesPitch,\n });\n if (cancelled) {\n playout.dispose();\n return;\n }\n createdPlayout = playout;\n\n playout.addTrack({\n source: track.source,\n peaks: track.waveformData,\n name: track.name,\n audioContext,\n fadeIn: track.fadeIn,\n fadeOut: track.fadeOut,\n });\n\n // Set up time update callback\n const mediaTrack = playout.getTrack(playout['track']?.id ?? '');\n if (mediaTrack) {\n mediaTrack.setOnTimeUpdateCallback((time) => {\n currentTimeRef.current = time;\n });\n }\n\n // Set up playback complete callback\n playout.setOnPlaybackComplete(() => {\n stopAnimationFrameLoop();\n setIsPlaying(false);\n setActiveAnnotationId(null);\n currentTimeRef.current = 0;\n setCurrentTime(0);\n });\n\n playoutRef.current = playout;\n setDuration(track.waveformData.duration);\n onReady?.();\n } catch (err) {\n console.warn('[waveform-playlist] MediaElement playout init failed: ' + String(err));\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n })();\n\n return () => {\n cancelled = true;\n stopAnimationFrameLoop();\n if (createdPlayout) {\n createdPlayout.dispose();\n }\n playoutRef.current = null;\n };\n }, [\n track.source,\n track.waveformData,\n track.name,\n track.fadeIn,\n track.fadeOut,\n audioContext,\n initialPlaybackRate,\n preservesPitch,\n onReady,\n onError,\n stopAnimationFrameLoop,\n setActiveAnnotationId,\n createPlayout,\n ]);\n\n // Generate peaks from waveform data\n useEffect(() => {\n try {\n const extractedPeaks = extractPeaksFromWaveformData(\n track.waveformData as WaveformData,\n samplesPerPixel,\n 0, // channel index\n 0, // offset\n Math.ceil(track.waveformData.duration * sampleRate) // duration in samples\n );\n\n const clipPeaks: ClipPeaks = {\n clipId: 'media-element-clip',\n trackName: track.name ?? 'Track',\n peaks: {\n length: extractedPeaks.length,\n data: [extractedPeaks.data],\n bits: extractedPeaks.bits,\n } as PeakData,\n startSample: 0,\n durationSamples: Math.ceil(track.waveformData.duration * sampleRate),\n };\n\n setPeaksDataArray([[clipPeaks]]);\n } catch (err) {\n console.warn('[waveform-playlist] Failed to extract peaks from waveform data:', err);\n }\n }, [track.waveformData, track.name, samplesPerPixel, sampleRate]);\n\n // Animation loop\n const startAnimationLoop = useCallback(() => {\n const updateTime = () => {\n const time = playoutRef.current?.getCurrentTime() ?? 0;\n currentTimeRef.current = time;\n\n // Handle annotation playback\n const currentAnnotations = annotationsRef.current;\n if (currentAnnotations.length > 0) {\n const currentAnnotation = currentAnnotations.find(\n (ann) => time >= ann.start && time < ann.end\n );\n\n if (continuousPlayRef.current) {\n if (currentAnnotation && currentAnnotation.id !== activeAnnotationIdRef.current) {\n setActiveAnnotationId(currentAnnotation.id);\n } else if (!currentAnnotation && activeAnnotationIdRef.current !== null) {\n // Clear the active annotation when we're past it, but don't stop playback\n // Let playback continue until the audio element fires its 'ended' event\n setActiveAnnotationId(null);\n }\n } else {\n if (activeAnnotationIdRef.current) {\n const activeAnnotation = currentAnnotations.find(\n (ann) => ann.id === activeAnnotationIdRef.current\n );\n if (activeAnnotation && time >= activeAnnotation.end) {\n playoutRef.current?.stop();\n setIsPlaying(false);\n return;\n }\n } else if (currentAnnotation) {\n setActiveAnnotationId(currentAnnotation.id);\n }\n }\n }\n\n // Handle automatic scroll - continuously center the playhead\n if (isAutomaticScrollRef.current && scrollContainerRef.current) {\n const container = scrollContainerRef.current;\n const pixelPosition = (time * sampleRate) / samplesPerPixelRef.current;\n const containerWidth = container.clientWidth;\n\n // Continuously scroll to keep playhead centered\n const targetScrollLeft = Math.max(0, pixelPosition - containerWidth / 2);\n container.scrollLeft = targetScrollLeft;\n }\n\n startAnimationFrameLoop(updateTime);\n };\n\n startAnimationFrameLoop(updateTime);\n }, [setActiveAnnotationId, sampleRate, startAnimationFrameLoop]);\n\n const stopAnimationLoop = stopAnimationFrameLoop;\n\n // Playback controls\n const play = useCallback(\n (startTime?: number) => {\n if (!playoutRef.current) return;\n\n const actualStartTime = startTime ?? currentTimeRef.current;\n playoutRef.current.play(undefined, actualStartTime);\n setIsPlaying(true);\n startAnimationLoop();\n },\n [startAnimationLoop]\n );\n\n const pause = useCallback(() => {\n if (!playoutRef.current) return;\n\n playoutRef.current.pause();\n setIsPlaying(false);\n stopAnimationLoop();\n setCurrentTime(playoutRef.current.getCurrentTime());\n }, [stopAnimationLoop]);\n\n const stop = useCallback(() => {\n if (!playoutRef.current) return;\n\n playoutRef.current.stop();\n setIsPlaying(false);\n stopAnimationLoop();\n currentTimeRef.current = 0;\n setCurrentTime(0);\n setActiveAnnotationId(null);\n }, [stopAnimationLoop, setActiveAnnotationId]);\n\n const seekTo = useCallback(\n (time: number) => {\n const clampedTime = Math.max(0, Math.min(time, duration));\n currentTimeRef.current = clampedTime;\n setCurrentTime(clampedTime);\n\n if (playoutRef.current) {\n playoutRef.current.seekTo(clampedTime);\n }\n },\n [duration]\n );\n\n const setPlaybackRate = useCallback((rate: number) => {\n const clampedRate = Math.max(0.5, Math.min(2.0, rate));\n setPlaybackRateState(clampedRate);\n if (playoutRef.current) {\n playoutRef.current.setPlaybackRate(clampedRate);\n }\n }, []);\n\n const timeScaleHeight = timescale ? 30 : 0;\n\n // Context values\n const animationValue: MediaElementAnimationContextValue = useMemo(\n () => ({\n isPlaying,\n currentTime,\n currentTimeRef,\n }),\n [isPlaying, currentTime]\n );\n\n const stateValue: MediaElementStateContextValue = useMemo(\n () => ({\n continuousPlay,\n annotations,\n activeAnnotationId,\n playbackRate,\n isAutomaticScroll,\n }),\n [continuousPlay, annotations, activeAnnotationId, playbackRate, isAutomaticScroll]\n );\n\n // Stable callback ref for onAnnotationsChange to avoid re-creating controls context\n const onAnnotationsChangeRef = useRef(onAnnotationsChange);\n onAnnotationsChangeRef.current = onAnnotationsChange;\n\n const setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>> = useCallback(\n (action) => {\n const updated = typeof action === 'function' ? action(annotationsRef.current) : action;\n if (!onAnnotationsChangeRef.current) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n 'waveform-playlist: setAnnotations was called but no onAnnotationsChange callback is provided. ' +\n 'Annotation edits will not persist. Pass onAnnotationsChange to MediaElementPlaylistProvider to handle annotation updates.'\n );\n }\n return;\n }\n onAnnotationsChangeRef.current(updated);\n },\n []\n );\n\n const controlsValue: MediaElementControlsContextValue = useMemo(\n () => ({\n play,\n pause,\n stop,\n seekTo,\n setPlaybackRate,\n setContinuousPlay,\n setAnnotations,\n setActiveAnnotationId,\n setAutomaticScroll: (enabled: boolean) => {\n setIsAutomaticScroll(enabled);\n },\n setScrollContainer,\n scrollContainerRef,\n }),\n [\n play,\n pause,\n stop,\n seekTo,\n setPlaybackRate,\n setContinuousPlay,\n setAnnotations,\n setActiveAnnotationId,\n setScrollContainer,\n ]\n );\n\n const dataValue: MediaElementDataContextValue = useMemo(\n () => ({\n duration,\n peaksDataArray,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n samplesPerPixel,\n playoutRef,\n controls,\n barWidth,\n barGap,\n roundedBars,\n progressBarWidth,\n fadeIn: track.fadeIn,\n fadeOut: track.fadeOut,\n }),\n [\n duration,\n peaksDataArray,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n samplesPerPixel,\n controls,\n barWidth,\n barGap,\n roundedBars,\n progressBarWidth,\n track.fadeIn,\n track.fadeOut,\n ]\n );\n\n const mergedTheme = { ...defaultTheme, ...userTheme };\n\n return (\n <ThemeProvider theme={mergedTheme}>\n <MediaElementAnimationContext.Provider value={animationValue}>\n <MediaElementStateContext.Provider value={stateValue}>\n <MediaElementControlsContext.Provider value={controlsValue}>\n <MediaElementDataContext.Provider value={dataValue}>\n {children}\n </MediaElementDataContext.Provider>\n </MediaElementControlsContext.Provider>\n </MediaElementStateContext.Provider>\n </MediaElementAnimationContext.Provider>\n </ThemeProvider>\n );\n};\n\n// Hooks\nexport const useMediaElementAnimation = () => {\n const context = useContext(MediaElementAnimationContext);\n if (!context) {\n throw new Error('useMediaElementAnimation must be used within MediaElementPlaylistProvider');\n }\n return context;\n};\n\nexport const useMediaElementState = () => {\n const context = useContext(MediaElementStateContext);\n if (!context) {\n throw new Error('useMediaElementState must be used within MediaElementPlaylistProvider');\n }\n return context;\n};\n\nexport const useMediaElementControls = () => {\n const context = useContext(MediaElementControlsContext);\n if (!context) {\n throw new Error('useMediaElementControls must be used within MediaElementPlaylistProvider');\n }\n return context;\n};\n\nexport const useMediaElementData = () => {\n const context = useContext(MediaElementDataContext);\n if (!context) {\n throw new Error('useMediaElementData must be used within MediaElementPlaylistProvider');\n }\n return context;\n};\n","import type { MediaElementPlayout } from '@waveform-playlist/media-element-playout';\n\n/** Options for {@link resolveMediaElementPlayout}. */\nexport interface ResolveMediaElementPlayoutOptions {\n /** Consumer-supplied factory. When present, the default engine is never imported. */\n createPlayout?: () => MediaElementPlayout;\n /** Initial playback rate forwarded to the default engine. */\n playbackRate?: number;\n /** Whether to preserve pitch on rate change (default engine only). */\n preservesPitch?: boolean;\n}\n\nconst INSTALL_HINT =\n '@waveform-playlist/media-element-playout is required for the default MediaElement engine. ' +\n 'Install with: npm install @waveform-playlist/media-element-playout — or pass a custom `createPlayout`.';\n\n/**\n * Resolve a MediaElement playout. With `createPlayout`, returns its result and\n * never imports the default engine. Otherwise dynamically imports\n * `@waveform-playlist/media-element-playout`, rethrowing a friendly install hint\n * (and console.warn-ing the original error) when the optional peer is absent.\n */\nexport async function resolveMediaElementPlayout(\n opts: ResolveMediaElementPlayoutOptions\n): Promise<MediaElementPlayout> {\n if (opts.createPlayout) {\n return opts.createPlayout();\n }\n\n let mod: typeof import('@waveform-playlist/media-element-playout');\n try {\n mod = await import('@waveform-playlist/media-element-playout');\n } catch (originalErr) {\n console.warn(\n '[waveform-playlist] @waveform-playlist/media-element-playout dynamic import failed: ' +\n String(originalErr)\n );\n throw new Error(INSTALL_HINT);\n }\n\n return new mod.MediaElementPlayout({\n playbackRate: opts.playbackRate,\n preservesPitch: opts.preservesPitch,\n });\n}\n","import React, { useCallback } from 'react';\nimport { BaseControlButton } from '@waveform-playlist/ui-components';\nimport {\n usePlaybackAnimation,\n usePlaylistState,\n usePlaylistControls,\n usePlaylistData,\n} from '../WaveformPlaylistContext';\n\nexport const PlayButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying, currentTimeRef } = usePlaybackAnimation();\n const { selectionStart, selectionEnd, isLoopEnabled } = usePlaylistState();\n const { play } = usePlaylistControls();\n\n const handleClick = async () => {\n const hasSelection = selectionStart !== selectionEnd && selectionEnd > selectionStart;\n\n if (hasSelection && !isLoopEnabled) {\n // Without loop: Play selection region only, then stop\n const duration = selectionEnd - selectionStart;\n await play(selectionStart, duration);\n } else {\n // With loop or no selection: Play from current cursor position.\n // Transport loop handles boundaries when loop is enabled.\n await play(currentTimeRef.current ?? 0);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} disabled={isPlaying} className={className}>\n Play\n </BaseControlButton>\n );\n};\n\nexport const PauseButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying } = usePlaybackAnimation();\n const { pause } = usePlaylistControls();\n\n return (\n <BaseControlButton onClick={pause} disabled={!isPlaying} className={className}>\n Pause\n </BaseControlButton>\n );\n};\n\nexport const StopButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying } = usePlaybackAnimation();\n const { stop } = usePlaylistControls();\n\n return (\n <BaseControlButton onClick={stop} disabled={!isPlaying} className={className}>\n Stop\n </BaseControlButton>\n );\n};\n\nexport const RewindButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying } = usePlaybackAnimation();\n const { play, setCurrentTime } = usePlaylistControls();\n\n const handleClick = () => {\n setCurrentTime(0);\n\n if (isPlaying) {\n play(0);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} className={className}>\n Rewind\n </BaseControlButton>\n );\n};\n\nexport const FastForwardButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying } = usePlaybackAnimation();\n const { play, setCurrentTime } = usePlaylistControls();\n const { duration } = usePlaylistData();\n\n const handleClick = () => {\n setCurrentTime(duration);\n\n if (isPlaying) {\n play(duration);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} className={className}>\n Fast Forward\n </BaseControlButton>\n );\n};\n\nexport const SkipBackwardButton: React.FC<{ skipAmount?: number; className?: string }> = ({\n skipAmount = 5,\n className,\n}) => {\n const { currentTimeRef, isPlaying } = usePlaybackAnimation();\n const { play, setCurrentTime } = usePlaylistControls();\n\n const handleClick = () => {\n const newTime = Math.max(0, (currentTimeRef.current ?? 0) - skipAmount);\n setCurrentTime(newTime);\n\n if (isPlaying) {\n play(newTime);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} className={className}>\n Skip Backward\n </BaseControlButton>\n );\n};\n\nexport const SkipForwardButton: React.FC<{ skipAmount?: number; className?: string }> = ({\n skipAmount = 5,\n className,\n}) => {\n const { currentTimeRef, isPlaying } = usePlaybackAnimation();\n const { play, setCurrentTime } = usePlaylistControls();\n const { duration } = usePlaylistData();\n\n const handleClick = () => {\n const newTime = Math.min(duration, (currentTimeRef.current ?? 0) + skipAmount);\n setCurrentTime(newTime);\n\n if (isPlaying) {\n play(newTime);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} className={className}>\n Skip Forward\n </BaseControlButton>\n );\n};\n\nexport const LoopButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isLoopEnabled, loopStart, loopEnd } = usePlaylistState();\n const { setLoopEnabled, setLoopRegion } = usePlaylistControls();\n const { duration } = usePlaylistData();\n\n const hasValidLoopRegion = loopStart !== loopEnd && loopEnd > loopStart;\n\n const handleClick = () => {\n if (!isLoopEnabled && !hasValidLoopRegion) {\n // Create a default loop region when enabling loop without one\n // Default to first 10 seconds or 25% of duration, whichever is smaller\n const defaultEnd = Math.min(10, duration * 0.25);\n setLoopRegion(0, Math.max(1, defaultEnd)); // At least 1 second\n }\n setLoopEnabled(!isLoopEnabled);\n };\n\n return (\n <BaseControlButton\n onClick={handleClick}\n className={className}\n title={isLoopEnabled ? 'Disable loop' : 'Enable loop'}\n >\n {isLoopEnabled ? 'Loop On' : 'Loop Off'}\n </BaseControlButton>\n );\n};\n\nexport const SetLoopRegionButton: React.FC<{ className?: string }> = ({ className }) => {\n const { selectionStart, selectionEnd, loopStart, loopEnd } = usePlaylistState();\n const { setLoopRegionFromSelection, clearLoopRegion } = usePlaylistControls();\n\n const hasValidSelection = selectionStart !== selectionEnd && selectionEnd > selectionStart;\n const hasLoopRegion = loopStart !== loopEnd && loopEnd > loopStart;\n\n const handleClick = () => {\n if (hasLoopRegion) {\n clearLoopRegion();\n } else {\n setLoopRegionFromSelection();\n }\n };\n\n return (\n <BaseControlButton\n onClick={handleClick}\n disabled={!hasValidSelection && !hasLoopRegion}\n className={className}\n title={\n hasLoopRegion\n ? 'Clear loop region'\n : hasValidSelection\n ? 'Set loop region from selection'\n : 'Create a selection first'\n }\n >\n {hasLoopRegion ? 'Clear Loop' : 'Set Loop'}\n </BaseControlButton>\n );\n};\n\nexport interface ClearAllButtonProps {\n onClearAll: () => void;\n label?: string;\n className?: string;\n}\n\nexport const ClearAllButton: React.FC<ClearAllButtonProps> = ({\n onClearAll,\n label = 'Clear All',\n className,\n}) => {\n const { stop } = usePlaylistControls();\n\n const handleClick = useCallback(() => {\n stop();\n onClearAll();\n }, [stop, onClearAll]);\n\n return (\n <BaseControlButton onClick={handleClick} className={className} title=\"Remove all tracks\">\n {label}\n </BaseControlButton>\n );\n};\n","import React from 'react';\nimport { BaseControlButton } from '@waveform-playlist/ui-components';\nimport { usePlaylistControls, usePlaylistData } from '../WaveformPlaylistContext';\n\nexport const ZoomInButton: React.FC<{ className?: string; disabled?: boolean }> = ({\n className,\n disabled,\n}) => {\n const { zoomIn } = usePlaylistControls();\n const { canZoomIn } = usePlaylistData();\n\n return (\n <BaseControlButton onClick={zoomIn} disabled={disabled || !canZoomIn} className={className}>\n Zoom In\n </BaseControlButton>\n );\n};\n\nexport const ZoomOutButton: React.FC<{ className?: string; disabled?: boolean }> = ({\n className,\n disabled,\n}) => {\n const { zoomOut } = usePlaylistControls();\n const { canZoomOut } = usePlaylistData();\n\n return (\n <BaseControlButton onClick={zoomOut} disabled={disabled || !canZoomOut} className={className}>\n Zoom Out\n </BaseControlButton>\n );\n};\n","import React, { useRef, useEffect } from 'react';\nimport {\n MasterVolumeControl as BaseMasterVolumeControl,\n TimeFormatSelect as BaseTimeFormatSelect,\n AutomaticScrollCheckbox as BaseAutomaticScrollCheckbox,\n SelectionTimeInputs as BaseSelectionTimeInputs,\n formatTime,\n} from '@waveform-playlist/ui-components';\nimport styled from 'styled-components';\nimport {\n usePlaybackAnimation,\n usePlaylistState,\n usePlaylistControls,\n usePlaylistData,\n} from '../WaveformPlaylistContext';\n\n/**\n * Master volume control that uses the playlist context\n */\nexport const MasterVolumeControl: React.FC<{ className?: string }> = ({ className }) => {\n const { masterVolume } = usePlaylistData();\n const { setMasterVolume } = usePlaylistControls();\n\n return (\n <BaseMasterVolumeControl\n volume={masterVolume}\n onChange={setMasterVolume}\n className={className}\n />\n );\n};\n\n/**\n * Time format selector that uses the playlist context\n */\nexport const TimeFormatSelect: React.FC<{ className?: string }> = ({ className }) => {\n const { timeFormat } = usePlaylistData();\n const { setTimeFormat } = usePlaylistControls();\n\n return <BaseTimeFormatSelect value={timeFormat} onChange={setTimeFormat} className={className} />;\n};\n\nconst PositionDisplay = styled.span`\n font-family: 'Courier New', Monaco, monospace;\n font-size: 1rem;\n font-weight: 600;\n color: ${(props) => props.theme?.textColor || '#333'};\n user-select: none;\n`;\n\n/**\n * Audio position display that uses the playlist context.\n * Updates via the shared animation frame registry — no own rAF loop.\n * Direct DOM manipulation avoids React re-renders.\n */\nexport const AudioPosition: React.FC<{ className?: string }> = ({ className }) => {\n const timeRef = useRef<HTMLSpanElement>(null);\n const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } =\n usePlaybackAnimation();\n const { timeFormat: format } = usePlaylistData();\n\n // Register per-frame callback during playback — uses raw time for display\n useEffect(() => {\n const id = 'audio-position';\n if (isPlaying) {\n registerFrameCallback(id, ({ time }) => {\n if (timeRef.current) {\n timeRef.current.textContent = formatTime(time, format);\n }\n });\n }\n return () => unregisterFrameCallback(id);\n }, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);\n\n // Update when stopped (for seeks)\n useEffect(() => {\n if (!isPlaying && timeRef.current) {\n timeRef.current.textContent = formatTime(currentTimeRef.current ?? 0, format);\n }\n });\n\n return (\n <PositionDisplay ref={timeRef} className={className} aria-label=\"Audio position\">\n {formatTime(currentTimeRef.current ?? 0, format)}\n </PositionDisplay>\n );\n};\n\n/**\n * Selection time inputs that use the playlist context\n */\nexport const SelectionTimeInputs: React.FC<{ className?: string }> = ({ className }) => {\n const { selectionStart, selectionEnd } = usePlaylistState();\n const { setSelection } = usePlaylistControls();\n\n return (\n <BaseSelectionTimeInputs\n selectionStart={selectionStart}\n selectionEnd={selectionEnd}\n onSelectionChange={setSelection}\n className={className}\n />\n );\n};\n\n/**\n * Automatic scroll checkbox that uses the playlist context\n * Uses split contexts to avoid re-rendering during animation\n */\nexport const AutomaticScrollCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n const { isAutomaticScroll } = usePlaylistState();\n const { setAutomaticScroll } = usePlaylistControls();\n\n return (\n <BaseAutomaticScrollCheckbox\n checked={isAutomaticScroll}\n onChange={setAutomaticScroll}\n className={className}\n />\n );\n};\n","import { createContext, useContext } from 'react';\nimport type {\n AnnotationData,\n AnnotationAction,\n AnnotationActionOptions,\n RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\n\n/**\n * Props the browser package passes to the AnnotationText component.\n * Mirrors what PlaylistAnnotationList and MediaElementAnnotationList actually use.\n */\nexport interface AnnotationTextIntegrationProps {\n annotations: AnnotationData[];\n activeAnnotationId?: string;\n shouldScrollToActive?: boolean;\n scrollActivePosition?: ScrollLogicalPosition;\n scrollActiveContainer?: 'nearest' | 'all';\n editable?: boolean;\n controls?: AnnotationAction[];\n annotationListConfig?: AnnotationActionOptions;\n height?: number;\n onAnnotationUpdate?: (updatedAnnotations: AnnotationData[]) => void;\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n}\n\n/**\n * Props the browser package passes to the AnnotationBox component.\n * Mirrors what PlaylistVisualization and MediaElementPlaylist actually use.\n */\nexport interface AnnotationBoxIntegrationProps {\n annotationId: string;\n annotationIndex: number;\n startPosition: number;\n endPosition: number;\n label?: string;\n color?: string;\n isActive?: boolean;\n onClick?: () => void;\n editable?: boolean;\n}\n\n/**\n * Props the browser package passes to the AnnotationBoxesWrapper component.\n * Mirrors what PlaylistVisualization and MediaElementPlaylist actually use.\n */\nexport interface AnnotationBoxesWrapperIntegrationProps {\n children?: React.ReactNode;\n height?: number;\n width?: number;\n}\n\n/**\n * Interface for annotation integration provided by @waveform-playlist/annotations.\n *\n * The browser package defines what it needs, and the optional annotations package\n * provides it via <AnnotationProvider>.\n */\nexport interface AnnotationIntegration {\n // Parser functions\n parseAeneas: (data: unknown) => AnnotationData;\n serializeAeneas: (annotation: AnnotationData) => unknown;\n\n // Visualization components (typed with the props the browser package actually passes)\n AnnotationText: React.ComponentType<AnnotationTextIntegrationProps>;\n AnnotationBox: React.ComponentType<AnnotationBoxIntegrationProps>;\n AnnotationBoxesWrapper: React.ComponentType<AnnotationBoxesWrapperIntegrationProps>;\n\n // Control components\n ContinuousPlayCheckbox: React.ComponentType<{\n checked: boolean;\n onChange: (checked: boolean) => void;\n className?: string;\n }>;\n LinkEndpointsCheckbox: React.ComponentType<{\n checked: boolean;\n onChange: (checked: boolean) => void;\n className?: string;\n }>;\n EditableCheckbox: React.ComponentType<{\n checked: boolean;\n onChange: (checked: boolean) => void;\n className?: string;\n }>;\n DownloadAnnotationsButton: React.ComponentType<{\n annotations: AnnotationData[];\n filename?: string;\n className?: string;\n }>;\n}\n\nexport const AnnotationIntegrationContext = createContext<AnnotationIntegration | null>(null);\n\nexport const AnnotationIntegrationProvider = AnnotationIntegrationContext.Provider;\n\n/**\n * Hook to access annotation integration provided by @waveform-playlist/annotations.\n * Throws if used without <AnnotationProvider> wrapping the component tree.\n *\n * Follows the Kent C. Dodds pattern:\n * https://kentcdodds.com/blog/how-to-use-react-context-effectively\n */\nexport function useAnnotationIntegration(): AnnotationIntegration {\n const context = useContext(AnnotationIntegrationContext);\n if (!context) {\n throw new Error(\n 'useAnnotationIntegration must be used within <AnnotationProvider>. ' +\n 'Install @waveform-playlist/annotations and wrap your app with <AnnotationProvider>. ' +\n 'See: https://waveform-playlist.naomiaro.com/docs/guides/annotations'\n );\n }\n return context;\n}\n","import React from 'react';\nimport { useAnnotationIntegration } from '../AnnotationIntegrationContext';\nimport { usePlaylistState, usePlaylistControls } from '../WaveformPlaylistContext';\n\n/**\n * Continuous play checkbox that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const ContinuousPlayCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n const { ContinuousPlayCheckbox: Base } = useAnnotationIntegration();\n const { continuousPlay } = usePlaylistState();\n const { setContinuousPlay } = usePlaylistControls();\n\n return <Base checked={continuousPlay} onChange={setContinuousPlay} className={className} />;\n};\n\n/**\n * Link endpoints checkbox that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const LinkEndpointsCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n const { LinkEndpointsCheckbox: Base } = useAnnotationIntegration();\n const { linkEndpoints } = usePlaylistState();\n const { setLinkEndpoints } = usePlaylistControls();\n\n return <Base checked={linkEndpoints} onChange={setLinkEndpoints} className={className} />;\n};\n\n/**\n * Editable annotations checkbox that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const EditableCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n const { EditableCheckbox: Base } = useAnnotationIntegration();\n const { annotationsEditable } = usePlaylistState();\n const { setAnnotationsEditable } = usePlaylistControls();\n\n return (\n <Base checked={annotationsEditable} onChange={setAnnotationsEditable} className={className} />\n );\n};\n\n/**\n * Download annotations button that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const DownloadAnnotationsButton: React.FC<{ filename?: string; className?: string }> = ({\n filename,\n className,\n}) => {\n const { DownloadAnnotationsButton: Base } = useAnnotationIntegration();\n const { annotations } = usePlaylistState();\n\n return <Base annotations={annotations} filename={filename} className={className} />;\n};\n","import { createContext, useContext } from 'react';\n\nconst ClipInteractionContext = createContext(false);\n\nexport const ClipInteractionContextProvider = ClipInteractionContext.Provider;\n\nexport function useClipInteractionEnabled(): boolean {\n return useContext(ClipInteractionContext);\n}\n","import React, { useContext, useRef, useState, useMemo, type ReactNode, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport styled from 'styled-components';\nimport {\n Playlist,\n Track as TrackComponent,\n Clip,\n Selection,\n TimescaleLoopRegion,\n PlaylistInfoContext,\n DevicePixelRatioProvider,\n SmartScale,\n CloseButton,\n Controls,\n Header,\n Button,\n ButtonGroup,\n Slider,\n SliderWrapper,\n VolumeDownIcon,\n VolumeUpIcon,\n TrackMenu,\n useTheme,\n waveformColorToCss,\n type RenderPlayheadFunction,\n SpectrogramLabels,\n CLIP_HEADER_HEIGHT,\n} from '@waveform-playlist/ui-components';\nimport { AnnotationIntegrationContext } from '../AnnotationIntegrationContext';\nimport {\n usePlaybackAnimation,\n usePlaylistState,\n usePlaylistControls,\n usePlaylistData,\n type ClipPeaks,\n} from '../WaveformPlaylistContext';\nimport { resolveRecordingOffsetSamples, type Peaks } from '@waveform-playlist/core';\nimport { AnimatedPlayhead } from './AnimatedPlayhead';\nimport { ChannelWithProgress } from './ChannelWithProgress';\nimport type { SpectrogramConfig } from '@waveform-playlist/core';\nimport type { AnnotationAction } from '@waveform-playlist/core';\nimport type { AnnotationData, GetAnnotationBoxLabelFn } from '../types/annotations';\nimport { SpectrogramIntegrationContext } from '../SpectrogramIntegrationContext';\n\n// Default duration in seconds for empty tracks (used for recording workflow)\nconst DEFAULT_EMPTY_TRACK_DURATION = 60;\n\ninterface ControlSlotProps {\n readonly $height: number;\n readonly $isSelected?: boolean;\n}\n\n/**\n * Height-synced container for each track's controls in the ControlsColumn.\n * Uses the same height formula as Track: waveHeight * numChannels + clipHeaderHeight.\n */\nconst ControlSlot = styled.div.attrs<ControlSlotProps>((props) => ({\n style: { height: `${props.$height}px` },\n}))<ControlSlotProps>`\n overflow: hidden;\n pointer-events: auto;\n background: ${(props) => props.theme.surfaceColor};\n transition: background 0.15s ease-in-out;\n ${(props) => props.$isSelected && `background: ${props.theme.selectedTrackControlsBackground};`}\n`;\n\nexport interface PlaylistVisualizationProps {\n renderTrackControls?: (trackIndex: number) => ReactNode;\n renderTick?: (label: string, pixelPosition: number) => ReactNode;\n /** Custom playhead render function. Receives position (pixels) and color from theme. */\n renderPlayhead?: RenderPlayheadFunction;\n annotationControls?: AnnotationAction[];\n /**\n * Custom function to generate the label shown on annotation boxes in the waveform.\n * Receives the annotation data and its index, returns a string label.\n * Default: annotation.id\n */\n getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n className?: string;\n showClipHeaders?: boolean;\n interactiveClips?: boolean;\n showFades?: boolean;\n /**\n * Enable mobile-optimized touch interactions.\n * When true, increases touch target sizes for clip boundaries.\n * Use with useDragSensors({ touchOptimized: true }) for best results.\n */\n touchOptimized?: boolean;\n /** Callback when a track's close button is clicked. Only renders close button when provided. */\n onRemoveTrack?: (trackIndex: number) => void;\n // Live recording state for real-time waveform preview\n recordingState?: {\n isRecording: boolean;\n trackId: string;\n startSample: number;\n durationSamples: number;\n peaks: (Int8Array | Int16Array)[];\n bits: 8 | 16;\n /**\n * Latency offset (seconds) to skip in the live preview. Absolute replacement\n * for the auto-computed outputLatency + lookAhead value. Pass the same value\n * given to useIntegratedRecording so preview and finalized clip match.\n */\n latencyOffset?: number;\n };\n}\n\n/**\n * Wrapper that isolates the custom playhead's hooks from PlaylistVisualization.\n * Calling renderPlayhead() directly would merge its hooks into the parent,\n * causing \"Rendered more hooks\" errors if renderPlayhead is conditionally provided.\n */\nconst CustomPlayhead: React.FC<{\n renderPlayhead: RenderPlayheadFunction;\n color: string;\n samplesPerPixel: number;\n sampleRate: number;\n}> = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {\n const {\n isPlaying,\n currentTimeRef,\n visualTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n getPlaybackTime,\n getAudioContextTime,\n } = usePlaybackAnimation();\n\n // Use visualTimeRef so the initial position matches the audible output\n // (the playhead's rAF loop also reads visualTimeRef for in-sync animation).\n const visualTime = visualTimeRef.current ?? currentTimeRef.current ?? 0;\n\n return renderPlayhead({\n position: (visualTime * sampleRate) / samplesPerPixel,\n color,\n isPlaying,\n currentTimeRef,\n visualTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n samplesPerPixel,\n sampleRate,\n controlsOffset: 0,\n getAudioContextTime,\n getPlaybackTime,\n }) as React.ReactElement;\n};\n\n/** Compute the maximum channel count for a track, considering both clip peaks and live recording. */\nfunction getTrackChannelCount(\n trackClipPeaks: ClipPeaks[],\n recordingState: PlaylistVisualizationProps['recordingState'],\n trackId: string | undefined,\n mono: boolean\n): number {\n const clipChannels =\n trackClipPeaks.length > 0\n ? Math.max(1, ...trackClipPeaks.map((clip) => clip.peaks.data.length))\n : 1;\n const recordingChannels =\n recordingState?.isRecording && recordingState.trackId === trackId\n ? mono\n ? 1\n : recordingState.peaks.length\n : 0;\n return Math.max(clipChannels, recordingChannels);\n}\n\n/**\n * Standalone playlist visualization component (WebAudio version).\n *\n * Renders the waveform tracks, timescale, annotations boxes, selection,\n * playhead, loop regions, and track controls — everything that lives\n * inside <Playlist> plus wrapping providers.\n *\n * Does NOT render AnnotationText (the annotation list below the waveform).\n * Pair with PlaylistAnnotationList for a full annotation editing UI.\n */\nexport const PlaylistVisualization: React.FC<PlaylistVisualizationProps> = ({\n renderTrackControls,\n renderTick,\n renderPlayhead,\n annotationControls: _annotationControls,\n getAnnotationBoxLabel,\n className,\n showClipHeaders = false,\n interactiveClips = false,\n showFades = false,\n touchOptimized = false,\n onRemoveTrack,\n recordingState,\n}) => {\n const theme = useTheme() as import('@waveform-playlist/ui-components').WaveformPlaylistTheme;\n\n const { isPlaying, getLookAhead, getOutputLatency } = usePlaybackAnimation();\n const {\n selectionStart,\n selectionEnd,\n annotations,\n activeAnnotationId,\n annotationsEditable,\n linkEndpoints: _linkEndpoints,\n continuousPlay,\n selectedTrackId,\n loopStart,\n loopEnd,\n isLoopEnabled,\n indefinitePlayback,\n } = usePlaylistState();\n const annotationIntegration = useContext(AnnotationIntegrationContext);\n const {\n setAnnotations: _setAnnotations,\n setActiveAnnotationId,\n setTrackMute,\n setTrackSolo,\n setTrackVolume,\n setTrackPan,\n setSelection,\n play,\n setScrollContainer,\n setSelectedTrackId,\n setCurrentTime,\n setLoopRegion,\n } = usePlaylistControls();\n const {\n peaksDataArray,\n trackStates,\n tracks,\n duration,\n samplesPerPixel,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n controls,\n barWidth,\n barGap,\n roundedBars,\n isReady,\n mono,\n } = usePlaylistData();\n\n // Optional spectrogram integration (only available when SpectrogramProvider is present)\n const spectrogram = useContext(SpectrogramIntegrationContext);\n\n // Per-track spectrogram rendering helpers (memoized) — only computed when spectrogram is available\n const perTrackSpectrogramHelpers = useMemo(() => {\n if (!spectrogram)\n return new Map<\n string,\n {\n colorLUT: Uint8Array;\n frequencyScaleFn: (f: number, minF: number, maxF: number) => number;\n config: SpectrogramConfig | undefined;\n }\n >();\n const helpers = new Map<\n string,\n {\n colorLUT: Uint8Array;\n frequencyScaleFn: (f: number, minF: number, maxF: number) => number;\n config: SpectrogramConfig | undefined;\n }\n >();\n tracks.forEach((track) => {\n const mode =\n spectrogram.trackSpectrogramOverrides.get(track.id)?.renderMode ??\n track.renderMode ??\n 'waveform';\n if (mode === 'waveform') return;\n const overrides = spectrogram.trackSpectrogramOverrides.get(track.id);\n const cm =\n overrides?.colorMap ??\n track.spectrogramColorMap ??\n spectrogram.spectrogramColorMap ??\n 'viridis';\n const cfg = overrides?.config ?? track.spectrogramConfig ?? spectrogram.spectrogramConfig;\n helpers.set(track.id, {\n colorLUT: spectrogram.getColorMap(cm),\n frequencyScaleFn: spectrogram.getFrequencyScale(cfg?.frequencyScale ?? 'mel'),\n config: cfg,\n });\n });\n return helpers;\n }, [tracks, spectrogram]);\n\n // State for spectrogram settings modal\n const [settingsModalTrackId, setSettingsModalTrackId] = useState<string | null>(null);\n\n const [isSelecting, setIsSelecting] = useState(false);\n const mouseDownTimeRef = useRef<number>(0);\n\n const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n\n const handleScrollContainerRef = useCallback(\n (element: HTMLDivElement | null) => {\n scrollContainerRef.current = element;\n setScrollContainer(element);\n },\n [setScrollContainer]\n );\n\n // Calculate dimensions — derive duration directly from tracks prop to prevent width\n // shift. The `duration` state is set in an effect and lags tracks by at least one render.\n const tracksMaxDuration = tracks.reduce((max, track) => {\n return track.clips.reduce((clipMax, clip) => {\n const end = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n return Math.max(clipMax, end);\n }, max);\n }, 0);\n let displayDuration =\n tracksMaxDuration > 0\n ? tracksMaxDuration\n : duration > 0\n ? duration\n : DEFAULT_EMPTY_TRACK_DURATION;\n\n // When indefinitePlayback is enabled, ensure the timeline fills the visible scroll container\n if (indefinitePlayback) {\n const containerWidth = scrollContainerRef.current?.clientWidth ?? 0;\n const minContainerDuration = (containerWidth * samplesPerPixel) / sampleRate;\n displayDuration = Math.max(displayDuration, minContainerDuration);\n }\n\n if (recordingState?.isRecording) {\n const recordingEndSample = recordingState.startSample + recordingState.durationSamples;\n const recordingEndTime = recordingEndSample / sampleRate;\n displayDuration = Math.max(displayDuration, recordingEndTime + 10);\n }\n\n const tracksFullWidth = Math.floor((displayDuration * sampleRate) / samplesPerPixel);\n\n const handleAnnotationClick = async (annotation: AnnotationData) => {\n setActiveAnnotationId(annotation.id);\n const playDuration = !continuousPlay ? annotation.end - annotation.start : undefined;\n try {\n await play(annotation.start, playDuration);\n } catch (err) {\n console.error(\n 'waveform-playlist: Failed to start playback for annotation',\n annotation.id,\n err\n );\n }\n };\n\n const selectTrack = useCallback(\n (trackIndex: number) => {\n if (trackIndex >= 0 && trackIndex < tracks.length) {\n const track = tracks[trackIndex];\n setSelectedTrackId(track.id);\n }\n },\n [tracks, setSelectedTrackId]\n );\n\n const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const clickTime = (x * samplesPerPixel) / sampleRate;\n\n const y = e.clientY - rect.top;\n const trackY = y;\n\n let cumulativeHeight = 0;\n let clickedTrackIndex = -1;\n\n for (let i = 0; i < peaksDataArray.length; i++) {\n const trackClipPeaks = peaksDataArray[i];\n const rawCh = getTrackChannelCount(trackClipPeaks, recordingState, tracks[i]?.id, mono);\n const trackMode =\n spectrogram?.trackSpectrogramOverrides.get(tracks[i]?.id)?.renderMode ??\n tracks[i]?.renderMode ??\n 'waveform';\n const effectiveCh = trackMode === 'both' ? rawCh * 2 : rawCh;\n const trackHeight = effectiveCh * waveHeight + (showClipHeaders ? 22 : 0);\n\n if (trackY >= cumulativeHeight && trackY < cumulativeHeight + trackHeight) {\n clickedTrackIndex = i;\n break;\n }\n cumulativeHeight += trackHeight;\n }\n\n if (clickedTrackIndex !== -1) {\n selectTrack(clickedTrackIndex);\n }\n\n mouseDownTimeRef.current = clickTime;\n setIsSelecting(true);\n setCurrentTime(clickTime);\n setSelection(clickTime, clickTime);\n };\n\n const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {\n if (!isSelecting || isPlaying) return;\n\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const moveTime = (x * samplesPerPixel) / sampleRate;\n\n const start = Math.min(selectionStart, moveTime);\n const end = Math.max(selectionStart, moveTime);\n setSelection(start, end);\n };\n\n const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {\n if (!isSelecting) return;\n\n setIsSelecting(false);\n\n // During playback, use the time captured at mouseDown — auto-scroll shifts the\n // overlay between mouseDown and mouseUp, so recomputing from getBoundingClientRect()\n // would produce a wrong (shifted) position.\n if (isPlaying) {\n const clickTime = Math.max(0, mouseDownTimeRef.current);\n setCurrentTime(clickTime);\n setSelection(clickTime, clickTime);\n play(clickTime);\n return;\n }\n\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const endTime = (x * samplesPerPixel) / sampleRate;\n\n const start = Math.min(selectionStart, endTime);\n const end = Math.max(selectionStart, endTime);\n\n if (Math.abs(end - start) < 0.1) {\n setCurrentTime(start);\n } else {\n setSelection(start, end);\n }\n };\n\n // Only show loading if we have tracks WITH clips but peaks haven't been computed yet.\n // Don't check audioBuffers — it's set in an effect and can be stale for one or more\n // renders after tracks change, causing the playlist to unmount and remount (layout shift).\n // Placeholder tracks (clips: []) bypass this check intentionally.\n const hasClips = tracks.some((track) => track.clips.length > 0);\n if (hasClips && peaksDataArray.length === 0) {\n return <div className={className}>Loading waveform...</div>;\n }\n\n // Build track controls slots for the ControlsColumn (outside scroll area)\n const trackControlsSlots = controls.show\n ? peaksDataArray.map((trackClipPeaks, trackIndex) => {\n const track = tracks[trackIndex];\n if (!track) return null;\n\n const trackState = trackStates[trackIndex] || {\n name: `Track ${trackIndex + 1}`,\n muted: false,\n soloed: false,\n volume: 1.0,\n pan: 0,\n };\n\n const hasMidiNotes = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);\n const effectiveRenderMode =\n spectrogram?.trackSpectrogramOverrides.get(track.id)?.renderMode ??\n track.renderMode ??\n (hasMidiNotes ? 'piano-roll' : 'waveform');\n\n const maxChannels = getTrackChannelCount(trackClipPeaks, recordingState, track.id, mono);\n\n // Height must match Track component: waveHeight * numChannels + clipHeaderHeight\n const slotHeight = waveHeight * maxChannels + (showClipHeaders ? CLIP_HEADER_HEIGHT : 0);\n\n const trackControlContent = renderTrackControls ? (\n renderTrackControls(trackIndex)\n ) : (\n <Controls onClick={() => selectTrack(trackIndex)}>\n <Header style={{ justifyContent: 'center', position: 'relative' }}>\n {onRemoveTrack && (\n <CloseButton\n onClick={(e) => {\n e.stopPropagation();\n onRemoveTrack(trackIndex);\n }}\n />\n )}\n <span\n style={{\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n padding: '0 24px',\n display: 'block',\n }}\n >\n {trackState.name || `Track ${trackIndex + 1}`}\n </span>\n {spectrogram?.renderMenuItems && (\n <span style={{ position: 'absolute', right: 0, top: 0 }}>\n <TrackMenu\n items={(onClose) =>\n spectrogram.renderMenuItems!({\n renderMode: effectiveRenderMode,\n onRenderModeChange: (mode) =>\n spectrogram.setTrackRenderMode(track.id, mode),\n onOpenSettings: () => setSettingsModalTrackId(track.id),\n onClose,\n })\n }\n />\n </span>\n )}\n </Header>\n <ButtonGroup>\n <Button\n $variant={trackState.muted ? 'danger' : 'outline'}\n onClick={() => setTrackMute(trackIndex, !trackState.muted)}\n >\n Mute\n </Button>\n <Button\n $variant={trackState.soloed ? 'info' : 'outline'}\n onClick={() => setTrackSolo(trackIndex, !trackState.soloed)}\n >\n Solo\n </Button>\n </ButtonGroup>\n <SliderWrapper>\n <VolumeDownIcon />\n <Slider\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={trackState.volume}\n onChange={(e) => setTrackVolume(trackIndex, parseFloat(e.target.value))}\n />\n <VolumeUpIcon />\n </SliderWrapper>\n <SliderWrapper>\n <span>L</span>\n <Slider\n min=\"-1\"\n max=\"1\"\n step=\"0.01\"\n value={trackState.pan}\n onChange={(e) => setTrackPan(trackIndex, parseFloat(e.target.value))}\n />\n <span>R</span>\n </SliderWrapper>\n </Controls>\n );\n\n return (\n <ControlSlot\n key={track.id}\n $height={slotHeight}\n $isSelected={track.id === selectedTrackId}\n >\n {trackControlContent}\n </ControlSlot>\n );\n })\n : undefined;\n\n return (\n <DevicePixelRatioProvider>\n <PlaylistInfoContext.Provider\n value={{\n samplesPerPixel,\n sampleRate,\n zoomLevels: [samplesPerPixel],\n waveHeight,\n timeScaleHeight,\n duration: displayDuration * 1000,\n controls,\n barWidth,\n barGap,\n roundedBars,\n }}\n >\n <Playlist\n theme={theme}\n backgroundColor={\n theme.playlistBackgroundColor || waveformColorToCss(theme.waveOutlineColor)\n }\n timescaleBackgroundColor={theme.timescaleBackgroundColor}\n timescaleWidth={tracksFullWidth}\n tracksWidth={tracksFullWidth}\n controlsWidth={controls.show ? controls.width : 0}\n onTracksMouseDown={handleMouseDown}\n onTracksMouseMove={handleMouseMove}\n onTracksMouseUp={handleMouseUp}\n scrollContainerRef={handleScrollContainerRef}\n isSelecting={isSelecting}\n data-playlist-state={isReady ? 'ready' : 'loading'}\n trackControlsSlots={trackControlsSlots}\n timescaleGapHeight={timeScaleHeight > 0 ? timeScaleHeight + 1 : 0}\n timescale={\n timeScaleHeight > 0 ? (\n <>\n <SmartScale renderTick={renderTick} />\n {isLoopEnabled && (\n <TimescaleLoopRegion\n startPosition={(Math.min(loopStart, loopEnd) * sampleRate) / samplesPerPixel}\n endPosition={(Math.max(loopStart, loopEnd) * sampleRate) / samplesPerPixel}\n markerColor={theme.loopMarkerColor}\n regionColor={theme.loopRegionColor}\n minPosition={0}\n maxPosition={tracksFullWidth}\n onLoopRegionChange={(startPixels, endPixels) => {\n const startSeconds = (startPixels * samplesPerPixel) / sampleRate;\n const endSeconds = (endPixels * samplesPerPixel) / sampleRate;\n setLoopRegion(startSeconds, endSeconds);\n }}\n />\n )}\n </>\n ) : undefined\n }\n >\n <>\n {peaksDataArray.map((trackClipPeaks, trackIndex) => {\n const track = tracks[trackIndex];\n if (!track) return null;\n\n const hasMidiNotes = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);\n const effectiveRenderMode =\n spectrogram?.trackSpectrogramOverrides.get(track.id)?.renderMode ??\n track.renderMode ??\n (hasMidiNotes ? 'piano-roll' : 'waveform');\n\n const maxChannels = getTrackChannelCount(\n trackClipPeaks,\n recordingState,\n track.id,\n mono\n );\n\n return (\n <TrackComponent\n key={track.id}\n numChannels={maxChannels}\n backgroundColor={\n effectiveRenderMode === 'piano-roll'\n ? theme.pianoRollBackgroundColor || '#1a1a2e'\n : waveformColorToCss(theme.waveOutlineColor)\n }\n offset={0}\n width={tracksFullWidth}\n hasClipHeaders={showClipHeaders}\n trackId={track.id}\n isSelected={track.id === selectedTrackId}\n >\n {effectiveRenderMode !== 'waveform' &&\n (() => {\n const helpers = perTrackSpectrogramHelpers.get(track.id);\n const trackCfg = helpers?.config;\n if (!trackCfg?.labels || !helpers) return null;\n return (\n <SpectrogramLabels\n waveHeight={waveHeight}\n numChannels={maxChannels}\n frequencyScaleFn={helpers.frequencyScaleFn}\n minFrequency={trackCfg.minFrequency ?? 0}\n maxFrequency={trackCfg.maxFrequency ?? sampleRate / 2}\n labelsColor={trackCfg.labelsColor}\n labelsBackground={trackCfg.labelsBackground}\n renderMode={effectiveRenderMode as 'spectrogram' | 'both'}\n hasClipHeaders={showClipHeaders}\n />\n );\n })()}\n {trackClipPeaks.map((clip, clipIndex) => {\n const peaksData = clip.peaks;\n const width = peaksData.length;\n\n return (\n <Clip\n key={clip.clipId}\n clipId={clip.clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n trackName={clip.trackName}\n startSample={clip.startSample}\n durationSamples={clip.durationSamples}\n samplesPerPixel={samplesPerPixel}\n showHeader={showClipHeaders}\n disableHeaderDrag={!interactiveClips}\n isSelected={track.id === selectedTrackId}\n trackId={track.id}\n fadeIn={clip.fadeIn}\n fadeOut={clip.fadeOut}\n sampleRate={sampleRate}\n showFades={showFades}\n touchOptimized={touchOptimized}\n onMouseDown={(e) => {\n const target = e.target as HTMLElement;\n const isDraggable = target.closest(\n '[role=\"button\"][aria-roledescription=\"draggable\"]'\n );\n if (isDraggable) {\n return;\n }\n selectTrack(trackIndex);\n }}\n >\n {peaksData.data.map((channelPeaks: Peaks, channelIndex: number) => {\n return (\n <ChannelWithProgress\n key={`${clip.clipId}-${channelIndex}`}\n index={channelIndex}\n data={channelPeaks}\n bits={peaksData.bits}\n length={width}\n isSelected={track.id === selectedTrackId}\n clipStartSample={clip.startSample}\n clipDurationSamples={clip.durationSamples}\n renderMode={clip.midiNotes ? 'piano-roll' : effectiveRenderMode}\n midiNotes={clip.midiNotes}\n clipSampleRate={clip.sampleRate}\n clipOffsetSeconds={\n clip.offsetSamples != null\n ? clip.offsetSamples / (clip.sampleRate || sampleRate)\n : 0\n }\n samplesPerPixel={samplesPerPixel}\n spectrogramClipId={clip.clipId}\n spectrogramOnCanvasRegister={\n spectrogram ? spectrogram.registerSpectrogramCanvas : undefined\n }\n spectrogramOnCanvasUnregister={\n spectrogram ? spectrogram.unregisterSpectrogramCanvas : undefined\n }\n />\n );\n })}\n </Clip>\n );\n })}\n {recordingState?.isRecording &&\n recordingState.trackId === track.id &&\n recordingState.peaks[0]?.length > 0 &&\n (() => {\n // Strip leading-silence peaks so the visible preview matches\n // audible content. Recorder runs at real time, but the user's\n // first reaction lands ~outputLatency+lookAhead seconds into\n // the buffer (they hadn't heard the backing track yet). Without\n // this, the playhead — which now uses audible time — would\n // appear behind the right edge of the recorded waveform.\n // Mirrors useIntegratedRecording's finalization compensation\n // (shared `resolveRecordingOffsetSamples`) and dawcore's preview-skip\n // pattern. `getLookAhead()` reads from the same engine the\n // playhead's animation loop uses — keeps the two in lockstep.\n const latencyOffsetSamples = resolveRecordingOffsetSamples({\n overrideSeconds: recordingState.latencyOffset,\n outputLatency: getOutputLatency(),\n lookAhead: getLookAhead(),\n sampleRate,\n });\n const latencyPixels = Math.floor(latencyOffsetSamples / samplesPerPixel);\n const skipPeakElements = latencyPixels * 2; // each pixel is a min/max pair\n const previewDuration = Math.max(\n 0,\n recordingState.durationSamples - latencyOffsetSamples\n );\n const previewChannels = (\n mono ? recordingState.peaks.slice(0, 1) : recordingState.peaks\n ).map((channelPeaks) =>\n skipPeakElements > 0 && skipPeakElements < channelPeaks.length\n ? channelPeaks.subarray(skipPeakElements)\n : channelPeaks\n );\n return (\n <Clip\n key={`${track.id}-recording`}\n clipId=\"recording-preview\"\n trackIndex={trackIndex}\n clipIndex={trackClipPeaks.length}\n trackName=\"Recording...\"\n startSample={recordingState.startSample}\n durationSamples={previewDuration}\n samplesPerPixel={samplesPerPixel}\n showHeader={showClipHeaders}\n disableHeaderDrag={true}\n isSelected={track.id === selectedTrackId}\n trackId={track.id}\n >\n {previewChannels.map((channelPeaks, chIdx) => (\n <ChannelWithProgress\n key={`${track.id}-recording-${chIdx}`}\n index={chIdx}\n data={channelPeaks}\n bits={recordingState.bits}\n length={Math.floor(channelPeaks.length / 2)}\n isSelected={track.id === selectedTrackId}\n clipStartSample={recordingState.startSample}\n clipDurationSamples={previewDuration}\n />\n ))}\n </Clip>\n );\n })()}\n </TrackComponent>\n );\n })}\n {annotations.length > 0 && annotationIntegration && (\n <annotationIntegration.AnnotationBoxesWrapper height={30} width={tracksFullWidth}>\n {annotations.map((annotation, index) => {\n const startPosition = (annotation.start * sampleRate) / samplesPerPixel;\n const endPosition = (annotation.end * sampleRate) / samplesPerPixel;\n const label = getAnnotationBoxLabel\n ? getAnnotationBoxLabel(annotation, index)\n : annotation.id;\n return (\n <annotationIntegration.AnnotationBox\n key={annotation.id}\n annotationId={annotation.id}\n annotationIndex={index}\n startPosition={startPosition}\n endPosition={endPosition}\n label={label}\n color=\"#ff9800\"\n isActive={annotation.id === activeAnnotationId}\n onClick={() => handleAnnotationClick(annotation)}\n editable={annotationsEditable}\n />\n );\n })}\n </annotationIntegration.AnnotationBoxesWrapper>\n )}\n {selectionStart !== selectionEnd && (\n <Selection\n startPosition={\n (Math.min(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n }\n endPosition={\n (Math.max(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n }\n color={theme.selectionColor}\n />\n )}\n {(isPlaying || selectionStart === selectionEnd) &&\n (renderPlayhead ? (\n <CustomPlayhead\n renderPlayhead={renderPlayhead}\n color={theme.playheadColor}\n samplesPerPixel={samplesPerPixel}\n sampleRate={sampleRate}\n />\n ) : (\n <AnimatedPlayhead color={theme.playheadColor} />\n ))}\n </>\n </Playlist>\n </PlaylistInfoContext.Provider>\n {spectrogram?.SettingsModal &&\n typeof document !== 'undefined' &&\n createPortal(\n <spectrogram.SettingsModal\n open={settingsModalTrackId !== null}\n onClose={() => setSettingsModalTrackId(null)}\n config={\n settingsModalTrackId !== null\n ? (spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)?.config ??\n tracks.find((t) => t.id === settingsModalTrackId)?.spectrogramConfig ??\n spectrogram.spectrogramConfig ??\n {})\n : {}\n }\n colorMap={\n settingsModalTrackId !== null\n ? (spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)?.colorMap ??\n tracks.find((t) => t.id === settingsModalTrackId)?.spectrogramColorMap ??\n spectrogram.spectrogramColorMap ??\n 'viridis')\n : 'viridis'\n }\n onApply={(newConfig, newColorMap) => {\n if (settingsModalTrackId !== null) {\n spectrogram.setTrackSpectrogramConfig(settingsModalTrackId, newConfig, newColorMap);\n }\n }}\n />,\n document.body\n )}\n </DevicePixelRatioProvider>\n );\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { usePlaybackAnimation, usePlaylistData } from '../WaveformPlaylistContext';\n\nconst PlayheadLine = styled.div.attrs<{ $color: string; $width: number }>((props) => ({\n style: {\n width: `${props.$width}px`,\n background: props.$color,\n },\n}))<{ $color: string; $width: number }>`\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n z-index: 100; /* Below sticky controls (z-index: 101) so playhead is hidden when scrolled behind controls */\n pointer-events: none;\n will-change: transform;\n`;\n\ninterface AnimatedPlayheadProps {\n color?: string;\n}\n\n/**\n * Animated playhead that updates position via the shared animation frame registry.\n * No own rAF loop — the WaveformPlaylistContext animation loop drives all visual updates.\n */\nexport const AnimatedPlayhead: React.FC<AnimatedPlayheadProps> = ({ color = '#ff0000' }) => {\n const playheadRef = useRef<HTMLDivElement>(null);\n\n const {\n isPlaying,\n currentTimeRef,\n visualTimeRef,\n registerFrameCallback,\n unregisterFrameCallback,\n } = usePlaybackAnimation();\n const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();\n\n // Register per-frame callback during playback\n useEffect(() => {\n const id = 'playhead';\n if (isPlaying) {\n registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {\n if (playheadRef.current) {\n const px = (visualTime * sr) / spp;\n playheadRef.current.style.transform = `translate3d(${px}px, 0, 0)`;\n }\n });\n }\n return () => unregisterFrameCallback(id);\n }, [isPlaying, registerFrameCallback, unregisterFrameCallback]);\n\n // Update position when not playing (seeks, stops, etc.) — no rAF needed.\n // Reads visualTimeRef so the static playhead lines up with audible output\n // (the pause/seek paths sync visualTimeRef when they update currentTimeRef).\n useEffect(() => {\n if (!isPlaying && playheadRef.current) {\n const time = visualTimeRef.current ?? currentTimeRef.current ?? 0;\n const position = (time * sampleRate) / samplesPerPixel;\n playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;\n }\n });\n\n return <PlayheadLine ref={playheadRef} $color={color} $width={progressBarWidth} data-playhead />;\n};\n","import React, { useId, useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport {\n clipPixelWidth as computeClipPixelWidth,\n type MidiNoteData,\n} from '@waveform-playlist/core';\nimport {\n SmartChannel,\n type SmartChannelProps,\n useTheme,\n usePlaylistInfo,\n type WaveformPlaylistTheme,\n waveformColorToCss,\n} from '@waveform-playlist/ui-components';\nimport { usePlaybackAnimation, usePlaylistData } from '../WaveformPlaylistContext';\n\nconst ChannelWrapper = styled.div`\n position: relative;\n`;\n\ninterface BackgroundProps {\n readonly $color: string;\n readonly $height: number;\n readonly $top: number;\n readonly $width: number;\n}\n\nconst Background = styled.div.attrs<BackgroundProps>((props) => ({\n style: {\n top: `${props.$top}px`,\n width: `${props.$width}px`,\n height: `${props.$height}px`,\n background: props.$color,\n },\n}))<BackgroundProps>`\n position: absolute;\n left: 0;\n z-index: 0;\n /* Force GPU compositing layer to prevent gradient flickering during scroll */\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\ninterface ProgressOverlayProps {\n readonly $color: string;\n readonly $height: number;\n readonly $top: number;\n readonly $width: number;\n}\n\nconst ProgressOverlay = styled.div.attrs<ProgressOverlayProps>((props) => ({\n style: {\n top: `${props.$top}px`,\n height: `${props.$height}px`,\n width: `${props.$width}px`,\n background: props.$color,\n transform: 'scaleX(0)',\n },\n}))<ProgressOverlayProps>`\n position: absolute;\n left: 0;\n pointer-events: none;\n z-index: 1;\n transform-origin: left;\n /* scaleX changes are composite-only (GPU) — no layout reflow per frame */\n will-change: transform;\n`;\n\nconst ChannelContainer = styled.div`\n position: relative;\n z-index: 2;\n`;\n\nexport interface ChannelWithProgressProps extends SmartChannelProps {\n /** Start sample of the clip containing this channel (for progress calculation) */\n clipStartSample: number;\n /** Duration in samples of the clip */\n clipDurationSamples: number;\n /** MIDI note data for piano-roll rendering */\n midiNotes?: MidiNoteData[];\n /** Sample rate of the clip (for MIDI note positioning) */\n clipSampleRate?: number;\n /** Clip offset in seconds (for MIDI note positioning) */\n clipOffsetSeconds?: number;\n}\n\n/**\n * SmartChannel wrapper that adds an animated progress overlay.\n * The progress overlay shows the \"played\" portion of the waveform.\n * Updates via the shared animation frame registry — no own rAF loop.\n */\nexport const ChannelWithProgress: React.FC<ChannelWithProgressProps> = ({\n clipStartSample,\n clipDurationSamples,\n midiNotes,\n clipSampleRate,\n clipOffsetSeconds,\n ...smartChannelProps\n}) => {\n const progressRef = useRef<HTMLDivElement>(null);\n const callbackId = useId();\n const theme = useTheme() as WaveformPlaylistTheme;\n const { waveHeight } = usePlaylistInfo();\n\n const {\n isPlaying,\n currentTimeRef,\n visualTimeRef,\n registerFrameCallback,\n unregisterFrameCallback,\n } = usePlaybackAnimation();\n const { samplesPerPixel, sampleRate } = usePlaylistData();\n\n const progressColor = theme?.waveProgressColor || 'rgba(0, 0, 0, 0.1)';\n\n // Use shared helper to compute clip pixel width (must match Clip.tsx sizing)\n // peaksData.length may be shorter than the clip when audio is shorter than configured duration\n const clipPixelWidth = computeClipPixelWidth(\n clipStartSample,\n clipDurationSamples,\n samplesPerPixel\n );\n\n // Register per-frame callback during playback — no own rAF loop\n useEffect(() => {\n if (isPlaying) {\n registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {\n if (progressRef.current) {\n const currentSample = visualTime * sr;\n const clipEndSample = clipStartSample + clipDurationSamples;\n\n let ratio = 0;\n if (currentSample <= clipStartSample) {\n ratio = 0;\n } else if (currentSample >= clipEndSample) {\n ratio = 1;\n } else {\n const playedSamples = currentSample - clipStartSample;\n ratio = playedSamples / clipDurationSamples;\n }\n\n // scaleX is composite-only — no layout reflow, GPU-accelerated\n progressRef.current.style.transform = `scaleX(${ratio})`;\n }\n });\n }\n return () => unregisterFrameCallback(callbackId);\n }, [\n isPlaying,\n clipStartSample,\n clipDurationSamples,\n callbackId,\n registerFrameCallback,\n unregisterFrameCallback,\n ]);\n\n // Also update when not playing (for seeks, stops, etc.). Reads visualTimeRef\n // so the static progress fill matches the playhead's audible position.\n useEffect(() => {\n if (!isPlaying && progressRef.current) {\n const currentTime = visualTimeRef.current ?? currentTimeRef.current ?? 0;\n const currentSample = currentTime * sampleRate;\n const clipEndSample = clipStartSample + clipDurationSamples;\n\n let ratio = 0;\n if (currentSample <= clipStartSample) {\n ratio = 0;\n } else if (currentSample >= clipEndSample) {\n ratio = 1;\n } else {\n const playedSamples = currentSample - clipStartSample;\n ratio = playedSamples / clipDurationSamples;\n }\n\n progressRef.current.style.transform = `scaleX(${ratio})`;\n }\n });\n\n // Get the draw mode from theme (defaults to 'inverted')\n const drawMode = theme?.waveformDrawMode || 'inverted';\n\n let backgroundColor;\n if (drawMode === 'inverted') {\n backgroundColor =\n smartChannelProps.isSelected && theme\n ? theme.selectedWaveFillColor\n : theme?.waveFillColor || 'white';\n } else {\n backgroundColor =\n smartChannelProps.isSelected && theme\n ? theme.selectedWaveOutlineColor\n : theme?.waveOutlineColor || 'grey';\n }\n\n // Use black background for spectrogram mode, themed background for piano-roll\n const isSpectrogramMode =\n smartChannelProps.renderMode === 'spectrogram' || smartChannelProps.renderMode === 'both';\n const isPianoRollMode = smartChannelProps.renderMode === 'piano-roll';\n const isBothMode = smartChannelProps.renderMode === 'both';\n const backgroundCss = isSpectrogramMode\n ? '#000'\n : isPianoRollMode\n ? theme?.pianoRollBackgroundColor || '#1a1a2e'\n : waveformColorToCss(backgroundColor);\n\n // In \"both\" mode each half (spectrogram + waveform) is waveHeight/2 so the track\n // container stays the same height as a single-mode track.\n const halfHeight = Math.floor(waveHeight / 2);\n const effectiveHeight = waveHeight;\n const effectiveTop = isBothMode\n ? smartChannelProps.index * waveHeight\n : smartChannelProps.index * waveHeight;\n\n // In \"both\" mode, the waveform portion needs its own (non-black) background\n const waveformBackgroundCss = waveformColorToCss(backgroundColor);\n\n return (\n <ChannelWrapper>\n {/* Background layer - color depends on draw mode */}\n {isBothMode ? (\n <>\n {/* Spectrogram portion: black background */}\n <Background\n $color=\"#000\"\n $height={halfHeight}\n $top={effectiveTop}\n $width={smartChannelProps.length}\n />\n {/* Waveform portion: themed background */}\n <Background\n $color={waveformBackgroundCss}\n $height={halfHeight}\n $top={effectiveTop + halfHeight}\n $width={smartChannelProps.length}\n />\n </>\n ) : (\n <Background\n $color={backgroundCss}\n $height={effectiveHeight}\n $top={effectiveTop}\n $width={smartChannelProps.length}\n />\n )}\n {/* Progress overlay - shows played portion with progress color (skip for piano-roll) */}\n {!isPianoRollMode && (\n <ProgressOverlay\n ref={progressRef}\n $color={progressColor}\n $height={effectiveHeight}\n $top={effectiveTop}\n $width={clipPixelWidth}\n />\n )}\n {/* Waveform canvas with transparent background */}\n <ChannelContainer>\n <SmartChannel\n {...smartChannelProps}\n transparentBackground\n midiNotes={midiNotes}\n sampleRate={clipSampleRate}\n clipOffsetSeconds={clipOffsetSeconds}\n />\n </ChannelContainer>\n </ChannelWrapper>\n );\n};\n","import { createContext, useContext } from 'react';\nimport type {\n SpectrogramConfig,\n ColorMapValue,\n RenderMode,\n TrackSpectrogramOverrides,\n} from '@waveform-playlist/core';\nimport type { TrackMenuItem } from '@waveform-playlist/ui-components';\n\n/**\n * Single-call canvas registration shape. Consolidates the OffscreenCanvas\n * transfer and per-canvas metadata that earlier required two separate\n * registration paths.\n *\n * Provider/orchestrator look up trackId from clipId and derive\n * globalPixelOffset = chunkIndex * MAX_CANVAS_WIDTH.\n */\nexport interface SpectrogramCanvasRegistration {\n canvasId: string;\n canvas: OffscreenCanvas;\n clipId: string;\n channelIndex: number;\n chunkIndex: number;\n widthPx: number;\n heightPx: number;\n}\n\nexport interface SpectrogramIntegration {\n trackSpectrogramOverrides: Map<string, TrackSpectrogramOverrides>;\n spectrogramConfig?: SpectrogramConfig;\n spectrogramColorMap?: ColorMapValue;\n setTrackRenderMode: (trackId: string, mode: RenderMode) => void;\n setTrackSpectrogramConfig: (\n trackId: string,\n config: SpectrogramConfig,\n colorMap?: ColorMapValue\n ) => void;\n /** Single-canvas registration. OffscreenCanvas is non-transferable — call once per canvas. */\n registerSpectrogramCanvas: (reg: SpectrogramCanvasRegistration) => void;\n unregisterSpectrogramCanvas: (canvasId: string) => void;\n /** Render spectrogram menu items for a track's context menu */\n renderMenuItems?: (props: {\n renderMode: string;\n onRenderModeChange: (mode: RenderMode) => void;\n onOpenSettings: () => void;\n onClose?: () => void;\n }) => TrackMenuItem[];\n /** Settings modal component provided by the spectrogram package */\n SettingsModal?: React.ComponentType<{\n open: boolean;\n onClose: () => void;\n config: SpectrogramConfig;\n colorMap: ColorMapValue;\n onApply: (config: SpectrogramConfig, colorMap: ColorMapValue) => void;\n }>;\n /** Get color lookup table for a color map name */\n getColorMap: (name: ColorMapValue) => Uint8Array;\n /** Get frequency scale function for a scale name */\n getFrequencyScale: (name: string) => (f: number, minF: number, maxF: number) => number;\n}\n\nexport const SpectrogramIntegrationContext = createContext<SpectrogramIntegration | null>(null);\n\nexport const SpectrogramIntegrationProvider = SpectrogramIntegrationContext.Provider;\n\n/**\n * Hook to access spectrogram integration provided by @waveform-playlist/spectrogram.\n * Throws if used without <SpectrogramProvider> wrapping the component tree.\n *\n * Follows the Kent C. Dodds pattern:\n * https://kentcdodds.com/blog/how-to-use-react-context-effectively\n */\nexport function useSpectrogramIntegration(): SpectrogramIntegration {\n const context = useContext(SpectrogramIntegrationContext);\n if (!context) {\n throw new Error(\n 'useSpectrogramIntegration must be used within <SpectrogramProvider>. ' +\n 'Install @waveform-playlist/spectrogram and wrap your app with <SpectrogramProvider>.'\n );\n }\n return context;\n}\n","import React, { useCallback } from 'react';\nimport type {\n AnnotationData,\n AnnotationAction,\n AnnotationActionOptions,\n RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\nimport { useAnnotationIntegration } from '../AnnotationIntegrationContext';\nimport { usePlaylistState, usePlaylistControls } from '../WaveformPlaylistContext';\nimport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport interface PlaylistAnnotationListProps {\n /** Height in pixels for the annotation text list */\n height?: number;\n /**\n * Custom render function for annotation items in the text list.\n * When provided, completely replaces the default annotation item rendering.\n */\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n /**\n * Callback when annotations are updated (e.g., text edited).\n * Called with the full updated annotations array.\n */\n onAnnotationUpdate?: OnAnnotationUpdateFn;\n /**\n * Action controls to show on each annotation item (e.g., delete, split).\n * Only rendered when `annotationsEditable` is true in context.\n */\n controls?: AnnotationAction[];\n /**\n * Override annotation list config. Falls back to context values\n * `{ linkEndpoints, continuousPlay }` if not provided.\n */\n annotationListConfig?: AnnotationActionOptions;\n /** Where to position the active annotation when auto-scrolling. Defaults to 'center'. */\n scrollActivePosition?: ScrollLogicalPosition;\n /** Which scrollable containers to scroll: 'nearest' or 'all'. Defaults to 'nearest'. */\n scrollActiveContainer?: 'nearest' | 'all';\n}\n\n/**\n * Standalone annotation text list component for WaveformPlaylistProvider (WebAudio).\n *\n * Requires @waveform-playlist/annotations with AnnotationProvider.\n * Throws if used without `<AnnotationProvider>` wrapping the component tree.\n */\nexport const PlaylistAnnotationList: React.FC<PlaylistAnnotationListProps> = ({\n height,\n renderAnnotationItem,\n onAnnotationUpdate,\n controls,\n annotationListConfig,\n scrollActivePosition = 'center',\n scrollActiveContainer = 'nearest',\n}) => {\n const { annotations, activeAnnotationId, annotationsEditable, linkEndpoints, continuousPlay } =\n usePlaylistState();\n const integration = useAnnotationIntegration();\n const { setAnnotations } = usePlaylistControls();\n\n const resolvedConfig = annotationListConfig ?? { linkEndpoints, continuousPlay };\n\n const handleAnnotationUpdate = useCallback(\n (updatedAnnotations: AnnotationData[]) => {\n setAnnotations(updatedAnnotations);\n onAnnotationUpdate?.(updatedAnnotations);\n },\n [setAnnotations, onAnnotationUpdate]\n );\n\n const { AnnotationText } = integration;\n\n return (\n <AnnotationText\n annotations={annotations}\n activeAnnotationId={activeAnnotationId ?? undefined}\n shouldScrollToActive={true}\n scrollActivePosition={scrollActivePosition}\n scrollActiveContainer={scrollActiveContainer}\n editable={annotationsEditable}\n controls={annotationsEditable ? controls : undefined}\n annotationListConfig={resolvedConfig}\n height={height}\n onAnnotationUpdate={handleAnnotationUpdate}\n renderAnnotationItem={renderAnnotationItem}\n />\n );\n};\n","import React, { ReactNode } from 'react';\nimport type { RenderPlayheadFunction } from '@waveform-playlist/ui-components';\nimport type {\n AnnotationAction,\n AnnotationActionOptions,\n RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\nimport { usePlaylistState } from '../WaveformPlaylistContext';\nimport { useClipInteractionEnabled } from '../contexts/ClipInteractionContext';\nimport type { GetAnnotationBoxLabelFn } from '../types/annotations';\nimport { PlaylistVisualization } from './PlaylistVisualization';\nimport { PlaylistAnnotationList } from './PlaylistAnnotationList';\n\nexport interface WaveformProps {\n renderTrackControls?: (trackIndex: number) => ReactNode;\n /** Custom render function for timescale tick labels. `label` is a formatted string\n * (bar/beat notation like \"2.3\" in beats mode, or \"m:ss\" in temporal mode). */\n renderTick?: (label: string, pixelPosition: number) => ReactNode;\n /** @deprecated Use `renderTick` instead. */\n renderTimestamp?: (timeMs: number, pixelPosition: number) => ReactNode;\n /** Custom playhead render function. Receives position (pixels) and color from theme. */\n renderPlayhead?: RenderPlayheadFunction;\n annotationControls?: AnnotationAction[];\n annotationListConfig?: AnnotationActionOptions;\n annotationTextHeight?: number; // Height in pixels for the annotation text list\n /**\n * Custom render function for annotation items in the text list.\n * Use this to completely customize how each annotation is displayed.\n */\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => ReactNode;\n /**\n * Custom function to generate the label shown on annotation boxes in the waveform.\n * Receives the annotation data and its index, returns a string label.\n * Default: annotation.id\n */\n getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n /** Where to position the active annotation when auto-scrolling: 'center', 'start', 'end', or 'nearest'. Defaults to 'center'. */\n scrollActivePosition?: ScrollLogicalPosition;\n /** Which scrollable containers to scroll: 'nearest' (only the annotation list) or 'all' (including viewport). Defaults to 'nearest'. */\n scrollActiveContainer?: 'nearest' | 'all';\n className?: string;\n showClipHeaders?: boolean; // Show headers on clips for visual organization\n interactiveClips?: boolean; // Enable dragging/trimming interactions on clips (auto-enabled by ClipInteractionProvider)\n showFades?: boolean; // Show fade in/out overlays on clips\n /**\n * Enable mobile-optimized touch interactions.\n * When true, increases touch target sizes for clip boundaries.\n * Use with useDragSensors({ touchOptimized: true }) for best results.\n */\n touchOptimized?: boolean;\n /** Callback when a track's close button is clicked. Only renders close button when provided. */\n onRemoveTrack?: (trackIndex: number) => void;\n // Live recording state for real-time waveform preview\n recordingState?: {\n isRecording: boolean;\n trackId: string; // Which track is being recorded into\n startSample: number; // Where recording started\n durationSamples: number; // Current recording length\n peaks: (Int8Array | Int16Array)[]; // Per-channel live peaks data\n bits: 8 | 16; // Bit depth of peak values\n /**\n * Latency offset (seconds) to skip in the live preview. Absolute replacement\n * for the auto-computed outputLatency + lookAhead value. Pass the same value\n * given to useIntegratedRecording so preview and finalized clip match.\n */\n latencyOffset?: number;\n };\n}\n\n/**\n * Waveform visualization component that uses the playlist context.\n *\n * Composes PlaylistVisualization (waveform + tracks) and\n * PlaylistAnnotationList (annotation text list below the waveform).\n */\nexport const Waveform: React.FC<WaveformProps> = ({\n renderTrackControls,\n renderTick,\n renderTimestamp,\n renderPlayhead,\n annotationControls,\n annotationListConfig,\n annotationTextHeight,\n renderAnnotationItem,\n getAnnotationBoxLabel,\n scrollActivePosition = 'center',\n scrollActiveContainer = 'nearest',\n className,\n showClipHeaders = false,\n interactiveClips = false,\n showFades = false,\n touchOptimized = false,\n onRemoveTrack,\n recordingState,\n}) => {\n const { annotations } = usePlaylistState();\n const clipInteractionEnabled = useClipInteractionEnabled();\n const effectiveInteractiveClips = interactiveClips || clipInteractionEnabled;\n\n return (\n <>\n <PlaylistVisualization\n renderTrackControls={renderTrackControls}\n renderTick={\n renderTick ??\n (renderTimestamp\n ? (label, pix) => renderTimestamp(parseFloat(label) || 0, pix)\n : undefined)\n }\n renderPlayhead={renderPlayhead}\n annotationControls={annotationControls}\n getAnnotationBoxLabel={getAnnotationBoxLabel}\n className={className}\n showClipHeaders={showClipHeaders}\n interactiveClips={effectiveInteractiveClips}\n showFades={showFades}\n touchOptimized={touchOptimized}\n onRemoveTrack={onRemoveTrack}\n recordingState={recordingState}\n />\n {annotations.length > 0 && (\n <PlaylistAnnotationList\n height={annotationTextHeight}\n renderAnnotationItem={renderAnnotationItem}\n controls={annotationControls}\n annotationListConfig={annotationListConfig}\n scrollActivePosition={scrollActivePosition}\n scrollActiveContainer={scrollActiveContainer}\n />\n )}\n </>\n );\n};\n","import React, { useContext, useRef, useState, useCallback } from 'react';\nimport { DragDropProvider } from '@dnd-kit/react';\nimport { RestrictToHorizontalAxis } from '@dnd-kit/abstract/modifiers';\nimport {\n Playlist,\n Track as TrackComponent,\n Clip,\n Selection,\n FadeOverlay,\n PlaylistInfoContext,\n DevicePixelRatioProvider,\n SmartScale,\n useTheme,\n waveformColorToCss,\n} from '@waveform-playlist/ui-components';\nimport type { RenderPlayheadFunction } from '@waveform-playlist/ui-components';\nimport { AnnotationIntegrationContext } from '../AnnotationIntegrationContext';\nimport type { Peaks } from '@waveform-playlist/core';\nimport {\n useMediaElementAnimation,\n useMediaElementState,\n useMediaElementControls,\n useMediaElementData,\n} from '../MediaElementPlaylistContext';\nimport { useAnnotationDragHandlers } from '../hooks/useAnnotationDragHandlers';\nimport { noDropAnimationPlugins } from '../plugins/noDropAnimationPlugins';\nimport { AnimatedMediaElementPlayhead } from './AnimatedMediaElementPlayhead';\nimport { ChannelWithMediaElementProgress } from './ChannelWithMediaElementProgress';\nimport type {\n AnnotationData,\n GetAnnotationBoxLabelFn,\n OnAnnotationUpdateFn,\n} from '../types/annotations';\n\n/**\n * Wrapper that isolates the custom playhead's hooks from MediaElementPlaylist.\n * Calling renderPlayhead() directly inside MediaElementPlaylist's render would\n * merge its hooks into MediaElementPlaylist's hook count, causing \"Rendered more\n * hooks\" errors if renderPlayhead is conditionally provided.\n */\n// Stable refs for MediaElement playback (no AudioContext, so these are always 0)\nconst ZERO_REF: React.RefObject<number> = { current: 0 };\n\nconst CustomMediaElementPlayhead: React.FC<{\n renderPlayhead: RenderPlayheadFunction;\n color: string;\n samplesPerPixel: number;\n sampleRate: number;\n}> = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {\n const { isPlaying, currentTimeRef } = useMediaElementAnimation();\n\n return renderPlayhead({\n position: ((currentTimeRef.current ?? 0) * sampleRate) / samplesPerPixel,\n color,\n isPlaying,\n currentTimeRef,\n playbackStartTimeRef: ZERO_REF,\n audioStartPositionRef: ZERO_REF,\n samplesPerPixel,\n sampleRate,\n controlsOffset: 0,\n }) as React.ReactElement;\n};\n\nexport interface MediaElementPlaylistProps {\n /** Custom function to generate the label shown on annotation boxes */\n getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n /** Whether annotation boundaries can be edited by dragging. Defaults to false. */\n editable?: boolean;\n /** Whether dragging one annotation boundary also moves the adjacent annotation's boundary. Defaults to false. */\n linkEndpoints?: boolean;\n /**\n * Callback when annotations are updated (e.g., boundaries dragged).\n * Called with the full updated annotations array.\n */\n onAnnotationUpdate?: OnAnnotationUpdateFn;\n /** Custom playhead render function. Receives position, color, and animation refs for smooth 60fps animation. */\n renderPlayhead?: RenderPlayheadFunction;\n /** Show fade in/out overlays on the waveform. Defaults to false. */\n showFades?: boolean;\n className?: string;\n}\n\n/**\n * Standalone waveform + annotation boxes component for MediaElementPlaylistProvider.\n *\n * Renders the waveform visualization, annotation boxes, selection, and playhead.\n * Does NOT render the annotation text list — use `MediaElementAnnotationList` for that.\n *\n * Must be used inside a `MediaElementPlaylistProvider`.\n *\n * This component can be placed independently in consumer layouts, allowing the\n * waveform and annotation list to be positioned separately (e.g., in different\n * panels or with custom elements between them).\n */\nexport const MediaElementPlaylist: React.FC<MediaElementPlaylistProps> = ({\n getAnnotationBoxLabel,\n editable = false,\n linkEndpoints: linkEndpointsProp = false,\n onAnnotationUpdate,\n renderPlayhead,\n showFades = false,\n className,\n}) => {\n const theme = useTheme() as import('@waveform-playlist/ui-components').WaveformPlaylistTheme;\n\n // MediaElement context hooks\n const { isPlaying } = useMediaElementAnimation();\n const { annotations, activeAnnotationId } = useMediaElementState();\n const annotationIntegration = useContext(AnnotationIntegrationContext);\n const { play, seekTo, setActiveAnnotationId, setAnnotations, setScrollContainer } =\n useMediaElementControls();\n const {\n duration,\n peaksDataArray,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n samplesPerPixel,\n controls,\n playoutRef,\n barWidth,\n barGap,\n roundedBars,\n fadeIn,\n fadeOut,\n } = useMediaElementData();\n\n const [selectionStart, setSelectionStart] = useState(0);\n const [selectionEnd, setSelectionEnd] = useState(0);\n const [isSelecting, setIsSelecting] = useState(false);\n\n // Local ref for scroll container - also register with context for auto-scroll\n const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n\n // Callback to register scroll container with context\n const handleScrollContainerRef = useCallback(\n (el: HTMLDivElement | null) => {\n scrollContainerRef.current = el;\n setScrollContainer(el);\n },\n [setScrollContainer]\n );\n\n // Calculate dimensions\n const tracksFullWidth = Math.floor((duration * sampleRate) / samplesPerPixel);\n\n // Annotation click handler\n const handleAnnotationClick = useCallback(\n async (annotation: AnnotationData) => {\n setActiveAnnotationId(annotation.id);\n try {\n await play(annotation.start);\n } catch (err) {\n console.error(\n 'waveform-playlist: Failed to start playback for annotation',\n annotation.id,\n err\n );\n }\n },\n [setActiveAnnotationId, play]\n );\n\n // Handle annotation boundary updates\n const handleAnnotationUpdate = useCallback(\n (updatedAnnotations: AnnotationData[]) => {\n setAnnotations(updatedAnnotations);\n onAnnotationUpdate?.(updatedAnnotations);\n },\n [setAnnotations, onAnnotationUpdate]\n );\n\n // Drag handlers for annotation boundary editing\n const { onDragStart, onDragMove, onDragEnd } = useAnnotationDragHandlers({\n annotations,\n onAnnotationsChange: handleAnnotationUpdate,\n samplesPerPixel,\n duration,\n linkEndpoints: linkEndpointsProp,\n });\n\n // Mouse handlers for click-to-seek\n const mouseDownTimeRef = useRef<number>(0);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const clickTime = (x * samplesPerPixel) / sampleRate;\n\n mouseDownTimeRef.current = clickTime;\n setIsSelecting(true);\n setSelectionStart(clickTime);\n setSelectionEnd(clickTime);\n },\n [samplesPerPixel, sampleRate]\n );\n\n const handleMouseMove = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!isSelecting) return;\n\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const moveTime = (x * samplesPerPixel) / sampleRate;\n\n setSelectionEnd(moveTime);\n },\n [isSelecting, samplesPerPixel, sampleRate]\n );\n\n const handleMouseUp = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!isSelecting) return;\n\n setIsSelecting(false);\n\n // During playback, use the time captured at mouseDown — auto-scroll shifts the\n // overlay between mouseDown and mouseUp, so recomputing from getBoundingClientRect()\n // would produce a wrong (shifted) position.\n if (isPlaying && playoutRef.current) {\n const clickTime = Math.max(0, mouseDownTimeRef.current);\n seekTo(clickTime);\n setSelectionStart(clickTime);\n setSelectionEnd(clickTime);\n playoutRef.current.stop();\n play(clickTime);\n return;\n }\n\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const endTime = (x * samplesPerPixel) / sampleRate;\n\n const start = Math.min(selectionStart, endTime);\n const end = Math.max(selectionStart, endTime);\n\n // If it's just a click (not a drag), seek to that position\n if (Math.abs(end - start) < 0.1) {\n seekTo(start);\n setSelectionStart(start);\n setSelectionEnd(start);\n } else {\n // It was a drag - finalize the selection\n setSelectionStart(start);\n setSelectionEnd(end);\n }\n },\n [isSelecting, selectionStart, samplesPerPixel, sampleRate, seekTo, isPlaying, playoutRef, play]\n );\n\n // Show loading if peaks not ready\n if (peaksDataArray.length === 0) {\n return <div className={className}>Loading waveform...</div>;\n }\n\n return (\n <DevicePixelRatioProvider>\n <PlaylistInfoContext.Provider\n value={{\n samplesPerPixel,\n sampleRate,\n zoomLevels: [samplesPerPixel],\n waveHeight,\n timeScaleHeight,\n duration: duration * 1000,\n controls,\n barWidth,\n barGap,\n roundedBars,\n }}\n >\n <Playlist\n theme={theme}\n backgroundColor={waveformColorToCss(theme.waveOutlineColor)}\n timescaleBackgroundColor={theme.timescaleBackgroundColor}\n timescaleWidth={tracksFullWidth}\n tracksWidth={tracksFullWidth}\n controlsWidth={controls.show ? controls.width : 0}\n onTracksMouseDown={handleMouseDown}\n onTracksMouseMove={handleMouseMove}\n onTracksMouseUp={handleMouseUp}\n scrollContainerRef={handleScrollContainerRef}\n isSelecting={isSelecting}\n timescale={timeScaleHeight > 0 ? <SmartScale /> : undefined}\n >\n <>\n {peaksDataArray.map((trackClipPeaks, trackIndex) => {\n // For MediaElement, we have a single track with a single clip\n const maxChannels =\n trackClipPeaks.length > 0\n ? Math.max(...trackClipPeaks.map((clip) => clip.peaks.data.length))\n : 1;\n\n return (\n <TrackComponent\n key={trackIndex}\n numChannels={maxChannels}\n backgroundColor={waveformColorToCss(theme.waveOutlineColor)}\n offset={0}\n width={tracksFullWidth}\n hasClipHeaders={false}\n trackId={`media-element-track-${trackIndex}`}\n isSelected={true}\n >\n {trackClipPeaks.map((clip, clipIndex) => {\n const peaksData = clip.peaks;\n const width = peaksData.length;\n\n return (\n <Clip\n key={`${trackIndex}-${clipIndex}`}\n clipId={clip.clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n trackName={clip.trackName}\n startSample={clip.startSample}\n durationSamples={clip.durationSamples}\n samplesPerPixel={samplesPerPixel}\n showHeader={false}\n disableHeaderDrag={true}\n isSelected={true}\n trackId={`media-element-track-${trackIndex}`}\n >\n {peaksData.data.map((channelPeaks: Peaks, channelIndex: number) => (\n <ChannelWithMediaElementProgress\n key={`${trackIndex}-${clipIndex}-${channelIndex}`}\n index={channelIndex}\n data={channelPeaks}\n bits={peaksData.bits}\n length={width}\n clipStartSample={clip.startSample}\n clipDurationSamples={clip.durationSamples}\n />\n ))}\n {showFades && fadeIn && fadeIn.duration > 0 && (\n <FadeOverlay\n left={0}\n width={Math.floor((fadeIn.duration * sampleRate) / samplesPerPixel)}\n type=\"fadeIn\"\n curveType={fadeIn.type}\n />\n )}\n {showFades && fadeOut && fadeOut.duration > 0 && (\n <FadeOverlay\n left={\n width - Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)\n }\n width={Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)}\n type=\"fadeOut\"\n curveType={fadeOut.type}\n />\n )}\n </Clip>\n );\n })}\n </TrackComponent>\n );\n })}\n {annotations.length > 0 && annotationIntegration && (\n <DragDropProvider\n onDragStart={onDragStart}\n onDragMove={onDragMove}\n onDragEnd={onDragEnd}\n modifiers={editable ? [RestrictToHorizontalAxis] : []}\n plugins={noDropAnimationPlugins}\n >\n <annotationIntegration.AnnotationBoxesWrapper height={30} width={tracksFullWidth}>\n {annotations.map((annotation, index) => {\n const startPosition = (annotation.start * sampleRate) / samplesPerPixel;\n const endPosition = (annotation.end * sampleRate) / samplesPerPixel;\n const label = getAnnotationBoxLabel\n ? getAnnotationBoxLabel(annotation, index)\n : annotation.id;\n return (\n <annotationIntegration.AnnotationBox\n key={annotation.id}\n annotationId={annotation.id}\n annotationIndex={index}\n startPosition={startPosition}\n endPosition={endPosition}\n label={label}\n color=\"#ff9800\"\n isActive={annotation.id === activeAnnotationId}\n onClick={() => handleAnnotationClick(annotation)}\n editable={editable}\n />\n );\n })}\n </annotationIntegration.AnnotationBoxesWrapper>\n </DragDropProvider>\n )}\n {selectionStart !== selectionEnd && (\n <Selection\n startPosition={\n (Math.min(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n }\n endPosition={\n (Math.max(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n }\n color={theme.selectionColor}\n />\n )}\n {renderPlayhead ? (\n <CustomMediaElementPlayhead\n renderPlayhead={renderPlayhead}\n color={theme.playheadColor}\n samplesPerPixel={samplesPerPixel}\n sampleRate={sampleRate}\n />\n ) : (\n <AnimatedMediaElementPlayhead color={theme.playheadColor} />\n )}\n </>\n </Playlist>\n </PlaylistInfoContext.Provider>\n </DevicePixelRatioProvider>\n );\n};\n","import { Feedback } from '@dnd-kit/dom';\nimport type { Plugins } from '@dnd-kit/abstract';\n\n/**\n * DragDropProvider plugins customizer that disables the Feedback plugin's drop animation.\n *\n * Without this, the Feedback plugin animates the dragged element back to its original\n * position on drop, causing a visual snap-back before React re-renders at the new position.\n *\n * Usage:\n * ```tsx\n * <DragDropProvider plugins={noDropAnimationPlugins} ...>\n * ```\n */\nexport const noDropAnimationPlugins = (defaults: Plugins): Plugins => {\n let feedbackFound = false;\n const result = defaults.map((p) => {\n if (p === Feedback) {\n feedbackFound = true;\n return Feedback.configure({ dropAnimation: null });\n }\n return p;\n });\n if (!feedbackFound) {\n console.warn(\n '[waveform-playlist] noDropAnimationPlugins: Feedback plugin not found in defaults — ' +\n 'drop animation may not be disabled. Check @dnd-kit/dom version.'\n );\n }\n return result;\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { useMediaElementAnimation, useMediaElementData } from '../MediaElementPlaylistContext';\n\nconst PlayheadLine = styled.div<{ $color: string; $width: number }>`\n position: absolute;\n top: 0;\n left: 0;\n width: ${(props) => props.$width}px;\n background: ${(props) => props.$color};\n height: 100%;\n z-index: 100;\n pointer-events: none;\n will-change: transform;\n`;\n\ninterface AnimatedMediaElementPlayheadProps {\n color?: string;\n}\n\n/**\n * Animated playhead for MediaElementPlaylistProvider.\n * Uses the MediaElement context for time tracking instead of Tone.js audio context.\n * Updates position via direct DOM manipulation for smooth 60fps animation.\n */\nexport const AnimatedMediaElementPlayhead: React.FC<AnimatedMediaElementPlayheadProps> = ({\n color = '#ff0000',\n}) => {\n const playheadRef = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n\n const { isPlaying, currentTimeRef } = useMediaElementAnimation();\n const { samplesPerPixel, sampleRate, progressBarWidth } = useMediaElementData();\n\n useEffect(() => {\n const updatePosition = () => {\n if (playheadRef.current) {\n // Get current time from the ref (updated by animation loop in context)\n const time = currentTimeRef.current ?? 0;\n const position = (time * sampleRate) / samplesPerPixel;\n playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;\n }\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updatePosition);\n }\n };\n\n if (isPlaying) {\n // Start animation loop\n animationFrameRef.current = requestAnimationFrame(updatePosition);\n } else {\n // When stopped, update once to show final position\n updatePosition();\n }\n\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n };\n }, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef]);\n\n // Also update position when not playing (for seeks, stops, etc.)\n useEffect(() => {\n if (!isPlaying && playheadRef.current) {\n const time = currentTimeRef.current ?? 0;\n const position = (time * sampleRate) / samplesPerPixel;\n playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;\n }\n });\n\n return <PlayheadLine ref={playheadRef} $color={color} $width={progressBarWidth} data-playhead />;\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport {\n SmartChannel,\n type SmartChannelProps,\n useTheme,\n usePlaylistInfo,\n type WaveformPlaylistTheme,\n waveformColorToCss,\n} from '@waveform-playlist/ui-components';\nimport { useMediaElementAnimation, useMediaElementData } from '../MediaElementPlaylistContext';\n\nconst ChannelWrapper = styled.div`\n position: relative;\n`;\n\ninterface BackgroundProps {\n readonly $color: string;\n readonly $height: number;\n readonly $top: number;\n readonly $width: number;\n}\n\nconst Background = styled.div<BackgroundProps>`\n position: absolute;\n top: ${(props) => props.$top}px;\n left: 0;\n width: ${(props) => props.$width}px;\n height: ${(props) => props.$height}px;\n background: ${(props) => props.$color};\n z-index: 0;\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\ninterface ProgressOverlayProps {\n readonly $color: string;\n readonly $height: number;\n readonly $top: number;\n}\n\nconst ProgressOverlay = styled.div<ProgressOverlayProps>`\n position: absolute;\n top: ${(props) => props.$top}px;\n left: 0;\n height: ${(props) => props.$height}px;\n background: ${(props) => props.$color};\n pointer-events: none;\n z-index: 1;\n will-change: width;\n`;\n\nconst ChannelContainer = styled.div`\n position: relative;\n z-index: 2;\n`;\n\nexport interface ChannelWithMediaElementProgressProps\n extends Omit<SmartChannelProps, 'isSelected'> {\n /** Start sample of the clip containing this channel (for progress calculation) */\n clipStartSample: number;\n /** Duration in samples of the clip */\n clipDurationSamples: number;\n}\n\n/**\n * SmartChannel wrapper for MediaElementPlaylistProvider with animated progress overlay.\n * Uses MediaElement context for time tracking instead of Tone.js audio context.\n */\nexport const ChannelWithMediaElementProgress: React.FC<ChannelWithMediaElementProgressProps> = ({\n clipStartSample,\n clipDurationSamples,\n ...smartChannelProps\n}) => {\n const progressRef = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n const theme = useTheme() as WaveformPlaylistTheme;\n const { waveHeight } = usePlaylistInfo();\n\n const { isPlaying, currentTimeRef } = useMediaElementAnimation();\n const { samplesPerPixel, sampleRate } = useMediaElementData();\n\n const progressColor = theme?.waveProgressColor || 'rgba(0, 0, 0, 0.1)';\n\n useEffect(() => {\n const updateProgress = () => {\n if (progressRef.current) {\n // Get current time from the ref\n const currentTime = currentTimeRef.current ?? 0;\n\n // Convert current time to samples\n const currentSample = currentTime * sampleRate;\n\n // Calculate clip bounds in samples\n const clipEndSample = clipStartSample + clipDurationSamples;\n\n // Calculate how much of this clip has been played\n let progressWidth = 0;\n\n if (currentSample <= clipStartSample) {\n // Playhead is before this clip - no progress\n progressWidth = 0;\n } else if (currentSample >= clipEndSample) {\n // Playhead is past this clip - full progress\n progressWidth = smartChannelProps.length;\n } else {\n // Playhead is within this clip - partial progress\n const playedSamples = currentSample - clipStartSample;\n progressWidth = Math.floor(playedSamples / samplesPerPixel);\n }\n\n progressRef.current.style.width = `${progressWidth}px`;\n }\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updateProgress);\n }\n };\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updateProgress);\n } else {\n // When stopped, update once to show final position\n updateProgress();\n }\n\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n };\n }, [\n isPlaying,\n sampleRate,\n samplesPerPixel,\n clipStartSample,\n clipDurationSamples,\n smartChannelProps.length,\n currentTimeRef,\n ]);\n\n // Also update when not playing (for seeks, stops, etc.)\n useEffect(() => {\n if (!isPlaying && progressRef.current) {\n const currentTime = currentTimeRef.current ?? 0;\n const currentSample = currentTime * sampleRate;\n const clipEndSample = clipStartSample + clipDurationSamples;\n\n let progressWidth = 0;\n if (currentSample <= clipStartSample) {\n progressWidth = 0;\n } else if (currentSample >= clipEndSample) {\n progressWidth = smartChannelProps.length;\n } else {\n const playedSamples = currentSample - clipStartSample;\n progressWidth = Math.floor(playedSamples / samplesPerPixel);\n }\n\n progressRef.current.style.width = `${progressWidth}px`;\n }\n });\n\n // Get the draw mode from theme (defaults to 'inverted')\n const drawMode = theme?.waveformDrawMode || 'inverted';\n\n let backgroundColor;\n if (drawMode === 'inverted') {\n // For MediaElement, always treat as selected (single track)\n backgroundColor = theme?.selectedWaveFillColor || theme?.waveFillColor || 'white';\n } else {\n backgroundColor = theme?.selectedWaveOutlineColor || theme?.waveOutlineColor || 'grey';\n }\n\n const backgroundCss = waveformColorToCss(backgroundColor);\n\n return (\n <ChannelWrapper>\n <Background\n $color={backgroundCss}\n $height={waveHeight}\n $top={smartChannelProps.index * waveHeight}\n $width={smartChannelProps.length}\n />\n <ProgressOverlay\n ref={progressRef}\n $color={progressColor}\n $height={waveHeight}\n $top={smartChannelProps.index * waveHeight}\n />\n <ChannelContainer>\n <SmartChannel {...smartChannelProps} isSelected={true} transparentBackground />\n </ChannelContainer>\n </ChannelWrapper>\n );\n};\n","import React, { useCallback } from 'react';\nimport type {\n AnnotationData,\n AnnotationAction,\n AnnotationActionOptions,\n RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\nimport { useAnnotationIntegration } from '../AnnotationIntegrationContext';\nimport { useMediaElementState, useMediaElementControls } from '../MediaElementPlaylistContext';\nimport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport interface MediaElementAnnotationListProps {\n /** Height in pixels for the annotation text list */\n height?: number;\n /**\n * Custom render function for annotation items in the text list.\n * When provided, completely replaces the default annotation item rendering.\n */\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n /**\n * Callback when annotations are updated (e.g., text edited).\n * Called with the full updated annotations array.\n */\n onAnnotationUpdate?: OnAnnotationUpdateFn;\n /** Whether annotation text can be edited. Defaults to false. */\n editable?: boolean;\n /**\n * Action controls to show on each annotation item (e.g., delete, split).\n * Only rendered when `editable` is true.\n */\n controls?: AnnotationAction[];\n /**\n * Override annotation list config. Falls back to context values\n * `{ linkEndpoints: false, continuousPlay }` if not provided.\n */\n annotationListConfig?: AnnotationActionOptions;\n /** Where to position the active annotation when auto-scrolling. Defaults to 'center'. */\n scrollActivePosition?: ScrollLogicalPosition;\n /** Which scrollable containers to scroll: 'nearest' or 'all'. Defaults to 'nearest'. */\n scrollActiveContainer?: 'nearest' | 'all';\n}\n\n/**\n * Standalone annotation text list component for MediaElementPlaylistProvider.\n *\n * Requires @waveform-playlist/annotations with AnnotationProvider.\n * Throws if used without `<AnnotationProvider>` wrapping the component tree.\n */\nexport const MediaElementAnnotationList: React.FC<MediaElementAnnotationListProps> = ({\n height,\n renderAnnotationItem,\n onAnnotationUpdate,\n editable = false,\n controls,\n annotationListConfig,\n scrollActivePosition = 'center',\n scrollActiveContainer = 'nearest',\n}) => {\n const { annotations, activeAnnotationId, continuousPlay } = useMediaElementState();\n const integration = useAnnotationIntegration();\n const { setAnnotations } = useMediaElementControls();\n\n const resolvedConfig = annotationListConfig ?? { linkEndpoints: false, continuousPlay };\n\n const handleAnnotationUpdate = useCallback(\n (updatedAnnotations: AnnotationData[]) => {\n setAnnotations(updatedAnnotations);\n onAnnotationUpdate?.(updatedAnnotations);\n },\n [setAnnotations, onAnnotationUpdate]\n );\n\n const { AnnotationText } = integration;\n\n return (\n <AnnotationText\n annotations={annotations}\n activeAnnotationId={activeAnnotationId ?? undefined}\n shouldScrollToActive={true}\n scrollActivePosition={scrollActivePosition}\n scrollActiveContainer={scrollActiveContainer}\n editable={editable}\n controls={editable ? controls : undefined}\n annotationListConfig={resolvedConfig}\n height={height}\n onAnnotationUpdate={handleAnnotationUpdate}\n renderAnnotationItem={renderAnnotationItem}\n />\n );\n};\n","import React from 'react';\nimport type { RenderAnnotationItemProps } from '@waveform-playlist/core';\nimport type { RenderPlayheadFunction } from '@waveform-playlist/ui-components';\nimport { useMediaElementState } from '../MediaElementPlaylistContext';\nimport type { GetAnnotationBoxLabelFn, OnAnnotationUpdateFn } from '../types/annotations';\nimport { MediaElementPlaylist } from './MediaElementPlaylist';\nimport { MediaElementAnnotationList } from './MediaElementAnnotationList';\n\n// Re-export annotation types for convenience\nexport type { GetAnnotationBoxLabelFn, OnAnnotationUpdateFn } from '../types/annotations';\n\nexport interface MediaElementWaveformProps {\n /** Height in pixels for the annotation text list */\n annotationTextHeight?: number;\n /** Custom function to generate the label shown on annotation boxes */\n getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n /**\n * Custom render function for annotation items in the text list.\n * When provided, completely replaces the default annotation item rendering.\n * Use this to customize the appearance of each annotation (e.g., add furigana).\n */\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n /** Whether annotation boundaries can be edited by dragging. Defaults to false. */\n editable?: boolean;\n /** Whether dragging one annotation boundary also moves the adjacent annotation's boundary. Defaults to false. */\n linkEndpoints?: boolean;\n /**\n * Callback when annotations are updated (e.g., boundaries dragged).\n * Called with the full updated annotations array.\n */\n onAnnotationUpdate?: OnAnnotationUpdateFn;\n /** Where to position the active annotation when auto-scrolling: 'center', 'start', 'end', or 'nearest'. Defaults to 'center'. */\n scrollActivePosition?: ScrollLogicalPosition;\n /** Which scrollable containers to scroll: 'nearest' (only the annotation list) or 'all' (including viewport). Defaults to 'nearest'. */\n scrollActiveContainer?: 'nearest' | 'all';\n /** Custom playhead render function. Receives position, color, and animation refs for smooth 60fps animation. */\n renderPlayhead?: RenderPlayheadFunction;\n /** Show fade in/out overlays on the waveform. Defaults to false. */\n showFades?: boolean;\n className?: string;\n}\n\n/**\n * Simplified Waveform component for MediaElementPlaylistProvider\n *\n * This is a stripped-down version of Waveform that works with the\n * MediaElement context. It supports:\n * - Single track visualization\n * - Click to seek\n * - Annotation display and click-to-play\n * - Playhead animation\n *\n * For multi-track editing, use the full Waveform with WaveformPlaylistProvider.\n */\nexport const MediaElementWaveform: React.FC<MediaElementWaveformProps> = ({\n annotationTextHeight,\n getAnnotationBoxLabel,\n renderAnnotationItem,\n editable = false,\n linkEndpoints = false,\n onAnnotationUpdate,\n scrollActivePosition = 'center',\n scrollActiveContainer = 'nearest',\n renderPlayhead,\n showFades = false,\n className,\n}) => {\n const { annotations } = useMediaElementState();\n\n return (\n <>\n <MediaElementPlaylist\n getAnnotationBoxLabel={getAnnotationBoxLabel}\n editable={editable}\n linkEndpoints={linkEndpoints}\n onAnnotationUpdate={onAnnotationUpdate}\n renderPlayhead={renderPlayhead}\n showFades={showFades}\n className={className}\n />\n {annotations.length > 0 && (\n <MediaElementAnnotationList\n height={annotationTextHeight}\n renderAnnotationItem={renderAnnotationItem}\n onAnnotationUpdate={onAnnotationUpdate}\n editable={editable}\n annotationListConfig={{ linkEndpoints, continuousPlay: false }}\n scrollActivePosition={scrollActivePosition}\n scrollActiveContainer={scrollActiveContainer}\n />\n )}\n </>\n );\n};\n","import React from 'react';\nimport type { KeyboardShortcut } from '../hooks/useKeyboardShortcuts';\nimport { usePlaybackShortcuts } from '../hooks/usePlaybackShortcuts';\nimport { useClipSplitting } from '../hooks/useClipSplitting';\nimport { useAnnotationKeyboardControls } from '../hooks/useAnnotationKeyboardControls';\nimport { usePlaylistData, usePlaylistState, usePlaylistControls } from '../WaveformPlaylistContext';\n\nexport interface KeyboardShortcutsProps {\n /** Enable default playback shortcuts (Space, Escape, 0). Defaults to false. */\n playback?: boolean;\n /** Enable clip splitting shortcut ('s' key). Defaults to false. */\n clipSplitting?: boolean;\n /** Enable annotation keyboard controls (arrow nav, boundary editing). Defaults to false. */\n annotations?: boolean;\n /** Enable undo/redo shortcuts (Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z). Defaults to false. */\n undo?: boolean;\n /** Additional shortcuts appended to the defaults. */\n additionalShortcuts?: KeyboardShortcut[];\n}\n\n/**\n * Self-closing component that sets up keyboard shortcuts for the playlist.\n * Must be rendered inside a WaveformPlaylistProvider.\n *\n * @example\n * ```tsx\n * <WaveformPlaylistProvider tracks={tracks} {...}>\n * <KeyboardShortcuts playback clipSplitting />\n * <Waveform />\n * </WaveformPlaylistProvider>\n * ```\n */\nexport const KeyboardShortcuts: React.FC<KeyboardShortcutsProps> = ({\n playback = false,\n clipSplitting = false,\n annotations = false,\n undo: undoEnabled = false,\n additionalShortcuts = [],\n}) => {\n // Clip splitting setup\n const { tracks, samplesPerPixel, playoutRef, duration } = usePlaylistData();\n const {\n annotations: annotationList,\n linkEndpoints,\n activeAnnotationId,\n continuousPlay,\n } = usePlaylistState();\n const { setAnnotations, setActiveAnnotationId, scrollContainerRef, play, undo, redo } =\n usePlaylistControls();\n\n const { splitClipAtPlayhead } = useClipSplitting({\n tracks,\n samplesPerPixel,\n engineRef: playoutRef,\n });\n\n // Build additional shortcuts from enabled features\n const allAdditional: KeyboardShortcut[] = [];\n\n if (clipSplitting) {\n allAdditional.push({\n key: 's',\n action: splitClipAtPlayhead,\n description: 'Split clip at playhead',\n preventDefault: true,\n });\n }\n\n if (undoEnabled) {\n allAdditional.push(\n { key: 'z', ctrlKey: true, shiftKey: false, action: undo, description: 'Undo' },\n { key: 'z', metaKey: true, shiftKey: false, action: undo, description: 'Undo' },\n { key: 'z', ctrlKey: true, shiftKey: true, action: redo, description: 'Redo' },\n { key: 'z', metaKey: true, shiftKey: true, action: redo, description: 'Redo' }\n );\n }\n\n if (additionalShortcuts.length > 0) {\n allAdditional.push(...additionalShortcuts);\n }\n\n // Playback shortcuts (or just additional if playback is off)\n usePlaybackShortcuts({\n enabled: playback || allAdditional.length > 0,\n ...(playback ? { additionalShortcuts: allAdditional } : { shortcuts: allAdditional }),\n });\n\n // Annotation keyboard controls\n useAnnotationKeyboardControls({\n annotations: annotationList,\n activeAnnotationId,\n onAnnotationsChange: setAnnotations,\n onActiveAnnotationChange: setActiveAnnotationId,\n duration,\n linkEndpoints,\n continuousPlay,\n scrollContainerRef,\n onPlay: play,\n enabled: annotations && annotationList.length > 0,\n });\n\n return null;\n};\n","import React, { useEffect, useMemo } from 'react';\nimport { DragDropProvider } from '@dnd-kit/react';\nimport { RestrictToHorizontalAxis } from '@dnd-kit/abstract/modifiers';\nimport type { Modifiers } from '@dnd-kit/abstract';\nimport {\n samplesToTicks,\n ticksToSamples,\n snapToGrid,\n ticksPerBeat,\n ticksPerBar,\n} from '@waveform-playlist/core';\nimport { useBeatsAndBars, getScaleInfo } from '@waveform-playlist/ui-components';\n\nimport { usePlaylistData, usePlaylistControls } from '../WaveformPlaylistContext';\nimport { useClipDragHandlers } from '../hooks/useClipDragHandlers';\nimport { useDragSensors } from '../hooks/useDragSensors';\nimport { ClipCollisionModifier } from '../modifiers/ClipCollisionModifier';\nimport { SnapToGridModifier } from '../modifiers/SnapToGridModifier';\nimport { noDropAnimationPlugins } from '../plugins/noDropAnimationPlugins';\nimport { ClipInteractionContextProvider } from '../contexts/ClipInteractionContext';\n\n// Stable noop to avoid creating a new function reference on every render\nconst NOOP_TRACKS_CHANGE = () => {};\n\nexport interface ClipInteractionProviderProps {\n /** Enable snap-to-grid for clip moves and boundary trims. When true,\n * auto-detects beats snapping from BeatsAndBarsProvider context\n * (if present with scaleMode=\"beats\" and snapTo!=\"off\"), otherwise\n * falls back to timescale-based snapping. Default: false. */\n snap?: boolean;\n touchOptimized?: boolean;\n children: React.ReactNode;\n}\n\nexport const ClipInteractionProvider: React.FC<ClipInteractionProviderProps> = ({\n snap = false,\n touchOptimized = false,\n children,\n}) => {\n const { tracks, samplesPerPixel, sampleRate, playoutRef, isDraggingRef, onTracksChange } =\n usePlaylistData();\n const { setSelectedTrackId } = usePlaylistControls();\n const beatsAndBars = useBeatsAndBars();\n\n // Derive snap mode from context: beats if provider is in beats mode with snap enabled,\n // timescale otherwise, off when snap prop is false.\n const useBeatsSnap =\n snap &&\n beatsAndBars != null &&\n beatsAndBars.scaleMode === 'beats' &&\n beatsAndBars.snapTo !== 'off';\n const useTimescaleSnap = snap && !useBeatsSnap;\n\n // Warn once if onTracksChange is missing — drag/trim edits will be lost\n useEffect(() => {\n if (onTracksChange == null) {\n console.warn(\n '[waveform-playlist] ClipInteractionProvider: onTracksChange is not set on ' +\n 'WaveformPlaylistProvider. Drag and trim edits will not be persisted.'\n );\n }\n }, [onTracksChange]);\n\n // Build snapSamplePosition for boundary trim snapping\n const snapSamplePosition = useMemo(() => {\n if (useBeatsSnap && beatsAndBars) {\n const { bpm, timeSignature, snapTo } = beatsAndBars;\n const gridTicks = snapTo === 'bar' ? ticksPerBar(timeSignature) : ticksPerBeat(timeSignature);\n return (samplePos: number) => {\n const ticks = samplesToTicks(samplePos, bpm, sampleRate);\n const snapped = snapToGrid(ticks, gridTicks);\n return ticksToSamples(snapped, bpm, sampleRate);\n };\n }\n if (useTimescaleSnap) {\n const gridSamples = Math.round((getScaleInfo(samplesPerPixel).smallStep / 1000) * sampleRate);\n return (samplePos: number) => Math.round(samplePos / gridSamples) * gridSamples;\n }\n return undefined;\n }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, sampleRate, samplesPerPixel]);\n\n // Sensors\n const sensors = useDragSensors({ touchOptimized });\n\n // Drag handlers\n const {\n onDragStart: handleDragStart,\n onDragMove,\n onDragEnd,\n } = useClipDragHandlers({\n tracks,\n onTracksChange: onTracksChange ?? NOOP_TRACKS_CHANGE,\n samplesPerPixel,\n engineRef: playoutRef,\n isDraggingRef,\n snapSamplePosition,\n });\n\n // Wrap onDragStart to auto-select track\n const onDragStart = React.useCallback(\n (event: Parameters<typeof handleDragStart>[0]) => {\n const trackIndex = event.operation?.source?.data?.trackIndex as number | undefined;\n if (trackIndex !== undefined && tracks[trackIndex]) {\n setSelectedTrackId(tracks[trackIndex].id);\n }\n handleDragStart(event);\n },\n [handleDragStart, tracks, setSelectedTrackId]\n );\n\n // Build modifiers array\n const modifiers = useMemo(() => {\n const mods: Modifiers = [RestrictToHorizontalAxis];\n\n if (useBeatsSnap && beatsAndBars) {\n mods.push(\n SnapToGridModifier.configure({\n mode: 'beats',\n snapTo: beatsAndBars.snapTo,\n bpm: beatsAndBars.bpm,\n timeSignature: beatsAndBars.timeSignature,\n samplesPerPixel,\n sampleRate,\n })\n );\n } else if (useTimescaleSnap) {\n mods.push(\n SnapToGridModifier.configure({\n mode: 'timescale',\n gridSamples: Math.round((getScaleInfo(samplesPerPixel).smallStep / 1000) * sampleRate),\n samplesPerPixel,\n })\n );\n }\n\n mods.push(ClipCollisionModifier.configure({ tracks, samplesPerPixel }));\n return mods;\n }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, tracks, samplesPerPixel, sampleRate]);\n\n return (\n <ClipInteractionContextProvider value={true}>\n <DragDropProvider\n sensors={sensors}\n onDragStart={onDragStart}\n onDragMove={onDragMove}\n onDragEnd={onDragEnd}\n modifiers={modifiers}\n plugins={noDropAnimationPlugins}\n >\n {children}\n </DragDropProvider>\n </ClipInteractionContextProvider>\n );\n};\n","import {\n Modifier,\n configurator,\n type DragDropManager,\n type DragOperation,\n} from '@dnd-kit/abstract';\nimport { constrainClipDrag } from '@waveform-playlist/engine';\nimport type { ClipTrack, AudioClip } from '@waveform-playlist/core';\n\ninterface ClipCollisionOptions {\n tracks: ClipTrack[];\n samplesPerPixel: number;\n}\n\n/**\n * Modifier that constrains clip drag movement to prevent overlaps.\n *\n * For clip move operations: constrains horizontal transform to valid positions\n * using the engine's collision detection.\n *\n * For boundary trim operations: returns zero transform because visual feedback\n * comes from React state updates resizing the clip, not from CSS translate.\n */\nexport class ClipCollisionModifier extends Modifier<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n DragDropManager<any, any>,\n ClipCollisionOptions\n> {\n apply(operation: DragOperation): { x: number; y: number } {\n const { transform, source } = operation;\n\n if (!source?.data || !this.options) return transform;\n\n const { boundary, trackIndex, clipIndex } = source.data as {\n boundary?: 'left' | 'right';\n trackIndex: number;\n clipIndex: number;\n };\n\n // Boundary trims: zero transform (visual feedback from React state updates)\n if (boundary) return { x: 0, y: 0 };\n\n const { tracks, samplesPerPixel } = this.options;\n const track = tracks[trackIndex];\n if (!track) return transform;\n\n const clip = track.clips[clipIndex];\n if (!clip) return transform;\n\n // Convert pixel delta to samples, constrain, convert back\n const deltaSamples = transform.x * samplesPerPixel;\n const sortedClips = [...track.clips].sort((a, b) => a.startSample - b.startSample);\n const sortedIndex = sortedClips.findIndex((c: AudioClip) => c.id === clip.id);\n const constrainedDelta = constrainClipDrag(clip, deltaSamples, sortedClips, sortedIndex);\n\n return { x: constrainedDelta / samplesPerPixel, y: 0 };\n }\n\n static configure = configurator(ClipCollisionModifier);\n}\n","import {\n Modifier,\n configurator,\n type DragDropManager,\n type DragOperation,\n} from '@dnd-kit/abstract';\nimport {\n ticksPerBeat,\n ticksPerBar,\n ticksToSamples,\n samplesToTicks,\n snapToGrid,\n} from '@waveform-playlist/core';\nimport type { SnapTo } from '@waveform-playlist/ui-components';\n\ninterface SnapToGridBeatsOptions {\n mode: 'beats';\n snapTo: SnapTo;\n bpm: number;\n timeSignature: [number, number];\n samplesPerPixel: number;\n sampleRate: number;\n}\n\ninterface SnapToGridTimescaleOptions {\n mode: 'timescale';\n gridSamples: number;\n samplesPerPixel: number;\n}\n\ntype SnapToGridOptions = SnapToGridBeatsOptions | SnapToGridTimescaleOptions;\n\n/**\n * dnd-kit modifier that quantizes clip drag movement to a grid.\n *\n * Two modes:\n * - \"beats\": Snaps to beat/bar grid using PPQN tick space for exact musical timing.\n * - \"timescale\": Snaps to a sample-based grid derived from timescale markers.\n *\n * Designed to compose with ClipCollisionModifier — snap first,\n * then collision constrains the snapped position.\n */\nexport class SnapToGridModifier extends Modifier<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n DragDropManager<any, any>,\n SnapToGridOptions\n> {\n apply(operation: DragOperation): { x: number; y: number } {\n const { transform, source } = operation;\n\n if (!this.options || !source?.data) return transform;\n\n // Don't snap boundary trims — snapping for trims is handled in the drag handler\n const { boundary, startSample } = source.data as {\n boundary?: 'left' | 'right';\n startSample?: number;\n };\n if (boundary) return transform;\n\n const { samplesPerPixel } = this.options;\n\n // Snap the absolute position (not just the delta) so clips land exactly\n // on grid lines even if they started off-grid.\n if (this.options.mode === 'timescale') {\n const { gridSamples } = this.options;\n if (startSample !== undefined) {\n const proposedPosition = startSample + transform.x * samplesPerPixel;\n const snappedPosition = Math.round(proposedPosition / gridSamples) * gridSamples;\n return { x: (snappedPosition - startSample) / samplesPerPixel, y: 0 };\n }\n // Fallback: snap delta (no startSample available)\n const deltaSamples = transform.x * samplesPerPixel;\n const snappedSamples = Math.round(deltaSamples / gridSamples) * gridSamples;\n return { x: snappedSamples / samplesPerPixel, y: 0 };\n }\n\n // Beats mode\n const { snapTo, bpm, timeSignature, sampleRate } = this.options;\n\n if (snapTo === 'off') return transform;\n\n const gridTicks = snapTo === 'bar' ? ticksPerBar(timeSignature) : ticksPerBeat(timeSignature);\n\n if (startSample !== undefined) {\n // Snap absolute position in tick space\n const proposedSamples = startSample + transform.x * samplesPerPixel;\n const proposedTicks = samplesToTicks(proposedSamples, bpm, sampleRate);\n const snappedTicks = snapToGrid(proposedTicks, gridTicks);\n const snappedSamples = ticksToSamples(snappedTicks, bpm, sampleRate);\n return { x: (snappedSamples - startSample) / samplesPerPixel, y: 0 };\n }\n\n // Fallback: snap delta (no startSample available)\n const deltaSamples = transform.x * samplesPerPixel;\n const deltaTicks = samplesToTicks(deltaSamples, bpm, sampleRate);\n const snappedTicks = snapToGrid(deltaTicks, gridTicks);\n const snappedSamples = ticksToSamples(snappedTicks, bpm, sampleRate);\n\n return { x: snappedSamples / samplesPerPixel, y: 0 };\n }\n\n static configure = configurator(SnapToGridModifier);\n}\n"],"mappings":"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","names":["import_react","import_engine","import_ui_components","WaveformData","bits","formatTime","formatTimeUtil","parseTimeUtil","import_react","import_react","import_react","import_react","import_react","import_react","import_react","React","trackIndex","import_react","React","import_react","import_react","import_engine","track","import_react","import_core","import_react","import_react","LINK_THRESHOLD","import_react","import_react","import_waveform_data","WaveformData","formatTime","_a","_b","_c","_d","sampleRate","volume","import_react","import_styled_components","INSTALL_HINT","import_ui_components","import_jsx_runtime","_a","import_react","import_ui_components","import_jsx_runtime","import_ui_components","import_jsx_runtime","import_react","import_ui_components","import_styled_components","import_jsx_runtime","BaseMasterVolumeControl","BaseTimeFormatSelect","styled","_a","BaseSelectionTimeInputs","BaseAutomaticScrollCheckbox","import_react","import_jsx_runtime","import_react","import_react","import_styled_components","import_ui_components","import_core","import_react","import_styled_components","import_jsx_runtime","styled","import_react","import_styled_components","import_core","import_ui_components","import_jsx_runtime","styled","computeClipPixelWidth","_a","_b","import_react","import_jsx_runtime","styled","_a","_b","_c","_d","_e","_f","_g","_h","_i","TrackComponent","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_ui_components","import_dom","import_react","import_styled_components","import_jsx_runtime","PlayheadLine","styled","import_react","import_styled_components","import_ui_components","import_jsx_runtime","ChannelWrapper","styled","Background","ProgressOverlay","ChannelContainer","_a","import_jsx_runtime","TrackComponent","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_modifiers","import_core","import_ui_components","import_engine","import_abstract","import_core","deltaSamples","snappedSamples","snappedTicks","import_jsx_runtime","React"]}
1
+ {"version":3,"sources":["../src/index.tsx","../src/WaveformPlaylistContext.tsx","../src/playout/resolvePlayoutAdapter.ts","../src/waveformDataLoader.ts","../src/soundFontSync.ts","../src/hooks/useTimeFormat.ts","../src/hooks/useZoomControls.ts","../src/hooks/useMasterVolume.ts","../src/hooks/useSelectionState.ts","../src/hooks/useLoopState.ts","../src/hooks/useSelectedTrack.ts","../src/hooks/useUndoState.ts","../src/hooks/useClipDragHandlers.ts","../src/utils/boundaryTrim.ts","../src/hooks/useAnnotationDragHandlers.ts","../src/hooks/useDragSensors.ts","../src/hooks/useClipSplitting.ts","../src/hooks/useKeyboardShortcuts.ts","../src/hooks/usePlaybackShortcuts.ts","../src/hooks/useAnnotationKeyboardControls.ts","../src/hooks/useAnimationFrameLoop.ts","../src/hooks/useWaveformDataCache.ts","../src/workers/peaksWorker.ts","../src/MediaElementPlaylistContext.tsx","../src/playout/resolveMediaElementPlayout.ts","../src/components/PlaybackControls.tsx","../src/components/ZoomControls.tsx","../src/components/ContextualControls.tsx","../src/AnnotationIntegrationContext.tsx","../src/components/AnnotationControls.tsx","../src/contexts/ClipInteractionContext.tsx","../src/components/PlaylistVisualization.tsx","../src/components/AnimatedPlayhead.tsx","../src/components/ChannelWithProgress.tsx","../src/SpectrogramIntegrationContext.tsx","../src/components/PlaylistAnnotationList.tsx","../src/components/Waveform.tsx","../src/components/MediaElementPlaylist.tsx","../src/plugins/noDropAnimationPlugins.ts","../src/components/AnimatedMediaElementPlayhead.tsx","../src/components/ChannelWithMediaElementProgress.tsx","../src/components/MediaElementAnnotationList.tsx","../src/components/MediaElementWaveform.tsx","../src/components/KeyboardShortcuts.tsx","../src/components/ClipInteractionProvider.tsx","../src/modifiers/ClipCollisionModifier.ts","../src/modifiers/SnapToGridModifier.ts"],"sourcesContent":["// Export types from playout\nexport type { EffectsFunction, TrackEffectsFunction } from '@waveform-playlist/playout';\n\n// Export new flexible/headless API\nexport {\n WaveformPlaylistProvider,\n usePlaybackAnimation,\n usePlaylistState,\n usePlaylistControls,\n usePlaylistData,\n usePlaylistDataOptional,\n} from './WaveformPlaylistContext';\nexport type { WaveformTrack, TrackState, FrameData } from './WaveformPlaylistContext';\n\n// Export MediaElement-based provider (single-track with playback rate control)\nexport {\n MediaElementPlaylistProvider,\n useMediaElementAnimation,\n useMediaElementState,\n useMediaElementControls,\n useMediaElementData,\n} from './MediaElementPlaylistContext';\nexport type {\n MediaElementTrackConfig,\n MediaElementAnimationContextValue,\n MediaElementStateContextValue,\n MediaElementControlsContextValue,\n MediaElementDataContextValue,\n} from './MediaElementPlaylistContext';\nexport {\n useClipDragHandlers,\n useAnnotationDragHandlers,\n useAnnotationKeyboardControls,\n useDragSensors,\n useClipSplitting,\n useKeyboardShortcuts,\n getShortcutLabel,\n usePlaybackShortcuts,\n useZoomControls,\n useTimeFormat,\n useMasterVolume,\n} from './hooks';\nexport type {\n UsePlaybackShortcutsOptions,\n UsePlaybackShortcutsReturn,\n ZoomControls,\n TimeFormatControls,\n MasterVolumeControls,\n} from './hooks';\n\nexport {\n PlayButton,\n PauseButton,\n StopButton,\n RewindButton,\n FastForwardButton,\n SkipBackwardButton,\n SkipForwardButton,\n LoopButton,\n SetLoopRegionButton,\n ClearAllButton,\n ZoomInButton,\n ZoomOutButton,\n MasterVolumeControl,\n TimeFormatSelect,\n AudioPosition,\n SelectionTimeInputs,\n AutomaticScrollCheckbox,\n ContinuousPlayCheckbox,\n LinkEndpointsCheckbox,\n EditableCheckbox,\n DownloadAnnotationsButton,\n Waveform,\n MediaElementWaveform,\n MediaElementPlaylist,\n MediaElementAnnotationList,\n PlaylistVisualization,\n PlaylistAnnotationList,\n KeyboardShortcuts,\n} from './components';\nexport type { KeyboardShortcutsProps } from './components/KeyboardShortcuts';\nexport type { ClearAllButtonProps } from './components/PlaybackControls';\nexport type { WaveformProps } from './components/Waveform';\nexport type { MediaElementWaveformProps } from './components/MediaElementWaveform';\nexport type { MediaElementPlaylistProps } from './components/MediaElementPlaylist';\nexport type { MediaElementAnnotationListProps } from './components/MediaElementAnnotationList';\nexport type { PlaylistVisualizationProps } from './components/PlaylistVisualization';\nexport type { PlaylistAnnotationListProps } from './components/PlaylistAnnotationList';\n\n// Re-export TimeFormat type from ui-components for convenience\nexport type { TimeFormat } from '@waveform-playlist/ui-components';\n\n// Re-export core types for convenience\nexport type { ClipTrack, AudioClip, Fade, AnnotationData } from '@waveform-playlist/core';\n\n// Export annotation callback types for Waveform components\nexport type { GetAnnotationBoxLabelFn, OnAnnotationUpdateFn } from './types/annotations';\n\n// Annotation integration context (for optional annotation support)\nexport {\n AnnotationIntegrationProvider,\n useAnnotationIntegration,\n} from './AnnotationIntegrationContext';\nexport type { AnnotationIntegration } from './AnnotationIntegrationContext';\n\n// Spectrogram integration context (for optional spectrogram support)\nexport {\n SpectrogramIntegrationProvider,\n useSpectrogramIntegration,\n} from './SpectrogramIntegrationContext';\nexport type {\n SpectrogramIntegration,\n SpectrogramCanvasRegistration,\n} from './SpectrogramIntegrationContext';\n\n// Export ClipInteractionProvider for encapsulated drag/trim/snap/collision setup\nexport { ClipInteractionProvider } from './components/ClipInteractionProvider';\nexport type { ClipInteractionProviderProps } from './components/ClipInteractionProvider';\nexport { useClipInteractionEnabled } from './contexts/ClipInteractionContext';\n\n// Export modifiers and plugins for drag-and-drop\nexport { ClipCollisionModifier } from './modifiers/ClipCollisionModifier';\nexport { SnapToGridModifier } from './modifiers/SnapToGridModifier';\nexport { noDropAnimationPlugins } from './plugins/noDropAnimationPlugins';\n\n// Export waveform-data.js utilities\nexport {\n loadWaveformData,\n waveformDataToPeaks,\n loadPeaksFromWaveformData,\n getWaveformDataMetadata,\n} from './waveformDataLoader';\n","import React, {\n createContext,\n useContext,\n useState,\n useEffect,\n useRef,\n useCallback,\n useMemo,\n type ReactNode,\n} from 'react';\nimport { ThemeProvider } from 'styled-components';\nimport type {\n EffectsFunction,\n TrackEffectsFunction,\n SoundFontCache,\n} from '@waveform-playlist/playout';\nimport { resolvePlayoutAdapter } from './playout/resolvePlayoutAdapter';\nimport { PlaylistEngine, type EngineState, type PlayoutAdapter } from '@waveform-playlist/engine';\nimport {\n type ClipTrack,\n type Fade,\n type AnnotationAction,\n type MidiNoteData,\n} from '@waveform-playlist/core';\nimport {\n type TimeFormat,\n type WaveformPlaylistTheme,\n defaultTheme,\n} from '@waveform-playlist/ui-components';\nimport { extractPeaksFromWaveformDataFull } from './waveformDataLoader';\nimport { syncSoundFontCacheToAdapter } from './soundFontSync';\nimport type WaveformData from 'waveform-data';\nimport type { PeakData } from '@waveform-playlist/core';\nimport type { AnnotationData } from '@waveform-playlist/core';\nimport {\n useTimeFormat,\n useZoomControls,\n useMasterVolume,\n useSelectionState,\n useLoopState,\n useSelectedTrack,\n useUndoState,\n useAnimationFrameLoop,\n useWaveformDataCache,\n} from './hooks';\n\n// Types\nexport interface ClipPeaks {\n clipId: string;\n trackName: string;\n peaks: PeakData;\n startSample: number;\n durationSamples: number;\n fadeIn?: Fade;\n fadeOut?: Fade;\n midiNotes?: MidiNoteData[];\n sampleRate?: number;\n offsetSamples?: number;\n}\n\nexport type TrackClipPeaks = ClipPeaks[];\n\n// Legacy WaveformTrack type - kept for reference but deprecated\n// @deprecated Use ClipTrack from @waveform-playlist/core instead\nexport interface WaveformTrack {\n src: string | AudioBuffer;\n name?: string;\n effects?: TrackEffectsFunction;\n}\n\nexport interface TrackState {\n name: string;\n muted: boolean;\n soloed: boolean;\n volume: number;\n pan: number;\n}\n\n// Split contexts for performance optimization\n// Animation context contains playback state and timing refs — no per-frame state updates\n\n/** Per-frame data passed to registered animation callbacks. */\nexport interface FrameData {\n /** Raw engine time (for state/logic — NOT for visual positioning). */\n readonly time: number;\n /** Visually-aligned time for DOM positioning: engine.getAudibleTime() while\n * playing (matches speaker output), raw time when resting. */\n readonly visualTime: number;\n readonly sampleRate: number;\n readonly samplesPerPixel: number;\n}\n\nexport interface PlaybackAnimationContextValue {\n isPlaying: boolean;\n currentTime: number;\n currentTimeRef: React.RefObject<number>;\n /**\n * Visually-aligned playback time (engine.getAudibleTime() while playing;\n * raw resting time otherwise). Kept current by the animation loop during playback\n * and by pause/seek/stop paths when stopped. Read from this for any visual\n * positioning that should match the audible output.\n */\n visualTimeRef: React.RefObject<number>;\n // Refs for direct time calculation in animated components (avoids timing drift)\n playbackStartTimeRef: React.RefObject<number>; // context.currentTime when playback started\n audioStartPositionRef: React.RefObject<number>; // Audio position when playback started\n /** Returns raw playback time from engine (auto-wraps at loop boundaries). */\n getPlaybackTime: () => number;\n /** Current time of the adapter's AudioContext, in seconds. */\n getAudioContextTime: () => number;\n /**\n * Returns the current adapter scheduler lookahead (Tone ~0.1s, native 0).\n * Use this for any audible-latency calculation that must match the playhead\n * (e.g., recording live-preview peak slicing).\n */\n getLookAhead: () => number;\n /**\n * Returns the adapter AudioContext's output latency in seconds.\n * Use this for audible-latency calculations (e.g., recording live-preview peak slicing).\n */\n getOutputLatency: () => number;\n /** Register a per-frame callback driven by the single animation loop. */\n registerFrameCallback: (id: string, cb: (data: FrameData) => void) => void;\n /** Unregister a per-frame callback. */\n unregisterFrameCallback: (id: string) => void;\n}\n\nexport interface PlaylistStateContextValue {\n continuousPlay: boolean;\n linkEndpoints: boolean;\n annotationsEditable: boolean;\n isAutomaticScroll: boolean;\n isLoopEnabled: boolean;\n annotations: AnnotationData[];\n activeAnnotationId: string | null;\n selectionStart: number;\n selectionEnd: number;\n selectedTrackId: string | null; // ID of currently selected track for editing operations\n // Loop region (separate from selection) - Audacity-style loop points\n loopStart: number;\n loopEnd: number;\n /** Whether playback continues past the end of loaded audio instead of\n * auto-stopping (implies the fillViewport layout) */\n indefinitePlayback: boolean;\n /** Whether the timeline visually fills the scroll container even when the\n * audio is shorter (layout only — no effect on playback) */\n fillViewport: boolean;\n /** Whether undo is available */\n canUndo: boolean;\n /** Whether redo is available */\n canRedo: boolean;\n}\n\nexport interface PlaylistControlsContextValue {\n // Playback controls\n play: (startTime?: number, playDuration?: number) => Promise<void>;\n pause: () => void;\n stop: () => void;\n seekTo: (time: number) => void;\n setCurrentTime: (time: number) => void;\n\n // Track controls\n setTrackMute: (trackIndex: number, muted: boolean) => void;\n setTrackSolo: (trackIndex: number, soloed: boolean) => void;\n setTrackVolume: (trackIndex: number, volume: number) => void;\n setTrackPan: (trackIndex: number, pan: number) => void;\n\n // Selection\n setSelection: (start: number, end: number) => void;\n setSelectedTrackId: (trackId: string | null) => void;\n\n // Time format\n setTimeFormat: (format: TimeFormat) => void;\n formatTime: (seconds: number) => string;\n\n // Zoom\n zoomIn: () => void;\n zoomOut: () => void;\n\n // Master volume\n setMasterVolume: (volume: number) => void;\n\n // Automatic scroll\n setAutomaticScroll: (enabled: boolean) => void;\n setScrollContainer: (element: HTMLDivElement | null) => void;\n scrollContainerRef: React.RefObject<HTMLDivElement | null>;\n\n // Annotation controls\n setContinuousPlay: (enabled: boolean) => void;\n setLinkEndpoints: (enabled: boolean) => void;\n setAnnotationsEditable: (enabled: boolean) => void;\n setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>>;\n setActiveAnnotationId: (id: string | null) => void;\n\n // Loop controls\n setLoopEnabled: (enabled: boolean) => void;\n setLoopRegion: (start: number, end: number) => void;\n setLoopRegionFromSelection: () => void;\n clearLoopRegion: () => void;\n\n // Undo/redo\n undo: () => void;\n redo: () => void;\n\n // Recording\n /** Mark a recording session active/inactive. While active: (1) the\n * end-of-audio auto-stop is suppressed so overdub playback runs past the\n * end of existing material, and (2) with an `armedTrackId`, that track's\n * existing content is transiently muted — punch-in recording replaces\n * whatever the take overlaps (#579), so the doomed material must not play\n * under the overdub; its previous mute state is restored when the session\n * ends (audio-only, the UI mute control is untouched). Wired automatically\n * from the Waveform / PlaylistVisualization `recordingState` prop; overdub\n * flows should also call it eagerly (before `play()`). */\n setRecordingActive: (active: boolean, armedTrackId?: string | null) => void;\n}\n\nexport interface PlaylistDataContextValue {\n duration: number;\n audioBuffers: AudioBuffer[];\n peaksDataArray: TrackClipPeaks[]; // Array of tracks, each containing array of clip peaks\n trackStates: TrackState[];\n tracks: ClipTrack[]; // Original tracks array with IDs\n sampleRate: number;\n waveHeight: number;\n timeScaleHeight: number;\n minimumPlaylistHeight: number;\n controls: { show: boolean; width: number };\n playoutRef: React.RefObject<PlaylistEngine | null>;\n samplesPerPixel: number;\n timeFormat: TimeFormat;\n masterVolume: number;\n canZoomIn: boolean;\n canZoomOut: boolean;\n barWidth: number;\n barGap: number;\n /** Draw bars with pill-shaped rounded caps (radius barWidth/2). */\n roundedBars: boolean;\n /** Width in pixels of progress bars. Defaults to barWidth + barGap (fills gaps). */\n progressBarWidth: number;\n /** Whether the playlist has finished loading all tracks */\n isReady: boolean;\n /** Whether tracks are rendered in mono mode */\n mono: boolean;\n /** Ref set by useClipDragHandlers during boundary trim drags.\n * When true, loadAudio skips engine rebuild — visual updates flow via React state only. */\n isDraggingRef: React.MutableRefObject<boolean>;\n onTracksChange: ((tracks: ClipTrack[]) => void) | undefined;\n}\n\n// Create the 4 separate contexts\nconst PlaybackAnimationContext = createContext<PlaybackAnimationContextValue | null>(null);\nconst PlaylistStateContext = createContext<PlaylistStateContextValue | null>(null);\nconst PlaylistControlsContext = createContext<PlaylistControlsContextValue | null>(null);\nconst PlaylistDataContext = createContext<PlaylistDataContextValue | null>(null);\n\nexport interface WaveformPlaylistProviderProps {\n tracks: ClipTrack[]; // Updated to use clip-based model\n timescale?: boolean;\n mono?: boolean;\n waveHeight?: number;\n samplesPerPixel?: number;\n zoomLevels?: number[]; // Array of zoom levels in samples per pixel (lower = more zoomed in)\n automaticScroll?: boolean;\n theme?: Partial<WaveformPlaylistTheme>;\n controls?: {\n show: boolean;\n width: number;\n };\n annotationList?: {\n annotations?: AnnotationData[];\n editable?: boolean;\n isContinuousPlay?: boolean;\n linkEndpoints?: boolean;\n controls?: AnnotationAction[];\n };\n effects?: EffectsFunction;\n onReady?: () => void;\n /** Called when audio/engine initialization fails (e.g. a missing optional peer,\n * a throwing `createAdapter`, or an invalid `zoomLevels`/`samplesPerPixel`). The\n * provider already logs the error; use this to surface it in your UI. */\n onError?: (err: Error) => void;\n /** @deprecated Use onAnnotationsChange instead */\n onAnnotationUpdate?: (annotations: AnnotationData[]) => void;\n /** Callback when annotations are changed (drag, edit, etc.) */\n onAnnotationsChange?: (annotations: AnnotationData[]) => void;\n /** Width in pixels of waveform bars. Default: 1 */\n barWidth?: number;\n /** Spacing in pixels between waveform bars. Default: 0 */\n barGap?: number;\n /** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */\n roundedBars?: boolean;\n /** Width in pixels of progress bars. Default: barWidth + barGap (fills gaps). */\n progressBarWidth?: number;\n /** Callback when engine clip operations (move, trim, split) change tracks.\n * The provider calls this so the parent can update its tracks state without\n * triggering a full engine rebuild.\n *\n * **Important:** The parent must pass the received `tracks` reference back as\n * the `tracks` prop (i.e. `setState(tracks)`). The provider uses reference\n * identity (`tracks === engineTracksRef.current`) to detect engine-originated\n * updates and skip the expensive `loadAudio` rebuild. */\n onTracksChange?: (tracks: ClipTrack[]) => void;\n /** SoundFont cache for sample-based MIDI playback. When provided, MIDI clips\n * use SoundFont samples instead of PolySynth synthesis. */\n soundFontCache?: SoundFontCache;\n /** When true, tracks render visually but the engine build is deferred.\n * Use this during progressive loading to avoid rebuilding the engine for\n * each track — flip to false when all tracks are ready for a single build. */\n deferEngineRebuild?: boolean;\n /** Disable the automatic stop when the cursor reaches the end of the\n * longest track — the transport rolls until an explicit stop/pause\n * (DAW-style). Implies the fillViewport layout. Recording sessions\n * suppress the auto-stop automatically, so most recording UIs don't\n * need this. */\n indefinitePlayback?: boolean;\n /** Extend the timeline (background + timescale) to fill the visible scroll\n * container even when the audio is shorter. Layout only. Recording UIs\n * typically want this so the empty timeline doesn't collapse to the audio\n * width. */\n fillViewport?: boolean;\n /** Desired AudioContext sample rate. Creates a cross-browser AudioContext at\n * this rate via standardized-audio-context. Pre-computed peaks (.dat files)\n * render instantly when they match. On mismatch, falls back to worker. */\n sampleRate?: number;\n /** Factory for a custom PlayoutAdapter. When omitted, the Tone.js engine\n * (@waveform-playlist/playout) is dynamically imported — so a custom adapter\n * lets a consumer use neither @waveform-playlist/playout nor tone. Called once\n * per engine rebuild; the provider owns and disposes the returned instance.\n * Pass a stable reference (module-level or useCallback) — it is read directly\n * inside the curated `loadAudio` effect, not via its dependency array. */\n createAdapter?: () => PlayoutAdapter;\n children: ReactNode;\n}\n\nexport const WaveformPlaylistProvider: React.FC<WaveformPlaylistProviderProps> = ({\n tracks,\n timescale = false,\n mono = false,\n waveHeight = 80,\n samplesPerPixel: initialSamplesPerPixel = 1024,\n zoomLevels,\n automaticScroll = false,\n theme: userTheme,\n controls = { show: false, width: 0 },\n annotationList,\n effects,\n onReady,\n onError,\n onAnnotationUpdate: _onAnnotationUpdate,\n onAnnotationsChange,\n barWidth = 1,\n barGap = 0,\n roundedBars = false,\n progressBarWidth: progressBarWidthProp,\n onTracksChange,\n soundFontCache,\n deferEngineRebuild = false,\n indefinitePlayback = false,\n fillViewport = false,\n sampleRate: sampleRateProp,\n createAdapter,\n children,\n}) => {\n // Default progressBarWidth to barWidth + barGap (fills gaps)\n const progressBarWidth = progressBarWidthProp ?? barWidth + barGap;\n\n // Stabilize zoomLevels reference — inline arrays (e.g. zoomLevels={[256, 512]})\n // create a new reference every render, which would trigger engine rebuild via\n // loadAudio deps. Content-based comparison avoids spurious rebuilds.\n const stableZoomLevels = useMemo(\n () => zoomLevels,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [zoomLevels?.join(',')]\n );\n\n // Annotations are derived from prop (single source of truth in parent)\n // In v6, annotations must be pre-parsed (numeric start/end). Use parseAeneas() from @waveform-playlist/annotations before passing.\n const annotations = useMemo(() => {\n if (!annotationList?.annotations) return [];\n if (process.env.NODE_ENV !== 'production' && annotationList.annotations.length > 0) {\n const first = annotationList.annotations[0] as unknown as Record<string, unknown>;\n if (typeof first.start !== 'number' || typeof first.end !== 'number') {\n console.error(\n '[waveform-playlist] Annotations must have numeric start/end values. ' +\n 'In v6, use parseAeneas() from @waveform-playlist/annotations before passing annotations. ' +\n 'Received start type: ' +\n typeof first.start\n );\n return [];\n }\n }\n return annotationList.annotations;\n }, [annotationList?.annotations]);\n\n // Ref for animation loop (avoids restarting loop on annotation change)\n const annotationsRef = useRef<AnnotationData[]>(annotations);\n annotationsRef.current = annotations;\n\n // State\n const [activeAnnotationId, setActiveAnnotationIdState] = useState<string | null>(null);\n const [isPlaying, setIsPlaying] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [audioBuffers, setAudioBuffers] = useState<AudioBuffer[]>([]);\n const [peaksDataArray, setPeaksDataArray] = useState<TrackClipPeaks[]>([]); // Updated for clip-based peaks\n const [trackStates, setTrackStates] = useState<TrackState[]>([]);\n const [isAutomaticScroll, setIsAutomaticScroll] = useState(automaticScroll);\n const [continuousPlay, setContinuousPlayState] = useState(\n annotationList?.isContinuousPlay ?? false\n );\n const [linkEndpoints, setLinkEndpoints] = useState(annotationList?.linkEndpoints ?? false);\n const [annotationsEditable, setAnnotationsEditable] = useState(annotationList?.editable ?? false);\n const [isReady, setIsReady] = useState(false);\n\n // Refs\n // Engine owns selection, loop, selectedTrackId, zoom, and masterVolume state.\n // React subscribes to engine statechange and mirrors into useState/refs.\n // Playback timing (currentTime, isPlaying) remains in React for animation loop.\n const engineRef = useRef<PlaylistEngine | null>(null);\n const adapterRef = useRef<PlayoutAdapter | null>(null);\n const audioInitializedRef = useRef<boolean>(false);\n\n // Ref for animation loop access (avoids adding the prop to useCallback deps)\n const indefinitePlaybackRef = useRef(indefinitePlayback);\n indefinitePlaybackRef.current = indefinitePlayback;\n\n // Recording session state. While active:\n // 1. The animation loop's end-of-audio auto-stop is suppressed so overdub\n // playback runs past the end of existing material (#589/#590).\n // 2. The armed track's existing content is transiently muted: punch-in\n // replaces whatever the take overlaps (#579), so the doomed material\n // must not play under the overdub. Engine-level mute (survives adapter\n // rebuilds), captured/restored around the session; the track's UI mute\n // state is untouched.\n // Auto-wired from PlaylistVisualization's recordingState prop; overdub\n // flows also call it eagerly before play() — a render round-trip loses the\n // race against the first animation frame (empty timeline: time 0 >=\n // duration 0 would stop playback instantly).\n const recordingActiveRef = useRef(false);\n const armedTrackMuteRef = useRef<{ trackId: string; prevMuted: boolean } | null>(null);\n const setRecordingActive = useCallback((active: boolean, armedTrackId?: string | null) => {\n recordingActiveRef.current = active;\n const engine = engineRef.current;\n if (!engine) return;\n if (active && armedTrackId) {\n if (armedTrackMuteRef.current?.trackId === armedTrackId) return; // already armed\n if (armedTrackMuteRef.current) {\n // Armed track changed mid-session — restore the previous one first.\n engine.setTrackMute(armedTrackMuteRef.current.trackId, armedTrackMuteRef.current.prevMuted);\n }\n const prevMuted = engine.getState().tracks.find((t) => t.id === armedTrackId)?.muted ?? false;\n armedTrackMuteRef.current = { trackId: armedTrackId, prevMuted };\n engine.setTrackMute(armedTrackId, true);\n } else if (!active && armedTrackMuteRef.current) {\n engine.setTrackMute(armedTrackMuteRef.current.trackId, armedTrackMuteRef.current.prevMuted);\n armedTrackMuteRef.current = null;\n }\n }, []);\n const isPlayingRef = useRef<boolean>(false);\n isPlayingRef.current = isPlaying;\n const playStartPositionRef = useRef<number>(0);\n const currentTimeRef = useRef<number>(0);\n // Visually-aligned playback time (see toVisualTime / getAudibleTime). The\n // animation loop updates this per frame. Pause/seek/stop paths sync it\n // explicitly so the static playhead reflects the commanded resting position.\n const visualTimeRef = useRef<number>(0);\n const tracksRef = useRef<ClipTrack[]>(tracks);\n const soundFontCacheRef = useRef(soundFontCache);\n soundFontCacheRef.current = soundFontCache;\n const trackStatesRef = useRef<TrackState[]>(trackStates);\n const playbackStartTimeRef = useRef<number>(0); // context.currentTime when playback started\n const audioStartPositionRef = useRef<number>(0); // Audio position when playback started\n const playbackEndTimeRef = useRef<number | null>(null); // Audio position where playback should stop (for selections)\n const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n const isAutomaticScrollRef = useRef<boolean>(false);\n // Animation frame callback registry — components register per-frame DOM update\n // callbacks instead of running their own rAF loops. Single loop drives all.\n const frameCallbacksRef = useRef<Map<string, (data: FrameData) => void>>(new Map());\n const continuousPlayRef = useRef<boolean>(annotationList?.isContinuousPlay ?? false);\n const activeAnnotationIdRef = useRef<string | null>(null);\n // Engine clip operations guard: tracks reference from the last engine statechange.\n // When engine methods (moveClip, trimClip, splitClip) mutate tracks, the statechange\n // handler stores state.tracks here and calls onTracksChange with the same reference.\n // loadAudio checks tracks === engineTracksRef.current to skip the full rebuild.\n const engineTracksRef = useRef<ClipTrack[] | null>(null);\n // Monotonic counter from engine.getState().tracksVersion — used to detect\n // track mutations (move/trim/split/add/remove) vs other statechange events.\n const lastTracksVersionRef = useRef(0);\n // Flag set during render to prevent the previous effect cleanup from disposing the\n // engine when we're skipping the rebuild. React runs previous cleanup before the\n // current effect body, so the flag must be set during render (which precedes cleanup).\n const skipEngineDisposeRef = useRef(false);\n // Set by useClipDragHandlers during boundary trim drags. When true,\n // loadAudio skips the full engine rebuild — visual updates flow via React\n // state only. On drag end, engine.trimClip() commits the final delta.\n const isDraggingRef = useRef(false);\n // Snapshot of tracks from the previous loadAudio run, used to detect\n // additive-only changes (new tracks appended, existing unchanged).\n const prevTracksRef = useRef<ClipTrack[]>([]);\n // Provider-level ref for scroll-position math and animation loop pixel\n // calculation. Distinct from useZoomControls's internal ref (statechange guard).\n const samplesPerPixelRef = useRef<number>(initialSamplesPerPixel);\n // Provisional sample rate until the adapter (which owns the AudioContext) is\n // created in loadAudio. Reconciled from adapter.audioContext.sampleRate there.\n // (#510: avoids a static @waveform-playlist/playout import at mount.)\n const [initialSampleRate] = useState<number>(() => sampleRateProp ?? 48000);\n const sampleRateRef = useRef<number>(initialSampleRate);\n\n // Custom hooks — engine-owned state delegated to hooks with onEngineState() pattern\n const { timeFormat, setTimeFormat, formatTime } = useTimeFormat();\n const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });\n const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;\n const volume = useMasterVolume({ engineRef, initialVolume: 1.0 });\n const {\n masterVolume,\n setMasterVolume,\n masterVolumeRef,\n onEngineState: onVolumeEngineState,\n } = volume;\n const {\n selectionStart,\n selectionEnd,\n setSelection: setSelectionEngine,\n selectionStartRef,\n selectionEndRef,\n onEngineState: onSelectionEngineState,\n } = useSelectionState({ engineRef });\n const {\n isLoopEnabled,\n loopStart,\n loopEnd,\n setLoopEnabled,\n setLoopRegion,\n clearLoopRegion,\n isLoopEnabledRef,\n loopStartRef,\n loopEndRef,\n onEngineState: onLoopEngineState,\n } = useLoopState({ engineRef });\n const {\n selectedTrackId,\n setSelectedTrackId: setSelectedTrackIdControl,\n onEngineState: onSelectedTrackEngineState,\n selectedTrackIdRef,\n } = useSelectedTrack({ engineRef });\n const {\n canUndo,\n canRedo,\n undo,\n redo,\n onEngineState: onUndoEngineState,\n } = useUndoState({ engineRef });\n const { animationFrameRef, startAnimationFrameLoop, stopAnimationFrameLoop } =\n useAnimationFrameLoop();\n\n // Worker-based WaveformData cache for fast zoom resampling\n const baseScale = useMemo(\n () => Math.min(...(stableZoomLevels ?? [256, 512, 1024, 2048, 4096, 8192])),\n [stableZoomLevels]\n );\n const { cache: waveformDataCache } = useWaveformDataCache(tracks, baseScale);\n\n // Custom setter for continuousPlay that updates BOTH state and ref synchronously\n // This ensures the ref is updated immediately, before the animation loop can read it\n const setContinuousPlay = useCallback((value: boolean) => {\n continuousPlayRef.current = value; // Update ref synchronously\n setContinuousPlayState(value); // Update state (triggers re-render)\n }, []);\n\n // Custom setter for activeAnnotationId that updates BOTH state and ref synchronously\n const setActiveAnnotationId = useCallback((value: string | null) => {\n activeAnnotationIdRef.current = value; // Update ref synchronously\n setActiveAnnotationIdState(value); // Update state (triggers re-render)\n }, []);\n\n // Cross-hook concern: reads selection refs to set loop region\n const setLoopRegionFromSelection = useCallback(() => {\n const start = selectionStartRef.current ?? 0;\n const end = selectionEndRef.current ?? 0;\n if (start !== end && end > start) {\n setLoopRegion(start, end);\n }\n }, [setLoopRegion, selectionStartRef, selectionEndRef]);\n\n // Keep refs in sync with state\n useEffect(() => {\n isAutomaticScrollRef.current = isAutomaticScroll;\n }, [isAutomaticScroll]);\n\n useEffect(() => {\n trackStatesRef.current = trackStates;\n }, [trackStates]);\n\n tracksRef.current = tracks;\n\n // Render-phase guard: detect when tracks prop came from an engine clip operation.\n // Must be set before effects run so the previous effect cleanup can skip disposal.\n // Also skip disposal during active boundary trim drags — onDragMove updates React\n // tracks per-frame, triggering effect re-runs whose cleanup would dispose the engine.\n const isEngineTracks = tracks === engineTracksRef.current;\n\n // Detect additive-only track changes: new tracks appended, existing tracks unchanged.\n // Uses reference equality on individual tracks — if any existing track object changed\n // (e.g., clip added to it), this is false and triggers a full rebuild.\n const prevTracks = prevTracksRef.current;\n const isIncrementalAdd =\n engineRef.current !== null &&\n tracks.length > prevTracks.length &&\n prevTracks.every((pt) => {\n const current = tracks.find((t) => t.id === pt.id);\n return current === pt; // reference equality — existing tracks must be untouched\n });\n // Note: prevTracks may be empty here (e.g. recording's first track on a\n // persistent empty-timeline engine) — that's a valid 0→1 incremental add, so\n // it must NOT require prevTracks.length > 0. The engineRef !== null guard keeps\n // it correct: a lazy/no-engine consumer still takes the full build path.\n\n skipEngineDisposeRef.current = isEngineTracks || isDraggingRef.current || isIncrementalAdd;\n\n // Adjust scroll position proportionally when zoom changes\n useEffect(() => {\n if (!scrollContainerRef.current || duration === 0) return;\n\n const container = scrollContainerRef.current;\n const oldSamplesPerPixel = samplesPerPixelRef.current;\n const newSamplesPerPixel = samplesPerPixel;\n\n if (oldSamplesPerPixel === newSamplesPerPixel) return;\n\n // Calculate the current center time in the viewport\n const containerWidth = container.clientWidth;\n const currentScrollLeft = container.scrollLeft;\n const centerPixel = currentScrollLeft + containerWidth / 2;\n const sr = sampleRateRef.current;\n const centerTime = (centerPixel * oldSamplesPerPixel) / sr;\n\n // Calculate new scroll position to keep the same center time\n const newCenterPixel = (centerTime * sr) / newSamplesPerPixel;\n const newScrollLeft = Math.max(0, newCenterPixel - containerWidth / 2);\n\n container.scrollLeft = newScrollLeft;\n samplesPerPixelRef.current = newSamplesPerPixel;\n }, [samplesPerPixel, duration]);\n\n // Track pending playback resume after tracks change\n const pendingResumeRef = useRef<{ position: number } | null>(null);\n\n // Load audio from clips (only when tracks change)\n useEffect(() => {\n // Guard: skip full rebuild when tracks came from an engine clip operation\n // (moveClip, trimClip, splitClip). The engine and adapter already have the\n // correct tracks — we just need to update duration for the timeline.\n // Also skip during active boundary trim drags — visual updates flow via\n // React state only; engine.trimClip() commits the final delta on drag end.\n if (isEngineTracks || isDraggingRef.current) {\n if (isEngineTracks) {\n engineTracksRef.current = null;\n }\n\n // Recalculate duration from updated clip positions\n let maxDuration = 0;\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n const clipEnd = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n maxDuration = Math.max(maxDuration, clipEnd);\n });\n });\n setDuration(maxDuration);\n prevTracksRef.current = tracks;\n return;\n }\n\n // Guard: incremental track addition — only new tracks appended, existing\n // tracks unchanged. Add each new track to the existing engine without\n // tearing down the playout. This avoids disposing and recreating all\n // audio nodes when dropping a file or finishing a recording.\n if (isIncrementalAdd && engineRef.current) {\n try {\n // Use prevTracks captured during render (not prevTracksRef.current) to ensure\n // the guard condition and inner logic operate on the same snapshot.\n const prevIds = new Set(prevTracks.map((t) => t.id));\n const addedTracks = tracks.filter((t) => !prevIds.has(t.id));\n\n // Merge current UI state into new tracks before adding to engine\n const currentTrackStates = trackStatesRef.current;\n for (const track of addedTracks) {\n const trackIndex = tracks.indexOf(track);\n const trackState = currentTrackStates[trackIndex];\n const trackWithState = {\n ...track,\n volume: trackState?.volume ?? track.volume,\n muted: trackState?.muted ?? track.muted,\n soloed: trackState?.soloed ?? track.soloed,\n pan: trackState?.pan ?? track.pan,\n };\n engineRef.current!.addTrack(trackWithState);\n }\n\n // Update duration from all tracks (including newly added)\n let maxDuration = 0;\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n const clipEnd = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n maxDuration = Math.max(maxDuration, clipEnd);\n });\n });\n setDuration(maxDuration);\n\n // Initialize track states for the new tracks (preserve existing)\n setTrackStates((prev) => {\n if (prev.length === tracks.length) return prev;\n const newStates = [...prev];\n for (const track of addedTracks) {\n newStates.push({\n name: track.name,\n muted: track.muted,\n soloed: track.soloed,\n volume: track.volume,\n pan: track.pan,\n });\n }\n return newStates;\n });\n\n // Extract audio buffers from all clips across all tracks\n const buffers: AudioBuffer[] = [];\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n if (clip.audioBuffer) {\n buffers.push(clip.audioBuffer);\n }\n });\n });\n setAudioBuffers(buffers);\n\n prevTracksRef.current = tracks;\n return;\n } catch (err) {\n console.warn(\n '[waveform-playlist] Incremental add failed, falling through to full rebuild:',\n String(err)\n );\n // Fall through to full loadAudio path\n }\n }\n\n // Defer engine rebuild during progressive loading — tracks render visually\n // (controls, clip shapes, peaks via worker) but the engine isn't built yet.\n // When deferEngineRebuild flips to false, this effect re-runs and builds once.\n if (deferEngineRebuild) {\n // Still update duration so the timeline renders at the correct width\n let maxDuration = 0;\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n const clipEnd = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n maxDuration = Math.max(maxDuration, clipEnd);\n });\n });\n setDuration(maxDuration);\n return;\n }\n\n // Reset ready state for full rebuild\n setIsReady(false);\n\n if (tracks.length === 0) {\n // Clear content state when all tracks are removed.\n setAudioBuffers([]);\n setDuration(0);\n setTrackStates([]);\n setPeaksDataArray([]);\n\n // Always keep a persistent engine alive — even with no tracks — so the\n // Transport is ready before playback/recording starts. Without this the\n // engine is rebuilt asynchronously when the first track is added, and\n // play() races that rebuild — intermittently no-opping (frozen\n // playhead/time) and paying first-record latency. The engine is still\n // created via dynamic import (lazy module load preserved, #510); only its\n // lifecycle is eager.\n if (engineRef.current) {\n // Keep the existing engine; just clear its tracks.\n engineRef.current.setTracks([]);\n prevTracksRef.current = tracks;\n return;\n }\n // No engine yet (initial mount with no tracks): fall through to the\n // loadAudio path below, which builds a persistent engine for 0 tracks.\n }\n\n // Capture playback state before rebuilding playout (read from ref, not state,\n // so isPlaying can be excluded from the dep array — play/pause must not trigger rebuilds)\n const wasPlaying = isPlayingRef.current;\n const resumePosition = currentTimeRef.current;\n\n // Stop current playback and animation before disposing\n if (engineRef.current && wasPlaying) {\n engineRef.current.stop();\n stopAnimationFrameLoop();\n // Mark that we need to resume playback after playout is rebuilt\n pendingResumeRef.current = { position: resumePosition };\n }\n\n let cancelled = false;\n\n const loadAudio = async () => {\n try {\n // Extract all audio buffers from all clips across all tracks\n const buffers: AudioBuffer[] = [];\n\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n if (clip.audioBuffer) {\n buffers.push(clip.audioBuffer);\n }\n });\n });\n\n // Calculate total timeline duration from all clips across all tracks\n // Use clip.sampleRate which is always defined (works for peaks-only clips too)\n let maxDuration = 0;\n tracks.forEach((track) => {\n track.clips.forEach((clip) => {\n const sampleRate = clip.sampleRate;\n const clipEndSample = clip.startSample + clip.durationSamples;\n const clipEnd = clipEndSample / sampleRate;\n maxDuration = Math.max(maxDuration, clipEnd);\n });\n });\n\n setAudioBuffers(buffers);\n setDuration(maxDuration);\n\n // Initialize or update track states, preserving existing UI state (mute/solo/volume/pan)\n // Only initialize from ClipTrack props when trackStates is empty or track count changes\n setTrackStates((prevStates) => {\n if (prevStates.length === tracks.length) {\n // Same number of tracks - preserve existing UI state, just update names\n return prevStates.map((state, i) => ({\n ...state,\n name: tracks[i].name,\n }));\n }\n // Track count changed - reinitialize from ClipTrack properties\n return tracks.map((track) => ({\n name: track.name,\n muted: track.muted,\n soloed: track.soloed,\n volume: track.volume,\n pan: track.pan,\n }));\n });\n\n // Dispose old engine before creating new one\n if (engineRef.current) {\n engineRef.current.dispose();\n }\n\n // Reset tracks version tracking for the new engine\n lastTracksVersionRef.current = 0;\n engineTracksRef.current = null;\n\n // Create engine adapter — custom if provided, else Tone.js (dynamically imported).\n // Reset init flag — new adapter needs AudioContext resume on first play.\n audioInitializedRef.current = false;\n const adapter = await resolvePlayoutAdapter({\n createAdapter,\n effects,\n soundFontCache: soundFontCacheRef.current,\n sampleRate: sampleRateProp,\n });\n if (cancelled) {\n adapter.dispose();\n return;\n }\n // Reconcile sample rate from the adapter's own AudioContext. The setState\n // calls later in loadAudio trigger a re-render that propagates this via\n // `const sampleRate = sampleRateRef.current` at render.\n sampleRateRef.current = adapter.audioContext.sampleRate;\n adapterRef.current = adapter;\n const engine = new PlaylistEngine({\n adapter,\n samplesPerPixel: samplesPerPixelRef.current,\n zoomLevels: stableZoomLevels,\n });\n\n // Seed engine with current UI state so a fresh engine doesn't\n // reset selection/loop/selectedTrack to defaults on the first statechange emission.\n engine.setSelection(selectionStartRef.current ?? 0, selectionEndRef.current ?? 0);\n engine.setLoopRegion(loopStartRef.current ?? 0, loopEndRef.current ?? 0);\n if (isLoopEnabledRef.current) engine.setLoopEnabled(true);\n engine.setMasterVolume(masterVolumeRef.current ?? 1.0);\n if (selectedTrackIdRef.current) engine.selectTrack(selectedTrackIdRef.current);\n\n // Merge current UI state into tracks before passing to engine\n const currentTrackStates = trackStatesRef.current;\n const tracksWithState = tracks.map((track, index) => {\n const trackState = currentTrackStates[index];\n return {\n ...track,\n volume: trackState?.volume ?? track.volume,\n muted: trackState?.muted ?? track.muted,\n soloed: trackState?.soloed ?? track.soloed,\n pan: trackState?.pan ?? track.pan,\n };\n });\n\n // Subscribe to engine statechange BEFORE setTracks() so the first\n // emission (which carries correct canZoomIn/canZoomOut) is captured.\n // Seeding statechanges fired above are harmless — they contain values\n // that already match the hooks' current state, so onEngineState()\n // ref guards skip them.\n // Suppress tracks mirroring during the initial setTracks — the parent\n // already has these tracks. The flag is local so it becomes false\n // after setTracks returns (statechange fires synchronously).\n let suppressTracksMirroring = true;\n engine.on('statechange', (state: EngineState) => {\n onSelectionEngineState(state);\n onLoopEngineState(state);\n onSelectedTrackEngineState(state);\n onZoomEngineState(state);\n onVolumeEngineState(state);\n onUndoEngineState(state);\n\n // Mirror engine tracks changes to parent via onTracksChange.\n // tracksVersion only increments on structural track mutations (move,\n // trim, split, setTracks, addTrack, removeTrack), not on\n // selection/zoom/volume changes. Per-track mixer edits bump the\n // engine's separate mixerVersion (#501) and are persisted here via\n // trackStates, so they intentionally do not flow through onTracksChange.\n if (!suppressTracksMirroring && state.tracksVersion !== lastTracksVersionRef.current) {\n lastTracksVersionRef.current = state.tracksVersion;\n engineTracksRef.current = state.tracks;\n if (onTracksChange) {\n onTracksChange(state.tracks);\n } else {\n console.warn(\n '[waveform-playlist] Engine tracks changed but onTracksChange prop is not set — ' +\n 'UI will revert on next render. Pass onTracksChange to WaveformPlaylistProvider.'\n );\n }\n }\n });\n\n engine.setTracks(tracksWithState);\n suppressTracksMirroring = false;\n lastTracksVersionRef.current = engine.getState().tracksVersion;\n engineRef.current = engine;\n\n setIsReady(true);\n\n // Dispatch custom event for external listeners\n const event = new CustomEvent('waveform-playlist:ready', {\n detail: {\n trackCount: tracks.length,\n duration: maxDuration,\n },\n });\n window.dispatchEvent(event);\n\n prevTracksRef.current = tracks;\n\n onReady?.();\n } catch (error) {\n // Dispose an adapter created before the engine failed to build (e.g. invalid\n // zoomLevels make `new PlaylistEngine` throw) — the cleanup skips it because\n // engineRef was never assigned, so it would otherwise leak a live AudioContext.\n if (!engineRef.current && adapterRef.current) {\n try {\n adapterRef.current.dispose();\n } catch (disposeErr) {\n console.warn(\n '[waveform-playlist] adapter dispose after load failure threw: ' + String(disposeErr)\n );\n }\n adapterRef.current = null;\n }\n console.warn('[waveform-playlist] Error loading audio: ' + String(error));\n onError?.(error instanceof Error ? error : new Error(String(error)));\n }\n };\n\n loadAudio();\n\n return () => {\n cancelled = true;\n // Skip disposal when the next render's guard will keep the engine alive.\n // skipEngineDisposeRef is set during the render phase (before this cleanup runs).\n if (skipEngineDisposeRef.current) {\n skipEngineDisposeRef.current = false;\n return;\n }\n stopAnimationFrameLoop();\n if (engineRef.current) {\n engineRef.current.dispose();\n adapterRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n tracks,\n // isEngineTracks is intentionally excluded — it's a render-phase guard read\n // inside the effect body, not a trigger. Including it causes a spurious re-run\n // when it flips from true→false after engineTracksRef is cleared.\n // isPlaying is intentionally excluded — read from isPlayingRef inside the\n // effect body. Including it causes a full engine+playout rebuild on every\n // play/pause/stop, destroying and recreating all audio Players.\n // soundFontCache is deliberately excluded — late cache changes are forwarded\n // to the live adapter by the sync effect below; only adapter creation reads it\n // (via soundFontCacheRef).\n onReady,\n onError,\n effects,\n stopAnimationFrameLoop,\n onSelectionEngineState,\n onLoopEngineState,\n onSelectedTrackEngineState,\n onZoomEngineState,\n onVolumeEngineState,\n onUndoEngineState,\n onTracksChange,\n masterVolumeRef,\n selectionStartRef,\n selectionEndRef,\n loopStartRef,\n loopEndRef,\n isLoopEnabledRef,\n stableZoomLevels,\n deferEngineRebuild,\n ]);\n\n // Forward late-arriving / swapped SoundFontCache to the live adapter so\n // MIDI tracks upgrade from PolySynth without an engine rebuild. On mount\n // this may run against a just-created adapter (the loadAudio effect is\n // declared earlier, runs first, and assigns adapterRef synchronously) or\n // against null (empty tracks). Both are safe: the helper no-ops on null,\n // and the adapter skips MIDI tracks whose routing is unchanged.\n useEffect(() => {\n syncSoundFontCacheToAdapter(adapterRef.current, soundFontCache);\n }, [soundFontCache]);\n\n // Regenerate peaks when zoom, mono, or waveformDataCache changes (without reloading audio)\n // Peak sources in priority order:\n // A) clip.waveformData — external pre-computed peaks (e.g. from BBC audiowaveform)\n // B) waveformDataCache — worker-generated WaveformData (fast resample on zoom)\n // C) empty peaks — clip is still loading or has no audio data\n useEffect(() => {\n if (tracks.length === 0) return;\n\n const allTrackPeaks: TrackClipPeaks[] = tracks.map((track) => {\n const clipPeaks: ClipPeaks[] = track.clips.map((clip) => {\n let peaks: PeakData | undefined;\n\n // Path A: External pre-computed waveform data (e.g. from audiowaveform .dat file)\n // When sample rates match, use clip offsets directly.\n // When rates mismatch (e.g., waveformData at 48000 Hz, clip rebuilt at 44100 Hz\n // after audio decode), convert offsets to the waveformData's sample space and\n // adjust samplesPerPixel so peaks fill the correct container width.\n // Path B (worker) replaces these on next render once cache is populated.\n if (clip.waveformData) {\n try {\n const wdRate = clip.waveformData.sample_rate;\n const clipRate = clip.sampleRate;\n let peakOffset = clip.offsetSamples;\n let peakDuration = clip.durationSamples;\n let peakSpp = samplesPerPixel;\n if (wdRate !== clipRate && clipRate > 0 && wdRate > 0) {\n const ratio = wdRate / clipRate;\n peakOffset = Math.round(clip.offsetSamples * ratio);\n peakDuration = Math.round(clip.durationSamples * ratio);\n peakSpp = Math.max(1, Math.round(samplesPerPixel * ratio));\n }\n peaks = extractPeaksFromWaveformDataFull(\n clip.waveformData as WaveformData,\n peakSpp,\n mono,\n peakOffset,\n peakDuration\n );\n } catch (err) {\n console.warn(\n '[waveform-playlist] Failed to extract peaks from waveformData: ' + String(err)\n );\n }\n }\n\n // Path B: Worker-generated WaveformData cache (fast resample on zoom)\n if (!peaks) {\n const cached = waveformDataCache.get(clip.id);\n if (cached) {\n try {\n peaks = extractPeaksFromWaveformDataFull(\n cached,\n samplesPerPixel,\n mono,\n clip.offsetSamples,\n clip.durationSamples\n );\n } catch (err) {\n console.warn(\n '[waveform-playlist] Failed to extract peaks from cache: ' + String(err)\n );\n }\n }\n }\n\n // Path C: No peaks data available yet — render empty while worker processes.\n // Use correct channel count from audioBuffer to prevent track height shift\n // when peaks arrive (mono mode collapses to 1 channel).\n if (!peaks) {\n if (!deferEngineRebuild && !clip.audioBuffer && !clip.waveformData && !clip.midiNotes) {\n console.warn(\n `[waveform-playlist] Clip \"${clip.id}\" has no audio data or waveform data`\n );\n }\n const numChannels = mono ? 1 : (clip.audioBuffer?.numberOfChannels ?? 1);\n // MIDI clips: derive pixel width from sample duration so the clip renders\n // at the correct width instead of zero-width blank tracks.\n const pixelLength = clip.midiNotes\n ? Math.ceil(clip.durationSamples / samplesPerPixel)\n : 0;\n peaks = {\n length: pixelLength,\n data: Array.from({ length: numChannels }, () => new Int16Array(pixelLength * 2)),\n bits: 16,\n };\n }\n\n return {\n clipId: clip.id,\n trackName: track.name,\n peaks,\n startSample: clip.startSample,\n durationSamples: clip.durationSamples,\n fadeIn: clip.fadeIn,\n fadeOut: clip.fadeOut,\n midiNotes: clip.midiNotes,\n sampleRate: clip.sampleRate,\n offsetSamples: clip.offsetSamples,\n };\n });\n\n return clipPeaks;\n });\n\n setPeaksDataArray(allTrackPeaks);\n }, [tracks, samplesPerPixel, mono, waveformDataCache, deferEngineRebuild]);\n\n // Audio clock from the adapter's own AudioContext (#510 — replaces tone's\n // getContext()). Stable; reads a ref so identity never changes.\n const getAudioContextTime = useCallback(\n () => adapterRef.current?.audioContext.currentTime ?? 0,\n []\n );\n\n // Returns current playback time from engine (auto-wraps at loop boundaries).\n // Falls back to manual calculation when engine is unavailable.\n const getPlaybackTimeFallbackWarnedRef = useRef(false);\n const getPlaybackTime = useCallback(() => {\n if (engineRef.current) {\n return engineRef.current.getCurrentTime();\n }\n // Fallback: manual calculation (does not handle loop wrapping)\n if (!getPlaybackTimeFallbackWarnedRef.current) {\n getPlaybackTimeFallbackWarnedRef.current = true;\n console.warn(\n '[waveform-playlist] getPlaybackTime called without engine. ' +\n 'Falling back to manual elapsed time (loop wrapping will not work).'\n );\n }\n const elapsed = getAudioContextTime() - (playbackStartTimeRef.current ?? 0);\n return (audioStartPositionRef.current ?? 0) + elapsed;\n }, [getAudioContextTime]);\n\n // Resting (non-playing) cursor positions display the raw time unchanged.\n // Latency compensation (outputLatency + lookAhead) models the gap between\n // the Transport's scheduling position and audible output — a property of\n // *playing*, not of position. A seeked/paused/stopped cursor has no audible\n // counterpart; the commanded position is the truth. During playback the\n // animation loop uses engine.getAudibleTime() instead.\n const toVisualTime = useCallback((rawTime: number): number => {\n return Number.isFinite(rawTime) ? Math.max(0, rawTime) : 0;\n }, []);\n\n // Pair-write helper for the dual-ref pattern. Storage stays raw on\n // `currentTimeRef`; display gets the guard-clamped raw version on `visualTimeRef`\n // (compensation happens in the animation loop via engine.getAudibleTime() while playing).\n // Use this at every pause/seek/stop/loop site — never assign one ref alone\n // (browser/CLAUDE.md: \"Display uses visualTimeRef\").\n const setCurrentTimeRefs = useCallback(\n (rawTime: number) => {\n currentTimeRef.current = rawTime;\n visualTimeRef.current = toVisualTime(rawTime);\n },\n [toVisualTime]\n );\n\n // Read the engine's scheduler lookahead (Tone adapter ~0.1s, native 0).\n // Exposed through PlaybackAnimationContextValue so consumers (e.g., recording\n // live preview) compute audible-latency from the same source as the playhead.\n const getLookAhead = useCallback((): number => {\n return engineRef.current?.lookAhead ?? 0;\n }, []);\n\n const getOutputLatency = useCallback((): number => {\n const audioCtx = adapterRef.current?.audioContext;\n return audioCtx && 'outputLatency' in audioCtx ? (audioCtx as AudioContext).outputLatency : 0;\n }, []);\n\n const registerFrameCallback = useCallback((id: string, cb: (data: FrameData) => void) => {\n frameCallbacksRef.current.set(id, cb);\n }, []);\n\n const unregisterFrameCallback = useCallback((id: string) => {\n frameCallbacksRef.current.delete(id);\n }, []);\n\n // Animation loop\n const startAnimationLoop = useCallback(() => {\n const updateTime = () => {\n // Get current time from engine (auto-wraps at loop boundaries via Transport.seconds)\n const time = getPlaybackTime();\n currentTimeRef.current = time;\n\n // Compute visual time once — all visual consumers use this same value.\n // engine.getAudibleTime() subtracts outputLatency (hardware DAC delay)\n // and adapter.lookAhead (Tone.js Transport runs ~100ms ahead of audible),\n // holding at the play-start position during the pre-roll window. Native\n // adapters report lookAhead 0, so this is a near no-op there.\n const visualRaw = engineRef.current ? engineRef.current.getAudibleTime() : time;\n const visualTime = Number.isFinite(visualRaw) ? Math.max(0, visualRaw) : 0;\n visualTimeRef.current = visualTime;\n\n // Drive registered per-frame callbacks BEFORE stop checks so the\n // final frame renders at the correct stop position (not one frame behind).\n const sr = sampleRateRef.current;\n const spp = samplesPerPixelRef.current;\n const frameData: FrameData = {\n time,\n visualTime,\n sampleRate: sr,\n samplesPerPixel: spp,\n };\n for (const cb of frameCallbacksRef.current.values()) {\n cb(frameData);\n }\n\n // Handle annotation playback based on continuous play mode\n const currentAnnotations = annotationsRef.current;\n if (currentAnnotations.length > 0) {\n const currentAnnotation = currentAnnotations.find(\n (ann) => time >= ann.start && time < ann.end\n );\n\n if (continuousPlayRef.current) {\n // Continuous play ON: update active annotation, let audio play to the end\n if (currentAnnotation && currentAnnotation.id !== activeAnnotationIdRef.current) {\n setActiveAnnotationId(currentAnnotation.id);\n } else if (!currentAnnotation && activeAnnotationIdRef.current !== null) {\n // Clear the active annotation when we're past it, but don't stop playback\n // Let playback continue until the audio actually ends (handled by duration check)\n setActiveAnnotationId(null);\n }\n } else {\n // Continuous play OFF: stop at end of current annotation\n if (activeAnnotationIdRef.current) {\n const activeAnnotation = currentAnnotations.find(\n (ann) => ann.id === activeAnnotationIdRef.current\n );\n if (activeAnnotation && time >= activeAnnotation.end) {\n // Stop playback at end of current annotation\n if (engineRef.current) {\n engineRef.current.stop();\n }\n setIsPlaying(false);\n setCurrentTimeRefs(playStartPositionRef.current);\n setCurrentTime(playStartPositionRef.current);\n return;\n }\n } else {\n // If no active annotation ID is set, use the current annotation\n if (currentAnnotation) {\n setActiveAnnotationId(currentAnnotation.id);\n }\n }\n }\n }\n\n // Handle automatic scroll — uses shared visualTime for alignment with playhead\n if (isAutomaticScrollRef.current && scrollContainerRef.current && duration > 0) {\n const container = scrollContainerRef.current;\n const pixelPosition = (visualTime * sr) / spp;\n const containerWidth = container.clientWidth;\n\n // Continuously scroll to keep playhead centered.\n // Math.round prevents sub-pixel jitter from browser integer rounding.\n const targetScrollLeft = Math.round(Math.max(0, pixelPosition - containerWidth / 2));\n container.scrollLeft = targetScrollLeft;\n }\n\n // Check if we've reached the playback end time (for selection playback)\n if (playbackEndTimeRef.current !== null && time >= playbackEndTimeRef.current) {\n // Stop playback at selection end (selection playback is separate from looping)\n if (engineRef.current) {\n engineRef.current.stop();\n }\n setIsPlaying(false);\n setCurrentTimeRefs(playbackEndTimeRef.current);\n setCurrentTime(playbackEndTimeRef.current);\n playbackEndTimeRef.current = null; // Clear the end time\n return;\n }\n\n // Loop is handled natively by Tone.js Transport (Transport.loop/loopStart/loopEnd).\n // Transport.seconds auto-wraps at loop boundaries, so getPlaybackTime() returns\n // the correct position without manual detection here.\n\n // Auto-stop at the end of the timeline (player-style default), unless:\n // - indefinitePlayback: the transport rolls until an explicit\n // stop/pause (DAW-style, matching dawcore's indefinite-playback), or\n // - a recording session is active: overdub playback must run past the\n // end of existing material (#589/#590).\n // Explicit ends (selection playback, annotation boundaries) are\n // handled above regardless.\n if (time >= duration && !indefinitePlaybackRef.current && !recordingActiveRef.current) {\n // Stop playback - inline to avoid circular dependency\n if (engineRef.current) {\n engineRef.current.stop();\n }\n setIsPlaying(false);\n setCurrentTimeRefs(playStartPositionRef.current);\n setCurrentTime(playStartPositionRef.current);\n setActiveAnnotationId(null);\n return;\n }\n startAnimationFrameLoop(updateTime);\n };\n startAnimationFrameLoop(updateTime);\n }, [\n duration,\n setActiveAnnotationId,\n startAnimationFrameLoop,\n getPlaybackTime,\n setCurrentTimeRefs,\n ]);\n\n const stopAnimationLoop = stopAnimationFrameLoop;\n\n // Restart animation loop and reschedule playout when continuousPlay changes during playback\n // This ensures the loop always has the current continuousPlay value\n // and removes duration limits when switching to continuous play\n useEffect(() => {\n const reschedulePlayback = async () => {\n if (isPlaying && animationFrameRef.current && engineRef.current) {\n // When toggling continuous play ON, reschedule playout without duration limit\n // so audio continues past the current annotation boundary\n if (continuousPlay) {\n const currentPos = currentTimeRef.current;\n\n // Stop current playout (which may have duration limit + pause callback)\n engineRef.current.stop();\n stopAnimationLoop();\n\n const timeNow = getAudioContextTime();\n playbackStartTimeRef.current = timeNow;\n audioStartPositionRef.current = currentPos;\n\n // Play without duration - will play to end of track\n engineRef.current.play(currentPos);\n startAnimationLoop();\n } else {\n // Just restart animation loop for continuous play OFF\n stopAnimationLoop();\n startAnimationLoop();\n }\n }\n };\n\n reschedulePlayback().catch((err) => {\n console.warn('[waveform-playlist] Failed to reschedule playback:', err);\n setIsPlaying(false);\n stopAnimationLoop();\n });\n }, [\n continuousPlay,\n isPlaying,\n startAnimationLoop,\n stopAnimationLoop,\n animationFrameRef,\n getAudioContextTime,\n ]);\n\n // Resume playback after tracks change (e.g., after splitting a clip during playback)\n useEffect(() => {\n const resumePlayback = async () => {\n if (pendingResumeRef.current && engineRef.current) {\n const { position } = pendingResumeRef.current;\n pendingResumeRef.current = null;\n\n const timeNow = getAudioContextTime();\n playbackStartTimeRef.current = timeNow;\n audioStartPositionRef.current = position;\n\n if (!audioInitializedRef.current) {\n await engineRef.current.init();\n audioInitializedRef.current = true;\n }\n\n engineRef.current.play(position);\n setIsPlaying(true);\n startAnimationLoop();\n }\n };\n\n resumePlayback().catch((err) => {\n console.warn('[waveform-playlist] Failed to resume playback after track change:', err);\n setIsPlaying(false);\n stopAnimationLoop();\n });\n }, [tracks, startAnimationLoop, stopAnimationLoop, getAudioContextTime]);\n\n // Playback controls\n const play = useCallback(\n async (startTime?: number, playDuration?: number) => {\n if (!engineRef.current) return;\n\n const actualStartTime = startTime ?? currentTimeRef.current;\n playStartPositionRef.current = actualStartTime;\n\n // Update currentTimeRef to match the actual start position\n // This ensures the animation loop starts from the correct position\n setCurrentTimeRefs(actualStartTime);\n\n // Stop existing playback and animation loop before restarting.\n // engine.stop() resets engine._currentTime to _playStartPosition (could be 0)\n // and sets _isPlaying=false. AnimatedPlayhead's RAF may fire during this\n // window and call engine.getCurrentTime() which would return 0.\n // The seek() immediately sets _currentTime to the intended start position,\n // preventing the cursor flash.\n engineRef.current.stop();\n engineRef.current.seek(actualStartTime);\n stopAnimationLoop();\n\n // Record timing for accurate position tracking using the adapter's AudioContext\n const startTimeNow = getAudioContextTime();\n playbackStartTimeRef.current = startTimeNow;\n audioStartPositionRef.current = actualStartTime;\n\n // Set playback end time if playing with duration (e.g., selection playback)\n playbackEndTimeRef.current =\n playDuration !== undefined ? actualStartTime + playDuration : null;\n\n // Don't set up playback complete callback for annotations\n // The animation loop handles stopping at annotation boundaries\n // This avoids callback timing issues when switching between annotations\n\n // Ensure AudioContext is resumed (requires user gesture, no-op after first call).\n // Guarded by ref so subsequent plays skip the await entirely.\n if (!audioInitializedRef.current) {\n await engineRef.current.init();\n audioInitializedRef.current = true;\n }\n\n const endTime = playDuration !== undefined ? actualStartTime + playDuration : undefined;\n try {\n engineRef.current.play(actualStartTime, endTime);\n } catch (err) {\n console.warn('[waveform-playlist] Playback failed to start:', err);\n stopAnimationLoop();\n return;\n }\n setIsPlaying(true);\n startAnimationLoop();\n },\n [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs, getAudioContextTime]\n );\n\n const pause = useCallback(() => {\n if (!engineRef.current) return;\n\n // Get current position from engine (auto-wraps at loop boundaries)\n const pauseTime = getPlaybackTime();\n\n engineRef.current.pause();\n setIsPlaying(false);\n stopAnimationLoop();\n\n // Update to the calculated pause position\n setCurrentTimeRefs(pauseTime);\n setCurrentTime(pauseTime);\n }, [stopAnimationLoop, getPlaybackTime, setCurrentTimeRefs]);\n\n const stop = useCallback(() => {\n if (!engineRef.current) return;\n\n engineRef.current.stop();\n setIsPlaying(false);\n stopAnimationLoop();\n\n // Return cursor to where playback started (Audacity-style)\n setCurrentTimeRefs(playStartPositionRef.current);\n setCurrentTime(playStartPositionRef.current);\n setActiveAnnotationId(null);\n }, [stopAnimationLoop, setActiveAnnotationId, setCurrentTimeRefs]);\n\n // Seek to a specific time - works whether playing or stopped\n const seekTo = useCallback(\n (time: number) => {\n // Clamp time to valid range\n const clampedTime = Math.max(0, Math.min(time, duration));\n\n // Update the current time state\n setCurrentTimeRefs(clampedTime);\n setCurrentTime(clampedTime);\n\n // If currently playing, restart at the new position.\n // play() handles stop+seek+restart internally.\n if (isPlaying && engineRef.current) {\n play(clampedTime);\n }\n },\n [duration, isPlaying, play, setCurrentTimeRefs]\n );\n\n // Track controls\n const setTrackMute = useCallback(\n (trackIndex: number, muted: boolean) => {\n const trackId = tracksRef.current[trackIndex]?.id;\n if (!trackId) return;\n\n const newStates = [...trackStates];\n newStates[trackIndex] = { ...newStates[trackIndex], muted };\n setTrackStates(newStates);\n\n if (engineRef.current) {\n engineRef.current.setTrackMute(trackId, muted);\n }\n },\n [trackStates]\n );\n\n const setTrackSolo = useCallback(\n (trackIndex: number, soloed: boolean) => {\n const trackId = tracksRef.current[trackIndex]?.id;\n if (!trackId) return;\n\n const newStates = [...trackStates];\n newStates[trackIndex] = { ...newStates[trackIndex], soloed };\n setTrackStates(newStates);\n\n if (engineRef.current) {\n engineRef.current.setTrackSolo(trackId, soloed);\n }\n },\n [trackStates]\n );\n\n const setTrackVolume = useCallback(\n (trackIndex: number, volume: number) => {\n const trackId = tracksRef.current[trackIndex]?.id;\n if (!trackId) return;\n\n const newStates = [...trackStates];\n newStates[trackIndex] = { ...newStates[trackIndex], volume };\n setTrackStates(newStates);\n\n if (engineRef.current) {\n engineRef.current.setTrackVolume(trackId, volume);\n }\n },\n [trackStates]\n );\n\n const setTrackPan = useCallback(\n (trackIndex: number, pan: number) => {\n const trackId = tracksRef.current[trackIndex]?.id;\n if (!trackId) return;\n\n const newStates = [...trackStates];\n newStates[trackIndex] = { ...newStates[trackIndex], pan };\n setTrackStates(newStates);\n\n if (engineRef.current) {\n engineRef.current.setTrackPan(trackId, pan);\n }\n },\n [trackStates]\n );\n\n // Selection — wraps hook setter with playback side-effects (provider concern).\n const setSelection = useCallback(\n (start: number, end: number) => {\n setSelectionEngine(start, end);\n setCurrentTimeRefs(start);\n setCurrentTime(start);\n\n if (isPlaying && engineRef.current) {\n // Stop + seek before restarting playback from selection start.\n // engine.stop() resets _currentTime and _isPlaying, creating a window\n // where getCurrentTime() returns wrong value. seek() immediately\n // corrects _currentTime so any RAF firing in between reads the\n // intended position. engine.play() then restarts everything.\n engineRef.current.stop();\n engineRef.current.seek(start);\n engineRef.current.play(start);\n }\n },\n [isPlaying, setSelectionEngine, setCurrentTimeRefs]\n );\n\n // Memoize setScrollContainer callback\n const setScrollContainer = useCallback((element: HTMLDivElement | null) => {\n scrollContainerRef.current = element;\n }, []);\n\n // Stable callback ref for onAnnotationsChange to avoid re-creating controls context\n const onAnnotationsChangeRef = useRef(onAnnotationsChange);\n onAnnotationsChangeRef.current = onAnnotationsChange;\n\n const setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>> = useCallback(\n (action) => {\n const updated = typeof action === 'function' ? action(annotationsRef.current) : action;\n if (!onAnnotationsChangeRef.current) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n 'waveform-playlist: setAnnotations was called but no onAnnotationsChange callback is provided. ' +\n 'Annotation edits will not persist. Pass onAnnotationsChange to WaveformPlaylistProvider to handle annotation updates.'\n );\n }\n return;\n }\n onAnnotationsChangeRef.current(updated);\n },\n []\n );\n\n const sampleRate = sampleRateRef.current;\n const timeScaleHeight = timescale ? 30 : 0;\n const minimumPlaylistHeight = tracks.length * waveHeight + timeScaleHeight;\n\n // Split context values for performance optimization\n // Animation context only re-renders consumers on discrete events\n // (play/pause/stop/seek), never during the animation loop itself\n\n const animationValue: PlaybackAnimationContextValue = useMemo(\n () => ({\n isPlaying,\n currentTime,\n currentTimeRef,\n visualTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n getPlaybackTime,\n getAudioContextTime,\n getLookAhead,\n getOutputLatency,\n registerFrameCallback,\n unregisterFrameCallback,\n }),\n [\n isPlaying,\n currentTime,\n currentTimeRef,\n visualTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n getPlaybackTime,\n getAudioContextTime,\n getLookAhead,\n getOutputLatency,\n registerFrameCallback,\n unregisterFrameCallback,\n ]\n );\n\n const stateValue: PlaylistStateContextValue = useMemo(\n () => ({\n continuousPlay,\n linkEndpoints,\n annotationsEditable,\n isAutomaticScroll,\n isLoopEnabled,\n annotations,\n activeAnnotationId,\n selectionStart,\n selectionEnd,\n selectedTrackId,\n loopStart,\n loopEnd,\n indefinitePlayback,\n fillViewport,\n canUndo,\n canRedo,\n }),\n [\n continuousPlay,\n linkEndpoints,\n annotationsEditable,\n isAutomaticScroll,\n isLoopEnabled,\n annotations,\n activeAnnotationId,\n selectionStart,\n selectionEnd,\n selectedTrackId,\n loopStart,\n loopEnd,\n indefinitePlayback,\n fillViewport,\n canUndo,\n canRedo,\n ]\n );\n\n const setCurrentTimeControl = useCallback(\n (time: number) => {\n setCurrentTimeRefs(time);\n setCurrentTime(time);\n },\n [setCurrentTimeRefs]\n );\n\n const setAutomaticScrollControl = useCallback((enabled: boolean) => {\n setIsAutomaticScroll(enabled);\n }, []);\n\n const controlsValue: PlaylistControlsContextValue = useMemo(\n () => ({\n // Playback controls\n play,\n pause,\n stop,\n seekTo,\n setCurrentTime: setCurrentTimeControl,\n\n // Track controls\n setTrackMute,\n setTrackSolo,\n setTrackVolume,\n setTrackPan,\n\n // Selection\n setSelection,\n setSelectedTrackId: setSelectedTrackIdControl,\n\n // Time format\n setTimeFormat,\n formatTime,\n\n // Zoom\n zoomIn: zoom.zoomIn,\n zoomOut: zoom.zoomOut,\n\n // Master volume\n setMasterVolume,\n\n // Automatic scroll\n setAutomaticScroll: setAutomaticScrollControl,\n setScrollContainer,\n scrollContainerRef,\n\n // Annotation controls\n setContinuousPlay,\n setLinkEndpoints,\n setAnnotationsEditable,\n setAnnotations,\n setActiveAnnotationId,\n\n // Loop controls\n setLoopEnabled,\n setLoopRegion,\n setLoopRegionFromSelection,\n clearLoopRegion,\n\n // Undo/redo\n undo,\n redo,\n\n // Recording\n setRecordingActive,\n }),\n [\n play,\n pause,\n stop,\n seekTo,\n setCurrentTimeControl,\n setTrackMute,\n setTrackSolo,\n setTrackVolume,\n setTrackPan,\n setSelection,\n setSelectedTrackIdControl,\n setTimeFormat,\n formatTime,\n zoom.zoomIn,\n zoom.zoomOut,\n setMasterVolume,\n setAutomaticScrollControl,\n setScrollContainer,\n scrollContainerRef,\n setContinuousPlay,\n setLinkEndpoints,\n setAnnotationsEditable,\n setAnnotations,\n setActiveAnnotationId,\n setLoopEnabled,\n setLoopRegion,\n setLoopRegionFromSelection,\n clearLoopRegion,\n undo,\n redo,\n setRecordingActive,\n ]\n );\n\n const dataValue: PlaylistDataContextValue = useMemo(\n () => ({\n duration,\n audioBuffers,\n peaksDataArray,\n trackStates,\n tracks,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n minimumPlaylistHeight,\n controls,\n playoutRef: engineRef,\n samplesPerPixel,\n timeFormat,\n masterVolume,\n canZoomIn: zoom.canZoomIn,\n canZoomOut: zoom.canZoomOut,\n barWidth,\n barGap,\n roundedBars,\n progressBarWidth,\n isReady,\n mono,\n isDraggingRef,\n onTracksChange,\n }),\n [\n duration,\n audioBuffers,\n peaksDataArray,\n trackStates,\n tracks,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n minimumPlaylistHeight,\n controls,\n engineRef,\n samplesPerPixel,\n timeFormat,\n masterVolume,\n zoom.canZoomIn,\n zoom.canZoomOut,\n barWidth,\n barGap,\n roundedBars,\n progressBarWidth,\n isReady,\n mono,\n isDraggingRef,\n onTracksChange,\n ]\n );\n\n // Merge user theme with default theme\n const mergedTheme = { ...defaultTheme, ...userTheme };\n\n return (\n <ThemeProvider theme={mergedTheme}>\n <PlaybackAnimationContext.Provider value={animationValue}>\n <PlaylistStateContext.Provider value={stateValue}>\n <PlaylistControlsContext.Provider value={controlsValue}>\n <PlaylistDataContext.Provider value={dataValue}>\n {children}\n </PlaylistDataContext.Provider>\n </PlaylistControlsContext.Provider>\n </PlaylistStateContext.Provider>\n </PlaybackAnimationContext.Provider>\n </ThemeProvider>\n );\n};\n\n// Individual hooks for each context - use these for optimal performance\n// Components only re-render when their specific context data changes\n\nexport const usePlaybackAnimation = () => {\n const context = useContext(PlaybackAnimationContext);\n if (!context) {\n throw new Error('usePlaybackAnimation must be used within WaveformPlaylistProvider');\n }\n return context;\n};\n\nexport const usePlaylistState = () => {\n const context = useContext(PlaylistStateContext);\n if (!context) {\n throw new Error('usePlaylistState must be used within WaveformPlaylistProvider');\n }\n return context;\n};\n\nexport const usePlaylistControls = () => {\n const context = useContext(PlaylistControlsContext);\n if (!context) {\n throw new Error('usePlaylistControls must be used within WaveformPlaylistProvider');\n }\n return context;\n};\n\nexport const usePlaylistData = () => {\n const context = useContext(PlaylistDataContext);\n if (!context) {\n throw new Error('usePlaylistData must be used within WaveformPlaylistProvider');\n }\n return context;\n};\n\n/**\n * Like {@link usePlaylistData} but returns `null` instead of throwing when there\n * is no WaveformPlaylistProvider ancestor. Use this in hooks/components that may\n * also run in the MediaElement path (MediaElementPlaylistProvider), where the\n * WebAudio playlist context is absent.\n */\nexport const usePlaylistDataOptional = () => useContext(PlaylistDataContext);\n","import type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport type { EffectsFunction, SoundFontCache } from '@waveform-playlist/playout';\n\n/** Options for {@link resolvePlayoutAdapter}. */\nexport interface ResolvePlayoutAdapterOptions {\n /** Consumer-supplied factory. When present, the default engine is never imported. */\n createAdapter?: () => PlayoutAdapter;\n /** Master effects function — forwarded to the default Tone.js adapter only. */\n effects?: EffectsFunction;\n /** SoundFont cache — forwarded to the default Tone.js adapter only. */\n soundFontCache?: SoundFontCache;\n /** Desired AudioContext sample rate — configures the global Tone context (default path only). */\n sampleRate?: number;\n}\n\nconst INSTALL_HINT =\n '@waveform-playlist/playout (and its peer `tone`) is required for the default WebAudio engine. ' +\n 'Install with: npm install @waveform-playlist/playout tone — or pass a custom `createAdapter`.';\n\n/**\n * Resolve a {@link PlayoutAdapter}. With `createAdapter`, returns its result and\n * never touches the default engine — so consumers with a custom adapter install\n * neither `@waveform-playlist/playout` nor `tone`. Otherwise dynamically imports\n * the Tone.js engine, rethrowing a friendly install hint (and console.warn-ing the\n * original error) when the optional peer is absent.\n */\nexport async function resolvePlayoutAdapter(\n opts: ResolvePlayoutAdapterOptions\n): Promise<PlayoutAdapter> {\n if (opts.createAdapter) {\n return opts.createAdapter();\n }\n\n let mod: typeof import('@waveform-playlist/playout');\n try {\n mod = await import('@waveform-playlist/playout');\n } catch (originalErr) {\n console.warn(\n '[waveform-playlist] @waveform-playlist/playout dynamic import failed: ' + String(originalErr)\n );\n throw new Error(INSTALL_HINT);\n }\n\n if (opts.sampleRate !== undefined) {\n // Non-fatal: a failed context configuration falls back to the default rate\n // (restores pre-#510 behavior — it was caught in the old mount-time initializer).\n try {\n mod.configureGlobalContext({ sampleRate: opts.sampleRate });\n } catch (ctxErr) {\n console.warn(\n '[waveform-playlist] configureGlobalContext failed (continuing with default rate): ' +\n String(ctxErr)\n );\n }\n }\n return mod.createToneAdapter({ effects: opts.effects, soundFontCache: opts.soundFontCache });\n}\n","/**\n * Waveform Data Loader\n *\n * Utilities for loading pre-computed waveform data in waveform-data.js format.\n * Supports both binary (.dat) and JSON formats from BBC's audiowaveform tool.\n */\n\nimport WaveformData from 'waveform-data';\nimport type { PeakData, Peaks } from '@waveform-playlist/core';\n\n/**\n * Load waveform data from a .dat or .json file\n *\n * @param src - URL to waveform data file (.dat or .json)\n * @returns WaveformData instance\n */\nexport async function loadWaveformData(src: string): Promise<WaveformData> {\n const response = await fetch(src);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch waveform data: ${response.statusText}`);\n }\n\n // Detect binary format from pathname (ignores query string and fragment)\n const { pathname } = new URL(src, globalThis.location?.href ?? 'http://localhost');\n const isBinary = pathname.toLowerCase().endsWith('.dat');\n\n if (isBinary) {\n const arrayBuffer = await response.arrayBuffer();\n return WaveformData.create(arrayBuffer);\n } else {\n const json = await response.json();\n return WaveformData.create(json);\n }\n}\n\n/**\n * Convert WaveformData to our internal Peaks format\n *\n * @param waveformData - WaveformData instance from waveform-data.js\n * @param channelIndex - Channel index (0 for mono/left, 1 for right)\n * @returns Peaks data with alternating min/max values, preserving original bit depth\n */\nexport function waveformDataToPeaks(\n waveformData: WaveformData,\n channelIndex: number = 0\n): { data: Int8Array | Int16Array; bits: 8 | 16; length: number; sampleRate: number } {\n const channel = waveformData.channel(channelIndex);\n const bits = waveformData.bits as 8 | 16;\n\n // Get the min/max arrays to determine length\n const minArray = channel.min_array();\n const maxArray = channel.max_array();\n const length = minArray.length;\n\n // Use appropriate typed array based on source file bit depth\n // 8-bit: values range from -128 to 127\n // 16-bit: values range from -32768 to 32767\n const peaks = bits === 8 ? new Int8Array(length * 2) : new Int16Array(length * 2);\n\n // Interleave min/max pairs\n for (let i = 0; i < length; i++) {\n peaks[i * 2] = minArray[i];\n peaks[i * 2 + 1] = maxArray[i];\n }\n\n return {\n data: peaks,\n bits,\n length,\n sampleRate: waveformData.sample_rate,\n };\n}\n\n/**\n * Load waveform data file and convert to Peaks format in one step\n *\n * @param src - URL to waveform data file (.dat or .json)\n * @param channelIndex - Channel index (default: 0)\n * @returns Peaks data ready for rendering\n */\nexport async function loadPeaksFromWaveformData(\n src: string,\n channelIndex: number = 0\n): Promise<{ data: Int8Array | Int16Array; bits: 8 | 16; length: number; sampleRate: number }> {\n const waveformData = await loadWaveformData(src);\n return waveformDataToPeaks(waveformData, channelIndex);\n}\n\n/**\n * Get metadata from waveform data file without converting to peaks\n *\n * @param src - URL to waveform data file\n * @returns Metadata (sample rate, channels, duration, bits, etc.)\n */\nexport async function getWaveformDataMetadata(src: string): Promise<{\n sampleRate: number;\n channels: number;\n duration: number;\n samplesPerPixel: number;\n length: number;\n bits: 8 | 16;\n}> {\n const waveformData = await loadWaveformData(src);\n\n return {\n sampleRate: waveformData.sample_rate,\n channels: waveformData.channels,\n duration: waveformData.duration,\n samplesPerPixel: waveformData.scale,\n length: waveformData.length,\n bits: waveformData.bits as 8 | 16,\n };\n}\n\n/**\n * Slice and resample WaveformData with aligned source indices.\n *\n * Handles three cases:\n * 1. Scale differs + offset/duration given → aligned slice then resample\n * 2. Same scale + offset/duration given → direct slice\n * 3. No offset/duration → full-file resample (or identity)\n *\n * @returns Processed WaveformData ready for peak extraction\n */\nfunction sliceAndResample(\n waveformData: WaveformData,\n samplesPerPixel: number,\n offsetSamples?: number,\n durationSamples?: number\n): WaveformData | null {\n let processedData = waveformData;\n\n if (offsetSamples !== undefined && durationSamples !== undefined) {\n if (processedData.scale !== samplesPerPixel) {\n const sourceScale = waveformData.scale;\n const ratio = samplesPerPixel / sourceScale;\n\n // Compute clip's pixel range in target-scale bins\n const targetStart = Math.floor(offsetSamples / samplesPerPixel);\n const targetEnd = Math.ceil((offsetSamples + durationSamples) / samplesPerPixel);\n\n // Convert to source indices using floor/ceil for inclusive slicing.\n // For integer ratios (power-of-two scales), this gives exact alignment.\n // For non-integer ratios, floor/ceil ensures we always include all source\n // bins that contribute to the target bins — never underrepresenting peaks.\n const sourceStart = Math.floor(targetStart * ratio);\n const sourceEnd = Math.min(waveformData.length, Math.ceil(targetEnd * ratio));\n\n if (sourceStart >= sourceEnd) {\n return null; // Clip offset beyond waveform data\n }\n\n processedData = processedData.slice({\n startIndex: sourceStart,\n endIndex: sourceEnd,\n });\n processedData = processedData.resample({ scale: samplesPerPixel });\n } else {\n // No resampling needed — slice directly at base scale\n const startIndex = Math.floor(offsetSamples / samplesPerPixel);\n const endIndex = Math.ceil((offsetSamples + durationSamples) / samplesPerPixel);\n processedData = processedData.slice({ startIndex, endIndex });\n }\n } else if (processedData.scale !== samplesPerPixel) {\n processedData = processedData.resample({ scale: samplesPerPixel });\n }\n\n return processedData;\n}\n\n/**\n * Extract peaks from a WaveformData object at a specific scale (samplesPerPixel)\n * and optionally slice to a sample range.\n *\n * @param waveformData - WaveformData instance from waveform-data.js\n * @param samplesPerPixel - Target samples per pixel (will resample if different)\n * @param channelIndex - Channel index (default: 0)\n * @param offsetSamples - Optional start offset in samples (for clip trimming)\n * @param durationSamples - Optional duration in samples (for clip trimming)\n * @returns Peaks data ready for rendering\n */\nexport function extractPeaksFromWaveformData(\n waveformData: WaveformData,\n samplesPerPixel: number,\n channelIndex: number = 0,\n offsetSamples?: number,\n durationSamples?: number\n): { data: Int8Array | Int16Array; bits: 8 | 16; length: number } {\n const processedData = sliceAndResample(\n waveformData,\n samplesPerPixel,\n offsetSamples,\n durationSamples\n );\n\n if (processedData === null) {\n const bits = waveformData.bits as 8 | 16;\n const emptyPeaks = bits === 8 ? new Int8Array(0) : new Int16Array(0);\n return { data: emptyPeaks, bits, length: 0 };\n }\n\n // Convert to our peaks format\n const channel = processedData.channel(channelIndex);\n const bits = processedData.bits as 8 | 16;\n const minArray = channel.min_array();\n const maxArray = channel.max_array();\n const length = minArray.length;\n\n const peaks = bits === 8 ? new Int8Array(length * 2) : new Int16Array(length * 2);\n\n for (let i = 0; i < length; i++) {\n peaks[i * 2] = minArray[i];\n peaks[i * 2 + 1] = maxArray[i];\n }\n\n return { data: peaks, bits, length };\n}\n\n/**\n * Extract peaks from a WaveformData object, handling ALL channels, mono merging,\n * slicing, and resampling.\n *\n * Bit depth is determined by the WaveformData source — all typed arrays match\n * the source's bit depth for consistent data/metadata.\n *\n * @param waveformData - WaveformData instance (should be generated with split_channels: true)\n * @param samplesPerPixel - Target samples per pixel\n * @param isMono - Whether to merge channels to mono\n * @param offsetSamples - Optional start offset in samples (for clip trimming)\n * @param durationSamples - Optional duration in samples (for clip trimming)\n * @returns PeakData matching the interface from @waveform-playlist/core\n */\nexport function extractPeaksFromWaveformDataFull(\n waveformData: WaveformData,\n samplesPerPixel: number,\n isMono: boolean,\n offsetSamples?: number,\n durationSamples?: number\n): PeakData {\n const processedData = sliceAndResample(\n waveformData,\n samplesPerPixel,\n offsetSamples,\n durationSamples\n );\n\n if (processedData === null) {\n const bits = waveformData.bits as 8 | 16;\n return { length: 0, data: [], bits };\n }\n\n const numChannels = processedData.channels;\n const bits = processedData.bits as 8 | 16;\n\n // Extract peaks for all channels\n const channelPeaks: Peaks[] = [];\n for (let c = 0; c < numChannels; c++) {\n const channel = processedData.channel(c);\n const minArray = channel.min_array();\n const maxArray = channel.max_array();\n const len = minArray.length;\n\n const peaks: Peaks = bits === 8 ? new Int8Array(len * 2) : new Int16Array(len * 2);\n\n for (let i = 0; i < len; i++) {\n peaks[i * 2] = minArray[i];\n peaks[i * 2 + 1] = maxArray[i];\n }\n channelPeaks.push(peaks);\n }\n\n // Handle mono merging (same algorithm as makeMono in webaudio-peaks)\n if (isMono && channelPeaks.length > 1) {\n const weight = 1 / channelPeaks.length;\n const numPeaks = channelPeaks[0].length / 2;\n const monoPeaks: Peaks =\n bits === 8 ? new Int8Array(numPeaks * 2) : new Int16Array(numPeaks * 2);\n\n for (let i = 0; i < numPeaks; i++) {\n let min = 0;\n let max = 0;\n for (let c = 0; c < channelPeaks.length; c++) {\n min += weight * channelPeaks[c][i * 2];\n max += weight * channelPeaks[c][i * 2 + 1];\n }\n monoPeaks[i * 2] = min;\n monoPeaks[i * 2 + 1] = max;\n }\n\n return {\n length: numPeaks,\n data: [monoPeaks],\n bits,\n };\n }\n\n const peakLength = channelPeaks.length > 0 ? channelPeaks[0].length / 2 : 0;\n\n return {\n length: peakLength,\n data: channelPeaks,\n bits,\n };\n}\n","import type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport type { SoundFontCache } from '@waveform-playlist/playout';\n\n/** Adapters that accept a SoundFontCache (e.g. the Tone.js adapter). */\ninterface SoundFontCapableAdapter {\n setSoundFontCache(cache: SoundFontCache | undefined): void;\n}\n\n/**\n * Structural check for soundfont support — avoids a runtime import of\n * `@waveform-playlist/playout`. `isToneAdapter` from playout does the same\n * structural check (via optional chaining); our local `supportsSoundFont`\n * adds an explicit `adapter != null` guard. Keeping it structural lets the\n * core barrel stay engine-free (#510) and works for any custom adapter that\n * exposes `setSoundFontCache`.\n */\nfunction supportsSoundFont(\n adapter: PlayoutAdapter | null\n): adapter is PlayoutAdapter & SoundFontCapableAdapter {\n return (\n adapter != null &&\n typeof (adapter as Partial<SoundFontCapableAdapter>).setSoundFontCache === 'function'\n );\n}\n\n/**\n * Forward a (possibly late-loaded or swapped) SoundFontCache to the live\n * adapter. Safe no-op when the adapter is absent or doesn't support\n * soundfonts. The adapter itself skips MIDI tracks whose routing is\n * unchanged, so redundant calls (e.g. on mount) cause no rebuild churn.\n */\nexport function syncSoundFontCacheToAdapter(\n adapter: PlayoutAdapter | null,\n cache: SoundFontCache | undefined\n): void {\n if (!supportsSoundFont(adapter)) return;\n adapter.setSoundFontCache(cache);\n}\n","import { useState } from 'react';\nimport {\n formatTime as formatTimeUtil,\n parseTime as parseTimeUtil,\n type TimeFormat,\n} from '@waveform-playlist/ui-components';\n\nexport interface TimeFormatControls {\n timeFormat: TimeFormat;\n setTimeFormat: (format: TimeFormat) => void;\n formatTime: (seconds: number) => string;\n parseTime: (timeString: string) => number;\n}\n\n/**\n * Hook to manage time format state\n *\n * @example\n * ```tsx\n * const { timeFormat, setTimeFormat, formatTime, parseTime } = useTimeFormat();\n *\n * <TimeFormatSelect\n * value={timeFormat}\n * onChange={setTimeFormat}\n * />\n * <span>{formatTime(currentTime)}</span>\n * <input onChange={(e) => seekTo(parseTime(e.target.value))} />\n * ```\n */\nexport function useTimeFormat(): TimeFormatControls {\n const [timeFormat, setTimeFormat] = useState<TimeFormat>('hh:mm:ss.uuu');\n\n const formatTime = (seconds: number) => {\n return formatTimeUtil(seconds, timeFormat);\n };\n\n const parseTime = (timeString: string) => {\n return parseTimeUtil(timeString, timeFormat);\n };\n\n return {\n timeFormat,\n setTimeFormat,\n formatTime,\n parseTime,\n };\n}\n","import { useState, useCallback, useRef, startTransition, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface ZoomControls {\n samplesPerPixel: number;\n zoomIn: () => void;\n zoomOut: () => void;\n canZoomIn: boolean;\n canZoomOut: boolean;\n}\n\nexport interface UseZoomControlsProps {\n engineRef: RefObject<PlaylistEngine | null>;\n initialSamplesPerPixel: number;\n}\n\n/**\n * Hook for managing zoom controls via PlaylistEngine delegation.\n *\n * zoomIn/zoomOut delegate to the engine. State is mirrored back from\n * the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n *\n * samplesPerPixel updates use startTransition so React treats them as\n * non-urgent — during playback, animation RAF callbacks interleave\n * with the zoom re-render instead of being blocked.\n */\nexport function useZoomControls({\n engineRef,\n initialSamplesPerPixel,\n}: UseZoomControlsProps): ZoomControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [samplesPerPixel, setSamplesPerPixel] = useState(initialSamplesPerPixel);\n const [canZoomIn, setCanZoomIn] = useState(true);\n const [canZoomOut, setCanZoomOut] = useState(true);\n\n // Internal refs for statechange guards — prevent redundant setState\n // calls during high-frequency engine events (clip drags, play/pause).\n const canZoomInRef = useRef(true);\n const canZoomOutRef = useRef(true);\n const samplesPerPixelRef = useRef(initialSamplesPerPixel);\n\n const zoomIn = useCallback(() => {\n engineRef.current?.zoomIn();\n }, [engineRef]);\n\n const zoomOut = useCallback(() => {\n engineRef.current?.zoomOut();\n }, [engineRef]);\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.samplesPerPixel !== samplesPerPixelRef.current) {\n samplesPerPixelRef.current = state.samplesPerPixel;\n startTransition(() => {\n setSamplesPerPixel(state.samplesPerPixel);\n });\n }\n if (state.canZoomIn !== canZoomInRef.current) {\n canZoomInRef.current = state.canZoomIn;\n setCanZoomIn(state.canZoomIn);\n }\n if (state.canZoomOut !== canZoomOutRef.current) {\n canZoomOutRef.current = state.canZoomOut;\n setCanZoomOut(state.canZoomOut);\n }\n }, []);\n\n return {\n samplesPerPixel,\n zoomIn,\n zoomOut,\n canZoomIn,\n canZoomOut,\n onEngineState,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseMasterVolumeProps {\n engineRef: RefObject<PlaylistEngine | null>;\n initialVolume?: number; // 0-1.0 (linear gain, consistent with Web Audio API)\n}\n\nexport interface MasterVolumeControls {\n masterVolume: number;\n setMasterVolume: (volume: number) => void;\n /** Ref holding the current masterVolume for seeding a fresh engine. */\n masterVolumeRef: React.RefObject<number>;\n}\n\n/**\n * Hook for managing master volume via PlaylistEngine delegation.\n *\n * setMasterVolume delegates to the engine. State is mirrored back from\n * the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n */\nexport function useMasterVolume({\n engineRef,\n initialVolume = 1.0,\n}: UseMasterVolumeProps): MasterVolumeControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [masterVolume, setMasterVolumeState] = useState(initialVolume);\n\n // Internal ref for statechange guard + engine seeding on rebuild.\n const masterVolumeRef = useRef(initialVolume);\n\n const setMasterVolume = useCallback(\n (volume: number) => {\n if (engineRef.current) {\n // Engine exists — delegate; statechange will update ref + React state.\n engineRef.current.setMasterVolume(volume);\n } else {\n // No engine yet — persist locally so loadAudio() seeds correctly.\n masterVolumeRef.current = volume;\n setMasterVolumeState(volume);\n }\n },\n [engineRef]\n );\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.masterVolume !== masterVolumeRef.current) {\n masterVolumeRef.current = state.masterVolume;\n setMasterVolumeState(state.masterVolume);\n }\n }, []);\n\n return {\n masterVolume,\n setMasterVolume,\n masterVolumeRef,\n onEngineState,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseSelectionStateProps {\n engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface SelectionControls {\n selectionStart: number;\n selectionEnd: number;\n setSelection: (start: number, end: number) => void;\n selectionStartRef: React.RefObject<number>;\n selectionEndRef: React.RefObject<number>;\n}\n\n/**\n * Hook for managing selection state via PlaylistEngine delegation.\n *\n * setSelection delegates to the engine. State is mirrored back from\n * the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n *\n * Note: The provider wraps this setter with playback side-effects\n * (updating currentTime, restarting playback). This hook only handles\n * the engine delegation + state mirroring.\n */\nexport function useSelectionState({ engineRef }: UseSelectionStateProps): SelectionControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [selectionStart, setSelectionStartState] = useState(0);\n const [selectionEnd, setSelectionEndState] = useState(0);\n\n // Internal refs for statechange guard + engine seeding on rebuild.\n const selectionStartRef = useRef(0);\n const selectionEndRef = useRef(0);\n\n const setSelection = useCallback(\n (start: number, end: number) => {\n engineRef.current?.setSelection(start, end);\n },\n [engineRef]\n );\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.selectionStart !== selectionStartRef.current) {\n selectionStartRef.current = state.selectionStart;\n setSelectionStartState(state.selectionStart);\n }\n if (state.selectionEnd !== selectionEndRef.current) {\n selectionEndRef.current = state.selectionEnd;\n setSelectionEndState(state.selectionEnd);\n }\n }, []);\n\n return {\n selectionStart,\n selectionEnd,\n setSelection,\n selectionStartRef,\n selectionEndRef,\n onEngineState,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseLoopStateProps {\n engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface LoopControls {\n isLoopEnabled: boolean;\n loopStart: number;\n loopEnd: number;\n setLoopEnabled: (value: boolean) => void;\n setLoopRegion: (start: number, end: number) => void;\n clearLoopRegion: () => void;\n isLoopEnabledRef: React.RefObject<boolean>;\n loopStartRef: React.RefObject<number>;\n loopEndRef: React.RefObject<number>;\n}\n\n/**\n * Hook for managing loop state via PlaylistEngine delegation.\n *\n * setLoopEnabled, setLoopRegion, and clearLoopRegion delegate to the\n * engine. State is mirrored back from the engine via onEngineState(),\n * which the provider's statechange handler calls on every engine event.\n */\nexport function useLoopState({ engineRef }: UseLoopStateProps): LoopControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [isLoopEnabled, setIsLoopEnabledState] = useState(false);\n const [loopStart, setLoopStartState] = useState(0);\n const [loopEnd, setLoopEndState] = useState(0);\n\n // Internal refs for statechange guard + animation loop reads.\n const isLoopEnabledRef = useRef(false);\n const loopStartRef = useRef(0);\n const loopEndRef = useRef(0);\n\n const setLoopEnabled = useCallback(\n (value: boolean) => {\n engineRef.current?.setLoopEnabled(value);\n },\n [engineRef]\n );\n\n const setLoopRegion = useCallback(\n (start: number, end: number) => {\n engineRef.current?.setLoopRegion(start, end);\n },\n [engineRef]\n );\n\n const clearLoopRegion = useCallback(() => {\n engineRef.current?.setLoopRegion(0, 0);\n }, [engineRef]);\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.isLoopEnabled !== isLoopEnabledRef.current) {\n isLoopEnabledRef.current = state.isLoopEnabled;\n setIsLoopEnabledState(state.isLoopEnabled);\n }\n if (state.loopStart !== loopStartRef.current) {\n loopStartRef.current = state.loopStart;\n setLoopStartState(state.loopStart);\n }\n if (state.loopEnd !== loopEndRef.current) {\n loopEndRef.current = state.loopEnd;\n setLoopEndState(state.loopEnd);\n }\n }, []);\n\n return {\n isLoopEnabled,\n loopStart,\n loopEnd,\n setLoopEnabled,\n setLoopRegion,\n clearLoopRegion,\n isLoopEnabledRef,\n loopStartRef,\n loopEndRef,\n onEngineState,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseSelectedTrackProps {\n engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface SelectedTrackControls {\n selectedTrackId: string | null;\n setSelectedTrackId: (trackId: string | null) => void;\n}\n\n/**\n * Hook for managing selected track state via PlaylistEngine delegation.\n *\n * setSelectedTrackId delegates to the engine's selectTrack method.\n * State is mirrored back from the engine via onEngineState(), which\n * the provider's statechange handler calls on every engine event.\n */\nexport function useSelectedTrack({ engineRef }: UseSelectedTrackProps): SelectedTrackControls & {\n onEngineState: (state: EngineState) => void;\n selectedTrackIdRef: React.RefObject<string | null>;\n} {\n const [selectedTrackId, setSelectedTrackIdState] = useState<string | null>(null);\n\n // Internal ref for statechange guard.\n const selectedTrackIdRef = useRef<string | null>(null);\n\n const setSelectedTrackId = useCallback(\n (trackId: string | null) => {\n engineRef.current?.selectTrack(trackId);\n },\n [engineRef]\n );\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.selectedTrackId !== selectedTrackIdRef.current) {\n selectedTrackIdRef.current = state.selectedTrackId;\n setSelectedTrackIdState(state.selectedTrackId);\n }\n }, []);\n\n return {\n selectedTrackId,\n setSelectedTrackId,\n onEngineState,\n selectedTrackIdRef,\n };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseUndoStateProps {\n engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface UndoControls {\n canUndo: boolean;\n canRedo: boolean;\n undo: () => void;\n redo: () => void;\n}\n\n/**\n * Hook for managing undo/redo state via PlaylistEngine delegation.\n *\n * undo/redo delegate to the engine. canUndo/canRedo are mirrored back\n * from the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n *\n * No refs are exposed for engine seeding — undo history intentionally\n * resets on engine rebuild (setTracks calls clearHistory).\n */\nexport function useUndoState({ engineRef }: UseUndoStateProps): UndoControls & {\n onEngineState: (state: EngineState) => void;\n} {\n const [canUndo, setCanUndo] = useState(false);\n const [canRedo, setCanRedo] = useState(false);\n\n // Internal refs for statechange guard (same pattern as useSelectionState etc.)\n const canUndoRef = useRef(false);\n const canRedoRef = useRef(false);\n\n const undo = useCallback(() => {\n if (!engineRef.current) {\n console.warn('[waveform-playlist] undo: engine not ready, call ignored');\n return;\n }\n engineRef.current.undo();\n }, [engineRef]);\n\n const redo = useCallback(() => {\n if (!engineRef.current) {\n console.warn('[waveform-playlist] redo: engine not ready, call ignored');\n return;\n }\n engineRef.current.redo();\n }, [engineRef]);\n\n // Called by the provider's statechange handler to mirror engine state.\n const onEngineState = useCallback((state: EngineState) => {\n if (state.canUndo !== canUndoRef.current) {\n canUndoRef.current = state.canUndo;\n setCanUndo(state.canUndo);\n }\n if (state.canRedo !== canRedoRef.current) {\n canRedoRef.current = state.canRedo;\n setCanRedo(state.canRedo);\n }\n }, []);\n\n return {\n canUndo,\n canRedo,\n undo,\n redo,\n onEngineState,\n };\n}\n","import React from 'react';\nimport type {\n DragStartEvent as DragStartCallback,\n DragMoveEvent as DragMoveCallback,\n DragEndEvent as DragEndCallback,\n} from '@dnd-kit/abstract';\nimport type { ClipTrack } from '@waveform-playlist/core';\nimport type { PlaylistEngine } from '@waveform-playlist/engine';\nimport { calculateBoundaryTrim } from '../utils/boundaryTrim';\nimport { usePlaylistData } from '../WaveformPlaylistContext';\n\ninterface UseClipDragHandlersOptions {\n tracks: ClipTrack[];\n onTracksChange: (tracks: ClipTrack[]) => void;\n samplesPerPixel: number;\n engineRef: React.RefObject<PlaylistEngine | null>;\n /** Ref toggled during boundary trim drags. When true, the provider's loadAudio\n * skips engine rebuilds so engine keeps original clip positions. On drag end,\n * engine.trimClip() commits the final delta. Obtain from usePlaylistData(). */\n isDraggingRef: React.MutableRefObject<boolean>;\n /** Optional function that snaps a sample position to the nearest grid position.\n * Used for boundary trim snapping (move snapping is handled by the SnapToGridModifier). */\n snapSamplePosition?: (samplePosition: number) => number;\n}\n\n/**\n * Custom hook for handling clip drag operations (movement and trimming)\n *\n * Provides drag handlers for use with @dnd-kit/react DragDropProvider.\n * Handles both clip movement (dragging entire clips) and boundary trimming (adjusting clip edges).\n *\n * Collision detection for clip moves is handled by `ClipCollisionModifier` (passed to DragDropProvider).\n *\n * **Move:** `onDragEnd` delegates to `engine.moveClip()` in one shot.\n *\n * **Trim:** `onDragMove` updates React state per-frame via `onTracksChange` for smooth\n * visual feedback (using cumulative deltas from the original clip snapshot). `isDraggingRef`\n * prevents loadAudio from rebuilding the engine during the drag, so the engine keeps the\n * original clip positions. On drag end, `engine.trimClip()` commits the final delta.\n *\n * @example\n * ```tsx\n * const { onDragStart, onDragMove, onDragEnd } = useClipDragHandlers({\n * tracks,\n * onTracksChange: setTracks,\n * samplesPerPixel,\n * engineRef: playoutRef,\n * isDraggingRef,\n * });\n *\n * return (\n * <DragDropProvider\n * onDragStart={onDragStart}\n * onDragMove={onDragMove}\n * onDragEnd={onDragEnd}\n * modifiers={[RestrictToHorizontalAxis, ClipCollisionModifier.configure({ tracks, samplesPerPixel })]}\n * >\n * <Waveform showClipHeaders={true} />\n * </DragDropProvider>\n * );\n * ```\n */\nexport function useClipDragHandlers({\n tracks,\n onTracksChange,\n samplesPerPixel,\n engineRef,\n isDraggingRef,\n snapSamplePosition,\n}: UseClipDragHandlersOptions) {\n const { sampleRate } = usePlaylistData();\n // Store snap function in ref so onDragMove doesn't need it as a dependency\n const snapSamplePositionRef = React.useRef(snapSamplePosition);\n snapSamplePositionRef.current = snapSamplePosition;\n // Store original clip state when drag starts (for cumulative delta application)\n const originalClipStateRef = React.useRef<{\n offsetSamples: number;\n durationSamples: number;\n startSample: number;\n } | null>(null);\n\n // Store the last boundary trim delta (samples) so onDragEnd uses the same value\n // as the last onDragMove visual update, avoiding stale-position mismatches.\n const lastBoundaryDeltaRef = React.useRef(0);\n\n const onDragStart = React.useCallback(\n (event: Parameters<DragStartCallback>[0]) => {\n const data = event.operation.source?.data as\n | {\n boundary?: 'left' | 'right';\n trackIndex: number;\n clipIndex: number;\n }\n | undefined;\n\n if (!data) return;\n\n // Only store state for boundary trimming operations\n if (!data.boundary) {\n originalClipStateRef.current = null;\n // Group move into one undo step\n if (engineRef.current) {\n engineRef.current.beginTransaction();\n } else {\n console.warn(\n '[waveform-playlist] onDragStart: engine not ready, move will not be grouped for undo'\n );\n }\n return;\n }\n\n const track = tracks[data.trackIndex];\n const clip = track?.clips[data.clipIndex];\n\n if (clip) {\n // Store original clip state for cumulative delta application\n originalClipStateRef.current = {\n offsetSamples: clip.offsetSamples,\n durationSamples: clip.durationSamples,\n startSample: clip.startSample,\n };\n // Signal provider to skip loadAudio rebuilds during the drag\n isDraggingRef.current = true;\n // Group the drag into one undo step\n if (engineRef.current) {\n engineRef.current.beginTransaction();\n } else {\n console.warn(\n '[waveform-playlist] onDragStart: engine not ready, trim will not be grouped for undo'\n );\n }\n }\n },\n [tracks, isDraggingRef, engineRef]\n );\n\n const onDragMove = React.useCallback(\n (event: Parameters<DragMoveCallback>[0]) => {\n const data = event.operation.source?.data as\n | {\n boundary?: 'left' | 'right';\n trackIndex: number;\n clipIndex: number;\n }\n | undefined;\n\n if (!data) return;\n\n // Only update for boundary trimming operations (not clip movement)\n if (!data.boundary) return;\n\n // Need original clip state to apply cumulative delta\n if (!originalClipStateRef.current) return;\n\n const { boundary, trackIndex, clipIndex } = data;\n\n // The dragmove event is dispatched BEFORE position.current is updated (happens\n // in a microtask), so the snapshot's position.current is stale. Use event.to\n // (the pointer's current coordinates from the sensor) for the correct position.\n const currentX = event.to?.x ?? event.operation.position.current.x;\n const rawDeltaX = currentX - event.operation.position.initial.x;\n const sampleDelta = rawDeltaX * samplesPerPixel;\n\n // Get original clip state (stored on drag start)\n const originalClip = originalClipStateRef.current;\n const snapFn = snapSamplePositionRef.current;\n\n // Track the effective delta after snapping (for engine.trimClip on drag end)\n let effectiveDelta = sampleDelta;\n\n // Update tracks in real-time during drag\n const newTracks = tracks.map((track, tIdx) => {\n if (tIdx !== trackIndex) return track;\n\n const sortedClips = [...track.clips].sort((a, b) => a.startSample - b.startSample);\n const sortedIndex = sortedClips.findIndex((clip) => clip === track.clips[clipIndex]);\n\n const newClips = track.clips.map((clip, cIdx) => {\n if (cIdx !== clipIndex) return clip;\n\n let trimResult = calculateBoundaryTrim({\n originalClip,\n clip,\n pixelDelta: rawDeltaX,\n samplesPerPixel,\n sampleRate,\n boundary,\n sortedClips,\n sortedIndex,\n });\n\n // Snap the boundary position to the grid if a snap function is provided\n if (snapFn) {\n if (boundary === 'left') {\n const snappedStart = snapFn(trimResult.startSample);\n const delta = snappedStart - trimResult.startSample;\n trimResult = {\n startSample: snappedStart,\n durationSamples: trimResult.durationSamples - delta,\n offsetSamples: trimResult.offsetSamples + delta,\n };\n // Effective delta = how far the start moved from original\n effectiveDelta = snappedStart - originalClip.startSample;\n } else {\n const endSample = trimResult.startSample + trimResult.durationSamples;\n const snappedEnd = snapFn(endSample);\n trimResult = {\n ...trimResult,\n durationSamples: snappedEnd - trimResult.startSample,\n };\n // Effective delta = how much the duration changed from original\n effectiveDelta = trimResult.durationSamples - originalClip.durationSamples;\n }\n }\n\n return {\n ...clip,\n startSample: trimResult.startSample,\n durationSamples: trimResult.durationSamples,\n offsetSamples: trimResult.offsetSamples,\n };\n });\n\n return { ...track, clips: newClips };\n });\n\n lastBoundaryDeltaRef.current = effectiveDelta;\n onTracksChange(newTracks);\n },\n [tracks, onTracksChange, samplesPerPixel, sampleRate]\n );\n\n const onDragEnd = React.useCallback(\n (event: Parameters<DragEndCallback>[0]) => {\n // Handle canceled drags (focus loss, Escape key, component unmount).\n // Without this, isDraggingRef stays true and loadAudio skips rebuilds permanently.\n if (event.canceled) {\n // Revert React state for boundary trims — onDragMove updated tracks per-frame,\n // but the engine still has original positions (isDraggingRef blocked rebuilds).\n // For clip moves, originalClipStateRef is null so this block is skipped —\n // no React state revert needed because the Feedback plugin handles the visual\n // position (CSS translate removed on cancel) and engine was never mutated.\n if (originalClipStateRef.current) {\n const cancelData = event.operation.source?.data as\n | { trackIndex: number; clipIndex: number }\n | undefined;\n if (cancelData) {\n const { trackIndex, clipIndex } = cancelData;\n const original = originalClipStateRef.current;\n const revertedTracks = tracks.map((track, tIdx) => {\n if (tIdx !== trackIndex) return track;\n const newClips = track.clips.map((clip, cIdx) => {\n if (cIdx !== clipIndex) return clip;\n return {\n ...clip,\n offsetSamples: original.offsetSamples,\n durationSamples: original.durationSamples,\n startSample: original.startSample,\n };\n });\n return { ...track, clips: newClips };\n });\n onTracksChange(revertedTracks);\n }\n }\n isDraggingRef.current = false;\n originalClipStateRef.current = null;\n lastBoundaryDeltaRef.current = 0;\n // Cancel aborts the transaction — no undo step pushed\n engineRef.current?.abortTransaction();\n return;\n }\n\n const data = event.operation.source?.data as\n | {\n clipId: string;\n trackIndex: number;\n boundary?: 'left' | 'right';\n }\n | undefined;\n\n if (!data) {\n // Transaction was opened in onDragStart — abort to prevent leak\n isDraggingRef.current = false;\n engineRef.current?.abortTransaction();\n return;\n }\n\n const { trackIndex, clipId, boundary } = data;\n\n // Boundary trims: use the last delta stored by onDragMove (avoids stale snapshot position).\n // Clip moves: use transform.x (modifier-constrained, cached as plain object in snapshot).\n const sampleDelta = boundary\n ? lastBoundaryDeltaRef.current\n : event.operation.transform.x * samplesPerPixel;\n\n const trackId = tracks[trackIndex]?.id;\n\n // Boundary trim: onDragMove updated React state per-frame for visuals.\n // isDraggingRef prevented loadAudio from rebuilding the engine, so the\n // engine still has the original (pre-drag) clip positions. Commit the\n // final delta via engine.trimClip() so the adapter has correct positions.\n if (boundary) {\n isDraggingRef.current = false;\n if (!trackId) {\n console.warn(\n `[waveform-playlist] onDragEnd: track at index ${trackIndex} not found — trim not synced to adapter`\n );\n engineRef.current?.abortTransaction();\n } else if (!engineRef.current) {\n console.warn('[waveform-playlist] engineRef is null — trim not synced to adapter');\n } else {\n engineRef.current.trimClip(trackId, clipId, boundary, Math.floor(sampleDelta));\n engineRef.current.commitTransaction();\n }\n originalClipStateRef.current = null;\n lastBoundaryDeltaRef.current = 0;\n return;\n }\n\n // Clip move: delegate to engine in one shot\n if (!trackId) {\n console.warn(\n `[waveform-playlist] onDragEnd: track at index ${trackIndex} not found — move not synced to adapter`\n );\n engineRef.current?.abortTransaction();\n } else if (!engineRef.current) {\n console.warn('[waveform-playlist] engineRef is null — move not synced to adapter');\n } else {\n engineRef.current.moveClip(trackId, clipId, Math.floor(sampleDelta));\n engineRef.current.commitTransaction();\n }\n },\n [tracks, onTracksChange, samplesPerPixel, engineRef, isDraggingRef]\n );\n\n return {\n onDragStart,\n onDragMove,\n onDragEnd,\n };\n}\n","import type { AudioClip } from '@waveform-playlist/core';\nimport { constrainBoundaryTrim } from '@waveform-playlist/engine';\n\n/**\n * Result of a boundary trim calculation — the new clip dimensions\n * after dragging a left or right boundary by a given pixel delta.\n */\nexport interface BoundaryTrimResult {\n startSample: number;\n durationSamples: number;\n offsetSamples: number;\n}\n\nexport interface CalculateBoundaryTrimParams {\n /** The original clip state captured at drag start */\n originalClip: Pick<AudioClip, 'startSample' | 'durationSamples' | 'offsetSamples'>;\n /** The current clip with full AudioClip shape (used for constraint calculation) */\n clip: AudioClip;\n /** Raw pixel delta from drag start (positive = rightward) */\n pixelDelta: number;\n /** Samples per pixel (zoom level) */\n samplesPerPixel: number;\n /** Audio sample rate (used to compute minimum duration) */\n sampleRate: number;\n /** Which boundary is being dragged */\n boundary: 'left' | 'right';\n /** Clips on the same track, sorted by startSample (for collision detection) */\n sortedClips: AudioClip[];\n /** Index of the clip being trimmed within sortedClips */\n sortedIndex: number;\n}\n\n/**\n * Pure function that calculates new clip dimensions after a boundary trim drag.\n *\n * Converts a pixel delta to sample delta, constrains it via the engine's\n * `constrainBoundaryTrim`, and applies the constrained delta to the original\n * clip snapshot to produce new startSample, durationSamples, and offsetSamples.\n *\n * For left boundary: startSample and offsetSamples increase by the constrained delta,\n * durationSamples decreases by the same amount.\n *\n * For right boundary: only durationSamples changes (increases by the constrained delta).\n */\nexport function calculateBoundaryTrim({\n originalClip,\n clip,\n pixelDelta,\n samplesPerPixel,\n sampleRate,\n boundary,\n sortedClips,\n sortedIndex,\n}: CalculateBoundaryTrimParams): BoundaryTrimResult {\n const sampleDelta = pixelDelta * samplesPerPixel;\n const MIN_DURATION_SAMPLES = Math.floor(0.1 * sampleRate);\n\n // Build a temporary clip with original state for constraint calculation\n const tempClip: AudioClip = {\n ...clip,\n startSample: originalClip.startSample,\n offsetSamples: originalClip.offsetSamples,\n durationSamples: originalClip.durationSamples,\n };\n\n const constrainedDelta = constrainBoundaryTrim(\n tempClip,\n Math.floor(sampleDelta),\n boundary,\n sortedClips,\n sortedIndex,\n MIN_DURATION_SAMPLES\n );\n\n if (boundary === 'left') {\n return {\n startSample: originalClip.startSample + constrainedDelta,\n durationSamples: originalClip.durationSamples - constrainedDelta,\n offsetSamples: originalClip.offsetSamples + constrainedDelta,\n };\n } else {\n return {\n startSample: originalClip.startSample,\n durationSamples: originalClip.durationSamples + constrainedDelta,\n offsetSamples: originalClip.offsetSamples,\n };\n }\n}\n","import React from 'react';\nimport type {\n DragStartEvent as DragStartCallback,\n DragMoveEvent as DragMoveCallback,\n DragEndEvent as DragEndCallback,\n} from '@dnd-kit/abstract';\nimport type { AnnotationData } from '@waveform-playlist/core';\n\nconst LINK_THRESHOLD = 0.01; // Consider edges \"linked\" if within 10ms\n\ninterface UseAnnotationDragHandlersOptions {\n annotations: AnnotationData[];\n onAnnotationsChange: (annotations: AnnotationData[]) => void;\n samplesPerPixel: number;\n /** Sample rate for pixel-to-time conversion. Providers pass the real rate from\n * context; defaults to 48000 (the engine default) for out-of-provider usage. */\n sampleRate?: number;\n duration: number;\n linkEndpoints: boolean;\n}\n\n/**\n * Custom hook for handling annotation drag operations (boundary trimming)\n *\n * Provides drag handlers for use with @dnd-kit/react DragDropProvider.\n * Handles annotation boundary resizing with linked endpoints support.\n *\n * @example\n * ```tsx\n * const { onDragStart, onDragMove, onDragEnd } = useAnnotationDragHandlers({\n * annotations,\n * onAnnotationsChange: setAnnotations,\n * samplesPerPixel,\n * duration,\n * linkEndpoints,\n * });\n *\n * return (\n * <DragDropProvider\n * onDragStart={onDragStart}\n * onDragMove={onDragMove}\n * onDragEnd={onDragEnd}\n * modifiers={[RestrictToHorizontalAxis]}\n * >\n * {renderAnnotations()}\n * </DragDropProvider>\n * );\n * ```\n */\nexport function useAnnotationDragHandlers({\n annotations,\n onAnnotationsChange,\n samplesPerPixel,\n // Default mirrors the engine default; the providers always pass the real rate from context (#510).\n sampleRate = 48000,\n duration,\n linkEndpoints,\n}: UseAnnotationDragHandlersOptions) {\n // Store original annotation state when drag starts (for cumulative delta application)\n const originalAnnotationStateRef = React.useRef<{\n start: number;\n end: number;\n annotationIndex: number;\n } | null>(null);\n\n const onDragStart = React.useCallback(\n (event: Parameters<DragStartCallback>[0]) => {\n const data = event.operation.source?.data as\n | {\n annotationId: string;\n annotationIndex: number;\n edge: 'start' | 'end';\n }\n | undefined;\n\n if (!data || data.annotationIndex === undefined) {\n originalAnnotationStateRef.current = null;\n return;\n }\n\n const annotation = annotations[data.annotationIndex];\n if (annotation) {\n originalAnnotationStateRef.current = {\n start: annotation.start,\n end: annotation.end,\n annotationIndex: data.annotationIndex,\n };\n }\n },\n [annotations]\n );\n\n const onDragMove = React.useCallback(\n (event: Parameters<DragMoveCallback>[0]) => {\n if (!originalAnnotationStateRef.current) {\n return;\n }\n\n const data = event.operation.source?.data as\n | {\n annotationId: string;\n annotationIndex: number;\n edge: 'start' | 'end';\n }\n | undefined;\n\n if (!data) return;\n\n const { edge, annotationIndex } = data;\n const originalState = originalAnnotationStateRef.current;\n\n // The dragmove event is dispatched BEFORE position.current is updated (happens\n // in a microtask), so the snapshot's position.current is stale. Use event.to\n // (the pointer's current coordinates from the sensor) for the correct position.\n const currentX = event.to?.x ?? event.operation.position.current.x;\n const rawDeltaX = currentX - event.operation.position.initial.x;\n const timeDelta = (rawDeltaX * samplesPerPixel) / sampleRate;\n\n // Apply delta to original state\n const newTime =\n edge === 'start' ? originalState.start + timeDelta : originalState.end + timeDelta;\n\n // Update annotations using the boundary logic\n const updatedAnnotations = updateAnnotationBoundaries({\n annotationIndex,\n newTime,\n isDraggingStart: edge === 'start',\n annotations,\n duration,\n linkEndpoints,\n });\n\n onAnnotationsChange(updatedAnnotations);\n },\n [annotations, onAnnotationsChange, samplesPerPixel, sampleRate, duration, linkEndpoints]\n );\n\n const onDragEnd = React.useCallback(\n (event: Parameters<DragEndCallback>[0]) => {\n // Handle canceled drags — revert annotations to pre-drag state\n if (event.canceled && originalAnnotationStateRef.current) {\n const { annotationIndex, start, end } = originalAnnotationStateRef.current;\n const revertedAnnotations = annotations.map((annotation, idx) => {\n if (idx !== annotationIndex) return annotation;\n return { ...annotation, start, end };\n });\n onAnnotationsChange(revertedAnnotations);\n }\n originalAnnotationStateRef.current = null;\n },\n [annotations, onAnnotationsChange]\n );\n\n return {\n onDragStart,\n onDragMove,\n onDragEnd,\n };\n}\n\n/**\n * Updates annotation boundaries based on drag operations.\n * Handles linked endpoints and collision detection.\n */\nfunction updateAnnotationBoundaries({\n annotationIndex,\n newTime,\n isDraggingStart,\n annotations,\n duration,\n linkEndpoints: shouldLinkEndpoints,\n}: {\n annotationIndex: number;\n newTime: number;\n isDraggingStart: boolean;\n annotations: AnnotationData[];\n duration: number;\n linkEndpoints: boolean;\n}): AnnotationData[] {\n const updatedAnnotations = [...annotations];\n const annotation = annotations[annotationIndex];\n\n if (isDraggingStart) {\n // Dragging start edge\n const constrainedStart = Math.min(annotation.end - 0.1, Math.max(0, newTime));\n const delta = constrainedStart - annotation.start;\n\n updatedAnnotations[annotationIndex] = {\n ...annotation,\n start: constrainedStart,\n };\n\n if (shouldLinkEndpoints && annotationIndex > 0) {\n // Link Endpoints mode: handle both already-linked and collision scenarios\n const prevAnnotation = updatedAnnotations[annotationIndex - 1];\n\n if (Math.abs(prevAnnotation.end - annotation.start) < LINK_THRESHOLD) {\n // Already linked: move previous annotation's end together with this start\n updatedAnnotations[annotationIndex - 1] = {\n ...prevAnnotation,\n end: Math.max(prevAnnotation.start + 0.1, prevAnnotation.end + delta),\n };\n } else if (constrainedStart <= prevAnnotation.end) {\n // Dragged past previous annotation: snap to link them together\n updatedAnnotations[annotationIndex] = {\n ...updatedAnnotations[annotationIndex],\n start: prevAnnotation.end,\n };\n }\n } else if (\n !shouldLinkEndpoints &&\n annotationIndex > 0 &&\n constrainedStart < updatedAnnotations[annotationIndex - 1].end\n ) {\n // Collision detection: push previous annotation's end back\n updatedAnnotations[annotationIndex - 1] = {\n ...updatedAnnotations[annotationIndex - 1],\n end: constrainedStart,\n };\n }\n } else {\n // Dragging end edge\n const constrainedEnd = Math.max(annotation.start + 0.1, Math.min(newTime, duration));\n const delta = constrainedEnd - annotation.end;\n\n updatedAnnotations[annotationIndex] = {\n ...annotation,\n end: constrainedEnd,\n };\n\n if (shouldLinkEndpoints && annotationIndex < updatedAnnotations.length - 1) {\n // Link Endpoints mode: handle both already-linked and collision scenarios\n const nextAnnotation = updatedAnnotations[annotationIndex + 1];\n\n if (Math.abs(nextAnnotation.start - annotation.end) < LINK_THRESHOLD) {\n // Already linked: move next annotation's start together with this end\n const newStart = nextAnnotation.start + delta;\n updatedAnnotations[annotationIndex + 1] = {\n ...nextAnnotation,\n start: Math.min(nextAnnotation.end - 0.1, newStart),\n };\n\n // Cascade linked endpoints\n let currentIndex = annotationIndex + 1;\n while (currentIndex < updatedAnnotations.length - 1) {\n const current = updatedAnnotations[currentIndex];\n const next = updatedAnnotations[currentIndex + 1];\n\n if (Math.abs(next.start - current.end) < LINK_THRESHOLD) {\n const nextDelta = current.end - annotations[currentIndex].end;\n updatedAnnotations[currentIndex + 1] = {\n ...next,\n start: Math.min(next.end - 0.1, next.start + nextDelta),\n };\n currentIndex++;\n } else {\n break; // No more linked endpoints\n }\n }\n } else if (constrainedEnd >= nextAnnotation.start) {\n // Dragged past next annotation: snap to link them together\n updatedAnnotations[annotationIndex] = {\n ...updatedAnnotations[annotationIndex],\n end: nextAnnotation.start,\n };\n }\n } else if (\n !shouldLinkEndpoints &&\n annotationIndex < updatedAnnotations.length - 1 &&\n constrainedEnd > updatedAnnotations[annotationIndex + 1].start\n ) {\n // Collision detection: push next annotation's start forward\n const nextAnnotation = updatedAnnotations[annotationIndex + 1];\n\n updatedAnnotations[annotationIndex + 1] = {\n ...nextAnnotation,\n start: constrainedEnd,\n };\n\n // Cascade collisions\n let currentIndex = annotationIndex + 1;\n while (currentIndex < updatedAnnotations.length - 1) {\n const current = updatedAnnotations[currentIndex];\n const next = updatedAnnotations[currentIndex + 1];\n\n if (current.end > next.start) {\n updatedAnnotations[currentIndex + 1] = {\n ...next,\n start: current.end,\n };\n currentIndex++;\n } else {\n break; // No more collisions\n }\n }\n }\n }\n\n return updatedAnnotations;\n}\n","/**\n * Hook for configuring @dnd-kit sensors for clip dragging\n *\n * Provides consistent drag activation behavior across all examples.\n * Always overrides PointerSensor defaults with custom activation constraints:\n * - Default mode: distance-based activation (1px) for all pointer types\n * - Touch-optimized mode: delay-based activation for touch (250ms),\n * distance-based for mouse/pen\n */\n\nimport { useMemo } from 'react';\nimport { PointerSensor, PointerActivationConstraints } from '@dnd-kit/dom';\nimport type { PluginDescriptor } from '@dnd-kit/abstract';\n\nexport interface DragSensorOptions {\n /**\n * Enable mobile-optimized touch handling with delay-based activation.\n * When true, touch events get delay-based activation while mouse/pen get distance-based.\n * When false (default), all pointer types use distance-based activation (1px).\n */\n touchOptimized?: boolean;\n /**\n * Delay in milliseconds before touch drag activates (only when touchOptimized is true).\n * Default: 250ms - long enough to distinguish from scroll intent\n */\n touchDelay?: number;\n /**\n * Distance tolerance during touch delay (only when touchOptimized is true).\n * If finger moves more than this during delay, drag is cancelled.\n * Default: 5px - allows slight finger movement\n */\n touchTolerance?: number;\n /**\n * Distance in pixels before mouse drag activates.\n * Default: 1px for immediate feedback on desktop\n */\n mouseDistance?: number;\n}\n\n/**\n * Returns configured sensors for @dnd-kit drag operations\n *\n * @param options - Configuration options for drag sensors\n * @returns Array of sensor constructors/descriptors for DragDropProvider's sensors prop\n *\n * @example\n * // Desktop-optimized (default — 1px distance activation for all pointer types)\n * const sensors = useDragSensors();\n *\n * @example\n * // Mobile-optimized with custom touch delay\n * const sensors = useDragSensors({ touchOptimized: true, touchDelay: 300 });\n */\nexport function useDragSensors(\n options: DragSensorOptions = {}\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (typeof PointerSensor | PluginDescriptor<any, any, any>)[] {\n const {\n touchOptimized = false,\n touchDelay = 250,\n touchTolerance = 5,\n mouseDistance = 1,\n } = options;\n\n return useMemo(() => {\n if (touchOptimized) {\n // Custom activation constraints for touch-optimized mode\n return [\n PointerSensor.configure({\n activationConstraints(event) {\n // Touch events get delay-based activation\n if (event.pointerType === 'touch') {\n return [\n new PointerActivationConstraints.Delay({\n value: touchDelay,\n tolerance: touchTolerance,\n }),\n ];\n }\n // Mouse/pen get distance-based activation\n return [new PointerActivationConstraints.Distance({ value: mouseDistance })];\n },\n }),\n ];\n }\n\n // Default: PointerSensor with distance-based activation\n return [\n PointerSensor.configure({\n activationConstraints: [\n new PointerActivationConstraints.Distance({ value: mouseDistance }),\n ],\n }),\n ];\n }, [touchOptimized, touchDelay, touchTolerance, mouseDistance]);\n}\n","import React, { useCallback } from 'react';\nimport type { ClipTrack } from '@waveform-playlist/core';\nimport { calculateSplitPoint, canSplitAt } from '@waveform-playlist/engine';\nimport type { PlaylistEngine } from '@waveform-playlist/engine';\nimport {\n usePlaybackAnimation,\n usePlaylistData,\n usePlaylistState,\n} from '../WaveformPlaylistContext';\n\nexport interface UseClipSplittingOptions {\n tracks: ClipTrack[];\n samplesPerPixel: number;\n engineRef: React.RefObject<PlaylistEngine | null>;\n}\n\nexport interface UseClipSplittingResult {\n splitClipAtPlayhead: () => boolean;\n splitClipAt: (trackIndex: number, clipIndex: number, splitTime: number) => boolean;\n}\n\n/**\n * Hook for splitting clips at the playhead or at a specific time\n *\n * Splitting delegates to `engine.splitClip()` — the engine handles clip creation,\n * adapter sync, and emits statechange. The provider's statechange handler propagates\n * the updated tracks to the parent via `onTracksChange`.\n *\n * @param options - Configuration options\n * @returns Object with split functions\n *\n * @example\n * ```tsx\n * const { splitClipAtPlayhead } = useClipSplitting({\n * tracks,\n * samplesPerPixel,\n * engineRef: playoutRef,\n * });\n *\n * // In keyboard handler\n * const handleKeyPress = (e: KeyboardEvent) => {\n * if (e.key === 's' || e.key === 'S') {\n * splitClipAtPlayhead();\n * }\n * };\n * ```\n */\nexport const useClipSplitting = (options: UseClipSplittingOptions): UseClipSplittingResult => {\n const { tracks, engineRef } = options;\n const { sampleRate } = usePlaylistData();\n const { currentTimeRef } = usePlaybackAnimation();\n const { selectedTrackId } = usePlaylistState();\n\n /**\n * Split a specific clip at a given time\n *\n * @param trackIndex - Index of the track containing the clip\n * @param clipIndex - Index of the clip within the track\n * @param splitTime - Timeline position where to split (in seconds)\n * @returns true if pre-validation passed and the engine was called, false otherwise.\n * Note: engine.splitClip() returns void so a true here assumes the engine accepted\n * the split (it performs its own canSplitAt check internally).\n */\n const splitClipAt = useCallback(\n (trackIndex: number, clipIndex: number, splitTime: number): boolean => {\n const { samplesPerPixel } = options;\n\n const track = tracks[trackIndex];\n if (!track) return false;\n\n const clip = track.clips[clipIndex];\n if (!clip) return false;\n\n // Convert split time to sample position, snapped to pixel boundary\n const splitSample = Math.round(splitTime * sampleRate);\n const snappedSplitSample = calculateSplitPoint(splitSample, samplesPerPixel);\n\n // Pre-flight validation before sending to engine (engine also validates internally).\n // Duplicated here to provide early user feedback via console.warn.\n const MIN_DURATION_SAMPLES = Math.floor(0.1 * sampleRate);\n if (!canSplitAt(clip, snappedSplitSample, MIN_DURATION_SAMPLES)) {\n console.warn('Split point is invalid (outside bounds or too close to edge)');\n return false;\n }\n\n // Delegate to engine — handles clip creation, adapter sync, and emits statechange\n const engine = engineRef.current;\n if (!engine) {\n console.warn('[waveform-playlist] engineRef is null — split not synced to adapter');\n return false;\n }\n\n engine.splitClip(track.id, clip.id, snappedSplitSample);\n return true;\n },\n [tracks, options, engineRef, sampleRate]\n );\n\n /**\n * Split clip at the current playhead position on the selected track\n * If no track is selected, does nothing\n *\n * @returns true if a clip was split, false otherwise\n */\n const splitClipAtPlayhead = useCallback((): boolean => {\n // If no track is selected, cannot split\n if (!selectedTrackId) {\n console.warn('[waveform-playlist] No track selected — click a clip to select a track first');\n return false;\n }\n\n // Find the selected track\n const trackIndex = tracks.findIndex((track) => track.id === selectedTrackId);\n if (trackIndex === -1) {\n console.warn('Selected track not found');\n return false;\n }\n\n const track = tracks[trackIndex];\n\n // Use ref for real-time position during playback (state updates are throttled)\n const currentTime = currentTimeRef.current ?? 0;\n\n // Find clip at current time on the selected track\n for (let clipIndex = 0; clipIndex < track.clips.length; clipIndex++) {\n const clip = track.clips[clipIndex];\n const clipStartTime = clip.startSample / sampleRate;\n const clipEndTime = (clip.startSample + clip.durationSamples) / sampleRate;\n\n // Check if currentTime is within this clip (not at boundaries)\n if (currentTime > clipStartTime && currentTime < clipEndTime) {\n // Found a clip! Split it\n return splitClipAt(trackIndex, clipIndex, currentTime);\n }\n }\n\n console.warn(`[waveform-playlist] No clip found at playhead position on track \"${track.name}\"`);\n return false;\n }, [tracks, currentTimeRef, selectedTrackId, splitClipAt, sampleRate]);\n\n return {\n splitClipAtPlayhead,\n splitClipAt,\n };\n};\n","import { useEffect, useCallback } from 'react';\nimport { handleKeyboardEvent } from '@waveform-playlist/core';\nimport type { KeyboardShortcut } from '@waveform-playlist/core';\n\n// Re-export from core for backwards compatibility\nexport type { KeyboardShortcut } from '@waveform-playlist/core';\nexport { handleKeyboardEvent, getShortcutLabel } from '@waveform-playlist/core';\n\nexport interface UseKeyboardShortcutsOptions {\n shortcuts: KeyboardShortcut[];\n enabled?: boolean;\n}\n\n/**\n * Hook for managing keyboard shortcuts\n *\n * @param options - Configuration options\n *\n * @example\n * ```tsx\n * useKeyboardShortcuts({\n * shortcuts: [\n * {\n * key: ' ',\n * action: togglePlayPause,\n * description: 'Play/Pause',\n * preventDefault: true,\n * },\n * {\n * key: 's',\n * action: splitClipAtPlayhead,\n * description: 'Split clip at playhead',\n * preventDefault: true,\n * },\n * ],\n * });\n * ```\n */\nexport const useKeyboardShortcuts = (options: UseKeyboardShortcutsOptions): void => {\n const { shortcuts, enabled = true } = options;\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent) => handleKeyboardEvent(event, shortcuts, enabled),\n [shortcuts, enabled]\n );\n\n useEffect(() => {\n if (!enabled) return;\n\n window.addEventListener('keydown', handleKeyDown);\n\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n };\n }, [handleKeyDown, enabled]);\n};\n","import { useCallback } from 'react';\nimport { usePlaybackAnimation, usePlaylistControls } from '../WaveformPlaylistContext';\nimport { useKeyboardShortcuts, type KeyboardShortcut } from './useKeyboardShortcuts';\n\nexport interface UsePlaybackShortcutsOptions {\n /**\n * Enable the shortcuts. Defaults to true.\n */\n enabled?: boolean;\n /**\n * Additional shortcuts to include alongside the default playback shortcuts.\n */\n additionalShortcuts?: KeyboardShortcut[];\n /**\n * Override default shortcuts. If provided, only these shortcuts will be used.\n */\n shortcuts?: KeyboardShortcut[];\n}\n\nexport interface UsePlaybackShortcutsReturn {\n /** Rewind to the beginning (time = 0) */\n rewindToStart: () => void;\n /** Toggle play/pause */\n togglePlayPause: () => void;\n /** Stop playback and return to start position */\n stopPlayback: () => void;\n /** The list of active keyboard shortcuts */\n shortcuts: KeyboardShortcut[];\n}\n\n/**\n * Hook that provides common playback keyboard shortcuts for the playlist.\n *\n * Default shortcuts:\n * - `Space` - Toggle play/pause\n * - `Escape` - Stop playback\n * - `0` - Rewind to start (seek to time 0)\n *\n * @example\n * ```tsx\n * // Basic usage - enables default shortcuts\n * usePlaybackShortcuts();\n *\n * // With additional custom shortcuts\n * usePlaybackShortcuts({\n * additionalShortcuts: [\n * { key: 's', action: splitClipAtPlayhead, description: 'Split clip' },\n * ],\n * });\n *\n * // Completely override shortcuts\n * usePlaybackShortcuts({\n * shortcuts: [\n * { key: 'Home', action: rewindToStart, description: 'Go to start' },\n * ],\n * });\n * ```\n */\nexport const usePlaybackShortcuts = (\n options: UsePlaybackShortcutsOptions = {}\n): UsePlaybackShortcutsReturn => {\n const { enabled = true, additionalShortcuts = [], shortcuts: overrideShortcuts } = options;\n\n const { isPlaying } = usePlaybackAnimation();\n const { setCurrentTime, play, pause, stop } = usePlaylistControls();\n\n /**\n * Toggle between play and pause.\n */\n const togglePlayPause = useCallback(() => {\n if (isPlaying) {\n pause();\n } else {\n play();\n }\n }, [isPlaying, play, pause]);\n\n /**\n * Stop playback and return to start position.\n */\n const stopPlayback = useCallback(() => {\n stop();\n }, [stop]);\n\n /**\n * Rewind to the beginning of the timeline.\n * If playing, stops and restarts playback from the beginning.\n */\n const rewindToStart = useCallback(() => {\n setCurrentTime(0);\n\n if (isPlaying) {\n play(0);\n }\n }, [isPlaying, setCurrentTime, play]);\n\n // Default playback shortcuts\n const defaultShortcuts: KeyboardShortcut[] = [\n {\n key: ' ',\n action: togglePlayPause,\n description: 'Play/Pause',\n preventDefault: true,\n },\n {\n key: 'Escape',\n action: stopPlayback,\n description: 'Stop',\n preventDefault: true,\n },\n {\n key: '0',\n action: rewindToStart,\n description: 'Rewind to start',\n preventDefault: true,\n },\n ];\n\n // Use override shortcuts if provided, otherwise combine defaults with additional\n const activeShortcuts = overrideShortcuts ?? [...defaultShortcuts, ...additionalShortcuts];\n\n // Register the keyboard shortcuts\n useKeyboardShortcuts({\n shortcuts: activeShortcuts,\n enabled,\n });\n\n return {\n rewindToStart,\n togglePlayPause,\n stopPlayback,\n shortcuts: activeShortcuts,\n };\n};\n","import { useCallback, useMemo, useEffect } from 'react';\nimport type { AnnotationData } from '@waveform-playlist/core';\nimport { usePlaylistDataOptional } from '../WaveformPlaylistContext';\nimport { useKeyboardShortcuts } from './useKeyboardShortcuts';\n\nconst LINK_THRESHOLD = 0.01; // Consider edges \"linked\" if within 10ms\nconst TIME_DELTA = 0.01; // 10ms adjustment per keypress\n\ninterface UseAnnotationKeyboardControlsOptions {\n annotations: AnnotationData[];\n activeAnnotationId: string | null;\n onAnnotationsChange: (annotations: AnnotationData[]) => void;\n /** Callback to set the active annotation ID for selection */\n onActiveAnnotationChange?: (id: string | null) => void;\n duration: number;\n linkEndpoints: boolean;\n /** Whether continuous play is enabled (affects playback duration) */\n continuousPlay?: boolean;\n enabled?: boolean;\n /** Optional: scroll container ref for auto-scrolling to annotation */\n scrollContainerRef?: React.RefObject<HTMLDivElement | null>;\n /** Optional: callback to start playback at a time with optional duration */\n onPlay?: (startTime: number, duration?: number) => void;\n /**\n * Pixels-per-sample for auto-scroll positioning. Falls back to the\n * WaveformPlaylistProvider context when omitted (WebAudio path). Pass\n * explicitly when used outside that provider (e.g. the MediaElement path).\n */\n samplesPerPixel?: number;\n /** Sample rate for auto-scroll positioning. Falls back to context. */\n sampleRate?: number;\n}\n\n/**\n * Hook for keyboard-based annotation navigation and boundary editing\n *\n * Navigation Shortcuts:\n * - ArrowUp / ArrowLeft = Select previous annotation\n * - ArrowDown / ArrowRight = Select next annotation\n * - Home = Select first annotation\n * - End = Select last annotation\n * - Escape = Deselect annotation\n * - Enter = Play selected annotation\n *\n * Boundary Editing Shortcuts (requires active annotation):\n * - [ = Move start boundary earlier (left)\n * - ] = Move start boundary later (right)\n * - Shift+[ = Move end boundary earlier (left)\n * - Shift+] = Move end boundary later (right)\n *\n * Respects linkEndpoints and continuousPlay settings.\n *\n * @example\n * ```tsx\n * useAnnotationKeyboardControls({\n * annotations,\n * activeAnnotationId,\n * onAnnotationsChange: setAnnotations,\n * onActiveAnnotationChange: setActiveAnnotationId,\n * duration,\n * linkEndpoints,\n * });\n * ```\n */\nexport function useAnnotationKeyboardControls({\n annotations,\n activeAnnotationId,\n onAnnotationsChange,\n onActiveAnnotationChange,\n duration,\n linkEndpoints,\n continuousPlay = false,\n enabled = true,\n scrollContainerRef,\n onPlay,\n samplesPerPixel: samplesPerPixelProp,\n sampleRate: sampleRateProp,\n}: UseAnnotationKeyboardControlsOptions) {\n // Auto-scroll positioning needs samplesPerPixel/sampleRate. Prefer explicit\n // props (works in the MediaElement path); otherwise read them from the\n // WaveformPlaylistProvider context when present. usePlaylistDataOptional()\n // returns null instead of throwing, so this hook is safe outside that provider.\n const playlistData = usePlaylistDataOptional();\n const samplesPerPixel = samplesPerPixelProp ?? playlistData?.samplesPerPixel;\n const sampleRate = sampleRateProp ?? playlistData?.sampleRate;\n\n const activeIndex = useMemo(() => {\n if (!activeAnnotationId) return -1;\n return annotations.findIndex((a) => a.id === activeAnnotationId);\n }, [annotations, activeAnnotationId]);\n\n // Scroll waveform to show a specific annotation\n const scrollToAnnotation = useCallback(\n (annotationId: string) => {\n if (!scrollContainerRef?.current || !samplesPerPixel || !sampleRate) return;\n\n const annotation = annotations.find((a) => a.id === annotationId);\n if (!annotation) return;\n\n const container = scrollContainerRef.current;\n const containerWidth = container.clientWidth;\n\n // Calculate pixel positions for annotation start and center\n const startPixel = (annotation.start * sampleRate) / samplesPerPixel;\n const endPixel = (annotation.end * sampleRate) / samplesPerPixel;\n const annotationCenter = (startPixel + endPixel) / 2;\n\n // Check if annotation is currently visible\n const scrollLeft = container.scrollLeft;\n const visibleStart = scrollLeft;\n const visibleEnd = scrollLeft + containerWidth;\n\n // If annotation is not fully visible, scroll to center it\n if (startPixel < visibleStart || endPixel > visibleEnd) {\n const targetScrollLeft = Math.max(0, annotationCenter - containerWidth / 2);\n container.scrollTo({\n left: targetScrollLeft,\n behavior: 'smooth',\n });\n }\n },\n [annotations, scrollContainerRef, samplesPerPixel, sampleRate]\n );\n\n // Auto-scroll when active annotation changes via keyboard navigation\n useEffect(() => {\n if (activeAnnotationId && scrollContainerRef?.current && samplesPerPixel && sampleRate) {\n scrollToAnnotation(activeAnnotationId);\n }\n }, [activeAnnotationId, scrollToAnnotation, scrollContainerRef, samplesPerPixel, sampleRate]);\n\n const moveStartBoundary = useCallback(\n (delta: number) => {\n if (activeIndex < 0) return;\n\n const annotation = annotations[activeIndex];\n const newStart = Math.max(0, Math.min(annotation.end - 0.1, annotation.start + delta));\n const actualDelta = newStart - annotation.start;\n\n const updatedAnnotations = [...annotations];\n updatedAnnotations[activeIndex] = {\n ...annotation,\n start: newStart,\n };\n\n // Handle linked endpoints\n if (linkEndpoints && activeIndex > 0) {\n const prevAnnotation = updatedAnnotations[activeIndex - 1];\n if (Math.abs(prevAnnotation.end - annotation.start) < LINK_THRESHOLD) {\n // Already linked: move previous annotation's end together\n updatedAnnotations[activeIndex - 1] = {\n ...prevAnnotation,\n end: Math.max(prevAnnotation.start + 0.1, prevAnnotation.end + actualDelta),\n };\n }\n } else if (!linkEndpoints && activeIndex > 0) {\n // Non-linked mode: don't overlap previous annotation\n const prevAnnotation = updatedAnnotations[activeIndex - 1];\n if (newStart < prevAnnotation.end) {\n // Push back previous annotation's end\n updatedAnnotations[activeIndex - 1] = {\n ...prevAnnotation,\n end: newStart,\n };\n }\n }\n\n onAnnotationsChange(updatedAnnotations);\n },\n [annotations, activeIndex, linkEndpoints, onAnnotationsChange]\n );\n\n const moveEndBoundary = useCallback(\n (delta: number) => {\n if (activeIndex < 0) return;\n\n const annotation = annotations[activeIndex];\n const newEnd = Math.max(annotation.start + 0.1, Math.min(duration, annotation.end + delta));\n const actualDelta = newEnd - annotation.end;\n\n const updatedAnnotations = [...annotations];\n updatedAnnotations[activeIndex] = {\n ...annotation,\n end: newEnd,\n };\n\n // Handle linked endpoints\n if (linkEndpoints && activeIndex < annotations.length - 1) {\n const nextAnnotation = updatedAnnotations[activeIndex + 1];\n if (Math.abs(nextAnnotation.start - annotation.end) < LINK_THRESHOLD) {\n // Already linked: move next annotation's start together\n const newNextStart = Math.min(\n nextAnnotation.end - 0.1,\n nextAnnotation.start + actualDelta\n );\n updatedAnnotations[activeIndex + 1] = {\n ...nextAnnotation,\n start: newNextStart,\n };\n\n // Cascade linked endpoints\n let currentIndex = activeIndex + 1;\n while (currentIndex < updatedAnnotations.length - 1) {\n const current = updatedAnnotations[currentIndex];\n const next = updatedAnnotations[currentIndex + 1];\n\n if (Math.abs(next.start - annotations[currentIndex].end) < LINK_THRESHOLD) {\n const nextDelta = current.end - annotations[currentIndex].end;\n updatedAnnotations[currentIndex + 1] = {\n ...next,\n start: Math.min(next.end - 0.1, next.start + nextDelta),\n };\n currentIndex++;\n } else {\n break;\n }\n }\n }\n } else if (!linkEndpoints && activeIndex < annotations.length - 1) {\n // Non-linked mode: don't overlap next annotation\n const nextAnnotation = updatedAnnotations[activeIndex + 1];\n if (newEnd > nextAnnotation.start) {\n // Push forward next annotation's start\n updatedAnnotations[activeIndex + 1] = {\n ...nextAnnotation,\n start: newEnd,\n };\n\n // Cascade collisions\n let currentIndex = activeIndex + 1;\n while (currentIndex < updatedAnnotations.length - 1) {\n const current = updatedAnnotations[currentIndex];\n const next = updatedAnnotations[currentIndex + 1];\n\n if (current.end > next.start) {\n updatedAnnotations[currentIndex + 1] = {\n ...next,\n start: current.end,\n };\n currentIndex++;\n } else {\n break;\n }\n }\n }\n }\n\n onAnnotationsChange(updatedAnnotations);\n },\n [annotations, activeIndex, duration, linkEndpoints, onAnnotationsChange]\n );\n\n // Navigation functions\n const selectPrevious = useCallback(() => {\n if (!onActiveAnnotationChange || annotations.length === 0) return;\n\n if (activeIndex <= 0) {\n // If no selection or at first, select last annotation\n onActiveAnnotationChange(annotations[annotations.length - 1].id);\n } else {\n onActiveAnnotationChange(annotations[activeIndex - 1].id);\n }\n }, [annotations, activeIndex, onActiveAnnotationChange]);\n\n const selectNext = useCallback(() => {\n if (!onActiveAnnotationChange || annotations.length === 0) return;\n\n if (activeIndex < 0 || activeIndex >= annotations.length - 1) {\n // If no selection or at last, select first annotation\n onActiveAnnotationChange(annotations[0].id);\n } else {\n onActiveAnnotationChange(annotations[activeIndex + 1].id);\n }\n }, [annotations, activeIndex, onActiveAnnotationChange]);\n\n const selectFirst = useCallback(() => {\n if (!onActiveAnnotationChange || annotations.length === 0) return;\n onActiveAnnotationChange(annotations[0].id);\n }, [annotations, onActiveAnnotationChange]);\n\n const selectLast = useCallback(() => {\n if (!onActiveAnnotationChange || annotations.length === 0) return;\n onActiveAnnotationChange(annotations[annotations.length - 1].id);\n }, [annotations, onActiveAnnotationChange]);\n\n const clearSelection = useCallback(() => {\n if (!onActiveAnnotationChange) return;\n onActiveAnnotationChange(null);\n }, [onActiveAnnotationChange]);\n\n // Play the currently selected annotation\n const playActiveAnnotation = useCallback(() => {\n if (activeIndex < 0 || !onPlay) return;\n\n const annotation = annotations[activeIndex];\n // If continuous play is off, play just this annotation's duration\n const playDuration = !continuousPlay ? annotation.end - annotation.start : undefined;\n onPlay(annotation.start, playDuration);\n }, [annotations, activeIndex, continuousPlay, onPlay]);\n\n // Shortcuts that require an active annotation (boundary editing + playback)\n const activeAnnotationShortcuts = useMemo(\n () => [\n {\n key: '[',\n action: () => moveStartBoundary(-TIME_DELTA),\n description: 'Move annotation start earlier',\n preventDefault: true,\n },\n {\n key: ']',\n action: () => moveStartBoundary(TIME_DELTA),\n description: 'Move annotation start later',\n preventDefault: true,\n },\n {\n key: '{',\n shiftKey: true,\n action: () => moveEndBoundary(-TIME_DELTA),\n description: 'Move annotation end earlier',\n preventDefault: true,\n },\n {\n key: '}',\n shiftKey: true,\n action: () => moveEndBoundary(TIME_DELTA),\n description: 'Move annotation end later',\n preventDefault: true,\n },\n {\n key: 'Enter',\n action: playActiveAnnotation,\n description: 'Play selected annotation',\n preventDefault: true,\n },\n ],\n [moveStartBoundary, moveEndBoundary, playActiveAnnotation]\n );\n\n // Navigation shortcuts (always active when enabled and there are annotations)\n const navigationShortcuts = useMemo(\n () => [\n {\n key: 'ArrowUp',\n action: selectPrevious,\n description: 'Select previous annotation',\n preventDefault: true,\n },\n {\n key: 'ArrowLeft',\n action: selectPrevious,\n description: 'Select previous annotation',\n preventDefault: true,\n },\n {\n key: 'ArrowDown',\n action: selectNext,\n description: 'Select next annotation',\n preventDefault: true,\n },\n {\n key: 'ArrowRight',\n action: selectNext,\n description: 'Select next annotation',\n preventDefault: true,\n },\n {\n key: 'Home',\n action: selectFirst,\n description: 'Select first annotation',\n preventDefault: true,\n },\n {\n key: 'End',\n action: selectLast,\n description: 'Select last annotation',\n preventDefault: true,\n },\n {\n key: 'Escape',\n action: clearSelection,\n description: 'Deselect annotation',\n preventDefault: true,\n },\n ],\n [selectPrevious, selectNext, selectFirst, selectLast, clearSelection]\n );\n\n // Active annotation shortcuts only work when an annotation is selected\n useKeyboardShortcuts({\n shortcuts: activeAnnotationShortcuts,\n enabled: enabled && activeIndex >= 0,\n });\n\n // Navigation shortcuts work whenever there are annotations\n useKeyboardShortcuts({\n shortcuts: navigationShortcuts,\n enabled: enabled && annotations.length > 0 && !!onActiveAnnotationChange,\n });\n\n return {\n moveStartBoundary,\n moveEndBoundary,\n selectPrevious,\n selectNext,\n selectFirst,\n selectLast,\n clearSelection,\n scrollToAnnotation,\n playActiveAnnotation,\n };\n}\n","import { useCallback, useEffect, useRef } from 'react';\n\nexport interface AnimationFrameLoopControls {\n animationFrameRef: React.MutableRefObject<number | null>;\n startAnimationFrameLoop: (callback: () => void) => void;\n stopAnimationFrameLoop: () => void;\n}\n\n/**\n * Shared RAF loop controller used by playlist providers.\n * Always guarantees a single in-flight animation frame.\n */\nexport const useAnimationFrameLoop = (): AnimationFrameLoopControls => {\n const animationFrameRef = useRef<number | null>(null);\n\n const stopAnimationFrameLoop = useCallback(() => {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n }, []);\n\n const startAnimationFrameLoop = useCallback(\n (callback: () => void) => {\n stopAnimationFrameLoop();\n animationFrameRef.current = requestAnimationFrame(callback);\n },\n [stopAnimationFrameLoop]\n );\n\n useEffect(() => {\n return () => {\n stopAnimationFrameLoop();\n };\n }, [stopAnimationFrameLoop]);\n\n return {\n animationFrameRef,\n startAnimationFrameLoop,\n stopAnimationFrameLoop,\n };\n};\n","/**\n * useWaveformDataCache\n *\n * Manages a Map<clipId, WaveformData> that stores pre-computed WaveformData\n * objects generated by a web worker. These enable near-instant zoom changes\n * via WaveformData.resample() instead of re-scanning raw audio samples.\n *\n * Behavior:\n * - Creates the worker lazily on first clip needing generation\n * - Generates waveform data once per unique AudioBuffer\n * - Fans out cached/generated waveform data to all clips sharing that buffer\n * - Uses a cancelled flag to ignore state updates after effect cleanup\n * - Terminates the worker on unmount\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { ClipTrack } from '@waveform-playlist/core';\nimport type WaveformData from 'waveform-data';\nimport { createPeaksWorker, type PeaksWorkerApi } from '../workers/peaksWorker';\n\nexport interface UseWaveformDataCacheReturn {\n cache: ReadonlyMap<string, WaveformData>;\n isGenerating: boolean;\n}\n\nexport function useWaveformDataCache(\n tracks: ClipTrack[],\n baseScale: number\n): UseWaveformDataCacheReturn {\n const [cache, setCache] = useState<Map<string, WaveformData>>(() => new Map());\n const [isGenerating, setIsGenerating] = useState(false);\n\n const workerRef = useRef<PeaksWorkerApi | null>(null);\n const generatedByBufferRef = useRef<WeakMap<AudioBuffer, WaveformData>>(new WeakMap());\n const inflightByBufferRef = useRef<WeakMap<AudioBuffer, Promise<void>>>(new WeakMap());\n const subscribersByBufferRef = useRef<WeakMap<AudioBuffer, Set<string>>>(new WeakMap());\n const pendingCountRef = useRef(0);\n\n // Stable callback to get or create the worker\n const getWorker = useCallback(() => {\n if (!workerRef.current) {\n workerRef.current = createPeaksWorker();\n }\n return workerRef.current;\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n const generatedByBuffer = generatedByBufferRef.current;\n const inflightByBuffer = inflightByBufferRef.current;\n const subscribersByBuffer = subscribersByBufferRef.current;\n const immediateAssignments: Array<{ clipId: string; waveformData: WaveformData }> = [];\n const buffersToProcess = new Map<AudioBuffer, Set<string>>();\n\n for (const track of tracks) {\n for (const clip of track.clips) {\n if (!clip.audioBuffer || clip.waveformData) {\n continue;\n }\n\n const existingWaveformData = generatedByBuffer.get(clip.audioBuffer);\n if (existingWaveformData) {\n immediateAssignments.push({\n clipId: clip.id,\n waveformData: existingWaveformData,\n });\n continue;\n }\n\n if (inflightByBuffer.get(clip.audioBuffer)) {\n const existingSubscribers = subscribersByBuffer.get(clip.audioBuffer);\n if (existingSubscribers) {\n existingSubscribers.add(clip.id);\n } else {\n subscribersByBuffer.set(clip.audioBuffer, new Set([clip.id]));\n }\n continue;\n }\n\n const clipsForBuffer = buffersToProcess.get(clip.audioBuffer);\n if (clipsForBuffer) {\n clipsForBuffer.add(clip.id);\n } else {\n buffersToProcess.set(clip.audioBuffer, new Set([clip.id]));\n }\n }\n }\n\n if (immediateAssignments.length > 0) {\n setCache((prev) => {\n const next = new Map(prev);\n for (const assignment of immediateAssignments) {\n next.set(assignment.clipId, assignment.waveformData);\n }\n return next;\n });\n }\n\n if (buffersToProcess.size === 0) return;\n\n pendingCountRef.current += buffersToProcess.size;\n setIsGenerating(true);\n\n const worker = getWorker();\n\n for (const [audioBuffer, clipIds] of buffersToProcess) {\n subscribersByBuffer.set(audioBuffer, new Set(clipIds));\n\n // .slice() channel buffers to avoid detaching the original AudioBuffer views\n const channels: ArrayBuffer[] = [];\n for (let c = 0; c < audioBuffer.numberOfChannels; c++) {\n channels.push(audioBuffer.getChannelData(c).slice().buffer as ArrayBuffer);\n }\n\n const id = `buffer-${Math.random().toString(36).slice(2, 11)}`;\n const job = worker\n .generate({\n id,\n channels,\n length: audioBuffer.length,\n sampleRate: audioBuffer.sampleRate,\n scale: baseScale,\n bits: 16,\n splitChannels: true,\n })\n .then((waveformData) => {\n generatedByBuffer.set(audioBuffer, waveformData);\n const subscribers = subscribersByBuffer.get(audioBuffer) ?? new Set<string>();\n subscribersByBuffer.delete(audioBuffer);\n inflightByBuffer.delete(audioBuffer);\n if (cancelled) return;\n\n setCache((prev) => {\n const next = new Map(prev);\n for (const clipId of subscribers) {\n next.set(clipId, waveformData);\n }\n return next;\n });\n\n pendingCountRef.current--;\n if (pendingCountRef.current <= 0) {\n pendingCountRef.current = 0;\n setIsGenerating(false);\n }\n })\n .catch((err) => {\n subscribersByBuffer.delete(audioBuffer);\n inflightByBuffer.delete(audioBuffer);\n if (cancelled) return;\n console.warn('[waveform-playlist] Worker peak generation failed:', err);\n pendingCountRef.current--;\n if (pendingCountRef.current <= 0) {\n pendingCountRef.current = 0;\n setIsGenerating(false);\n }\n });\n\n inflightByBuffer.set(audioBuffer, job);\n }\n\n return () => {\n cancelled = true;\n inflightByBufferRef.current = new WeakMap();\n subscribersByBufferRef.current = new WeakMap();\n pendingCountRef.current = 0;\n setIsGenerating(false);\n };\n }, [tracks, baseScale, getWorker]);\n\n // Terminate worker on unmount\n useEffect(() => {\n return () => {\n workerRef.current?.terminate();\n workerRef.current = null;\n };\n }, []);\n\n return { cache, isGenerating };\n}\n","/**\n * Inline Web Worker for generating WaveformData binary format from AudioBuffer channels.\n *\n * Uses a Blob URL approach (portable across all bundlers) with the generateWaveformData\n * algorithm adapted from BBC's waveform-data.js (MIT licensed, adapted from Audacity).\n *\n * The worker generates peaks at a base scale (finest zoom level). The main thread\n * then uses WaveformData.resample() for near-instant zoom changes.\n */\n\nimport WaveformData from 'waveform-data';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Worker code (runs inside the Blob URL worker)\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Self-contained worker source.\n * Contains the generateWaveformData function from waveform-data.js/src/waveform-generator.js\n * (MIT License, BBC). Pure JS, zero dependencies.\n */\nconst workerSource = `\n\"use strict\";\n\nvar INT8_MAX = 127;\nvar INT8_MIN = -128;\nvar INT16_MAX = 32767;\nvar INT16_MIN = -32768;\n\nfunction calculateWaveformDataLength(audio_sample_count, scale) {\n var data_length = Math.floor(audio_sample_count / scale);\n var samples_remaining = audio_sample_count - (data_length * scale);\n if (samples_remaining > 0) {\n data_length++;\n }\n return data_length;\n}\n\nfunction generateWaveformData(options) {\n var scale = options.scale;\n var amplitude_scale = options.amplitude_scale;\n var split_channels = options.split_channels;\n var length = options.length;\n var sample_rate = options.sample_rate;\n var channels = options.channels.map(function(channel) {\n return new Float32Array(channel);\n });\n var output_channels = split_channels ? channels.length : 1;\n var header_size = 24;\n var data_length = calculateWaveformDataLength(length, scale);\n var bytes_per_sample = options.bits === 8 ? 1 : 2;\n var total_size = header_size + data_length * 2 * bytes_per_sample * output_channels;\n var buffer = new ArrayBuffer(total_size);\n var data_view = new DataView(buffer);\n\n var scale_counter = 0;\n var offset = header_size;\n\n var min_value = new Array(output_channels);\n var max_value = new Array(output_channels);\n\n for (var channel = 0; channel < output_channels; channel++) {\n min_value[channel] = Infinity;\n max_value[channel] = -Infinity;\n }\n\n var range_min = options.bits === 8 ? INT8_MIN : INT16_MIN;\n var range_max = options.bits === 8 ? INT8_MAX : INT16_MAX;\n\n data_view.setInt32(0, 2, true);\n data_view.setUint32(4, options.bits === 8, true);\n data_view.setInt32(8, sample_rate, true);\n data_view.setInt32(12, scale, true);\n data_view.setInt32(16, data_length, true);\n data_view.setInt32(20, output_channels, true);\n\n for (var i = 0; i < length; i++) {\n var sample = 0;\n\n if (output_channels === 1) {\n for (var ch = 0; ch < channels.length; ++ch) {\n sample += channels[ch][i];\n }\n sample = Math.floor(range_max * sample * amplitude_scale / channels.length);\n\n if (sample < min_value[0]) {\n min_value[0] = sample;\n if (min_value[0] < range_min) {\n min_value[0] = range_min;\n }\n }\n if (sample > max_value[0]) {\n max_value[0] = sample;\n if (max_value[0] > range_max) {\n max_value[0] = range_max;\n }\n }\n }\n else {\n for (var ch2 = 0; ch2 < output_channels; ++ch2) {\n sample = Math.floor(range_max * channels[ch2][i] * amplitude_scale);\n\n if (sample < min_value[ch2]) {\n min_value[ch2] = sample;\n if (min_value[ch2] < range_min) {\n min_value[ch2] = range_min;\n }\n }\n if (sample > max_value[ch2]) {\n max_value[ch2] = sample;\n if (max_value[ch2] > range_max) {\n max_value[ch2] = range_max;\n }\n }\n }\n }\n\n if (++scale_counter === scale) {\n for (var ch3 = 0; ch3 < output_channels; ch3++) {\n if (options.bits === 8) {\n data_view.setInt8(offset++, min_value[ch3]);\n data_view.setInt8(offset++, max_value[ch3]);\n }\n else {\n data_view.setInt16(offset, min_value[ch3], true);\n data_view.setInt16(offset + 2, max_value[ch3], true);\n offset += 4;\n }\n min_value[ch3] = Infinity;\n max_value[ch3] = -Infinity;\n }\n scale_counter = 0;\n }\n }\n\n if (scale_counter > 0) {\n for (var ch4 = 0; ch4 < output_channels; ch4++) {\n if (options.bits === 8) {\n data_view.setInt8(offset++, min_value[ch4]);\n data_view.setInt8(offset++, max_value[ch4]);\n }\n else {\n data_view.setInt16(offset, min_value[ch4], true);\n data_view.setInt16(offset + 2, max_value[ch4], true);\n }\n }\n }\n\n return buffer;\n}\n\nself.onmessage = function(e) {\n var msg = e.data;\n try {\n var result = generateWaveformData({\n scale: msg.scale,\n bits: msg.bits,\n amplitude_scale: msg.amplitude_scale,\n split_channels: msg.split_channels,\n length: msg.length,\n sample_rate: msg.sample_rate,\n channels: msg.channels\n });\n self.postMessage({ id: msg.id, buffer: result }, [result]);\n } catch (err) {\n self.postMessage({ id: msg.id, error: err.message || String(err) });\n }\n};\n`;\n\n// ────────────────────────────────────────────────────────────────────────────\n// Promise-based worker API (runs on the main thread)\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface PeaksWorkerApi {\n generate(params: {\n id: string;\n channels: ArrayBuffer[];\n length: number;\n sampleRate: number;\n scale: number;\n bits: 8 | 16;\n splitChannels: boolean;\n }): Promise<WaveformData>;\n terminate(): void;\n}\n\ninterface PendingEntry {\n resolve: (value: WaveformData) => void;\n reject: (reason: unknown) => void;\n}\n\nlet idCounter = 0;\n\nexport function createPeaksWorker(): PeaksWorkerApi {\n let worker: Worker;\n try {\n const blob = new Blob([workerSource], { type: 'application/javascript' });\n const url = URL.createObjectURL(blob);\n worker = new Worker(url);\n URL.revokeObjectURL(url);\n } catch (err) {\n // Worker creation can fail in CSP-restricted environments that block blob: URLs.\n // Return a no-op API that rejects all generate calls gracefully.\n console.warn('[waveform-playlist] Failed to create peaks worker (CSP restriction?):', err);\n return {\n generate() {\n return Promise.reject(new Error('Worker creation failed'));\n },\n terminate() {\n /* no-op */\n },\n };\n }\n\n const pending = new Map<string, PendingEntry>();\n let terminated = false;\n\n worker.onmessage = (e: MessageEvent) => {\n const msg = e.data;\n const entry = pending.get(msg.id);\n if (!entry) return;\n pending.delete(msg.id);\n\n if (msg.error) {\n entry.reject(new Error(msg.error));\n } else {\n try {\n const waveformData = WaveformData.create(msg.buffer);\n entry.resolve(waveformData);\n } catch (err) {\n entry.reject(err);\n }\n }\n };\n\n worker.onerror = (e: ErrorEvent) => {\n terminated = true;\n worker.terminate();\n for (const [, entry] of pending) {\n entry.reject(e.error ?? new Error(e.message));\n }\n pending.clear();\n };\n\n return {\n generate(params) {\n if (terminated) return Promise.reject(new Error('Worker terminated'));\n const messageId = String(++idCounter);\n\n return new Promise<WaveformData>((resolve, reject) => {\n pending.set(messageId, { resolve, reject });\n\n worker.postMessage(\n {\n id: messageId,\n scale: params.scale,\n bits: params.bits,\n amplitude_scale: 1.0,\n split_channels: params.splitChannels,\n length: params.length,\n sample_rate: params.sampleRate,\n channels: params.channels,\n },\n params.channels // Transfer ownership\n );\n });\n },\n\n terminate() {\n terminated = true;\n worker.terminate();\n for (const [, entry] of pending) {\n entry.reject(new Error('Worker terminated'));\n }\n pending.clear();\n },\n };\n}\n","import React, {\n createContext,\n useContext,\n useState,\n useEffect,\n useRef,\n useCallback,\n useMemo,\n type ReactNode,\n} from 'react';\nimport { ThemeProvider } from 'styled-components';\nimport type { MediaElementPlayout, FadeConfig } from '@waveform-playlist/media-element-playout';\nimport { resolveMediaElementPlayout } from './playout/resolveMediaElementPlayout';\nimport { type WaveformDataObject } from '@waveform-playlist/core';\nimport { type WaveformPlaylistTheme, defaultTheme } from '@waveform-playlist/ui-components';\nimport type { AnnotationData } from '@waveform-playlist/core';\nimport { extractPeaksFromWaveformData } from './waveformDataLoader';\nimport type WaveformData from 'waveform-data';\nimport type { PeakData } from '@waveform-playlist/core';\nimport type { ClipPeaks, TrackClipPeaks } from './WaveformPlaylistContext';\nimport { useAnimationFrameLoop } from './hooks/useAnimationFrameLoop';\n\n// Configuration for a single media element track\nexport interface MediaElementTrackConfig {\n /** Audio source URL or Blob URL */\n source: string;\n /** Pre-computed waveform data (required for visualization) */\n waveformData: WaveformDataObject;\n /** Track name for display */\n name?: string;\n /** Fade in configuration (requires audioContext on provider) */\n fadeIn?: FadeConfig;\n /** Fade out configuration (requires audioContext on provider) */\n fadeOut?: FadeConfig;\n}\n\n// Context values for animation (high-frequency updates)\nexport interface MediaElementAnimationContextValue {\n isPlaying: boolean;\n currentTime: number;\n currentTimeRef: React.RefObject<number>;\n}\n\n// Context values for playlist state\nexport interface MediaElementStateContextValue {\n continuousPlay: boolean;\n annotations: AnnotationData[];\n activeAnnotationId: string | null;\n playbackRate: number;\n isAutomaticScroll: boolean;\n}\n\n// Context values for controls\nexport interface MediaElementControlsContextValue {\n play: (startTime?: number) => void;\n pause: () => void;\n stop: () => void;\n seekTo: (time: number) => void;\n setPlaybackRate: (rate: number) => void;\n setContinuousPlay: (enabled: boolean) => void;\n setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>>;\n setActiveAnnotationId: (id: string | null) => void;\n setAutomaticScroll: (enabled: boolean) => void;\n setScrollContainer: (element: HTMLDivElement | null) => void;\n scrollContainerRef: React.RefObject<HTMLDivElement | null>;\n}\n\n// Context values for playlist data\nexport interface MediaElementDataContextValue {\n duration: number;\n peaksDataArray: TrackClipPeaks[];\n sampleRate: number;\n waveHeight: number;\n timeScaleHeight: number;\n samplesPerPixel: number;\n playoutRef: React.RefObject<MediaElementPlayout | null>;\n controls: { show: boolean; width: number };\n barWidth: number;\n barGap: number;\n /** Draw bars with pill-shaped rounded caps (radius barWidth/2). */\n roundedBars: boolean;\n progressBarWidth: number;\n fadeIn?: FadeConfig;\n fadeOut?: FadeConfig;\n}\n\n// Create contexts\nconst MediaElementAnimationContext = createContext<MediaElementAnimationContextValue | null>(null);\nconst MediaElementStateContext = createContext<MediaElementStateContextValue | null>(null);\nconst MediaElementControlsContext = createContext<MediaElementControlsContextValue | null>(null);\nconst MediaElementDataContext = createContext<MediaElementDataContextValue | null>(null);\n\nexport interface MediaElementPlaylistProviderProps {\n /** Single track configuration with source URL and waveform data */\n track: MediaElementTrackConfig;\n /** Initial samples per pixel (zoom level) */\n samplesPerPixel?: number;\n /** Height of each waveform track */\n waveHeight?: number;\n /** Show timescale */\n timescale?: boolean;\n /** Initial playback rate (0.5 to 2.0) */\n playbackRate?: number;\n /** Whether to preserve pitch when changing playback rate (default: true).\n * Set to false when using an external pitch processor like SoundTouch. */\n preservesPitch?: boolean;\n /** Enable automatic scroll to keep playhead centered */\n automaticScroll?: boolean;\n /** Theme configuration */\n theme?: Partial<WaveformPlaylistTheme>;\n /** Track controls configuration */\n controls?: { show: boolean; width: number };\n /** Annotations */\n annotationList?: {\n annotations?: AnnotationData[];\n isContinuousPlay?: boolean;\n };\n /** Width of waveform bars */\n barWidth?: number;\n /** Gap between waveform bars */\n barGap?: number;\n /** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */\n roundedBars?: boolean;\n /** Width of progress bars */\n progressBarWidth?: number;\n /** Callback when annotations are changed (drag, edit, etc.) */\n onAnnotationsChange?: (annotations: AnnotationData[]) => void;\n /**\n * AudioContext for Web Audio routing (fades, effects).\n * When provided, audio routes through Web Audio nodes:\n * HTMLAudioElement → MediaElementSourceNode → fadeGain → volumeGain → destination\n *\n * Without this, playback uses HTMLAudioElement directly (no fades or effects).\n * Each provider instance should use its own AudioContext or share one —\n * createMediaElementSource() is called once per audio element.\n */\n audioContext?: AudioContext;\n /** Callback when audio is ready */\n onReady?: () => void;\n /** Called when playout initialization fails (e.g. a missing optional peer,\n * a throwing `createPlayout`, or an `addTrack` error). The provider already\n * logs the error; use this to surface it in your UI. */\n onError?: (err: Error) => void;\n /** Factory for a custom MediaElement playout. When omitted, the bundled engine\n * (@waveform-playlist/media-element-playout) is dynamically imported. */\n createPlayout?: () => MediaElementPlayout;\n children: ReactNode;\n}\n\n/**\n * MediaElementPlaylistProvider\n *\n * A simplified playlist provider for single-track playback using HTMLAudioElement.\n * Key features:\n * - Pitch-preserving playback rate (0.5x - 2.0x)\n * - Pre-computed peaks visualization (no AudioBuffer needed)\n * - Simpler API than full WaveformPlaylistProvider\n *\n * Use this for:\n * - Language learning apps (speed control)\n * - Podcast players\n * - Single-track audio viewers\n *\n * For multi-track editing, use WaveformPlaylistProvider instead.\n */\nexport const MediaElementPlaylistProvider: React.FC<MediaElementPlaylistProviderProps> = ({\n track,\n samplesPerPixel: initialSamplesPerPixel = 1024,\n waveHeight = 100,\n timescale = false,\n playbackRate: initialPlaybackRate = 1,\n preservesPitch = true,\n automaticScroll = false,\n theme: userTheme,\n controls = { show: false, width: 0 },\n annotationList,\n barWidth = 1,\n barGap = 0,\n roundedBars = false,\n progressBarWidth: progressBarWidthProp,\n audioContext,\n onAnnotationsChange,\n onReady,\n onError,\n createPlayout,\n children,\n}) => {\n const progressBarWidth = progressBarWidthProp ?? barWidth + barGap;\n\n // State\n const [isPlaying, setIsPlaying] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [peaksDataArray, setPeaksDataArray] = useState<TrackClipPeaks[]>([]);\n const [playbackRate, setPlaybackRateState] = useState(initialPlaybackRate);\n // Annotations are derived from prop (single source of truth in parent)\n // In v6, annotations must be pre-parsed (numeric start/end). Use parseAeneas() from @waveform-playlist/annotations before passing.\n const annotations = useMemo(() => {\n if (!annotationList?.annotations) return [];\n if (process.env.NODE_ENV !== 'production' && annotationList.annotations.length > 0) {\n const first = annotationList.annotations[0] as unknown as Record<string, unknown>;\n if (typeof first.start !== 'number' || typeof first.end !== 'number') {\n console.error(\n '[waveform-playlist] Annotations must have numeric start/end values. ' +\n 'In v6, use parseAeneas() from @waveform-playlist/annotations before passing annotations. ' +\n 'Received start type: ' +\n typeof first.start\n );\n return [];\n }\n }\n return annotationList.annotations;\n }, [annotationList?.annotations]);\n\n // Ref for animation loop (avoids restarting loop on annotation change)\n const annotationsRef = useRef<AnnotationData[]>(annotations);\n annotationsRef.current = annotations;\n\n const [activeAnnotationId, setActiveAnnotationIdState] = useState<string | null>(null);\n const [continuousPlay, setContinuousPlayState] = useState(\n annotationList?.isContinuousPlay ?? false\n );\n const [samplesPerPixel] = useState(initialSamplesPerPixel);\n const [isAutomaticScroll, setIsAutomaticScroll] = useState(automaticScroll);\n\n // Refs\n const playoutRef = useRef<MediaElementPlayout | null>(null);\n const currentTimeRef = useRef<number>(0);\n const continuousPlayRef = useRef<boolean>(continuousPlay);\n const activeAnnotationIdRef = useRef<string | null>(null);\n const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n const isAutomaticScrollRef = useRef<boolean>(automaticScroll);\n const samplesPerPixelRef = useRef<number>(initialSamplesPerPixel);\n const { startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();\n\n // Sync refs\n useEffect(() => {\n continuousPlayRef.current = continuousPlay;\n }, [continuousPlay]);\n\n useEffect(() => {\n isAutomaticScrollRef.current = isAutomaticScroll;\n }, [isAutomaticScroll]);\n\n // Custom setter for activeAnnotationId\n const setActiveAnnotationId = useCallback((value: string | null) => {\n activeAnnotationIdRef.current = value;\n setActiveAnnotationIdState(value);\n }, []);\n\n const setContinuousPlay = useCallback((value: boolean) => {\n continuousPlayRef.current = value;\n setContinuousPlayState(value);\n }, []);\n\n // Memoize setScrollContainer callback\n const setScrollContainer = useCallback((element: HTMLDivElement | null) => {\n scrollContainerRef.current = element;\n }, []);\n\n // Get sample rate from waveform data\n const sampleRate = track.waveformData.sample_rate;\n\n // Initialize playout and load track\n useEffect(() => {\n let cancelled = false;\n let createdPlayout: MediaElementPlayout | null = null;\n\n (async () => {\n try {\n const playout = await resolveMediaElementPlayout({\n createPlayout,\n playbackRate: initialPlaybackRate,\n preservesPitch,\n });\n if (cancelled) {\n playout.dispose();\n return;\n }\n createdPlayout = playout;\n\n playout.addTrack({\n source: track.source,\n peaks: track.waveformData,\n name: track.name,\n audioContext,\n fadeIn: track.fadeIn,\n fadeOut: track.fadeOut,\n });\n\n // Set up time update callback\n const mediaTrack = playout.getTrack(playout['track']?.id ?? '');\n if (mediaTrack) {\n mediaTrack.setOnTimeUpdateCallback((time) => {\n currentTimeRef.current = time;\n });\n }\n\n // Set up playback complete callback\n playout.setOnPlaybackComplete(() => {\n stopAnimationFrameLoop();\n setIsPlaying(false);\n setActiveAnnotationId(null);\n currentTimeRef.current = 0;\n setCurrentTime(0);\n });\n\n playoutRef.current = playout;\n setDuration(track.waveformData.duration);\n onReady?.();\n } catch (err) {\n console.warn('[waveform-playlist] MediaElement playout init failed: ' + String(err));\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n })();\n\n return () => {\n cancelled = true;\n stopAnimationFrameLoop();\n if (createdPlayout) {\n createdPlayout.dispose();\n }\n playoutRef.current = null;\n };\n }, [\n track.source,\n track.waveformData,\n track.name,\n track.fadeIn,\n track.fadeOut,\n audioContext,\n initialPlaybackRate,\n preservesPitch,\n onReady,\n onError,\n stopAnimationFrameLoop,\n setActiveAnnotationId,\n createPlayout,\n ]);\n\n // Generate peaks from waveform data\n useEffect(() => {\n try {\n const extractedPeaks = extractPeaksFromWaveformData(\n track.waveformData as WaveformData,\n samplesPerPixel,\n 0, // channel index\n 0, // offset\n Math.ceil(track.waveformData.duration * sampleRate) // duration in samples\n );\n\n const clipPeaks: ClipPeaks = {\n clipId: 'media-element-clip',\n trackName: track.name ?? 'Track',\n peaks: {\n length: extractedPeaks.length,\n data: [extractedPeaks.data],\n bits: extractedPeaks.bits,\n } as PeakData,\n startSample: 0,\n durationSamples: Math.ceil(track.waveformData.duration * sampleRate),\n };\n\n setPeaksDataArray([[clipPeaks]]);\n } catch (err) {\n console.warn('[waveform-playlist] Failed to extract peaks from waveform data:', err);\n }\n }, [track.waveformData, track.name, samplesPerPixel, sampleRate]);\n\n // Animation loop\n const startAnimationLoop = useCallback(() => {\n const updateTime = () => {\n const time = playoutRef.current?.getCurrentTime() ?? 0;\n currentTimeRef.current = time;\n\n // Handle annotation playback\n const currentAnnotations = annotationsRef.current;\n if (currentAnnotations.length > 0) {\n const currentAnnotation = currentAnnotations.find(\n (ann) => time >= ann.start && time < ann.end\n );\n\n if (continuousPlayRef.current) {\n if (currentAnnotation && currentAnnotation.id !== activeAnnotationIdRef.current) {\n setActiveAnnotationId(currentAnnotation.id);\n } else if (!currentAnnotation && activeAnnotationIdRef.current !== null) {\n // Clear the active annotation when we're past it, but don't stop playback\n // Let playback continue until the audio element fires its 'ended' event\n setActiveAnnotationId(null);\n }\n } else {\n if (activeAnnotationIdRef.current) {\n const activeAnnotation = currentAnnotations.find(\n (ann) => ann.id === activeAnnotationIdRef.current\n );\n if (activeAnnotation && time >= activeAnnotation.end) {\n playoutRef.current?.stop();\n setIsPlaying(false);\n return;\n }\n } else if (currentAnnotation) {\n setActiveAnnotationId(currentAnnotation.id);\n }\n }\n }\n\n // Handle automatic scroll - continuously center the playhead\n if (isAutomaticScrollRef.current && scrollContainerRef.current) {\n const container = scrollContainerRef.current;\n const pixelPosition = (time * sampleRate) / samplesPerPixelRef.current;\n const containerWidth = container.clientWidth;\n\n // Continuously scroll to keep playhead centered\n const targetScrollLeft = Math.max(0, pixelPosition - containerWidth / 2);\n container.scrollLeft = targetScrollLeft;\n }\n\n startAnimationFrameLoop(updateTime);\n };\n\n startAnimationFrameLoop(updateTime);\n }, [setActiveAnnotationId, sampleRate, startAnimationFrameLoop]);\n\n const stopAnimationLoop = stopAnimationFrameLoop;\n\n // Playback controls\n const play = useCallback(\n (startTime?: number) => {\n if (!playoutRef.current) return;\n\n const actualStartTime = startTime ?? currentTimeRef.current;\n playoutRef.current.play(undefined, actualStartTime);\n setIsPlaying(true);\n startAnimationLoop();\n },\n [startAnimationLoop]\n );\n\n const pause = useCallback(() => {\n if (!playoutRef.current) return;\n\n playoutRef.current.pause();\n setIsPlaying(false);\n stopAnimationLoop();\n setCurrentTime(playoutRef.current.getCurrentTime());\n }, [stopAnimationLoop]);\n\n const stop = useCallback(() => {\n if (!playoutRef.current) return;\n\n playoutRef.current.stop();\n setIsPlaying(false);\n stopAnimationLoop();\n currentTimeRef.current = 0;\n setCurrentTime(0);\n setActiveAnnotationId(null);\n }, [stopAnimationLoop, setActiveAnnotationId]);\n\n const seekTo = useCallback(\n (time: number) => {\n const clampedTime = Math.max(0, Math.min(time, duration));\n currentTimeRef.current = clampedTime;\n setCurrentTime(clampedTime);\n\n if (playoutRef.current) {\n playoutRef.current.seekTo(clampedTime);\n }\n },\n [duration]\n );\n\n const setPlaybackRate = useCallback((rate: number) => {\n const clampedRate = Math.max(0.5, Math.min(2.0, rate));\n setPlaybackRateState(clampedRate);\n if (playoutRef.current) {\n playoutRef.current.setPlaybackRate(clampedRate);\n }\n }, []);\n\n const timeScaleHeight = timescale ? 30 : 0;\n\n // Context values\n const animationValue: MediaElementAnimationContextValue = useMemo(\n () => ({\n isPlaying,\n currentTime,\n currentTimeRef,\n }),\n [isPlaying, currentTime]\n );\n\n const stateValue: MediaElementStateContextValue = useMemo(\n () => ({\n continuousPlay,\n annotations,\n activeAnnotationId,\n playbackRate,\n isAutomaticScroll,\n }),\n [continuousPlay, annotations, activeAnnotationId, playbackRate, isAutomaticScroll]\n );\n\n // Stable callback ref for onAnnotationsChange to avoid re-creating controls context\n const onAnnotationsChangeRef = useRef(onAnnotationsChange);\n onAnnotationsChangeRef.current = onAnnotationsChange;\n\n const setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>> = useCallback(\n (action) => {\n const updated = typeof action === 'function' ? action(annotationsRef.current) : action;\n if (!onAnnotationsChangeRef.current) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n 'waveform-playlist: setAnnotations was called but no onAnnotationsChange callback is provided. ' +\n 'Annotation edits will not persist. Pass onAnnotationsChange to MediaElementPlaylistProvider to handle annotation updates.'\n );\n }\n return;\n }\n onAnnotationsChangeRef.current(updated);\n },\n []\n );\n\n const controlsValue: MediaElementControlsContextValue = useMemo(\n () => ({\n play,\n pause,\n stop,\n seekTo,\n setPlaybackRate,\n setContinuousPlay,\n setAnnotations,\n setActiveAnnotationId,\n setAutomaticScroll: (enabled: boolean) => {\n setIsAutomaticScroll(enabled);\n },\n setScrollContainer,\n scrollContainerRef,\n }),\n [\n play,\n pause,\n stop,\n seekTo,\n setPlaybackRate,\n setContinuousPlay,\n setAnnotations,\n setActiveAnnotationId,\n setScrollContainer,\n ]\n );\n\n const dataValue: MediaElementDataContextValue = useMemo(\n () => ({\n duration,\n peaksDataArray,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n samplesPerPixel,\n playoutRef,\n controls,\n barWidth,\n barGap,\n roundedBars,\n progressBarWidth,\n fadeIn: track.fadeIn,\n fadeOut: track.fadeOut,\n }),\n [\n duration,\n peaksDataArray,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n samplesPerPixel,\n controls,\n barWidth,\n barGap,\n roundedBars,\n progressBarWidth,\n track.fadeIn,\n track.fadeOut,\n ]\n );\n\n const mergedTheme = { ...defaultTheme, ...userTheme };\n\n return (\n <ThemeProvider theme={mergedTheme}>\n <MediaElementAnimationContext.Provider value={animationValue}>\n <MediaElementStateContext.Provider value={stateValue}>\n <MediaElementControlsContext.Provider value={controlsValue}>\n <MediaElementDataContext.Provider value={dataValue}>\n {children}\n </MediaElementDataContext.Provider>\n </MediaElementControlsContext.Provider>\n </MediaElementStateContext.Provider>\n </MediaElementAnimationContext.Provider>\n </ThemeProvider>\n );\n};\n\n// Hooks\nexport const useMediaElementAnimation = () => {\n const context = useContext(MediaElementAnimationContext);\n if (!context) {\n throw new Error('useMediaElementAnimation must be used within MediaElementPlaylistProvider');\n }\n return context;\n};\n\nexport const useMediaElementState = () => {\n const context = useContext(MediaElementStateContext);\n if (!context) {\n throw new Error('useMediaElementState must be used within MediaElementPlaylistProvider');\n }\n return context;\n};\n\nexport const useMediaElementControls = () => {\n const context = useContext(MediaElementControlsContext);\n if (!context) {\n throw new Error('useMediaElementControls must be used within MediaElementPlaylistProvider');\n }\n return context;\n};\n\nexport const useMediaElementData = () => {\n const context = useContext(MediaElementDataContext);\n if (!context) {\n throw new Error('useMediaElementData must be used within MediaElementPlaylistProvider');\n }\n return context;\n};\n","import type { MediaElementPlayout } from '@waveform-playlist/media-element-playout';\n\n/** Options for {@link resolveMediaElementPlayout}. */\nexport interface ResolveMediaElementPlayoutOptions {\n /** Consumer-supplied factory. When present, the default engine is never imported. */\n createPlayout?: () => MediaElementPlayout;\n /** Initial playback rate forwarded to the default engine. */\n playbackRate?: number;\n /** Whether to preserve pitch on rate change (default engine only). */\n preservesPitch?: boolean;\n}\n\nconst INSTALL_HINT =\n '@waveform-playlist/media-element-playout is required for the default MediaElement engine. ' +\n 'Install with: npm install @waveform-playlist/media-element-playout — or pass a custom `createPlayout`.';\n\n/**\n * Resolve a MediaElement playout. With `createPlayout`, returns its result and\n * never imports the default engine. Otherwise dynamically imports\n * `@waveform-playlist/media-element-playout`, rethrowing a friendly install hint\n * (and console.warn-ing the original error) when the optional peer is absent.\n */\nexport async function resolveMediaElementPlayout(\n opts: ResolveMediaElementPlayoutOptions\n): Promise<MediaElementPlayout> {\n if (opts.createPlayout) {\n return opts.createPlayout();\n }\n\n let mod: typeof import('@waveform-playlist/media-element-playout');\n try {\n mod = await import('@waveform-playlist/media-element-playout');\n } catch (originalErr) {\n console.warn(\n '[waveform-playlist] @waveform-playlist/media-element-playout dynamic import failed: ' +\n String(originalErr)\n );\n throw new Error(INSTALL_HINT);\n }\n\n return new mod.MediaElementPlayout({\n playbackRate: opts.playbackRate,\n preservesPitch: opts.preservesPitch,\n });\n}\n","import React, { useCallback } from 'react';\nimport { BaseControlButton } from '@waveform-playlist/ui-components';\nimport {\n usePlaybackAnimation,\n usePlaylistState,\n usePlaylistControls,\n usePlaylistData,\n} from '../WaveformPlaylistContext';\n\nexport const PlayButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying, currentTimeRef } = usePlaybackAnimation();\n const { selectionStart, selectionEnd, isLoopEnabled } = usePlaylistState();\n const { play } = usePlaylistControls();\n\n const handleClick = async () => {\n const hasSelection = selectionStart !== selectionEnd && selectionEnd > selectionStart;\n\n if (hasSelection && !isLoopEnabled) {\n // Without loop: Play selection region only, then stop\n const duration = selectionEnd - selectionStart;\n await play(selectionStart, duration);\n } else {\n // With loop or no selection: Play from current cursor position.\n // Transport loop handles boundaries when loop is enabled.\n await play(currentTimeRef.current ?? 0);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} disabled={isPlaying} className={className}>\n Play\n </BaseControlButton>\n );\n};\n\nexport const PauseButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying } = usePlaybackAnimation();\n const { pause } = usePlaylistControls();\n\n return (\n <BaseControlButton onClick={pause} disabled={!isPlaying} className={className}>\n Pause\n </BaseControlButton>\n );\n};\n\nexport const StopButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying } = usePlaybackAnimation();\n const { stop } = usePlaylistControls();\n\n return (\n <BaseControlButton onClick={stop} disabled={!isPlaying} className={className}>\n Stop\n </BaseControlButton>\n );\n};\n\nexport const RewindButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying } = usePlaybackAnimation();\n const { play, setCurrentTime } = usePlaylistControls();\n\n const handleClick = () => {\n setCurrentTime(0);\n\n if (isPlaying) {\n play(0);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} className={className}>\n Rewind\n </BaseControlButton>\n );\n};\n\nexport const FastForwardButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isPlaying } = usePlaybackAnimation();\n const { play, setCurrentTime } = usePlaylistControls();\n const { duration } = usePlaylistData();\n\n const handleClick = () => {\n setCurrentTime(duration);\n\n if (isPlaying) {\n play(duration);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} className={className}>\n Fast Forward\n </BaseControlButton>\n );\n};\n\nexport const SkipBackwardButton: React.FC<{ skipAmount?: number; className?: string }> = ({\n skipAmount = 5,\n className,\n}) => {\n const { currentTimeRef, isPlaying } = usePlaybackAnimation();\n const { play, setCurrentTime } = usePlaylistControls();\n\n const handleClick = () => {\n const newTime = Math.max(0, (currentTimeRef.current ?? 0) - skipAmount);\n setCurrentTime(newTime);\n\n if (isPlaying) {\n play(newTime);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} className={className}>\n Skip Backward\n </BaseControlButton>\n );\n};\n\nexport const SkipForwardButton: React.FC<{ skipAmount?: number; className?: string }> = ({\n skipAmount = 5,\n className,\n}) => {\n const { currentTimeRef, isPlaying } = usePlaybackAnimation();\n const { play, setCurrentTime } = usePlaylistControls();\n const { duration } = usePlaylistData();\n\n const handleClick = () => {\n const newTime = Math.min(duration, (currentTimeRef.current ?? 0) + skipAmount);\n setCurrentTime(newTime);\n\n if (isPlaying) {\n play(newTime);\n }\n };\n\n return (\n <BaseControlButton onClick={handleClick} className={className}>\n Skip Forward\n </BaseControlButton>\n );\n};\n\nexport const LoopButton: React.FC<{ className?: string }> = ({ className }) => {\n const { isLoopEnabled, loopStart, loopEnd } = usePlaylistState();\n const { setLoopEnabled, setLoopRegion } = usePlaylistControls();\n const { duration } = usePlaylistData();\n\n const hasValidLoopRegion = loopStart !== loopEnd && loopEnd > loopStart;\n\n const handleClick = () => {\n if (!isLoopEnabled && !hasValidLoopRegion) {\n // Create a default loop region when enabling loop without one\n // Default to first 10 seconds or 25% of duration, whichever is smaller\n const defaultEnd = Math.min(10, duration * 0.25);\n setLoopRegion(0, Math.max(1, defaultEnd)); // At least 1 second\n }\n setLoopEnabled(!isLoopEnabled);\n };\n\n return (\n <BaseControlButton\n onClick={handleClick}\n className={className}\n title={isLoopEnabled ? 'Disable loop' : 'Enable loop'}\n >\n {isLoopEnabled ? 'Loop On' : 'Loop Off'}\n </BaseControlButton>\n );\n};\n\nexport const SetLoopRegionButton: React.FC<{ className?: string }> = ({ className }) => {\n const { selectionStart, selectionEnd, loopStart, loopEnd } = usePlaylistState();\n const { setLoopRegionFromSelection, clearLoopRegion } = usePlaylistControls();\n\n const hasValidSelection = selectionStart !== selectionEnd && selectionEnd > selectionStart;\n const hasLoopRegion = loopStart !== loopEnd && loopEnd > loopStart;\n\n const handleClick = () => {\n if (hasLoopRegion) {\n clearLoopRegion();\n } else {\n setLoopRegionFromSelection();\n }\n };\n\n return (\n <BaseControlButton\n onClick={handleClick}\n disabled={!hasValidSelection && !hasLoopRegion}\n className={className}\n title={\n hasLoopRegion\n ? 'Clear loop region'\n : hasValidSelection\n ? 'Set loop region from selection'\n : 'Create a selection first'\n }\n >\n {hasLoopRegion ? 'Clear Loop' : 'Set Loop'}\n </BaseControlButton>\n );\n};\n\nexport interface ClearAllButtonProps {\n onClearAll: () => void;\n label?: string;\n className?: string;\n}\n\nexport const ClearAllButton: React.FC<ClearAllButtonProps> = ({\n onClearAll,\n label = 'Clear All',\n className,\n}) => {\n const { stop } = usePlaylistControls();\n\n const handleClick = useCallback(() => {\n stop();\n onClearAll();\n }, [stop, onClearAll]);\n\n return (\n <BaseControlButton onClick={handleClick} className={className} title=\"Remove all tracks\">\n {label}\n </BaseControlButton>\n );\n};\n","import React from 'react';\nimport { BaseControlButton } from '@waveform-playlist/ui-components';\nimport { usePlaylistControls, usePlaylistData } from '../WaveformPlaylistContext';\n\nexport const ZoomInButton: React.FC<{ className?: string; disabled?: boolean }> = ({\n className,\n disabled,\n}) => {\n const { zoomIn } = usePlaylistControls();\n const { canZoomIn } = usePlaylistData();\n\n return (\n <BaseControlButton onClick={zoomIn} disabled={disabled || !canZoomIn} className={className}>\n Zoom In\n </BaseControlButton>\n );\n};\n\nexport const ZoomOutButton: React.FC<{ className?: string; disabled?: boolean }> = ({\n className,\n disabled,\n}) => {\n const { zoomOut } = usePlaylistControls();\n const { canZoomOut } = usePlaylistData();\n\n return (\n <BaseControlButton onClick={zoomOut} disabled={disabled || !canZoomOut} className={className}>\n Zoom Out\n </BaseControlButton>\n );\n};\n","import React, { useRef, useEffect } from 'react';\nimport {\n MasterVolumeControl as BaseMasterVolumeControl,\n TimeFormatSelect as BaseTimeFormatSelect,\n AutomaticScrollCheckbox as BaseAutomaticScrollCheckbox,\n SelectionTimeInputs as BaseSelectionTimeInputs,\n formatTime,\n} from '@waveform-playlist/ui-components';\nimport styled from 'styled-components';\nimport {\n usePlaybackAnimation,\n usePlaylistState,\n usePlaylistControls,\n usePlaylistData,\n} from '../WaveformPlaylistContext';\n\n/**\n * Master volume control that uses the playlist context\n */\nexport const MasterVolumeControl: React.FC<{ className?: string }> = ({ className }) => {\n const { masterVolume } = usePlaylistData();\n const { setMasterVolume } = usePlaylistControls();\n\n return (\n <BaseMasterVolumeControl\n volume={masterVolume}\n onChange={setMasterVolume}\n className={className}\n />\n );\n};\n\n/**\n * Time format selector that uses the playlist context\n */\nexport const TimeFormatSelect: React.FC<{ className?: string }> = ({ className }) => {\n const { timeFormat } = usePlaylistData();\n const { setTimeFormat } = usePlaylistControls();\n\n return <BaseTimeFormatSelect value={timeFormat} onChange={setTimeFormat} className={className} />;\n};\n\nconst PositionDisplay = styled.span`\n font-family: 'Courier New', Monaco, monospace;\n font-size: 1rem;\n font-weight: 600;\n color: ${(props) => props.theme?.textColor || '#333'};\n user-select: none;\n`;\n\n/**\n * Audio position display that uses the playlist context.\n * Updates via the shared animation frame registry — no own rAF loop.\n * Direct DOM manipulation avoids React re-renders.\n */\nexport const AudioPosition: React.FC<{ className?: string }> = ({ className }) => {\n const timeRef = useRef<HTMLSpanElement>(null);\n const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } =\n usePlaybackAnimation();\n const { timeFormat: format } = usePlaylistData();\n\n // Register per-frame callback during playback — uses raw time for display\n useEffect(() => {\n const id = 'audio-position';\n if (isPlaying) {\n registerFrameCallback(id, ({ time }) => {\n if (timeRef.current) {\n timeRef.current.textContent = formatTime(time, format);\n }\n });\n }\n return () => unregisterFrameCallback(id);\n }, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);\n\n // Update when stopped (for seeks)\n useEffect(() => {\n if (!isPlaying && timeRef.current) {\n timeRef.current.textContent = formatTime(currentTimeRef.current ?? 0, format);\n }\n });\n\n return (\n <PositionDisplay ref={timeRef} className={className} aria-label=\"Audio position\">\n {formatTime(currentTimeRef.current ?? 0, format)}\n </PositionDisplay>\n );\n};\n\n/**\n * Selection time inputs that use the playlist context\n */\nexport const SelectionTimeInputs: React.FC<{ className?: string }> = ({ className }) => {\n const { selectionStart, selectionEnd } = usePlaylistState();\n const { setSelection } = usePlaylistControls();\n\n return (\n <BaseSelectionTimeInputs\n selectionStart={selectionStart}\n selectionEnd={selectionEnd}\n onSelectionChange={setSelection}\n className={className}\n />\n );\n};\n\n/**\n * Automatic scroll checkbox that uses the playlist context\n * Uses split contexts to avoid re-rendering during animation\n */\nexport const AutomaticScrollCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n const { isAutomaticScroll } = usePlaylistState();\n const { setAutomaticScroll } = usePlaylistControls();\n\n return (\n <BaseAutomaticScrollCheckbox\n checked={isAutomaticScroll}\n onChange={setAutomaticScroll}\n className={className}\n />\n );\n};\n","import { createContext, useContext } from 'react';\nimport type {\n AnnotationData,\n AnnotationAction,\n AnnotationActionOptions,\n RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\n\n/**\n * Props the browser package passes to the AnnotationText component.\n * Mirrors what PlaylistAnnotationList and MediaElementAnnotationList actually use.\n */\nexport interface AnnotationTextIntegrationProps {\n annotations: AnnotationData[];\n activeAnnotationId?: string;\n shouldScrollToActive?: boolean;\n scrollActivePosition?: ScrollLogicalPosition;\n scrollActiveContainer?: 'nearest' | 'all';\n editable?: boolean;\n controls?: AnnotationAction[];\n annotationListConfig?: AnnotationActionOptions;\n height?: number;\n onAnnotationUpdate?: (updatedAnnotations: AnnotationData[]) => void;\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n}\n\n/**\n * Props the browser package passes to the AnnotationBox component.\n * Mirrors what PlaylistVisualization and MediaElementPlaylist actually use.\n */\nexport interface AnnotationBoxIntegrationProps {\n annotationId: string;\n annotationIndex: number;\n startPosition: number;\n endPosition: number;\n label?: string;\n color?: string;\n isActive?: boolean;\n onClick?: () => void;\n editable?: boolean;\n}\n\n/**\n * Props the browser package passes to the AnnotationBoxesWrapper component.\n * Mirrors what PlaylistVisualization and MediaElementPlaylist actually use.\n */\nexport interface AnnotationBoxesWrapperIntegrationProps {\n children?: React.ReactNode;\n height?: number;\n width?: number;\n}\n\n/**\n * Interface for annotation integration provided by @waveform-playlist/annotations.\n *\n * The browser package defines what it needs, and the optional annotations package\n * provides it via <AnnotationProvider>.\n */\nexport interface AnnotationIntegration {\n // Parser functions\n parseAeneas: (data: unknown) => AnnotationData;\n serializeAeneas: (annotation: AnnotationData) => unknown;\n\n // Visualization components (typed with the props the browser package actually passes)\n AnnotationText: React.ComponentType<AnnotationTextIntegrationProps>;\n AnnotationBox: React.ComponentType<AnnotationBoxIntegrationProps>;\n AnnotationBoxesWrapper: React.ComponentType<AnnotationBoxesWrapperIntegrationProps>;\n\n // Control components\n ContinuousPlayCheckbox: React.ComponentType<{\n checked: boolean;\n onChange: (checked: boolean) => void;\n className?: string;\n }>;\n LinkEndpointsCheckbox: React.ComponentType<{\n checked: boolean;\n onChange: (checked: boolean) => void;\n className?: string;\n }>;\n EditableCheckbox: React.ComponentType<{\n checked: boolean;\n onChange: (checked: boolean) => void;\n className?: string;\n }>;\n DownloadAnnotationsButton: React.ComponentType<{\n annotations: AnnotationData[];\n filename?: string;\n className?: string;\n }>;\n}\n\nexport const AnnotationIntegrationContext = createContext<AnnotationIntegration | null>(null);\n\nexport const AnnotationIntegrationProvider = AnnotationIntegrationContext.Provider;\n\n/**\n * Hook to access annotation integration provided by @waveform-playlist/annotations.\n * Throws if used without <AnnotationProvider> wrapping the component tree.\n *\n * Follows the Kent C. Dodds pattern:\n * https://kentcdodds.com/blog/how-to-use-react-context-effectively\n */\nexport function useAnnotationIntegration(): AnnotationIntegration {\n const context = useContext(AnnotationIntegrationContext);\n if (!context) {\n throw new Error(\n 'useAnnotationIntegration must be used within <AnnotationProvider>. ' +\n 'Install @waveform-playlist/annotations and wrap your app with <AnnotationProvider>. ' +\n 'See: https://waveform-playlist.naomiaro.com/docs/guides/annotations'\n );\n }\n return context;\n}\n","import React from 'react';\nimport { useAnnotationIntegration } from '../AnnotationIntegrationContext';\nimport { usePlaylistState, usePlaylistControls } from '../WaveformPlaylistContext';\n\n/**\n * Continuous play checkbox that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const ContinuousPlayCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n const { ContinuousPlayCheckbox: Base } = useAnnotationIntegration();\n const { continuousPlay } = usePlaylistState();\n const { setContinuousPlay } = usePlaylistControls();\n\n return <Base checked={continuousPlay} onChange={setContinuousPlay} className={className} />;\n};\n\n/**\n * Link endpoints checkbox that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const LinkEndpointsCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n const { LinkEndpointsCheckbox: Base } = useAnnotationIntegration();\n const { linkEndpoints } = usePlaylistState();\n const { setLinkEndpoints } = usePlaylistControls();\n\n return <Base checked={linkEndpoints} onChange={setLinkEndpoints} className={className} />;\n};\n\n/**\n * Editable annotations checkbox that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const EditableCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n const { EditableCheckbox: Base } = useAnnotationIntegration();\n const { annotationsEditable } = usePlaylistState();\n const { setAnnotationsEditable } = usePlaylistControls();\n\n return (\n <Base checked={annotationsEditable} onChange={setAnnotationsEditable} className={className} />\n );\n};\n\n/**\n * Download annotations button that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const DownloadAnnotationsButton: React.FC<{ filename?: string; className?: string }> = ({\n filename,\n className,\n}) => {\n const { DownloadAnnotationsButton: Base } = useAnnotationIntegration();\n const { annotations } = usePlaylistState();\n\n return <Base annotations={annotations} filename={filename} className={className} />;\n};\n","import { createContext, useContext } from 'react';\n\nconst ClipInteractionContext = createContext(false);\n\nexport const ClipInteractionContextProvider = ClipInteractionContext.Provider;\n\nexport function useClipInteractionEnabled(): boolean {\n return useContext(ClipInteractionContext);\n}\n","import React, {\n useContext,\n useEffect,\n useRef,\n useState,\n useMemo,\n type ReactNode,\n useCallback,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport styled from 'styled-components';\nimport {\n Playlist,\n Track as TrackComponent,\n Clip,\n Selection,\n TimescaleLoopRegion,\n PlaylistInfoContext,\n DevicePixelRatioProvider,\n SmartScale,\n CloseButton,\n Controls,\n Header,\n Button,\n ButtonGroup,\n Slider,\n SliderWrapper,\n VolumeDownIcon,\n VolumeUpIcon,\n TrackMenu,\n useTheme,\n waveformColorToCss,\n type RenderPlayheadFunction,\n SpectrogramLabels,\n CLIP_HEADER_HEIGHT,\n} from '@waveform-playlist/ui-components';\nimport { AnnotationIntegrationContext } from '../AnnotationIntegrationContext';\nimport {\n usePlaybackAnimation,\n usePlaylistState,\n usePlaylistControls,\n usePlaylistData,\n type ClipPeaks,\n} from '../WaveformPlaylistContext';\nimport { resolveRecordingOffsetSamples, type Peaks } from '@waveform-playlist/core';\nimport { AnimatedPlayhead } from './AnimatedPlayhead';\nimport { ChannelWithProgress } from './ChannelWithProgress';\nimport type { SpectrogramConfig } from '@waveform-playlist/core';\nimport type { AnnotationAction } from '@waveform-playlist/core';\nimport type { AnnotationData, GetAnnotationBoxLabelFn } from '../types/annotations';\nimport { SpectrogramIntegrationContext } from '../SpectrogramIntegrationContext';\n\n// Default duration in seconds for empty tracks (used for recording workflow)\nconst DEFAULT_EMPTY_TRACK_DURATION = 60;\n\ninterface ControlSlotProps {\n readonly $height: number;\n readonly $isSelected?: boolean;\n}\n\n/**\n * Height-synced container for each track's controls in the ControlsColumn.\n * Uses the same height formula as Track: waveHeight * numChannels + clipHeaderHeight.\n */\nconst ControlSlot = styled.div.attrs<ControlSlotProps>((props) => ({\n style: { height: `${props.$height}px` },\n}))<ControlSlotProps>`\n overflow: hidden;\n pointer-events: auto;\n background: ${(props) => props.theme.surfaceColor};\n transition: background 0.15s ease-in-out;\n ${(props) => props.$isSelected && `background: ${props.theme.selectedTrackControlsBackground};`}\n`;\n\nexport interface PlaylistVisualizationProps {\n renderTrackControls?: (trackIndex: number) => ReactNode;\n renderTick?: (label: string, pixelPosition: number) => ReactNode;\n /** Custom playhead render function. Receives position (pixels) and color from theme. */\n renderPlayhead?: RenderPlayheadFunction;\n annotationControls?: AnnotationAction[];\n /**\n * Custom function to generate the label shown on annotation boxes in the waveform.\n * Receives the annotation data and its index, returns a string label.\n * Default: annotation.id\n */\n getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n className?: string;\n showClipHeaders?: boolean;\n interactiveClips?: boolean;\n showFades?: boolean;\n /**\n * Enable mobile-optimized touch interactions.\n * When true, increases touch target sizes for clip boundaries.\n * Use with useDragSensors({ touchOptimized: true }) for best results.\n */\n touchOptimized?: boolean;\n /** Callback when a track's close button is clicked. Only renders close button when provided. */\n onRemoveTrack?: (trackIndex: number) => void;\n // Live recording state for real-time waveform preview\n recordingState?: {\n isRecording: boolean;\n trackId: string;\n startSample: number;\n durationSamples: number;\n peaks: (Int8Array | Int16Array)[];\n bits: 8 | 16;\n /**\n * Latency offset (seconds) to skip in the live preview. Absolute replacement\n * for the auto-computed outputLatency + lookAhead value. Pass the same value\n * given to useIntegratedRecording so preview and finalized clip match.\n */\n latencyOffset?: number;\n };\n}\n\n/**\n * Wrapper that isolates the custom playhead's hooks from PlaylistVisualization.\n * Calling renderPlayhead() directly would merge its hooks into the parent,\n * causing \"Rendered more hooks\" errors if renderPlayhead is conditionally provided.\n */\nconst CustomPlayhead: React.FC<{\n renderPlayhead: RenderPlayheadFunction;\n color: string;\n samplesPerPixel: number;\n sampleRate: number;\n}> = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {\n const {\n isPlaying,\n currentTimeRef,\n visualTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n getPlaybackTime,\n getAudioContextTime,\n } = usePlaybackAnimation();\n\n // Use visualTimeRef so the initial position matches the audible output\n // (the playhead's rAF loop also reads visualTimeRef for in-sync animation).\n const visualTime = visualTimeRef.current ?? currentTimeRef.current ?? 0;\n\n return renderPlayhead({\n position: (visualTime * sampleRate) / samplesPerPixel,\n color,\n isPlaying,\n currentTimeRef,\n visualTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n samplesPerPixel,\n sampleRate,\n controlsOffset: 0,\n getAudioContextTime,\n getPlaybackTime,\n }) as React.ReactElement;\n};\n\n/** Compute the maximum channel count for a track, considering both clip peaks and live recording. */\nfunction getTrackChannelCount(\n trackClipPeaks: ClipPeaks[],\n recordingState: PlaylistVisualizationProps['recordingState'],\n trackId: string | undefined,\n mono: boolean\n): number {\n const clipChannels =\n trackClipPeaks.length > 0\n ? Math.max(1, ...trackClipPeaks.map((clip) => clip.peaks.data.length))\n : 1;\n const recordingChannels =\n recordingState?.isRecording && recordingState.trackId === trackId\n ? mono\n ? 1\n : recordingState.peaks.length\n : 0;\n return Math.max(clipChannels, recordingChannels);\n}\n\n/**\n * Standalone playlist visualization component (WebAudio version).\n *\n * Renders the waveform tracks, timescale, annotations boxes, selection,\n * playhead, loop regions, and track controls — everything that lives\n * inside <Playlist> plus wrapping providers.\n *\n * Does NOT render AnnotationText (the annotation list below the waveform).\n * Pair with PlaylistAnnotationList for a full annotation editing UI.\n */\nexport const PlaylistVisualization: React.FC<PlaylistVisualizationProps> = ({\n renderTrackControls,\n renderTick,\n renderPlayhead,\n annotationControls: _annotationControls,\n getAnnotationBoxLabel,\n className,\n showClipHeaders = false,\n interactiveClips = false,\n showFades = false,\n touchOptimized = false,\n onRemoveTrack,\n recordingState,\n}) => {\n const theme = useTheme() as import('@waveform-playlist/ui-components').WaveformPlaylistTheme;\n\n const { isPlaying, getLookAhead, getOutputLatency } = usePlaybackAnimation();\n const {\n selectionStart,\n selectionEnd,\n annotations,\n activeAnnotationId,\n annotationsEditable,\n linkEndpoints: _linkEndpoints,\n continuousPlay,\n selectedTrackId,\n loopStart,\n loopEnd,\n isLoopEnabled,\n indefinitePlayback,\n fillViewport,\n } = usePlaylistState();\n const annotationIntegration = useContext(AnnotationIntegrationContext);\n const {\n setAnnotations: _setAnnotations,\n setActiveAnnotationId,\n setTrackMute,\n setTrackSolo,\n setTrackVolume,\n setTrackPan,\n setSelection,\n play,\n setScrollContainer,\n setSelectedTrackId,\n setCurrentTime,\n setLoopRegion,\n setRecordingActive,\n } = usePlaylistControls();\n const {\n peaksDataArray,\n trackStates,\n tracks,\n duration,\n samplesPerPixel,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n controls,\n barWidth,\n barGap,\n roundedBars,\n isReady,\n mono,\n } = usePlaylistData();\n\n // Optional spectrogram integration (only available when SpectrogramProvider is present)\n const spectrogram = useContext(SpectrogramIntegrationContext);\n\n // Sync the recording session (and its armed track) to the provider so the\n // armed track's existing content is transiently muted for the take —\n // punch-in replaces whatever it overlaps (#579/#589). The cleanup releases\n // the session on recording end AND on unmount, restoring the track's\n // previous mute state. Consumers that start playback in the same handler\n // as the recording should also call setRecordingActive eagerly so the\n // doomed material never blips before this effect commits.\n const recordingActive = !!recordingState?.isRecording;\n const armedTrackId = recordingState?.trackId ?? null;\n useEffect(() => {\n if (!recordingActive) return undefined;\n setRecordingActive(true, armedTrackId);\n return () => setRecordingActive(false);\n }, [recordingActive, armedTrackId, setRecordingActive]);\n\n // Per-track spectrogram rendering helpers (memoized) — only computed when spectrogram is available\n const perTrackSpectrogramHelpers = useMemo(() => {\n if (!spectrogram)\n return new Map<\n string,\n {\n colorLUT: Uint8Array;\n frequencyScaleFn: (f: number, minF: number, maxF: number) => number;\n config: SpectrogramConfig | undefined;\n }\n >();\n const helpers = new Map<\n string,\n {\n colorLUT: Uint8Array;\n frequencyScaleFn: (f: number, minF: number, maxF: number) => number;\n config: SpectrogramConfig | undefined;\n }\n >();\n tracks.forEach((track) => {\n const mode =\n spectrogram.trackSpectrogramOverrides.get(track.id)?.renderMode ??\n track.renderMode ??\n 'waveform';\n if (mode === 'waveform') return;\n const overrides = spectrogram.trackSpectrogramOverrides.get(track.id);\n const cm =\n overrides?.colorMap ??\n track.spectrogramColorMap ??\n spectrogram.spectrogramColorMap ??\n 'viridis';\n const cfg = overrides?.config ?? track.spectrogramConfig ?? spectrogram.spectrogramConfig;\n helpers.set(track.id, {\n colorLUT: spectrogram.getColorMap(cm),\n frequencyScaleFn: spectrogram.getFrequencyScale(cfg?.frequencyScale ?? 'mel'),\n config: cfg,\n });\n });\n return helpers;\n }, [tracks, spectrogram]);\n\n // State for spectrogram settings modal\n const [settingsModalTrackId, setSettingsModalTrackId] = useState<string | null>(null);\n\n const [isSelecting, setIsSelecting] = useState(false);\n const mouseDownTimeRef = useRef<number>(0);\n\n const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n\n const handleScrollContainerRef = useCallback(\n (element: HTMLDivElement | null) => {\n scrollContainerRef.current = element;\n setScrollContainer(element);\n },\n [setScrollContainer]\n );\n\n // Calculate dimensions — derive duration directly from tracks prop to prevent width\n // shift. The `duration` state is set in an effect and lags tracks by at least one render.\n const tracksMaxDuration = tracks.reduce((max, track) => {\n return track.clips.reduce((clipMax, clip) => {\n const end = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n return Math.max(clipMax, end);\n }, max);\n }, 0);\n let displayDuration =\n tracksMaxDuration > 0\n ? tracksMaxDuration\n : duration > 0\n ? duration\n : DEFAULT_EMPTY_TRACK_DURATION;\n\n // Fill the visible scroll container when requested — indefinitePlayback\n // implies it (endless playback needs an endless-looking timeline);\n // fillViewport requests the layout alone.\n if (indefinitePlayback || fillViewport) {\n const containerWidth = scrollContainerRef.current?.clientWidth ?? 0;\n const minContainerDuration = (containerWidth * samplesPerPixel) / sampleRate;\n displayDuration = Math.max(displayDuration, minContainerDuration);\n }\n\n if (recordingState?.isRecording) {\n const recordingEndSample = recordingState.startSample + recordingState.durationSamples;\n const recordingEndTime = recordingEndSample / sampleRate;\n displayDuration = Math.max(displayDuration, recordingEndTime + 10);\n }\n\n const tracksFullWidth = Math.floor((displayDuration * sampleRate) / samplesPerPixel);\n\n const handleAnnotationClick = async (annotation: AnnotationData) => {\n setActiveAnnotationId(annotation.id);\n const playDuration = !continuousPlay ? annotation.end - annotation.start : undefined;\n try {\n await play(annotation.start, playDuration);\n } catch (err) {\n console.error(\n 'waveform-playlist: Failed to start playback for annotation',\n annotation.id,\n err\n );\n }\n };\n\n const selectTrack = useCallback(\n (trackIndex: number) => {\n if (trackIndex >= 0 && trackIndex < tracks.length) {\n const track = tracks[trackIndex];\n setSelectedTrackId(track.id);\n }\n },\n [tracks, setSelectedTrackId]\n );\n\n const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const clickTime = (x * samplesPerPixel) / sampleRate;\n\n const y = e.clientY - rect.top;\n const trackY = y;\n\n let cumulativeHeight = 0;\n let clickedTrackIndex = -1;\n\n for (let i = 0; i < peaksDataArray.length; i++) {\n const trackClipPeaks = peaksDataArray[i];\n const rawCh = getTrackChannelCount(trackClipPeaks, recordingState, tracks[i]?.id, mono);\n const trackMode =\n spectrogram?.trackSpectrogramOverrides.get(tracks[i]?.id)?.renderMode ??\n tracks[i]?.renderMode ??\n 'waveform';\n const effectiveCh = trackMode === 'both' ? rawCh * 2 : rawCh;\n const trackHeight = effectiveCh * waveHeight + (showClipHeaders ? 22 : 0);\n\n if (trackY >= cumulativeHeight && trackY < cumulativeHeight + trackHeight) {\n clickedTrackIndex = i;\n break;\n }\n cumulativeHeight += trackHeight;\n }\n\n if (clickedTrackIndex !== -1) {\n selectTrack(clickedTrackIndex);\n }\n\n mouseDownTimeRef.current = clickTime;\n setIsSelecting(true);\n setCurrentTime(clickTime);\n setSelection(clickTime, clickTime);\n };\n\n const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {\n if (!isSelecting || isPlaying) return;\n\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const moveTime = (x * samplesPerPixel) / sampleRate;\n\n const start = Math.min(selectionStart, moveTime);\n const end = Math.max(selectionStart, moveTime);\n setSelection(start, end);\n };\n\n const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {\n if (!isSelecting) return;\n\n setIsSelecting(false);\n\n // During playback, use the time captured at mouseDown — auto-scroll shifts the\n // overlay between mouseDown and mouseUp, so recomputing from getBoundingClientRect()\n // would produce a wrong (shifted) position.\n if (isPlaying) {\n const clickTime = Math.max(0, mouseDownTimeRef.current);\n setCurrentTime(clickTime);\n setSelection(clickTime, clickTime);\n play(clickTime);\n return;\n }\n\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const endTime = (x * samplesPerPixel) / sampleRate;\n\n const start = Math.min(selectionStart, endTime);\n const end = Math.max(selectionStart, endTime);\n\n if (Math.abs(end - start) < 0.1) {\n setCurrentTime(start);\n } else {\n setSelection(start, end);\n }\n };\n\n // Only show loading if we have tracks WITH clips but peaks haven't been computed yet.\n // Don't check audioBuffers — it's set in an effect and can be stale for one or more\n // renders after tracks change, causing the playlist to unmount and remount (layout shift).\n // Placeholder tracks (clips: []) bypass this check intentionally.\n const hasClips = tracks.some((track) => track.clips.length > 0);\n if (hasClips && peaksDataArray.length === 0) {\n return <div className={className}>Loading waveform...</div>;\n }\n\n // Build track controls slots for the ControlsColumn (outside scroll area)\n const trackControlsSlots = controls.show\n ? peaksDataArray.map((trackClipPeaks, trackIndex) => {\n const track = tracks[trackIndex];\n if (!track) return null;\n\n const trackState = trackStates[trackIndex] || {\n name: `Track ${trackIndex + 1}`,\n muted: false,\n soloed: false,\n volume: 1.0,\n pan: 0,\n };\n\n const hasMidiNotes = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);\n const effectiveRenderMode =\n spectrogram?.trackSpectrogramOverrides.get(track.id)?.renderMode ??\n track.renderMode ??\n (hasMidiNotes ? 'piano-roll' : 'waveform');\n\n const maxChannels = getTrackChannelCount(trackClipPeaks, recordingState, track.id, mono);\n\n // Height must match Track component: waveHeight * numChannels + clipHeaderHeight\n const slotHeight = waveHeight * maxChannels + (showClipHeaders ? CLIP_HEADER_HEIGHT : 0);\n\n const trackControlContent = renderTrackControls ? (\n renderTrackControls(trackIndex)\n ) : (\n <Controls onClick={() => selectTrack(trackIndex)}>\n <Header style={{ justifyContent: 'center', position: 'relative' }}>\n {onRemoveTrack && (\n <CloseButton\n onClick={(e) => {\n e.stopPropagation();\n onRemoveTrack(trackIndex);\n }}\n />\n )}\n <span\n style={{\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n padding: '0 24px',\n display: 'block',\n }}\n >\n {trackState.name || `Track ${trackIndex + 1}`}\n </span>\n {spectrogram?.renderMenuItems && (\n <span style={{ position: 'absolute', right: 0, top: 0 }}>\n <TrackMenu\n items={(onClose) =>\n spectrogram.renderMenuItems!({\n renderMode: effectiveRenderMode,\n onRenderModeChange: (mode) =>\n spectrogram.setTrackRenderMode(track.id, mode),\n onOpenSettings: () => setSettingsModalTrackId(track.id),\n onClose,\n })\n }\n />\n </span>\n )}\n </Header>\n <ButtonGroup>\n <Button\n $variant={trackState.muted ? 'danger' : 'outline'}\n onClick={() => setTrackMute(trackIndex, !trackState.muted)}\n >\n Mute\n </Button>\n <Button\n $variant={trackState.soloed ? 'info' : 'outline'}\n onClick={() => setTrackSolo(trackIndex, !trackState.soloed)}\n >\n Solo\n </Button>\n </ButtonGroup>\n <SliderWrapper>\n <VolumeDownIcon />\n <Slider\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={trackState.volume}\n onChange={(e) => setTrackVolume(trackIndex, parseFloat(e.target.value))}\n />\n <VolumeUpIcon />\n </SliderWrapper>\n <SliderWrapper>\n <span>L</span>\n <Slider\n min=\"-1\"\n max=\"1\"\n step=\"0.01\"\n value={trackState.pan}\n onChange={(e) => setTrackPan(trackIndex, parseFloat(e.target.value))}\n />\n <span>R</span>\n </SliderWrapper>\n </Controls>\n );\n\n return (\n <ControlSlot\n key={track.id}\n $height={slotHeight}\n $isSelected={track.id === selectedTrackId}\n >\n {trackControlContent}\n </ControlSlot>\n );\n })\n : undefined;\n\n return (\n <DevicePixelRatioProvider>\n <PlaylistInfoContext.Provider\n value={{\n samplesPerPixel,\n sampleRate,\n zoomLevels: [samplesPerPixel],\n waveHeight,\n timeScaleHeight,\n duration: displayDuration * 1000,\n controls,\n barWidth,\n barGap,\n roundedBars,\n }}\n >\n <Playlist\n theme={theme}\n backgroundColor={\n theme.playlistBackgroundColor || waveformColorToCss(theme.waveOutlineColor)\n }\n timescaleBackgroundColor={theme.timescaleBackgroundColor}\n timescaleWidth={tracksFullWidth}\n tracksWidth={tracksFullWidth}\n controlsWidth={controls.show ? controls.width : 0}\n onTracksMouseDown={handleMouseDown}\n onTracksMouseMove={handleMouseMove}\n onTracksMouseUp={handleMouseUp}\n scrollContainerRef={handleScrollContainerRef}\n isSelecting={isSelecting}\n data-playlist-state={isReady ? 'ready' : 'loading'}\n trackControlsSlots={trackControlsSlots}\n timescaleGapHeight={timeScaleHeight > 0 ? timeScaleHeight + 1 : 0}\n timescale={\n timeScaleHeight > 0 ? (\n <>\n <SmartScale renderTick={renderTick} />\n {isLoopEnabled && (\n <TimescaleLoopRegion\n startPosition={(Math.min(loopStart, loopEnd) * sampleRate) / samplesPerPixel}\n endPosition={(Math.max(loopStart, loopEnd) * sampleRate) / samplesPerPixel}\n markerColor={theme.loopMarkerColor}\n regionColor={theme.loopRegionColor}\n minPosition={0}\n maxPosition={tracksFullWidth}\n onLoopRegionChange={(startPixels, endPixels) => {\n const startSeconds = (startPixels * samplesPerPixel) / sampleRate;\n const endSeconds = (endPixels * samplesPerPixel) / sampleRate;\n setLoopRegion(startSeconds, endSeconds);\n }}\n />\n )}\n </>\n ) : undefined\n }\n >\n <>\n {peaksDataArray.map((trackClipPeaks, trackIndex) => {\n const track = tracks[trackIndex];\n if (!track) return null;\n\n const hasMidiNotes = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);\n const effectiveRenderMode =\n spectrogram?.trackSpectrogramOverrides.get(track.id)?.renderMode ??\n track.renderMode ??\n (hasMidiNotes ? 'piano-roll' : 'waveform');\n\n const maxChannels = getTrackChannelCount(\n trackClipPeaks,\n recordingState,\n track.id,\n mono\n );\n\n return (\n <TrackComponent\n key={track.id}\n numChannels={maxChannels}\n backgroundColor={\n effectiveRenderMode === 'piano-roll'\n ? theme.pianoRollBackgroundColor || '#1a1a2e'\n : waveformColorToCss(theme.waveOutlineColor)\n }\n offset={0}\n width={tracksFullWidth}\n hasClipHeaders={showClipHeaders}\n trackId={track.id}\n isSelected={track.id === selectedTrackId}\n >\n {effectiveRenderMode !== 'waveform' &&\n (() => {\n const helpers = perTrackSpectrogramHelpers.get(track.id);\n const trackCfg = helpers?.config;\n if (!trackCfg?.labels || !helpers) return null;\n return (\n <SpectrogramLabels\n waveHeight={waveHeight}\n numChannels={maxChannels}\n frequencyScaleFn={helpers.frequencyScaleFn}\n minFrequency={trackCfg.minFrequency ?? 0}\n maxFrequency={trackCfg.maxFrequency ?? sampleRate / 2}\n labelsColor={trackCfg.labelsColor}\n labelsBackground={trackCfg.labelsBackground}\n renderMode={effectiveRenderMode as 'spectrogram' | 'both'}\n hasClipHeaders={showClipHeaders}\n />\n );\n })()}\n {trackClipPeaks.map((clip, clipIndex) => {\n const peaksData = clip.peaks;\n const width = peaksData.length;\n\n return (\n <Clip\n key={clip.clipId}\n clipId={clip.clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n trackName={clip.trackName}\n startSample={clip.startSample}\n durationSamples={clip.durationSamples}\n samplesPerPixel={samplesPerPixel}\n showHeader={showClipHeaders}\n disableHeaderDrag={!interactiveClips}\n isSelected={track.id === selectedTrackId}\n trackId={track.id}\n fadeIn={clip.fadeIn}\n fadeOut={clip.fadeOut}\n sampleRate={sampleRate}\n showFades={showFades}\n touchOptimized={touchOptimized}\n onMouseDown={(e) => {\n const target = e.target as HTMLElement;\n const isDraggable = target.closest(\n '[role=\"button\"][aria-roledescription=\"draggable\"]'\n );\n if (isDraggable) {\n return;\n }\n selectTrack(trackIndex);\n }}\n >\n {peaksData.data.map((channelPeaks: Peaks, channelIndex: number) => {\n return (\n <ChannelWithProgress\n key={`${clip.clipId}-${channelIndex}`}\n index={channelIndex}\n data={channelPeaks}\n bits={peaksData.bits}\n length={width}\n isSelected={track.id === selectedTrackId}\n clipStartSample={clip.startSample}\n clipDurationSamples={clip.durationSamples}\n renderMode={clip.midiNotes ? 'piano-roll' : effectiveRenderMode}\n midiNotes={clip.midiNotes}\n clipSampleRate={clip.sampleRate}\n clipOffsetSeconds={\n clip.offsetSamples != null\n ? clip.offsetSamples / (clip.sampleRate || sampleRate)\n : 0\n }\n samplesPerPixel={samplesPerPixel}\n spectrogramClipId={clip.clipId}\n spectrogramOnCanvasRegister={\n spectrogram ? spectrogram.registerSpectrogramCanvas : undefined\n }\n spectrogramOnCanvasUnregister={\n spectrogram ? spectrogram.unregisterSpectrogramCanvas : undefined\n }\n />\n );\n })}\n </Clip>\n );\n })}\n {recordingState?.isRecording &&\n recordingState.trackId === track.id &&\n recordingState.peaks[0]?.length > 0 &&\n (() => {\n // Strip leading-silence peaks so the visible preview matches\n // audible content. Recorder runs at real time, but the user's\n // first reaction lands ~outputLatency+lookAhead seconds into\n // the buffer (they hadn't heard the backing track yet). Without\n // this, the playhead — which now uses audible time — would\n // appear behind the right edge of the recorded waveform.\n // Mirrors useIntegratedRecording's finalization compensation\n // (shared `resolveRecordingOffsetSamples`) and dawcore's preview-skip\n // pattern. `getLookAhead()` reads from the same engine the\n // playhead's animation loop uses — keeps the two in lockstep.\n const latencyOffsetSamples = resolveRecordingOffsetSamples({\n overrideSeconds: recordingState.latencyOffset,\n outputLatency: getOutputLatency(),\n lookAhead: getLookAhead(),\n sampleRate,\n });\n const latencyPixels = Math.floor(latencyOffsetSamples / samplesPerPixel);\n const skipPeakElements = latencyPixels * 2; // each pixel is a min/max pair\n const previewDuration = Math.max(\n 0,\n recordingState.durationSamples - latencyOffsetSamples\n );\n const previewChannels = (\n mono ? recordingState.peaks.slice(0, 1) : recordingState.peaks\n ).map((channelPeaks) =>\n skipPeakElements > 0 && skipPeakElements < channelPeaks.length\n ? channelPeaks.subarray(skipPeakElements)\n : channelPeaks\n );\n return (\n <Clip\n key={`${track.id}-recording`}\n clipId=\"recording-preview\"\n trackIndex={trackIndex}\n clipIndex={trackClipPeaks.length}\n trackName=\"Recording...\"\n startSample={recordingState.startSample}\n durationSamples={previewDuration}\n samplesPerPixel={samplesPerPixel}\n showHeader={showClipHeaders}\n disableHeaderDrag={true}\n isSelected={track.id === selectedTrackId}\n trackId={track.id}\n >\n {previewChannels.map((channelPeaks, chIdx) => (\n <ChannelWithProgress\n key={`${track.id}-recording-${chIdx}`}\n index={chIdx}\n data={channelPeaks}\n bits={recordingState.bits}\n length={Math.floor(channelPeaks.length / 2)}\n isSelected={track.id === selectedTrackId}\n clipStartSample={recordingState.startSample}\n clipDurationSamples={previewDuration}\n />\n ))}\n </Clip>\n );\n })()}\n </TrackComponent>\n );\n })}\n {annotations.length > 0 && annotationIntegration && (\n <annotationIntegration.AnnotationBoxesWrapper height={30} width={tracksFullWidth}>\n {annotations.map((annotation, index) => {\n const startPosition = (annotation.start * sampleRate) / samplesPerPixel;\n const endPosition = (annotation.end * sampleRate) / samplesPerPixel;\n const label = getAnnotationBoxLabel\n ? getAnnotationBoxLabel(annotation, index)\n : annotation.id;\n return (\n <annotationIntegration.AnnotationBox\n key={annotation.id}\n annotationId={annotation.id}\n annotationIndex={index}\n startPosition={startPosition}\n endPosition={endPosition}\n label={label}\n color=\"#ff9800\"\n isActive={annotation.id === activeAnnotationId}\n onClick={() => handleAnnotationClick(annotation)}\n editable={annotationsEditable}\n />\n );\n })}\n </annotationIntegration.AnnotationBoxesWrapper>\n )}\n {selectionStart !== selectionEnd && (\n <Selection\n startPosition={\n (Math.min(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n }\n endPosition={\n (Math.max(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n }\n color={theme.selectionColor}\n />\n )}\n {(isPlaying || selectionStart === selectionEnd) &&\n (renderPlayhead ? (\n <CustomPlayhead\n renderPlayhead={renderPlayhead}\n color={theme.playheadColor}\n samplesPerPixel={samplesPerPixel}\n sampleRate={sampleRate}\n />\n ) : (\n <AnimatedPlayhead color={theme.playheadColor} />\n ))}\n </>\n </Playlist>\n </PlaylistInfoContext.Provider>\n {spectrogram?.SettingsModal &&\n typeof document !== 'undefined' &&\n createPortal(\n <spectrogram.SettingsModal\n open={settingsModalTrackId !== null}\n onClose={() => setSettingsModalTrackId(null)}\n config={\n settingsModalTrackId !== null\n ? (spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)?.config ??\n tracks.find((t) => t.id === settingsModalTrackId)?.spectrogramConfig ??\n spectrogram.spectrogramConfig ??\n {})\n : {}\n }\n colorMap={\n settingsModalTrackId !== null\n ? (spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)?.colorMap ??\n tracks.find((t) => t.id === settingsModalTrackId)?.spectrogramColorMap ??\n spectrogram.spectrogramColorMap ??\n 'viridis')\n : 'viridis'\n }\n onApply={(newConfig, newColorMap) => {\n if (settingsModalTrackId !== null) {\n spectrogram.setTrackSpectrogramConfig(settingsModalTrackId, newConfig, newColorMap);\n }\n }}\n />,\n document.body\n )}\n </DevicePixelRatioProvider>\n );\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { usePlaybackAnimation, usePlaylistData } from '../WaveformPlaylistContext';\n\nconst PlayheadLine = styled.div.attrs<{ $color: string; $width: number }>((props) => ({\n style: {\n width: `${props.$width}px`,\n background: props.$color,\n },\n}))<{ $color: string; $width: number }>`\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n z-index: 100; /* Below sticky controls (z-index: 101) so playhead is hidden when scrolled behind controls */\n pointer-events: none;\n will-change: transform;\n`;\n\ninterface AnimatedPlayheadProps {\n color?: string;\n}\n\n/**\n * Animated playhead that updates position via the shared animation frame registry.\n * No own rAF loop — the WaveformPlaylistContext animation loop drives all visual updates.\n */\nexport const AnimatedPlayhead: React.FC<AnimatedPlayheadProps> = ({ color = '#ff0000' }) => {\n const playheadRef = useRef<HTMLDivElement>(null);\n\n const {\n isPlaying,\n currentTimeRef,\n visualTimeRef,\n registerFrameCallback,\n unregisterFrameCallback,\n } = usePlaybackAnimation();\n const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();\n\n // Register per-frame callback during playback\n useEffect(() => {\n const id = 'playhead';\n if (isPlaying) {\n registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {\n if (playheadRef.current) {\n const px = (visualTime * sr) / spp;\n playheadRef.current.style.transform = `translate3d(${px}px, 0, 0)`;\n }\n });\n }\n return () => unregisterFrameCallback(id);\n }, [isPlaying, registerFrameCallback, unregisterFrameCallback]);\n\n // Update position when not playing (seeks, stops, etc.) — no rAF needed.\n // Reads visualTimeRef so the static playhead lines up with audible output\n // (the pause/seek paths sync visualTimeRef when they update currentTimeRef).\n useEffect(() => {\n if (!isPlaying && playheadRef.current) {\n const time = visualTimeRef.current ?? currentTimeRef.current ?? 0;\n const position = (time * sampleRate) / samplesPerPixel;\n playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;\n }\n });\n\n return <PlayheadLine ref={playheadRef} $color={color} $width={progressBarWidth} data-playhead />;\n};\n","import React, { useId, useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport {\n clipPixelWidth as computeClipPixelWidth,\n type MidiNoteData,\n} from '@waveform-playlist/core';\nimport {\n SmartChannel,\n type SmartChannelProps,\n useTheme,\n usePlaylistInfo,\n type WaveformPlaylistTheme,\n waveformColorToCss,\n} from '@waveform-playlist/ui-components';\nimport { usePlaybackAnimation, usePlaylistData } from '../WaveformPlaylistContext';\n\nconst ChannelWrapper = styled.div`\n position: relative;\n`;\n\ninterface BackgroundProps {\n readonly $color: string;\n readonly $height: number;\n readonly $top: number;\n readonly $width: number;\n}\n\nconst Background = styled.div.attrs<BackgroundProps>((props) => ({\n style: {\n top: `${props.$top}px`,\n width: `${props.$width}px`,\n height: `${props.$height}px`,\n background: props.$color,\n },\n}))<BackgroundProps>`\n position: absolute;\n left: 0;\n z-index: 0;\n /* Force GPU compositing layer to prevent gradient flickering during scroll */\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\ninterface ProgressOverlayProps {\n readonly $color: string;\n readonly $height: number;\n readonly $top: number;\n readonly $width: number;\n}\n\nconst ProgressOverlay = styled.div.attrs<ProgressOverlayProps>((props) => ({\n style: {\n top: `${props.$top}px`,\n height: `${props.$height}px`,\n width: `${props.$width}px`,\n background: props.$color,\n transform: 'scaleX(0)',\n },\n}))<ProgressOverlayProps>`\n position: absolute;\n left: 0;\n pointer-events: none;\n z-index: 1;\n transform-origin: left;\n /* scaleX changes are composite-only (GPU) — no layout reflow per frame */\n will-change: transform;\n`;\n\nconst ChannelContainer = styled.div`\n position: relative;\n z-index: 2;\n`;\n\nexport interface ChannelWithProgressProps extends SmartChannelProps {\n /** Start sample of the clip containing this channel (for progress calculation) */\n clipStartSample: number;\n /** Duration in samples of the clip */\n clipDurationSamples: number;\n /** MIDI note data for piano-roll rendering */\n midiNotes?: MidiNoteData[];\n /** Sample rate of the clip (for MIDI note positioning) */\n clipSampleRate?: number;\n /** Clip offset in seconds (for MIDI note positioning) */\n clipOffsetSeconds?: number;\n}\n\n/**\n * SmartChannel wrapper that adds an animated progress overlay.\n * The progress overlay shows the \"played\" portion of the waveform.\n * Updates via the shared animation frame registry — no own rAF loop.\n */\nexport const ChannelWithProgress: React.FC<ChannelWithProgressProps> = ({\n clipStartSample,\n clipDurationSamples,\n midiNotes,\n clipSampleRate,\n clipOffsetSeconds,\n ...smartChannelProps\n}) => {\n const progressRef = useRef<HTMLDivElement>(null);\n const callbackId = useId();\n const theme = useTheme() as WaveformPlaylistTheme;\n const { waveHeight } = usePlaylistInfo();\n\n const {\n isPlaying,\n currentTimeRef,\n visualTimeRef,\n registerFrameCallback,\n unregisterFrameCallback,\n } = usePlaybackAnimation();\n const { samplesPerPixel, sampleRate } = usePlaylistData();\n\n const progressColor = theme?.waveProgressColor || 'rgba(0, 0, 0, 0.1)';\n\n // Use shared helper to compute clip pixel width (must match Clip.tsx sizing)\n // peaksData.length may be shorter than the clip when audio is shorter than configured duration\n const clipPixelWidth = computeClipPixelWidth(\n clipStartSample,\n clipDurationSamples,\n samplesPerPixel\n );\n\n // Register per-frame callback during playback — no own rAF loop\n useEffect(() => {\n if (isPlaying) {\n registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {\n if (progressRef.current) {\n const currentSample = visualTime * sr;\n const clipEndSample = clipStartSample + clipDurationSamples;\n\n let ratio = 0;\n if (currentSample <= clipStartSample) {\n ratio = 0;\n } else if (currentSample >= clipEndSample) {\n ratio = 1;\n } else {\n const playedSamples = currentSample - clipStartSample;\n ratio = playedSamples / clipDurationSamples;\n }\n\n // scaleX is composite-only — no layout reflow, GPU-accelerated\n progressRef.current.style.transform = `scaleX(${ratio})`;\n }\n });\n }\n return () => unregisterFrameCallback(callbackId);\n }, [\n isPlaying,\n clipStartSample,\n clipDurationSamples,\n callbackId,\n registerFrameCallback,\n unregisterFrameCallback,\n ]);\n\n // Also update when not playing (for seeks, stops, etc.). Reads visualTimeRef\n // so the static progress fill matches the playhead's audible position.\n useEffect(() => {\n if (!isPlaying && progressRef.current) {\n const currentTime = visualTimeRef.current ?? currentTimeRef.current ?? 0;\n const currentSample = currentTime * sampleRate;\n const clipEndSample = clipStartSample + clipDurationSamples;\n\n let ratio = 0;\n if (currentSample <= clipStartSample) {\n ratio = 0;\n } else if (currentSample >= clipEndSample) {\n ratio = 1;\n } else {\n const playedSamples = currentSample - clipStartSample;\n ratio = playedSamples / clipDurationSamples;\n }\n\n progressRef.current.style.transform = `scaleX(${ratio})`;\n }\n });\n\n // Get the draw mode from theme (defaults to 'inverted')\n const drawMode = theme?.waveformDrawMode || 'inverted';\n\n let backgroundColor;\n if (drawMode === 'inverted') {\n backgroundColor =\n smartChannelProps.isSelected && theme\n ? theme.selectedWaveFillColor\n : theme?.waveFillColor || 'white';\n } else {\n backgroundColor =\n smartChannelProps.isSelected && theme\n ? theme.selectedWaveOutlineColor\n : theme?.waveOutlineColor || 'grey';\n }\n\n // Use black background for spectrogram mode, themed background for piano-roll\n const isSpectrogramMode =\n smartChannelProps.renderMode === 'spectrogram' || smartChannelProps.renderMode === 'both';\n const isPianoRollMode = smartChannelProps.renderMode === 'piano-roll';\n const isBothMode = smartChannelProps.renderMode === 'both';\n const backgroundCss = isSpectrogramMode\n ? '#000'\n : isPianoRollMode\n ? theme?.pianoRollBackgroundColor || '#1a1a2e'\n : waveformColorToCss(backgroundColor);\n\n // In \"both\" mode each half (spectrogram + waveform) is waveHeight/2 so the track\n // container stays the same height as a single-mode track.\n const halfHeight = Math.floor(waveHeight / 2);\n const effectiveHeight = waveHeight;\n const effectiveTop = isBothMode\n ? smartChannelProps.index * waveHeight\n : smartChannelProps.index * waveHeight;\n\n // In \"both\" mode, the waveform portion needs its own (non-black) background\n const waveformBackgroundCss = waveformColorToCss(backgroundColor);\n\n return (\n <ChannelWrapper>\n {/* Background layer - color depends on draw mode */}\n {isBothMode ? (\n <>\n {/* Spectrogram portion: black background */}\n <Background\n $color=\"#000\"\n $height={halfHeight}\n $top={effectiveTop}\n $width={smartChannelProps.length}\n />\n {/* Waveform portion: themed background */}\n <Background\n $color={waveformBackgroundCss}\n $height={halfHeight}\n $top={effectiveTop + halfHeight}\n $width={smartChannelProps.length}\n />\n </>\n ) : (\n <Background\n $color={backgroundCss}\n $height={effectiveHeight}\n $top={effectiveTop}\n $width={smartChannelProps.length}\n />\n )}\n {/* Progress overlay - shows played portion with progress color (skip for piano-roll) */}\n {!isPianoRollMode && (\n <ProgressOverlay\n ref={progressRef}\n $color={progressColor}\n $height={effectiveHeight}\n $top={effectiveTop}\n $width={clipPixelWidth}\n />\n )}\n {/* Waveform canvas with transparent background */}\n <ChannelContainer>\n <SmartChannel\n {...smartChannelProps}\n transparentBackground\n midiNotes={midiNotes}\n sampleRate={clipSampleRate}\n clipOffsetSeconds={clipOffsetSeconds}\n />\n </ChannelContainer>\n </ChannelWrapper>\n );\n};\n","import { createContext, useContext } from 'react';\nimport type {\n SpectrogramConfig,\n ColorMapValue,\n RenderMode,\n TrackSpectrogramOverrides,\n} from '@waveform-playlist/core';\nimport type { TrackMenuItem } from '@waveform-playlist/ui-components';\n\n/**\n * Single-call canvas registration shape. Consolidates the OffscreenCanvas\n * transfer and per-canvas metadata that earlier required two separate\n * registration paths.\n *\n * Provider/orchestrator look up trackId from clipId and derive\n * globalPixelOffset = chunkIndex * MAX_CANVAS_WIDTH.\n */\nexport interface SpectrogramCanvasRegistration {\n canvasId: string;\n canvas: OffscreenCanvas;\n clipId: string;\n channelIndex: number;\n chunkIndex: number;\n widthPx: number;\n heightPx: number;\n}\n\nexport interface SpectrogramIntegration {\n trackSpectrogramOverrides: Map<string, TrackSpectrogramOverrides>;\n spectrogramConfig?: SpectrogramConfig;\n spectrogramColorMap?: ColorMapValue;\n setTrackRenderMode: (trackId: string, mode: RenderMode) => void;\n setTrackSpectrogramConfig: (\n trackId: string,\n config: SpectrogramConfig,\n colorMap?: ColorMapValue\n ) => void;\n /** Single-canvas registration. OffscreenCanvas is non-transferable — call once per canvas. */\n registerSpectrogramCanvas: (reg: SpectrogramCanvasRegistration) => void;\n unregisterSpectrogramCanvas: (canvasId: string) => void;\n /** Render spectrogram menu items for a track's context menu */\n renderMenuItems?: (props: {\n renderMode: string;\n onRenderModeChange: (mode: RenderMode) => void;\n onOpenSettings: () => void;\n onClose?: () => void;\n }) => TrackMenuItem[];\n /** Settings modal component provided by the spectrogram package */\n SettingsModal?: React.ComponentType<{\n open: boolean;\n onClose: () => void;\n config: SpectrogramConfig;\n colorMap: ColorMapValue;\n onApply: (config: SpectrogramConfig, colorMap: ColorMapValue) => void;\n }>;\n /** Get color lookup table for a color map name */\n getColorMap: (name: ColorMapValue) => Uint8Array;\n /** Get frequency scale function for a scale name */\n getFrequencyScale: (name: string) => (f: number, minF: number, maxF: number) => number;\n}\n\nexport const SpectrogramIntegrationContext = createContext<SpectrogramIntegration | null>(null);\n\nexport const SpectrogramIntegrationProvider = SpectrogramIntegrationContext.Provider;\n\n/**\n * Hook to access spectrogram integration provided by @waveform-playlist/spectrogram.\n * Throws if used without <SpectrogramProvider> wrapping the component tree.\n *\n * Follows the Kent C. Dodds pattern:\n * https://kentcdodds.com/blog/how-to-use-react-context-effectively\n */\nexport function useSpectrogramIntegration(): SpectrogramIntegration {\n const context = useContext(SpectrogramIntegrationContext);\n if (!context) {\n throw new Error(\n 'useSpectrogramIntegration must be used within <SpectrogramProvider>. ' +\n 'Install @waveform-playlist/spectrogram and wrap your app with <SpectrogramProvider>.'\n );\n }\n return context;\n}\n","import React, { useCallback } from 'react';\nimport type {\n AnnotationData,\n AnnotationAction,\n AnnotationActionOptions,\n RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\nimport { useAnnotationIntegration } from '../AnnotationIntegrationContext';\nimport { usePlaylistState, usePlaylistControls } from '../WaveformPlaylistContext';\nimport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport interface PlaylistAnnotationListProps {\n /** Height in pixels for the annotation text list */\n height?: number;\n /**\n * Custom render function for annotation items in the text list.\n * When provided, completely replaces the default annotation item rendering.\n */\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n /**\n * Callback when annotations are updated (e.g., text edited).\n * Called with the full updated annotations array.\n */\n onAnnotationUpdate?: OnAnnotationUpdateFn;\n /**\n * Action controls to show on each annotation item (e.g., delete, split).\n * Only rendered when `annotationsEditable` is true in context.\n */\n controls?: AnnotationAction[];\n /**\n * Override annotation list config. Falls back to context values\n * `{ linkEndpoints, continuousPlay }` if not provided.\n */\n annotationListConfig?: AnnotationActionOptions;\n /** Where to position the active annotation when auto-scrolling. Defaults to 'center'. */\n scrollActivePosition?: ScrollLogicalPosition;\n /** Which scrollable containers to scroll: 'nearest' or 'all'. Defaults to 'nearest'. */\n scrollActiveContainer?: 'nearest' | 'all';\n}\n\n/**\n * Standalone annotation text list component for WaveformPlaylistProvider (WebAudio).\n *\n * Requires @waveform-playlist/annotations with AnnotationProvider.\n * Throws if used without `<AnnotationProvider>` wrapping the component tree.\n */\nexport const PlaylistAnnotationList: React.FC<PlaylistAnnotationListProps> = ({\n height,\n renderAnnotationItem,\n onAnnotationUpdate,\n controls,\n annotationListConfig,\n scrollActivePosition = 'center',\n scrollActiveContainer = 'nearest',\n}) => {\n const { annotations, activeAnnotationId, annotationsEditable, linkEndpoints, continuousPlay } =\n usePlaylistState();\n const integration = useAnnotationIntegration();\n const { setAnnotations } = usePlaylistControls();\n\n const resolvedConfig = annotationListConfig ?? { linkEndpoints, continuousPlay };\n\n const handleAnnotationUpdate = useCallback(\n (updatedAnnotations: AnnotationData[]) => {\n setAnnotations(updatedAnnotations);\n onAnnotationUpdate?.(updatedAnnotations);\n },\n [setAnnotations, onAnnotationUpdate]\n );\n\n const { AnnotationText } = integration;\n\n return (\n <AnnotationText\n annotations={annotations}\n activeAnnotationId={activeAnnotationId ?? undefined}\n shouldScrollToActive={true}\n scrollActivePosition={scrollActivePosition}\n scrollActiveContainer={scrollActiveContainer}\n editable={annotationsEditable}\n controls={annotationsEditable ? controls : undefined}\n annotationListConfig={resolvedConfig}\n height={height}\n onAnnotationUpdate={handleAnnotationUpdate}\n renderAnnotationItem={renderAnnotationItem}\n />\n );\n};\n","import React, { ReactNode } from 'react';\nimport type { RenderPlayheadFunction } from '@waveform-playlist/ui-components';\nimport type {\n AnnotationAction,\n AnnotationActionOptions,\n RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\nimport { usePlaylistState } from '../WaveformPlaylistContext';\nimport { useClipInteractionEnabled } from '../contexts/ClipInteractionContext';\nimport type { GetAnnotationBoxLabelFn } from '../types/annotations';\nimport { PlaylistVisualization } from './PlaylistVisualization';\nimport { PlaylistAnnotationList } from './PlaylistAnnotationList';\n\nexport interface WaveformProps {\n renderTrackControls?: (trackIndex: number) => ReactNode;\n /** Custom render function for timescale tick labels. `label` is a formatted string\n * (bar/beat notation like \"2.3\" in beats mode, or \"m:ss\" in temporal mode). */\n renderTick?: (label: string, pixelPosition: number) => ReactNode;\n /** @deprecated Use `renderTick` instead. */\n renderTimestamp?: (timeMs: number, pixelPosition: number) => ReactNode;\n /** Custom playhead render function. Receives position (pixels) and color from theme. */\n renderPlayhead?: RenderPlayheadFunction;\n annotationControls?: AnnotationAction[];\n annotationListConfig?: AnnotationActionOptions;\n annotationTextHeight?: number; // Height in pixels for the annotation text list\n /**\n * Custom render function for annotation items in the text list.\n * Use this to completely customize how each annotation is displayed.\n */\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => ReactNode;\n /**\n * Custom function to generate the label shown on annotation boxes in the waveform.\n * Receives the annotation data and its index, returns a string label.\n * Default: annotation.id\n */\n getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n /** Where to position the active annotation when auto-scrolling: 'center', 'start', 'end', or 'nearest'. Defaults to 'center'. */\n scrollActivePosition?: ScrollLogicalPosition;\n /** Which scrollable containers to scroll: 'nearest' (only the annotation list) or 'all' (including viewport). Defaults to 'nearest'. */\n scrollActiveContainer?: 'nearest' | 'all';\n className?: string;\n showClipHeaders?: boolean; // Show headers on clips for visual organization\n interactiveClips?: boolean; // Enable dragging/trimming interactions on clips (auto-enabled by ClipInteractionProvider)\n showFades?: boolean; // Show fade in/out overlays on clips\n /**\n * Enable mobile-optimized touch interactions.\n * When true, increases touch target sizes for clip boundaries.\n * Use with useDragSensors({ touchOptimized: true }) for best results.\n */\n touchOptimized?: boolean;\n /** Callback when a track's close button is clicked. Only renders close button when provided. */\n onRemoveTrack?: (trackIndex: number) => void;\n // Live recording state for real-time waveform preview\n recordingState?: {\n isRecording: boolean;\n trackId: string; // Which track is being recorded into\n startSample: number; // Where recording started\n durationSamples: number; // Current recording length\n peaks: (Int8Array | Int16Array)[]; // Per-channel live peaks data\n bits: 8 | 16; // Bit depth of peak values\n /**\n * Latency offset (seconds) to skip in the live preview. Absolute replacement\n * for the auto-computed outputLatency + lookAhead value. Pass the same value\n * given to useIntegratedRecording so preview and finalized clip match.\n */\n latencyOffset?: number;\n };\n}\n\n/**\n * Waveform visualization component that uses the playlist context.\n *\n * Composes PlaylistVisualization (waveform + tracks) and\n * PlaylistAnnotationList (annotation text list below the waveform).\n */\nexport const Waveform: React.FC<WaveformProps> = ({\n renderTrackControls,\n renderTick,\n renderTimestamp,\n renderPlayhead,\n annotationControls,\n annotationListConfig,\n annotationTextHeight,\n renderAnnotationItem,\n getAnnotationBoxLabel,\n scrollActivePosition = 'center',\n scrollActiveContainer = 'nearest',\n className,\n showClipHeaders = false,\n interactiveClips = false,\n showFades = false,\n touchOptimized = false,\n onRemoveTrack,\n recordingState,\n}) => {\n const { annotations } = usePlaylistState();\n const clipInteractionEnabled = useClipInteractionEnabled();\n const effectiveInteractiveClips = interactiveClips || clipInteractionEnabled;\n\n return (\n <>\n <PlaylistVisualization\n renderTrackControls={renderTrackControls}\n renderTick={\n renderTick ??\n (renderTimestamp\n ? (label, pix) => renderTimestamp(parseFloat(label) || 0, pix)\n : undefined)\n }\n renderPlayhead={renderPlayhead}\n annotationControls={annotationControls}\n getAnnotationBoxLabel={getAnnotationBoxLabel}\n className={className}\n showClipHeaders={showClipHeaders}\n interactiveClips={effectiveInteractiveClips}\n showFades={showFades}\n touchOptimized={touchOptimized}\n onRemoveTrack={onRemoveTrack}\n recordingState={recordingState}\n />\n {annotations.length > 0 && (\n <PlaylistAnnotationList\n height={annotationTextHeight}\n renderAnnotationItem={renderAnnotationItem}\n controls={annotationControls}\n annotationListConfig={annotationListConfig}\n scrollActivePosition={scrollActivePosition}\n scrollActiveContainer={scrollActiveContainer}\n />\n )}\n </>\n );\n};\n","import React, { useContext, useRef, useState, useCallback } from 'react';\nimport { DragDropProvider } from '@dnd-kit/react';\nimport { RestrictToHorizontalAxis } from '@dnd-kit/abstract/modifiers';\nimport {\n Playlist,\n Track as TrackComponent,\n Clip,\n Selection,\n FadeOverlay,\n PlaylistInfoContext,\n DevicePixelRatioProvider,\n SmartScale,\n useTheme,\n waveformColorToCss,\n} from '@waveform-playlist/ui-components';\nimport type { RenderPlayheadFunction } from '@waveform-playlist/ui-components';\nimport { AnnotationIntegrationContext } from '../AnnotationIntegrationContext';\nimport type { Peaks } from '@waveform-playlist/core';\nimport {\n useMediaElementAnimation,\n useMediaElementState,\n useMediaElementControls,\n useMediaElementData,\n} from '../MediaElementPlaylistContext';\nimport { useAnnotationDragHandlers } from '../hooks/useAnnotationDragHandlers';\nimport { noDropAnimationPlugins } from '../plugins/noDropAnimationPlugins';\nimport { AnimatedMediaElementPlayhead } from './AnimatedMediaElementPlayhead';\nimport { ChannelWithMediaElementProgress } from './ChannelWithMediaElementProgress';\nimport type {\n AnnotationData,\n GetAnnotationBoxLabelFn,\n OnAnnotationUpdateFn,\n} from '../types/annotations';\n\n/**\n * Wrapper that isolates the custom playhead's hooks from MediaElementPlaylist.\n * Calling renderPlayhead() directly inside MediaElementPlaylist's render would\n * merge its hooks into MediaElementPlaylist's hook count, causing \"Rendered more\n * hooks\" errors if renderPlayhead is conditionally provided.\n */\n// Stable refs for MediaElement playback (no AudioContext, so these are always 0)\nconst ZERO_REF: React.RefObject<number> = { current: 0 };\n\nconst CustomMediaElementPlayhead: React.FC<{\n renderPlayhead: RenderPlayheadFunction;\n color: string;\n samplesPerPixel: number;\n sampleRate: number;\n}> = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {\n const { isPlaying, currentTimeRef } = useMediaElementAnimation();\n\n return renderPlayhead({\n position: ((currentTimeRef.current ?? 0) * sampleRate) / samplesPerPixel,\n color,\n isPlaying,\n currentTimeRef,\n playbackStartTimeRef: ZERO_REF,\n audioStartPositionRef: ZERO_REF,\n samplesPerPixel,\n sampleRate,\n controlsOffset: 0,\n }) as React.ReactElement;\n};\n\nexport interface MediaElementPlaylistProps {\n /** Custom function to generate the label shown on annotation boxes */\n getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n /** Whether annotation boundaries can be edited by dragging. Defaults to false. */\n editable?: boolean;\n /** Whether dragging one annotation boundary also moves the adjacent annotation's boundary. Defaults to false. */\n linkEndpoints?: boolean;\n /**\n * Callback when annotations are updated (e.g., boundaries dragged).\n * Called with the full updated annotations array.\n */\n onAnnotationUpdate?: OnAnnotationUpdateFn;\n /** Custom playhead render function. Receives position, color, and animation refs for smooth 60fps animation. */\n renderPlayhead?: RenderPlayheadFunction;\n /** Show fade in/out overlays on the waveform. Defaults to false. */\n showFades?: boolean;\n className?: string;\n}\n\n/**\n * Standalone waveform + annotation boxes component for MediaElementPlaylistProvider.\n *\n * Renders the waveform visualization, annotation boxes, selection, and playhead.\n * Does NOT render the annotation text list — use `MediaElementAnnotationList` for that.\n *\n * Must be used inside a `MediaElementPlaylistProvider`.\n *\n * This component can be placed independently in consumer layouts, allowing the\n * waveform and annotation list to be positioned separately (e.g., in different\n * panels or with custom elements between them).\n */\nexport const MediaElementPlaylist: React.FC<MediaElementPlaylistProps> = ({\n getAnnotationBoxLabel,\n editable = false,\n linkEndpoints: linkEndpointsProp = false,\n onAnnotationUpdate,\n renderPlayhead,\n showFades = false,\n className,\n}) => {\n const theme = useTheme() as import('@waveform-playlist/ui-components').WaveformPlaylistTheme;\n\n // MediaElement context hooks\n const { isPlaying } = useMediaElementAnimation();\n const { annotations, activeAnnotationId } = useMediaElementState();\n const annotationIntegration = useContext(AnnotationIntegrationContext);\n const { play, seekTo, setActiveAnnotationId, setAnnotations, setScrollContainer } =\n useMediaElementControls();\n const {\n duration,\n peaksDataArray,\n sampleRate,\n waveHeight,\n timeScaleHeight,\n samplesPerPixel,\n controls,\n playoutRef,\n barWidth,\n barGap,\n roundedBars,\n fadeIn,\n fadeOut,\n } = useMediaElementData();\n\n const [selectionStart, setSelectionStart] = useState(0);\n const [selectionEnd, setSelectionEnd] = useState(0);\n const [isSelecting, setIsSelecting] = useState(false);\n\n // Local ref for scroll container - also register with context for auto-scroll\n const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n\n // Callback to register scroll container with context\n const handleScrollContainerRef = useCallback(\n (el: HTMLDivElement | null) => {\n scrollContainerRef.current = el;\n setScrollContainer(el);\n },\n [setScrollContainer]\n );\n\n // Calculate dimensions\n const tracksFullWidth = Math.floor((duration * sampleRate) / samplesPerPixel);\n\n // Annotation click handler\n const handleAnnotationClick = useCallback(\n async (annotation: AnnotationData) => {\n setActiveAnnotationId(annotation.id);\n try {\n await play(annotation.start);\n } catch (err) {\n console.error(\n 'waveform-playlist: Failed to start playback for annotation',\n annotation.id,\n err\n );\n }\n },\n [setActiveAnnotationId, play]\n );\n\n // Handle annotation boundary updates\n const handleAnnotationUpdate = useCallback(\n (updatedAnnotations: AnnotationData[]) => {\n setAnnotations(updatedAnnotations);\n onAnnotationUpdate?.(updatedAnnotations);\n },\n [setAnnotations, onAnnotationUpdate]\n );\n\n // Drag handlers for annotation boundary editing\n const { onDragStart, onDragMove, onDragEnd } = useAnnotationDragHandlers({\n annotations,\n onAnnotationsChange: handleAnnotationUpdate,\n samplesPerPixel,\n duration,\n linkEndpoints: linkEndpointsProp,\n });\n\n // Mouse handlers for click-to-seek\n const mouseDownTimeRef = useRef<number>(0);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const clickTime = (x * samplesPerPixel) / sampleRate;\n\n mouseDownTimeRef.current = clickTime;\n setIsSelecting(true);\n setSelectionStart(clickTime);\n setSelectionEnd(clickTime);\n },\n [samplesPerPixel, sampleRate]\n );\n\n const handleMouseMove = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!isSelecting) return;\n\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const moveTime = (x * samplesPerPixel) / sampleRate;\n\n setSelectionEnd(moveTime);\n },\n [isSelecting, samplesPerPixel, sampleRate]\n );\n\n const handleMouseUp = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!isSelecting) return;\n\n setIsSelecting(false);\n\n // During playback, use the time captured at mouseDown — auto-scroll shifts the\n // overlay between mouseDown and mouseUp, so recomputing from getBoundingClientRect()\n // would produce a wrong (shifted) position.\n if (isPlaying && playoutRef.current) {\n const clickTime = Math.max(0, mouseDownTimeRef.current);\n seekTo(clickTime);\n setSelectionStart(clickTime);\n setSelectionEnd(clickTime);\n playoutRef.current.stop();\n play(clickTime);\n return;\n }\n\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const x = e.clientX - rect.left;\n const endTime = (x * samplesPerPixel) / sampleRate;\n\n const start = Math.min(selectionStart, endTime);\n const end = Math.max(selectionStart, endTime);\n\n // If it's just a click (not a drag), seek to that position\n if (Math.abs(end - start) < 0.1) {\n seekTo(start);\n setSelectionStart(start);\n setSelectionEnd(start);\n } else {\n // It was a drag - finalize the selection\n setSelectionStart(start);\n setSelectionEnd(end);\n }\n },\n [isSelecting, selectionStart, samplesPerPixel, sampleRate, seekTo, isPlaying, playoutRef, play]\n );\n\n // Show loading if peaks not ready\n if (peaksDataArray.length === 0) {\n return <div className={className}>Loading waveform...</div>;\n }\n\n return (\n <DevicePixelRatioProvider>\n <PlaylistInfoContext.Provider\n value={{\n samplesPerPixel,\n sampleRate,\n zoomLevels: [samplesPerPixel],\n waveHeight,\n timeScaleHeight,\n duration: duration * 1000,\n controls,\n barWidth,\n barGap,\n roundedBars,\n }}\n >\n <Playlist\n theme={theme}\n backgroundColor={waveformColorToCss(theme.waveOutlineColor)}\n timescaleBackgroundColor={theme.timescaleBackgroundColor}\n timescaleWidth={tracksFullWidth}\n tracksWidth={tracksFullWidth}\n controlsWidth={controls.show ? controls.width : 0}\n onTracksMouseDown={handleMouseDown}\n onTracksMouseMove={handleMouseMove}\n onTracksMouseUp={handleMouseUp}\n scrollContainerRef={handleScrollContainerRef}\n isSelecting={isSelecting}\n timescale={timeScaleHeight > 0 ? <SmartScale /> : undefined}\n >\n <>\n {peaksDataArray.map((trackClipPeaks, trackIndex) => {\n // For MediaElement, we have a single track with a single clip\n const maxChannels =\n trackClipPeaks.length > 0\n ? Math.max(...trackClipPeaks.map((clip) => clip.peaks.data.length))\n : 1;\n\n return (\n <TrackComponent\n key={trackIndex}\n numChannels={maxChannels}\n backgroundColor={waveformColorToCss(theme.waveOutlineColor)}\n offset={0}\n width={tracksFullWidth}\n hasClipHeaders={false}\n trackId={`media-element-track-${trackIndex}`}\n isSelected={true}\n >\n {trackClipPeaks.map((clip, clipIndex) => {\n const peaksData = clip.peaks;\n const width = peaksData.length;\n\n return (\n <Clip\n key={`${trackIndex}-${clipIndex}`}\n clipId={clip.clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n trackName={clip.trackName}\n startSample={clip.startSample}\n durationSamples={clip.durationSamples}\n samplesPerPixel={samplesPerPixel}\n showHeader={false}\n disableHeaderDrag={true}\n isSelected={true}\n trackId={`media-element-track-${trackIndex}`}\n >\n {peaksData.data.map((channelPeaks: Peaks, channelIndex: number) => (\n <ChannelWithMediaElementProgress\n key={`${trackIndex}-${clipIndex}-${channelIndex}`}\n index={channelIndex}\n data={channelPeaks}\n bits={peaksData.bits}\n length={width}\n clipStartSample={clip.startSample}\n clipDurationSamples={clip.durationSamples}\n />\n ))}\n {showFades && fadeIn && fadeIn.duration > 0 && (\n <FadeOverlay\n left={0}\n width={Math.floor((fadeIn.duration * sampleRate) / samplesPerPixel)}\n type=\"fadeIn\"\n curveType={fadeIn.type}\n />\n )}\n {showFades && fadeOut && fadeOut.duration > 0 && (\n <FadeOverlay\n left={\n width - Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)\n }\n width={Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)}\n type=\"fadeOut\"\n curveType={fadeOut.type}\n />\n )}\n </Clip>\n );\n })}\n </TrackComponent>\n );\n })}\n {annotations.length > 0 && annotationIntegration && (\n <DragDropProvider\n onDragStart={onDragStart}\n onDragMove={onDragMove}\n onDragEnd={onDragEnd}\n modifiers={editable ? [RestrictToHorizontalAxis] : []}\n plugins={noDropAnimationPlugins}\n >\n <annotationIntegration.AnnotationBoxesWrapper height={30} width={tracksFullWidth}>\n {annotations.map((annotation, index) => {\n const startPosition = (annotation.start * sampleRate) / samplesPerPixel;\n const endPosition = (annotation.end * sampleRate) / samplesPerPixel;\n const label = getAnnotationBoxLabel\n ? getAnnotationBoxLabel(annotation, index)\n : annotation.id;\n return (\n <annotationIntegration.AnnotationBox\n key={annotation.id}\n annotationId={annotation.id}\n annotationIndex={index}\n startPosition={startPosition}\n endPosition={endPosition}\n label={label}\n color=\"#ff9800\"\n isActive={annotation.id === activeAnnotationId}\n onClick={() => handleAnnotationClick(annotation)}\n editable={editable}\n />\n );\n })}\n </annotationIntegration.AnnotationBoxesWrapper>\n </DragDropProvider>\n )}\n {selectionStart !== selectionEnd && (\n <Selection\n startPosition={\n (Math.min(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n }\n endPosition={\n (Math.max(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n }\n color={theme.selectionColor}\n />\n )}\n {renderPlayhead ? (\n <CustomMediaElementPlayhead\n renderPlayhead={renderPlayhead}\n color={theme.playheadColor}\n samplesPerPixel={samplesPerPixel}\n sampleRate={sampleRate}\n />\n ) : (\n <AnimatedMediaElementPlayhead color={theme.playheadColor} />\n )}\n </>\n </Playlist>\n </PlaylistInfoContext.Provider>\n </DevicePixelRatioProvider>\n );\n};\n","import { Feedback } from '@dnd-kit/dom';\nimport type { Plugins } from '@dnd-kit/abstract';\n\n/**\n * DragDropProvider plugins customizer that disables the Feedback plugin's drop animation.\n *\n * Without this, the Feedback plugin animates the dragged element back to its original\n * position on drop, causing a visual snap-back before React re-renders at the new position.\n *\n * Usage:\n * ```tsx\n * <DragDropProvider plugins={noDropAnimationPlugins} ...>\n * ```\n */\nexport const noDropAnimationPlugins = (defaults: Plugins): Plugins => {\n let feedbackFound = false;\n const result = defaults.map((p) => {\n if (p === Feedback) {\n feedbackFound = true;\n return Feedback.configure({ dropAnimation: null });\n }\n return p;\n });\n if (!feedbackFound) {\n console.warn(\n '[waveform-playlist] noDropAnimationPlugins: Feedback plugin not found in defaults — ' +\n 'drop animation may not be disabled. Check @dnd-kit/dom version.'\n );\n }\n return result;\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { useMediaElementAnimation, useMediaElementData } from '../MediaElementPlaylistContext';\n\nconst PlayheadLine = styled.div<{ $color: string; $width: number }>`\n position: absolute;\n top: 0;\n left: 0;\n width: ${(props) => props.$width}px;\n background: ${(props) => props.$color};\n height: 100%;\n z-index: 100;\n pointer-events: none;\n will-change: transform;\n`;\n\ninterface AnimatedMediaElementPlayheadProps {\n color?: string;\n}\n\n/**\n * Animated playhead for MediaElementPlaylistProvider.\n * Uses the MediaElement context for time tracking instead of Tone.js audio context.\n * Updates position via direct DOM manipulation for smooth 60fps animation.\n */\nexport const AnimatedMediaElementPlayhead: React.FC<AnimatedMediaElementPlayheadProps> = ({\n color = '#ff0000',\n}) => {\n const playheadRef = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n\n const { isPlaying, currentTimeRef } = useMediaElementAnimation();\n const { samplesPerPixel, sampleRate, progressBarWidth } = useMediaElementData();\n\n useEffect(() => {\n const updatePosition = () => {\n if (playheadRef.current) {\n // Get current time from the ref (updated by animation loop in context)\n const time = currentTimeRef.current ?? 0;\n const position = (time * sampleRate) / samplesPerPixel;\n playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;\n }\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updatePosition);\n }\n };\n\n if (isPlaying) {\n // Start animation loop\n animationFrameRef.current = requestAnimationFrame(updatePosition);\n } else {\n // When stopped, update once to show final position\n updatePosition();\n }\n\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n };\n }, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef]);\n\n // Also update position when not playing (for seeks, stops, etc.)\n useEffect(() => {\n if (!isPlaying && playheadRef.current) {\n const time = currentTimeRef.current ?? 0;\n const position = (time * sampleRate) / samplesPerPixel;\n playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;\n }\n });\n\n return <PlayheadLine ref={playheadRef} $color={color} $width={progressBarWidth} data-playhead />;\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport {\n SmartChannel,\n type SmartChannelProps,\n useTheme,\n usePlaylistInfo,\n type WaveformPlaylistTheme,\n waveformColorToCss,\n} from '@waveform-playlist/ui-components';\nimport { useMediaElementAnimation, useMediaElementData } from '../MediaElementPlaylistContext';\n\nconst ChannelWrapper = styled.div`\n position: relative;\n`;\n\ninterface BackgroundProps {\n readonly $color: string;\n readonly $height: number;\n readonly $top: number;\n readonly $width: number;\n}\n\nconst Background = styled.div<BackgroundProps>`\n position: absolute;\n top: ${(props) => props.$top}px;\n left: 0;\n width: ${(props) => props.$width}px;\n height: ${(props) => props.$height}px;\n background: ${(props) => props.$color};\n z-index: 0;\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\ninterface ProgressOverlayProps {\n readonly $color: string;\n readonly $height: number;\n readonly $top: number;\n}\n\nconst ProgressOverlay = styled.div<ProgressOverlayProps>`\n position: absolute;\n top: ${(props) => props.$top}px;\n left: 0;\n height: ${(props) => props.$height}px;\n background: ${(props) => props.$color};\n pointer-events: none;\n z-index: 1;\n will-change: width;\n`;\n\nconst ChannelContainer = styled.div`\n position: relative;\n z-index: 2;\n`;\n\nexport interface ChannelWithMediaElementProgressProps\n extends Omit<SmartChannelProps, 'isSelected'> {\n /** Start sample of the clip containing this channel (for progress calculation) */\n clipStartSample: number;\n /** Duration in samples of the clip */\n clipDurationSamples: number;\n}\n\n/**\n * SmartChannel wrapper for MediaElementPlaylistProvider with animated progress overlay.\n * Uses MediaElement context for time tracking instead of Tone.js audio context.\n */\nexport const ChannelWithMediaElementProgress: React.FC<ChannelWithMediaElementProgressProps> = ({\n clipStartSample,\n clipDurationSamples,\n ...smartChannelProps\n}) => {\n const progressRef = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n const theme = useTheme() as WaveformPlaylistTheme;\n const { waveHeight } = usePlaylistInfo();\n\n const { isPlaying, currentTimeRef } = useMediaElementAnimation();\n const { samplesPerPixel, sampleRate } = useMediaElementData();\n\n const progressColor = theme?.waveProgressColor || 'rgba(0, 0, 0, 0.1)';\n\n useEffect(() => {\n const updateProgress = () => {\n if (progressRef.current) {\n // Get current time from the ref\n const currentTime = currentTimeRef.current ?? 0;\n\n // Convert current time to samples\n const currentSample = currentTime * sampleRate;\n\n // Calculate clip bounds in samples\n const clipEndSample = clipStartSample + clipDurationSamples;\n\n // Calculate how much of this clip has been played\n let progressWidth = 0;\n\n if (currentSample <= clipStartSample) {\n // Playhead is before this clip - no progress\n progressWidth = 0;\n } else if (currentSample >= clipEndSample) {\n // Playhead is past this clip - full progress\n progressWidth = smartChannelProps.length;\n } else {\n // Playhead is within this clip - partial progress\n const playedSamples = currentSample - clipStartSample;\n progressWidth = Math.floor(playedSamples / samplesPerPixel);\n }\n\n progressRef.current.style.width = `${progressWidth}px`;\n }\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updateProgress);\n }\n };\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updateProgress);\n } else {\n // When stopped, update once to show final position\n updateProgress();\n }\n\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n };\n }, [\n isPlaying,\n sampleRate,\n samplesPerPixel,\n clipStartSample,\n clipDurationSamples,\n smartChannelProps.length,\n currentTimeRef,\n ]);\n\n // Also update when not playing (for seeks, stops, etc.)\n useEffect(() => {\n if (!isPlaying && progressRef.current) {\n const currentTime = currentTimeRef.current ?? 0;\n const currentSample = currentTime * sampleRate;\n const clipEndSample = clipStartSample + clipDurationSamples;\n\n let progressWidth = 0;\n if (currentSample <= clipStartSample) {\n progressWidth = 0;\n } else if (currentSample >= clipEndSample) {\n progressWidth = smartChannelProps.length;\n } else {\n const playedSamples = currentSample - clipStartSample;\n progressWidth = Math.floor(playedSamples / samplesPerPixel);\n }\n\n progressRef.current.style.width = `${progressWidth}px`;\n }\n });\n\n // Get the draw mode from theme (defaults to 'inverted')\n const drawMode = theme?.waveformDrawMode || 'inverted';\n\n let backgroundColor;\n if (drawMode === 'inverted') {\n // For MediaElement, always treat as selected (single track)\n backgroundColor = theme?.selectedWaveFillColor || theme?.waveFillColor || 'white';\n } else {\n backgroundColor = theme?.selectedWaveOutlineColor || theme?.waveOutlineColor || 'grey';\n }\n\n const backgroundCss = waveformColorToCss(backgroundColor);\n\n return (\n <ChannelWrapper>\n <Background\n $color={backgroundCss}\n $height={waveHeight}\n $top={smartChannelProps.index * waveHeight}\n $width={smartChannelProps.length}\n />\n <ProgressOverlay\n ref={progressRef}\n $color={progressColor}\n $height={waveHeight}\n $top={smartChannelProps.index * waveHeight}\n />\n <ChannelContainer>\n <SmartChannel {...smartChannelProps} isSelected={true} transparentBackground />\n </ChannelContainer>\n </ChannelWrapper>\n );\n};\n","import React, { useCallback } from 'react';\nimport type {\n AnnotationData,\n AnnotationAction,\n AnnotationActionOptions,\n RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\nimport { useAnnotationIntegration } from '../AnnotationIntegrationContext';\nimport { useMediaElementState, useMediaElementControls } from '../MediaElementPlaylistContext';\nimport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport interface MediaElementAnnotationListProps {\n /** Height in pixels for the annotation text list */\n height?: number;\n /**\n * Custom render function for annotation items in the text list.\n * When provided, completely replaces the default annotation item rendering.\n */\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n /**\n * Callback when annotations are updated (e.g., text edited).\n * Called with the full updated annotations array.\n */\n onAnnotationUpdate?: OnAnnotationUpdateFn;\n /** Whether annotation text can be edited. Defaults to false. */\n editable?: boolean;\n /**\n * Action controls to show on each annotation item (e.g., delete, split).\n * Only rendered when `editable` is true.\n */\n controls?: AnnotationAction[];\n /**\n * Override annotation list config. Falls back to context values\n * `{ linkEndpoints: false, continuousPlay }` if not provided.\n */\n annotationListConfig?: AnnotationActionOptions;\n /** Where to position the active annotation when auto-scrolling. Defaults to 'center'. */\n scrollActivePosition?: ScrollLogicalPosition;\n /** Which scrollable containers to scroll: 'nearest' or 'all'. Defaults to 'nearest'. */\n scrollActiveContainer?: 'nearest' | 'all';\n}\n\n/**\n * Standalone annotation text list component for MediaElementPlaylistProvider.\n *\n * Requires @waveform-playlist/annotations with AnnotationProvider.\n * Throws if used without `<AnnotationProvider>` wrapping the component tree.\n */\nexport const MediaElementAnnotationList: React.FC<MediaElementAnnotationListProps> = ({\n height,\n renderAnnotationItem,\n onAnnotationUpdate,\n editable = false,\n controls,\n annotationListConfig,\n scrollActivePosition = 'center',\n scrollActiveContainer = 'nearest',\n}) => {\n const { annotations, activeAnnotationId, continuousPlay } = useMediaElementState();\n const integration = useAnnotationIntegration();\n const { setAnnotations } = useMediaElementControls();\n\n const resolvedConfig = annotationListConfig ?? { linkEndpoints: false, continuousPlay };\n\n const handleAnnotationUpdate = useCallback(\n (updatedAnnotations: AnnotationData[]) => {\n setAnnotations(updatedAnnotations);\n onAnnotationUpdate?.(updatedAnnotations);\n },\n [setAnnotations, onAnnotationUpdate]\n );\n\n const { AnnotationText } = integration;\n\n return (\n <AnnotationText\n annotations={annotations}\n activeAnnotationId={activeAnnotationId ?? undefined}\n shouldScrollToActive={true}\n scrollActivePosition={scrollActivePosition}\n scrollActiveContainer={scrollActiveContainer}\n editable={editable}\n controls={editable ? controls : undefined}\n annotationListConfig={resolvedConfig}\n height={height}\n onAnnotationUpdate={handleAnnotationUpdate}\n renderAnnotationItem={renderAnnotationItem}\n />\n );\n};\n","import React from 'react';\nimport type { RenderAnnotationItemProps } from '@waveform-playlist/core';\nimport type { RenderPlayheadFunction } from '@waveform-playlist/ui-components';\nimport { useMediaElementState } from '../MediaElementPlaylistContext';\nimport type { GetAnnotationBoxLabelFn, OnAnnotationUpdateFn } from '../types/annotations';\nimport { MediaElementPlaylist } from './MediaElementPlaylist';\nimport { MediaElementAnnotationList } from './MediaElementAnnotationList';\n\n// Re-export annotation types for convenience\nexport type { GetAnnotationBoxLabelFn, OnAnnotationUpdateFn } from '../types/annotations';\n\nexport interface MediaElementWaveformProps {\n /** Height in pixels for the annotation text list */\n annotationTextHeight?: number;\n /** Custom function to generate the label shown on annotation boxes */\n getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n /**\n * Custom render function for annotation items in the text list.\n * When provided, completely replaces the default annotation item rendering.\n * Use this to customize the appearance of each annotation (e.g., add furigana).\n */\n renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n /** Whether annotation boundaries can be edited by dragging. Defaults to false. */\n editable?: boolean;\n /** Whether dragging one annotation boundary also moves the adjacent annotation's boundary. Defaults to false. */\n linkEndpoints?: boolean;\n /**\n * Callback when annotations are updated (e.g., boundaries dragged).\n * Called with the full updated annotations array.\n */\n onAnnotationUpdate?: OnAnnotationUpdateFn;\n /** Where to position the active annotation when auto-scrolling: 'center', 'start', 'end', or 'nearest'. Defaults to 'center'. */\n scrollActivePosition?: ScrollLogicalPosition;\n /** Which scrollable containers to scroll: 'nearest' (only the annotation list) or 'all' (including viewport). Defaults to 'nearest'. */\n scrollActiveContainer?: 'nearest' | 'all';\n /** Custom playhead render function. Receives position, color, and animation refs for smooth 60fps animation. */\n renderPlayhead?: RenderPlayheadFunction;\n /** Show fade in/out overlays on the waveform. Defaults to false. */\n showFades?: boolean;\n className?: string;\n}\n\n/**\n * Simplified Waveform component for MediaElementPlaylistProvider\n *\n * This is a stripped-down version of Waveform that works with the\n * MediaElement context. It supports:\n * - Single track visualization\n * - Click to seek\n * - Annotation display and click-to-play\n * - Playhead animation\n *\n * For multi-track editing, use the full Waveform with WaveformPlaylistProvider.\n */\nexport const MediaElementWaveform: React.FC<MediaElementWaveformProps> = ({\n annotationTextHeight,\n getAnnotationBoxLabel,\n renderAnnotationItem,\n editable = false,\n linkEndpoints = false,\n onAnnotationUpdate,\n scrollActivePosition = 'center',\n scrollActiveContainer = 'nearest',\n renderPlayhead,\n showFades = false,\n className,\n}) => {\n const { annotations } = useMediaElementState();\n\n return (\n <>\n <MediaElementPlaylist\n getAnnotationBoxLabel={getAnnotationBoxLabel}\n editable={editable}\n linkEndpoints={linkEndpoints}\n onAnnotationUpdate={onAnnotationUpdate}\n renderPlayhead={renderPlayhead}\n showFades={showFades}\n className={className}\n />\n {annotations.length > 0 && (\n <MediaElementAnnotationList\n height={annotationTextHeight}\n renderAnnotationItem={renderAnnotationItem}\n onAnnotationUpdate={onAnnotationUpdate}\n editable={editable}\n annotationListConfig={{ linkEndpoints, continuousPlay: false }}\n scrollActivePosition={scrollActivePosition}\n scrollActiveContainer={scrollActiveContainer}\n />\n )}\n </>\n );\n};\n","import React from 'react';\nimport type { KeyboardShortcut } from '../hooks/useKeyboardShortcuts';\nimport { usePlaybackShortcuts } from '../hooks/usePlaybackShortcuts';\nimport { useClipSplitting } from '../hooks/useClipSplitting';\nimport { useAnnotationKeyboardControls } from '../hooks/useAnnotationKeyboardControls';\nimport { usePlaylistData, usePlaylistState, usePlaylistControls } from '../WaveformPlaylistContext';\n\nexport interface KeyboardShortcutsProps {\n /** Enable default playback shortcuts (Space, Escape, 0). Defaults to false. */\n playback?: boolean;\n /** Enable clip splitting shortcut ('s' key). Defaults to false. */\n clipSplitting?: boolean;\n /** Enable annotation keyboard controls (arrow nav, boundary editing). Defaults to false. */\n annotations?: boolean;\n /** Enable undo/redo shortcuts (Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z). Defaults to false. */\n undo?: boolean;\n /** Additional shortcuts appended to the defaults. */\n additionalShortcuts?: KeyboardShortcut[];\n}\n\n/**\n * Self-closing component that sets up keyboard shortcuts for the playlist.\n * Must be rendered inside a WaveformPlaylistProvider.\n *\n * @example\n * ```tsx\n * <WaveformPlaylistProvider tracks={tracks} {...}>\n * <KeyboardShortcuts playback clipSplitting />\n * <Waveform />\n * </WaveformPlaylistProvider>\n * ```\n */\nexport const KeyboardShortcuts: React.FC<KeyboardShortcutsProps> = ({\n playback = false,\n clipSplitting = false,\n annotations = false,\n undo: undoEnabled = false,\n additionalShortcuts = [],\n}) => {\n // Clip splitting setup\n const { tracks, samplesPerPixel, playoutRef, duration } = usePlaylistData();\n const {\n annotations: annotationList,\n linkEndpoints,\n activeAnnotationId,\n continuousPlay,\n } = usePlaylistState();\n const { setAnnotations, setActiveAnnotationId, scrollContainerRef, play, undo, redo } =\n usePlaylistControls();\n\n const { splitClipAtPlayhead } = useClipSplitting({\n tracks,\n samplesPerPixel,\n engineRef: playoutRef,\n });\n\n // Build additional shortcuts from enabled features\n const allAdditional: KeyboardShortcut[] = [];\n\n if (clipSplitting) {\n allAdditional.push({\n key: 's',\n action: splitClipAtPlayhead,\n description: 'Split clip at playhead',\n preventDefault: true,\n });\n }\n\n if (undoEnabled) {\n allAdditional.push(\n { key: 'z', ctrlKey: true, shiftKey: false, action: undo, description: 'Undo' },\n { key: 'z', metaKey: true, shiftKey: false, action: undo, description: 'Undo' },\n { key: 'z', ctrlKey: true, shiftKey: true, action: redo, description: 'Redo' },\n { key: 'z', metaKey: true, shiftKey: true, action: redo, description: 'Redo' }\n );\n }\n\n if (additionalShortcuts.length > 0) {\n allAdditional.push(...additionalShortcuts);\n }\n\n // Playback shortcuts (or just additional if playback is off)\n usePlaybackShortcuts({\n enabled: playback || allAdditional.length > 0,\n ...(playback ? { additionalShortcuts: allAdditional } : { shortcuts: allAdditional }),\n });\n\n // Annotation keyboard controls\n useAnnotationKeyboardControls({\n annotations: annotationList,\n activeAnnotationId,\n onAnnotationsChange: setAnnotations,\n onActiveAnnotationChange: setActiveAnnotationId,\n duration,\n linkEndpoints,\n continuousPlay,\n scrollContainerRef,\n onPlay: play,\n enabled: annotations && annotationList.length > 0,\n });\n\n return null;\n};\n","import React, { useEffect, useMemo } from 'react';\nimport { DragDropProvider } from '@dnd-kit/react';\nimport { RestrictToHorizontalAxis } from '@dnd-kit/abstract/modifiers';\nimport type { Modifiers } from '@dnd-kit/abstract';\nimport {\n samplesToTicks,\n ticksToSamples,\n snapToGrid,\n ticksPerBeat,\n ticksPerBar,\n} from '@waveform-playlist/core';\nimport { useBeatsAndBars, getScaleInfo } from '@waveform-playlist/ui-components';\n\nimport { usePlaylistData, usePlaylistControls } from '../WaveformPlaylistContext';\nimport { useClipDragHandlers } from '../hooks/useClipDragHandlers';\nimport { useDragSensors } from '../hooks/useDragSensors';\nimport { ClipCollisionModifier } from '../modifiers/ClipCollisionModifier';\nimport { SnapToGridModifier } from '../modifiers/SnapToGridModifier';\nimport { noDropAnimationPlugins } from '../plugins/noDropAnimationPlugins';\nimport { ClipInteractionContextProvider } from '../contexts/ClipInteractionContext';\n\n// Stable noop to avoid creating a new function reference on every render\nconst NOOP_TRACKS_CHANGE = () => {};\n\nexport interface ClipInteractionProviderProps {\n /** Enable snap-to-grid for clip moves and boundary trims. When true,\n * auto-detects beats snapping from BeatsAndBarsProvider context\n * (if present with scaleMode=\"beats\" and snapTo!=\"off\"), otherwise\n * falls back to timescale-based snapping. Default: false. */\n snap?: boolean;\n touchOptimized?: boolean;\n children: React.ReactNode;\n}\n\nexport const ClipInteractionProvider: React.FC<ClipInteractionProviderProps> = ({\n snap = false,\n touchOptimized = false,\n children,\n}) => {\n const { tracks, samplesPerPixel, sampleRate, playoutRef, isDraggingRef, onTracksChange } =\n usePlaylistData();\n const { setSelectedTrackId } = usePlaylistControls();\n const beatsAndBars = useBeatsAndBars();\n\n // Derive snap mode from context: beats if provider is in beats mode with snap enabled,\n // timescale otherwise, off when snap prop is false.\n const useBeatsSnap =\n snap &&\n beatsAndBars != null &&\n beatsAndBars.scaleMode === 'beats' &&\n beatsAndBars.snapTo !== 'off';\n const useTimescaleSnap = snap && !useBeatsSnap;\n\n // Warn once if onTracksChange is missing — drag/trim edits will be lost\n useEffect(() => {\n if (onTracksChange == null) {\n console.warn(\n '[waveform-playlist] ClipInteractionProvider: onTracksChange is not set on ' +\n 'WaveformPlaylistProvider. Drag and trim edits will not be persisted.'\n );\n }\n }, [onTracksChange]);\n\n // Build snapSamplePosition for boundary trim snapping\n const snapSamplePosition = useMemo(() => {\n if (useBeatsSnap && beatsAndBars) {\n const { bpm, timeSignature, snapTo } = beatsAndBars;\n const gridTicks = snapTo === 'bar' ? ticksPerBar(timeSignature) : ticksPerBeat(timeSignature);\n return (samplePos: number) => {\n const ticks = samplesToTicks(samplePos, bpm, sampleRate);\n const snapped = snapToGrid(ticks, gridTicks);\n return ticksToSamples(snapped, bpm, sampleRate);\n };\n }\n if (useTimescaleSnap) {\n const gridSamples = Math.round((getScaleInfo(samplesPerPixel).smallStep / 1000) * sampleRate);\n return (samplePos: number) => Math.round(samplePos / gridSamples) * gridSamples;\n }\n return undefined;\n }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, sampleRate, samplesPerPixel]);\n\n // Sensors\n const sensors = useDragSensors({ touchOptimized });\n\n // Drag handlers\n const {\n onDragStart: handleDragStart,\n onDragMove,\n onDragEnd,\n } = useClipDragHandlers({\n tracks,\n onTracksChange: onTracksChange ?? NOOP_TRACKS_CHANGE,\n samplesPerPixel,\n engineRef: playoutRef,\n isDraggingRef,\n snapSamplePosition,\n });\n\n // Wrap onDragStart to auto-select track\n const onDragStart = React.useCallback(\n (event: Parameters<typeof handleDragStart>[0]) => {\n const trackIndex = event.operation?.source?.data?.trackIndex as number | undefined;\n if (trackIndex !== undefined && tracks[trackIndex]) {\n setSelectedTrackId(tracks[trackIndex].id);\n }\n handleDragStart(event);\n },\n [handleDragStart, tracks, setSelectedTrackId]\n );\n\n // Build modifiers array\n const modifiers = useMemo(() => {\n const mods: Modifiers = [RestrictToHorizontalAxis];\n\n if (useBeatsSnap && beatsAndBars) {\n mods.push(\n SnapToGridModifier.configure({\n mode: 'beats',\n snapTo: beatsAndBars.snapTo,\n bpm: beatsAndBars.bpm,\n timeSignature: beatsAndBars.timeSignature,\n samplesPerPixel,\n sampleRate,\n })\n );\n } else if (useTimescaleSnap) {\n mods.push(\n SnapToGridModifier.configure({\n mode: 'timescale',\n gridSamples: Math.round((getScaleInfo(samplesPerPixel).smallStep / 1000) * sampleRate),\n samplesPerPixel,\n })\n );\n }\n\n mods.push(ClipCollisionModifier.configure({ tracks, samplesPerPixel }));\n return mods;\n }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, tracks, samplesPerPixel, sampleRate]);\n\n return (\n <ClipInteractionContextProvider value={true}>\n <DragDropProvider\n sensors={sensors}\n onDragStart={onDragStart}\n onDragMove={onDragMove}\n onDragEnd={onDragEnd}\n modifiers={modifiers}\n plugins={noDropAnimationPlugins}\n >\n {children}\n </DragDropProvider>\n </ClipInteractionContextProvider>\n );\n};\n","import {\n Modifier,\n configurator,\n type DragDropManager,\n type DragOperation,\n} from '@dnd-kit/abstract';\nimport { constrainClipDrag } from '@waveform-playlist/engine';\nimport type { ClipTrack, AudioClip } from '@waveform-playlist/core';\n\ninterface ClipCollisionOptions {\n tracks: ClipTrack[];\n samplesPerPixel: number;\n}\n\n/**\n * Modifier that constrains clip drag movement to prevent overlaps.\n *\n * For clip move operations: constrains horizontal transform to valid positions\n * using the engine's collision detection.\n *\n * For boundary trim operations: returns zero transform because visual feedback\n * comes from React state updates resizing the clip, not from CSS translate.\n */\nexport class ClipCollisionModifier extends Modifier<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n DragDropManager<any, any>,\n ClipCollisionOptions\n> {\n apply(operation: DragOperation): { x: number; y: number } {\n const { transform, source } = operation;\n\n if (!source?.data || !this.options) return transform;\n\n const { boundary, trackIndex, clipIndex } = source.data as {\n boundary?: 'left' | 'right';\n trackIndex: number;\n clipIndex: number;\n };\n\n // Boundary trims: zero transform (visual feedback from React state updates)\n if (boundary) return { x: 0, y: 0 };\n\n const { tracks, samplesPerPixel } = this.options;\n const track = tracks[trackIndex];\n if (!track) return transform;\n\n const clip = track.clips[clipIndex];\n if (!clip) return transform;\n\n // Convert pixel delta to samples, constrain, convert back\n const deltaSamples = transform.x * samplesPerPixel;\n const sortedClips = [...track.clips].sort((a, b) => a.startSample - b.startSample);\n const sortedIndex = sortedClips.findIndex((c: AudioClip) => c.id === clip.id);\n const constrainedDelta = constrainClipDrag(clip, deltaSamples, sortedClips, sortedIndex);\n\n return { x: constrainedDelta / samplesPerPixel, y: 0 };\n }\n\n static configure = configurator(ClipCollisionModifier);\n}\n","import {\n Modifier,\n configurator,\n type DragDropManager,\n type DragOperation,\n} from '@dnd-kit/abstract';\nimport {\n ticksPerBeat,\n ticksPerBar,\n ticksToSamples,\n samplesToTicks,\n snapToGrid,\n} from '@waveform-playlist/core';\nimport type { SnapTo } from '@waveform-playlist/ui-components';\n\ninterface SnapToGridBeatsOptions {\n mode: 'beats';\n snapTo: SnapTo;\n bpm: number;\n timeSignature: [number, number];\n samplesPerPixel: number;\n sampleRate: number;\n}\n\ninterface SnapToGridTimescaleOptions {\n mode: 'timescale';\n gridSamples: number;\n samplesPerPixel: number;\n}\n\ntype SnapToGridOptions = SnapToGridBeatsOptions | SnapToGridTimescaleOptions;\n\n/**\n * dnd-kit modifier that quantizes clip drag movement to a grid.\n *\n * Two modes:\n * - \"beats\": Snaps to beat/bar grid using PPQN tick space for exact musical timing.\n * - \"timescale\": Snaps to a sample-based grid derived from timescale markers.\n *\n * Designed to compose with ClipCollisionModifier — snap first,\n * then collision constrains the snapped position.\n */\nexport class SnapToGridModifier extends Modifier<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n DragDropManager<any, any>,\n SnapToGridOptions\n> {\n apply(operation: DragOperation): { x: number; y: number } {\n const { transform, source } = operation;\n\n if (!this.options || !source?.data) return transform;\n\n // Don't snap boundary trims — snapping for trims is handled in the drag handler\n const { boundary, startSample } = source.data as {\n boundary?: 'left' | 'right';\n startSample?: number;\n };\n if (boundary) return transform;\n\n const { samplesPerPixel } = this.options;\n\n // Snap the absolute position (not just the delta) so clips land exactly\n // on grid lines even if they started off-grid.\n if (this.options.mode === 'timescale') {\n const { gridSamples } = this.options;\n if (startSample !== undefined) {\n const proposedPosition = startSample + transform.x * samplesPerPixel;\n const snappedPosition = Math.round(proposedPosition / gridSamples) * gridSamples;\n return { x: (snappedPosition - startSample) / samplesPerPixel, y: 0 };\n }\n // Fallback: snap delta (no startSample available)\n const deltaSamples = transform.x * samplesPerPixel;\n const snappedSamples = Math.round(deltaSamples / gridSamples) * gridSamples;\n return { x: snappedSamples / samplesPerPixel, y: 0 };\n }\n\n // Beats mode\n const { snapTo, bpm, timeSignature, sampleRate } = this.options;\n\n if (snapTo === 'off') return transform;\n\n const gridTicks = snapTo === 'bar' ? ticksPerBar(timeSignature) : ticksPerBeat(timeSignature);\n\n if (startSample !== undefined) {\n // Snap absolute position in tick space\n const proposedSamples = startSample + transform.x * samplesPerPixel;\n const proposedTicks = samplesToTicks(proposedSamples, bpm, sampleRate);\n const snappedTicks = snapToGrid(proposedTicks, gridTicks);\n const snappedSamples = ticksToSamples(snappedTicks, bpm, sampleRate);\n return { x: (snappedSamples - startSample) / samplesPerPixel, y: 0 };\n }\n\n // Fallback: snap delta (no startSample available)\n const deltaSamples = transform.x * samplesPerPixel;\n const deltaTicks = samplesToTicks(deltaSamples, bpm, sampleRate);\n const snappedTicks = snapToGrid(deltaTicks, gridTicks);\n const snappedSamples = ticksToSamples(snappedTicks, bpm, sampleRate);\n\n return { x: snappedSamples / samplesPerPixel, y: 0 };\n }\n\n static configure = configurator(SnapToGridModifier);\n}\n"],"mappings":"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","names":["import_react","import_engine","import_ui_components","WaveformData","bits","formatTime","formatTimeUtil","parseTimeUtil","import_react","import_react","import_react","import_react","import_react","import_react","import_react","React","trackIndex","import_react","React","import_react","import_react","import_engine","track","import_react","import_core","import_react","import_react","LINK_THRESHOLD","import_react","import_react","import_waveform_data","WaveformData","_a","_b","_c","formatTime","_d","sampleRate","volume","import_react","import_styled_components","INSTALL_HINT","import_ui_components","import_jsx_runtime","_a","import_react","import_ui_components","import_jsx_runtime","import_ui_components","import_jsx_runtime","import_react","import_ui_components","import_styled_components","import_jsx_runtime","BaseMasterVolumeControl","BaseTimeFormatSelect","styled","_a","BaseSelectionTimeInputs","BaseAutomaticScrollCheckbox","import_react","import_jsx_runtime","import_react","import_react","import_styled_components","import_ui_components","import_core","import_react","import_styled_components","import_jsx_runtime","styled","import_react","import_styled_components","import_core","import_ui_components","import_jsx_runtime","styled","computeClipPixelWidth","_a","_b","import_react","import_jsx_runtime","styled","_a","_b","_c","_d","_e","_f","_g","_h","_i","TrackComponent","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_ui_components","import_dom","import_react","import_styled_components","import_jsx_runtime","PlayheadLine","styled","import_react","import_styled_components","import_ui_components","import_jsx_runtime","ChannelWrapper","styled","Background","ProgressOverlay","ChannelContainer","_a","import_jsx_runtime","TrackComponent","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_modifiers","import_core","import_ui_components","import_engine","import_abstract","import_core","deltaSamples","snappedSamples","snappedTicks","import_jsx_runtime","React"]}