@waveform-playlist/ui-components 9.1.1 → 9.2.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/components/AudioPosition.tsx","../src/styled/BaseButton.tsx","../src/styled/BaseCheckbox.tsx","../src/styled/BaseControlButton.tsx","../src/styled/BaseInput.tsx","../src/styled/BaseLabel.tsx","../src/styled/BaseSelect.tsx","../src/styled/BaseSlider.tsx","../src/components/AutomaticScrollCheckbox.tsx","../src/components/Channel.tsx","../src/wfpl-theme.ts","../src/contexts/ScrollViewport.tsx","../src/contexts/ClipViewportOrigin.tsx","../src/hooks/useChunkedCanvasRefs.ts","../src/utils/peakRendering.ts","../src/components/ErrorBoundary.tsx","../src/components/Clip.tsx","../src/components/ClipHeader.tsx","../src/components/ClipBoundary.tsx","../src/components/FadeOverlay.tsx","../src/components/MasterVolumeControl.tsx","../src/components/PianoRollChannel.tsx","../src/components/Playhead.tsx","../src/components/Playlist.tsx","../src/components/Selection.tsx","../src/components/LoopRegion.tsx","../src/components/SelectionTimeInputs.tsx","../src/components/TimeInput.tsx","../src/utils/timeFormat.ts","../src/contexts/DevicePixelRatio.tsx","../src/contexts/PlaylistInfo.tsx","../src/contexts/Theme.tsx","../src/contexts/TrackControls.tsx","../src/contexts/Playout.tsx","../src/components/SpectrogramChannel.tsx","../src/components/SmartChannel.tsx","../src/components/SpectrogramLabels.tsx","../src/components/SmartScale.tsx","../src/components/TimeScale.tsx","../src/utils/conversions.ts","../src/components/TimeFormatSelect.tsx","../src/components/Track.tsx","../src/components/TrackControls/Button.tsx","../src/components/TrackControls/ButtonGroup.tsx","../src/components/TrackControls/CloseButton.tsx","../src/components/TrackControls/Controls.tsx","../src/components/TrackControls/Header.tsx","../src/components/TrackControls/VolumeDownIcon.tsx","../src/components/TrackControls/VolumeUpIcon.tsx","../src/components/TrackControls/TrashIcon.tsx","../src/components/TrackControls/DotsIcon.tsx","../src/components/TrackControls/Slider.tsx","../src/components/TrackControls/SliderWrapper.tsx","../src/components/TrackMenu.tsx"],"sourcesContent":["export * from './components';\nexport * from './contexts';\nexport * from './utils/conversions';\nexport * from './utils/timeFormat';\nexport * from './styled/index';\nexport * from './wfpl-theme';\n","import React from 'react';\nimport styled from 'styled-components';\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\nexport interface AudioPositionProps {\n formattedTime: string;\n className?: string;\n}\n\n/**\n * Displays the current audio playback position\n */\nexport const AudioPosition: React.FC<AudioPositionProps> = ({ formattedTime, className }) => {\n return (\n <PositionDisplay className={className} aria-label=\"Audio position\">\n {formattedTime}\n </PositionDisplay>\n );\n};\n","import styled from 'styled-components';\n\n/**\n * BaseButton - A styled button component that uses theme values\n *\n * This provides consistent styling across all button elements in the waveform playlist.\n */\nexport const BaseButton = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0.5rem 1rem;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n font-weight: 500;\n color: ${(props) => props.theme.buttonText};\n background-color: ${(props) => props.theme.buttonBackground};\n border: 1px solid ${(props) => props.theme.buttonBorder};\n border-radius: ${(props) => props.theme.borderRadius};\n cursor: pointer;\n outline: none;\n transition:\n background-color 0.15s ease-in-out,\n border-color 0.15s ease-in-out,\n box-shadow 0.15s ease-in-out;\n\n &:hover:not(:disabled) {\n background-color: ${(props) => props.theme.buttonHoverBackground};\n }\n\n &:focus {\n box-shadow: 0 0 0 2px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n`;\n\n/**\n * BaseButtonSmall - A smaller variant for compact layouts\n */\nexport const BaseButtonSmall = styled(BaseButton)`\n padding: 0.25rem 0.5rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n`;\n\n/**\n * IconButton - A square button for icons\n */\nexport const IconButton = styled(BaseButton)`\n padding: 0.5rem;\n min-width: 2.25rem;\n min-height: 2.25rem;\n`;\n\n/**\n * IconButtonSmall - A smaller square button for icons\n */\nexport const IconButtonSmall = styled(BaseButton)`\n padding: 0.25rem;\n min-width: 1.75rem;\n min-height: 1.75rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseCheckboxWrapper - Container for checkbox + label\n */\nexport const BaseCheckboxWrapper = styled.div`\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n`;\n\n/**\n * BaseCheckbox - A styled checkbox input\n */\nexport const BaseCheckbox = styled.input`\n cursor: pointer;\n accent-color: ${(props) => props.theme.inputFocusBorder};\n\n &:disabled {\n cursor: not-allowed;\n }\n`;\n\n/**\n * BaseCheckboxLabel - Label for checkboxes\n */\nexport const BaseCheckboxLabel = styled.label`\n margin: 0;\n cursor: pointer;\n user-select: none;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n color: ${(props) => props.theme.textColor};\n`;\n","import styled from 'styled-components';\n\n/**\n * ControlButton - A colored action button for prominent actions like Play, Pause, Record.\n * For neutral buttons, use BaseButton from the styled primitives.\n *\n * Uses theme colors when available, with fallbacks for standalone use.\n */\nexport const BaseControlButton = styled.button`\n padding: 0.5rem 1rem;\n background: ${(props) => props.theme.buttonBackground || '#007bff'};\n color: ${(props) => props.theme.buttonText || 'white'};\n border: none;\n border-radius: ${(props) => props.theme.borderRadius};\n cursor: pointer;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n font-weight: 500;\n transition: background-color 0.15s ease-in-out;\n\n &:hover:not(:disabled) {\n background: ${(props) => props.theme.buttonHoverBackground || '#0056b3'};\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 2px ${(props) => props.theme.buttonBackground || '#007bff'}66;\n }\n\n &:disabled {\n background: #6c757d;\n cursor: not-allowed;\n opacity: 0.6;\n }\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseInput - A styled input component that uses theme values\n *\n * This provides consistent styling across all input elements in the waveform playlist.\n * Styling is controlled via the theme, making it easy to adapt to different environments.\n */\nexport const BaseInput = styled.input`\n padding: 0.5rem 0.75rem;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n color: ${(props) => props.theme.inputText};\n background-color: ${(props) => props.theme.inputBackground};\n border: 1px solid ${(props) => props.theme.inputBorder};\n border-radius: ${(props) => props.theme.borderRadius};\n outline: none;\n transition:\n border-color 0.15s ease-in-out,\n box-shadow 0.15s ease-in-out;\n\n &::placeholder {\n color: ${(props) => props.theme.inputPlaceholder};\n }\n\n &:focus {\n border-color: ${(props) => props.theme.inputFocusBorder};\n box-shadow: 0 0 0 2px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n`;\n\n/**\n * BaseInputSmall - A smaller variant for compact layouts\n */\nexport const BaseInputSmall = styled(BaseInput)`\n padding: 0.25rem 0.5rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseLabel - A styled label component that uses theme values\n */\nexport const BaseLabel = styled.label`\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSizeSmall};\n font-weight: 500;\n color: ${(props) => props.theme.textColorMuted};\n margin-bottom: 0.25rem;\n display: block;\n`;\n\n/**\n * InlineLabel - A label that displays inline with its input\n */\nexport const InlineLabel = styled.label`\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n color: ${(props) => props.theme.textColor};\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n cursor: pointer;\n`;\n\n/**\n * ScreenReaderOnly - Visually hidden but accessible to screen readers\n */\nexport const ScreenReaderOnly = styled.span`\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseSelect - A styled select component that uses theme values\n *\n * This provides consistent styling across all select elements in the waveform playlist.\n */\nexport const BaseSelect = styled.select`\n padding: 0.5rem 2rem 0.5rem 0.75rem;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n color: ${(props) => props.theme.inputText};\n background-color: ${(props) => props.theme.inputBackground};\n border: 1px solid ${(props) => props.theme.inputBorder};\n border-radius: ${(props) => props.theme.borderRadius};\n outline: none;\n cursor: pointer;\n appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 0.75rem center;\n transition:\n border-color 0.15s ease-in-out,\n box-shadow 0.15s ease-in-out;\n\n &:focus {\n border-color: ${(props) => props.theme.inputFocusBorder};\n box-shadow: 0 0 0 2px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n /* Style native option elements for dark mode support */\n option {\n color: ${(props) => props.theme.inputText};\n background-color: ${(props) => props.theme.inputBackground};\n }\n`;\n\n/**\n * BaseSelectSmall - A smaller variant for compact layouts\n */\nexport const BaseSelectSmall = styled(BaseSelect)`\n padding: 0.25rem 1.75rem 0.25rem 0.5rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseSlider - Themed range input for volume controls, etc.\n *\n * Uses theme values for consistent styling across light/dark modes.\n * Provides custom styling for the track and thumb.\n */\nexport const BaseSlider = styled.input.attrs({ type: 'range' })`\n -webkit-appearance: none;\n appearance: none;\n width: 100%;\n height: 6px;\n background: ${(props) => props.theme.sliderTrackColor};\n border-radius: 3px;\n cursor: pointer;\n outline: none;\n\n /* WebKit (Chrome, Safari) */\n &::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 16px;\n height: 16px;\n background: ${(props) => props.theme.sliderThumbColor};\n border: 2px solid ${(props) => props.theme.inputBackground};\n border-radius: 50%;\n cursor: pointer;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n transition:\n transform 0.15s ease,\n box-shadow 0.15s ease;\n }\n\n &::-webkit-slider-thumb:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n }\n\n /* Firefox */\n &::-moz-range-thumb {\n width: 16px;\n height: 16px;\n background: ${(props) => props.theme.sliderThumbColor};\n border: 2px solid ${(props) => props.theme.inputBackground};\n border-radius: 50%;\n cursor: pointer;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n transition:\n transform 0.15s ease,\n box-shadow 0.15s ease;\n }\n\n &::-moz-range-thumb:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n }\n\n &::-moz-range-track {\n background: ${(props) => props.theme.sliderTrackColor};\n border-radius: 3px;\n height: 6px;\n }\n\n &:focus {\n outline: none;\n }\n\n &:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 3px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:focus::-moz-range-thumb {\n box-shadow: 0 0 0 3px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n\n &:disabled::-webkit-slider-thumb {\n cursor: not-allowed;\n }\n\n &:disabled::-moz-range-thumb {\n cursor: not-allowed;\n }\n`;\n","import React from 'react';\nimport { BaseCheckboxWrapper, BaseCheckbox, BaseCheckboxLabel } from '../styled/index';\n\nexport interface AutomaticScrollCheckboxProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * Checkbox control for enabling/disabling automatic scroll during playback\n */\nexport const AutomaticScrollCheckbox: React.FC<AutomaticScrollCheckboxProps> = ({\n checked,\n onChange,\n disabled = false,\n className,\n}) => {\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n onChange(e.target.checked);\n };\n\n return (\n <BaseCheckboxWrapper className={className}>\n <BaseCheckbox\n type=\"checkbox\"\n id=\"automatic-scroll\"\n className=\"automatic-scroll\"\n checked={checked}\n onChange={handleChange}\n disabled={disabled}\n />\n <BaseCheckboxLabel htmlFor=\"automatic-scroll\">Automatic Scroll</BaseCheckboxLabel>\n </BaseCheckboxWrapper>\n );\n};\n","import React, { FunctionComponent, useEffect } from 'react';\nimport styled from 'styled-components';\nimport type { Peaks, Bits } from '@waveform-playlist/core';\nimport {\n WaveformColor,\n WaveformDrawMode,\n isWaveformGradient,\n waveformColorToCss,\n} from '../wfpl-theme';\nimport { useVisibleChunkIndices } from '../contexts/ScrollViewport';\nimport { useClipViewportOrigin } from '../contexts/ClipViewportOrigin';\nimport { useChunkedCanvasRefs } from '../hooks/useChunkedCanvasRefs';\nimport { MAX_CANVAS_WIDTH } from '@waveform-playlist/core';\nimport {\n aggregatePeaks,\n calculateBarRects,\n calculateFirstBarPosition,\n} from '../utils/peakRendering';\n\n// Re-export WaveformColor for consumers\nexport type { WaveformColor } from '../wfpl-theme';\nexport type { WaveformDrawMode } from '../wfpl-theme';\n\n/**\n * Creates a Canvas gradient from a WaveformColor configuration\n */\nfunction createCanvasFillStyle(\n ctx: CanvasRenderingContext2D,\n color: WaveformColor,\n width: number,\n height: number\n): string | CanvasGradient {\n if (!isWaveformGradient(color)) {\n return color;\n }\n\n let gradient: CanvasGradient;\n if (color.direction === 'vertical') {\n gradient = ctx.createLinearGradient(0, 0, 0, height);\n } else {\n gradient = ctx.createLinearGradient(0, 0, width, 0);\n }\n\n for (const stop of color.stops) {\n gradient.addColorStop(stop.offset, stop.color);\n }\n\n return gradient;\n}\n\ninterface WaveformProps {\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $left: number;\n}\n\nconst Waveform = styled.canvas.attrs<WaveformProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n left: `${props.$left}px`,\n },\n}))<WaveformProps>`\n position: absolute;\n top: 0;\n /* Disable image rendering interpolation */\n image-rendering: pixelated;\n image-rendering: crisp-edges;\n`;\n\ninterface WrapperProps {\n readonly $index: number;\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $waveFillColor: string; // CSS background value (solid or gradient)\n}\n\nconst Wrapper = styled.div.attrs<WrapperProps>((props) => ({\n style: {\n top: `${props.$waveHeight * props.$index}px`,\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n },\n}))<WrapperProps>`\n position: absolute;\n background: ${(props) => props.$waveFillColor};\n /* Force GPU compositing layer to reduce scroll flickering */\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\nexport interface ChannelProps {\n className?: string;\n index: number;\n data: Peaks;\n bits: Bits;\n length: number;\n devicePixelRatio?: number;\n waveHeight?: number;\n /** Waveform bar color - can be a solid color string or gradient config */\n waveOutlineColor?: WaveformColor;\n /** Waveform background color - can be a solid color string or gradient config */\n waveFillColor?: WaveformColor;\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 /** If true, background is transparent (for use with external progress overlay) */\n transparentBackground?: boolean;\n /**\n * Drawing mode:\n * - 'inverted': Canvas draws waveOutlineColor where there's NO audio, revealing waveFillColor background as bars (default). Good for gradient bars.\n * - 'normal': Canvas draws waveFillColor where there IS audio. Use with transparentBackground for progress overlays.\n */\n drawMode?: WaveformDrawMode;\n}\n\nexport const Channel: FunctionComponent<ChannelProps> = (props) => {\n const {\n data,\n bits,\n length,\n index,\n className,\n devicePixelRatio = 1,\n waveHeight = 80,\n waveOutlineColor = '#E0EFF1',\n waveFillColor = 'grey',\n barWidth = 1,\n barGap = 0,\n transparentBackground = false,\n drawMode = 'inverted',\n } = props;\n const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();\n const clipOriginX = useClipViewportOrigin();\n\n const visibleChunkIndices = useVisibleChunkIndices(length, MAX_CANVAS_WIDTH, clipOriginX);\n\n // Draw waveform bars on visible canvas chunks.\n // visibleChunkIndices changes only when chunks mount/unmount, not on every scroll pixel.\n // useEffect (not useLayoutEffect) so the browser paints the track layout\n // (controls + empty canvas containers) before heavy canvas drawing starts.\n // This prevents browser-initiated scrollLeft shifts and main-thread blocking.\n useEffect(() => {\n const tDraw = performance.now();\n const step = barWidth + barGap;\n\n for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {\n const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;\n\n const ctx = canvas.getContext('2d');\n const h2 = Math.floor(waveHeight / 2);\n\n if (ctx) {\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n // Create gradient using CSS pixel coordinates (after scaling)\n // This ensures the gradient aligns with the drawing coordinates\n const canvasWidth = canvas.width / devicePixelRatio;\n\n // Choose canvas fill color based on draw mode:\n let fillColor: WaveformColor;\n if (drawMode === 'normal') {\n // Normal: canvas draws the bars directly\n fillColor = waveFillColor;\n } else {\n // Inverted: canvas masks non-audio areas, background shows as bars\n fillColor = waveOutlineColor;\n }\n ctx.fillStyle = createCanvasFillStyle(ctx, fillColor, canvasWidth, waveHeight);\n\n const canvasStartGlobal = globalPixelOffset;\n const canvasEndGlobal = globalPixelOffset + canvasWidth;\n const firstBarGlobal = calculateFirstBarPosition(canvasStartGlobal, barWidth, step);\n\n for (\n let barGlobal = Math.max(0, firstBarGlobal);\n barGlobal < canvasEndGlobal;\n barGlobal += step\n ) {\n const x = barGlobal - canvasStartGlobal;\n\n // Skip if the entire bar would be before this canvas\n if (x + barWidth <= 0) continue;\n\n const peakEnd = Math.min(barGlobal + step, length);\n const peak = aggregatePeaks(data, bits, barGlobal, peakEnd);\n\n if (peak) {\n const rects = calculateBarRects(x, barWidth, h2, peak.min, peak.max, drawMode);\n for (const rect of rects) {\n ctx.fillRect(rect.x, rect.y, rect.width, rect.height);\n }\n }\n }\n }\n }\n console.log(\n `[waveform] draw ch${index}: ${canvasMapRef.current.size} chunks, ${(performance.now() - tDraw).toFixed(1)}ms`\n );\n }, [\n canvasMapRef,\n data,\n bits,\n waveHeight,\n waveOutlineColor,\n waveFillColor,\n devicePixelRatio,\n length,\n barWidth,\n barGap,\n drawMode,\n visibleChunkIndices,\n index,\n ]);\n\n // Build visible canvas chunk elements\n const waveforms = visibleChunkIndices.map((i) => {\n const chunkLeft = i * MAX_CANVAS_WIDTH;\n const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);\n\n return (\n <Waveform\n key={`${length}-${i}`}\n $cssWidth={currentWidth}\n $left={chunkLeft}\n width={currentWidth * devicePixelRatio}\n height={waveHeight * devicePixelRatio}\n $waveHeight={waveHeight}\n data-index={i}\n ref={canvasRef}\n />\n );\n });\n\n // Background color depends on draw mode:\n // - inverted: waveFillColor background, canvas masks non-audio areas with waveOutlineColor\n // - normal: waveFillColor background, canvas draws waveFillColor at audio peaks (use with transparentBackground)\n const bgColor = waveFillColor;\n const backgroundCss = transparentBackground ? 'transparent' : waveformColorToCss(bgColor);\n\n return (\n <Wrapper\n $index={index}\n $cssWidth={length}\n className={className}\n $waveHeight={waveHeight}\n $waveFillColor={backgroundCss}\n >\n {waveforms}\n </Wrapper>\n );\n};\n","/**\n * Waveform Playlist Theme\n *\n * This file defines the theme interface and default values for the waveform playlist components.\n */\n\n/**\n * Gradient color stop for waveform gradients\n */\nexport interface GradientStop {\n offset: number; // 0 to 1\n color: string;\n}\n\n/**\n * Gradient configuration for waveforms\n * Can be applied vertically (top to bottom) or horizontally (left to right)\n */\nexport interface WaveformGradient {\n type: 'linear';\n direction: 'vertical' | 'horizontal';\n stops: GradientStop[];\n}\n\n/**\n * Waveform color can be a simple string or a gradient configuration\n */\nexport type WaveformColor = string | WaveformGradient;\n\n/**\n * Type guard to check if a WaveformColor is a gradient\n */\nexport function isWaveformGradient(color: WaveformColor): color is WaveformGradient {\n return typeof color === 'object' && color !== null && 'type' in color;\n}\n\n/**\n * Converts WaveformColor to a CSS background value\n */\nexport function waveformColorToCss(color: WaveformColor): string {\n if (!isWaveformGradient(color)) {\n return color;\n }\n\n const direction = color.direction === 'vertical' ? 'to bottom' : 'to right';\n const stops = color.stops.map((stop) => `${stop.color} ${stop.offset * 100}%`).join(', ');\n\n return `linear-gradient(${direction}, ${stops})`;\n}\n\n/**\n * Waveform drawing mode determines how colors are applied:\n * - 'inverted': Canvas draws waveOutlineColor in areas WITHOUT audio (current default).\n * waveFillColor shows through where audio peaks are. Good for gradient bars.\n * - 'normal': Canvas draws waveFillColor bars where audio peaks ARE.\n * waveOutlineColor is used as background. Good for gradient backgrounds.\n */\nexport type WaveformDrawMode = 'inverted' | 'normal';\n\nexport interface WaveformPlaylistTheme {\n // Waveform drawing mode - controls how colors are applied\n waveformDrawMode?: WaveformDrawMode;\n\n // Waveform colors - can be solid colors or gradients\n waveOutlineColor: WaveformColor;\n waveFillColor: WaveformColor;\n waveProgressColor: string; // Progress stays solid for simplicity\n\n // Selected track colors - can also be gradients\n selectedWaveOutlineColor: WaveformColor;\n selectedWaveFillColor: WaveformColor;\n selectedTrackControlsBackground: string;\n\n // Timescale colors\n timeColor: string;\n timescaleBackgroundColor: string;\n\n // Playback UI colors\n playheadColor: string;\n selectionColor: string;\n\n // Loop region colors (Audacity-style loop markers)\n loopRegionColor: string;\n loopMarkerColor: string;\n\n // Clip header colors (for multi-clip editing)\n clipHeaderBackgroundColor: string;\n clipHeaderBorderColor: string;\n clipHeaderTextColor: string;\n clipHeaderFontFamily: string;\n\n // Selected clip header colors\n selectedClipHeaderBackgroundColor: string;\n\n // Fade overlay colors\n fadeOverlayColor: string;\n\n // UI component colors\n backgroundColor: string;\n surfaceColor: string;\n borderColor: string;\n textColor: string;\n textColorMuted: string;\n\n // Interactive element colors\n inputBackground: string;\n inputBorder: string;\n inputText: string;\n inputPlaceholder: string;\n inputFocusBorder: string;\n\n // Button colors\n buttonBackground: string;\n buttonText: string;\n buttonBorder: string;\n buttonHoverBackground: string;\n\n // Slider colors\n sliderTrackColor: string;\n sliderThumbColor: string;\n\n // Annotation colors\n annotationBoxBackground: string;\n annotationBoxActiveBackground: string;\n annotationBoxHoverBackground: string;\n annotationBoxBorder: string;\n annotationBoxActiveBorder: string;\n annotationLabelColor: string;\n annotationResizeHandleColor: string;\n annotationResizeHandleActiveColor: string;\n annotationTextItemHoverBackground: string;\n\n // Playlist container background (falls back to waveOutlineColor when unset)\n playlistBackgroundColor?: string;\n\n // Piano roll colors (MIDI visualization)\n pianoRollNoteColor: string;\n pianoRollSelectedNoteColor: string;\n pianoRollBackgroundColor: string;\n\n // Spacing and sizing\n borderRadius: string;\n fontFamily: string;\n fontSize: string;\n fontSizeSmall: string;\n}\n\nexport const defaultTheme: WaveformPlaylistTheme = {\n waveformDrawMode: 'inverted',\n waveOutlineColor: '#ffffff',\n waveFillColor: '#1a7f8e', // White background for crisp look\n waveProgressColor: 'rgba(0, 0, 0, 0.10)', // Subtle dark overlay for light mode\n\n selectedWaveOutlineColor: '#ffffff',\n selectedWaveFillColor: '#00b4d8', // Selected: brighter cyan\n selectedTrackControlsBackground: '#d9e9ff', // Light blue background for selected track controls\n timeColor: '#000',\n timescaleBackgroundColor: '#fff',\n playheadColor: '#f00',\n selectionColor: 'rgba(255, 105, 180, 0.7)', // hot pink - high contrast on light backgrounds\n loopRegionColor: 'rgba(59, 130, 246, 0.3)', // Blue - distinct from pink selection\n loopMarkerColor: '#3b82f6', // Blue marker triangles\n clipHeaderBackgroundColor: 'rgba(0, 0, 0, 0.1)',\n clipHeaderBorderColor: 'rgba(0, 0, 0, 0.2)',\n clipHeaderTextColor: '#333',\n clipHeaderFontFamily: 'inherit',\n selectedClipHeaderBackgroundColor: '#b3d9ff', // Brighter blue for selected track clip headers\n\n // Fade overlay colors\n fadeOverlayColor: 'rgba(0, 0, 0, 0.4)', // Semi-transparent overlay for fade regions\n\n // UI component colors\n backgroundColor: '#ffffff',\n surfaceColor: '#f5f5f5',\n borderColor: '#ddd',\n textColor: '#333',\n textColorMuted: '#666',\n\n // Interactive element colors\n inputBackground: '#ffffff',\n inputBorder: '#ccc',\n inputText: '#333',\n inputPlaceholder: '#999',\n inputFocusBorder: '#0066cc',\n\n // Button colors - blue to match common UI patterns\n buttonBackground: '#0091ff',\n buttonText: '#ffffff',\n buttonBorder: '#0081e6',\n buttonHoverBackground: '#0081e6',\n\n // Slider colors\n sliderTrackColor: '#ddd',\n sliderThumbColor: '#daa520', // goldenrod\n\n // Annotation colors\n annotationBoxBackground: 'rgba(255, 255, 255, 0.85)',\n annotationBoxActiveBackground: 'rgba(255, 255, 255, 0.95)',\n annotationBoxHoverBackground: 'rgba(255, 255, 255, 0.98)',\n annotationBoxBorder: '#ff9800',\n annotationBoxActiveBorder: '#d67600',\n annotationLabelColor: '#2a2a2a',\n annotationResizeHandleColor: 'rgba(0, 0, 0, 0.4)',\n annotationResizeHandleActiveColor: 'rgba(0, 0, 0, 0.8)',\n annotationTextItemHoverBackground: 'rgba(0, 0, 0, 0.03)',\n\n // Piano roll colors\n pianoRollNoteColor: '#2a7070',\n pianoRollSelectedNoteColor: '#3d9e9e',\n pianoRollBackgroundColor: '#1a1a2e',\n\n // Spacing and sizing\n borderRadius: '4px',\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, sans-serif',\n fontSize: '14px',\n fontSizeSmall: '12px',\n};\n\nexport const darkTheme: WaveformPlaylistTheme = {\n // Normal mode: waveOutlineColor = bars, waveFillColor = background\n waveformDrawMode: 'inverted',\n // Dark bars on warm amber background\n waveOutlineColor: '#c49a6c', // Solid warm amber for background\n waveFillColor: '#1a1612', // Very dark warm brown for bars\n waveProgressColor: 'rgba(100, 70, 40, 0.6)', // Warm brown progress overlay\n // Selected: slightly lighter bars on brighter amber background\n selectedWaveFillColor: '#241c14', // Slightly lighter warm brown bars when selected\n selectedWaveOutlineColor: '#e8c090', // Brighter amber background when selected\n selectedTrackControlsBackground: '#2a2218', // Dark warm brown for selected track controls\n timeColor: '#d8c0a8', // Warm amber for timescale text\n timescaleBackgroundColor: '#1a1612', // Dark warm brown background\n playheadColor: '#3a8838', // Darker Ampelmännchen green playhead\n selectionColor: 'rgba(60, 140, 58, 0.6)', // Darker Ampelmännchen green selection - visible on dark backgrounds\n loopRegionColor: 'rgba(96, 165, 250, 0.35)', // Light blue - distinct from green selection\n loopMarkerColor: '#60a5fa', // Light blue marker triangles\n clipHeaderBackgroundColor: 'rgba(20, 16, 12, 0.85)', // Dark background for clip headers\n clipHeaderBorderColor: 'rgba(200, 160, 120, 0.25)',\n clipHeaderTextColor: '#d8c0a8', // Warm amber text\n clipHeaderFontFamily: 'inherit',\n selectedClipHeaderBackgroundColor: '#3a2c20', // Darker warm brown for selected clip headers\n\n // Fade overlay colors\n fadeOverlayColor: 'rgba(200, 100, 80, 0.5)', // Warm red-orange overlay visible on dark backgrounds\n\n // UI component colors\n backgroundColor: '#1e1e1e',\n surfaceColor: '#2d2d2d',\n borderColor: '#444',\n textColor: '#e0e0e0',\n textColorMuted: '#999',\n\n // Interactive element colors\n inputBackground: '#2d2d2d',\n inputBorder: '#555',\n inputText: '#e0e0e0',\n inputPlaceholder: '#777',\n inputFocusBorder: '#4A9EFF',\n\n // Button colors - Ampelmännchen green (#63C75F) with black text\n buttonBackground: '#63C75F',\n buttonText: '#0a0a0f',\n buttonBorder: '#52b84e',\n buttonHoverBackground: '#78d074',\n\n // Slider colors\n sliderTrackColor: '#555',\n sliderThumbColor: '#f0c040', // brighter goldenrod for dark mode\n\n // Annotation colors (dark mode - warm amber theme)\n annotationBoxBackground: 'rgba(40, 32, 24, 0.9)',\n annotationBoxActiveBackground: 'rgba(50, 40, 30, 0.95)',\n annotationBoxHoverBackground: 'rgba(60, 48, 36, 0.98)',\n annotationBoxBorder: '#c49a6c',\n annotationBoxActiveBorder: '#d4a87c',\n annotationLabelColor: '#d8c0a8',\n annotationResizeHandleColor: 'rgba(200, 160, 120, 0.5)',\n annotationResizeHandleActiveColor: 'rgba(220, 180, 140, 0.8)',\n annotationTextItemHoverBackground: 'rgba(200, 160, 120, 0.08)',\n\n // Piano roll colors\n pianoRollNoteColor: '#c49a6c',\n pianoRollSelectedNoteColor: '#e8c090',\n pianoRollBackgroundColor: '#0d0d14',\n\n // Spacing and sizing\n borderRadius: '4px',\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, sans-serif',\n fontSize: '14px',\n fontSizeSmall: '12px',\n};\n","import React, {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useCallback,\n useMemo,\n useRef,\n useSyncExternalStore,\n ReactNode,\n} from 'react';\n\nexport interface ScrollViewport {\n scrollLeft: number;\n containerWidth: number;\n /** Left edge of the rendering window in pixels. Includes a 1.5× container-width over-scan buffer to the left of the visible area. */\n visibleStart: number;\n /** Right edge of the rendering window in pixels. Includes a 1.5× container-width over-scan buffer to the right of the visible area. */\n visibleEnd: number;\n}\n\n/**\n * External store for viewport state. Using useSyncExternalStore instead of\n * React context state allows consumers to use selectors — they only re-render\n * when their derived value changes, not on every viewport update.\n */\nclass ViewportStore {\n private _state: ScrollViewport | null;\n private _listeners = new Set<() => void>();\n private _notifyRafId: number | null = null;\n\n constructor(containerEl?: HTMLElement | null) {\n // Seed with actual container width if available, otherwise estimate from\n // window.innerWidth. This prevents the first render from mounting ALL\n // canvas chunks (viewport=null → no filtering), only to prune them to\n // ~3 visible chunks after the first measurement.\n const width =\n containerEl?.clientWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 1024);\n const buffer = width * 1.5;\n this._state = {\n scrollLeft: 0,\n containerWidth: width,\n visibleStart: 0,\n visibleEnd: width + buffer,\n };\n }\n\n subscribe = (callback: () => void): (() => void) => {\n this._listeners.add(callback);\n return () => this._listeners.delete(callback);\n };\n\n getSnapshot = (): ScrollViewport | null => this._state;\n\n /**\n * Update viewport state. Applies a 100px scroll threshold to skip updates\n * that don't affect chunk visibility (1000px chunks with 1.5× overscan buffer).\n * Only notifies listeners when the state actually changes.\n *\n * Listener notification is deferred by one frame via requestAnimationFrame\n * to avoid conflicting with React 19's concurrent rendering. When React\n * time-slices a render across frames, synchronous useSyncExternalStore\n * notifications can trigger \"Should not already be working\" errors.\n */\n update(scrollLeft: number, containerWidth: number): void {\n const buffer = containerWidth * 1.5;\n const visibleStart = Math.max(0, scrollLeft - buffer);\n const visibleEnd = scrollLeft + containerWidth + buffer;\n\n // Skip update if scroll hasn't moved enough to matter for chunk visibility.\n if (\n this._state &&\n this._state.containerWidth === containerWidth &&\n Math.abs(this._state.scrollLeft - scrollLeft) < 100\n ) {\n return;\n }\n\n this._state = { scrollLeft, containerWidth, visibleStart, visibleEnd };\n\n // Defer listener notification to the next frame so it doesn't fire\n // during React's concurrent render time-slice. getSnapshot() returns\n // the new state immediately, so React picks it up on the next render.\n if (this._notifyRafId === null) {\n this._notifyRafId = requestAnimationFrame(() => {\n this._notifyRafId = null;\n for (const listener of this._listeners) {\n listener();\n }\n });\n }\n }\n\n cancelPendingNotification(): void {\n if (this._notifyRafId !== null) {\n cancelAnimationFrame(this._notifyRafId);\n this._notifyRafId = null;\n }\n }\n}\n\nconst ViewportStoreContext = createContext<ViewportStore | null>(null);\n\n// Stable no-op subscribe for when no provider exists\nconst EMPTY_SUBSCRIBE = () => () => {};\nconst NULL_SNAPSHOT = () => null;\n\ntype ScrollViewportProviderProps = {\n containerRef: React.RefObject<HTMLElement | null>;\n children: ReactNode;\n};\n\nexport const ScrollViewportProvider = ({ containerRef, children }: ScrollViewportProviderProps) => {\n const storeRef = useRef<ViewportStore | null>(null);\n if (storeRef.current === null) {\n storeRef.current = new ViewportStore(containerRef.current);\n }\n const store = storeRef.current;\n const rafIdRef = useRef<number | null>(null);\n\n const measure = useCallback(() => {\n const el = containerRef.current;\n if (!el) return;\n store.update(el.scrollLeft, el.clientWidth);\n }, [containerRef, store]);\n\n const scheduleUpdate = useCallback(() => {\n if (rafIdRef.current !== null) return;\n rafIdRef.current = requestAnimationFrame(() => {\n rafIdRef.current = null;\n measure();\n });\n }, [measure]);\n\n // Synchronous initial measurement so children have viewport data on first render.\n // Without this, viewport is null during the first paint and all canvas chunks\n // mount (e.g., 8 per track × 13 tracks = 104 canvases), only to be pruned to\n // ~3 visible chunks after the useEffect measurement fires post-paint.\n useLayoutEffect(() => {\n measure();\n }, [measure]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n // Scroll listener throttled via requestAnimationFrame\n el.addEventListener('scroll', scheduleUpdate, { passive: true });\n\n // ResizeObserver for container width changes\n const resizeObserver = new ResizeObserver(() => {\n scheduleUpdate();\n });\n resizeObserver.observe(el);\n\n return () => {\n el.removeEventListener('scroll', scheduleUpdate);\n resizeObserver.disconnect();\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n store.cancelPendingNotification();\n };\n }, [containerRef, scheduleUpdate, store]);\n\n return <ViewportStoreContext.Provider value={store}>{children}</ViewportStoreContext.Provider>;\n};\n\n/**\n * Full viewport hook — re-renders on every viewport update (after threshold).\n * Use useScrollViewportSelector() instead when you only need derived state.\n */\nexport const useScrollViewport = (): ScrollViewport | null => {\n const store = useContext(ViewportStoreContext);\n return useSyncExternalStore(\n store ? store.subscribe : EMPTY_SUBSCRIBE,\n store ? store.getSnapshot : NULL_SNAPSHOT,\n NULL_SNAPSHOT\n );\n};\n\n/**\n * Selector hook — only re-renders when the selector's return value changes\n * (compared via Object.is). Return primitive values (strings, numbers) for\n * best results, since objects/arrays create new references each call.\n *\n * Example: compute visible chunk key so the component only re-renders when\n * the set of visible chunks actually changes, not on every scroll update.\n */\nexport function useScrollViewportSelector<T>(selector: (viewport: ScrollViewport | null) => T): T {\n const store = useContext(ViewportStoreContext);\n return useSyncExternalStore(\n store ? store.subscribe : EMPTY_SUBSCRIBE,\n () => selector(store ? store.getSnapshot() : null),\n () => selector(null)\n );\n}\n\n/**\n * Returns the indices of canvas chunks that are currently visible (plus overscan buffer).\n * Only triggers a re-render when the set of visible chunks changes, not on every scroll pixel.\n *\n * @param totalWidth Total width in CSS pixels of the content being chunked.\n * @param chunkWidth Width of each chunk in CSS pixels (typically MAX_CANVAS_WIDTH, 1000).\n * @param originX Pixel offset of this content's origin within the global scroll container.\n * Clips not starting at position 0 must provide their left offset so chunk visibility\n * is computed in global viewport coordinates. Defaults to 0 (e.g., TimeScale).\n */\nexport function useVisibleChunkIndices(\n totalWidth: number,\n chunkWidth: number,\n originX: number = 0\n): number[] {\n const visibleChunkKey = useScrollViewportSelector((viewport) => {\n const totalChunks = Math.ceil(totalWidth / chunkWidth);\n const indices: number[] = [];\n\n for (let i = 0; i < totalChunks; i++) {\n const chunkLeft = i * chunkWidth;\n const thisChunkWidth = Math.min(totalWidth - chunkLeft, chunkWidth);\n\n if (viewport) {\n // Convert local chunk coordinates to global viewport space\n const chunkLeftGlobal = originX + chunkLeft;\n const chunkEndGlobal = chunkLeftGlobal + thisChunkWidth;\n if (chunkEndGlobal <= viewport.visibleStart || chunkLeftGlobal >= viewport.visibleEnd) {\n continue;\n }\n }\n\n indices.push(i);\n }\n\n return indices.join(',');\n });\n\n // Memoize on the key string so the returned array is referentially stable\n // between renders — safe to use directly in useLayoutEffect dependency arrays.\n return useMemo(\n () => (visibleChunkKey ? visibleChunkKey.split(',').map(Number) : []),\n [visibleChunkKey]\n );\n}\n","import React, { createContext, useContext, ReactNode } from 'react';\n\nconst ClipViewportOriginContext = createContext<number>(0);\n\ninterface ClipViewportOriginProviderProps {\n originX: number;\n children: ReactNode;\n}\n\n/**\n * Provides the clip's pixel-space origin (left offset) to descendant Channel\n * and SpectrogramChannel components so they can convert local chunk coordinates\n * to global viewport coordinates for virtual scrolling visibility checks.\n *\n * Without this, chunks are compared against the viewport in local (clip-relative)\n * space, which causes them to be culled incorrectly when a clip doesn't start\n * at position 0 on the timeline.\n */\nexport const ClipViewportOriginProvider = ({\n originX,\n children,\n}: ClipViewportOriginProviderProps) => (\n <ClipViewportOriginContext.Provider value={originX}>\n {children}\n </ClipViewportOriginContext.Provider>\n);\n\n/**\n * Returns the clip's pixel-space left offset within the timeline.\n * Defaults to 0 when used outside a ClipViewportOriginProvider (e.g., TimeScale).\n */\nexport const useClipViewportOrigin = (): number => useContext(ClipViewportOriginContext);\n","import { useCallback, useEffect, useRef } from 'react';\n\n/**\n * Manages canvas element refs for chunked virtual-scroll rendering.\n *\n * Provides a callback ref (for `ref={canvasRef}`) that stores canvases\n * by their `data-index` chunk index, and automatically cleans up refs\n * for canvases that have been unmounted by the virtualizer.\n *\n * Used by Channel, TimeScale, and SpectrogramChannel.\n *\n * @returns\n * - `canvasRef` — Callback ref to attach to each `<canvas data-index={i}>` element.\n * - `canvasMapRef` — Stable ref to a `Map<number, HTMLCanvasElement>` for iterating\n * over mounted canvases during draw effects.\n */\nexport function useChunkedCanvasRefs() {\n const canvasMapRef = useRef<Map<number, HTMLCanvasElement>>(new Map());\n\n const canvasRef = useCallback((canvas: HTMLCanvasElement | null) => {\n if (canvas !== null) {\n const idx = parseInt(canvas.dataset.index!, 10);\n canvasMapRef.current.set(idx, canvas);\n }\n }, []);\n\n // Clean up stale refs for unmounted chunks.\n // Intentionally has no dependency array — runs after every render because\n // the virtualizer can unmount canvases at any time, and drawing effects\n // need the map pruned before they iterate it.\n useEffect(() => {\n const map = canvasMapRef.current;\n for (const [idx, canvas] of map.entries()) {\n if (!canvas.isConnected) {\n map.delete(idx);\n }\n }\n });\n\n return { canvasRef, canvasMapRef };\n}\n","import type { Peaks, Bits } from '@waveform-playlist/core';\nimport type { WaveformDrawMode } from '../wfpl-theme';\n\n/**\n * Result of aggregating peaks over a range.\n *\n * Invariants (assumed from valid waveform input):\n * - min and max are normalized to [-1, 1] by dividing by 2^(bits-1)\n * - min <= max (min-of-mins, max-of-maxes — guaranteed by waveform-data library)\n * - Values are finite (derived from integer typed arrays)\n *\n * Construct via aggregatePeaks() — do not create directly.\n */\nexport interface AggregatedPeak {\n min: number;\n max: number;\n}\n\n/**\n * Canvas fillRect parameters for a single waveform bar.\n * width >= 0 and height >= 0 when peak values are in [-1, 1] (guaranteed by\n * waveform-data library normalization).\n */\nexport interface BarRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Aggregates peaks over a range of interleaved min/max pairs.\n * Finds min-of-mins and max-of-maxes, normalized by bit depth.\n *\n * @param data - Interleaved peak data [min0, max0, min1, max1, ...]\n * @param bits - Bit depth (8 or 16)\n * @param startIndex - First peak index (not array index — peak i is at data[i*2], data[i*2+1])\n * @param endIndex - One past the last peak index to include\n * @returns Normalized { min, max } or null if startIndex is out of bounds\n */\nexport function aggregatePeaks(\n data: Peaks,\n bits: Bits,\n startIndex: number,\n endIndex: number\n): AggregatedPeak | null {\n if (startIndex * 2 + 1 >= data.length) {\n return null;\n }\n\n const maxValue = 2 ** (bits - 1);\n let minPeak = data[startIndex * 2] / maxValue;\n let maxPeak = data[startIndex * 2 + 1] / maxValue;\n\n for (let p = startIndex + 1; p < endIndex; p++) {\n if (p * 2 + 1 >= data.length) break;\n const pMin = data[p * 2] / maxValue;\n const pMax = data[p * 2 + 1] / maxValue;\n if (pMin < minPeak) minPeak = pMin;\n if (pMax > maxPeak) maxPeak = pMax;\n }\n\n return { min: minPeak, max: maxPeak };\n}\n\n/**\n * Computes canvas fillRect parameters for a single waveform bar.\n *\n * @param x - Bar x position in canvas coordinates\n * @param barWidth - Width of the bar in pixels\n * @param halfHeight - Half the waveform height (center line y)\n * @param minPeak - Normalized min peak value (negative for below center)\n * @param maxPeak - Normalized max peak value (positive for above center)\n * @param drawMode - 'normal' draws the peak region, 'inverted' draws the non-peak regions\n * @returns Array of BarRect — 1 rect for 'normal', 2 rects for 'inverted'\n */\nexport function calculateBarRects(\n x: number,\n barWidth: number,\n halfHeight: number,\n minPeak: number,\n maxPeak: number,\n drawMode: WaveformDrawMode\n): BarRect[] {\n const min = Math.abs(minPeak * halfHeight);\n const max = Math.abs(maxPeak * halfHeight);\n\n if (drawMode === 'normal') {\n return [{ x, y: halfHeight - max, width: barWidth, height: max + min }];\n }\n\n // Inverted: draw areas WITHOUT audio (top gap + bottom gap)\n return [\n { x, y: 0, width: barWidth, height: halfHeight - max },\n { x, y: halfHeight + min, width: barWidth, height: halfHeight - min },\n ];\n}\n\n/**\n * Computes the first bar position (in global pixel coordinates) that could\n * affect a given canvas chunk.\n *\n * A bar at position X extends from X to X+barWidth-1, so we need bars where\n * barStart + barWidth > canvasStartGlobal.\n *\n * @param canvasStartGlobal - Global pixel offset of the canvas chunk\n * @param barWidth - Width of each bar in pixels\n * @param step - Bar stride (barWidth + barGap)\n * @returns The first bar's global position (always >= 0 when step >= barWidth; caller clamps to 0)\n */\nexport function calculateFirstBarPosition(\n canvasStartGlobal: number,\n barWidth: number,\n step: number\n): number {\n return Math.floor((canvasStartGlobal - barWidth + step) / step) * step;\n}\n","import React from 'react';\n\nconst errorContainerStyle: React.CSSProperties = {\n padding: '16px',\n background: '#1a1a2e',\n color: '#e0e0e0',\n border: '1px solid #d08070',\n borderRadius: '4px',\n fontFamily: 'monospace',\n fontSize: '13px',\n minHeight: '60px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n};\n\nexport interface PlaylistErrorBoundaryProps {\n children: React.ReactNode;\n /** Custom fallback UI to show when an error occurs */\n fallback?: React.ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * Error Boundary for waveform playlist components.\n *\n * Catches render errors in child components (canvas failures, audio\n * processing errors) and displays a fallback UI instead of crashing\n * the entire application.\n *\n * @example\n * ```tsx\n * <PlaylistErrorBoundary>\n * <WaveformPlaylistProvider tracks={tracks}>\n * <Waveform />\n * </WaveformPlaylistProvider>\n * </PlaylistErrorBoundary>\n * ```\n */\nexport class PlaylistErrorBoundary extends React.Component<\n PlaylistErrorBoundaryProps,\n ErrorBoundaryState\n> {\n constructor(props: PlaylistErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n console.error('[waveform-playlist] Render error:', error, errorInfo.componentStack);\n }\n\n render(): React.ReactNode {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n return (\n <div style={errorContainerStyle}>\n Waveform playlist encountered an error. Check console for details.\n </div>\n );\n }\n return this.props.children;\n }\n}\n","import React, { FunctionComponent, ReactNode } from 'react';\nimport styled from 'styled-components';\nimport { useDraggable } from '@dnd-kit/react';\nimport { ClipHeader } from './ClipHeader';\nimport { ClipBoundary } from './ClipBoundary';\nimport { FadeOverlay } from './FadeOverlay';\nimport type { Fade } from '@waveform-playlist/core';\nimport { ClipViewportOriginProvider } from '../contexts/ClipViewportOrigin';\n\ninterface ClipContainerProps {\n readonly $left?: number; // Horizontal position in pixels (optional for overlay)\n readonly $width?: number; // Width in pixels (optional for overlay)\n readonly $isOverlay?: boolean; // Whether this is rendering in overlay mode\n}\n\nconst ClipContainer = styled.div.attrs<ClipContainerProps>((props) => ({\n style: props.$isOverlay\n ? {}\n : {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<ClipContainerProps>`\n position: ${(props) => (props.$isOverlay ? 'relative' : 'absolute')};\n top: 0;\n height: ${(props) => (props.$isOverlay ? 'auto' : '100%')};\n width: ${(props) => (props.$isOverlay ? `${props.$width}px` : 'auto')};\n display: flex;\n flex-direction: column;\n background: rgba(255, 255, 255, 0.05);\n z-index: 10; /* Above progress overlay (z-index: 2) but below controls/playhead */\n pointer-events: none; /* Let clicks pass through to ClickOverlay for playhead positioning */\n\n &:hover {\n background: rgba(255, 255, 255, 0.08);\n }\n`;\n\ninterface ChannelsWrapperProps {\n readonly $isOverlay?: boolean;\n}\n\nconst ChannelsWrapper = styled.div<ChannelsWrapperProps>`\n flex: 1;\n position: relative;\n overflow: ${(props) => (props.$isOverlay ? 'visible' : 'hidden')};\n`;\n\nexport interface ClipProps {\n className?: string;\n children?: ReactNode;\n clipId: string; // Unique clip ID\n trackIndex: number; // Track index (for drag operations)\n clipIndex: number; // Clip index within track (for drag operations)\n trackName: string; // Track name (shown in header)\n startSample: number; // Start position in samples\n durationSamples: number; // Duration in samples\n samplesPerPixel: number;\n // Optional header (for multi-clip editing with drag-to-move)\n showHeader?: boolean;\n disableHeaderDrag?: boolean; // Disable drag on header (for presentation-only rendering)\n isOverlay?: boolean; // Rendering in overlay mode (disables absolute positioning)\n // Track selection\n isSelected?: boolean; // Whether the track is selected\n onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void; // Called when clip is pressed (for track selection - fires before drag)\n trackId?: string; // Track ID for identifying which track this clip belongs to\n // Fade configuration\n fadeIn?: Fade; // Fade in effect\n fadeOut?: Fade; // Fade out effect\n sampleRate?: number; // Sample rate for converting fade duration to pixels\n showFades?: boolean; // Show fade in/out overlays\n // Mobile optimization\n touchOptimized?: boolean; // Enable larger touch targets for mobile devices\n}\n\n/**\n * Clip component for rendering individual audio clips within a track\n *\n * Each clip is positioned based on its startSample and has a width based on its durationSamples.\n * This allows multiple clips to be arranged on a single track with gaps or overlaps.\n *\n * Includes a draggable ClipHeader at the top for repositioning clips on the timeline.\n */\nexport const Clip: FunctionComponent<ClipProps> = ({\n children,\n className,\n clipId,\n trackIndex,\n clipIndex,\n trackName,\n startSample,\n durationSamples,\n samplesPerPixel,\n showHeader = false,\n disableHeaderDrag = false,\n isOverlay = false,\n isSelected = false,\n onMouseDown,\n trackId,\n fadeIn,\n fadeOut,\n sampleRate = 44100,\n showFades = false,\n touchOptimized = false,\n}) => {\n // Calculate horizontal position based on start sample\n // Use Math.floor to always snap to pixel boundaries\n const left = Math.floor(startSample / samplesPerPixel);\n\n // Calculate width as the difference between end and start pixel positions\n // This ensures clips are perfectly adjacent with no gaps\n const endPixel = Math.floor((startSample + durationSamples) / samplesPerPixel);\n const width = endPixel - left;\n\n // Use draggable only if header is shown and drag is enabled\n const enableDrag = showHeader && !disableHeaderDrag && !isOverlay;\n\n // Main clip draggable (for moving entire clip)\n const draggableId = `clip-${trackIndex}-${clipIndex}`;\n const {\n ref: clipRef,\n handleRef,\n isDragSource,\n } = useDraggable({\n id: draggableId,\n data: { clipId, trackIndex, clipIndex },\n disabled: !enableDrag,\n });\n\n // Left boundary draggable (for trimming start)\n // feedback: 'none' disables the Feedback plugin for this draggable — trim visual feedback\n // comes from React state updates resizing the clip, not CSS translate.\n const leftBoundaryId = `clip-boundary-left-${trackIndex}-${clipIndex}`;\n const { ref: leftBoundaryRef, isDragSource: isLeftBoundaryDragging } = useDraggable({\n id: leftBoundaryId,\n data: { clipId, trackIndex, clipIndex, boundary: 'left' },\n disabled: !enableDrag,\n feedback: 'none',\n });\n\n // Right boundary draggable (for trimming end)\n const rightBoundaryId = `clip-boundary-right-${trackIndex}-${clipIndex}`;\n const { ref: rightBoundaryRef, isDragSource: isRightBoundaryDragging } = useDraggable({\n id: rightBoundaryId,\n data: { clipId, trackIndex, clipIndex, boundary: 'right' },\n disabled: !enableDrag,\n feedback: 'none',\n });\n\n // Elevate z-index during drag (below controls z-index: 999, above other clips)\n const style = isDragSource ? { zIndex: 100 } : undefined;\n\n return (\n <ClipContainer\n ref={clipRef}\n style={style}\n className={className}\n $left={left}\n $width={width}\n $isOverlay={isOverlay}\n data-clip-container=\"true\"\n data-track-id={trackId}\n onMouseDown={onMouseDown}\n // Always set tabIndex=-1 so @dnd-kit doesn't add tabindex=\"0\".\n // Without this, the browser auto-scrolls overflow containers to show\n // the focused clip element when many clips mount simultaneously.\n // Drag still works — the handle (ClipHeader) receives keyboard focus.\n tabIndex={-1}\n >\n {showHeader && (\n <ClipHeader\n clipId={clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n trackName={trackName}\n isSelected={isSelected}\n disableDrag={disableHeaderDrag}\n dragHandleProps={enableDrag ? { handleRef } : undefined}\n />\n )}\n <ClipViewportOriginProvider originX={left}>\n <ChannelsWrapper $isOverlay={isOverlay}>\n {children}\n {/* Fade overlays */}\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={width - Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)}\n width={Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)}\n type=\"fadeOut\"\n curveType={fadeOut.type}\n />\n )}\n </ChannelsWrapper>\n </ClipViewportOriginProvider>\n {/* Clip boundaries - outside ChannelsWrapper to avoid overflow:hidden clipping */}\n {showHeader && !disableHeaderDrag && !isOverlay && (\n <>\n <ClipBoundary\n clipId={clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n edge=\"left\"\n touchOptimized={touchOptimized}\n dragHandleProps={{\n ref: leftBoundaryRef,\n isDragging: isLeftBoundaryDragging,\n }}\n />\n <ClipBoundary\n clipId={clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n edge=\"right\"\n touchOptimized={touchOptimized}\n dragHandleProps={{\n ref: rightBoundaryRef,\n isDragging: isRightBoundaryDragging,\n }}\n />\n </>\n )}\n </ClipContainer>\n );\n};\n","import React, { FunctionComponent } from 'react';\nimport styled from 'styled-components';\n\nexport const CLIP_HEADER_HEIGHT = 22; // Height of the clip header in pixels\n\ninterface HeaderContainerProps {\n readonly $interactive?: boolean; // Whether it's draggable or just presentational\n readonly $isSelected?: boolean; // Whether the track is selected\n}\n\nconst HeaderContainer = styled.div<HeaderContainerProps>`\n position: relative;\n height: ${CLIP_HEADER_HEIGHT}px;\n background: ${(props) =>\n props.$isSelected\n ? props.theme.selectedClipHeaderBackgroundColor\n : props.theme.clipHeaderBackgroundColor};\n border-bottom: 1px solid ${(props) => props.theme.clipHeaderBorderColor};\n display: flex;\n align-items: center;\n padding: 0 8px;\n cursor: ${(props) => (props.$interactive ? 'grab' : 'default')};\n user-select: none;\n z-index: 110;\n flex-shrink: 0;\n pointer-events: auto; /* Re-enable pointer events (parent ClipContainer has pointer-events: none) */\n touch-action: ${(props) =>\n props.$interactive ? 'none' : 'auto'}; /* Prevent browser scroll during drag on touch devices */\n\n ${(props) =>\n props.$interactive &&\n `\n &:hover {\n background: ${props.theme.clipHeaderBackgroundColor}dd;\n }\n\n &:active {\n cursor: grabbing;\n }\n `}\n`;\n\nconst TrackName = styled.span`\n font-size: 11px;\n font-weight: 600;\n font-family: ${(props) => props.theme.clipHeaderFontFamily};\n color: ${(props) => props.theme.clipHeaderTextColor};\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n`;\n\n// Presentational-only header (no drag behavior)\nexport interface ClipHeaderPresentationalProps {\n trackName: string;\n isSelected?: boolean; // Whether the track is selected\n}\n\nexport const ClipHeaderPresentational: FunctionComponent<ClipHeaderPresentationalProps> = ({\n trackName,\n isSelected = false,\n}) => {\n return (\n <HeaderContainer $interactive={false} $isSelected={isSelected}>\n <TrackName title={trackName}>{trackName}</TrackName>\n </HeaderContainer>\n );\n};\n\nexport interface DragHandleProps {\n handleRef: (element: Element | null) => void;\n}\n\nexport interface ClipHeaderProps {\n clipId: string;\n trackIndex: number;\n clipIndex: number;\n trackName: string;\n isSelected?: boolean; // Whether the track is selected\n disableDrag?: boolean; // Disable drag behavior (for presentation-only rendering in overlays)\n dragHandleProps?: DragHandleProps; // Props for drag handle functionality\n}\n\n/**\n * ClipHeader component - Draggable title bar for audio clips\n *\n * Renders at the top of each clip (above all channels).\n * Drag the header to move the clip along the timeline.\n * Shows the track name (not clip-specific info).\n *\n * Theme colors (from useTheme):\n * - clipHeaderBackgroundColor / selectedClipHeaderBackgroundColor\n * - clipHeaderBorderColor\n * - clipHeaderTextColor\n */\nexport const ClipHeader: FunctionComponent<ClipHeaderProps> = ({\n clipId,\n trackIndex: _trackIndex,\n clipIndex: _clipIndex,\n trackName,\n isSelected = false,\n disableDrag = false,\n dragHandleProps,\n}) => {\n // Use purely presentational version when drag is disabled or no drag handle props\n if (disableDrag || !dragHandleProps) {\n return <ClipHeaderPresentational trackName={trackName} isSelected={isSelected} />;\n }\n\n const { handleRef } = dragHandleProps;\n\n return (\n <HeaderContainer\n ref={handleRef}\n data-clip-id={clipId}\n $interactive={true}\n $isSelected={isSelected}\n >\n <TrackName title={trackName}>{trackName}</TrackName>\n </HeaderContainer>\n );\n};\n","import React, { FunctionComponent } from 'react';\nimport styled from 'styled-components';\n\nexport const CLIP_BOUNDARY_WIDTH = 8; // Width of the draggable boundary in pixels\nexport const CLIP_BOUNDARY_WIDTH_TOUCH = 24; // Larger touch target for mobile (minimum 44px recommended, but 24px works well for trim handles)\n\ntype BoundaryEdge = 'left' | 'right';\n\ninterface BoundaryContainerProps {\n readonly $edge: BoundaryEdge;\n readonly $isDragging?: boolean;\n readonly $isHovered?: boolean;\n readonly $touchOptimized?: boolean;\n}\n\nconst BoundaryContainer = styled.div<BoundaryContainerProps>`\n position: absolute;\n ${(props) => (props.$edge === 'left' ? 'left: 0;' : 'right: 0;')}\n top: 0;\n bottom: 0;\n width: ${(props) => (props.$touchOptimized ? CLIP_BOUNDARY_WIDTH_TOUCH : CLIP_BOUNDARY_WIDTH)}px;\n cursor: col-resize;\n user-select: none;\n z-index: 105; /* Above waveform, below header */\n pointer-events: auto; /* Re-enable pointer events (parent ClipContainer has pointer-events: none) */\n touch-action: none; /* Prevent browser scroll during drag on touch devices */\n\n /* Invisible by default, visible on hover */\n background: ${(props) =>\n props.$isDragging\n ? 'rgba(255, 255, 255, 0.4)'\n : props.$isHovered\n ? 'rgba(255, 255, 255, 0.2)'\n : 'transparent'};\n\n ${(props) =>\n props.$edge === 'left'\n ? `border-left: 2px solid ${\n props.$isDragging\n ? 'rgba(255, 255, 255, 0.8)'\n : props.$isHovered\n ? 'rgba(255, 255, 255, 0.5)'\n : 'transparent'\n };`\n : `border-right: 2px solid ${\n props.$isDragging\n ? 'rgba(255, 255, 255, 0.8)'\n : props.$isHovered\n ? 'rgba(255, 255, 255, 0.5)'\n : 'transparent'\n };`}\n\n transition: background 0.15s ease, border-color 0.15s ease;\n\n &:hover {\n background: rgba(255, 255, 255, 0.2);\n ${(props) =>\n props.$edge === 'left'\n ? 'border-left: 2px solid rgba(255, 255, 255, 0.5);'\n : 'border-right: 2px solid rgba(255, 255, 255, 0.5);'}\n }\n\n &:active {\n background: rgba(255, 255, 255, 0.4);\n ${(props) =>\n props.$edge === 'left'\n ? 'border-left: 2px solid rgba(255, 255, 255, 0.8);'\n : 'border-right: 2px solid rgba(255, 255, 255, 0.8);'}\n }\n`;\n\ninterface BoundaryDragHandleProps {\n ref: (element: Element | null) => void;\n isDragging?: boolean;\n}\n\nexport interface ClipBoundaryProps {\n clipId: string;\n trackIndex: number;\n clipIndex: number;\n edge: BoundaryEdge;\n dragHandleProps?: BoundaryDragHandleProps;\n /**\n * Enable larger touch targets for mobile devices.\n * When true, boundary width increases from 8px to 24px for easier touch targeting.\n */\n touchOptimized?: boolean;\n}\n\n/**\n * ClipBoundary component - Draggable edge for trimming clips\n *\n * Renders at the left or right edge of a clip.\n * Drag to trim the clip (adjust offset and duration).\n * Supports bidirectional trimming (trim in and out).\n *\n * On mobile (touchOptimized=true), boundaries are wider for easier targeting.\n */\nexport const ClipBoundary: FunctionComponent<ClipBoundaryProps> = ({\n clipId,\n trackIndex: _trackIndex,\n clipIndex: _clipIndex,\n edge,\n dragHandleProps,\n touchOptimized = false,\n}) => {\n const [isHovered, setIsHovered] = React.useState(false);\n\n if (!dragHandleProps) {\n // No drag handle props provided, render non-interactive boundary\n return null;\n }\n\n const { ref: boundaryRef, isDragging } = dragHandleProps;\n\n return (\n <BoundaryContainer\n ref={boundaryRef}\n data-clip-id={clipId}\n data-boundary-edge={edge}\n $edge={edge}\n $isDragging={isDragging}\n $isHovered={isHovered}\n $touchOptimized={touchOptimized}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n />\n );\n};\n","import React, { FunctionComponent } from 'react';\nimport styled, { useTheme } from 'styled-components';\nimport type { FadeType } from '@waveform-playlist/core';\nimport type { WaveformPlaylistTheme } from '../wfpl-theme';\n\ninterface FadeContainerProps {\n readonly $left: number;\n readonly $width: number;\n readonly $type: 'fadeIn' | 'fadeOut';\n}\n\n// Use .attrs() for left/width to avoid generating new CSS classes on every render\nconst FadeContainer = styled.div.attrs<FadeContainerProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<FadeContainerProps>`\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n z-index: 50;\n`;\n\ninterface FadeSvgProps {\n readonly $type: 'fadeIn' | 'fadeOut';\n}\n\nconst FadeSvg = styled.svg<FadeSvgProps>`\n width: 100%;\n height: 100%;\n display: block;\n /* Flip horizontally for fadeOut - makes it mirror of fadeIn */\n transform: ${(props) => (props.$type === 'fadeOut' ? 'scaleX(-1)' : 'none')};\n`;\n\nexport interface FadeOverlayProps {\n /** Position in pixels from the start of the clip */\n left: number;\n /** Width of the fade region in pixels */\n width: number;\n /** Type of fade: fadeIn or fadeOut */\n type: 'fadeIn' | 'fadeOut';\n /** Fade curve type */\n curveType?: FadeType;\n /** Custom fill color (defaults to theme.fadeOverlayColor) */\n color?: string;\n}\n\n/**\n * Generates an SVG path for a fade in curve\n * Always generates fadeIn shape - fadeOut is achieved by CSS transform scaleX(-1)\n *\n * The curve shows: more overlay at start (audio quiet), less overlay at end (audio full)\n */\nfunction generateFadePath(\n width: number,\n height: number,\n curveType: FadeType = 'logarithmic'\n): string {\n const points: string[] = [];\n const numPoints = Math.max(20, Math.min(width, 100)); // More points for smoother curves\n\n for (let i = 0; i <= numPoints; i++) {\n const x = (i / numPoints) * width;\n const progress = i / numPoints; // 0 to 1\n\n // Apply curve transformation based on type\n let curvedProgress: number;\n switch (curveType) {\n case 'linear':\n curvedProgress = progress;\n break;\n case 'exponential':\n curvedProgress = progress * progress;\n break;\n case 'sCurve':\n // S-curve using sine\n curvedProgress = (1 - Math.cos(progress * Math.PI)) / 2;\n break;\n case 'logarithmic':\n default:\n // Logarithmic curve (more natural for audio)\n curvedProgress = Math.log10(1 + progress * 9) / Math.log10(10);\n break;\n }\n\n // fadeIn: starts covered (y near 0), ends uncovered (y near height)\n // Y=0 is top of SVG, Y=height is bottom\n // We draw the curve edge, then fill above it\n const y = (1 - curvedProgress) * height;\n points.push(`${x},${y}`);\n }\n\n // Path: start at bottom-left, draw curve, go to top-right, top-left, close\n return `M 0,${height} L ${points.join(' L ')} L ${width},0 L 0,0 Z`;\n}\n\n/**\n * FadeOverlay component - Visual indicator for fade in/out regions on clips\n *\n * Renders a semi-transparent overlay with a curved shape indicating\n * the fade envelope. The shape follows the selected fade curve type.\n */\nexport const FadeOverlay: FunctionComponent<FadeOverlayProps> = ({\n left,\n width,\n type,\n curveType = 'logarithmic',\n color,\n}) => {\n const theme = useTheme() as WaveformPlaylistTheme;\n\n // Don't render if width is too small\n if (width < 1) return null;\n\n // Use color prop, then theme color, then fallback\n const fillColor = color || theme?.fadeOverlayColor || 'rgba(0, 0, 0, 0.4)';\n\n return (\n <FadeContainer $left={left} $width={width} $type={type}>\n <FadeSvg $type={type} viewBox={`0 0 ${width} 100`} preserveAspectRatio=\"none\">\n <path d={generateFadePath(width, 100, curveType)} fill={fillColor} />\n </FadeSvg>\n </FadeContainer>\n );\n};\n","import React from 'react';\nimport styled from 'styled-components';\nimport { BaseSlider, BaseLabel } from '../styled/index';\n\nconst VolumeContainer = styled.div`\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n`;\n\nconst VolumeLabel = styled(BaseLabel)`\n margin: 0;\n white-space: nowrap;\n`;\n\nconst VolumeSlider = styled(BaseSlider)`\n width: 120px;\n`;\n\nexport interface MasterVolumeControlProps {\n volume: number; // 0-1.0 (linear gain, consistent with Web Audio API)\n onChange: (volume: number) => void;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * Master volume control slider component\n * Accepts volume as 0-1.0 range (linear gain) and displays as percentage\n */\nexport const MasterVolumeControl: React.FC<MasterVolumeControlProps> = ({\n volume,\n onChange,\n disabled = false,\n className,\n}) => {\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n // Convert percentage (0-100) to linear gain (0-1.0)\n onChange(parseFloat(e.target.value) / 100);\n };\n\n return (\n <VolumeContainer className={className}>\n <VolumeLabel htmlFor=\"master-gain\">Master Volume</VolumeLabel>\n <VolumeSlider\n min=\"0\"\n max=\"100\"\n value={volume * 100}\n onChange={handleChange}\n disabled={disabled}\n id=\"master-gain\"\n />\n </VolumeContainer>\n );\n};\n","import React, { FunctionComponent, useEffect, useMemo } from 'react';\nimport styled from 'styled-components';\nimport type { MidiNoteData } from '@waveform-playlist/core';\nimport { MAX_CANVAS_WIDTH } from '@waveform-playlist/core';\nimport { useVisibleChunkIndices } from '../contexts/ScrollViewport';\nimport { useClipViewportOrigin } from '../contexts/ClipViewportOrigin';\nimport { useChunkedCanvasRefs } from '../hooks/useChunkedCanvasRefs';\n\ninterface CanvasProps {\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $left: number;\n}\n\nconst NoteCanvas = styled.canvas.attrs<CanvasProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n left: `${props.$left}px`,\n },\n}))<CanvasProps>`\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n image-rendering: crisp-edges;\n`;\n\ninterface WrapperProps {\n readonly $index: number;\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $backgroundColor: string;\n}\n\nconst Wrapper = styled.div.attrs<WrapperProps>((props) => ({\n style: {\n top: `${props.$waveHeight * props.$index}px`,\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n },\n}))<WrapperProps>`\n position: absolute;\n background: ${(props) => props.$backgroundColor};\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\nexport interface PianoRollChannelProps {\n index: number;\n midiNotes: MidiNoteData[];\n length: number;\n waveHeight: number;\n devicePixelRatio: number;\n samplesPerPixel: number;\n sampleRate: number;\n clipOffsetSeconds: number;\n noteColor?: string;\n selectedNoteColor?: string;\n isSelected?: boolean;\n transparentBackground?: boolean;\n backgroundColor?: string;\n}\n\nexport const PianoRollChannel: FunctionComponent<PianoRollChannelProps> = ({\n index,\n midiNotes,\n length,\n waveHeight,\n devicePixelRatio,\n samplesPerPixel,\n sampleRate,\n clipOffsetSeconds,\n noteColor = '#2a7070',\n selectedNoteColor = '#3d9e9e',\n isSelected = false,\n transparentBackground = false,\n backgroundColor = '#1a1a2e',\n}) => {\n const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();\n const clipOriginX = useClipViewportOrigin();\n const visibleChunkIndices = useVisibleChunkIndices(length, MAX_CANVAS_WIDTH, clipOriginX);\n\n // Compute note pitch range for vertical mapping\n const { minMidi, maxMidi } = useMemo(() => {\n if (midiNotes.length === 0) return { minMidi: 0, maxMidi: 127 };\n let min = 127,\n max = 0;\n for (const note of midiNotes) {\n if (note.midi < min) min = note.midi;\n if (note.midi > max) max = note.midi;\n }\n // Add 1-note padding on each side for visual breathing room\n return { minMidi: Math.max(0, min - 1), maxMidi: Math.min(127, max + 1) };\n }, [midiNotes]);\n\n const color = isSelected ? selectedNoteColor : noteColor;\n\n // useEffect (not useLayoutEffect) so the browser paints the track layout\n // (controls + empty canvas containers) before heavy canvas drawing starts.\n useEffect(() => {\n const tDraw = performance.now();\n const noteRange = maxMidi - minMidi + 1;\n const noteHeight = Math.max(2, waveHeight / noteRange);\n const pixelsPerSecond = sampleRate / samplesPerPixel;\n\n for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {\n const chunkPixelStart = canvasIdx * MAX_CANVAS_WIDTH;\n const canvasWidth = canvas.width / devicePixelRatio;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n // Time range this chunk covers (relative to clip start)\n const chunkStartTime = (chunkPixelStart * samplesPerPixel) / sampleRate;\n const chunkEndTime = ((chunkPixelStart + canvasWidth) * samplesPerPixel) / sampleRate;\n\n for (const note of midiNotes) {\n // Note times are relative to clip start; clipOffsetSeconds shifts them\n const noteStart = note.time - clipOffsetSeconds;\n const noteEnd = noteStart + note.duration;\n\n // Skip notes outside this chunk's time range\n if (noteEnd <= chunkStartTime || noteStart >= chunkEndTime) continue;\n\n const x = noteStart * pixelsPerSecond - chunkPixelStart;\n const w = Math.max(2, note.duration * pixelsPerSecond);\n // MIDI note 127 is at top (y=0), note 0 at bottom\n const y = ((maxMidi - note.midi) / noteRange) * waveHeight;\n\n // Velocity maps to opacity: 0.3 (pp) → 1.0 (ff)\n const alpha = 0.3 + note.velocity * 0.7;\n ctx.fillStyle = color;\n ctx.globalAlpha = alpha;\n\n // Rounded rectangle (1px radius)\n const r = 1;\n ctx.beginPath();\n ctx.roundRect(x, y, w, noteHeight, r);\n ctx.fill();\n }\n\n ctx.globalAlpha = 1;\n }\n console.log(\n `[piano-roll] draw ch${index}: ${canvasMapRef.current.size} chunks, ${midiNotes.length} notes, ${(performance.now() - tDraw).toFixed(1)}ms`\n );\n }, [\n canvasMapRef,\n midiNotes,\n waveHeight,\n devicePixelRatio,\n samplesPerPixel,\n sampleRate,\n clipOffsetSeconds,\n color,\n minMidi,\n maxMidi,\n length,\n visibleChunkIndices,\n index,\n ]);\n\n const canvases = visibleChunkIndices.map((i) => {\n const chunkLeft = i * MAX_CANVAS_WIDTH;\n const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);\n\n return (\n <NoteCanvas\n key={`${length}-${i}`}\n $cssWidth={currentWidth}\n $left={chunkLeft}\n width={currentWidth * devicePixelRatio}\n height={waveHeight * devicePixelRatio}\n $waveHeight={waveHeight}\n data-index={i}\n ref={canvasRef}\n />\n );\n });\n\n const bgColor = transparentBackground ? 'transparent' : backgroundColor;\n\n return (\n <Wrapper $index={index} $cssWidth={length} $waveHeight={waveHeight} $backgroundColor={bgColor}>\n {canvases}\n </Wrapper>\n );\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\n\ninterface PlayheadLineProps {\n readonly $position: number;\n readonly $color: string;\n}\n\nconst PlayheadLine = styled.div.attrs<PlayheadLineProps>((props) => ({\n style: {\n transform: `translate3d(${props.$position}px, 0, 0)`,\n },\n}))<PlayheadLineProps>`\n position: absolute;\n top: 0;\n left: 0;\n width: 2px;\n background: ${(props) => props.$color};\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\n/**\n * Props passed to the default playhead component or custom render function.\n */\nexport interface PlayheadProps {\n /** Position in pixels from left edge (only valid when not playing) */\n position: number;\n /** Playhead color (default: #ff0000) */\n color?: string;\n /** Whether audio is currently playing */\n isPlaying: boolean;\n /** Ref to current time in seconds - use for smooth animation during playback */\n currentTimeRef: React.RefObject<number>;\n /** Audio context start time when playback began. Fallback when getPlaybackTime is not provided. */\n playbackStartTimeRef: React.RefObject<number>;\n /** Audio position when playback started. Fallback when getPlaybackTime is not provided. */\n audioStartPositionRef: React.RefObject<number>;\n /** Samples per pixel - for converting time to pixels */\n samplesPerPixel: number;\n /** Sample rate - for converting time to pixels */\n sampleRate: number;\n /** Controls offset in pixels (deprecated, always 0 — controls are now outside scroll area) */\n controlsOffset?: number;\n /** Function to get current audio context time - required for smooth animation */\n getAudioContextTime?: () => number;\n /** Returns current playback time (auto-wraps at loop boundaries). Preferred over manual elapsed calculation. */\n getPlaybackTime?: () => number;\n}\n\n/**\n * Type for custom playhead render functions.\n * Receives position, color, and animation refs for smooth 60fps animation.\n * Custom playheads should use requestAnimationFrame with the refs during playback.\n */\nexport type RenderPlayheadFunction = (props: PlayheadProps) => React.ReactNode;\n\n/**\n * Default playhead component - a simple vertical line.\n * Uses GPU-accelerated transform for smooth animation.\n */\nexport const Playhead: React.FC<PlayheadProps> = ({ position, color = '#ff0000' }) => {\n return <PlayheadLine $position={position} $color={color} />;\n};\n\n// === Custom Playhead Variants ===\n\nconst PlayheadWithMarkerContainer = styled.div<{ $color: string }>`\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\nconst MarkerTriangle = styled.div<{ $color: string }>`\n position: absolute;\n top: -10px;\n left: -6px;\n width: 0;\n height: 0;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 10px solid ${(props) => props.$color};\n`;\n\nconst MarkerLine = styled.div<{ $color: string }>`\n position: absolute;\n top: 0;\n left: 0;\n width: 2px;\n height: 100%;\n background: ${(props) => props.$color};\n`;\n\n/**\n * Playhead with a triangle marker at the top.\n * Provides better visual indication of the current position.\n * Uses requestAnimationFrame for smooth 60fps animation during playback.\n */\nexport const PlayheadWithMarker: React.FC<PlayheadProps> = ({\n color = '#ff0000',\n isPlaying,\n currentTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n samplesPerPixel,\n sampleRate,\n controlsOffset = 0,\n getAudioContextTime,\n getPlaybackTime,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n\n useEffect(() => {\n const updatePosition = () => {\n if (containerRef.current) {\n let time: number;\n if (isPlaying) {\n if (getPlaybackTime) {\n time = getPlaybackTime();\n } else if (getAudioContextTime) {\n const elapsed = getAudioContextTime() - (playbackStartTimeRef.current ?? 0);\n time = (audioStartPositionRef.current ?? 0) + elapsed;\n } else {\n time = currentTimeRef.current ?? 0;\n }\n } else {\n time = currentTimeRef.current ?? 0;\n }\n const pos = (time * sampleRate) / samplesPerPixel + controlsOffset;\n containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;\n }\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updatePosition);\n }\n };\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updatePosition);\n } else {\n updatePosition();\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 controlsOffset,\n currentTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n getAudioContextTime,\n getPlaybackTime,\n ]);\n\n // Update position when stopped (for seeks)\n useEffect(() => {\n if (!isPlaying && containerRef.current) {\n const time = currentTimeRef.current ?? 0;\n const pos = (time * sampleRate) / samplesPerPixel + controlsOffset;\n containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;\n }\n });\n\n return (\n <PlayheadWithMarkerContainer ref={containerRef} $color={color}>\n <MarkerTriangle $color={color} />\n <MarkerLine $color={color} />\n </PlayheadWithMarkerContainer>\n );\n};\n","import styled, { DefaultTheme, withTheme } from 'styled-components';\nimport React, { FunctionComponent, useRef, useCallback } from 'react';\nimport { ScrollViewportProvider } from '../contexts/ScrollViewport';\n\n/**\n * Outer wrapper: flex layout separating controls column from scroll area.\n * overflow-y: hidden prevents vertical scrollbar on the wrapper itself.\n */\nconst Wrapper = styled.div`\n display: flex;\n overflow-y: hidden;\n position: relative;\n`;\n\ninterface ControlsColumnProps {\n readonly $width: number;\n}\n\nconst ControlsColumn = styled.div.attrs<ControlsColumnProps>((props) => ({\n style: { width: `${props.$width}px` },\n}))<ControlsColumnProps>`\n flex-shrink: 0;\n overflow: hidden;\n`;\n\ninterface TimescaleGapProps {\n readonly $height: number;\n}\n\nconst TimescaleGap = styled.div.attrs<TimescaleGapProps>((props) => ({\n style: { height: `${props.$height}px` },\n}))<TimescaleGapProps>``;\n\nconst ScrollArea = styled.div`\n overflow-x: auto;\n overflow-y: hidden;\n overflow-anchor: none;\n flex: 1;\n position: relative;\n`;\n\ninterface ScrollContainerInnerProps {\n readonly $backgroundColor?: string;\n readonly $width?: number;\n}\n\n// Use .attrs() for width to avoid generating new CSS classes on every render\nconst ScrollContainerInner = styled.div.attrs<ScrollContainerInnerProps>((props) => ({\n style: props.$width !== undefined ? { width: `${props.$width}px` } : {},\n}))<ScrollContainerInnerProps>`\n position: relative;\n background: ${(props) => props.$backgroundColor || 'transparent'};\n`;\n\ninterface TimescaleWrapperProps {\n readonly $width?: number;\n readonly $backgroundColor?: string;\n}\n\n// Use .attrs() for width to avoid generating new CSS classes on every render\nconst TimescaleWrapper = styled.div.attrs<TimescaleWrapperProps>((props) => ({\n style: props.$width ? { minWidth: `${props.$width}px` } : {},\n}))<TimescaleWrapperProps>`\n background: ${(props) => props.$backgroundColor || 'white'};\n width: 100%;\n position: relative;\n overflow: hidden; /* Constrain loop region to timescale area */\n`;\n\ninterface TracksContainerProps {\n readonly $width?: number;\n readonly $backgroundColor?: string;\n}\n\n// Use .attrs() for width to avoid generating new CSS classes on every render\nconst TracksContainer = styled.div.attrs<TracksContainerProps>((props) => ({\n style: props.$width !== undefined ? { minWidth: `${props.$width}px` } : {},\n}))<TracksContainerProps>`\n position: relative;\n background: ${(props) => props.$backgroundColor || 'transparent'};\n width: 100%;\n`;\n\ninterface ClickOverlayProps {\n readonly $isSelecting?: boolean;\n}\n\nconst ClickOverlay = styled.div<ClickOverlayProps>`\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n cursor: crosshair;\n /* When selecting, raise z-index above clip boundaries (z-index: 105) to prevent interference */\n z-index: ${(props) => (props.$isSelecting ? 110 : 1)};\n`;\n\nexport interface PlaylistProps {\n readonly theme: DefaultTheme;\n readonly children?: React.ReactNode;\n readonly backgroundColor?: string;\n readonly timescaleBackgroundColor?: string;\n readonly timescale?: React.ReactElement;\n readonly timescaleWidth?: number;\n readonly tracksWidth?: number;\n readonly controlsWidth?: number;\n readonly onTracksClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n readonly onTracksMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;\n readonly onTracksMouseMove?: (e: React.MouseEvent<HTMLDivElement>) => void;\n readonly onTracksMouseUp?: (e: React.MouseEvent<HTMLDivElement>) => void;\n readonly scrollContainerRef?: (el: HTMLDivElement | null) => void;\n /** When true, selection is in progress - raises z-index to prevent clip boundary interference */\n readonly isSelecting?: boolean;\n /** Data attribute indicating playlist loading state ('loading' | 'ready') */\n readonly 'data-playlist-state'?: 'loading' | 'ready';\n /** Track control slots rendered in the controls column, one per track */\n readonly trackControlsSlots?: React.ReactNode[];\n /** Height of the timescale gap spacer in the controls column (matches timescale height) */\n readonly timescaleGapHeight?: number;\n}\nexport const Playlist: FunctionComponent<PlaylistProps> = ({\n children,\n backgroundColor,\n timescaleBackgroundColor,\n timescale,\n timescaleWidth,\n tracksWidth,\n controlsWidth,\n onTracksClick,\n onTracksMouseDown,\n onTracksMouseMove,\n onTracksMouseUp,\n scrollContainerRef,\n isSelecting,\n 'data-playlist-state': playlistState,\n trackControlsSlots,\n timescaleGapHeight = 0,\n}) => {\n const scrollAreaRef = useRef<HTMLDivElement | null>(null);\n\n const handleRef = useCallback(\n (el: HTMLDivElement | null) => {\n scrollAreaRef.current = el;\n scrollContainerRef?.(el);\n },\n [scrollContainerRef]\n );\n\n const showControls = controlsWidth !== undefined && controlsWidth > 0;\n\n return (\n <Wrapper data-playlist-state={playlistState}>\n {showControls && (\n <ControlsColumn $width={controlsWidth}>\n {timescaleGapHeight > 0 && <TimescaleGap $height={timescaleGapHeight} />}\n {trackControlsSlots}\n </ControlsColumn>\n )}\n <ScrollArea data-scroll-container=\"true\" ref={handleRef}>\n <ScrollViewportProvider containerRef={scrollAreaRef}>\n <ScrollContainerInner $backgroundColor={backgroundColor} $width={tracksWidth}>\n {timescale && (\n <TimescaleWrapper $width={timescaleWidth} $backgroundColor={timescaleBackgroundColor}>\n {timescale}\n </TimescaleWrapper>\n )}\n <TracksContainer $width={tracksWidth} $backgroundColor={backgroundColor}>\n {children}\n {(onTracksClick || onTracksMouseDown) && (\n <ClickOverlay\n $isSelecting={isSelecting}\n onClick={onTracksClick}\n onMouseDown={onTracksMouseDown}\n onMouseMove={onTracksMouseMove}\n onMouseUp={onTracksMouseUp}\n />\n )}\n </TracksContainer>\n </ScrollContainerInner>\n </ScrollViewportProvider>\n </ScrollArea>\n </Wrapper>\n );\n};\n\nexport const StyledPlaylist = withTheme(Playlist);\n","import React from 'react';\nimport styled from 'styled-components';\n\ninterface SelectionOverlayProps {\n readonly $left: number;\n readonly $width: number;\n readonly $color: string;\n}\n\nconst SelectionOverlay = styled.div.attrs<SelectionOverlayProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<SelectionOverlayProps>`\n position: absolute;\n top: 0;\n background: ${(props) => props.$color};\n height: 100%;\n z-index: 60; /* Above clips (z-index: 10) and fades (z-index: 50), below playhead (z-index: 100) */\n pointer-events: none;\n opacity: 0.3;\n`;\n\nexport interface SelectionProps {\n startPosition: number; // Start position in pixels\n endPosition: number; // End position in pixels\n color?: string;\n}\n\nexport const Selection: React.FC<SelectionProps> = ({\n startPosition,\n endPosition,\n color = '#00ff00',\n}) => {\n const width = Math.max(0, endPosition - startPosition);\n\n if (width <= 0) {\n return null;\n }\n\n return <SelectionOverlay $left={startPosition} $width={width} $color={color} data-selection />;\n};\n","import React, { useCallback, useRef, useState } from 'react';\nimport styled from 'styled-components';\n\ninterface LoopRegionOverlayProps {\n readonly $left: number;\n readonly $width: number;\n readonly $color: string;\n}\n\nconst LoopRegionOverlayDiv = styled.div.attrs<LoopRegionOverlayProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<LoopRegionOverlayProps>`\n position: absolute;\n top: 0;\n background: ${(props) => props.$color};\n height: 100%;\n z-index: 55; /* Between clips (z-index: 50) and selection (z-index: 60) */\n pointer-events: none;\n`;\n\ninterface LoopMarkerProps {\n readonly $left: number;\n readonly $color: string;\n readonly $isStart: boolean;\n readonly $isDragging?: boolean;\n}\n\nconst LoopMarker = styled.div.attrs<LoopMarkerProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n },\n}))<LoopMarkerProps>`\n position: absolute;\n top: 0;\n width: 2px;\n height: 100%;\n background: ${(props) => props.$color};\n z-index: 90; /* Below playhead (z-index: 100) */\n pointer-events: none;\n\n /* Triangle marker at top */\n &::before {\n content: '';\n position: absolute;\n top: 0;\n ${(props) => (props.$isStart ? 'left: 0' : 'right: 0')};\n width: 0;\n height: 0;\n border-top: 8px solid ${(props) => props.$color};\n ${(props) =>\n props.$isStart\n ? 'border-right: 8px solid transparent;'\n : 'border-left: 8px solid transparent;'}\n }\n`;\n\nexport interface LoopRegionProps {\n startPosition: number; // Start position in pixels\n endPosition: number; // End position in pixels\n regionColor?: string;\n markerColor?: string;\n}\n\n/**\n * Loop region overlay with non-interactive markers.\n * This renders over the tracks area - markers are visual only here.\n */\nexport const LoopRegion: React.FC<LoopRegionProps> = ({\n startPosition,\n endPosition,\n regionColor = 'rgba(59, 130, 246, 0.3)',\n markerColor = '#3b82f6',\n}) => {\n const width = Math.max(0, endPosition - startPosition);\n\n if (width <= 0) {\n return null;\n }\n\n return (\n <>\n <LoopRegionOverlayDiv\n $left={startPosition}\n $width={width}\n $color={regionColor}\n data-loop-region\n />\n <LoopMarker\n $left={startPosition}\n $color={markerColor}\n $isStart={true}\n data-loop-marker=\"start\"\n />\n <LoopMarker\n $left={endPosition - 2}\n $color={markerColor}\n $isStart={false}\n data-loop-marker=\"end\"\n />\n </>\n );\n};\n\n// Draggable marker handle for timescale area\ninterface DraggableMarkerHandleProps {\n readonly $left: number;\n readonly $color: string;\n readonly $isStart: boolean;\n readonly $isDragging?: boolean;\n}\n\nconst DraggableMarkerHandle = styled.div.attrs<DraggableMarkerHandleProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n },\n}))<DraggableMarkerHandleProps>`\n position: absolute;\n top: 0;\n width: 12px;\n height: 100%;\n cursor: ew-resize;\n z-index: 100;\n /* Center the handle on the marker position */\n transform: translateX(-5px);\n\n /* Visual marker line */\n &::before {\n content: '';\n position: absolute;\n top: 0;\n left: 5px;\n width: 2px;\n height: 100%;\n background: ${(props) => props.$color};\n opacity: ${(props) => (props.$isDragging ? 1 : 0.8)};\n }\n\n /* Triangle marker at top */\n &::after {\n content: '';\n position: absolute;\n top: 0;\n ${(props) => (props.$isStart ? 'left: 5px' : 'left: -1px')};\n width: 0;\n height: 0;\n border-top: 10px solid ${(props) => props.$color};\n ${(props) =>\n props.$isStart\n ? 'border-right: 10px solid transparent;'\n : 'border-left: 10px solid transparent;'}\n }\n\n &:hover::before {\n opacity: 1;\n }\n`;\n\n// Background shading in timescale - draggable to move entire region\ninterface TimescaleLoopShadeProps {\n readonly $left: number;\n readonly $width: number;\n readonly $color: string;\n readonly $isDragging?: boolean;\n}\n\nconst TimescaleLoopShade = styled.div.attrs<TimescaleLoopShadeProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<TimescaleLoopShadeProps>`\n position: absolute;\n top: 0;\n height: 100%;\n background: ${(props) => props.$color};\n z-index: 50;\n cursor: grab;\n\n &:active {\n cursor: grabbing;\n }\n`;\n\nexport interface LoopRegionMarkersProps {\n startPosition: number; // Start position in pixels\n endPosition: number; // End position in pixels\n markerColor?: string;\n regionColor?: string;\n onLoopStartChange?: (newPositionPixels: number) => void;\n onLoopEndChange?: (newPositionPixels: number) => void;\n /** Called when the entire region is moved */\n onLoopRegionMove?: (newStartPixels: number, newEndPixels: number) => void;\n /** Minimum position in pixels (usually controls width offset) */\n minPosition?: number;\n /** Maximum position in pixels */\n maxPosition?: number;\n}\n\n/**\n * Draggable loop region markers for the timescale area.\n * These are interactive and can be dragged to adjust loop boundaries.\n * The shaded region between markers can be dragged to move the entire loop.\n */\nexport const LoopRegionMarkers: React.FC<LoopRegionMarkersProps> = ({\n startPosition,\n endPosition,\n markerColor = '#3b82f6',\n regionColor = 'rgba(59, 130, 246, 0.3)',\n onLoopStartChange,\n onLoopEndChange,\n onLoopRegionMove,\n minPosition = 0,\n maxPosition = Infinity,\n}) => {\n const [draggingMarker, setDraggingMarker] = useState<'start' | 'end' | 'region' | null>(null);\n const dragStartX = useRef<number>(0);\n const dragStartPosition = useRef<number>(0);\n const dragStartEnd = useRef<number>(0);\n\n const width = Math.max(0, endPosition - startPosition);\n\n // Handle dragging individual markers\n const handleMarkerMouseDown = useCallback(\n (e: React.MouseEvent, marker: 'start' | 'end') => {\n e.preventDefault();\n e.stopPropagation();\n setDraggingMarker(marker);\n dragStartX.current = e.clientX;\n dragStartPosition.current = marker === 'start' ? startPosition : endPosition;\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n const delta = moveEvent.clientX - dragStartX.current;\n const newPosition = dragStartPosition.current + delta;\n\n if (marker === 'start') {\n // Start marker can't go past end marker or outside bounds\n const clampedPosition = Math.max(minPosition, Math.min(endPosition - 10, newPosition));\n onLoopStartChange?.(clampedPosition);\n } else {\n // End marker can't go before start marker or outside bounds\n const clampedPosition = Math.max(startPosition + 10, Math.min(maxPosition, newPosition));\n onLoopEndChange?.(clampedPosition);\n }\n };\n\n const handleMouseUp = () => {\n setDraggingMarker(null);\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n },\n [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]\n );\n\n // Handle dragging the entire region\n const handleRegionMouseDown = useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setDraggingMarker('region');\n dragStartX.current = e.clientX;\n dragStartPosition.current = startPosition;\n dragStartEnd.current = endPosition;\n\n const regionWidth = endPosition - startPosition;\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n const delta = moveEvent.clientX - dragStartX.current;\n let newStart = dragStartPosition.current + delta;\n let newEnd = dragStartEnd.current + delta;\n\n // Clamp to bounds while maintaining region width\n if (newStart < minPosition) {\n newStart = minPosition;\n newEnd = minPosition + regionWidth;\n }\n if (newEnd > maxPosition) {\n newEnd = maxPosition;\n newStart = maxPosition - regionWidth;\n }\n\n onLoopRegionMove?.(newStart, newEnd);\n };\n\n const handleMouseUp = () => {\n setDraggingMarker(null);\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n },\n [startPosition, endPosition, minPosition, maxPosition, onLoopRegionMove]\n );\n\n if (width <= 0) {\n return null;\n }\n\n return (\n <>\n <TimescaleLoopShade\n $left={startPosition}\n $width={width}\n $color={regionColor}\n $isDragging={draggingMarker === 'region'}\n onMouseDown={handleRegionMouseDown}\n data-loop-region-timescale\n />\n <DraggableMarkerHandle\n $left={startPosition}\n $color={markerColor}\n $isStart={true}\n $isDragging={draggingMarker === 'start'}\n onMouseDown={(e) => handleMarkerMouseDown(e, 'start')}\n data-loop-marker-handle=\"start\"\n />\n <DraggableMarkerHandle\n $left={endPosition}\n $color={markerColor}\n $isStart={false}\n $isDragging={draggingMarker === 'end'}\n onMouseDown={(e) => handleMarkerMouseDown(e, 'end')}\n data-loop-marker-handle=\"end\"\n />\n </>\n );\n};\n\n// Click-to-create wrapper for timescale area\nconst TimescaleLoopCreator = styled.div`\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 100%; /* Stay within timescale bounds, don't extend into tracks */\n cursor: crosshair;\n z-index: 40; /* Below markers and shading */\n`;\n\nexport interface TimescaleLoopRegionProps {\n /** Current loop start position in pixels */\n startPosition: number;\n /** Current loop end position in pixels */\n endPosition: number;\n markerColor?: string;\n regionColor?: string;\n /** Called when loop region changes (start pixels, end pixels) */\n onLoopRegionChange?: (startPixels: number, endPixels: number) => void;\n /** Minimum position in pixels */\n minPosition?: number;\n /** Maximum position in pixels */\n maxPosition?: number;\n}\n\n/**\n * Complete timescale loop region component with:\n * - Click and drag to create a new loop region\n * - Drag markers to resize existing loop region\n * - Drag the shaded region to move the entire loop\n */\nexport const TimescaleLoopRegion: React.FC<TimescaleLoopRegionProps> = ({\n startPosition,\n endPosition,\n markerColor = '#3b82f6',\n regionColor = 'rgba(59, 130, 246, 0.3)',\n onLoopRegionChange,\n minPosition = 0,\n maxPosition = Infinity,\n}) => {\n const [, setIsCreating] = useState(false);\n const createStartX = useRef<number>(0);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const hasLoopRegion = endPosition > startPosition;\n\n // Handle creating a new loop region by clicking and dragging\n const handleBackgroundMouseDown = useCallback(\n (e: React.MouseEvent) => {\n // Only create new region if clicking on the background (not on markers or region)\n const target = e.target as HTMLElement;\n if (\n target.closest('[data-loop-marker-handle]') ||\n target.closest('[data-loop-region-timescale]')\n ) {\n return;\n }\n\n e.preventDefault();\n setIsCreating(true);\n\n const rect = containerRef.current?.getBoundingClientRect();\n if (!rect) return;\n\n const clickX = e.clientX - rect.left;\n const clampedX = Math.max(minPosition, Math.min(maxPosition, clickX));\n createStartX.current = clampedX;\n\n // Set initial position (will be a point until dragged)\n onLoopRegionChange?.(clampedX, clampedX);\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n const currentX = moveEvent.clientX - rect.left;\n const clampedCurrentX = Math.max(minPosition, Math.min(maxPosition, currentX));\n\n const newStart = Math.min(createStartX.current, clampedCurrentX);\n const newEnd = Math.max(createStartX.current, clampedCurrentX);\n\n onLoopRegionChange?.(newStart, newEnd);\n };\n\n const handleMouseUp = () => {\n setIsCreating(false);\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n },\n [minPosition, maxPosition, onLoopRegionChange]\n );\n\n return (\n <TimescaleLoopCreator\n ref={containerRef}\n onMouseDown={handleBackgroundMouseDown}\n data-timescale-loop-creator\n >\n {hasLoopRegion && (\n <LoopRegionMarkers\n startPosition={startPosition}\n endPosition={endPosition}\n markerColor={markerColor}\n regionColor={regionColor}\n minPosition={minPosition}\n maxPosition={maxPosition}\n onLoopStartChange={(newStart) => onLoopRegionChange?.(newStart, endPosition)}\n onLoopEndChange={(newEnd) => onLoopRegionChange?.(startPosition, newEnd)}\n onLoopRegionMove={(newStart, newEnd) => onLoopRegionChange?.(newStart, newEnd)}\n />\n )}\n </TimescaleLoopCreator>\n );\n};\n","import React, { useEffect, useState } from 'react';\nimport { TimeInput } from './TimeInput';\nimport { type TimeFormat } from '../utils/timeFormat';\n\nexport interface SelectionTimeInputsProps {\n selectionStart: number; // Time in seconds\n selectionEnd: number; // Time in seconds\n onSelectionChange?: (start: number, end: number) => void;\n className?: string;\n}\n\nexport const SelectionTimeInputs: React.FC<SelectionTimeInputsProps> = ({\n selectionStart,\n selectionEnd,\n onSelectionChange,\n className,\n}) => {\n const [timeFormat, setTimeFormat] = useState<TimeFormat>('hh:mm:ss.uuu');\n\n // Listen to the external time-format dropdown\n useEffect(() => {\n const timeFormatSelect = document.querySelector('.time-format') as HTMLSelectElement;\n\n const handleFormatChange = () => {\n if (timeFormatSelect) {\n setTimeFormat(timeFormatSelect.value as TimeFormat);\n }\n };\n\n // Set initial value\n if (timeFormatSelect) {\n setTimeFormat(timeFormatSelect.value as TimeFormat);\n timeFormatSelect.addEventListener('change', handleFormatChange);\n }\n\n return () => {\n timeFormatSelect?.removeEventListener('change', handleFormatChange);\n };\n }, []);\n\n const handleStartChange = (value: number) => {\n if (onSelectionChange) {\n onSelectionChange(value, selectionEnd);\n }\n };\n\n const handleEndChange = (value: number) => {\n if (onSelectionChange) {\n onSelectionChange(selectionStart, value);\n }\n };\n\n return (\n <div className={className}>\n <TimeInput\n id=\"audio_start\"\n label=\"Start of audio selection\"\n value={selectionStart}\n format={timeFormat}\n className=\"audio-start form-control mr-sm-2\"\n onChange={handleStartChange}\n />\n <TimeInput\n id=\"audio_end\"\n label=\"End of audio selection\"\n value={selectionEnd}\n format={timeFormat}\n className=\"audio-end form-control mr-sm-2\"\n onChange={handleEndChange}\n />\n </div>\n );\n};\n","import React, { useEffect, useState } from 'react';\nimport { formatTime, parseTime, type TimeFormat } from '../utils/timeFormat';\nimport { BaseInput, ScreenReaderOnly } from '../styled/index';\n\nexport interface TimeInputProps {\n id: string;\n label: string;\n value: number; // Time in seconds\n format: TimeFormat;\n className?: string;\n onChange?: (value: number) => void;\n readOnly?: boolean;\n}\n\n/**\n * TimeInput - A styled input for time values with format support\n *\n * Uses BaseInput for consistent theming. Displays time in the specified\n * format and parses user input on blur.\n */\nexport const TimeInput: React.FC<TimeInputProps> = ({\n id,\n label,\n value,\n format,\n className,\n onChange,\n readOnly = false,\n}) => {\n const [displayValue, setDisplayValue] = useState('');\n\n // Update display value when value or format changes\n useEffect(() => {\n const formatted = formatTime(value, format);\n setDisplayValue(formatted);\n }, [value, format, id]);\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const newDisplayValue = e.target.value;\n setDisplayValue(newDisplayValue);\n };\n\n const handleBlur = () => {\n // Parse the display value and notify parent\n if (onChange) {\n const parsedValue = parseTime(displayValue, format);\n onChange(parsedValue);\n }\n // Re-format to ensure consistent display\n setDisplayValue(formatTime(value, format));\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.currentTarget.blur();\n }\n };\n\n return (\n <>\n <ScreenReaderOnly as=\"label\" htmlFor={id}>\n {label}\n </ScreenReaderOnly>\n <BaseInput\n type=\"text\"\n className={className}\n id={id}\n value={displayValue}\n onChange={handleChange}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n readOnly={readOnly}\n />\n </>\n );\n};\n","/**\n * Time format utilities for displaying and parsing audio timestamps\n */\n\nexport type TimeFormat =\n | 'seconds'\n | 'thousandths'\n | 'hh:mm:ss'\n | 'hh:mm:ss.u'\n | 'hh:mm:ss.uu'\n | 'hh:mm:ss.uuu';\n\n/**\n * Format time in clock format (hh:mm:ss with optional decimals)\n */\nfunction clockFormat(seconds: number, decimals: number): string {\n const hours = Math.floor(seconds / 3600) % 24;\n const minutes = Math.floor(seconds / 60) % 60;\n const secs = (seconds % 60).toFixed(decimals);\n\n return (\n String(hours).padStart(2, '0') +\n ':' +\n String(minutes).padStart(2, '0') +\n ':' +\n secs.padStart(decimals + 3, '0')\n );\n}\n\n/**\n * Format seconds according to the specified format\n */\nexport function formatTime(seconds: number, format: TimeFormat): string {\n switch (format) {\n case 'seconds':\n return seconds.toFixed(0);\n case 'thousandths':\n return seconds.toFixed(3);\n case 'hh:mm:ss':\n return clockFormat(seconds, 0);\n case 'hh:mm:ss.u':\n return clockFormat(seconds, 1);\n case 'hh:mm:ss.uu':\n return clockFormat(seconds, 2);\n case 'hh:mm:ss.uuu':\n return clockFormat(seconds, 3);\n default:\n return clockFormat(seconds, 3);\n }\n}\n\n/**\n * Parse a formatted time string back to seconds\n */\nexport function parseTime(timeStr: string, format: TimeFormat): number {\n if (!timeStr) return 0;\n\n switch (format) {\n case 'seconds':\n case 'thousandths':\n return parseFloat(timeStr) || 0;\n\n case 'hh:mm:ss':\n case 'hh:mm:ss.u':\n case 'hh:mm:ss.uu':\n case 'hh:mm:ss.uuu': {\n // Parse hh:mm:ss format\n const parts = timeStr.split(':');\n if (parts.length !== 3) return 0;\n\n const hours = parseInt(parts[0], 10) || 0;\n const minutes = parseInt(parts[1], 10) || 0;\n const seconds = parseFloat(parts[2]) || 0;\n\n return hours * 3600 + minutes * 60 + seconds;\n }\n\n default:\n return 0;\n }\n}\n","import React, { useState, createContext, useContext, ReactNode } from 'react';\n\nfunction getScale() {\n return window.devicePixelRatio;\n}\n\nconst DevicePixelRatioContext = createContext(getScale());\n\ntype Props = {\n children: ReactNode;\n};\nexport const DevicePixelRatioProvider = ({ children }: Props) => {\n const [scale, setScale] = useState(getScale());\n\n matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(\n 'change',\n () => {\n setScale(getScale());\n },\n { once: true }\n );\n\n return (\n <DevicePixelRatioContext.Provider value={Math.ceil(scale)}>\n {children}\n </DevicePixelRatioContext.Provider>\n );\n};\n\nexport const useDevicePixelRatio = () => useContext(DevicePixelRatioContext);\n","import { createContext, useContext } from 'react';\n\ntype Controls = {\n show: boolean;\n width: number;\n};\n\ntype PlaylistInfo = {\n sampleRate: number;\n samplesPerPixel: number;\n zoomLevels: Array<number>;\n waveHeight: number;\n timeScaleHeight: number;\n duration: number;\n controls: Controls;\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 /** Width in pixels of progress bars. Default: barWidth + barGap (fills gaps). Set to barWidth for no gap fill. */\n progressBarWidth?: number;\n};\n\nexport const PlaylistInfoContext = createContext<PlaylistInfo>({\n sampleRate: 48000,\n samplesPerPixel: 1000,\n zoomLevels: [1000, 1500, 2000, 2500],\n waveHeight: 80,\n timeScaleHeight: 15,\n controls: {\n show: false,\n width: 150,\n },\n duration: 30000,\n barWidth: 1,\n barGap: 0,\n});\n\nexport const usePlaylistInfo = () => useContext(PlaylistInfoContext);\n","import { useContext } from 'react';\nimport { ThemeContext } from 'styled-components';\n\nexport const useTheme = () => useContext(ThemeContext);\n","import React, { createContext, useContext, Fragment } from 'react';\n\nexport const TrackControlsContext = createContext<React.ReactNode>(<Fragment />);\n\nexport const useTrackControls = () => useContext(TrackControlsContext);\n","import React, {\n useState,\n createContext,\n useContext,\n ReactNode,\n Dispatch,\n SetStateAction,\n} from 'react';\n\nconst defaultProgress = 0;\nconst defaultIsPlaying = false;\nconst defaultSelectionStart = 0;\nconst defaultSelectionEnd = 0;\n\nconst defaultPlayout = {\n progress: defaultProgress,\n isPlaying: defaultIsPlaying,\n selectionStart: defaultSelectionStart,\n selectionEnd: defaultSelectionEnd,\n};\n\nconst PlayoutStatusContext = createContext(defaultPlayout);\n\ntype PlayoutStatusUpdate = {\n setIsPlaying: Dispatch<SetStateAction<boolean>>;\n setProgress: Dispatch<SetStateAction<number>>;\n setSelection: (start: number, end: number) => void;\n};\nconst PlayoutStatusUpdateContext = createContext<PlayoutStatusUpdate>({\n setIsPlaying: () => {},\n setProgress: () => {},\n setSelection: () => {},\n});\n\ntype Props = {\n children: ReactNode;\n};\nexport const PlayoutProvider = ({ children }: Props) => {\n const [isPlaying, setIsPlaying] = useState(defaultIsPlaying);\n const [progress, setProgress] = useState(defaultProgress);\n const [selectionStart, setSelectionStart] = useState(defaultSelectionStart);\n const [selectionEnd, setSelectionEnd] = useState(defaultSelectionEnd);\n\n const setSelection = (start: number, end: number) => {\n setSelectionStart(start);\n setSelectionEnd(end);\n };\n\n return (\n <PlayoutStatusUpdateContext.Provider value={{ setIsPlaying, setProgress, setSelection }}>\n <PlayoutStatusContext.Provider value={{ isPlaying, progress, selectionStart, selectionEnd }}>\n {children}\n </PlayoutStatusContext.Provider>\n </PlayoutStatusUpdateContext.Provider>\n );\n};\n\nexport const usePlayoutStatus = () => useContext(PlayoutStatusContext);\nexport const usePlayoutStatusUpdate = () => useContext(PlayoutStatusUpdateContext);\n","import React, { FunctionComponent, useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport type { SpectrogramData } from '@waveform-playlist/core';\nimport { useVisibleChunkIndices } from '../contexts/ScrollViewport';\nimport { useClipViewportOrigin } from '../contexts/ClipViewportOrigin';\nimport { useChunkedCanvasRefs } from '../hooks/useChunkedCanvasRefs';\nimport { MAX_CANVAS_WIDTH } from '@waveform-playlist/core';\nconst LINEAR_FREQUENCY_SCALE = (f: number, minF: number, maxF: number) =>\n (f - minF) / (maxF - minF);\n\ninterface WrapperProps {\n readonly $index: number;\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n}\n\nconst Wrapper = styled.div.attrs<WrapperProps>((props) => ({\n style: {\n top: `${props.$waveHeight * props.$index}px`,\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n },\n}))<WrapperProps>`\n position: absolute;\n background: #000;\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\ninterface CanvasProps {\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $left: number;\n}\n\nconst SpectrogramCanvas = styled.canvas.attrs<CanvasProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n left: `${props.$left}px`,\n },\n}))<CanvasProps>`\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n image-rendering: crisp-edges;\n`;\n\n// Inline getColorMap to avoid cross-package import at component level\n// This avoids needing browser package as dependency of ui-components\nfunction defaultGetColorMap(): Uint8Array {\n // Grayscale fallback — 256-entry LUT (used when no colorLUT prop provided)\n const lut = new Uint8Array(256 * 3);\n for (let i = 0; i < 256; i++) {\n lut[i * 3] = lut[i * 3 + 1] = lut[i * 3 + 2] = i;\n }\n return lut;\n}\nconst DEFAULT_COLOR_LUT = defaultGetColorMap();\n\nexport interface SpectrogramWorkerCanvasApi {\n registerCanvas(canvasId: string, canvas: OffscreenCanvas): void;\n unregisterCanvas(canvasId: string): void;\n}\n\nexport interface SpectrogramChannelProps {\n /** Visual position index — used for CSS positioning (top offset). */\n index: number;\n /** Audio channel index for canvas ID construction. Defaults to `index` when omitted. */\n channelIndex?: number;\n /** Computed spectrogram data (not needed when workerApi is provided) */\n data?: SpectrogramData;\n /** Width in CSS pixels */\n length: number;\n /** Height in CSS pixels */\n waveHeight: number;\n /** Device pixel ratio for sharp rendering */\n devicePixelRatio?: number;\n /** Samples per pixel at current zoom level */\n samplesPerPixel: number;\n /** 256-entry RGB LUT (768 bytes) from getColorMap() */\n colorLUT?: Uint8Array;\n /** Frequency scale function: (freqHz, minF, maxF) => [0,1] */\n frequencyScaleFn?: (f: number, minF: number, maxF: number) => number;\n /** Min frequency in Hz */\n minFrequency?: number;\n /** Max frequency in Hz (defaults to sampleRate/2) */\n maxFrequency?: number;\n /** Worker API for transferring canvas ownership. When provided, rendering is done in the worker. */\n workerApi?: SpectrogramWorkerCanvasApi;\n /** Clip ID used to construct unique canvas IDs for worker registration */\n clipId?: string;\n /** Callback when canvases are registered with the worker, providing canvas IDs and widths */\n onCanvasesReady?: (canvasIds: string[], canvasWidths: number[]) => void;\n}\n\nexport const SpectrogramChannel: FunctionComponent<SpectrogramChannelProps> = ({\n index,\n channelIndex: channelIndexProp,\n data,\n length,\n waveHeight,\n devicePixelRatio = 1,\n samplesPerPixel,\n colorLUT,\n frequencyScaleFn,\n minFrequency = 0,\n maxFrequency,\n workerApi,\n clipId,\n onCanvasesReady,\n}) => {\n const channelIndex = channelIndexProp ?? index;\n const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();\n const registeredIdsRef = useRef<string[]>([]);\n const transferredCanvasesRef = useRef<WeakSet<HTMLCanvasElement>>(new WeakSet());\n const workerApiRef = useRef(workerApi);\n const onCanvasesReadyRef = useRef(onCanvasesReady);\n\n // Track whether we're in worker mode (canvas transferred)\n const isWorkerMode = !!(workerApi && clipId);\n const clipOriginX = useClipViewportOrigin();\n\n const visibleChunkIndices = useVisibleChunkIndices(length, MAX_CANVAS_WIDTH, clipOriginX);\n\n const lut = colorLUT ?? DEFAULT_COLOR_LUT;\n const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);\n const scaleFn = frequencyScaleFn ?? LINEAR_FREQUENCY_SCALE;\n const hasCustomFrequencyScale = Boolean(frequencyScaleFn);\n\n // Keep refs in sync with latest props\n useEffect(() => {\n workerApiRef.current = workerApi;\n }, [workerApi]);\n\n useEffect(() => {\n onCanvasesReadyRef.current = onCanvasesReady;\n }, [onCanvasesReady]);\n\n // Worker mode: clean up stale canvases, then transfer new ones.\n // Cleanup and registration are combined in a single effect so that\n // `onCanvasesReady` always receives a clean list without stale IDs.\n // Uses visibleChunkIndices so it only re-runs when chunks mount/unmount.\n useEffect(() => {\n if (!isWorkerMode) return;\n const currentWorkerApi = workerApiRef.current;\n if (!currentWorkerApi || !clipId) return;\n\n // Step 1: Remove stale registrations for unmounted canvases.\n const previousCount = registeredIdsRef.current.length;\n const remaining: string[] = [];\n for (const id of registeredIdsRef.current) {\n const match = id.match(/chunk(\\d+)$/);\n if (!match) {\n remaining.push(id);\n continue;\n }\n const chunkIdx = parseInt(match[1], 10);\n const canvas = canvasMapRef.current.get(chunkIdx);\n if (canvas && canvas.isConnected) {\n remaining.push(id);\n } else {\n try {\n currentWorkerApi.unregisterCanvas(id);\n } catch (err) {\n console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);\n }\n }\n }\n registeredIdsRef.current = remaining;\n\n // Step 2: Transfer new canvases to the worker.\n const newIds: string[] = [];\n\n for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {\n if (transferredCanvasesRef.current.has(canvas)) continue;\n\n const canvasId = `${clipId}-ch${channelIndex}-chunk${canvasIdx}`;\n\n let offscreen: OffscreenCanvas;\n try {\n offscreen = canvas.transferControlToOffscreen();\n } catch (err) {\n console.warn(`[spectrogram] transferControlToOffscreen failed for ${canvasId}:`, err);\n continue;\n }\n\n // Mark transferred immediately — transferControlToOffscreen is irreversible.\n transferredCanvasesRef.current.add(canvas);\n\n try {\n currentWorkerApi.registerCanvas(canvasId, offscreen);\n newIds.push(canvasId);\n } catch (err) {\n console.warn(`[spectrogram] registerCanvas failed for ${canvasId}:`, err);\n continue;\n }\n }\n\n if (newIds.length > 0) {\n registeredIdsRef.current = [...registeredIdsRef.current, ...newIds];\n }\n\n // Step 3: Notify provider when canvas set changed (added or removed).\n const canvasSetChanged = newIds.length > 0 || remaining.length < previousCount;\n if (canvasSetChanged) {\n const allIds = registeredIdsRef.current;\n const allWidths = allIds.map((id) => {\n const match = id.match(/chunk(\\d+)$/);\n if (!match) {\n console.warn(`[spectrogram] Unexpected canvas ID format: ${id}`);\n return MAX_CANVAS_WIDTH;\n }\n const chunkIdx = parseInt(match[1], 10);\n return Math.min(length - chunkIdx * MAX_CANVAS_WIDTH, MAX_CANVAS_WIDTH);\n });\n onCanvasesReadyRef.current?.(allIds, allWidths);\n }\n }, [canvasMapRef, isWorkerMode, clipId, channelIndex, length, visibleChunkIndices]);\n\n // Unregister all canvases from worker on component unmount\n useEffect(() => {\n return () => {\n const api = workerApiRef.current;\n if (!api) return;\n for (const id of registeredIdsRef.current) {\n try {\n api.unregisterCanvas(id);\n } catch (err) {\n console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);\n }\n }\n registeredIdsRef.current = [];\n };\n }, []);\n\n // Main-thread rendering (skipped in worker mode).\n // useEffect (not useLayoutEffect) so the browser paints the track layout\n // (controls + empty canvas containers) before heavy canvas drawing starts.\n useEffect(() => {\n if (isWorkerMode || !data) return;\n\n const {\n frequencyBinCount,\n frameCount,\n hopSize,\n sampleRate,\n gainDb,\n rangeDb: rawRangeDb,\n } = data;\n const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;\n\n // Pre-compute Y mapping: for each pixel row, which frequency bin(s) to sample\n const binToFreq = (bin: number) => (bin / frequencyBinCount) * (sampleRate / 2);\n\n for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {\n const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n\n const canvasWidth = canvas.width / devicePixelRatio;\n const canvasHeight = waveHeight;\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n // Create ImageData at CSS pixel size, then putImageData at scaled resolution\n const imgData = ctx.createImageData(canvasWidth, canvasHeight);\n const pixels = imgData.data;\n\n for (let x = 0; x < canvasWidth; x++) {\n const globalX = globalPixelOffset + x;\n\n // Map pixel X → spectrogram frame\n const samplePos = globalX * samplesPerPixel;\n const frame = Math.floor(samplePos / hopSize);\n\n if (frame < 0 || frame >= frameCount) continue;\n\n const frameOffset = frame * frequencyBinCount;\n\n for (let y = 0; y < canvasHeight; y++) {\n // Y=0 is top of canvas, but low frequencies should be at bottom\n const normalizedY = 1 - y / canvasHeight; // 0=bottom, 1=top\n\n // Map normalizedY through frequency scale to find which frequency this pixel represents\n // We need the inverse: given a normalized position, find the frequency\n // Use binary search over frequency bins\n let bin = Math.floor(normalizedY * frequencyBinCount);\n\n // If we have a non-linear scale, find the correct bin\n if (hasCustomFrequencyScale) {\n // Binary search: find the bin whose scaled position is closest to normalizedY\n let lo = 0;\n let hi = frequencyBinCount - 1;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n const freq = binToFreq(mid);\n const scaled = scaleFn(freq, minFrequency, maxF);\n if (scaled < normalizedY) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n bin = lo;\n }\n\n if (bin < 0 || bin >= frequencyBinCount) continue;\n\n // Get dB value and normalize to [0, 1]\n const db = data.data[frameOffset + bin];\n const normalized = Math.max(0, Math.min(1, (db + rangeDb + gainDb) / rangeDb));\n\n // Map to color via LUT (0-255 index)\n const colorIdx = Math.floor(normalized * 255);\n const pixelIdx = (y * canvasWidth + x) * 4;\n pixels[pixelIdx] = lut[colorIdx * 3];\n pixels[pixelIdx + 1] = lut[colorIdx * 3 + 1];\n pixels[pixelIdx + 2] = lut[colorIdx * 3 + 2];\n pixels[pixelIdx + 3] = 255;\n }\n }\n\n // Put at device pixel ratio scale\n ctx.resetTransform();\n ctx.putImageData(imgData, 0, 0);\n\n // Scale up to fill canvas\n if (devicePixelRatio !== 1) {\n // Draw the image data at 1:1, then scale\n const tmpCanvas = document.createElement('canvas');\n tmpCanvas.width = canvasWidth;\n tmpCanvas.height = canvasHeight;\n const tmpCtx = tmpCanvas.getContext('2d');\n if (!tmpCtx) continue;\n tmpCtx.putImageData(imgData, 0, 0);\n\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.drawImage(tmpCanvas, 0, 0, canvas.width, canvas.height);\n }\n }\n }, [\n canvasMapRef,\n isWorkerMode,\n data,\n length,\n waveHeight,\n devicePixelRatio,\n samplesPerPixel,\n lut,\n minFrequency,\n maxF,\n scaleFn,\n hasCustomFrequencyScale,\n visibleChunkIndices,\n ]);\n\n // Build visible canvas chunk elements\n const canvases = visibleChunkIndices.map((i) => {\n const chunkLeft = i * MAX_CANVAS_WIDTH;\n const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);\n\n return (\n <SpectrogramCanvas\n key={`${length}-${i}`}\n $cssWidth={currentWidth}\n $left={chunkLeft}\n width={currentWidth * devicePixelRatio}\n height={waveHeight * devicePixelRatio}\n $waveHeight={waveHeight}\n data-index={i}\n ref={canvasRef}\n />\n );\n });\n\n return (\n <Wrapper $index={index} $cssWidth={length} $waveHeight={waveHeight}>\n {canvases}\n </Wrapper>\n );\n};\n","import React, { FunctionComponent } from 'react';\nimport { useDevicePixelRatio, usePlaylistInfo, useTheme } from '../contexts';\nimport { Channel } from './Channel';\nimport { PianoRollChannel } from './PianoRollChannel';\nimport { SpectrogramChannel, type SpectrogramWorkerCanvasApi } from './SpectrogramChannel';\nimport type { SpectrogramData, RenderMode, MidiNoteData } from '@waveform-playlist/core';\n\nexport interface SmartChannelProps {\n className?: string;\n index: number;\n data: Int8Array | Int16Array;\n bits: 8 | 16;\n length: number;\n isSelected?: boolean; // Whether this channel's track is selected\n /** If true, background is transparent (for use with external progress overlay) */\n transparentBackground?: boolean;\n /** Render mode: waveform, spectrogram, or both */\n renderMode?: RenderMode;\n /** Spectrogram data for this channel */\n spectrogramData?: SpectrogramData;\n /** 256-entry RGB LUT from getColorMap() */\n spectrogramColorLUT?: Uint8Array;\n /** Samples per pixel at current zoom level */\n samplesPerPixel?: number;\n /** Frequency scale function */\n spectrogramFrequencyScaleFn?: (f: number, minF: number, maxF: number) => number;\n /** Min frequency in Hz */\n spectrogramMinFrequency?: number;\n /** Max frequency in Hz */\n spectrogramMaxFrequency?: number;\n /** Worker API for OffscreenCanvas transfer */\n spectrogramWorkerApi?: SpectrogramWorkerCanvasApi;\n /** Clip ID for worker canvas registration */\n spectrogramClipId?: string;\n /** Callback when canvases are registered with the worker */\n spectrogramOnCanvasesReady?: (canvasIds: string[], canvasWidths: number[]) => void;\n /** MIDI note data for piano-roll rendering */\n midiNotes?: MidiNoteData[];\n /** Sample rate for MIDI note time → pixel conversion */\n sampleRate?: number;\n /** Clip offset in seconds for MIDI note positioning */\n clipOffsetSeconds?: number;\n}\n\nexport const SmartChannel: FunctionComponent<SmartChannelProps> = ({\n isSelected,\n transparentBackground,\n renderMode = 'waveform',\n spectrogramData,\n spectrogramColorLUT,\n samplesPerPixel: sppProp,\n spectrogramFrequencyScaleFn,\n spectrogramMinFrequency,\n spectrogramMaxFrequency,\n spectrogramWorkerApi,\n spectrogramClipId,\n spectrogramOnCanvasesReady,\n midiNotes,\n sampleRate: sampleRateProp,\n clipOffsetSeconds,\n ...props\n}) => {\n const theme = useTheme();\n const {\n waveHeight,\n barWidth,\n barGap,\n samplesPerPixel: contextSpp,\n sampleRate: contextSampleRate,\n } = usePlaylistInfo();\n const devicePixelRatio = useDevicePixelRatio();\n const samplesPerPixel = sppProp ?? contextSpp;\n\n // Use selected colors if track is selected\n const waveOutlineColor =\n isSelected && theme ? theme.selectedWaveOutlineColor : theme?.waveOutlineColor;\n\n const waveFillColor = isSelected && theme ? theme.selectedWaveFillColor : theme?.waveFillColor;\n\n // Get draw mode from theme (defaults to 'inverted' for backwards compatibility)\n const drawMode = theme?.waveformDrawMode || 'inverted';\n\n // Worker mode: spectrogram data is optional (worker renders directly)\n const hasSpectrogram = spectrogramData || spectrogramWorkerApi;\n\n if (renderMode === 'spectrogram' && hasSpectrogram) {\n return (\n <SpectrogramChannel\n index={props.index}\n data={spectrogramData}\n length={props.length}\n waveHeight={waveHeight}\n devicePixelRatio={devicePixelRatio}\n samplesPerPixel={samplesPerPixel}\n colorLUT={spectrogramColorLUT}\n frequencyScaleFn={spectrogramFrequencyScaleFn}\n minFrequency={spectrogramMinFrequency}\n maxFrequency={spectrogramMaxFrequency}\n workerApi={spectrogramWorkerApi}\n clipId={spectrogramClipId}\n onCanvasesReady={spectrogramOnCanvasesReady}\n />\n );\n }\n\n if (renderMode === 'both' && hasSpectrogram) {\n // Spectrogram above, waveform below — each at half waveHeight so the\n // overall track container stays the same height as a single-mode track.\n const halfHeight = Math.floor(waveHeight / 2);\n return (\n <>\n <SpectrogramChannel\n index={props.index * 2}\n channelIndex={props.index}\n data={spectrogramData}\n length={props.length}\n waveHeight={halfHeight}\n devicePixelRatio={devicePixelRatio}\n samplesPerPixel={samplesPerPixel}\n colorLUT={spectrogramColorLUT}\n frequencyScaleFn={spectrogramFrequencyScaleFn}\n minFrequency={spectrogramMinFrequency}\n maxFrequency={spectrogramMaxFrequency}\n workerApi={spectrogramWorkerApi}\n clipId={spectrogramClipId}\n onCanvasesReady={spectrogramOnCanvasesReady}\n />\n <div\n style={{\n position: 'absolute',\n top: (props.index * 2 + 1) * halfHeight,\n width: props.length,\n height: halfHeight,\n }}\n >\n <Channel\n {...props}\n index={0}\n waveOutlineColor={waveOutlineColor}\n waveFillColor={waveFillColor}\n waveHeight={halfHeight}\n devicePixelRatio={devicePixelRatio}\n barWidth={barWidth}\n barGap={barGap}\n transparentBackground={transparentBackground}\n drawMode={drawMode}\n />\n </div>\n </>\n );\n }\n\n if (renderMode === 'piano-roll') {\n return (\n <PianoRollChannel\n index={props.index}\n midiNotes={midiNotes ?? []}\n length={props.length}\n waveHeight={waveHeight}\n devicePixelRatio={devicePixelRatio}\n samplesPerPixel={samplesPerPixel}\n sampleRate={sampleRateProp ?? contextSampleRate}\n clipOffsetSeconds={clipOffsetSeconds ?? 0}\n noteColor={theme?.pianoRollNoteColor}\n selectedNoteColor={theme?.pianoRollSelectedNoteColor}\n isSelected={isSelected}\n transparentBackground={transparentBackground}\n backgroundColor={theme?.pianoRollBackgroundColor}\n />\n );\n }\n\n // Default: waveform mode\n return (\n <Channel\n {...props}\n waveOutlineColor={waveOutlineColor}\n waveFillColor={waveFillColor}\n waveHeight={waveHeight}\n devicePixelRatio={devicePixelRatio}\n barWidth={barWidth}\n barGap={barGap}\n transparentBackground={transparentBackground}\n drawMode={drawMode}\n />\n );\n};\n","import React, { useRef, useLayoutEffect } from 'react';\nimport styled from 'styled-components';\nimport { useDevicePixelRatio } from '../contexts';\n\nconst LABELS_WIDTH = 72;\n\ninterface LabelsStickyWrapperProps {\n readonly $height: number;\n}\n\nconst LabelsStickyWrapper = styled.div<LabelsStickyWrapperProps>`\n position: sticky;\n left: 0;\n z-index: 101;\n pointer-events: none;\n height: 0;\n width: 0;\n overflow: visible;\n`;\n\nexport interface SpectrogramLabelsProps {\n /** Height per channel in CSS pixels */\n waveHeight: number;\n /** Number of audio channels */\n numChannels: number;\n /** Frequency scale function */\n frequencyScaleFn: (f: number, minF: number, maxF: number) => number;\n /** Min frequency in Hz */\n minFrequency: number;\n /** Max frequency in Hz */\n maxFrequency: number;\n /** Label text color */\n labelsColor?: string;\n /** Label background color */\n labelsBackground?: string;\n /** Render mode — in \"both\" mode spectrogram is half height */\n renderMode?: 'spectrogram' | 'both';\n /** Whether clip headers are shown (adds offset) */\n hasClipHeaders?: boolean;\n}\n\n/** Generate nice frequency labels for the axis, limited by available height */\nfunction getFrequencyLabels(minF: number, maxF: number, height: number): number[] {\n const allCandidates = [\n 20, 50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 8000, 10000, 12000, 16000, 20000,\n ];\n const inRange = allCandidates.filter((f) => f >= minF && f <= maxF);\n\n // Each label needs ~20px of vertical space to avoid overlap\n const maxLabels = Math.max(2, Math.floor(height / 20));\n if (inRange.length <= maxLabels) return inRange;\n\n // Evenly sample from the available candidates\n const step = (inRange.length - 1) / (maxLabels - 1);\n const result: number[] = [];\n for (let i = 0; i < maxLabels; i++) {\n result.push(inRange[Math.round(i * step)]);\n }\n return result;\n}\n\nexport const SpectrogramLabels: React.FC<SpectrogramLabelsProps> = ({\n waveHeight,\n numChannels,\n frequencyScaleFn,\n minFrequency,\n maxFrequency,\n labelsColor = '#ccc',\n labelsBackground = 'rgba(0,0,0,0.6)',\n renderMode = 'spectrogram',\n hasClipHeaders = false,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const devicePixelRatio = useDevicePixelRatio();\n\n const spectrogramHeight = renderMode === 'both' ? Math.floor(waveHeight / 2) : waveHeight;\n\n const totalHeight = numChannels * waveHeight;\n const clipHeaderOffset = hasClipHeaders ? 22 : 0;\n\n useLayoutEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n const labelFreqs = getFrequencyLabels(minFrequency, maxFrequency, spectrogramHeight);\n\n for (let ch = 0; ch < numChannels; ch++) {\n const channelTop = ch * waveHeight + clipHeaderOffset;\n\n ctx.font = '11px monospace';\n ctx.textBaseline = 'middle';\n\n for (const freq of labelFreqs) {\n const normalized = frequencyScaleFn(freq, minFrequency, maxFrequency);\n if (normalized < 0 || normalized > 1) continue;\n const y = channelTop + spectrogramHeight * (1 - normalized);\n\n const text = freq >= 1000 ? `${(freq / 1000).toFixed(1)}k` : `${freq} Hz`;\n const metrics = ctx.measureText(text);\n const padding = 3;\n\n ctx.fillStyle = labelsBackground;\n ctx.fillRect(0, y - 7, metrics.width + padding * 2, 14);\n ctx.fillStyle = labelsColor;\n ctx.fillText(text, padding, y);\n }\n }\n }, [\n waveHeight,\n numChannels,\n frequencyScaleFn,\n minFrequency,\n maxFrequency,\n labelsColor,\n labelsBackground,\n devicePixelRatio,\n spectrogramHeight,\n clipHeaderOffset,\n ]);\n\n return (\n <LabelsStickyWrapper $height={totalHeight + clipHeaderOffset}>\n <canvas\n ref={canvasRef}\n width={LABELS_WIDTH * devicePixelRatio}\n height={(totalHeight + clipHeaderOffset) * devicePixelRatio}\n style={{\n width: LABELS_WIDTH,\n height: totalHeight + clipHeaderOffset,\n pointerEvents: 'none',\n }}\n />\n </LabelsStickyWrapper>\n );\n};\n","import React, { FunctionComponent, useContext, type ReactNode } from 'react';\nimport { PlaylistInfoContext } from '../contexts/PlaylistInfo';\nimport { StyledTimeScale } from './TimeScale';\n\nexport interface SmartScaleProps {\n readonly renderTimestamp?: (timeMs: number, pixelPosition: number) => ReactNode;\n}\n\nconst timeinfo = new Map([\n [\n 700,\n {\n marker: 1000,\n bigStep: 500,\n smallStep: 100,\n },\n ],\n [\n 1500,\n {\n marker: 2000,\n bigStep: 1000,\n smallStep: 200,\n },\n ],\n [\n 2500,\n {\n marker: 2000,\n bigStep: 1000,\n smallStep: 500,\n },\n ],\n [\n 5000,\n {\n marker: 5000,\n bigStep: 1000,\n smallStep: 500,\n },\n ],\n [\n 10000,\n {\n marker: 10000,\n bigStep: 5000,\n smallStep: 1000,\n },\n ],\n [\n 12000,\n {\n marker: 15000,\n bigStep: 5000,\n smallStep: 1000,\n },\n ],\n [\n Infinity,\n {\n marker: 30000,\n bigStep: 10000,\n smallStep: 5000,\n },\n ],\n]);\n\nfunction getScaleInfo(samplesPerPixel: number) {\n const keys = timeinfo.keys();\n let config;\n\n for (const resolution of keys) {\n if (samplesPerPixel < resolution) {\n config = timeinfo.get(resolution);\n break;\n }\n }\n\n if (config === undefined) {\n config = { marker: 30000, bigStep: 10000, smallStep: 5000 };\n }\n return config;\n}\n\nexport const SmartScale: FunctionComponent<SmartScaleProps> = ({ renderTimestamp }) => {\n const { samplesPerPixel, duration } = useContext(PlaylistInfoContext);\n let config = getScaleInfo(samplesPerPixel);\n\n return (\n <StyledTimeScale\n marker={config.marker}\n bigStep={config.bigStep}\n secondStep={config.smallStep}\n duration={duration}\n renderTimestamp={renderTimestamp}\n />\n );\n};\n","import React, { FunctionComponent, useLayoutEffect, useContext, useMemo } from 'react';\nimport styled, { withTheme, DefaultTheme } from 'styled-components';\nimport { PlaylistInfoContext } from '../contexts/PlaylistInfo';\nimport { useDevicePixelRatio } from '../contexts/DevicePixelRatio';\nimport { useVisibleChunkIndices } from '../contexts/ScrollViewport';\nimport { useChunkedCanvasRefs } from '../hooks/useChunkedCanvasRefs';\nimport { secondsToPixels } from '../utils/conversions';\nimport { MAX_CANVAS_WIDTH } from '@waveform-playlist/core';\n\nfunction formatTime(milliseconds: number) {\n const seconds = Math.floor(milliseconds / 1000);\n const s = seconds % 60;\n const m = (seconds - s) / 60;\n\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\ninterface PlaylistTimeScaleScrollProps {\n readonly $cssWidth: number;\n readonly $timeScaleHeight: number;\n}\nconst PlaylistTimeScaleScroll = styled.div.attrs<PlaylistTimeScaleScrollProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$timeScaleHeight}px`,\n },\n}))<PlaylistTimeScaleScrollProps>`\n position: relative;\n overflow: visible; /* Allow time labels to render above the container */\n border-bottom: 1px solid ${(props) => props.theme.timeColor};\n box-sizing: border-box;\n`;\n\ninterface TimeTickChunkProps {\n readonly $cssWidth: number;\n readonly $timeScaleHeight: number;\n readonly $left: number;\n}\nconst TimeTickChunk = styled.canvas.attrs<TimeTickChunkProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$timeScaleHeight}px`,\n left: `${props.$left}px`,\n },\n}))<TimeTickChunkProps>`\n position: absolute;\n bottom: 0;\n`;\n\ninterface TimeStampProps {\n readonly $left: number;\n}\nconst TimeStamp = styled.div.attrs<TimeStampProps>((props) => ({\n style: {\n left: `${props.$left + 4}px`, // Offset 4px to the right of the tick\n },\n}))<TimeStampProps>`\n position: absolute;\n font-size: 0.75rem; /* Smaller font to prevent overflow */\n white-space: nowrap; /* Prevent text wrapping */\n color: ${(props) => props.theme.timeColor}; /* Use theme color instead of inheriting */\n`;\n\nexport interface TimeScaleProps {\n readonly theme?: DefaultTheme;\n readonly duration: number;\n readonly marker: number;\n readonly bigStep: number;\n readonly secondStep: number;\n readonly renderTimestamp?: (timeMs: number, pixelPosition: number) => React.ReactNode;\n}\n\ninterface TimeScalePropsWithTheme extends TimeScaleProps {\n readonly theme: DefaultTheme;\n}\n\nexport const TimeScale: FunctionComponent<TimeScalePropsWithTheme> = (props) => {\n const {\n theme: { timeColor },\n duration,\n marker,\n bigStep,\n secondStep,\n renderTimestamp,\n } = props;\n const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();\n const { sampleRate, samplesPerPixel, timeScaleHeight } = useContext(PlaylistInfoContext);\n const devicePixelRatio = useDevicePixelRatio();\n\n const { widthX, canvasInfo, timeMarkersWithPositions } = useMemo(() => {\n const nextCanvasInfo = new Map<number, number>();\n const nextMarkers: Array<{ pix: number; element: React.ReactNode }> = [];\n const nextWidthX = secondsToPixels(duration / 1000, samplesPerPixel, sampleRate);\n const pixPerSec = sampleRate / samplesPerPixel;\n let counter = 0;\n\n for (let i = 0; i < nextWidthX; i += (pixPerSec * secondStep) / 1000) {\n const pix = Math.floor(i);\n\n if (counter % marker === 0) {\n const timeMs = counter;\n const timestamp = formatTime(timeMs);\n\n const element = renderTimestamp ? (\n <React.Fragment key={`timestamp-${counter}`}>\n {renderTimestamp(timeMs, pix)}\n </React.Fragment>\n ) : (\n <TimeStamp key={timestamp} $left={pix}>\n {timestamp}\n </TimeStamp>\n );\n\n nextMarkers.push({ pix, element });\n nextCanvasInfo.set(pix, timeScaleHeight);\n } else if (counter % bigStep === 0) {\n nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 2));\n } else if (counter % secondStep === 0) {\n nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 5));\n }\n\n counter += secondStep;\n }\n\n return {\n widthX: nextWidthX,\n canvasInfo: nextCanvasInfo,\n timeMarkersWithPositions: nextMarkers,\n };\n }, [\n duration,\n samplesPerPixel,\n sampleRate,\n marker,\n bigStep,\n secondStep,\n renderTimestamp,\n timeScaleHeight,\n ]);\n\n const visibleChunkIndices = useVisibleChunkIndices(widthX, MAX_CANVAS_WIDTH);\n\n // Build visible canvas chunk elements\n const visibleChunks = visibleChunkIndices.map((i) => {\n const chunkLeft = i * MAX_CANVAS_WIDTH;\n const chunkWidth = Math.min(widthX - chunkLeft, MAX_CANVAS_WIDTH);\n\n return (\n <TimeTickChunk\n key={`timescale-${i}`}\n $cssWidth={chunkWidth}\n $left={chunkLeft}\n $timeScaleHeight={timeScaleHeight}\n width={chunkWidth * devicePixelRatio}\n height={timeScaleHeight * devicePixelRatio}\n data-index={i}\n ref={canvasRef}\n />\n );\n });\n\n // Filter time markers to visible chunk range. Uses chunk boundaries\n // rather than exact viewport pixels — sufficient given the 1.5× overscan buffer.\n const firstChunkLeft =\n visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * MAX_CANVAS_WIDTH : 0;\n const lastChunkRight =\n visibleChunkIndices.length > 0\n ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * MAX_CANVAS_WIDTH\n : Infinity;\n\n const visibleMarkers =\n visibleChunkIndices.length > 0\n ? timeMarkersWithPositions\n .filter(({ pix }) => pix >= firstChunkLeft && pix < lastChunkRight)\n .map(({ element }) => element)\n : timeMarkersWithPositions.map(({ element }) => element);\n\n // Draw tick marks on visible canvas chunks.\n // visibleChunkIndices changes only when chunks mount/unmount, not on every scroll pixel.\n useLayoutEffect(() => {\n for (const [chunkIdx, canvas] of canvasMapRef.current.entries()) {\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n\n const chunkLeft = chunkIdx * MAX_CANVAS_WIDTH;\n const chunkWidth = canvas.width / devicePixelRatio;\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.fillStyle = timeColor;\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n for (const [pixLeft, scaleHeight] of canvasInfo.entries()) {\n // Only draw ticks within this chunk's range\n if (pixLeft < chunkLeft || pixLeft >= chunkLeft + chunkWidth) continue;\n\n const localX = pixLeft - chunkLeft;\n const scaleY = timeScaleHeight - scaleHeight;\n ctx.fillRect(localX, scaleY, 1, scaleHeight);\n }\n }\n }, [\n canvasMapRef,\n duration,\n devicePixelRatio,\n timeColor,\n timeScaleHeight,\n canvasInfo,\n visibleChunkIndices,\n ]);\n\n return (\n <PlaylistTimeScaleScroll $cssWidth={widthX} $timeScaleHeight={timeScaleHeight}>\n {visibleMarkers}\n {visibleChunks}\n </PlaylistTimeScaleScroll>\n );\n};\n\nexport const StyledTimeScale = withTheme(TimeScale) as FunctionComponent<TimeScaleProps>;\n","export function samplesToSeconds(samples: number, sampleRate: number) {\n return samples / sampleRate;\n}\n\nexport function secondsToSamples(seconds: number, sampleRate: number) {\n return Math.ceil(seconds * sampleRate);\n}\n\nexport function samplesToPixels(samples: number, samplesPerPixel: number) {\n return Math.floor(samples / samplesPerPixel);\n}\n\nexport function pixelsToSamples(pixels: number, samplesPerPixel: number) {\n return Math.floor(pixels * samplesPerPixel);\n}\n\nexport function pixelsToSeconds(pixels: number, samplesPerPixel: number, sampleRate: number) {\n return (pixels * samplesPerPixel) / sampleRate;\n}\n\nexport function secondsToPixels(seconds: number, samplesPerPixel: number, sampleRate: number) {\n return Math.ceil((seconds * sampleRate) / samplesPerPixel);\n}\n","import React from 'react';\nimport styled from 'styled-components';\nimport { type TimeFormat } from '../utils/timeFormat';\nimport { BaseSelect } from '../styled/index';\n\nconst SelectWrapper = styled.div`\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n`;\n\nexport interface TimeFormatSelectProps {\n value: TimeFormat;\n onChange: (format: TimeFormat) => void;\n disabled?: boolean;\n className?: string;\n}\n\nconst TIME_FORMAT_OPTIONS: { value: TimeFormat; label: string }[] = [\n { value: 'seconds', label: 'seconds' },\n { value: 'thousandths', label: 'thousandths' },\n { value: 'hh:mm:ss', label: 'hh:mm:ss' },\n { value: 'hh:mm:ss.u', label: 'hh:mm:ss + tenths' },\n { value: 'hh:mm:ss.uu', label: 'hh:mm:ss + hundredths' },\n { value: 'hh:mm:ss.uuu', label: 'hh:mm:ss + milliseconds' },\n];\n\n/**\n * Dropdown select for choosing time display format\n */\nexport const TimeFormatSelect: React.FC<TimeFormatSelectProps> = ({\n value,\n onChange,\n disabled = false,\n className,\n}) => {\n const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {\n onChange(e.target.value as TimeFormat);\n };\n\n return (\n <SelectWrapper className={className}>\n <BaseSelect\n className=\"time-format\"\n value={value}\n onChange={handleChange}\n disabled={disabled}\n aria-label=\"Time format selection\"\n >\n {TIME_FORMAT_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </BaseSelect>\n </SelectWrapper>\n );\n};\n","import React, { FunctionComponent, ReactNode } from 'react';\nimport styled from 'styled-components';\nimport { usePlaylistInfo } from '../contexts/PlaylistInfo';\nimport { CLIP_HEADER_HEIGHT } from './ClipHeader';\n\ninterface ContainerProps {\n readonly $numChannels: number;\n readonly $waveHeight: number;\n readonly $width?: number;\n}\n\ninterface ContainerWithHeaderProps extends ContainerProps {\n readonly $hasClipHeaders: boolean;\n}\n\nconst Container = styled.div.attrs<ContainerWithHeaderProps>((props) => ({\n style: {\n height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`,\n },\n}))<ContainerWithHeaderProps>`\n position: relative;\n ${(props) => props.$width !== undefined && `width: ${props.$width}px;`}\n`;\n\ninterface ChannelContainerProps {\n readonly $backgroundColor?: string;\n readonly $offset?: number;\n}\nconst ChannelContainer = styled.div.attrs<ChannelContainerProps>((props) => ({\n style: {\n paddingLeft: `${props.$offset || 0}px`,\n },\n}))<ChannelContainerProps>`\n position: relative;\n background: ${(props) => props.$backgroundColor || 'transparent'};\n height: 100%;\n`;\n\nexport interface TrackProps {\n className?: string;\n children?: ReactNode;\n numChannels: number;\n backgroundColor?: string;\n offset?: number; // Offset in pixels to shift the waveform right\n width?: number; // Total width of the track (for consistent backgrounds across tracks)\n hasClipHeaders?: boolean; // Whether clips have headers (for multi-clip editing)\n onClick?: () => void; // Called when track is clicked (for track selection)\n trackId?: string; // Track ID for identifying which track was clicked\n isSelected?: boolean; // Whether this track is currently selected (for visual feedback)\n}\n\nexport const Track: FunctionComponent<TrackProps> = ({\n numChannels,\n children,\n className,\n backgroundColor,\n offset = 0,\n width,\n hasClipHeaders = false,\n onClick,\n trackId,\n isSelected: _isSelected = false,\n}) => {\n const { waveHeight } = usePlaylistInfo();\n return (\n <Container\n $numChannels={numChannels}\n className={className}\n $waveHeight={waveHeight}\n $width={width}\n $hasClipHeaders={hasClipHeaders}\n >\n <ChannelContainer\n $backgroundColor={backgroundColor}\n $offset={offset}\n onClick={onClick}\n data-track-id={trackId}\n >\n {children}\n </ChannelContainer>\n </Container>\n );\n};\n","import styled from 'styled-components';\n\n/**\n * TrackControls Button - Small button for track controls (Mute, Solo, etc.)\n *\n * Supports variants: outline (default), danger, info\n * Uses theme values for consistent styling.\n */\nexport const Button = styled.button.attrs({\n type: 'button',\n})<{ $variant?: 'outline' | 'danger' | 'info' }>`\n display: inline-block;\n font-family: ${(props) => props.theme.fontFamily};\n font-weight: 500;\n text-align: center;\n vertical-align: middle;\n user-select: none;\n padding: 0.25rem 0.4rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n line-height: 1;\n border-radius: ${(props) => props.theme.borderRadius};\n transition:\n color 0.15s ease-in-out,\n background-color 0.15s ease-in-out,\n border-color 0.15s ease-in-out,\n box-shadow 0.15s ease-in-out;\n cursor: pointer;\n\n ${(props) => {\n if (props.$variant === 'danger') {\n return `\n color: #fff;\n background-color: #dc3545;\n border: 1px solid #dc3545;\n\n &:hover {\n background-color: #c82333;\n border-color: #bd2130;\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n }\n `;\n } else if (props.$variant === 'info') {\n return `\n color: #fff;\n background-color: #17a2b8;\n border: 1px solid #17a2b8;\n\n &:hover {\n background-color: #138496;\n border-color: #117a8b;\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n }\n `;\n } else {\n // outline variant (default) - uses theme colors\n return `\n color: ${props.theme.textColor};\n background-color: transparent;\n border: 1px solid ${props.theme.borderColor};\n\n &:hover {\n color: #fff;\n background-color: ${props.theme.textColor};\n border-color: ${props.theme.textColor};\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 0.2rem ${props.theme.inputFocusBorder}33;\n }\n `;\n }\n }}\n`;\n","import styled from 'styled-components';\n\nexport const ButtonGroup = styled.div`\n margin-bottom: 0.3rem;\n\n button:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n\n button:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n`;\n","import React from 'react';\nimport styled from 'styled-components';\nimport { X as XIcon } from '@phosphor-icons/react';\n\nconst StyledCloseButton = styled.button`\n position: absolute;\n left: 0;\n top: 0;\n border: none;\n background: transparent;\n color: inherit;\n cursor: pointer;\n font-size: 16px;\n padding: 2px 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n opacity: 0.7;\n transition:\n opacity 0.15s,\n color 0.15s;\n\n &:hover {\n opacity: 1;\n color: #dc3545;\n }\n`;\n\nexport interface CloseButtonProps {\n onClick: (e: React.MouseEvent) => void;\n title?: string;\n}\n\nexport const CloseButton: React.FC<CloseButtonProps> = ({ onClick, title = 'Remove track' }) => (\n <StyledCloseButton onClick={onClick} title={title}>\n <XIcon size={12} weight=\"bold\" />\n </StyledCloseButton>\n);\n","import styled from 'styled-components';\n\nexport const Controls = styled.div`\n background: transparent;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-start;\n overflow: hidden;\n box-sizing: border-box;\n text-align: center;\n border: 1px solid ${(props) => props.theme.borderColor};\n border-radius: ${(props) => props.theme.borderRadius};\n`;\n","import styled from 'styled-components';\n\nexport const Header = styled.header`\n overflow: hidden;\n height: 26px;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 0.2rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n color: ${(props) => props.theme.textColor};\n background-color: transparent;\n`;\n","import React from 'react';\nimport { SpeakerLowIcon, type IconProps } from '@phosphor-icons/react';\n\nexport const VolumeDownIcon: React.FC<IconProps> = (props) => (\n <SpeakerLowIcon weight=\"light\" {...props} />\n);\n","import React from 'react';\nimport { SpeakerHighIcon, type IconProps } from '@phosphor-icons/react';\n\nexport const VolumeUpIcon: React.FC<IconProps> = (props) => (\n <SpeakerHighIcon weight=\"light\" {...props} />\n);\n","import React from 'react';\nimport { TrashIcon as PhosphorTrashIcon, type IconProps } from '@phosphor-icons/react';\n\nexport const TrashIcon: React.FC<IconProps> = (props) => (\n <PhosphorTrashIcon weight=\"light\" {...props} />\n);\n","import React from 'react';\nimport { DotsThreeIcon, type IconProps } from '@phosphor-icons/react';\n\nexport const DotsIcon: React.FC<IconProps> = (props) => <DotsThreeIcon weight=\"bold\" {...props} />;\n","import styled from 'styled-components';\nimport { BaseSlider } from '../../styled/index';\n\n/**\n * TrackControls Slider - Compact slider for volume/pan controls\n *\n * Extends BaseSlider with track-specific styling:\n * - Smaller thumb and track for compact layout\n * - Uses theme's sliderThumbColor (goldenrod by default)\n */\nexport const Slider = styled(BaseSlider)`\n width: 75%;\n height: 5px;\n background: ${(props) => props.theme.sliderTrackColor};\n\n &::-webkit-slider-thumb {\n width: 12px;\n height: 12px;\n background: ${(props) => props.theme.sliderThumbColor};\n border: none;\n margin-top: -4px;\n cursor: ew-resize;\n }\n\n &::-moz-range-thumb {\n width: 12px;\n height: 12px;\n background: ${(props) => props.theme.sliderThumbColor};\n border: none;\n cursor: ew-resize;\n }\n\n &::-webkit-slider-runnable-track {\n height: 5px;\n background: ${(props) => props.theme.sliderTrackColor};\n border-radius: 3px;\n }\n\n &::-moz-range-track {\n height: 5px;\n background: ${(props) => props.theme.sliderTrackColor};\n border-radius: 3px;\n }\n\n &:focus::-webkit-slider-runnable-track {\n background: ${(props) => props.theme.inputBorder};\n }\n\n &:focus::-moz-range-track {\n background: ${(props) => props.theme.inputBorder};\n }\n\n &:focus::-webkit-slider-thumb {\n border: 2px solid ${(props) => props.theme.textColor};\n }\n\n &:focus::-moz-range-thumb {\n border: 2px solid ${(props) => props.theme.textColor};\n }\n`;\n","import styled from 'styled-components';\n\nexport const SliderWrapper = styled.label`\n width: 100%;\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0 1rem;\n margin-bottom: 0.2rem;\n font-size: 14px;\n`;\n","import React, { useState, useEffect, useRef, useCallback, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport styled from 'styled-components';\nimport { DotsIcon } from './TrackControls/DotsIcon';\n\nexport interface TrackMenuItem {\n id: string;\n label?: string;\n content: ReactNode;\n}\n\nexport interface TrackMenuProps {\n items: TrackMenuItem[] | ((onClose: () => void) => TrackMenuItem[]);\n}\n\nconst MenuContainer = styled.div`\n position: relative;\n display: inline-block;\n`;\n\nconst MenuButton = styled.button`\n background: none;\n border: none;\n cursor: pointer;\n padding: 2px 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: inherit;\n opacity: 0.7;\n\n &:hover {\n opacity: 1;\n }\n`;\n\nconst DROPDOWN_MIN_WIDTH = 180;\n\nconst Dropdown = styled.div<{ $top: number; $left: number }>`\n position: fixed;\n top: ${(p) => p.$top}px;\n left: ${(p) => p.$left}px;\n z-index: 10000;\n background: ${(p) => p.theme.timescaleBackgroundColor ?? '#222'};\n color: ${(p) => p.theme.textColor ?? 'inherit'};\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 6px;\n padding: 0.5rem 0;\n min-width: ${DROPDOWN_MIN_WIDTH}px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n`;\n\nconst Divider = styled.hr`\n border: none;\n border-top: 1px solid rgba(128, 128, 128, 0.3);\n margin: 0.35rem 0;\n`;\n\nexport const TrackMenu: React.FC<TrackMenuProps> = ({ items: itemsProp }) => {\n const [open, setOpen] = useState(false);\n const close = useCallback(() => setOpen(false), []);\n const items = typeof itemsProp === 'function' ? itemsProp(close) : itemsProp;\n const [dropdownPos, setDropdownPos] = useState({ top: 0, left: 0 });\n const buttonRef = useRef<HTMLButtonElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n const updatePosition = useCallback(() => {\n if (!buttonRef.current) return;\n const rect = buttonRef.current.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const dropHeight = dropdownRef.current?.offsetHeight ?? 160;\n\n // Prefer opening to the right of the button\n let left = rect.right + 4;\n if (left + DROPDOWN_MIN_WIDTH > vw) {\n left = rect.left - DROPDOWN_MIN_WIDTH - 4;\n }\n left = Math.max(4, Math.min(left, vw - DROPDOWN_MIN_WIDTH - 4));\n\n // Align top with the button, push up if it would overflow viewport\n let top = rect.top;\n if (top + dropHeight > vh - 4) {\n top = Math.max(4, rect.bottom - dropHeight);\n }\n\n setDropdownPos({ top, left });\n }, []);\n\n // Position on open, refine after mount, reposition on scroll/resize\n useEffect(() => {\n if (!open) return;\n updatePosition();\n\n // Refine once the dropdown has mounted and has actual dimensions\n const rafId = requestAnimationFrame(() => updatePosition());\n\n const onScroll = () => updatePosition();\n const onResize = () => updatePosition();\n window.addEventListener('scroll', onScroll, true);\n window.addEventListener('resize', onResize);\n return () => {\n cancelAnimationFrame(rafId);\n window.removeEventListener('scroll', onScroll, true);\n window.removeEventListener('resize', onResize);\n };\n }, [open, updatePosition]);\n\n // Close on outside click or Escape\n useEffect(() => {\n if (!open) return;\n\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n buttonRef.current &&\n !buttonRef.current.contains(target) &&\n dropdownRef.current &&\n !dropdownRef.current.contains(target)\n ) {\n setOpen(false);\n }\n };\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n setOpen(false);\n }\n };\n document.addEventListener('mousedown', handleClick);\n document.addEventListener('keydown', handleKeyDown);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n document.removeEventListener('keydown', handleKeyDown);\n };\n }, [open]);\n\n return (\n <MenuContainer>\n <MenuButton\n ref={buttonRef}\n onClick={(e) => {\n e.stopPropagation();\n setOpen((prev) => !prev);\n }}\n onMouseDown={(e) => e.stopPropagation()}\n title=\"Track menu\"\n aria-label=\"Track menu\"\n >\n <DotsIcon size={16} />\n </MenuButton>\n {open &&\n typeof document !== 'undefined' &&\n createPortal(\n <Dropdown\n ref={dropdownRef}\n $top={dropdownPos.top}\n $left={dropdownPos.left}\n onMouseDown={(e) => e.stopPropagation()}\n >\n {items.map((item, index) => (\n <React.Fragment key={item.id}>\n {index > 0 && <Divider />}\n {item.content}\n </React.Fragment>\n ))}\n </Dropdown>,\n document.body\n )}\n </MenuContainer>\n );\n};\n"],"mappings":"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","names":["useTheme","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_jsx_runtime","import_react","import_styled_components","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","styled","import_react","import_jsx_runtime","React","import_styled_components","import_react","import_styled_components","import_jsx_runtime","styled","import_react","import_styled_components","import_jsx_runtime","styled","React","import_styled_components","import_jsx_runtime","styled","import_jsx_runtime","styled","import_styled_components","import_jsx_runtime","styled","import_react","import_styled_components","import_core","import_jsx_runtime","styled","Wrapper","import_react","import_styled_components","import_jsx_runtime","styled","import_styled_components","import_react","import_jsx_runtime","Wrapper","styled","import_styled_components","import_jsx_runtime","styled","import_react","import_styled_components","import_jsx_runtime","styled","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_react","import_styled_components","useTheme","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_styled_components","import_core","import_jsx_runtime","Wrapper","styled","import_jsx_runtime","useTheme","import_react","import_styled_components","import_jsx_runtime","styled","import_react","import_react","import_styled_components","import_core","import_jsx_runtime","formatTime","styled","React","import_jsx_runtime","import_styled_components","import_jsx_runtime","styled","import_styled_components","import_jsx_runtime","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","import_react","import_jsx_runtime","styled","XIcon","import_styled_components","styled","import_styled_components","styled","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","PhosphorTrashIcon","import_react","import_jsx_runtime","import_styled_components","styled","import_styled_components","styled","import_react","import_styled_components","import_jsx_runtime","styled","React"]}
1
+ {"version":3,"sources":["../src/index.tsx","../src/components/AudioPosition.tsx","../src/styled/BaseButton.tsx","../src/styled/BaseCheckbox.tsx","../src/styled/BaseControlButton.tsx","../src/styled/BaseInput.tsx","../src/styled/BaseLabel.tsx","../src/styled/BaseSelect.tsx","../src/styled/BaseSlider.tsx","../src/components/AutomaticScrollCheckbox.tsx","../src/components/Channel.tsx","../src/wfpl-theme.ts","../src/contexts/ScrollViewport.tsx","../src/contexts/ClipViewportOrigin.tsx","../src/hooks/useChunkedCanvasRefs.ts","../src/utils/peakRendering.ts","../src/components/ErrorBoundary.tsx","../src/components/Clip.tsx","../src/components/ClipHeader.tsx","../src/components/ClipBoundary.tsx","../src/components/FadeOverlay.tsx","../src/components/MasterVolumeControl.tsx","../src/components/PianoRollChannel.tsx","../src/components/Playhead.tsx","../src/components/Playlist.tsx","../src/components/Selection.tsx","../src/components/LoopRegion.tsx","../src/components/SelectionTimeInputs.tsx","../src/components/TimeInput.tsx","../src/utils/timeFormat.ts","../src/contexts/BeatsAndBars.tsx","../src/contexts/DevicePixelRatio.tsx","../src/contexts/PlaylistInfo.tsx","../src/contexts/Theme.tsx","../src/contexts/TrackControls.tsx","../src/contexts/Playout.tsx","../src/components/SpectrogramChannel.tsx","../src/components/SmartChannel.tsx","../src/components/SpectrogramLabels.tsx","../src/components/SmartScale.tsx","../src/components/TimeScale.tsx","../src/components/TimeFormatSelect.tsx","../src/components/Track.tsx","../src/components/TrackControls/Button.tsx","../src/components/TrackControls/ButtonGroup.tsx","../src/components/TrackControls/CloseButton.tsx","../src/components/TrackControls/Controls.tsx","../src/components/TrackControls/Header.tsx","../src/components/TrackControls/VolumeDownIcon.tsx","../src/components/TrackControls/VolumeUpIcon.tsx","../src/components/TrackControls/TrashIcon.tsx","../src/components/TrackControls/DotsIcon.tsx","../src/components/TrackControls/Slider.tsx","../src/components/TrackControls/SliderWrapper.tsx","../src/components/TrackMenu.tsx","../src/utils/conversions.ts"],"sourcesContent":["export * from './components';\nexport * from './contexts';\nexport * from './utils/conversions';\nexport * from './utils/timeFormat';\nexport * from './styled/index';\nexport * from './wfpl-theme';\n","import React from 'react';\nimport styled from 'styled-components';\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\nexport interface AudioPositionProps {\n formattedTime: string;\n className?: string;\n}\n\n/**\n * Displays the current audio playback position\n */\nexport const AudioPosition: React.FC<AudioPositionProps> = ({ formattedTime, className }) => {\n return (\n <PositionDisplay className={className} aria-label=\"Audio position\">\n {formattedTime}\n </PositionDisplay>\n );\n};\n","import styled from 'styled-components';\n\n/**\n * BaseButton - A styled button component that uses theme values\n *\n * This provides consistent styling across all button elements in the waveform playlist.\n */\nexport const BaseButton = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0.5rem 1rem;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n font-weight: 500;\n color: ${(props) => props.theme.buttonText};\n background-color: ${(props) => props.theme.buttonBackground};\n border: 1px solid ${(props) => props.theme.buttonBorder};\n border-radius: ${(props) => props.theme.borderRadius};\n cursor: pointer;\n outline: none;\n transition:\n background-color 0.15s ease-in-out,\n border-color 0.15s ease-in-out,\n box-shadow 0.15s ease-in-out;\n\n &:hover:not(:disabled) {\n background-color: ${(props) => props.theme.buttonHoverBackground};\n }\n\n &:focus {\n box-shadow: 0 0 0 2px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n`;\n\n/**\n * BaseButtonSmall - A smaller variant for compact layouts\n */\nexport const BaseButtonSmall = styled(BaseButton)`\n padding: 0.25rem 0.5rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n`;\n\n/**\n * IconButton - A square button for icons\n */\nexport const IconButton = styled(BaseButton)`\n padding: 0.5rem;\n min-width: 2.25rem;\n min-height: 2.25rem;\n`;\n\n/**\n * IconButtonSmall - A smaller square button for icons\n */\nexport const IconButtonSmall = styled(BaseButton)`\n padding: 0.25rem;\n min-width: 1.75rem;\n min-height: 1.75rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseCheckboxWrapper - Container for checkbox + label\n */\nexport const BaseCheckboxWrapper = styled.div`\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n`;\n\n/**\n * BaseCheckbox - A styled checkbox input\n */\nexport const BaseCheckbox = styled.input`\n cursor: pointer;\n accent-color: ${(props) => props.theme.inputFocusBorder};\n\n &:disabled {\n cursor: not-allowed;\n }\n`;\n\n/**\n * BaseCheckboxLabel - Label for checkboxes\n */\nexport const BaseCheckboxLabel = styled.label`\n margin: 0;\n cursor: pointer;\n user-select: none;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n color: ${(props) => props.theme.textColor};\n`;\n","import styled from 'styled-components';\n\n/**\n * ControlButton - A colored action button for prominent actions like Play, Pause, Record.\n * For neutral buttons, use BaseButton from the styled primitives.\n *\n * Uses theme colors when available, with fallbacks for standalone use.\n */\nexport const BaseControlButton = styled.button`\n padding: 0.5rem 1rem;\n background: ${(props) => props.theme.buttonBackground || '#007bff'};\n color: ${(props) => props.theme.buttonText || 'white'};\n border: none;\n border-radius: ${(props) => props.theme.borderRadius};\n cursor: pointer;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n font-weight: 500;\n transition: background-color 0.15s ease-in-out;\n\n &:hover:not(:disabled) {\n background: ${(props) => props.theme.buttonHoverBackground || '#0056b3'};\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 2px ${(props) => props.theme.buttonBackground || '#007bff'}66;\n }\n\n &:disabled {\n background: #6c757d;\n cursor: not-allowed;\n opacity: 0.6;\n }\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseInput - A styled input component that uses theme values\n *\n * This provides consistent styling across all input elements in the waveform playlist.\n * Styling is controlled via the theme, making it easy to adapt to different environments.\n */\nexport const BaseInput = styled.input`\n padding: 0.5rem 0.75rem;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n color: ${(props) => props.theme.inputText};\n background-color: ${(props) => props.theme.inputBackground};\n border: 1px solid ${(props) => props.theme.inputBorder};\n border-radius: ${(props) => props.theme.borderRadius};\n outline: none;\n transition:\n border-color 0.15s ease-in-out,\n box-shadow 0.15s ease-in-out;\n\n &::placeholder {\n color: ${(props) => props.theme.inputPlaceholder};\n }\n\n &:focus {\n border-color: ${(props) => props.theme.inputFocusBorder};\n box-shadow: 0 0 0 2px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n`;\n\n/**\n * BaseInputSmall - A smaller variant for compact layouts\n */\nexport const BaseInputSmall = styled(BaseInput)`\n padding: 0.25rem 0.5rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseLabel - A styled label component that uses theme values\n */\nexport const BaseLabel = styled.label`\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSizeSmall};\n font-weight: 500;\n color: ${(props) => props.theme.textColorMuted};\n margin-bottom: 0.25rem;\n display: block;\n`;\n\n/**\n * InlineLabel - A label that displays inline with its input\n */\nexport const InlineLabel = styled.label`\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n color: ${(props) => props.theme.textColor};\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n cursor: pointer;\n`;\n\n/**\n * ScreenReaderOnly - Visually hidden but accessible to screen readers\n */\nexport const ScreenReaderOnly = styled.span`\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseSelect - A styled select component that uses theme values\n *\n * This provides consistent styling across all select elements in the waveform playlist.\n */\nexport const BaseSelect = styled.select`\n padding: 0.5rem 2rem 0.5rem 0.75rem;\n font-family: ${(props) => props.theme.fontFamily};\n font-size: ${(props) => props.theme.fontSize};\n color: ${(props) => props.theme.inputText};\n background-color: ${(props) => props.theme.inputBackground};\n border: 1px solid ${(props) => props.theme.inputBorder};\n border-radius: ${(props) => props.theme.borderRadius};\n outline: none;\n cursor: pointer;\n appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 0.75rem center;\n transition:\n border-color 0.15s ease-in-out,\n box-shadow 0.15s ease-in-out;\n\n &:focus {\n border-color: ${(props) => props.theme.inputFocusBorder};\n box-shadow: 0 0 0 2px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n /* Style native option elements for dark mode support */\n option {\n color: ${(props) => props.theme.inputText};\n background-color: ${(props) => props.theme.inputBackground};\n }\n`;\n\n/**\n * BaseSelectSmall - A smaller variant for compact layouts\n */\nexport const BaseSelectSmall = styled(BaseSelect)`\n padding: 0.25rem 1.75rem 0.25rem 0.5rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n`;\n","import styled from 'styled-components';\n\n/**\n * BaseSlider - Themed range input for volume controls, etc.\n *\n * Uses theme values for consistent styling across light/dark modes.\n * Provides custom styling for the track and thumb.\n */\nexport const BaseSlider = styled.input.attrs({ type: 'range' })`\n -webkit-appearance: none;\n appearance: none;\n width: 100%;\n height: 6px;\n background: ${(props) => props.theme.sliderTrackColor};\n border-radius: 3px;\n cursor: pointer;\n outline: none;\n\n /* WebKit (Chrome, Safari) */\n &::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 16px;\n height: 16px;\n background: ${(props) => props.theme.sliderThumbColor};\n border: 2px solid ${(props) => props.theme.inputBackground};\n border-radius: 50%;\n cursor: pointer;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n transition:\n transform 0.15s ease,\n box-shadow 0.15s ease;\n }\n\n &::-webkit-slider-thumb:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n }\n\n /* Firefox */\n &::-moz-range-thumb {\n width: 16px;\n height: 16px;\n background: ${(props) => props.theme.sliderThumbColor};\n border: 2px solid ${(props) => props.theme.inputBackground};\n border-radius: 50%;\n cursor: pointer;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n transition:\n transform 0.15s ease,\n box-shadow 0.15s ease;\n }\n\n &::-moz-range-thumb:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n }\n\n &::-moz-range-track {\n background: ${(props) => props.theme.sliderTrackColor};\n border-radius: 3px;\n height: 6px;\n }\n\n &:focus {\n outline: none;\n }\n\n &:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 3px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:focus::-moz-range-thumb {\n box-shadow: 0 0 0 3px ${(props) => props.theme.inputFocusBorder}33;\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n\n &:disabled::-webkit-slider-thumb {\n cursor: not-allowed;\n }\n\n &:disabled::-moz-range-thumb {\n cursor: not-allowed;\n }\n`;\n","import React from 'react';\nimport { BaseCheckboxWrapper, BaseCheckbox, BaseCheckboxLabel } from '../styled/index';\n\nexport interface AutomaticScrollCheckboxProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * Checkbox control for enabling/disabling automatic scroll during playback\n */\nexport const AutomaticScrollCheckbox: React.FC<AutomaticScrollCheckboxProps> = ({\n checked,\n onChange,\n disabled = false,\n className,\n}) => {\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n onChange(e.target.checked);\n };\n\n return (\n <BaseCheckboxWrapper className={className}>\n <BaseCheckbox\n type=\"checkbox\"\n id=\"automatic-scroll\"\n className=\"automatic-scroll\"\n checked={checked}\n onChange={handleChange}\n disabled={disabled}\n />\n <BaseCheckboxLabel htmlFor=\"automatic-scroll\">Automatic Scroll</BaseCheckboxLabel>\n </BaseCheckboxWrapper>\n );\n};\n","import React, { FunctionComponent, useEffect } from 'react';\nimport styled from 'styled-components';\nimport type { Peaks, Bits } from '@waveform-playlist/core';\nimport {\n WaveformColor,\n WaveformDrawMode,\n isWaveformGradient,\n waveformColorToCss,\n} from '../wfpl-theme';\nimport { useVisibleChunkIndices } from '../contexts/ScrollViewport';\nimport { useClipViewportOrigin } from '../contexts/ClipViewportOrigin';\nimport { useChunkedCanvasRefs } from '../hooks/useChunkedCanvasRefs';\nimport { MAX_CANVAS_WIDTH } from '@waveform-playlist/core';\nimport {\n aggregatePeaks,\n calculateBarRects,\n calculateFirstBarPosition,\n} from '../utils/peakRendering';\n\n// Re-export WaveformColor for consumers\nexport type { WaveformColor } from '../wfpl-theme';\nexport type { WaveformDrawMode } from '../wfpl-theme';\n\n/**\n * Creates a Canvas gradient from a WaveformColor configuration\n */\nfunction createCanvasFillStyle(\n ctx: CanvasRenderingContext2D,\n color: WaveformColor,\n width: number,\n height: number\n): string | CanvasGradient {\n if (!isWaveformGradient(color)) {\n return color;\n }\n\n let gradient: CanvasGradient;\n if (color.direction === 'vertical') {\n gradient = ctx.createLinearGradient(0, 0, 0, height);\n } else {\n gradient = ctx.createLinearGradient(0, 0, width, 0);\n }\n\n for (const stop of color.stops) {\n gradient.addColorStop(stop.offset, stop.color);\n }\n\n return gradient;\n}\n\ninterface WaveformProps {\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $left: number;\n}\n\nconst Waveform = styled.canvas.attrs<WaveformProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n left: `${props.$left}px`,\n },\n}))<WaveformProps>`\n position: absolute;\n top: 0;\n /* Disable image rendering interpolation */\n image-rendering: pixelated;\n image-rendering: crisp-edges;\n`;\n\ninterface WrapperProps {\n readonly $index: number;\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $waveFillColor: string; // CSS background value (solid or gradient)\n}\n\nconst Wrapper = styled.div.attrs<WrapperProps>((props) => ({\n style: {\n top: `${props.$waveHeight * props.$index}px`,\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n },\n}))<WrapperProps>`\n position: absolute;\n background: ${(props) => props.$waveFillColor};\n /* Force GPU compositing layer to reduce scroll flickering */\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\nexport interface ChannelProps {\n className?: string;\n index: number;\n data: Peaks;\n bits: Bits;\n length: number;\n devicePixelRatio?: number;\n waveHeight?: number;\n /** Waveform bar color - can be a solid color string or gradient config */\n waveOutlineColor?: WaveformColor;\n /** Waveform background color - can be a solid color string or gradient config */\n waveFillColor?: WaveformColor;\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 /** If true, background is transparent (for use with external progress overlay) */\n transparentBackground?: boolean;\n /**\n * Drawing mode:\n * - 'inverted': Canvas draws waveOutlineColor where there's NO audio, revealing waveFillColor background as bars (default). Good for gradient bars.\n * - 'normal': Canvas draws waveFillColor where there IS audio. Use with transparentBackground for progress overlays.\n */\n drawMode?: WaveformDrawMode;\n}\n\nexport const Channel: FunctionComponent<ChannelProps> = (props) => {\n const {\n data,\n bits,\n length,\n index,\n className,\n devicePixelRatio = 1,\n waveHeight = 80,\n waveOutlineColor = '#E0EFF1',\n waveFillColor = 'grey',\n barWidth = 1,\n barGap = 0,\n transparentBackground = false,\n drawMode = 'inverted',\n } = props;\n const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();\n const clipOriginX = useClipViewportOrigin();\n\n const visibleChunkIndices = useVisibleChunkIndices(length, MAX_CANVAS_WIDTH, clipOriginX);\n\n // Draw waveform bars on visible canvas chunks.\n // visibleChunkIndices changes only when chunks mount/unmount, not on every scroll pixel.\n // useEffect (not useLayoutEffect) so the browser paints the track layout\n // (controls + empty canvas containers) before heavy canvas drawing starts.\n // This prevents browser-initiated scrollLeft shifts and main-thread blocking.\n useEffect(() => {\n const step = barWidth + barGap;\n\n for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {\n const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;\n\n const ctx = canvas.getContext('2d');\n const h2 = Math.floor(waveHeight / 2);\n\n if (ctx) {\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n // Create gradient using CSS pixel coordinates (after scaling)\n // This ensures the gradient aligns with the drawing coordinates\n const canvasWidth = canvas.width / devicePixelRatio;\n\n // Choose canvas fill color based on draw mode:\n let fillColor: WaveformColor;\n if (drawMode === 'normal') {\n // Normal: canvas draws the bars directly\n fillColor = waveFillColor;\n } else {\n // Inverted: canvas masks non-audio areas, background shows as bars\n fillColor = waveOutlineColor;\n }\n ctx.fillStyle = createCanvasFillStyle(ctx, fillColor, canvasWidth, waveHeight);\n\n const canvasStartGlobal = globalPixelOffset;\n const canvasEndGlobal = globalPixelOffset + canvasWidth;\n const firstBarGlobal = calculateFirstBarPosition(canvasStartGlobal, barWidth, step);\n\n for (\n let barGlobal = Math.max(0, firstBarGlobal);\n barGlobal < canvasEndGlobal;\n barGlobal += step\n ) {\n const x = barGlobal - canvasStartGlobal;\n\n // Skip if the entire bar would be before this canvas\n if (x + barWidth <= 0) continue;\n\n const peakEnd = Math.min(barGlobal + step, length);\n const peak = aggregatePeaks(data, bits, barGlobal, peakEnd);\n\n if (peak) {\n const rects = calculateBarRects(x, barWidth, h2, peak.min, peak.max, drawMode);\n for (const rect of rects) {\n ctx.fillRect(rect.x, rect.y, rect.width, rect.height);\n }\n }\n }\n }\n }\n }, [\n canvasMapRef,\n data,\n bits,\n waveHeight,\n waveOutlineColor,\n waveFillColor,\n devicePixelRatio,\n length,\n barWidth,\n barGap,\n drawMode,\n visibleChunkIndices,\n index,\n ]);\n\n // Build visible canvas chunk elements\n const waveforms = visibleChunkIndices.map((i) => {\n const chunkLeft = i * MAX_CANVAS_WIDTH;\n const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);\n\n return (\n <Waveform\n key={`${length}-${i}`}\n $cssWidth={currentWidth}\n $left={chunkLeft}\n width={currentWidth * devicePixelRatio}\n height={waveHeight * devicePixelRatio}\n $waveHeight={waveHeight}\n data-index={i}\n ref={canvasRef}\n />\n );\n });\n\n // Background color depends on draw mode:\n // - inverted: waveFillColor background, canvas masks non-audio areas with waveOutlineColor\n // - normal: waveFillColor background, canvas draws waveFillColor at audio peaks (use with transparentBackground)\n const bgColor = waveFillColor;\n const backgroundCss = transparentBackground ? 'transparent' : waveformColorToCss(bgColor);\n\n return (\n <Wrapper\n $index={index}\n $cssWidth={length}\n className={className}\n $waveHeight={waveHeight}\n $waveFillColor={backgroundCss}\n >\n {waveforms}\n </Wrapper>\n );\n};\n","/**\n * Waveform Playlist Theme\n *\n * This file defines the theme interface and default values for the waveform playlist components.\n */\n\n/**\n * Gradient color stop for waveform gradients\n */\nexport interface GradientStop {\n offset: number; // 0 to 1\n color: string;\n}\n\n/**\n * Gradient configuration for waveforms\n * Can be applied vertically (top to bottom) or horizontally (left to right)\n */\nexport interface WaveformGradient {\n type: 'linear';\n direction: 'vertical' | 'horizontal';\n stops: GradientStop[];\n}\n\n/**\n * Waveform color can be a simple string or a gradient configuration\n */\nexport type WaveformColor = string | WaveformGradient;\n\n/**\n * Type guard to check if a WaveformColor is a gradient\n */\nexport function isWaveformGradient(color: WaveformColor): color is WaveformGradient {\n return typeof color === 'object' && color !== null && 'type' in color;\n}\n\n/**\n * Converts WaveformColor to a CSS background value\n */\nexport function waveformColorToCss(color: WaveformColor): string {\n if (!isWaveformGradient(color)) {\n return color;\n }\n\n const direction = color.direction === 'vertical' ? 'to bottom' : 'to right';\n const stops = color.stops.map((stop) => `${stop.color} ${stop.offset * 100}%`).join(', ');\n\n return `linear-gradient(${direction}, ${stops})`;\n}\n\n/**\n * Waveform drawing mode determines how colors are applied:\n * - 'inverted': Canvas draws waveOutlineColor in areas WITHOUT audio (current default).\n * waveFillColor shows through where audio peaks are. Good for gradient bars.\n * - 'normal': Canvas draws waveFillColor bars where audio peaks ARE.\n * waveOutlineColor is used as background. Good for gradient backgrounds.\n */\nexport type WaveformDrawMode = 'inverted' | 'normal';\n\nexport interface WaveformPlaylistTheme {\n // Waveform drawing mode - controls how colors are applied\n waveformDrawMode?: WaveformDrawMode;\n\n // Waveform colors - can be solid colors or gradients\n waveOutlineColor: WaveformColor;\n waveFillColor: WaveformColor;\n waveProgressColor: string; // Progress stays solid for simplicity\n\n // Selected track colors - can also be gradients\n selectedWaveOutlineColor: WaveformColor;\n selectedWaveFillColor: WaveformColor;\n selectedTrackControlsBackground: string;\n\n // Timescale colors\n timeColor: string;\n timescaleBackgroundColor: string;\n\n // Playback UI colors\n playheadColor: string;\n selectionColor: string;\n\n // Loop region colors (Audacity-style loop markers)\n loopRegionColor: string;\n loopMarkerColor: string;\n\n // Clip header colors (for multi-clip editing)\n clipHeaderBackgroundColor: string;\n clipHeaderBorderColor: string;\n clipHeaderTextColor: string;\n clipHeaderFontFamily: string;\n\n // Selected clip header colors\n selectedClipHeaderBackgroundColor: string;\n\n // Fade overlay colors\n fadeOverlayColor: string;\n\n // UI component colors\n backgroundColor: string;\n surfaceColor: string;\n borderColor: string;\n textColor: string;\n textColorMuted: string;\n\n // Interactive element colors\n inputBackground: string;\n inputBorder: string;\n inputText: string;\n inputPlaceholder: string;\n inputFocusBorder: string;\n\n // Button colors\n buttonBackground: string;\n buttonText: string;\n buttonBorder: string;\n buttonHoverBackground: string;\n\n // Slider colors\n sliderTrackColor: string;\n sliderThumbColor: string;\n\n // Annotation colors\n annotationBoxBackground: string;\n annotationBoxActiveBackground: string;\n annotationBoxHoverBackground: string;\n annotationBoxBorder: string;\n annotationBoxActiveBorder: string;\n annotationLabelColor: string;\n annotationResizeHandleColor: string;\n annotationResizeHandleActiveColor: string;\n annotationTextItemHoverBackground: string;\n\n // Playlist container background (falls back to waveOutlineColor when unset)\n playlistBackgroundColor?: string;\n\n // Piano roll colors (MIDI visualization)\n pianoRollNoteColor: string;\n pianoRollSelectedNoteColor: string;\n pianoRollBackgroundColor: string;\n\n // Spacing and sizing\n borderRadius: string;\n fontFamily: string;\n fontSize: string;\n fontSizeSmall: string;\n}\n\nexport const defaultTheme: WaveformPlaylistTheme = {\n waveformDrawMode: 'inverted',\n waveOutlineColor: '#ffffff',\n waveFillColor: '#1a7f8e', // White background for crisp look\n waveProgressColor: 'rgba(0, 0, 0, 0.10)', // Subtle dark overlay for light mode\n\n selectedWaveOutlineColor: '#ffffff',\n selectedWaveFillColor: '#00b4d8', // Selected: brighter cyan\n selectedTrackControlsBackground: '#d9e9ff', // Light blue background for selected track controls\n timeColor: '#000',\n timescaleBackgroundColor: '#fff',\n playheadColor: '#f00',\n selectionColor: 'rgba(255, 105, 180, 0.7)', // hot pink - high contrast on light backgrounds\n loopRegionColor: 'rgba(59, 130, 246, 0.3)', // Blue - distinct from pink selection\n loopMarkerColor: '#3b82f6', // Blue marker triangles\n clipHeaderBackgroundColor: 'rgba(0, 0, 0, 0.1)',\n clipHeaderBorderColor: 'rgba(0, 0, 0, 0.2)',\n clipHeaderTextColor: '#333',\n clipHeaderFontFamily: 'inherit',\n selectedClipHeaderBackgroundColor: '#b3d9ff', // Brighter blue for selected track clip headers\n\n // Fade overlay colors\n fadeOverlayColor: 'rgba(0, 0, 0, 0.4)', // Semi-transparent overlay for fade regions\n\n // UI component colors\n backgroundColor: '#ffffff',\n surfaceColor: '#f5f5f5',\n borderColor: '#ddd',\n textColor: '#333',\n textColorMuted: '#666',\n\n // Interactive element colors\n inputBackground: '#ffffff',\n inputBorder: '#ccc',\n inputText: '#333',\n inputPlaceholder: '#999',\n inputFocusBorder: '#0066cc',\n\n // Button colors - blue to match common UI patterns\n buttonBackground: '#0091ff',\n buttonText: '#ffffff',\n buttonBorder: '#0081e6',\n buttonHoverBackground: '#0081e6',\n\n // Slider colors\n sliderTrackColor: '#ddd',\n sliderThumbColor: '#daa520', // goldenrod\n\n // Annotation colors\n annotationBoxBackground: 'rgba(255, 255, 255, 0.85)',\n annotationBoxActiveBackground: 'rgba(255, 255, 255, 0.95)',\n annotationBoxHoverBackground: 'rgba(255, 255, 255, 0.98)',\n annotationBoxBorder: '#ff9800',\n annotationBoxActiveBorder: '#d67600',\n annotationLabelColor: '#2a2a2a',\n annotationResizeHandleColor: 'rgba(0, 0, 0, 0.4)',\n annotationResizeHandleActiveColor: 'rgba(0, 0, 0, 0.8)',\n annotationTextItemHoverBackground: 'rgba(0, 0, 0, 0.03)',\n\n // Piano roll colors\n pianoRollNoteColor: '#2a7070',\n pianoRollSelectedNoteColor: '#3d9e9e',\n pianoRollBackgroundColor: '#1a1a2e',\n\n // Spacing and sizing\n borderRadius: '4px',\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, sans-serif',\n fontSize: '14px',\n fontSizeSmall: '12px',\n};\n\nexport const darkTheme: WaveformPlaylistTheme = {\n // Normal mode: waveOutlineColor = bars, waveFillColor = background\n waveformDrawMode: 'inverted',\n // Dark bars on warm amber background\n waveOutlineColor: '#c49a6c', // Solid warm amber for background\n waveFillColor: '#1a1612', // Very dark warm brown for bars\n waveProgressColor: 'rgba(100, 70, 40, 0.6)', // Warm brown progress overlay\n // Selected: slightly lighter bars on brighter amber background\n selectedWaveFillColor: '#241c14', // Slightly lighter warm brown bars when selected\n selectedWaveOutlineColor: '#e8c090', // Brighter amber background when selected\n selectedTrackControlsBackground: '#2a2218', // Dark warm brown for selected track controls\n timeColor: '#d8c0a8', // Warm amber for timescale text\n timescaleBackgroundColor: '#1a1612', // Dark warm brown background\n playheadColor: '#3a8838', // Darker Ampelmännchen green playhead\n selectionColor: 'rgba(60, 140, 58, 0.6)', // Darker Ampelmännchen green selection - visible on dark backgrounds\n loopRegionColor: 'rgba(96, 165, 250, 0.35)', // Light blue - distinct from green selection\n loopMarkerColor: '#60a5fa', // Light blue marker triangles\n clipHeaderBackgroundColor: 'rgba(20, 16, 12, 0.85)', // Dark background for clip headers\n clipHeaderBorderColor: 'rgba(200, 160, 120, 0.25)',\n clipHeaderTextColor: '#d8c0a8', // Warm amber text\n clipHeaderFontFamily: 'inherit',\n selectedClipHeaderBackgroundColor: '#3a2c20', // Darker warm brown for selected clip headers\n\n // Fade overlay colors\n fadeOverlayColor: 'rgba(200, 100, 80, 0.5)', // Warm red-orange overlay visible on dark backgrounds\n\n // UI component colors\n backgroundColor: '#1e1e1e',\n surfaceColor: '#2d2d2d',\n borderColor: '#444',\n textColor: '#e0e0e0',\n textColorMuted: '#999',\n\n // Interactive element colors\n inputBackground: '#2d2d2d',\n inputBorder: '#555',\n inputText: '#e0e0e0',\n inputPlaceholder: '#777',\n inputFocusBorder: '#4A9EFF',\n\n // Button colors - Ampelmännchen green (#63C75F) with black text\n buttonBackground: '#63C75F',\n buttonText: '#0a0a0f',\n buttonBorder: '#52b84e',\n buttonHoverBackground: '#78d074',\n\n // Slider colors\n sliderTrackColor: '#555',\n sliderThumbColor: '#f0c040', // brighter goldenrod for dark mode\n\n // Annotation colors (dark mode - warm amber theme)\n annotationBoxBackground: 'rgba(40, 32, 24, 0.9)',\n annotationBoxActiveBackground: 'rgba(50, 40, 30, 0.95)',\n annotationBoxHoverBackground: 'rgba(60, 48, 36, 0.98)',\n annotationBoxBorder: '#c49a6c',\n annotationBoxActiveBorder: '#d4a87c',\n annotationLabelColor: '#d8c0a8',\n annotationResizeHandleColor: 'rgba(200, 160, 120, 0.5)',\n annotationResizeHandleActiveColor: 'rgba(220, 180, 140, 0.8)',\n annotationTextItemHoverBackground: 'rgba(200, 160, 120, 0.08)',\n\n // Piano roll colors\n pianoRollNoteColor: '#c49a6c',\n pianoRollSelectedNoteColor: '#e8c090',\n pianoRollBackgroundColor: '#0d0d14',\n\n // Spacing and sizing\n borderRadius: '4px',\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, sans-serif',\n fontSize: '14px',\n fontSizeSmall: '12px',\n};\n","import React, {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useCallback,\n useMemo,\n useRef,\n useSyncExternalStore,\n ReactNode,\n} from 'react';\n\nexport interface ScrollViewport {\n scrollLeft: number;\n containerWidth: number;\n /** Left edge of the rendering window in pixels. Includes a 1.5× container-width over-scan buffer to the left of the visible area. */\n visibleStart: number;\n /** Right edge of the rendering window in pixels. Includes a 1.5× container-width over-scan buffer to the right of the visible area. */\n visibleEnd: number;\n}\n\n/**\n * External store for viewport state. Using useSyncExternalStore instead of\n * React context state allows consumers to use selectors — they only re-render\n * when their derived value changes, not on every viewport update.\n */\nclass ViewportStore {\n private _state: ScrollViewport | null;\n private _listeners = new Set<() => void>();\n private _notifyRafId: number | null = null;\n\n constructor(containerEl?: HTMLElement | null) {\n // Seed with actual container width if available, otherwise estimate from\n // window.innerWidth. This prevents the first render from mounting ALL\n // canvas chunks (viewport=null → no filtering), only to prune them to\n // ~3 visible chunks after the first measurement.\n const width =\n containerEl?.clientWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 1024);\n const buffer = width * 1.5;\n this._state = {\n scrollLeft: 0,\n containerWidth: width,\n visibleStart: 0,\n visibleEnd: width + buffer,\n };\n }\n\n subscribe = (callback: () => void): (() => void) => {\n this._listeners.add(callback);\n return () => this._listeners.delete(callback);\n };\n\n getSnapshot = (): ScrollViewport | null => this._state;\n\n /**\n * Update viewport state. Applies a 100px scroll threshold to skip updates\n * that don't affect chunk visibility (1000px chunks with 1.5× overscan buffer).\n * Only notifies listeners when the state actually changes.\n *\n * Listener notification is deferred by one frame via requestAnimationFrame\n * to avoid conflicting with React 19's concurrent rendering. When React\n * time-slices a render across frames, synchronous useSyncExternalStore\n * notifications can trigger \"Should not already be working\" errors.\n */\n update(scrollLeft: number, containerWidth: number): void {\n const buffer = containerWidth * 1.5;\n const visibleStart = Math.max(0, scrollLeft - buffer);\n const visibleEnd = scrollLeft + containerWidth + buffer;\n\n // Skip update if scroll hasn't moved enough to matter for chunk visibility.\n if (\n this._state &&\n this._state.containerWidth === containerWidth &&\n Math.abs(this._state.scrollLeft - scrollLeft) < 100\n ) {\n return;\n }\n\n this._state = { scrollLeft, containerWidth, visibleStart, visibleEnd };\n\n // Defer listener notification to the next frame so it doesn't fire\n // during React's concurrent render time-slice. getSnapshot() returns\n // the new state immediately, so React picks it up on the next render.\n if (this._notifyRafId === null) {\n this._notifyRafId = requestAnimationFrame(() => {\n this._notifyRafId = null;\n for (const listener of this._listeners) {\n listener();\n }\n });\n }\n }\n\n cancelPendingNotification(): void {\n if (this._notifyRafId !== null) {\n cancelAnimationFrame(this._notifyRafId);\n this._notifyRafId = null;\n }\n }\n}\n\nconst ViewportStoreContext = createContext<ViewportStore | null>(null);\n\n// Stable no-op subscribe for when no provider exists\nconst EMPTY_SUBSCRIBE = () => () => {};\nconst NULL_SNAPSHOT = () => null;\n\ntype ScrollViewportProviderProps = {\n containerRef: React.RefObject<HTMLElement | null>;\n children: ReactNode;\n};\n\nexport const ScrollViewportProvider = ({ containerRef, children }: ScrollViewportProviderProps) => {\n const storeRef = useRef<ViewportStore | null>(null);\n if (storeRef.current === null) {\n storeRef.current = new ViewportStore(containerRef.current);\n }\n const store = storeRef.current;\n const rafIdRef = useRef<number | null>(null);\n\n const measure = useCallback(() => {\n const el = containerRef.current;\n if (!el) return;\n store.update(el.scrollLeft, el.clientWidth);\n }, [containerRef, store]);\n\n const scheduleUpdate = useCallback(() => {\n if (rafIdRef.current !== null) return;\n rafIdRef.current = requestAnimationFrame(() => {\n rafIdRef.current = null;\n measure();\n });\n }, [measure]);\n\n // Synchronous initial measurement so children have viewport data on first render.\n // Without this, viewport is null during the first paint and all canvas chunks\n // mount (e.g., 8 per track × 13 tracks = 104 canvases), only to be pruned to\n // ~3 visible chunks after the useEffect measurement fires post-paint.\n useLayoutEffect(() => {\n measure();\n }, [measure]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n // Scroll listener throttled via requestAnimationFrame\n el.addEventListener('scroll', scheduleUpdate, { passive: true });\n\n // ResizeObserver for container width changes\n const resizeObserver = new ResizeObserver(() => {\n scheduleUpdate();\n });\n resizeObserver.observe(el);\n\n return () => {\n el.removeEventListener('scroll', scheduleUpdate);\n resizeObserver.disconnect();\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n store.cancelPendingNotification();\n };\n }, [containerRef, scheduleUpdate, store]);\n\n return <ViewportStoreContext.Provider value={store}>{children}</ViewportStoreContext.Provider>;\n};\n\n/**\n * Full viewport hook — re-renders on every viewport update (after threshold).\n * Use useScrollViewportSelector() instead when you only need derived state.\n */\nexport const useScrollViewport = (): ScrollViewport | null => {\n const store = useContext(ViewportStoreContext);\n return useSyncExternalStore(\n store ? store.subscribe : EMPTY_SUBSCRIBE,\n store ? store.getSnapshot : NULL_SNAPSHOT,\n NULL_SNAPSHOT\n );\n};\n\n/**\n * Selector hook — only re-renders when the selector's return value changes\n * (compared via Object.is). Return primitive values (strings, numbers) for\n * best results, since objects/arrays create new references each call.\n *\n * Example: compute visible chunk key so the component only re-renders when\n * the set of visible chunks actually changes, not on every scroll update.\n */\nexport function useScrollViewportSelector<T>(selector: (viewport: ScrollViewport | null) => T): T {\n const store = useContext(ViewportStoreContext);\n return useSyncExternalStore(\n store ? store.subscribe : EMPTY_SUBSCRIBE,\n () => selector(store ? store.getSnapshot() : null),\n () => selector(null)\n );\n}\n\n/**\n * Returns the indices of canvas chunks that are currently visible (plus overscan buffer).\n * Only triggers a re-render when the set of visible chunks changes, not on every scroll pixel.\n *\n * @param totalWidth Total width in CSS pixels of the content being chunked.\n * @param chunkWidth Width of each chunk in CSS pixels (typically MAX_CANVAS_WIDTH, 1000).\n * @param originX Pixel offset of this content's origin within the global scroll container.\n * Clips not starting at position 0 must provide their left offset so chunk visibility\n * is computed in global viewport coordinates. Defaults to 0 (e.g., TimeScale).\n */\nexport function useVisibleChunkIndices(\n totalWidth: number,\n chunkWidth: number,\n originX: number = 0\n): number[] {\n const visibleChunkKey = useScrollViewportSelector((viewport) => {\n const totalChunks = Math.ceil(totalWidth / chunkWidth);\n const indices: number[] = [];\n\n for (let i = 0; i < totalChunks; i++) {\n const chunkLeft = i * chunkWidth;\n const thisChunkWidth = Math.min(totalWidth - chunkLeft, chunkWidth);\n\n if (viewport) {\n // Convert local chunk coordinates to global viewport space\n const chunkLeftGlobal = originX + chunkLeft;\n const chunkEndGlobal = chunkLeftGlobal + thisChunkWidth;\n if (chunkEndGlobal <= viewport.visibleStart || chunkLeftGlobal >= viewport.visibleEnd) {\n continue;\n }\n }\n\n indices.push(i);\n }\n\n return indices.join(',');\n });\n\n // Memoize on the key string so the returned array is referentially stable\n // between renders — safe to use directly in useLayoutEffect dependency arrays.\n return useMemo(\n () => (visibleChunkKey ? visibleChunkKey.split(',').map(Number) : []),\n [visibleChunkKey]\n );\n}\n","import React, { createContext, useContext, ReactNode } from 'react';\n\nconst ClipViewportOriginContext = createContext<number>(0);\n\ninterface ClipViewportOriginProviderProps {\n originX: number;\n children: ReactNode;\n}\n\n/**\n * Provides the clip's pixel-space origin (left offset) to descendant Channel\n * and SpectrogramChannel components so they can convert local chunk coordinates\n * to global viewport coordinates for virtual scrolling visibility checks.\n *\n * Without this, chunks are compared against the viewport in local (clip-relative)\n * space, which causes them to be culled incorrectly when a clip doesn't start\n * at position 0 on the timeline.\n */\nexport const ClipViewportOriginProvider = ({\n originX,\n children,\n}: ClipViewportOriginProviderProps) => (\n <ClipViewportOriginContext.Provider value={originX}>\n {children}\n </ClipViewportOriginContext.Provider>\n);\n\n/**\n * Returns the clip's pixel-space left offset within the timeline.\n * Defaults to 0 when used outside a ClipViewportOriginProvider (e.g., TimeScale).\n */\nexport const useClipViewportOrigin = (): number => useContext(ClipViewportOriginContext);\n","import { useCallback, useEffect, useRef } from 'react';\n\n/**\n * Manages canvas element refs for chunked virtual-scroll rendering.\n *\n * Provides a callback ref (for `ref={canvasRef}`) that stores canvases\n * by their `data-index` chunk index, and automatically cleans up refs\n * for canvases that have been unmounted by the virtualizer.\n *\n * Used by Channel, TimeScale, and SpectrogramChannel.\n *\n * @returns\n * - `canvasRef` — Callback ref to attach to each `<canvas data-index={i}>` element.\n * - `canvasMapRef` — Stable ref to a `Map<number, HTMLCanvasElement>` for iterating\n * over mounted canvases during draw effects.\n */\nexport function useChunkedCanvasRefs() {\n const canvasMapRef = useRef<Map<number, HTMLCanvasElement>>(new Map());\n\n const canvasRef = useCallback((canvas: HTMLCanvasElement | null) => {\n if (canvas !== null) {\n const idx = parseInt(canvas.dataset.index!, 10);\n canvasMapRef.current.set(idx, canvas);\n }\n }, []);\n\n // Clean up stale refs for unmounted chunks.\n // Intentionally has no dependency array — runs after every render because\n // the virtualizer can unmount canvases at any time, and drawing effects\n // need the map pruned before they iterate it.\n useEffect(() => {\n const map = canvasMapRef.current;\n for (const [idx, canvas] of map.entries()) {\n if (!canvas.isConnected) {\n map.delete(idx);\n }\n }\n });\n\n return { canvasRef, canvasMapRef };\n}\n","import type { Peaks, Bits } from '@waveform-playlist/core';\nimport type { WaveformDrawMode } from '../wfpl-theme';\n\n/**\n * Result of aggregating peaks over a range.\n *\n * Invariants (assumed from valid waveform input):\n * - min and max are normalized to [-1, 1] by dividing by 2^(bits-1)\n * - min <= max (min-of-mins, max-of-maxes — guaranteed by waveform-data library)\n * - Values are finite (derived from integer typed arrays)\n *\n * Construct via aggregatePeaks() — do not create directly.\n */\nexport interface AggregatedPeak {\n min: number;\n max: number;\n}\n\n/**\n * Canvas fillRect parameters for a single waveform bar.\n * width >= 0 and height >= 0 when peak values are in [-1, 1] (guaranteed by\n * waveform-data library normalization).\n */\nexport interface BarRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Aggregates peaks over a range of interleaved min/max pairs.\n * Finds min-of-mins and max-of-maxes, normalized by bit depth.\n *\n * @param data - Interleaved peak data [min0, max0, min1, max1, ...]\n * @param bits - Bit depth (8 or 16)\n * @param startIndex - First peak index (not array index — peak i is at data[i*2], data[i*2+1])\n * @param endIndex - One past the last peak index to include\n * @returns Normalized { min, max } or null if startIndex is out of bounds\n */\nexport function aggregatePeaks(\n data: Peaks,\n bits: Bits,\n startIndex: number,\n endIndex: number\n): AggregatedPeak | null {\n if (startIndex * 2 + 1 >= data.length) {\n return null;\n }\n\n const maxValue = 2 ** (bits - 1);\n let minPeak = data[startIndex * 2] / maxValue;\n let maxPeak = data[startIndex * 2 + 1] / maxValue;\n\n for (let p = startIndex + 1; p < endIndex; p++) {\n if (p * 2 + 1 >= data.length) break;\n const pMin = data[p * 2] / maxValue;\n const pMax = data[p * 2 + 1] / maxValue;\n if (pMin < minPeak) minPeak = pMin;\n if (pMax > maxPeak) maxPeak = pMax;\n }\n\n return { min: minPeak, max: maxPeak };\n}\n\n/**\n * Computes canvas fillRect parameters for a single waveform bar.\n *\n * @param x - Bar x position in canvas coordinates\n * @param barWidth - Width of the bar in pixels\n * @param halfHeight - Half the waveform height (center line y)\n * @param minPeak - Normalized min peak value (negative for below center)\n * @param maxPeak - Normalized max peak value (positive for above center)\n * @param drawMode - 'normal' draws the peak region, 'inverted' draws the non-peak regions\n * @returns Array of BarRect — 1 rect for 'normal', 2 rects for 'inverted'\n */\nexport function calculateBarRects(\n x: number,\n barWidth: number,\n halfHeight: number,\n minPeak: number,\n maxPeak: number,\n drawMode: WaveformDrawMode\n): BarRect[] {\n const min = Math.abs(minPeak * halfHeight);\n const max = Math.abs(maxPeak * halfHeight);\n\n if (drawMode === 'normal') {\n return [{ x, y: halfHeight - max, width: barWidth, height: max + min }];\n }\n\n // Inverted: draw areas WITHOUT audio (top gap + bottom gap)\n return [\n { x, y: 0, width: barWidth, height: halfHeight - max },\n { x, y: halfHeight + min, width: barWidth, height: halfHeight - min },\n ];\n}\n\n/**\n * Computes the first bar position (in global pixel coordinates) that could\n * affect a given canvas chunk.\n *\n * A bar at position X extends from X to X+barWidth-1, so we need bars where\n * barStart + barWidth > canvasStartGlobal.\n *\n * @param canvasStartGlobal - Global pixel offset of the canvas chunk\n * @param barWidth - Width of each bar in pixels\n * @param step - Bar stride (barWidth + barGap)\n * @returns The first bar's global position (always >= 0 when step >= barWidth; caller clamps to 0)\n */\nexport function calculateFirstBarPosition(\n canvasStartGlobal: number,\n barWidth: number,\n step: number\n): number {\n return Math.floor((canvasStartGlobal - barWidth + step) / step) * step;\n}\n","import React from 'react';\n\nconst errorContainerStyle: React.CSSProperties = {\n padding: '16px',\n background: '#1a1a2e',\n color: '#e0e0e0',\n border: '1px solid #d08070',\n borderRadius: '4px',\n fontFamily: 'monospace',\n fontSize: '13px',\n minHeight: '60px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n};\n\nexport interface PlaylistErrorBoundaryProps {\n children: React.ReactNode;\n /** Custom fallback UI to show when an error occurs */\n fallback?: React.ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * Error Boundary for waveform playlist components.\n *\n * Catches render errors in child components (canvas failures, audio\n * processing errors) and displays a fallback UI instead of crashing\n * the entire application.\n *\n * @example\n * ```tsx\n * <PlaylistErrorBoundary>\n * <WaveformPlaylistProvider tracks={tracks}>\n * <Waveform />\n * </WaveformPlaylistProvider>\n * </PlaylistErrorBoundary>\n * ```\n */\nexport class PlaylistErrorBoundary extends React.Component<\n PlaylistErrorBoundaryProps,\n ErrorBoundaryState\n> {\n constructor(props: PlaylistErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n console.error('[waveform-playlist] Render error:', error, errorInfo.componentStack);\n }\n\n render(): React.ReactNode {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n return (\n <div style={errorContainerStyle}>\n Waveform playlist encountered an error. Check console for details.\n </div>\n );\n }\n return this.props.children;\n }\n}\n","import React, { FunctionComponent, ReactNode } from 'react';\nimport styled from 'styled-components';\nimport { useDraggable } from '@dnd-kit/react';\nimport { ClipHeader } from './ClipHeader';\nimport { ClipBoundary } from './ClipBoundary';\nimport { FadeOverlay } from './FadeOverlay';\nimport { clipPixelWidth, type Fade } from '@waveform-playlist/core';\nimport { ClipViewportOriginProvider } from '../contexts/ClipViewportOrigin';\n\ninterface ClipContainerProps {\n readonly $left?: number; // Horizontal position in pixels (optional for overlay)\n readonly $width?: number; // Width in pixels (optional for overlay)\n readonly $isOverlay?: boolean; // Whether this is rendering in overlay mode\n}\n\nconst ClipContainer = styled.div.attrs<ClipContainerProps>((props) => ({\n style: props.$isOverlay\n ? {}\n : {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<ClipContainerProps>`\n position: ${(props) => (props.$isOverlay ? 'relative' : 'absolute')};\n top: 0;\n height: ${(props) => (props.$isOverlay ? 'auto' : '100%')};\n width: ${(props) => (props.$isOverlay ? `${props.$width}px` : 'auto')};\n display: flex;\n flex-direction: column;\n z-index: 10; /* Above progress overlay (z-index: 2) but below controls/playhead */\n pointer-events: none; /* Let clicks pass through to ClickOverlay for playhead positioning */\n`;\n\ninterface ChannelsWrapperProps {\n readonly $isOverlay?: boolean;\n}\n\nconst ChannelsWrapper = styled.div<ChannelsWrapperProps>`\n flex: 1;\n position: relative;\n overflow: ${(props) => (props.$isOverlay ? 'visible' : 'hidden')};\n`;\n\nexport interface ClipProps {\n className?: string;\n children?: ReactNode;\n clipId: string; // Unique clip ID\n trackIndex: number; // Track index (for drag operations)\n clipIndex: number; // Clip index within track (for drag operations)\n trackName: string; // Track name (shown in header)\n startSample: number; // Start position in samples\n durationSamples: number; // Duration in samples\n samplesPerPixel: number;\n // Optional header (for multi-clip editing with drag-to-move)\n showHeader?: boolean;\n disableHeaderDrag?: boolean; // Disable drag on header (for presentation-only rendering)\n isOverlay?: boolean; // Rendering in overlay mode (disables absolute positioning)\n // Track selection\n isSelected?: boolean; // Whether the track is selected\n onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void; // Called when clip is pressed (for track selection - fires before drag)\n trackId?: string; // Track ID for identifying which track this clip belongs to\n // Fade configuration\n fadeIn?: Fade; // Fade in effect\n fadeOut?: Fade; // Fade out effect\n sampleRate?: number; // Sample rate for converting fade duration to pixels\n showFades?: boolean; // Show fade in/out overlays\n // Mobile optimization\n touchOptimized?: boolean; // Enable larger touch targets for mobile devices\n}\n\n/**\n * Clip component for rendering individual audio clips within a track\n *\n * Each clip is positioned based on its startSample and has a width based on its durationSamples.\n * This allows multiple clips to be arranged on a single track with gaps or overlaps.\n *\n * Includes a draggable ClipHeader at the top for repositioning clips on the timeline.\n */\nexport const Clip: FunctionComponent<ClipProps> = ({\n children,\n className,\n clipId,\n trackIndex,\n clipIndex,\n trackName,\n startSample,\n durationSamples,\n samplesPerPixel,\n showHeader = false,\n disableHeaderDrag = false,\n isOverlay = false,\n isSelected = false,\n onMouseDown,\n trackId,\n fadeIn,\n fadeOut,\n sampleRate = 44100,\n showFades = false,\n touchOptimized = false,\n}) => {\n // Calculate horizontal position based on start sample\n // Use Math.floor to always snap to pixel boundaries\n const left = Math.floor(startSample / samplesPerPixel);\n\n // Calculate width using shared helper (must match ChannelWithProgress)\n const width = clipPixelWidth(startSample, durationSamples, samplesPerPixel);\n\n // Use draggable only if header is shown and drag is enabled\n const enableDrag = showHeader && !disableHeaderDrag && !isOverlay;\n\n // Main clip draggable (for moving entire clip)\n const draggableId = `clip-${trackIndex}-${clipIndex}`;\n const {\n ref: clipRef,\n handleRef,\n isDragSource,\n } = useDraggable({\n id: draggableId,\n data: { clipId, trackIndex, clipIndex, startSample, durationSamples },\n disabled: !enableDrag,\n });\n\n // Left boundary draggable (for trimming start)\n // feedback: 'none' disables the Feedback plugin for this draggable — trim visual feedback\n // comes from React state updates resizing the clip, not CSS translate.\n const leftBoundaryId = `clip-boundary-left-${trackIndex}-${clipIndex}`;\n const { ref: leftBoundaryRef, isDragSource: isLeftBoundaryDragging } = useDraggable({\n id: leftBoundaryId,\n data: { clipId, trackIndex, clipIndex, boundary: 'left', startSample, durationSamples },\n disabled: !enableDrag,\n feedback: 'none',\n });\n\n // Right boundary draggable (for trimming end)\n const rightBoundaryId = `clip-boundary-right-${trackIndex}-${clipIndex}`;\n const { ref: rightBoundaryRef, isDragSource: isRightBoundaryDragging } = useDraggable({\n id: rightBoundaryId,\n data: { clipId, trackIndex, clipIndex, boundary: 'right', startSample, durationSamples },\n disabled: !enableDrag,\n feedback: 'none',\n });\n\n // Elevate z-index during drag (below controls z-index: 999, above other clips)\n const style = isDragSource ? { zIndex: 100 } : undefined;\n\n return (\n <ClipContainer\n ref={clipRef}\n style={style}\n className={className}\n $left={left}\n $width={width}\n $isOverlay={isOverlay}\n data-clip-container=\"true\"\n data-track-id={trackId}\n onMouseDown={onMouseDown}\n // Always set tabIndex=-1 so @dnd-kit doesn't add tabindex=\"0\".\n // Without this, the browser auto-scrolls overflow containers to show\n // the focused clip element when many clips mount simultaneously.\n // Drag still works — the handle (ClipHeader) receives keyboard focus.\n tabIndex={-1}\n >\n {showHeader && (\n <ClipHeader\n clipId={clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n trackName={trackName}\n isSelected={isSelected}\n disableDrag={disableHeaderDrag}\n dragHandleProps={enableDrag ? { handleRef } : undefined}\n />\n )}\n <ClipViewportOriginProvider originX={left}>\n <ChannelsWrapper $isOverlay={isOverlay}>\n {children}\n {/* Fade overlays */}\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={width - Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)}\n width={Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)}\n type=\"fadeOut\"\n curveType={fadeOut.type}\n />\n )}\n </ChannelsWrapper>\n </ClipViewportOriginProvider>\n {/* Clip boundaries - outside ChannelsWrapper to avoid overflow:hidden clipping */}\n {showHeader && !disableHeaderDrag && !isOverlay && (\n <>\n <ClipBoundary\n clipId={clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n edge=\"left\"\n touchOptimized={touchOptimized}\n dragHandleProps={{\n ref: leftBoundaryRef,\n isDragging: isLeftBoundaryDragging,\n }}\n />\n <ClipBoundary\n clipId={clipId}\n trackIndex={trackIndex}\n clipIndex={clipIndex}\n edge=\"right\"\n touchOptimized={touchOptimized}\n dragHandleProps={{\n ref: rightBoundaryRef,\n isDragging: isRightBoundaryDragging,\n }}\n />\n </>\n )}\n </ClipContainer>\n );\n};\n","import React, { FunctionComponent } from 'react';\nimport styled from 'styled-components';\n\nexport const CLIP_HEADER_HEIGHT = 22; // Height of the clip header in pixels\n\ninterface HeaderContainerProps {\n readonly $interactive?: boolean; // Whether it's draggable or just presentational\n readonly $isSelected?: boolean; // Whether the track is selected\n}\n\nconst HeaderContainer = styled.div<HeaderContainerProps>`\n position: relative;\n height: ${CLIP_HEADER_HEIGHT}px;\n background: ${(props) =>\n props.$isSelected\n ? props.theme.selectedClipHeaderBackgroundColor\n : props.theme.clipHeaderBackgroundColor};\n border-bottom: 1px solid ${(props) => props.theme.clipHeaderBorderColor};\n display: flex;\n align-items: center;\n padding: 0 8px;\n cursor: ${(props) => (props.$interactive ? 'grab' : 'default')};\n user-select: none;\n z-index: 110;\n flex-shrink: 0;\n pointer-events: auto; /* Re-enable pointer events (parent ClipContainer has pointer-events: none) */\n touch-action: ${(props) =>\n props.$interactive ? 'none' : 'auto'}; /* Prevent browser scroll during drag on touch devices */\n\n ${(props) =>\n props.$interactive &&\n `\n &:hover {\n background: ${props.theme.clipHeaderBackgroundColor}dd;\n }\n\n &:active {\n cursor: grabbing;\n }\n `}\n`;\n\nconst TrackName = styled.span`\n font-size: 11px;\n font-weight: 600;\n font-family: ${(props) => props.theme.clipHeaderFontFamily};\n color: ${(props) => props.theme.clipHeaderTextColor};\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n`;\n\n// Presentational-only header (no drag behavior)\nexport interface ClipHeaderPresentationalProps {\n trackName: string;\n isSelected?: boolean; // Whether the track is selected\n}\n\nexport const ClipHeaderPresentational: FunctionComponent<ClipHeaderPresentationalProps> = ({\n trackName,\n isSelected = false,\n}) => {\n return (\n <HeaderContainer $interactive={false} $isSelected={isSelected}>\n <TrackName title={trackName}>{trackName}</TrackName>\n </HeaderContainer>\n );\n};\n\nexport interface DragHandleProps {\n handleRef: (element: Element | null) => void;\n}\n\nexport interface ClipHeaderProps {\n clipId: string;\n trackIndex: number;\n clipIndex: number;\n trackName: string;\n isSelected?: boolean; // Whether the track is selected\n disableDrag?: boolean; // Disable drag behavior (for presentation-only rendering in overlays)\n dragHandleProps?: DragHandleProps; // Props for drag handle functionality\n}\n\n/**\n * ClipHeader component - Draggable title bar for audio clips\n *\n * Renders at the top of each clip (above all channels).\n * Drag the header to move the clip along the timeline.\n * Shows the track name (not clip-specific info).\n *\n * Theme colors (from useTheme):\n * - clipHeaderBackgroundColor / selectedClipHeaderBackgroundColor\n * - clipHeaderBorderColor\n * - clipHeaderTextColor\n */\nexport const ClipHeader: FunctionComponent<ClipHeaderProps> = ({\n clipId,\n trackIndex: _trackIndex,\n clipIndex: _clipIndex,\n trackName,\n isSelected = false,\n disableDrag = false,\n dragHandleProps,\n}) => {\n // Use purely presentational version when drag is disabled or no drag handle props\n if (disableDrag || !dragHandleProps) {\n return <ClipHeaderPresentational trackName={trackName} isSelected={isSelected} />;\n }\n\n const { handleRef } = dragHandleProps;\n\n return (\n <HeaderContainer\n ref={handleRef}\n data-clip-id={clipId}\n $interactive={true}\n $isSelected={isSelected}\n >\n <TrackName title={trackName}>{trackName}</TrackName>\n </HeaderContainer>\n );\n};\n","import React, { FunctionComponent } from 'react';\nimport styled from 'styled-components';\n\nexport const CLIP_BOUNDARY_WIDTH = 8; // Width of the draggable boundary in pixels\nexport const CLIP_BOUNDARY_WIDTH_TOUCH = 24; // Larger touch target for mobile (minimum 44px recommended, but 24px works well for trim handles)\n\ntype BoundaryEdge = 'left' | 'right';\n\ninterface BoundaryContainerProps {\n readonly $edge: BoundaryEdge;\n readonly $isDragging?: boolean;\n readonly $isHovered?: boolean;\n readonly $touchOptimized?: boolean;\n}\n\nconst BoundaryContainer = styled.div<BoundaryContainerProps>`\n position: absolute;\n ${(props) => (props.$edge === 'left' ? 'left: 0;' : 'right: 0;')}\n top: 0;\n bottom: 0;\n width: ${(props) => (props.$touchOptimized ? CLIP_BOUNDARY_WIDTH_TOUCH : CLIP_BOUNDARY_WIDTH)}px;\n cursor: col-resize;\n user-select: none;\n z-index: 105; /* Above waveform, below header */\n pointer-events: auto; /* Re-enable pointer events (parent ClipContainer has pointer-events: none) */\n touch-action: none; /* Prevent browser scroll during drag on touch devices */\n\n /* Invisible by default, visible on hover */\n background: ${(props) =>\n props.$isDragging\n ? 'rgba(255, 255, 255, 0.4)'\n : props.$isHovered\n ? 'rgba(255, 255, 255, 0.2)'\n : 'transparent'};\n\n ${(props) =>\n props.$edge === 'left'\n ? `border-left: 2px solid ${\n props.$isDragging\n ? 'rgba(255, 255, 255, 0.8)'\n : props.$isHovered\n ? 'rgba(255, 255, 255, 0.5)'\n : 'transparent'\n };`\n : `border-right: 2px solid ${\n props.$isDragging\n ? 'rgba(255, 255, 255, 0.8)'\n : props.$isHovered\n ? 'rgba(255, 255, 255, 0.5)'\n : 'transparent'\n };`}\n\n transition: background 0.15s ease, border-color 0.15s ease;\n\n &:hover {\n background: rgba(255, 255, 255, 0.2);\n ${(props) =>\n props.$edge === 'left'\n ? 'border-left: 2px solid rgba(255, 255, 255, 0.5);'\n : 'border-right: 2px solid rgba(255, 255, 255, 0.5);'}\n }\n\n &:active {\n background: rgba(255, 255, 255, 0.4);\n ${(props) =>\n props.$edge === 'left'\n ? 'border-left: 2px solid rgba(255, 255, 255, 0.8);'\n : 'border-right: 2px solid rgba(255, 255, 255, 0.8);'}\n }\n`;\n\ninterface BoundaryDragHandleProps {\n ref: (element: Element | null) => void;\n isDragging?: boolean;\n}\n\nexport interface ClipBoundaryProps {\n clipId: string;\n trackIndex: number;\n clipIndex: number;\n edge: BoundaryEdge;\n dragHandleProps?: BoundaryDragHandleProps;\n /**\n * Enable larger touch targets for mobile devices.\n * When true, boundary width increases from 8px to 24px for easier touch targeting.\n */\n touchOptimized?: boolean;\n}\n\n/**\n * ClipBoundary component - Draggable edge for trimming clips\n *\n * Renders at the left or right edge of a clip.\n * Drag to trim the clip (adjust offset and duration).\n * Supports bidirectional trimming (trim in and out).\n *\n * On mobile (touchOptimized=true), boundaries are wider for easier targeting.\n */\nexport const ClipBoundary: FunctionComponent<ClipBoundaryProps> = ({\n clipId,\n trackIndex: _trackIndex,\n clipIndex: _clipIndex,\n edge,\n dragHandleProps,\n touchOptimized = false,\n}) => {\n const [isHovered, setIsHovered] = React.useState(false);\n\n if (!dragHandleProps) {\n // No drag handle props provided, render non-interactive boundary\n return null;\n }\n\n const { ref: boundaryRef, isDragging } = dragHandleProps;\n\n return (\n <BoundaryContainer\n ref={boundaryRef}\n data-clip-id={clipId}\n data-boundary-edge={edge}\n $edge={edge}\n $isDragging={isDragging}\n $isHovered={isHovered}\n $touchOptimized={touchOptimized}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n />\n );\n};\n","import React, { FunctionComponent } from 'react';\nimport styled, { useTheme } from 'styled-components';\nimport type { FadeType } from '@waveform-playlist/core';\nimport type { WaveformPlaylistTheme } from '../wfpl-theme';\n\ninterface FadeContainerProps {\n readonly $left: number;\n readonly $width: number;\n readonly $type: 'fadeIn' | 'fadeOut';\n}\n\n// Use .attrs() for left/width to avoid generating new CSS classes on every render\nconst FadeContainer = styled.div.attrs<FadeContainerProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<FadeContainerProps>`\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n z-index: 50;\n`;\n\ninterface FadeSvgProps {\n readonly $type: 'fadeIn' | 'fadeOut';\n}\n\nconst FadeSvg = styled.svg<FadeSvgProps>`\n width: 100%;\n height: 100%;\n display: block;\n /* Flip horizontally for fadeOut - makes it mirror of fadeIn */\n transform: ${(props) => (props.$type === 'fadeOut' ? 'scaleX(-1)' : 'none')};\n`;\n\nexport interface FadeOverlayProps {\n /** Position in pixels from the start of the clip */\n left: number;\n /** Width of the fade region in pixels */\n width: number;\n /** Type of fade: fadeIn or fadeOut */\n type: 'fadeIn' | 'fadeOut';\n /** Fade curve type */\n curveType?: FadeType;\n /** Custom fill color (defaults to theme.fadeOverlayColor) */\n color?: string;\n}\n\n/**\n * Generates an SVG path for a fade in curve\n * Always generates fadeIn shape - fadeOut is achieved by CSS transform scaleX(-1)\n *\n * The curve shows: more overlay at start (audio quiet), less overlay at end (audio full)\n */\nfunction generateFadePath(\n width: number,\n height: number,\n curveType: FadeType = 'logarithmic'\n): string {\n const points: string[] = [];\n const numPoints = Math.max(20, Math.min(width, 100)); // More points for smoother curves\n\n for (let i = 0; i <= numPoints; i++) {\n const x = (i / numPoints) * width;\n const progress = i / numPoints; // 0 to 1\n\n // Apply curve transformation based on type\n let curvedProgress: number;\n switch (curveType) {\n case 'linear':\n curvedProgress = progress;\n break;\n case 'exponential':\n curvedProgress = progress * progress;\n break;\n case 'sCurve':\n // S-curve using sine\n curvedProgress = (1 - Math.cos(progress * Math.PI)) / 2;\n break;\n case 'logarithmic':\n default:\n // Logarithmic curve (more natural for audio)\n curvedProgress = Math.log10(1 + progress * 9) / Math.log10(10);\n break;\n }\n\n // fadeIn: starts covered (y near 0), ends uncovered (y near height)\n // Y=0 is top of SVG, Y=height is bottom\n // We draw the curve edge, then fill above it\n const y = (1 - curvedProgress) * height;\n points.push(`${x},${y}`);\n }\n\n // Path: start at bottom-left, draw curve, go to top-right, top-left, close\n return `M 0,${height} L ${points.join(' L ')} L ${width},0 L 0,0 Z`;\n}\n\n/**\n * FadeOverlay component - Visual indicator for fade in/out regions on clips\n *\n * Renders a semi-transparent overlay with a curved shape indicating\n * the fade envelope. The shape follows the selected fade curve type.\n */\nexport const FadeOverlay: FunctionComponent<FadeOverlayProps> = ({\n left,\n width,\n type,\n curveType = 'logarithmic',\n color,\n}) => {\n const theme = useTheme() as WaveformPlaylistTheme;\n\n // Don't render if width is too small\n if (width < 1) return null;\n\n // Use color prop, then theme color, then fallback\n const fillColor = color || theme?.fadeOverlayColor || 'rgba(0, 0, 0, 0.4)';\n\n return (\n <FadeContainer $left={left} $width={width} $type={type}>\n <FadeSvg $type={type} viewBox={`0 0 ${width} 100`} preserveAspectRatio=\"none\">\n <path d={generateFadePath(width, 100, curveType)} fill={fillColor} />\n </FadeSvg>\n </FadeContainer>\n );\n};\n","import React from 'react';\nimport styled from 'styled-components';\nimport { BaseSlider, BaseLabel } from '../styled/index';\n\nconst VolumeContainer = styled.div`\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n`;\n\nconst VolumeLabel = styled(BaseLabel)`\n margin: 0;\n white-space: nowrap;\n`;\n\nconst VolumeSlider = styled(BaseSlider)`\n width: 120px;\n`;\n\nexport interface MasterVolumeControlProps {\n volume: number; // 0-1.0 (linear gain, consistent with Web Audio API)\n onChange: (volume: number) => void;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * Master volume control slider component\n * Accepts volume as 0-1.0 range (linear gain) and displays as percentage\n */\nexport const MasterVolumeControl: React.FC<MasterVolumeControlProps> = ({\n volume,\n onChange,\n disabled = false,\n className,\n}) => {\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n // Convert percentage (0-100) to linear gain (0-1.0)\n onChange(parseFloat(e.target.value) / 100);\n };\n\n return (\n <VolumeContainer className={className}>\n <VolumeLabel htmlFor=\"master-gain\">Master Volume</VolumeLabel>\n <VolumeSlider\n min=\"0\"\n max=\"100\"\n value={volume * 100}\n onChange={handleChange}\n disabled={disabled}\n id=\"master-gain\"\n />\n </VolumeContainer>\n );\n};\n","import React, { FunctionComponent, useEffect, useMemo } from 'react';\nimport styled from 'styled-components';\nimport type { MidiNoteData } from '@waveform-playlist/core';\nimport { MAX_CANVAS_WIDTH } from '@waveform-playlist/core';\nimport { useVisibleChunkIndices } from '../contexts/ScrollViewport';\nimport { useClipViewportOrigin } from '../contexts/ClipViewportOrigin';\nimport { useChunkedCanvasRefs } from '../hooks/useChunkedCanvasRefs';\n\ninterface CanvasProps {\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $left: number;\n}\n\nconst NoteCanvas = styled.canvas.attrs<CanvasProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n left: `${props.$left}px`,\n },\n}))<CanvasProps>`\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n image-rendering: crisp-edges;\n`;\n\ninterface WrapperProps {\n readonly $index: number;\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $backgroundColor: string;\n}\n\nconst Wrapper = styled.div.attrs<WrapperProps>((props) => ({\n style: {\n top: `${props.$waveHeight * props.$index}px`,\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n },\n}))<WrapperProps>`\n position: absolute;\n background: ${(props) => props.$backgroundColor};\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\nexport interface PianoRollChannelProps {\n index: number;\n midiNotes: MidiNoteData[];\n length: number;\n waveHeight: number;\n devicePixelRatio: number;\n samplesPerPixel: number;\n sampleRate: number;\n clipOffsetSeconds: number;\n noteColor?: string;\n selectedNoteColor?: string;\n isSelected?: boolean;\n transparentBackground?: boolean;\n backgroundColor?: string;\n}\n\nexport const PianoRollChannel: FunctionComponent<PianoRollChannelProps> = ({\n index,\n midiNotes,\n length,\n waveHeight,\n devicePixelRatio,\n samplesPerPixel,\n sampleRate,\n clipOffsetSeconds,\n noteColor = '#2a7070',\n selectedNoteColor = '#3d9e9e',\n isSelected = false,\n transparentBackground = false,\n backgroundColor = '#1a1a2e',\n}) => {\n const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();\n const clipOriginX = useClipViewportOrigin();\n const visibleChunkIndices = useVisibleChunkIndices(length, MAX_CANVAS_WIDTH, clipOriginX);\n\n // Compute note pitch range for vertical mapping\n const { minMidi, maxMidi } = useMemo(() => {\n if (midiNotes.length === 0) return { minMidi: 0, maxMidi: 127 };\n let min = 127,\n max = 0;\n for (const note of midiNotes) {\n if (note.midi < min) min = note.midi;\n if (note.midi > max) max = note.midi;\n }\n // Add 1-note padding on each side for visual breathing room\n return { minMidi: Math.max(0, min - 1), maxMidi: Math.min(127, max + 1) };\n }, [midiNotes]);\n\n const color = isSelected ? selectedNoteColor : noteColor;\n\n // useEffect (not useLayoutEffect) so the browser paints the track layout\n // (controls + empty canvas containers) before heavy canvas drawing starts.\n useEffect(() => {\n const noteRange = maxMidi - minMidi + 1;\n const noteHeight = Math.max(2, waveHeight / noteRange);\n const pixelsPerSecond = sampleRate / samplesPerPixel;\n\n for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {\n const chunkPixelStart = canvasIdx * MAX_CANVAS_WIDTH;\n const canvasWidth = canvas.width / devicePixelRatio;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n // Time range this chunk covers (relative to clip start)\n const chunkStartTime = (chunkPixelStart * samplesPerPixel) / sampleRate;\n const chunkEndTime = ((chunkPixelStart + canvasWidth) * samplesPerPixel) / sampleRate;\n\n for (const note of midiNotes) {\n // Note times are relative to clip start; clipOffsetSeconds shifts them\n const noteStart = note.time - clipOffsetSeconds;\n const noteEnd = noteStart + note.duration;\n\n // Skip notes outside this chunk's time range\n if (noteEnd <= chunkStartTime || noteStart >= chunkEndTime) continue;\n\n const x = noteStart * pixelsPerSecond - chunkPixelStart;\n const w = Math.max(2, note.duration * pixelsPerSecond);\n // MIDI note 127 is at top (y=0), note 0 at bottom\n const y = ((maxMidi - note.midi) / noteRange) * waveHeight;\n\n // Velocity maps to opacity: 0.3 (pp) → 1.0 (ff)\n const alpha = 0.3 + note.velocity * 0.7;\n ctx.fillStyle = color;\n ctx.globalAlpha = alpha;\n\n // Rounded rectangle (1px radius)\n const r = 1;\n ctx.beginPath();\n ctx.roundRect(x, y, w, noteHeight, r);\n ctx.fill();\n }\n\n ctx.globalAlpha = 1;\n }\n }, [\n canvasMapRef,\n midiNotes,\n waveHeight,\n devicePixelRatio,\n samplesPerPixel,\n sampleRate,\n clipOffsetSeconds,\n color,\n minMidi,\n maxMidi,\n length,\n visibleChunkIndices,\n index,\n ]);\n\n const canvases = visibleChunkIndices.map((i) => {\n const chunkLeft = i * MAX_CANVAS_WIDTH;\n const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);\n\n return (\n <NoteCanvas\n key={`${length}-${i}`}\n $cssWidth={currentWidth}\n $left={chunkLeft}\n width={currentWidth * devicePixelRatio}\n height={waveHeight * devicePixelRatio}\n $waveHeight={waveHeight}\n data-index={i}\n ref={canvasRef}\n />\n );\n });\n\n const bgColor = transparentBackground ? 'transparent' : backgroundColor;\n\n return (\n <Wrapper $index={index} $cssWidth={length} $waveHeight={waveHeight} $backgroundColor={bgColor}>\n {canvases}\n </Wrapper>\n );\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\n\ninterface PlayheadLineProps {\n readonly $position: number;\n readonly $color: string;\n}\n\nconst PlayheadLine = styled.div.attrs<PlayheadLineProps>((props) => ({\n style: {\n transform: `translate3d(${props.$position}px, 0, 0)`,\n },\n}))<PlayheadLineProps>`\n position: absolute;\n top: 0;\n left: 0;\n width: 2px;\n background: ${(props) => props.$color};\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\n/**\n * Props passed to the default playhead component or custom render function.\n */\nexport interface PlayheadProps {\n /** Position in pixels from left edge (only valid when not playing) */\n position: number;\n /** Playhead color (default: #ff0000) */\n color?: string;\n /** Whether audio is currently playing */\n isPlaying: boolean;\n /** Ref to current time in seconds - use for smooth animation during playback */\n currentTimeRef: React.RefObject<number>;\n /** Audio context start time when playback began. Fallback when getPlaybackTime is not provided. */\n playbackStartTimeRef: React.RefObject<number>;\n /** Audio position when playback started. Fallback when getPlaybackTime is not provided. */\n audioStartPositionRef: React.RefObject<number>;\n /** Samples per pixel - for converting time to pixels */\n samplesPerPixel: number;\n /** Sample rate - for converting time to pixels */\n sampleRate: number;\n /** Controls offset in pixels (deprecated, always 0 — controls are now outside scroll area) */\n controlsOffset?: number;\n /** Function to get current audio context time - required for smooth animation */\n getAudioContextTime?: () => number;\n /** Returns current playback time (auto-wraps at loop boundaries). Preferred over manual elapsed calculation. */\n getPlaybackTime?: () => number;\n}\n\n/**\n * Type for custom playhead render functions.\n * Receives position, color, and animation refs for smooth 60fps animation.\n * Custom playheads should use requestAnimationFrame with the refs during playback.\n */\nexport type RenderPlayheadFunction = (props: PlayheadProps) => React.ReactNode;\n\n/**\n * Default playhead component - a simple vertical line.\n * Uses GPU-accelerated transform for smooth animation.\n */\nexport const Playhead: React.FC<PlayheadProps> = ({ position, color = '#ff0000' }) => {\n return <PlayheadLine $position={position} $color={color} />;\n};\n\n// === Custom Playhead Variants ===\n\nconst PlayheadWithMarkerContainer = styled.div<{ $color: string }>`\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\nconst MarkerTriangle = styled.div<{ $color: string }>`\n position: absolute;\n top: -10px;\n left: -6px;\n width: 0;\n height: 0;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 10px solid ${(props) => props.$color};\n`;\n\nconst MarkerLine = styled.div<{ $color: string }>`\n position: absolute;\n top: 0;\n left: 0;\n width: 2px;\n height: 100%;\n background: ${(props) => props.$color};\n`;\n\n/**\n * Playhead with a triangle marker at the top.\n * Provides better visual indication of the current position.\n * Uses requestAnimationFrame for smooth 60fps animation during playback.\n */\nexport const PlayheadWithMarker: React.FC<PlayheadProps> = ({\n color = '#ff0000',\n isPlaying,\n currentTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n samplesPerPixel,\n sampleRate,\n controlsOffset = 0,\n getAudioContextTime,\n getPlaybackTime,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n\n useEffect(() => {\n const updatePosition = () => {\n if (containerRef.current) {\n let time: number;\n if (isPlaying) {\n if (getPlaybackTime) {\n time = getPlaybackTime();\n } else if (getAudioContextTime) {\n const elapsed = getAudioContextTime() - (playbackStartTimeRef.current ?? 0);\n time = (audioStartPositionRef.current ?? 0) + elapsed;\n } else {\n time = currentTimeRef.current ?? 0;\n }\n } else {\n time = currentTimeRef.current ?? 0;\n }\n const pos = (time * sampleRate) / samplesPerPixel + controlsOffset;\n containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;\n }\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updatePosition);\n }\n };\n\n if (isPlaying) {\n animationFrameRef.current = requestAnimationFrame(updatePosition);\n } else {\n updatePosition();\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 controlsOffset,\n currentTimeRef,\n playbackStartTimeRef,\n audioStartPositionRef,\n getAudioContextTime,\n getPlaybackTime,\n ]);\n\n // Update position when stopped (for seeks)\n useEffect(() => {\n if (!isPlaying && containerRef.current) {\n const time = currentTimeRef.current ?? 0;\n const pos = (time * sampleRate) / samplesPerPixel + controlsOffset;\n containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;\n }\n });\n\n return (\n <PlayheadWithMarkerContainer ref={containerRef} $color={color}>\n <MarkerTriangle $color={color} />\n <MarkerLine $color={color} />\n </PlayheadWithMarkerContainer>\n );\n};\n","import styled, { DefaultTheme, withTheme } from 'styled-components';\nimport React, { FunctionComponent, useRef, useCallback } from 'react';\nimport { ScrollViewportProvider } from '../contexts/ScrollViewport';\n\n/**\n * Outer wrapper: flex layout separating controls column from scroll area.\n * overflow-y: hidden prevents vertical scrollbar on the wrapper itself.\n */\nconst Wrapper = styled.div`\n display: flex;\n overflow-y: hidden;\n position: relative;\n`;\n\ninterface ControlsColumnProps {\n readonly $width: number;\n}\n\nconst ControlsColumn = styled.div.attrs<ControlsColumnProps>((props) => ({\n style: { width: `${props.$width}px` },\n}))<ControlsColumnProps>`\n flex-shrink: 0;\n overflow: hidden;\n`;\n\ninterface TimescaleGapProps {\n readonly $height: number;\n}\n\nconst TimescaleGap = styled.div.attrs<TimescaleGapProps>((props) => ({\n style: { height: `${props.$height}px` },\n}))<TimescaleGapProps>``;\n\nconst ScrollArea = styled.div`\n overflow-x: auto;\n overflow-y: hidden;\n overflow-anchor: none;\n flex: 1;\n position: relative;\n`;\n\ninterface ScrollContainerInnerProps {\n readonly $backgroundColor?: string;\n readonly $width?: number;\n}\n\n// Use .attrs() for width to avoid generating new CSS classes on every render\nconst ScrollContainerInner = styled.div.attrs<ScrollContainerInnerProps>((props) => ({\n style: props.$width !== undefined ? { width: `${props.$width}px` } : {},\n}))<ScrollContainerInnerProps>`\n position: relative;\n background: ${(props) => props.$backgroundColor || 'transparent'};\n`;\n\ninterface TimescaleWrapperProps {\n readonly $width?: number;\n readonly $backgroundColor?: string;\n}\n\n// Use .attrs() for width to avoid generating new CSS classes on every render\nconst TimescaleWrapper = styled.div.attrs<TimescaleWrapperProps>((props) => ({\n style: props.$width ? { minWidth: `${props.$width}px` } : {},\n}))<TimescaleWrapperProps>`\n background: ${(props) => props.$backgroundColor || 'white'};\n width: 100%;\n position: relative;\n overflow: hidden; /* Constrain loop region to timescale area */\n`;\n\ninterface TracksContainerProps {\n readonly $width?: number;\n readonly $backgroundColor?: string;\n}\n\n// Use .attrs() for width to avoid generating new CSS classes on every render\nconst TracksContainer = styled.div.attrs<TracksContainerProps>((props) => ({\n style: props.$width !== undefined ? { minWidth: `${props.$width}px` } : {},\n}))<TracksContainerProps>`\n position: relative;\n background: ${(props) => props.$backgroundColor || 'transparent'};\n width: 100%;\n`;\n\ninterface ClickOverlayProps {\n readonly $isSelecting?: boolean;\n}\n\nconst ClickOverlay = styled.div<ClickOverlayProps>`\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n cursor: crosshair;\n /* When selecting, raise z-index above clip boundaries (z-index: 105) to prevent interference */\n z-index: ${(props) => (props.$isSelecting ? 110 : 1)};\n`;\n\nexport interface PlaylistProps {\n readonly theme: DefaultTheme;\n readonly children?: React.ReactNode;\n readonly backgroundColor?: string;\n readonly timescaleBackgroundColor?: string;\n readonly timescale?: React.ReactElement;\n readonly timescaleWidth?: number;\n readonly tracksWidth?: number;\n readonly controlsWidth?: number;\n readonly onTracksClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n readonly onTracksMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;\n readonly onTracksMouseMove?: (e: React.MouseEvent<HTMLDivElement>) => void;\n readonly onTracksMouseUp?: (e: React.MouseEvent<HTMLDivElement>) => void;\n readonly scrollContainerRef?: (el: HTMLDivElement | null) => void;\n /** When true, selection is in progress - raises z-index to prevent clip boundary interference */\n readonly isSelecting?: boolean;\n /** Data attribute indicating playlist loading state ('loading' | 'ready') */\n readonly 'data-playlist-state'?: 'loading' | 'ready';\n /** Track control slots rendered in the controls column, one per track */\n readonly trackControlsSlots?: React.ReactNode[];\n /** Height of the timescale gap spacer in the controls column (matches timescale height) */\n readonly timescaleGapHeight?: number;\n}\nexport const Playlist: FunctionComponent<PlaylistProps> = ({\n children,\n backgroundColor,\n timescaleBackgroundColor,\n timescale,\n timescaleWidth,\n tracksWidth,\n controlsWidth,\n onTracksClick,\n onTracksMouseDown,\n onTracksMouseMove,\n onTracksMouseUp,\n scrollContainerRef,\n isSelecting,\n 'data-playlist-state': playlistState,\n trackControlsSlots,\n timescaleGapHeight = 0,\n}) => {\n const scrollAreaRef = useRef<HTMLDivElement | null>(null);\n\n const handleRef = useCallback(\n (el: HTMLDivElement | null) => {\n scrollAreaRef.current = el;\n scrollContainerRef?.(el);\n },\n [scrollContainerRef]\n );\n\n const showControls = controlsWidth !== undefined && controlsWidth > 0;\n\n return (\n <Wrapper data-playlist-state={playlistState}>\n {showControls && (\n <ControlsColumn $width={controlsWidth}>\n {timescaleGapHeight > 0 && <TimescaleGap $height={timescaleGapHeight} />}\n {trackControlsSlots}\n </ControlsColumn>\n )}\n <ScrollArea data-scroll-container=\"true\" ref={handleRef}>\n <ScrollViewportProvider containerRef={scrollAreaRef}>\n <ScrollContainerInner $backgroundColor={backgroundColor} $width={tracksWidth}>\n {timescale && (\n <TimescaleWrapper $width={timescaleWidth} $backgroundColor={timescaleBackgroundColor}>\n {timescale}\n </TimescaleWrapper>\n )}\n <TracksContainer $width={tracksWidth} $backgroundColor={backgroundColor}>\n {children}\n {(onTracksClick || onTracksMouseDown) && (\n <ClickOverlay\n $isSelecting={isSelecting}\n onClick={onTracksClick}\n onMouseDown={onTracksMouseDown}\n onMouseMove={onTracksMouseMove}\n onMouseUp={onTracksMouseUp}\n />\n )}\n </TracksContainer>\n </ScrollContainerInner>\n </ScrollViewportProvider>\n </ScrollArea>\n </Wrapper>\n );\n};\n\nexport const StyledPlaylist = withTheme(Playlist);\n","import React from 'react';\nimport styled from 'styled-components';\n\ninterface SelectionOverlayProps {\n readonly $left: number;\n readonly $width: number;\n readonly $color: string;\n}\n\nconst SelectionOverlay = styled.div.attrs<SelectionOverlayProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<SelectionOverlayProps>`\n position: absolute;\n top: 0;\n background: ${(props) => props.$color};\n height: 100%;\n z-index: 60; /* Above clips (z-index: 10) and fades (z-index: 50), below playhead (z-index: 100) */\n pointer-events: none;\n opacity: 0.3;\n`;\n\nexport interface SelectionProps {\n startPosition: number; // Start position in pixels\n endPosition: number; // End position in pixels\n color?: string;\n}\n\nexport const Selection: React.FC<SelectionProps> = ({\n startPosition,\n endPosition,\n color = '#00ff00',\n}) => {\n const width = Math.max(0, endPosition - startPosition);\n\n if (width <= 0) {\n return null;\n }\n\n return <SelectionOverlay $left={startPosition} $width={width} $color={color} data-selection />;\n};\n","import React, { useCallback, useRef, useState } from 'react';\nimport styled from 'styled-components';\n\ninterface LoopRegionOverlayProps {\n readonly $left: number;\n readonly $width: number;\n readonly $color: string;\n}\n\nconst LoopRegionOverlayDiv = styled.div.attrs<LoopRegionOverlayProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<LoopRegionOverlayProps>`\n position: absolute;\n top: 0;\n background: ${(props) => props.$color};\n height: 100%;\n z-index: 55; /* Between clips (z-index: 50) and selection (z-index: 60) */\n pointer-events: none;\n`;\n\ninterface LoopMarkerProps {\n readonly $left: number;\n readonly $color: string;\n readonly $isStart: boolean;\n readonly $isDragging?: boolean;\n}\n\nconst LoopMarker = styled.div.attrs<LoopMarkerProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n },\n}))<LoopMarkerProps>`\n position: absolute;\n top: 0;\n width: 2px;\n height: 100%;\n background: ${(props) => props.$color};\n z-index: 90; /* Below playhead (z-index: 100) */\n pointer-events: none;\n\n /* Triangle marker at top */\n &::before {\n content: '';\n position: absolute;\n top: 0;\n ${(props) => (props.$isStart ? 'left: 0' : 'right: 0')};\n width: 0;\n height: 0;\n border-top: 8px solid ${(props) => props.$color};\n ${(props) =>\n props.$isStart\n ? 'border-right: 8px solid transparent;'\n : 'border-left: 8px solid transparent;'}\n }\n`;\n\nexport interface LoopRegionProps {\n startPosition: number; // Start position in pixels\n endPosition: number; // End position in pixels\n regionColor?: string;\n markerColor?: string;\n}\n\n/**\n * Loop region overlay with non-interactive markers.\n * This renders over the tracks area - markers are visual only here.\n */\nexport const LoopRegion: React.FC<LoopRegionProps> = ({\n startPosition,\n endPosition,\n regionColor = 'rgba(59, 130, 246, 0.3)',\n markerColor = '#3b82f6',\n}) => {\n const width = Math.max(0, endPosition - startPosition);\n\n if (width <= 0) {\n return null;\n }\n\n return (\n <>\n <LoopRegionOverlayDiv\n $left={startPosition}\n $width={width}\n $color={regionColor}\n data-loop-region\n />\n <LoopMarker\n $left={startPosition}\n $color={markerColor}\n $isStart={true}\n data-loop-marker=\"start\"\n />\n <LoopMarker\n $left={endPosition - 2}\n $color={markerColor}\n $isStart={false}\n data-loop-marker=\"end\"\n />\n </>\n );\n};\n\n// Draggable marker handle for timescale area\ninterface DraggableMarkerHandleProps {\n readonly $left: number;\n readonly $color: string;\n readonly $isStart: boolean;\n readonly $isDragging?: boolean;\n}\n\nconst DraggableMarkerHandle = styled.div.attrs<DraggableMarkerHandleProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n },\n}))<DraggableMarkerHandleProps>`\n position: absolute;\n top: 0;\n width: 12px;\n height: 100%;\n cursor: ew-resize;\n z-index: 100;\n /* Center the handle on the marker position */\n transform: translateX(-5px);\n\n /* Visual marker line */\n &::before {\n content: '';\n position: absolute;\n top: 0;\n left: 5px;\n width: 2px;\n height: 100%;\n background: ${(props) => props.$color};\n opacity: ${(props) => (props.$isDragging ? 1 : 0.8)};\n }\n\n /* Triangle marker at top */\n &::after {\n content: '';\n position: absolute;\n top: 0;\n ${(props) => (props.$isStart ? 'left: 5px' : 'left: -1px')};\n width: 0;\n height: 0;\n border-top: 10px solid ${(props) => props.$color};\n ${(props) =>\n props.$isStart\n ? 'border-right: 10px solid transparent;'\n : 'border-left: 10px solid transparent;'}\n }\n\n &:hover::before {\n opacity: 1;\n }\n`;\n\n// Background shading in timescale - draggable to move entire region\ninterface TimescaleLoopShadeProps {\n readonly $left: number;\n readonly $width: number;\n readonly $color: string;\n readonly $isDragging?: boolean;\n}\n\nconst TimescaleLoopShade = styled.div.attrs<TimescaleLoopShadeProps>((props) => ({\n style: {\n left: `${props.$left}px`,\n width: `${props.$width}px`,\n },\n}))<TimescaleLoopShadeProps>`\n position: absolute;\n top: 0;\n height: 100%;\n background: ${(props) => props.$color};\n z-index: 50;\n cursor: grab;\n\n &:active {\n cursor: grabbing;\n }\n`;\n\nexport interface LoopRegionMarkersProps {\n startPosition: number; // Start position in pixels\n endPosition: number; // End position in pixels\n markerColor?: string;\n regionColor?: string;\n onLoopStartChange?: (newPositionPixels: number) => void;\n onLoopEndChange?: (newPositionPixels: number) => void;\n /** Called when the entire region is moved */\n onLoopRegionMove?: (newStartPixels: number, newEndPixels: number) => void;\n /** Minimum position in pixels (usually controls width offset) */\n minPosition?: number;\n /** Maximum position in pixels */\n maxPosition?: number;\n}\n\n/**\n * Draggable loop region markers for the timescale area.\n * These are interactive and can be dragged to adjust loop boundaries.\n * The shaded region between markers can be dragged to move the entire loop.\n */\nexport const LoopRegionMarkers: React.FC<LoopRegionMarkersProps> = ({\n startPosition,\n endPosition,\n markerColor = '#3b82f6',\n regionColor = 'rgba(59, 130, 246, 0.3)',\n onLoopStartChange,\n onLoopEndChange,\n onLoopRegionMove,\n minPosition = 0,\n maxPosition = Infinity,\n}) => {\n const [draggingMarker, setDraggingMarker] = useState<'start' | 'end' | 'region' | null>(null);\n const dragStartX = useRef<number>(0);\n const dragStartPosition = useRef<number>(0);\n const dragStartEnd = useRef<number>(0);\n\n const width = Math.max(0, endPosition - startPosition);\n\n // Handle dragging individual markers\n const handleMarkerMouseDown = useCallback(\n (e: React.MouseEvent, marker: 'start' | 'end') => {\n e.preventDefault();\n e.stopPropagation();\n setDraggingMarker(marker);\n dragStartX.current = e.clientX;\n dragStartPosition.current = marker === 'start' ? startPosition : endPosition;\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n const delta = moveEvent.clientX - dragStartX.current;\n const newPosition = dragStartPosition.current + delta;\n\n if (marker === 'start') {\n // Start marker can't go past end marker or outside bounds\n const clampedPosition = Math.max(minPosition, Math.min(endPosition - 10, newPosition));\n onLoopStartChange?.(clampedPosition);\n } else {\n // End marker can't go before start marker or outside bounds\n const clampedPosition = Math.max(startPosition + 10, Math.min(maxPosition, newPosition));\n onLoopEndChange?.(clampedPosition);\n }\n };\n\n const handleMouseUp = () => {\n setDraggingMarker(null);\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n },\n [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]\n );\n\n // Handle dragging the entire region\n const handleRegionMouseDown = useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setDraggingMarker('region');\n dragStartX.current = e.clientX;\n dragStartPosition.current = startPosition;\n dragStartEnd.current = endPosition;\n\n const regionWidth = endPosition - startPosition;\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n const delta = moveEvent.clientX - dragStartX.current;\n let newStart = dragStartPosition.current + delta;\n let newEnd = dragStartEnd.current + delta;\n\n // Clamp to bounds while maintaining region width\n if (newStart < minPosition) {\n newStart = minPosition;\n newEnd = minPosition + regionWidth;\n }\n if (newEnd > maxPosition) {\n newEnd = maxPosition;\n newStart = maxPosition - regionWidth;\n }\n\n onLoopRegionMove?.(newStart, newEnd);\n };\n\n const handleMouseUp = () => {\n setDraggingMarker(null);\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n },\n [startPosition, endPosition, minPosition, maxPosition, onLoopRegionMove]\n );\n\n if (width <= 0) {\n return null;\n }\n\n return (\n <>\n <TimescaleLoopShade\n $left={startPosition}\n $width={width}\n $color={regionColor}\n $isDragging={draggingMarker === 'region'}\n onMouseDown={handleRegionMouseDown}\n data-loop-region-timescale\n />\n <DraggableMarkerHandle\n $left={startPosition}\n $color={markerColor}\n $isStart={true}\n $isDragging={draggingMarker === 'start'}\n onMouseDown={(e) => handleMarkerMouseDown(e, 'start')}\n data-loop-marker-handle=\"start\"\n />\n <DraggableMarkerHandle\n $left={endPosition}\n $color={markerColor}\n $isStart={false}\n $isDragging={draggingMarker === 'end'}\n onMouseDown={(e) => handleMarkerMouseDown(e, 'end')}\n data-loop-marker-handle=\"end\"\n />\n </>\n );\n};\n\n// Click-to-create wrapper for timescale area\nconst TimescaleLoopCreator = styled.div`\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 100%; /* Stay within timescale bounds, don't extend into tracks */\n cursor: crosshair;\n z-index: 40; /* Below markers and shading */\n`;\n\nexport interface TimescaleLoopRegionProps {\n /** Current loop start position in pixels */\n startPosition: number;\n /** Current loop end position in pixels */\n endPosition: number;\n markerColor?: string;\n regionColor?: string;\n /** Called when loop region changes (start pixels, end pixels) */\n onLoopRegionChange?: (startPixels: number, endPixels: number) => void;\n /** Minimum position in pixels */\n minPosition?: number;\n /** Maximum position in pixels */\n maxPosition?: number;\n}\n\n/**\n * Complete timescale loop region component with:\n * - Click and drag to create a new loop region\n * - Drag markers to resize existing loop region\n * - Drag the shaded region to move the entire loop\n */\nexport const TimescaleLoopRegion: React.FC<TimescaleLoopRegionProps> = ({\n startPosition,\n endPosition,\n markerColor = '#3b82f6',\n regionColor = 'rgba(59, 130, 246, 0.3)',\n onLoopRegionChange,\n minPosition = 0,\n maxPosition = Infinity,\n}) => {\n const [, setIsCreating] = useState(false);\n const createStartX = useRef<number>(0);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const hasLoopRegion = endPosition > startPosition;\n\n // Handle creating a new loop region by clicking and dragging\n const handleBackgroundMouseDown = useCallback(\n (e: React.MouseEvent) => {\n // Only create new region if clicking on the background (not on markers or region)\n const target = e.target as HTMLElement;\n if (\n target.closest('[data-loop-marker-handle]') ||\n target.closest('[data-loop-region-timescale]')\n ) {\n return;\n }\n\n e.preventDefault();\n setIsCreating(true);\n\n const rect = containerRef.current?.getBoundingClientRect();\n if (!rect) return;\n\n const clickX = e.clientX - rect.left;\n const clampedX = Math.max(minPosition, Math.min(maxPosition, clickX));\n createStartX.current = clampedX;\n\n // Set initial position (will be a point until dragged)\n onLoopRegionChange?.(clampedX, clampedX);\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n const currentX = moveEvent.clientX - rect.left;\n const clampedCurrentX = Math.max(minPosition, Math.min(maxPosition, currentX));\n\n const newStart = Math.min(createStartX.current, clampedCurrentX);\n const newEnd = Math.max(createStartX.current, clampedCurrentX);\n\n onLoopRegionChange?.(newStart, newEnd);\n };\n\n const handleMouseUp = () => {\n setIsCreating(false);\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n },\n [minPosition, maxPosition, onLoopRegionChange]\n );\n\n return (\n <TimescaleLoopCreator\n ref={containerRef}\n onMouseDown={handleBackgroundMouseDown}\n data-timescale-loop-creator\n >\n {hasLoopRegion && (\n <LoopRegionMarkers\n startPosition={startPosition}\n endPosition={endPosition}\n markerColor={markerColor}\n regionColor={regionColor}\n minPosition={minPosition}\n maxPosition={maxPosition}\n onLoopStartChange={(newStart) => onLoopRegionChange?.(newStart, endPosition)}\n onLoopEndChange={(newEnd) => onLoopRegionChange?.(startPosition, newEnd)}\n onLoopRegionMove={(newStart, newEnd) => onLoopRegionChange?.(newStart, newEnd)}\n />\n )}\n </TimescaleLoopCreator>\n );\n};\n","import React, { useEffect, useState } from 'react';\nimport { TimeInput } from './TimeInput';\nimport { type TimeFormat } from '../utils/timeFormat';\n\nexport interface SelectionTimeInputsProps {\n selectionStart: number; // Time in seconds\n selectionEnd: number; // Time in seconds\n onSelectionChange?: (start: number, end: number) => void;\n className?: string;\n}\n\nexport const SelectionTimeInputs: React.FC<SelectionTimeInputsProps> = ({\n selectionStart,\n selectionEnd,\n onSelectionChange,\n className,\n}) => {\n const [timeFormat, setTimeFormat] = useState<TimeFormat>('hh:mm:ss.uuu');\n\n // Listen to the external time-format dropdown\n useEffect(() => {\n const timeFormatSelect = document.querySelector('.time-format') as HTMLSelectElement;\n\n const handleFormatChange = () => {\n if (timeFormatSelect) {\n setTimeFormat(timeFormatSelect.value as TimeFormat);\n }\n };\n\n // Set initial value\n if (timeFormatSelect) {\n setTimeFormat(timeFormatSelect.value as TimeFormat);\n timeFormatSelect.addEventListener('change', handleFormatChange);\n }\n\n return () => {\n timeFormatSelect?.removeEventListener('change', handleFormatChange);\n };\n }, []);\n\n const handleStartChange = (value: number) => {\n if (onSelectionChange) {\n onSelectionChange(value, selectionEnd);\n }\n };\n\n const handleEndChange = (value: number) => {\n if (onSelectionChange) {\n onSelectionChange(selectionStart, value);\n }\n };\n\n return (\n <div className={className}>\n <TimeInput\n id=\"audio_start\"\n label=\"Start of audio selection\"\n value={selectionStart}\n format={timeFormat}\n className=\"audio-start form-control mr-sm-2\"\n onChange={handleStartChange}\n />\n <TimeInput\n id=\"audio_end\"\n label=\"End of audio selection\"\n value={selectionEnd}\n format={timeFormat}\n className=\"audio-end form-control mr-sm-2\"\n onChange={handleEndChange}\n />\n </div>\n );\n};\n","import React, { useEffect, useState } from 'react';\nimport { formatTime, parseTime, type TimeFormat } from '../utils/timeFormat';\nimport { BaseInput, ScreenReaderOnly } from '../styled/index';\n\nexport interface TimeInputProps {\n id: string;\n label: string;\n value: number; // Time in seconds\n format: TimeFormat;\n className?: string;\n onChange?: (value: number) => void;\n readOnly?: boolean;\n}\n\n/**\n * TimeInput - A styled input for time values with format support\n *\n * Uses BaseInput for consistent theming. Displays time in the specified\n * format and parses user input on blur.\n */\nexport const TimeInput: React.FC<TimeInputProps> = ({\n id,\n label,\n value,\n format,\n className,\n onChange,\n readOnly = false,\n}) => {\n const [displayValue, setDisplayValue] = useState('');\n\n // Update display value when value or format changes\n useEffect(() => {\n const formatted = formatTime(value, format);\n setDisplayValue(formatted);\n }, [value, format, id]);\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const newDisplayValue = e.target.value;\n setDisplayValue(newDisplayValue);\n };\n\n const handleBlur = () => {\n // Parse the display value and notify parent\n if (onChange) {\n const parsedValue = parseTime(displayValue, format);\n onChange(parsedValue);\n }\n // Re-format to ensure consistent display\n setDisplayValue(formatTime(value, format));\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.currentTarget.blur();\n }\n };\n\n return (\n <>\n <ScreenReaderOnly as=\"label\" htmlFor={id}>\n {label}\n </ScreenReaderOnly>\n <BaseInput\n type=\"text\"\n className={className}\n id={id}\n value={displayValue}\n onChange={handleChange}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n readOnly={readOnly}\n />\n </>\n );\n};\n","/**\n * Time format utilities for displaying and parsing audio timestamps\n */\n\nexport type TimeFormat =\n | 'seconds'\n | 'thousandths'\n | 'hh:mm:ss'\n | 'hh:mm:ss.u'\n | 'hh:mm:ss.uu'\n | 'hh:mm:ss.uuu';\n\n/**\n * Format time in clock format (hh:mm:ss with optional decimals)\n */\nfunction clockFormat(seconds: number, decimals: number): string {\n const hours = Math.floor(seconds / 3600) % 24;\n const minutes = Math.floor(seconds / 60) % 60;\n const secs = (seconds % 60).toFixed(decimals);\n\n return (\n String(hours).padStart(2, '0') +\n ':' +\n String(minutes).padStart(2, '0') +\n ':' +\n secs.padStart(decimals > 0 ? decimals + 3 : 2, '0')\n );\n}\n\n/**\n * Format seconds according to the specified format\n */\nexport function formatTime(seconds: number, format: TimeFormat): string {\n switch (format) {\n case 'seconds':\n return seconds.toFixed(0);\n case 'thousandths':\n return seconds.toFixed(3);\n case 'hh:mm:ss':\n return clockFormat(seconds, 0);\n case 'hh:mm:ss.u':\n return clockFormat(seconds, 1);\n case 'hh:mm:ss.uu':\n return clockFormat(seconds, 2);\n case 'hh:mm:ss.uuu':\n return clockFormat(seconds, 3);\n default:\n return clockFormat(seconds, 3);\n }\n}\n\n/**\n * Parse a formatted time string back to seconds\n */\nexport function parseTime(timeStr: string, format: TimeFormat): number {\n if (!timeStr) return 0;\n\n switch (format) {\n case 'seconds':\n case 'thousandths':\n return parseFloat(timeStr) || 0;\n\n case 'hh:mm:ss':\n case 'hh:mm:ss.u':\n case 'hh:mm:ss.uu':\n case 'hh:mm:ss.uuu': {\n // Parse hh:mm:ss format\n const parts = timeStr.split(':');\n if (parts.length !== 3) return 0;\n\n const hours = parseInt(parts[0], 10) || 0;\n const minutes = parseInt(parts[1], 10) || 0;\n const seconds = parseFloat(parts[2]) || 0;\n\n return hours * 3600 + minutes * 60 + seconds;\n }\n\n default:\n return 0;\n }\n}\n","import React, { createContext, useContext, useMemo } from 'react';\nimport { ticksPerBeat, ticksPerBar } from '@waveform-playlist/core';\n\nexport type SnapTo = 'bar' | 'beat' | 'off';\n\nexport interface BeatsAndBarsContextValue {\n bpm: number;\n timeSignature: [number, number];\n snapTo: SnapTo;\n ticksPerBeat: number;\n ticksPerBar: number;\n}\n\nexport interface BeatsAndBarsProviderProps {\n bpm: number;\n timeSignature: [number, number];\n snapTo: SnapTo;\n children: React.ReactNode;\n}\n\nconst BeatsAndBarsContext = createContext<BeatsAndBarsContextValue | null>(null);\n\nexport function BeatsAndBarsProvider({\n bpm,\n timeSignature,\n snapTo,\n children,\n}: BeatsAndBarsProviderProps) {\n const [numerator, denominator] = timeSignature;\n const value = useMemo<BeatsAndBarsContextValue>(() => {\n const ts: [number, number] = [numerator, denominator];\n const tpBeat = ticksPerBeat(ts);\n const tpBar = ticksPerBar(ts);\n return {\n bpm,\n timeSignature: ts,\n snapTo,\n ticksPerBeat: tpBeat,\n ticksPerBar: tpBar,\n };\n }, [bpm, numerator, denominator, snapTo]);\n\n return <BeatsAndBarsContext.Provider value={value}>{children}</BeatsAndBarsContext.Provider>;\n}\n\nexport function useBeatsAndBars(): BeatsAndBarsContextValue | null {\n return useContext(BeatsAndBarsContext);\n}\n","import React, { useState, createContext, useContext, ReactNode } from 'react';\n\nfunction getScale() {\n return window.devicePixelRatio;\n}\n\nconst DevicePixelRatioContext = createContext(getScale());\n\ntype Props = {\n children: ReactNode;\n};\nexport const DevicePixelRatioProvider = ({ children }: Props) => {\n const [scale, setScale] = useState(getScale());\n\n matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(\n 'change',\n () => {\n setScale(getScale());\n },\n { once: true }\n );\n\n return (\n <DevicePixelRatioContext.Provider value={Math.ceil(scale)}>\n {children}\n </DevicePixelRatioContext.Provider>\n );\n};\n\nexport const useDevicePixelRatio = () => useContext(DevicePixelRatioContext);\n","import { createContext, useContext } from 'react';\n\ntype Controls = {\n show: boolean;\n width: number;\n};\n\ntype PlaylistInfo = {\n sampleRate: number;\n samplesPerPixel: number;\n zoomLevels: Array<number>;\n waveHeight: number;\n timeScaleHeight: number;\n duration: number;\n controls: Controls;\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 /** Width in pixels of progress bars. Default: barWidth + barGap (fills gaps). Set to barWidth for no gap fill. */\n progressBarWidth?: number;\n};\n\nexport const PlaylistInfoContext = createContext<PlaylistInfo>({\n sampleRate: 48000,\n samplesPerPixel: 1000,\n zoomLevels: [1000, 1500, 2000, 2500],\n waveHeight: 80,\n timeScaleHeight: 15,\n controls: {\n show: false,\n width: 150,\n },\n duration: 30000,\n barWidth: 1,\n barGap: 0,\n});\n\nexport const usePlaylistInfo = () => useContext(PlaylistInfoContext);\n","import { useContext } from 'react';\nimport { ThemeContext } from 'styled-components';\n\nexport const useTheme = () => useContext(ThemeContext);\n","import React, { createContext, useContext, Fragment } from 'react';\n\nexport const TrackControlsContext = createContext<React.ReactNode>(<Fragment />);\n\nexport const useTrackControls = () => useContext(TrackControlsContext);\n","import React, {\n useState,\n createContext,\n useContext,\n ReactNode,\n Dispatch,\n SetStateAction,\n} from 'react';\n\nconst defaultProgress = 0;\nconst defaultIsPlaying = false;\nconst defaultSelectionStart = 0;\nconst defaultSelectionEnd = 0;\n\nconst defaultPlayout = {\n progress: defaultProgress,\n isPlaying: defaultIsPlaying,\n selectionStart: defaultSelectionStart,\n selectionEnd: defaultSelectionEnd,\n};\n\nconst PlayoutStatusContext = createContext(defaultPlayout);\n\ntype PlayoutStatusUpdate = {\n setIsPlaying: Dispatch<SetStateAction<boolean>>;\n setProgress: Dispatch<SetStateAction<number>>;\n setSelection: (start: number, end: number) => void;\n};\nconst PlayoutStatusUpdateContext = createContext<PlayoutStatusUpdate>({\n setIsPlaying: () => {},\n setProgress: () => {},\n setSelection: () => {},\n});\n\ntype Props = {\n children: ReactNode;\n};\nexport const PlayoutProvider = ({ children }: Props) => {\n const [isPlaying, setIsPlaying] = useState(defaultIsPlaying);\n const [progress, setProgress] = useState(defaultProgress);\n const [selectionStart, setSelectionStart] = useState(defaultSelectionStart);\n const [selectionEnd, setSelectionEnd] = useState(defaultSelectionEnd);\n\n const setSelection = (start: number, end: number) => {\n setSelectionStart(start);\n setSelectionEnd(end);\n };\n\n return (\n <PlayoutStatusUpdateContext.Provider value={{ setIsPlaying, setProgress, setSelection }}>\n <PlayoutStatusContext.Provider value={{ isPlaying, progress, selectionStart, selectionEnd }}>\n {children}\n </PlayoutStatusContext.Provider>\n </PlayoutStatusUpdateContext.Provider>\n );\n};\n\nexport const usePlayoutStatus = () => useContext(PlayoutStatusContext);\nexport const usePlayoutStatusUpdate = () => useContext(PlayoutStatusUpdateContext);\n","import React, { FunctionComponent, useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport type { SpectrogramData } from '@waveform-playlist/core';\nimport { useVisibleChunkIndices } from '../contexts/ScrollViewport';\nimport { useClipViewportOrigin } from '../contexts/ClipViewportOrigin';\nimport { useChunkedCanvasRefs } from '../hooks/useChunkedCanvasRefs';\nimport { MAX_CANVAS_WIDTH } from '@waveform-playlist/core';\nconst LINEAR_FREQUENCY_SCALE = (f: number, minF: number, maxF: number) =>\n (f - minF) / (maxF - minF);\n\ninterface WrapperProps {\n readonly $index: number;\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n}\n\nconst Wrapper = styled.div.attrs<WrapperProps>((props) => ({\n style: {\n top: `${props.$waveHeight * props.$index}px`,\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n },\n}))<WrapperProps>`\n position: absolute;\n background: #000;\n transform: translateZ(0);\n backface-visibility: hidden;\n`;\n\ninterface CanvasProps {\n readonly $cssWidth: number;\n readonly $waveHeight: number;\n readonly $left: number;\n}\n\nconst SpectrogramCanvas = styled.canvas.attrs<CanvasProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$waveHeight}px`,\n left: `${props.$left}px`,\n },\n}))<CanvasProps>`\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n image-rendering: crisp-edges;\n`;\n\n// Inline getColorMap to avoid cross-package import at component level\n// This avoids needing browser package as dependency of ui-components\nfunction defaultGetColorMap(): Uint8Array {\n // Grayscale fallback — 256-entry LUT (used when no colorLUT prop provided)\n const lut = new Uint8Array(256 * 3);\n for (let i = 0; i < 256; i++) {\n lut[i * 3] = lut[i * 3 + 1] = lut[i * 3 + 2] = i;\n }\n return lut;\n}\nconst DEFAULT_COLOR_LUT = defaultGetColorMap();\n\nexport interface SpectrogramWorkerCanvasApi {\n registerCanvas(canvasId: string, canvas: OffscreenCanvas): void;\n unregisterCanvas(canvasId: string): void;\n}\n\nexport interface SpectrogramChannelProps {\n /** Visual position index — used for CSS positioning (top offset). */\n index: number;\n /** Audio channel index for canvas ID construction. Defaults to `index` when omitted. */\n channelIndex?: number;\n /** Computed spectrogram data (not needed when workerApi is provided) */\n data?: SpectrogramData;\n /** Width in CSS pixels */\n length: number;\n /** Height in CSS pixels */\n waveHeight: number;\n /** Device pixel ratio for sharp rendering */\n devicePixelRatio?: number;\n /** Samples per pixel at current zoom level */\n samplesPerPixel: number;\n /** 256-entry RGB LUT (768 bytes) from getColorMap() */\n colorLUT?: Uint8Array;\n /** Frequency scale function: (freqHz, minF, maxF) => [0,1] */\n frequencyScaleFn?: (f: number, minF: number, maxF: number) => number;\n /** Min frequency in Hz */\n minFrequency?: number;\n /** Max frequency in Hz (defaults to sampleRate/2) */\n maxFrequency?: number;\n /** Worker API for transferring canvas ownership. When provided, rendering is done in the worker. */\n workerApi?: SpectrogramWorkerCanvasApi;\n /** Clip ID used to construct unique canvas IDs for worker registration */\n clipId?: string;\n /** Callback when canvases are registered with the worker, providing canvas IDs and widths */\n onCanvasesReady?: (canvasIds: string[], canvasWidths: number[]) => void;\n}\n\nexport const SpectrogramChannel: FunctionComponent<SpectrogramChannelProps> = ({\n index,\n channelIndex: channelIndexProp,\n data,\n length,\n waveHeight,\n devicePixelRatio = 1,\n samplesPerPixel,\n colorLUT,\n frequencyScaleFn,\n minFrequency = 0,\n maxFrequency,\n workerApi,\n clipId,\n onCanvasesReady,\n}) => {\n const channelIndex = channelIndexProp ?? index;\n const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();\n const registeredIdsRef = useRef<string[]>([]);\n const transferredCanvasesRef = useRef<WeakSet<HTMLCanvasElement>>(new WeakSet());\n const workerApiRef = useRef(workerApi);\n const onCanvasesReadyRef = useRef(onCanvasesReady);\n\n // Track whether we're in worker mode (canvas transferred)\n const isWorkerMode = !!(workerApi && clipId);\n const clipOriginX = useClipViewportOrigin();\n\n const visibleChunkIndices = useVisibleChunkIndices(length, MAX_CANVAS_WIDTH, clipOriginX);\n\n const lut = colorLUT ?? DEFAULT_COLOR_LUT;\n const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);\n const scaleFn = frequencyScaleFn ?? LINEAR_FREQUENCY_SCALE;\n const hasCustomFrequencyScale = Boolean(frequencyScaleFn);\n\n // Keep refs in sync with latest props\n useEffect(() => {\n workerApiRef.current = workerApi;\n }, [workerApi]);\n\n useEffect(() => {\n onCanvasesReadyRef.current = onCanvasesReady;\n }, [onCanvasesReady]);\n\n // Worker mode: clean up stale canvases, then transfer new ones.\n // Cleanup and registration are combined in a single effect so that\n // `onCanvasesReady` always receives a clean list without stale IDs.\n // Uses visibleChunkIndices so it only re-runs when chunks mount/unmount.\n useEffect(() => {\n if (!isWorkerMode) return;\n const currentWorkerApi = workerApiRef.current;\n if (!currentWorkerApi || !clipId) return;\n\n // Step 1: Remove stale registrations for unmounted canvases.\n const previousCount = registeredIdsRef.current.length;\n const remaining: string[] = [];\n for (const id of registeredIdsRef.current) {\n const match = id.match(/chunk(\\d+)$/);\n if (!match) {\n remaining.push(id);\n continue;\n }\n const chunkIdx = parseInt(match[1], 10);\n const canvas = canvasMapRef.current.get(chunkIdx);\n if (canvas && canvas.isConnected) {\n remaining.push(id);\n } else {\n try {\n currentWorkerApi.unregisterCanvas(id);\n } catch (err) {\n console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);\n }\n }\n }\n registeredIdsRef.current = remaining;\n\n // Step 2: Transfer new canvases to the worker.\n const newIds: string[] = [];\n\n for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {\n if (transferredCanvasesRef.current.has(canvas)) continue;\n\n const canvasId = `${clipId}-ch${channelIndex}-chunk${canvasIdx}`;\n\n let offscreen: OffscreenCanvas;\n try {\n offscreen = canvas.transferControlToOffscreen();\n } catch (err) {\n console.warn(`[spectrogram] transferControlToOffscreen failed for ${canvasId}:`, err);\n continue;\n }\n\n // Mark transferred immediately — transferControlToOffscreen is irreversible.\n transferredCanvasesRef.current.add(canvas);\n\n try {\n currentWorkerApi.registerCanvas(canvasId, offscreen);\n newIds.push(canvasId);\n } catch (err) {\n console.warn(`[spectrogram] registerCanvas failed for ${canvasId}:`, err);\n continue;\n }\n }\n\n if (newIds.length > 0) {\n registeredIdsRef.current = [...registeredIdsRef.current, ...newIds];\n }\n\n // Step 3: Notify provider when canvas set changed (added or removed).\n const canvasSetChanged = newIds.length > 0 || remaining.length < previousCount;\n if (canvasSetChanged) {\n const allIds = registeredIdsRef.current;\n const allWidths = allIds.map((id) => {\n const match = id.match(/chunk(\\d+)$/);\n if (!match) {\n console.warn(`[spectrogram] Unexpected canvas ID format: ${id}`);\n return MAX_CANVAS_WIDTH;\n }\n const chunkIdx = parseInt(match[1], 10);\n return Math.min(length - chunkIdx * MAX_CANVAS_WIDTH, MAX_CANVAS_WIDTH);\n });\n onCanvasesReadyRef.current?.(allIds, allWidths);\n }\n }, [canvasMapRef, isWorkerMode, clipId, channelIndex, length, visibleChunkIndices]);\n\n // Unregister all canvases from worker on component unmount\n useEffect(() => {\n return () => {\n const api = workerApiRef.current;\n if (!api) return;\n for (const id of registeredIdsRef.current) {\n try {\n api.unregisterCanvas(id);\n } catch (err) {\n console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);\n }\n }\n registeredIdsRef.current = [];\n };\n }, []);\n\n // Main-thread rendering (skipped in worker mode).\n // useEffect (not useLayoutEffect) so the browser paints the track layout\n // (controls + empty canvas containers) before heavy canvas drawing starts.\n useEffect(() => {\n if (isWorkerMode || !data) return;\n\n const {\n frequencyBinCount,\n frameCount,\n hopSize,\n sampleRate,\n gainDb,\n rangeDb: rawRangeDb,\n } = data;\n const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;\n\n // Pre-compute Y mapping: for each pixel row, which frequency bin(s) to sample\n const binToFreq = (bin: number) => (bin / frequencyBinCount) * (sampleRate / 2);\n\n for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {\n const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n\n const canvasWidth = canvas.width / devicePixelRatio;\n const canvasHeight = waveHeight;\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n // Create ImageData at CSS pixel size, then putImageData at scaled resolution\n const imgData = ctx.createImageData(canvasWidth, canvasHeight);\n const pixels = imgData.data;\n\n for (let x = 0; x < canvasWidth; x++) {\n const globalX = globalPixelOffset + x;\n\n // Map pixel X → spectrogram frame\n const samplePos = globalX * samplesPerPixel;\n const frame = Math.floor(samplePos / hopSize);\n\n if (frame < 0 || frame >= frameCount) continue;\n\n const frameOffset = frame * frequencyBinCount;\n\n for (let y = 0; y < canvasHeight; y++) {\n // Y=0 is top of canvas, but low frequencies should be at bottom\n const normalizedY = 1 - y / canvasHeight; // 0=bottom, 1=top\n\n // Map normalizedY through frequency scale to find which frequency this pixel represents\n // We need the inverse: given a normalized position, find the frequency\n // Use binary search over frequency bins\n let bin = Math.floor(normalizedY * frequencyBinCount);\n\n // If we have a non-linear scale, find the correct bin\n if (hasCustomFrequencyScale) {\n // Binary search: find the bin whose scaled position is closest to normalizedY\n let lo = 0;\n let hi = frequencyBinCount - 1;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n const freq = binToFreq(mid);\n const scaled = scaleFn(freq, minFrequency, maxF);\n if (scaled < normalizedY) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n bin = lo;\n }\n\n if (bin < 0 || bin >= frequencyBinCount) continue;\n\n // Get dB value and normalize to [0, 1]\n const db = data.data[frameOffset + bin];\n const normalized = Math.max(0, Math.min(1, (db + rangeDb + gainDb) / rangeDb));\n\n // Map to color via LUT (0-255 index)\n const colorIdx = Math.floor(normalized * 255);\n const pixelIdx = (y * canvasWidth + x) * 4;\n pixels[pixelIdx] = lut[colorIdx * 3];\n pixels[pixelIdx + 1] = lut[colorIdx * 3 + 1];\n pixels[pixelIdx + 2] = lut[colorIdx * 3 + 2];\n pixels[pixelIdx + 3] = 255;\n }\n }\n\n // Put at device pixel ratio scale\n ctx.resetTransform();\n ctx.putImageData(imgData, 0, 0);\n\n // Scale up to fill canvas\n if (devicePixelRatio !== 1) {\n // Draw the image data at 1:1, then scale\n const tmpCanvas = document.createElement('canvas');\n tmpCanvas.width = canvasWidth;\n tmpCanvas.height = canvasHeight;\n const tmpCtx = tmpCanvas.getContext('2d');\n if (!tmpCtx) continue;\n tmpCtx.putImageData(imgData, 0, 0);\n\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.drawImage(tmpCanvas, 0, 0, canvas.width, canvas.height);\n }\n }\n }, [\n canvasMapRef,\n isWorkerMode,\n data,\n length,\n waveHeight,\n devicePixelRatio,\n samplesPerPixel,\n lut,\n minFrequency,\n maxF,\n scaleFn,\n hasCustomFrequencyScale,\n visibleChunkIndices,\n ]);\n\n // Build visible canvas chunk elements\n const canvases = visibleChunkIndices.map((i) => {\n const chunkLeft = i * MAX_CANVAS_WIDTH;\n const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);\n\n return (\n <SpectrogramCanvas\n key={`${length}-${i}`}\n $cssWidth={currentWidth}\n $left={chunkLeft}\n width={currentWidth * devicePixelRatio}\n height={waveHeight * devicePixelRatio}\n $waveHeight={waveHeight}\n data-index={i}\n ref={canvasRef}\n />\n );\n });\n\n return (\n <Wrapper $index={index} $cssWidth={length} $waveHeight={waveHeight}>\n {canvases}\n </Wrapper>\n );\n};\n","import React, { FunctionComponent } from 'react';\nimport { useDevicePixelRatio, usePlaylistInfo, useTheme } from '../contexts';\nimport { Channel } from './Channel';\nimport { PianoRollChannel } from './PianoRollChannel';\nimport { SpectrogramChannel, type SpectrogramWorkerCanvasApi } from './SpectrogramChannel';\nimport type { SpectrogramData, RenderMode, MidiNoteData } from '@waveform-playlist/core';\n\nexport interface SmartChannelProps {\n className?: string;\n index: number;\n data: Int8Array | Int16Array;\n bits: 8 | 16;\n length: number;\n isSelected?: boolean; // Whether this channel's track is selected\n /** If true, background is transparent (for use with external progress overlay) */\n transparentBackground?: boolean;\n /** Render mode: waveform, spectrogram, or both */\n renderMode?: RenderMode;\n /** Spectrogram data for this channel */\n spectrogramData?: SpectrogramData;\n /** 256-entry RGB LUT from getColorMap() */\n spectrogramColorLUT?: Uint8Array;\n /** Samples per pixel at current zoom level */\n samplesPerPixel?: number;\n /** Frequency scale function */\n spectrogramFrequencyScaleFn?: (f: number, minF: number, maxF: number) => number;\n /** Min frequency in Hz */\n spectrogramMinFrequency?: number;\n /** Max frequency in Hz */\n spectrogramMaxFrequency?: number;\n /** Worker API for OffscreenCanvas transfer */\n spectrogramWorkerApi?: SpectrogramWorkerCanvasApi;\n /** Clip ID for worker canvas registration */\n spectrogramClipId?: string;\n /** Callback when canvases are registered with the worker */\n spectrogramOnCanvasesReady?: (canvasIds: string[], canvasWidths: number[]) => void;\n /** MIDI note data for piano-roll rendering */\n midiNotes?: MidiNoteData[];\n /** Sample rate for MIDI note time → pixel conversion */\n sampleRate?: number;\n /** Clip offset in seconds for MIDI note positioning */\n clipOffsetSeconds?: number;\n}\n\nexport const SmartChannel: FunctionComponent<SmartChannelProps> = ({\n isSelected,\n transparentBackground,\n renderMode = 'waveform',\n spectrogramData,\n spectrogramColorLUT,\n samplesPerPixel: sppProp,\n spectrogramFrequencyScaleFn,\n spectrogramMinFrequency,\n spectrogramMaxFrequency,\n spectrogramWorkerApi,\n spectrogramClipId,\n spectrogramOnCanvasesReady,\n midiNotes,\n sampleRate: sampleRateProp,\n clipOffsetSeconds,\n ...props\n}) => {\n const theme = useTheme();\n const {\n waveHeight,\n barWidth,\n barGap,\n samplesPerPixel: contextSpp,\n sampleRate: contextSampleRate,\n } = usePlaylistInfo();\n const devicePixelRatio = useDevicePixelRatio();\n const samplesPerPixel = sppProp ?? contextSpp;\n\n // Use selected colors if track is selected\n const waveOutlineColor =\n isSelected && theme ? theme.selectedWaveOutlineColor : theme?.waveOutlineColor;\n\n const waveFillColor = isSelected && theme ? theme.selectedWaveFillColor : theme?.waveFillColor;\n\n // Get draw mode from theme (defaults to 'inverted' for backwards compatibility)\n const drawMode = theme?.waveformDrawMode || 'inverted';\n\n // Worker mode: spectrogram data is optional (worker renders directly)\n const hasSpectrogram = spectrogramData || spectrogramWorkerApi;\n\n if (renderMode === 'spectrogram' && hasSpectrogram) {\n return (\n <SpectrogramChannel\n index={props.index}\n data={spectrogramData}\n length={props.length}\n waveHeight={waveHeight}\n devicePixelRatio={devicePixelRatio}\n samplesPerPixel={samplesPerPixel}\n colorLUT={spectrogramColorLUT}\n frequencyScaleFn={spectrogramFrequencyScaleFn}\n minFrequency={spectrogramMinFrequency}\n maxFrequency={spectrogramMaxFrequency}\n workerApi={spectrogramWorkerApi}\n clipId={spectrogramClipId}\n onCanvasesReady={spectrogramOnCanvasesReady}\n />\n );\n }\n\n if (renderMode === 'both' && hasSpectrogram) {\n // Spectrogram above, waveform below — each at half waveHeight so the\n // overall track container stays the same height as a single-mode track.\n const halfHeight = Math.floor(waveHeight / 2);\n return (\n <>\n <SpectrogramChannel\n index={props.index * 2}\n channelIndex={props.index}\n data={spectrogramData}\n length={props.length}\n waveHeight={halfHeight}\n devicePixelRatio={devicePixelRatio}\n samplesPerPixel={samplesPerPixel}\n colorLUT={spectrogramColorLUT}\n frequencyScaleFn={spectrogramFrequencyScaleFn}\n minFrequency={spectrogramMinFrequency}\n maxFrequency={spectrogramMaxFrequency}\n workerApi={spectrogramWorkerApi}\n clipId={spectrogramClipId}\n onCanvasesReady={spectrogramOnCanvasesReady}\n />\n <div\n style={{\n position: 'absolute',\n top: (props.index * 2 + 1) * halfHeight,\n width: props.length,\n height: halfHeight,\n }}\n >\n <Channel\n {...props}\n index={0}\n waveOutlineColor={waveOutlineColor}\n waveFillColor={waveFillColor}\n waveHeight={halfHeight}\n devicePixelRatio={devicePixelRatio}\n barWidth={barWidth}\n barGap={barGap}\n transparentBackground={transparentBackground}\n drawMode={drawMode}\n />\n </div>\n </>\n );\n }\n\n if (renderMode === 'piano-roll') {\n return (\n <PianoRollChannel\n index={props.index}\n midiNotes={midiNotes ?? []}\n length={props.length}\n waveHeight={waveHeight}\n devicePixelRatio={devicePixelRatio}\n samplesPerPixel={samplesPerPixel}\n sampleRate={sampleRateProp ?? contextSampleRate}\n clipOffsetSeconds={clipOffsetSeconds ?? 0}\n noteColor={theme?.pianoRollNoteColor}\n selectedNoteColor={theme?.pianoRollSelectedNoteColor}\n isSelected={isSelected}\n transparentBackground={transparentBackground}\n backgroundColor={theme?.pianoRollBackgroundColor}\n />\n );\n }\n\n // Default: waveform mode\n return (\n <Channel\n {...props}\n waveOutlineColor={waveOutlineColor}\n waveFillColor={waveFillColor}\n waveHeight={waveHeight}\n devicePixelRatio={devicePixelRatio}\n barWidth={barWidth}\n barGap={barGap}\n transparentBackground={transparentBackground}\n drawMode={drawMode}\n />\n );\n};\n","import React, { useRef, useLayoutEffect } from 'react';\nimport styled from 'styled-components';\nimport { useDevicePixelRatio } from '../contexts';\n\nconst LABELS_WIDTH = 72;\n\ninterface LabelsStickyWrapperProps {\n readonly $height: number;\n}\n\nconst LabelsStickyWrapper = styled.div<LabelsStickyWrapperProps>`\n position: sticky;\n left: 0;\n z-index: 101;\n pointer-events: none;\n height: 0;\n width: 0;\n overflow: visible;\n`;\n\nexport interface SpectrogramLabelsProps {\n /** Height per channel in CSS pixels */\n waveHeight: number;\n /** Number of audio channels */\n numChannels: number;\n /** Frequency scale function */\n frequencyScaleFn: (f: number, minF: number, maxF: number) => number;\n /** Min frequency in Hz */\n minFrequency: number;\n /** Max frequency in Hz */\n maxFrequency: number;\n /** Label text color */\n labelsColor?: string;\n /** Label background color */\n labelsBackground?: string;\n /** Render mode — in \"both\" mode spectrogram is half height */\n renderMode?: 'spectrogram' | 'both';\n /** Whether clip headers are shown (adds offset) */\n hasClipHeaders?: boolean;\n}\n\n/** Generate nice frequency labels for the axis, limited by available height */\nfunction getFrequencyLabels(minF: number, maxF: number, height: number): number[] {\n const allCandidates = [\n 20, 50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 8000, 10000, 12000, 16000, 20000,\n ];\n const inRange = allCandidates.filter((f) => f >= minF && f <= maxF);\n\n // Each label needs ~20px of vertical space to avoid overlap\n const maxLabels = Math.max(2, Math.floor(height / 20));\n if (inRange.length <= maxLabels) return inRange;\n\n // Evenly sample from the available candidates\n const step = (inRange.length - 1) / (maxLabels - 1);\n const result: number[] = [];\n for (let i = 0; i < maxLabels; i++) {\n result.push(inRange[Math.round(i * step)]);\n }\n return result;\n}\n\nexport const SpectrogramLabels: React.FC<SpectrogramLabelsProps> = ({\n waveHeight,\n numChannels,\n frequencyScaleFn,\n minFrequency,\n maxFrequency,\n labelsColor = '#ccc',\n labelsBackground = 'rgba(0,0,0,0.6)',\n renderMode = 'spectrogram',\n hasClipHeaders = false,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const devicePixelRatio = useDevicePixelRatio();\n\n const spectrogramHeight = renderMode === 'both' ? Math.floor(waveHeight / 2) : waveHeight;\n\n const totalHeight = numChannels * waveHeight;\n const clipHeaderOffset = hasClipHeaders ? 22 : 0;\n\n useLayoutEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n const labelFreqs = getFrequencyLabels(minFrequency, maxFrequency, spectrogramHeight);\n\n for (let ch = 0; ch < numChannels; ch++) {\n const channelTop = ch * waveHeight + clipHeaderOffset;\n\n ctx.font = '11px monospace';\n ctx.textBaseline = 'middle';\n\n for (const freq of labelFreqs) {\n const normalized = frequencyScaleFn(freq, minFrequency, maxFrequency);\n if (normalized < 0 || normalized > 1) continue;\n const y = channelTop + spectrogramHeight * (1 - normalized);\n\n const text = freq >= 1000 ? `${(freq / 1000).toFixed(1)}k` : `${freq} Hz`;\n const metrics = ctx.measureText(text);\n const padding = 3;\n\n ctx.fillStyle = labelsBackground;\n ctx.fillRect(0, y - 7, metrics.width + padding * 2, 14);\n ctx.fillStyle = labelsColor;\n ctx.fillText(text, padding, y);\n }\n }\n }, [\n waveHeight,\n numChannels,\n frequencyScaleFn,\n minFrequency,\n maxFrequency,\n labelsColor,\n labelsBackground,\n devicePixelRatio,\n spectrogramHeight,\n clipHeaderOffset,\n ]);\n\n return (\n <LabelsStickyWrapper $height={totalHeight + clipHeaderOffset}>\n <canvas\n ref={canvasRef}\n width={LABELS_WIDTH * devicePixelRatio}\n height={(totalHeight + clipHeaderOffset) * devicePixelRatio}\n style={{\n width: LABELS_WIDTH,\n height: totalHeight + clipHeaderOffset,\n pointerEvents: 'none',\n }}\n />\n </LabelsStickyWrapper>\n );\n};\n","import React, { FunctionComponent, useContext, useMemo, type ReactNode } from 'react';\nimport styled from 'styled-components';\nimport { PlaylistInfoContext } from '../contexts/PlaylistInfo';\nimport { useBeatsAndBars } from '../contexts/BeatsAndBars';\nimport { StyledTimeScale } from './TimeScale';\nimport type { PrecomputedTickData } from './TimeScale';\nimport {\n PPQN,\n ticksToSamples,\n ticksToBarBeatLabel,\n samplesToPixels,\n secondsToPixels,\n} from '@waveform-playlist/core';\n\nexport interface SmartScaleProps {\n readonly renderTick?: (label: string, pixelPosition: number) => ReactNode;\n}\n\nconst timeinfo = new Map([\n [700, { marker: 1000, bigStep: 500, smallStep: 100 }],\n [1500, { marker: 2000, bigStep: 1000, smallStep: 200 }],\n [2500, { marker: 2000, bigStep: 1000, smallStep: 500 }],\n [5000, { marker: 5000, bigStep: 1000, smallStep: 500 }],\n [10000, { marker: 10000, bigStep: 5000, smallStep: 1000 }],\n [12000, { marker: 15000, bigStep: 5000, smallStep: 1000 }],\n [Infinity, { marker: 30000, bigStep: 10000, smallStep: 5000 }],\n]);\n\nexport function getScaleInfo(samplesPerPixel: number) {\n const keys = timeinfo.keys();\n let config;\n\n for (const resolution of keys) {\n if (samplesPerPixel < resolution) {\n config = timeinfo.get(resolution);\n break;\n }\n }\n\n if (config === undefined) {\n config = { marker: 30000, bigStep: 10000, smallStep: 5000 };\n }\n return config;\n}\n\nfunction formatTime(milliseconds: number) {\n const seconds = Math.floor(milliseconds / 1000);\n const s = seconds % 60;\n const m = (seconds - s) / 60;\n\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\ninterface TimeStampProps {\n readonly $left: number;\n}\nconst TimeStamp = styled.div.attrs<TimeStampProps>((props) => ({\n style: {\n left: `${props.$left + 4}px`, // Offset 4px to the right of the tick\n },\n}))<TimeStampProps>`\n position: absolute;\n font-size: 0.75rem; /* Smaller font to prevent overflow */\n white-space: nowrap; /* Prevent text wrapping */\n color: ${(props) => props.theme.timeColor}; /* Use theme color instead of inheriting */\n`;\n\nexport const SmartScale: FunctionComponent<SmartScaleProps> = ({ renderTick }) => {\n const { samplesPerPixel, sampleRate, duration, timeScaleHeight } =\n useContext(PlaylistInfoContext);\n const beatsAndBars = useBeatsAndBars();\n\n // Pre-compute tick data for beats & bars mode using integer PPQN math.\n // This avoids millisecond-based modular arithmetic which breaks\n // with non-integer beat durations (e.g., 119 BPM → 504.20ms per beat).\n const tickData = useMemo<PrecomputedTickData>(() => {\n const widthX = secondsToPixels(duration / 1000, samplesPerPixel, sampleRate);\n\n if (beatsAndBars) {\n const { bpm, timeSignature, ticksPerBar: tpBar, ticksPerBeat: tpBeat } = beatsAndBars;\n const canvasInfo = new Map<number, number>();\n const timeMarkersWithPositions: Array<{ pix: number; element: React.ReactNode }> = [];\n\n // Total duration in PPQN ticks\n const durationSeconds = duration / 1000;\n const totalTicks = Math.ceil((durationSeconds * bpm * PPQN) / 60);\n\n // Compute pixel spacing to determine tick density at this zoom level.\n // We pick the finest granularity that keeps ticks >= MIN_TICK_PX apart.\n const pixelsPerBeat = ticksToSamples(tpBeat, bpm, sampleRate) / samplesPerPixel;\n const pixelsPerBar = ticksToSamples(tpBar, bpm, sampleRate) / samplesPerPixel;\n\n const MIN_TICK_PX = 10; // Minimum pixels between tick marks\n const MIN_LABEL_PX = 30; // Minimum pixels between labels\n\n // Find the tick step: beat, bar, or N bars\n let tickStep: number;\n if (pixelsPerBeat >= MIN_TICK_PX) {\n tickStep = tpBeat;\n } else if (pixelsPerBar >= MIN_TICK_PX) {\n tickStep = tpBar;\n } else {\n // Skip bars: find smallest multiplier that gives >= MIN_TICK_PX\n const barsPerTick = Math.ceil(MIN_TICK_PX / pixelsPerBar);\n tickStep = tpBar * barsPerTick;\n }\n\n // Find the label step: beat, bar, or N bars\n let labelStep: number;\n if (pixelsPerBeat >= MIN_LABEL_PX) {\n labelStep = tpBeat;\n } else if (pixelsPerBar >= MIN_LABEL_PX) {\n labelStep = tpBar;\n } else {\n const barsPerLabel = Math.ceil(MIN_LABEL_PX / pixelsPerBar);\n labelStep = tpBar * barsPerLabel;\n }\n\n for (let tick = 0; tick <= totalTicks; tick += tickStep) {\n const samples = ticksToSamples(tick, bpm, sampleRate);\n const pix = samplesToPixels(samples, samplesPerPixel);\n if (pix >= widthX) break;\n\n const isBarLine = tick % tpBar === 0;\n const isLabelTick = tick % labelStep === 0;\n\n // Tick height: bar lines full, labeled ticks half, unlabeled ticks 1/5\n const tickHeight = isBarLine\n ? timeScaleHeight\n : isLabelTick\n ? Math.floor(timeScaleHeight / 2)\n : Math.floor(timeScaleHeight / 5);\n canvasInfo.set(pix, tickHeight);\n\n if (isLabelTick) {\n const label = ticksToBarBeatLabel(tick, timeSignature);\n const element = renderTick ? (\n <React.Fragment key={`bb-${tick}`}>{renderTick(label, pix)}</React.Fragment>\n ) : (\n <div\n key={`bb-${tick}`}\n style={{\n position: 'absolute',\n left: `${pix + 4}px`,\n fontSize: '0.75rem',\n whiteSpace: 'nowrap',\n }}\n >\n {label}\n </div>\n );\n timeMarkersWithPositions.push({ pix, element });\n }\n }\n\n return { widthX, canvasInfo, timeMarkersWithPositions };\n }\n\n // Temporal mode — iterate using timeinfo-based millisecond steps,\n // converting to pixel positions for the tick renderer.\n const config = getScaleInfo(samplesPerPixel);\n const { marker, bigStep, smallStep } = config;\n const canvasInfo = new Map<number, number>();\n const timeMarkersWithPositions: Array<{ pix: number; element: React.ReactNode }> = [];\n const pixPerSec = sampleRate / samplesPerPixel;\n\n let counter = 0;\n for (let i = 0; i < widthX; i += (pixPerSec * smallStep) / 1000) {\n const pix = Math.floor(i);\n\n if (counter % marker === 0) {\n const timestamp = formatTime(counter);\n\n const element = renderTick ? (\n <React.Fragment key={`timestamp-${counter}`}>{renderTick(timestamp, pix)}</React.Fragment>\n ) : (\n <TimeStamp key={timestamp} $left={pix}>\n {timestamp}\n </TimeStamp>\n );\n\n timeMarkersWithPositions.push({ pix, element });\n canvasInfo.set(pix, timeScaleHeight);\n } else if (counter % bigStep === 0) {\n canvasInfo.set(pix, Math.floor(timeScaleHeight / 2));\n } else if (counter % smallStep === 0) {\n canvasInfo.set(pix, Math.floor(timeScaleHeight / 5));\n }\n\n counter += smallStep;\n }\n\n return { widthX, canvasInfo, timeMarkersWithPositions };\n }, [beatsAndBars, duration, samplesPerPixel, sampleRate, timeScaleHeight, renderTick]);\n\n return <StyledTimeScale tickData={tickData} />;\n};\n","import React, { FunctionComponent, useLayoutEffect, useContext } from 'react';\nimport styled, { withTheme, DefaultTheme } from 'styled-components';\nimport { PlaylistInfoContext } from '../contexts/PlaylistInfo';\nimport { useDevicePixelRatio } from '../contexts/DevicePixelRatio';\nimport { useVisibleChunkIndices } from '../contexts/ScrollViewport';\nimport { useChunkedCanvasRefs } from '../hooks/useChunkedCanvasRefs';\nimport { MAX_CANVAS_WIDTH } from '@waveform-playlist/core';\n\ninterface PlaylistTimeScaleScrollProps {\n readonly $cssWidth: number;\n readonly $timeScaleHeight: number;\n}\nconst PlaylistTimeScaleScroll = styled.div.attrs<PlaylistTimeScaleScrollProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$timeScaleHeight}px`,\n },\n}))<PlaylistTimeScaleScrollProps>`\n position: relative;\n overflow: visible; /* Allow time labels to render above the container */\n border-bottom: 1px solid ${(props) => props.theme.timeColor};\n box-sizing: border-box;\n`;\n\ninterface TimeTickChunkProps {\n readonly $cssWidth: number;\n readonly $timeScaleHeight: number;\n readonly $left: number;\n}\nconst TimeTickChunk = styled.canvas.attrs<TimeTickChunkProps>((props) => ({\n style: {\n width: `${props.$cssWidth}px`,\n height: `${props.$timeScaleHeight}px`,\n left: `${props.$left}px`,\n },\n}))<TimeTickChunkProps>`\n position: absolute;\n bottom: 0;\n`;\n\nexport interface PrecomputedTickData {\n widthX: number;\n canvasInfo: Map<number, number>;\n timeMarkersWithPositions: Array<{ pix: number; element: React.ReactNode }>;\n}\n\nexport interface TimeScaleProps {\n readonly theme?: DefaultTheme;\n readonly tickData: PrecomputedTickData;\n}\n\ninterface TimeScalePropsWithTheme extends TimeScaleProps {\n readonly theme: DefaultTheme;\n}\n\nexport const TimeScale: FunctionComponent<TimeScalePropsWithTheme> = (props) => {\n const {\n theme: { timeColor },\n tickData,\n } = props;\n const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();\n const { timeScaleHeight } = useContext(PlaylistInfoContext);\n const devicePixelRatio = useDevicePixelRatio();\n\n const { widthX, canvasInfo, timeMarkersWithPositions } = tickData;\n\n const visibleChunkIndices = useVisibleChunkIndices(widthX, MAX_CANVAS_WIDTH);\n\n // Build visible canvas chunk elements\n const visibleChunks = visibleChunkIndices.map((i) => {\n const chunkLeft = i * MAX_CANVAS_WIDTH;\n const chunkWidth = Math.min(widthX - chunkLeft, MAX_CANVAS_WIDTH);\n\n return (\n <TimeTickChunk\n key={`timescale-${i}`}\n $cssWidth={chunkWidth}\n $left={chunkLeft}\n $timeScaleHeight={timeScaleHeight}\n width={chunkWidth * devicePixelRatio}\n height={timeScaleHeight * devicePixelRatio}\n data-index={i}\n ref={canvasRef}\n />\n );\n });\n\n // Filter time markers to visible chunk range. Uses chunk boundaries\n // rather than exact viewport pixels — sufficient given the 1.5× overscan buffer.\n const firstChunkLeft =\n visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * MAX_CANVAS_WIDTH : 0;\n const lastChunkRight =\n visibleChunkIndices.length > 0\n ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * MAX_CANVAS_WIDTH\n : Infinity;\n\n const visibleMarkers =\n visibleChunkIndices.length > 0\n ? timeMarkersWithPositions\n .filter(({ pix }) => pix >= firstChunkLeft && pix < lastChunkRight)\n .map(({ element }) => element)\n : timeMarkersWithPositions.map(({ element }) => element);\n\n // Draw tick marks on visible canvas chunks.\n // visibleChunkIndices changes only when chunks mount/unmount, not on every scroll pixel.\n useLayoutEffect(() => {\n for (const [chunkIdx, canvas] of canvasMapRef.current.entries()) {\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n\n const chunkLeft = chunkIdx * MAX_CANVAS_WIDTH;\n const chunkWidth = canvas.width / devicePixelRatio;\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.imageSmoothingEnabled = false;\n ctx.fillStyle = timeColor;\n ctx.scale(devicePixelRatio, devicePixelRatio);\n\n for (const [pixLeft, scaleHeight] of canvasInfo.entries()) {\n // Only draw ticks within this chunk's range\n if (pixLeft < chunkLeft || pixLeft >= chunkLeft + chunkWidth) continue;\n\n const localX = pixLeft - chunkLeft;\n const scaleY = timeScaleHeight - scaleHeight;\n ctx.fillRect(localX, scaleY, 1, scaleHeight);\n }\n }\n }, [canvasMapRef, devicePixelRatio, timeColor, timeScaleHeight, canvasInfo, visibleChunkIndices]);\n\n return (\n <PlaylistTimeScaleScroll $cssWidth={widthX} $timeScaleHeight={timeScaleHeight}>\n {visibleMarkers}\n {visibleChunks}\n </PlaylistTimeScaleScroll>\n );\n};\n\nexport const StyledTimeScale = withTheme(TimeScale) as FunctionComponent<TimeScaleProps>;\n","import React from 'react';\nimport styled from 'styled-components';\nimport { type TimeFormat } from '../utils/timeFormat';\nimport { BaseSelect } from '../styled/index';\n\nconst SelectWrapper = styled.div`\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n`;\n\nexport interface TimeFormatSelectProps {\n value: TimeFormat;\n onChange: (format: TimeFormat) => void;\n disabled?: boolean;\n className?: string;\n}\n\nconst TIME_FORMAT_OPTIONS: { value: TimeFormat; label: string }[] = [\n { value: 'seconds', label: 'seconds' },\n { value: 'thousandths', label: 'thousandths' },\n { value: 'hh:mm:ss', label: 'hh:mm:ss' },\n { value: 'hh:mm:ss.u', label: 'hh:mm:ss + tenths' },\n { value: 'hh:mm:ss.uu', label: 'hh:mm:ss + hundredths' },\n { value: 'hh:mm:ss.uuu', label: 'hh:mm:ss + milliseconds' },\n];\n\n/**\n * Dropdown select for choosing time display format\n */\nexport const TimeFormatSelect: React.FC<TimeFormatSelectProps> = ({\n value,\n onChange,\n disabled = false,\n className,\n}) => {\n const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {\n onChange(e.target.value as TimeFormat);\n };\n\n return (\n <SelectWrapper className={className}>\n <BaseSelect\n className=\"time-format\"\n value={value}\n onChange={handleChange}\n disabled={disabled}\n aria-label=\"Time format selection\"\n >\n {TIME_FORMAT_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </BaseSelect>\n </SelectWrapper>\n );\n};\n","import React, { FunctionComponent, ReactNode } from 'react';\nimport styled from 'styled-components';\nimport { usePlaylistInfo } from '../contexts/PlaylistInfo';\nimport { CLIP_HEADER_HEIGHT } from './ClipHeader';\n\ninterface ContainerProps {\n readonly $numChannels: number;\n readonly $waveHeight: number;\n readonly $width?: number;\n}\n\ninterface ContainerWithHeaderProps extends ContainerProps {\n readonly $hasClipHeaders: boolean;\n}\n\nconst Container = styled.div.attrs<ContainerWithHeaderProps>((props) => ({\n style: {\n height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`,\n },\n}))<ContainerWithHeaderProps>`\n position: relative;\n ${(props) => props.$width !== undefined && `width: ${props.$width}px;`}\n`;\n\ninterface ChannelContainerProps {\n readonly $backgroundColor?: string;\n readonly $offset?: number;\n}\nconst ChannelContainer = styled.div.attrs<ChannelContainerProps>((props) => ({\n style: {\n paddingLeft: `${props.$offset || 0}px`,\n },\n}))<ChannelContainerProps>`\n position: relative;\n background: ${(props) => props.$backgroundColor || 'transparent'};\n height: 100%;\n`;\n\nexport interface TrackProps {\n className?: string;\n children?: ReactNode;\n numChannels: number;\n backgroundColor?: string;\n offset?: number; // Offset in pixels to shift the waveform right\n width?: number; // Total width of the track (for consistent backgrounds across tracks)\n hasClipHeaders?: boolean; // Whether clips have headers (for multi-clip editing)\n onClick?: () => void; // Called when track is clicked (for track selection)\n trackId?: string; // Track ID for identifying which track was clicked\n isSelected?: boolean; // Whether this track is currently selected (for visual feedback)\n}\n\nexport const Track: FunctionComponent<TrackProps> = ({\n numChannels,\n children,\n className,\n backgroundColor,\n offset = 0,\n width,\n hasClipHeaders = false,\n onClick,\n trackId,\n isSelected: _isSelected = false,\n}) => {\n const { waveHeight } = usePlaylistInfo();\n return (\n <Container\n $numChannels={numChannels}\n className={className}\n $waveHeight={waveHeight}\n $width={width}\n $hasClipHeaders={hasClipHeaders}\n >\n <ChannelContainer\n $backgroundColor={backgroundColor}\n $offset={offset}\n onClick={onClick}\n data-track-id={trackId}\n >\n {children}\n </ChannelContainer>\n </Container>\n );\n};\n","import styled from 'styled-components';\n\n/**\n * TrackControls Button - Small button for track controls (Mute, Solo, etc.)\n *\n * Supports variants: outline (default), danger, info\n * Uses theme values for consistent styling.\n */\nexport const Button = styled.button.attrs({\n type: 'button',\n})<{ $variant?: 'outline' | 'danger' | 'info' }>`\n display: inline-block;\n font-family: ${(props) => props.theme.fontFamily};\n font-weight: 500;\n text-align: center;\n vertical-align: middle;\n user-select: none;\n padding: 0.25rem 0.4rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n line-height: 1;\n border-radius: ${(props) => props.theme.borderRadius};\n transition:\n color 0.15s ease-in-out,\n background-color 0.15s ease-in-out,\n border-color 0.15s ease-in-out,\n box-shadow 0.15s ease-in-out;\n cursor: pointer;\n\n ${(props) => {\n if (props.$variant === 'danger') {\n return `\n color: #fff;\n background-color: #dc3545;\n border: 1px solid #dc3545;\n\n &:hover {\n background-color: #c82333;\n border-color: #bd2130;\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n }\n `;\n } else if (props.$variant === 'info') {\n return `\n color: #fff;\n background-color: #17a2b8;\n border: 1px solid #17a2b8;\n\n &:hover {\n background-color: #138496;\n border-color: #117a8b;\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n }\n `;\n } else {\n // outline variant (default) - uses theme colors\n return `\n color: ${props.theme.textColor};\n background-color: transparent;\n border: 1px solid ${props.theme.borderColor};\n\n &:hover {\n color: #fff;\n background-color: ${props.theme.textColor};\n border-color: ${props.theme.textColor};\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 0.2rem ${props.theme.inputFocusBorder}33;\n }\n `;\n }\n }}\n`;\n","import styled from 'styled-components';\n\nexport const ButtonGroup = styled.div`\n margin-bottom: 0.3rem;\n\n button:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n\n button:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n`;\n","import React from 'react';\nimport styled from 'styled-components';\nimport { X as XIcon } from '@phosphor-icons/react';\n\nconst StyledCloseButton = styled.button`\n position: absolute;\n left: 0;\n top: 0;\n border: none;\n background: transparent;\n color: inherit;\n cursor: pointer;\n font-size: 16px;\n padding: 2px 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n opacity: 0.7;\n transition:\n opacity 0.15s,\n color 0.15s;\n\n &:hover {\n opacity: 1;\n color: #dc3545;\n }\n`;\n\nexport interface CloseButtonProps {\n onClick: (e: React.MouseEvent) => void;\n title?: string;\n}\n\nexport const CloseButton: React.FC<CloseButtonProps> = ({ onClick, title = 'Remove track' }) => (\n <StyledCloseButton onClick={onClick} title={title}>\n <XIcon size={12} weight=\"bold\" />\n </StyledCloseButton>\n);\n","import styled from 'styled-components';\n\nexport const Controls = styled.div`\n background: transparent;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-start;\n overflow: hidden;\n box-sizing: border-box;\n text-align: center;\n border: 1px solid ${(props) => props.theme.borderColor};\n border-radius: ${(props) => props.theme.borderRadius};\n`;\n","import styled from 'styled-components';\n\nexport const Header = styled.header`\n overflow: hidden;\n height: 26px;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 0.2rem;\n font-size: ${(props) => props.theme.fontSizeSmall};\n color: ${(props) => props.theme.textColor};\n background-color: transparent;\n`;\n","import React from 'react';\nimport { SpeakerLowIcon, type IconProps } from '@phosphor-icons/react';\n\nexport const VolumeDownIcon: React.FC<IconProps> = (props) => (\n <SpeakerLowIcon weight=\"light\" {...props} />\n);\n","import React from 'react';\nimport { SpeakerHighIcon, type IconProps } from '@phosphor-icons/react';\n\nexport const VolumeUpIcon: React.FC<IconProps> = (props) => (\n <SpeakerHighIcon weight=\"light\" {...props} />\n);\n","import React from 'react';\nimport { TrashIcon as PhosphorTrashIcon, type IconProps } from '@phosphor-icons/react';\n\nexport const TrashIcon: React.FC<IconProps> = (props) => (\n <PhosphorTrashIcon weight=\"light\" {...props} />\n);\n","import React from 'react';\nimport { DotsThreeIcon, type IconProps } from '@phosphor-icons/react';\n\nexport const DotsIcon: React.FC<IconProps> = (props) => <DotsThreeIcon weight=\"bold\" {...props} />;\n","import styled from 'styled-components';\nimport { BaseSlider } from '../../styled/index';\n\n/**\n * TrackControls Slider - Compact slider for volume/pan controls\n *\n * Extends BaseSlider with track-specific styling:\n * - Smaller thumb and track for compact layout\n * - Uses theme's sliderThumbColor (goldenrod by default)\n */\nexport const Slider = styled(BaseSlider)`\n width: 75%;\n height: 5px;\n background: ${(props) => props.theme.sliderTrackColor};\n\n &::-webkit-slider-thumb {\n width: 12px;\n height: 12px;\n background: ${(props) => props.theme.sliderThumbColor};\n border: none;\n margin-top: -4px;\n cursor: ew-resize;\n }\n\n &::-moz-range-thumb {\n width: 12px;\n height: 12px;\n background: ${(props) => props.theme.sliderThumbColor};\n border: none;\n cursor: ew-resize;\n }\n\n &::-webkit-slider-runnable-track {\n height: 5px;\n background: ${(props) => props.theme.sliderTrackColor};\n border-radius: 3px;\n }\n\n &::-moz-range-track {\n height: 5px;\n background: ${(props) => props.theme.sliderTrackColor};\n border-radius: 3px;\n }\n\n &:focus::-webkit-slider-runnable-track {\n background: ${(props) => props.theme.inputBorder};\n }\n\n &:focus::-moz-range-track {\n background: ${(props) => props.theme.inputBorder};\n }\n\n &:focus::-webkit-slider-thumb {\n border: 2px solid ${(props) => props.theme.textColor};\n }\n\n &:focus::-moz-range-thumb {\n border: 2px solid ${(props) => props.theme.textColor};\n }\n`;\n","import styled from 'styled-components';\n\nexport const SliderWrapper = styled.label`\n width: 100%;\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0 1rem;\n margin-bottom: 0.2rem;\n font-size: 14px;\n`;\n","import React, { useState, useEffect, useRef, useCallback, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport styled from 'styled-components';\nimport { DotsIcon } from './TrackControls/DotsIcon';\n\nexport interface TrackMenuItem {\n id: string;\n label?: string;\n content: ReactNode;\n}\n\nexport interface TrackMenuProps {\n items: TrackMenuItem[] | ((onClose: () => void) => TrackMenuItem[]);\n}\n\nconst MenuContainer = styled.div`\n position: relative;\n display: inline-block;\n`;\n\nconst MenuButton = styled.button`\n background: none;\n border: none;\n cursor: pointer;\n padding: 2px 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: inherit;\n opacity: 0.7;\n\n &:hover {\n opacity: 1;\n }\n`;\n\nconst DROPDOWN_MIN_WIDTH = 180;\n\nconst Dropdown = styled.div<{ $top: number; $left: number }>`\n position: fixed;\n top: ${(p) => p.$top}px;\n left: ${(p) => p.$left}px;\n z-index: 10000;\n background: ${(p) => p.theme.timescaleBackgroundColor ?? '#222'};\n color: ${(p) => p.theme.textColor ?? 'inherit'};\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 6px;\n padding: 0.5rem 0;\n min-width: ${DROPDOWN_MIN_WIDTH}px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n`;\n\nconst Divider = styled.hr`\n border: none;\n border-top: 1px solid rgba(128, 128, 128, 0.3);\n margin: 0.35rem 0;\n`;\n\nexport const TrackMenu: React.FC<TrackMenuProps> = ({ items: itemsProp }) => {\n const [open, setOpen] = useState(false);\n const close = useCallback(() => setOpen(false), []);\n const items = typeof itemsProp === 'function' ? itemsProp(close) : itemsProp;\n const [dropdownPos, setDropdownPos] = useState({ top: 0, left: 0 });\n const buttonRef = useRef<HTMLButtonElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n const updatePosition = useCallback(() => {\n if (!buttonRef.current) return;\n const rect = buttonRef.current.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const dropHeight = dropdownRef.current?.offsetHeight ?? 160;\n\n // Prefer opening to the right of the button\n let left = rect.right + 4;\n if (left + DROPDOWN_MIN_WIDTH > vw) {\n left = rect.left - DROPDOWN_MIN_WIDTH - 4;\n }\n left = Math.max(4, Math.min(left, vw - DROPDOWN_MIN_WIDTH - 4));\n\n // Align top with the button, push up if it would overflow viewport\n let top = rect.top;\n if (top + dropHeight > vh - 4) {\n top = Math.max(4, rect.bottom - dropHeight);\n }\n\n setDropdownPos({ top, left });\n }, []);\n\n // Position on open, refine after mount, reposition on scroll/resize\n useEffect(() => {\n if (!open) return;\n updatePosition();\n\n // Refine once the dropdown has mounted and has actual dimensions\n const rafId = requestAnimationFrame(() => updatePosition());\n\n const onScroll = () => updatePosition();\n const onResize = () => updatePosition();\n window.addEventListener('scroll', onScroll, true);\n window.addEventListener('resize', onResize);\n return () => {\n cancelAnimationFrame(rafId);\n window.removeEventListener('scroll', onScroll, true);\n window.removeEventListener('resize', onResize);\n };\n }, [open, updatePosition]);\n\n // Close on outside click or Escape\n useEffect(() => {\n if (!open) return;\n\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n buttonRef.current &&\n !buttonRef.current.contains(target) &&\n dropdownRef.current &&\n !dropdownRef.current.contains(target)\n ) {\n setOpen(false);\n }\n };\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n setOpen(false);\n }\n };\n document.addEventListener('mousedown', handleClick);\n document.addEventListener('keydown', handleKeyDown);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n document.removeEventListener('keydown', handleKeyDown);\n };\n }, [open]);\n\n return (\n <MenuContainer>\n <MenuButton\n ref={buttonRef}\n onClick={(e) => {\n e.stopPropagation();\n setOpen((prev) => !prev);\n }}\n onMouseDown={(e) => e.stopPropagation()}\n title=\"Track menu\"\n aria-label=\"Track menu\"\n >\n <DotsIcon size={16} />\n </MenuButton>\n {open &&\n typeof document !== 'undefined' &&\n createPortal(\n <Dropdown\n ref={dropdownRef}\n $top={dropdownPos.top}\n $left={dropdownPos.left}\n onMouseDown={(e) => e.stopPropagation()}\n >\n {items.map((item, index) => (\n <React.Fragment key={item.id}>\n {index > 0 && <Divider />}\n {item.content}\n </React.Fragment>\n ))}\n </Dropdown>,\n document.body\n )}\n </MenuContainer>\n );\n};\n","export function samplesToSeconds(samples: number, sampleRate: number) {\n return samples / sampleRate;\n}\n\nexport function secondsToSamples(seconds: number, sampleRate: number) {\n return Math.ceil(seconds * sampleRate);\n}\n\nexport function samplesToPixels(samples: number, samplesPerPixel: number) {\n return Math.floor(samples / samplesPerPixel);\n}\n\nexport function pixelsToSamples(pixels: number, samplesPerPixel: number) {\n return Math.floor(pixels * samplesPerPixel);\n}\n\nexport function pixelsToSeconds(pixels: number, samplesPerPixel: number, sampleRate: number) {\n return (pixels * samplesPerPixel) / sampleRate;\n}\n\nexport function secondsToPixels(seconds: number, samplesPerPixel: number, sampleRate: number) {\n return Math.ceil((seconds * sampleRate) / samplesPerPixel);\n}\n"],"mappings":"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","names":["samplesToPixels","secondsToPixels","useTheme","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","styled","import_jsx_runtime","import_react","import_styled_components","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","styled","import_react","import_jsx_runtime","React","import_styled_components","import_react","import_styled_components","import_jsx_runtime","styled","import_react","import_styled_components","import_jsx_runtime","styled","React","import_styled_components","import_jsx_runtime","styled","import_core","import_jsx_runtime","styled","import_styled_components","import_jsx_runtime","styled","import_react","import_styled_components","import_core","import_jsx_runtime","styled","Wrapper","import_react","import_styled_components","import_jsx_runtime","styled","import_styled_components","import_react","import_jsx_runtime","Wrapper","styled","import_styled_components","import_jsx_runtime","styled","import_react","import_styled_components","import_jsx_runtime","styled","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_core","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_react","import_styled_components","useTheme","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_styled_components","import_core","import_jsx_runtime","Wrapper","styled","import_jsx_runtime","useTheme","import_react","import_styled_components","import_jsx_runtime","styled","import_react","import_styled_components","import_react","import_styled_components","import_core","import_jsx_runtime","styled","import_core","import_jsx_runtime","formatTime","styled","canvasInfo","timeMarkersWithPositions","React","import_styled_components","import_jsx_runtime","styled","import_styled_components","import_jsx_runtime","styled","import_styled_components","styled","import_styled_components","styled","import_styled_components","import_react","import_jsx_runtime","styled","XIcon","import_styled_components","styled","import_styled_components","styled","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","PhosphorTrashIcon","import_react","import_jsx_runtime","import_styled_components","styled","import_styled_components","styled","import_react","import_styled_components","import_jsx_runtime","styled","React","samplesToPixels","secondsToPixels"]}