@waveform-playlist/spectrogram 13.0.1 → 13.0.3

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/SpectrogramMenuItems.tsx","../src/components/SpectrogramSettingsModal.tsx","../src/SpectrogramProvider.tsx"],"sourcesContent":["import React from 'react';\nimport styled from 'styled-components';\nimport type { RenderMode } from '@waveform-playlist/core';\nimport type { TrackMenuItem } from './types';\n\nconst SectionLabel = styled.div`\n font-size: 0.65rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n opacity: 0.5;\n margin-bottom: 0.25rem;\n`;\n\nconst RadioLabel = styled.label`\n display: flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.2rem 0;\n font-size: 0.8rem;\n cursor: pointer;\n\n &:hover {\n opacity: 0.8;\n }\n`;\n\nconst SettingsButton = styled.button`\n background: none;\n border: none;\n color: inherit;\n cursor: pointer;\n font-size: 0.8rem;\n padding: 0.35rem 0.75rem;\n width: 100%;\n text-align: left;\n\n &:hover {\n background: rgba(128, 128, 128, 0.15);\n }\n`;\n\nconst DropdownSection = styled.div`\n padding: 0.25rem 0.75rem;\n`;\n\nconst RENDER_MODES: { value: RenderMode; label: string }[] = [\n { value: 'waveform', label: 'Waveform' },\n { value: 'spectrogram', label: 'Spectrogram' },\n { value: 'both', label: 'Both' },\n];\n\nexport interface SpectrogramMenuItemsProps {\n renderMode: RenderMode;\n onRenderModeChange: (mode: RenderMode) => void;\n onOpenSettings: () => void;\n onClose?: () => void;\n}\n\n/**\n * Returns TrackMenuItem[] for the spectrogram display mode radios and settings button.\n */\nexport function SpectrogramMenuItems({\n renderMode,\n onRenderModeChange,\n onOpenSettings,\n onClose,\n}: SpectrogramMenuItemsProps): TrackMenuItem[] {\n return [\n {\n id: 'spectrogram-display',\n label: 'Display',\n content: (\n <DropdownSection>\n <SectionLabel>Display</SectionLabel>\n {RENDER_MODES.map(({ value, label }) => (\n <RadioLabel key={value}>\n <input\n type=\"radio\"\n name=\"render-mode\"\n checked={renderMode === value}\n onChange={() => {\n onRenderModeChange(value);\n onClose?.();\n }}\n />\n {label}\n </RadioLabel>\n ))}\n </DropdownSection>\n ),\n },\n {\n id: 'spectrogram-settings',\n content: (\n <SettingsButton\n onClick={(e) => {\n e.stopPropagation();\n onClose?.();\n onOpenSettings();\n }}\n onMouseDown={(e) => e.stopPropagation()}\n >\n Spectrogram Settings...\n </SettingsButton>\n ),\n },\n ];\n}\n","import React, { useRef, useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport type {\n SpectrogramConfig,\n ColorMapValue,\n ColorMapName,\n FFTSize,\n} from '@waveform-playlist/core';\n\nexport interface SpectrogramSettingsModalProps {\n open: boolean;\n onClose: () => void;\n config: SpectrogramConfig;\n colorMap: ColorMapValue;\n onApply: (config: SpectrogramConfig, colorMap: ColorMapValue) => void;\n}\n\nconst StyledDialog = styled.dialog`\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 8px;\n padding: 1.5rem;\n background: ${(p) => p.theme.timescaleBackgroundColor ?? '#222'};\n color: ${(p) => p.theme.textColor ?? 'inherit'};\n min-width: 380px;\n max-width: 500px;\n\n &::backdrop {\n background: rgba(0, 0, 0, 0.5);\n }\n`;\n\nconst Title = styled.h3`\n margin: 0 0 1rem;\n font-size: 1rem;\n`;\n\nconst FormGrid = styled.div`\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0.75rem;\n`;\n\nconst Field = styled.div<{ $span?: boolean }>`\n display: flex;\n flex-direction: column;\n gap: 0.2rem;\n grid-column: ${(p) => (p.$span ? '1 / -1' : 'auto')};\n`;\n\nconst Label = styled.label`\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n opacity: 0.6;\n`;\n\nconst Select = styled.select`\n padding: 0.3rem 0.4rem;\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 4px;\n background: rgba(128, 128, 128, 0.15);\n color: inherit;\n font-size: 0.85rem;\n`;\n\nconst NumberInput = styled.input`\n padding: 0.3rem 0.4rem;\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 4px;\n background: rgba(128, 128, 128, 0.15);\n color: inherit;\n font-size: 0.85rem;\n width: 100%;\n box-sizing: border-box;\n`;\n\nconst CheckboxLabel = styled.label`\n display: flex;\n align-items: center;\n gap: 0.4rem;\n font-size: 0.85rem;\n cursor: pointer;\n`;\n\nconst ButtonRow = styled.div`\n display: flex;\n justify-content: flex-end;\n gap: 0.5rem;\n margin-top: 1.25rem;\n`;\n\nconst ModalButton = styled.button<{ $primary?: boolean }>`\n padding: 0.4rem 1rem;\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 4px;\n cursor: pointer;\n font-size: 0.85rem;\n background: ${(p) => (p.$primary ? 'var(--ifm-color-primary, #4a9)' : 'transparent')};\n color: ${(p) => (p.$primary ? '#fff' : 'inherit')};\n\n &:hover {\n opacity: 0.85;\n }\n`;\n\nconst FFT_SIZES = [256, 512, 1024, 2048, 4096, 8192];\nconst WINDOW_FUNCTIONS = [\n 'hann',\n 'hamming',\n 'blackman',\n 'blackman-harris',\n 'bartlett',\n 'rectangular',\n] as const;\nconst FREQ_SCALES = ['linear', 'logarithmic', 'mel', 'bark', 'erb'] as const;\nconst COLOR_MAPS: ColorMapName[] = ['viridis', 'magma', 'inferno', 'grayscale', 'igray', 'roseus'];\n\nexport const SpectrogramSettingsModal: React.FC<SpectrogramSettingsModalProps> = ({\n open,\n onClose,\n config,\n colorMap,\n onApply,\n}) => {\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n // Local form state\n const [fftSize, setFftSize] = useState(config.fftSize ?? 2048);\n const [windowFn, setWindowFn] = useState(config.windowFunction ?? 'hann');\n const [freqScale, setFreqScale] = useState(config.frequencyScale ?? 'mel');\n const [localColorMap, setLocalColorMap] = useState<ColorMapName>(\n typeof colorMap === 'string' ? colorMap : 'viridis'\n );\n const [minFreq, setMinFreq] = useState(config.minFrequency ?? 0);\n const [maxFreq, setMaxFreq] = useState(config.maxFrequency ?? 20000);\n const [gainDb, setGainDb] = useState(config.gainDb ?? 20);\n const [rangeDb, setRangeDb] = useState(config.rangeDb ?? 80);\n const [zeroPadding, setZeroPadding] = useState(config.zeroPaddingFactor ?? 2);\n const [hopSize, setHopSize] = useState(\n config.hopSize ?? Math.floor((config.fftSize ?? 2048) / 4)\n );\n const [showLabels, setShowLabels] = useState(config.labels ?? false);\n\n // Sync local state when props change\n useEffect(() => {\n setFftSize(config.fftSize ?? 2048);\n setWindowFn(config.windowFunction ?? 'hann');\n setFreqScale(config.frequencyScale ?? 'mel');\n setLocalColorMap(typeof colorMap === 'string' ? colorMap : 'viridis');\n setMinFreq(config.minFrequency ?? 0);\n setMaxFreq(config.maxFrequency ?? 20000);\n setGainDb(config.gainDb ?? 20);\n setRangeDb(config.rangeDb ?? 80);\n setZeroPadding(config.zeroPaddingFactor ?? 2);\n setHopSize(config.hopSize ?? Math.floor((config.fftSize ?? 2048) / 4));\n setShowLabels(config.labels ?? false);\n }, [config, colorMap]);\n\n // Open/close dialog + handle native close (Escape key)\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (open && !dialog.open) {\n dialog.showModal();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n\n const handleClose = () => {\n // Only call onClose if dialog was open (avoids double-fire)\n if (open) {\n onClose();\n }\n };\n dialog.addEventListener('close', handleClose);\n return () => dialog.removeEventListener('close', handleClose);\n }, [open, onClose]);\n\n const handleApply = () => {\n onApply(\n {\n fftSize,\n windowFunction: windowFn as SpectrogramConfig['windowFunction'],\n frequencyScale: freqScale as SpectrogramConfig['frequencyScale'],\n minFrequency: minFreq,\n maxFrequency: maxFreq,\n gainDb,\n rangeDb,\n zeroPaddingFactor: zeroPadding,\n hopSize,\n labels: showLabels,\n },\n localColorMap\n );\n onClose();\n };\n\n return (\n <StyledDialog ref={dialogRef}>\n <Title>Spectrogram Settings</Title>\n <FormGrid>\n <Field>\n <Label>FFT Size</Label>\n <Select\n value={fftSize}\n onChange={(e) => {\n const v = Number(e.target.value) as FFTSize;\n setFftSize(v);\n setHopSize(Math.floor(v / 4));\n }}\n >\n {FFT_SIZES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Hop Size</Label>\n <Select value={hopSize} onChange={(e) => setHopSize(Number(e.target.value))}>\n {[\n { label: `${fftSize} (no overlap)`, value: fftSize },\n { label: `${Math.floor(fftSize / 2)} (50%)`, value: Math.floor(fftSize / 2) },\n { label: `${Math.floor(fftSize / 4)} (75%)`, value: Math.floor(fftSize / 4) },\n { label: `${Math.floor(fftSize / 8)} (87.5%)`, value: Math.floor(fftSize / 8) },\n ].map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Zero Padding</Label>\n <Select value={zeroPadding} onChange={(e) => setZeroPadding(Number(e.target.value))}>\n {[1, 2, 4, 8, 16].map((v) => (\n <option key={v} value={v}>\n {v}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Window Function</Label>\n <Select value={windowFn} onChange={(e) => setWindowFn(e.target.value as typeof windowFn)}>\n {WINDOW_FUNCTIONS.map((w) => (\n <option key={w} value={w}>\n {w}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Frequency Scale</Label>\n <Select\n value={freqScale}\n onChange={(e) => setFreqScale(e.target.value as typeof freqScale)}\n >\n {FREQ_SCALES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Color Map</Label>\n <Select\n value={localColorMap}\n onChange={(e) => setLocalColorMap(e.target.value as ColorMapName)}\n >\n {COLOR_MAPS.map((c) => (\n <option key={c} value={c}>\n {c}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Min Frequency (Hz)</Label>\n <NumberInput\n type=\"number\"\n min={0}\n max={5000}\n step={50}\n value={minFreq}\n onChange={(e) => setMinFreq(Number(e.target.value))}\n />\n </Field>\n\n <Field>\n <Label>Max Frequency (Hz)</Label>\n <NumberInput\n type=\"number\"\n min={1000}\n max={22050}\n step={50}\n value={maxFreq}\n onChange={(e) => setMaxFreq(Number(e.target.value))}\n />\n </Field>\n\n <Field>\n <Label>Range (dB)</Label>\n <NumberInput\n type=\"number\"\n min={1}\n max={120}\n step={1}\n value={rangeDb}\n onChange={(e) => setRangeDb(Number(e.target.value))}\n />\n </Field>\n\n <Field>\n <Label>Gain (dB)</Label>\n <NumberInput\n type=\"number\"\n min={-20}\n max={60}\n step={1}\n value={gainDb}\n onChange={(e) => setGainDb(Number(e.target.value))}\n />\n </Field>\n\n <Field $span>\n <CheckboxLabel>\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => setShowLabels(e.target.checked)}\n />\n Show Frequency Labels\n </CheckboxLabel>\n </Field>\n </FormGrid>\n\n <ButtonRow>\n <ModalButton onClick={onClose}>Cancel</ModalButton>\n <ModalButton $primary onClick={handleApply}>\n Apply\n </ModalButton>\n </ButtonRow>\n </StyledDialog>\n );\n};\n","import React, { useState, useEffect, useRef, useCallback, useMemo, type ReactNode } from 'react';\nimport {\n MAX_CANVAS_WIDTH,\n type SpectrogramConfig,\n type SpectrogramComputeConfig,\n type ColorMapValue,\n type RenderMode,\n type TrackSpectrogramOverrides,\n} from '@waveform-playlist/core';\nimport {\n getColorMap,\n getFrequencyScale,\n createSpectrogramWorkerPool,\n SpectrogramAbortError,\n type SpectrogramWorkerApi,\n} from '@dawcore/spectrogram';\nimport { SpectrogramMenuItems } from './components';\nimport { SpectrogramSettingsModal } from './components';\nimport {\n SpectrogramIntegrationProvider,\n type SpectrogramIntegration,\n type SpectrogramCanvasRegistration,\n} from '@waveform-playlist/browser';\nimport { usePlaylistData, usePlaylistControls } from '@waveform-playlist/browser';\n\n/** Extract the chunk number from a canvas ID like \"clipId-ch0-chunk5\" → 5 */\nfunction extractChunkNumber(canvasId: string): number {\n const match = canvasId.match(/chunk(\\d+)$/);\n if (!match) {\n console.warn(`[spectrogram] Unexpected canvas ID format: ${canvasId}`);\n return 0;\n }\n return parseInt(match[1], 10);\n}\n\nexport interface SpectrogramProviderProps {\n config?: SpectrogramConfig;\n colorMap?: ColorMapValue;\n /** Number of Web Workers for parallel FFT computation. Defaults to 2 (one per stereo channel). */\n workerPoolSize?: number;\n children: ReactNode;\n}\n\nexport const SpectrogramProvider: React.FC<SpectrogramProviderProps> = ({\n config: spectrogramConfig,\n colorMap: spectrogramColorMap,\n workerPoolSize,\n children,\n}) => {\n const { tracks, waveHeight, samplesPerPixel, isReady, mono } = usePlaylistData();\n const { scrollContainerRef } = usePlaylistControls();\n\n // State\n const [trackSpectrogramOverrides, setTrackSpectrogramOverrides] = useState<\n Map<string, TrackSpectrogramOverrides>\n >(new Map());\n\n // OffscreenCanvas registry for worker-rendered spectrograms\n const spectrogramCanvasRegistryRef = useRef<\n Map<string, Map<number, { canvasIds: string[]; canvasWidths: number[] }>>\n >(new Map());\n const [spectrogramCanvasVersion, setSpectrogramCanvasVersion] = useState(0);\n\n // Spectrogram refs\n const prevSpectrogramConfigRef = useRef<Map<string, string>>(new Map());\n const prevSpectrogramFFTKeyRef = useRef<Map<string, string>>(new Map());\n const spectrogramWorkerRef = useRef<SpectrogramWorkerApi | null>(null);\n const spectrogramGenerationRef = useRef(0);\n const prevCanvasVersionRef = useRef(0);\n const renderedClipIdsRef = useRef<Set<string>>(new Set());\n const backgroundRenderAbortRef = useRef<{ aborted: boolean } | null>(null);\n const registeredAudioClipIdsRef = useRef<Set<string>>(new Set());\n\n // Terminate worker on unmount\n useEffect(() => {\n return () => {\n spectrogramWorkerRef.current?.terminate();\n spectrogramWorkerRef.current = null;\n };\n }, []);\n\n // Eagerly transfer audio data to worker when tracks load\n useEffect(() => {\n if (!isReady || tracks.length === 0) return;\n\n let workerApi = spectrogramWorkerRef.current;\n if (!workerApi) {\n try {\n workerApi = createSpectrogramWorkerPool(\n () =>\n new Worker(new URL('@dawcore/spectrogram/worker/spectrogram.worker', import.meta.url), {\n type: 'module',\n }),\n workerPoolSize\n );\n spectrogramWorkerRef.current = workerApi;\n } catch (err) {\n console.warn(\n `[waveform-playlist] Spectrogram Web Worker unavailable for pre-transfer: ${err instanceof Error ? err.message : String(err)}`\n );\n return;\n }\n }\n\n const currentClipIds = new Set<string>();\n\n for (const track of tracks) {\n for (const clip of track.clips) {\n if (!clip.audioBuffer) continue;\n currentClipIds.add(clip.id);\n\n if (!registeredAudioClipIdsRef.current.has(clip.id)) {\n const channelDataArrays: Float32Array[] = [];\n for (let ch = 0; ch < clip.audioBuffer.numberOfChannels; ch++) {\n channelDataArrays.push(clip.audioBuffer.getChannelData(ch));\n }\n workerApi.registerAudioData(clip.id, channelDataArrays, clip.audioBuffer.sampleRate);\n registeredAudioClipIdsRef.current.add(clip.id);\n }\n }\n }\n\n for (const clipId of registeredAudioClipIdsRef.current) {\n if (!currentClipIds.has(clipId)) {\n workerApi.unregisterAudioData(clipId);\n registeredAudioClipIdsRef.current.delete(clipId);\n }\n }\n // workerPoolSize intentionally omitted — pool is created once via spectrogramWorkerRef guard\n }, [isReady, tracks]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Main spectrogram computation effect\n useEffect(() => {\n if (tracks.length === 0) return;\n\n const currentKeys = new Map<string, string>();\n const currentFFTKeys = new Map<string, string>();\n tracks.forEach((track) => {\n const mode =\n trackSpectrogramOverrides.get(track.id)?.renderMode ?? track.renderMode ?? 'waveform';\n if (mode === 'waveform') return;\n const cfg =\n trackSpectrogramOverrides.get(track.id)?.config ??\n track.spectrogramConfig ??\n spectrogramConfig;\n const cm =\n trackSpectrogramOverrides.get(track.id)?.colorMap ??\n track.spectrogramColorMap ??\n spectrogramColorMap;\n currentKeys.set(track.id, JSON.stringify({ mode, cfg, cm, mono }));\n const computeConfig: SpectrogramComputeConfig = {\n fftSize: cfg?.fftSize,\n hopSize: cfg?.hopSize,\n windowFunction: cfg?.windowFunction,\n alpha: cfg?.alpha,\n zeroPaddingFactor: cfg?.zeroPaddingFactor,\n };\n currentFFTKeys.set(track.id, JSON.stringify({ mode, mono, ...computeConfig }));\n });\n\n const prevKeys = prevSpectrogramConfigRef.current;\n const prevFFTKeys = prevSpectrogramFFTKeyRef.current;\n\n let configChanged = currentKeys.size !== prevKeys.size;\n if (!configChanged) {\n for (const [idx, key] of currentKeys) {\n if (prevKeys.get(idx) !== key) {\n configChanged = true;\n break;\n }\n }\n }\n\n let fftKeyChanged = currentFFTKeys.size !== prevFFTKeys.size;\n if (!fftKeyChanged) {\n for (const [idx, key] of currentFFTKeys) {\n if (prevFFTKeys.get(idx) !== key) {\n fftKeyChanged = true;\n break;\n }\n }\n }\n\n const canvasVersionChanged = spectrogramCanvasVersion !== prevCanvasVersionRef.current;\n prevCanvasVersionRef.current = spectrogramCanvasVersion;\n\n if (!configChanged && !canvasVersionChanged) return;\n\n if (configChanged) {\n prevSpectrogramConfigRef.current = currentKeys;\n prevSpectrogramFFTKeyRef.current = currentFFTKeys;\n }\n\n if (backgroundRenderAbortRef.current) {\n backgroundRenderAbortRef.current.aborted = true;\n }\n\n const generation = ++spectrogramGenerationRef.current;\n\n // Tell the worker to abort any in-flight FFT from previous generations\n if (spectrogramWorkerRef.current) {\n spectrogramWorkerRef.current.abortGeneration(generation);\n }\n\n let workerApi = spectrogramWorkerRef.current;\n if (!workerApi) {\n try {\n workerApi = createSpectrogramWorkerPool(\n () =>\n new Worker(new URL('@dawcore/spectrogram/worker/spectrogram.worker', import.meta.url), {\n type: 'module',\n }),\n workerPoolSize\n );\n spectrogramWorkerRef.current = workerApi;\n } catch (err) {\n console.error(\n `[waveform-playlist] Spectrogram Web Worker required but unavailable: ${err instanceof Error ? err.message : String(err)}`\n );\n return;\n }\n }\n\n const clipsNeedingFFT: Array<{\n clipId: string;\n trackIndex: number;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n clipStartSample: number;\n monoFlag: boolean;\n colorMap: ColorMapValue;\n }> = [];\n const clipsNeedingDisplayOnly: Array<{\n clipId: string;\n trackIndex: number;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n clipStartSample: number;\n monoFlag: boolean;\n colorMap: ColorMapValue;\n numChannels: number;\n }> = [];\n\n tracks.forEach((track, i) => {\n const mode =\n trackSpectrogramOverrides.get(track.id)?.renderMode ?? track.renderMode ?? 'waveform';\n if (mode === 'waveform') return;\n\n const trackConfigChanged =\n configChanged && currentKeys.get(track.id) !== prevKeys.get(track.id);\n const trackFFTChanged =\n fftKeyChanged && currentFFTKeys.get(track.id) !== prevFFTKeys.get(track.id);\n const hasRegisteredCanvases =\n canvasVersionChanged &&\n track.clips.some((clip) => spectrogramCanvasRegistryRef.current.has(clip.id));\n if (!trackConfigChanged && !hasRegisteredCanvases) return;\n\n const cfg =\n trackSpectrogramOverrides.get(track.id)?.config ??\n track.spectrogramConfig ??\n spectrogramConfig ??\n {};\n const cm =\n trackSpectrogramOverrides.get(track.id)?.colorMap ??\n track.spectrogramColorMap ??\n spectrogramColorMap ??\n 'viridis';\n\n for (const clip of track.clips) {\n if (!clip.audioBuffer) continue;\n\n const monoFlag = mono || clip.audioBuffer.numberOfChannels === 1;\n\n if (!trackFFTChanged && !hasRegisteredCanvases && renderedClipIdsRef.current.has(clip.id)) {\n const channelDataArrays: Float32Array[] = [];\n for (let ch = 0; ch < clip.audioBuffer.numberOfChannels; ch++) {\n channelDataArrays.push(clip.audioBuffer.getChannelData(ch));\n }\n clipsNeedingDisplayOnly.push({\n clipId: clip.id,\n trackIndex: i,\n channelDataArrays,\n config: cfg,\n sampleRate: clip.audioBuffer.sampleRate,\n offsetSamples: clip.offsetSamples,\n durationSamples: clip.durationSamples,\n clipStartSample: clip.startSample,\n monoFlag,\n colorMap: cm,\n numChannels: monoFlag ? 1 : clip.audioBuffer.numberOfChannels,\n });\n continue;\n }\n\n const channelDataArrays: Float32Array[] = [];\n for (let ch = 0; ch < clip.audioBuffer.numberOfChannels; ch++) {\n channelDataArrays.push(clip.audioBuffer.getChannelData(ch));\n }\n\n clipsNeedingFFT.push({\n clipId: clip.id,\n trackIndex: i,\n channelDataArrays,\n config: cfg,\n sampleRate: clip.audioBuffer.sampleRate,\n offsetSamples: clip.offsetSamples,\n durationSamples: clip.durationSamples,\n clipStartSample: clip.startSample,\n monoFlag,\n colorMap: cm,\n });\n }\n });\n\n if (clipsNeedingFFT.length === 0 && clipsNeedingDisplayOnly.length === 0) return;\n\n // Three-tier chunk classification:\n // - viewportIndices: chunks intersecting the exact viewport (phase 1a — fast first paint)\n // - bufferIndices: chunks in the 1.5× overscan buffer but outside viewport (phase 1b)\n // - remainingIndices: chunks outside the buffer (phase 2 — background batches)\n const getVisibleChunkRange = (\n channelInfo: { canvasIds: string[]; canvasWidths: number[] },\n clipPixelOffset = 0\n ): { viewportIndices: number[]; bufferIndices: number[]; remainingIndices: number[] } => {\n const container = scrollContainerRef.current;\n if (!container) {\n return {\n viewportIndices: channelInfo.canvasWidths.map((_, i) => i),\n bufferIndices: [],\n remainingIndices: [],\n };\n }\n\n const scrollLeft = container.scrollLeft;\n const viewportWidth = container.clientWidth;\n // Match the 1.5× overscan buffer used by useVisibleChunkIndices\n // (ScrollViewport.tsx) so spectrogram FFT covers all mounted canvases.\n const buffer = viewportWidth * 1.5;\n const bufferStart = Math.max(0, scrollLeft - buffer);\n const bufferEnd = scrollLeft + viewportWidth + buffer;\n\n const viewportIndices: number[] = [];\n const bufferIndices: number[] = [];\n const remainingIndices: number[] = [];\n\n for (let i = 0; i < channelInfo.canvasWidths.length; i++) {\n const chunkNumber = extractChunkNumber(channelInfo.canvasIds[i]);\n const chunkLeft = chunkNumber * MAX_CANVAS_WIDTH + clipPixelOffset;\n const chunkRight = chunkLeft + channelInfo.canvasWidths[i];\n if (chunkRight > scrollLeft && chunkLeft < scrollLeft + viewportWidth) {\n viewportIndices.push(i);\n } else if (chunkRight > bufferStart && chunkLeft < bufferEnd) {\n bufferIndices.push(i);\n } else {\n remainingIndices.push(i);\n }\n }\n\n return { viewportIndices, bufferIndices, remainingIndices };\n };\n\n const renderChunkSubset = async (\n api: SpectrogramWorkerApi,\n cacheKey: string,\n channelInfo: { canvasIds: string[]; canvasWidths: number[] },\n indices: number[],\n item: { config: SpectrogramConfig; colorMap: ColorMapValue },\n channelIndex: number,\n gen: number\n ) => {\n if (indices.length === 0) return;\n\n const canvasIds = indices.map((i) => channelInfo.canvasIds[i]);\n const canvasWidths = indices.map((i) => channelInfo.canvasWidths[i]);\n\n // Compute correct global pixel offsets by extracting chunk numbers from\n // canvas IDs. With virtual scrolling, the registry may contain non-consecutive\n // chunks (e.g., chunks 50-55), so summing widths from index 0 gives wrong offsets.\n const globalPixelOffsets: number[] = [];\n for (const idx of indices) {\n const chunkNumber = extractChunkNumber(channelInfo.canvasIds[idx]);\n globalPixelOffsets.push(chunkNumber * MAX_CANVAS_WIDTH);\n }\n\n const colorLUT = getColorMap(item.colorMap);\n\n await api.renderChunks(\n {\n cacheKey,\n canvasIds,\n canvasWidths,\n globalPixelOffsets,\n canvasHeight: waveHeight,\n devicePixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n samplesPerPixel,\n colorLUT,\n frequencyScale: item.config.frequencyScale ?? 'mel',\n minFrequency: item.config.minFrequency ?? 0,\n maxFrequency: item.config.maxFrequency ?? 0,\n gainDb: item.config.gainDb ?? 20,\n rangeDb: item.config.rangeDb ?? 80,\n channelIndex,\n },\n gen\n );\n };\n\n // Compute FFT for the sample range covered by a set of chunk indices.\n // Returns the cache key (data covers all channels).\n // This avoids computing a single full-clip FFT (which OOMs on 1hr+ files)\n // by computing per-batch ranges on demand.\n const computeFFTForChunks = async (\n api: SpectrogramWorkerApi,\n channelInfo: { canvasIds: string[]; canvasWidths: number[] },\n indices: number[],\n item: {\n clipId: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n monoFlag: boolean;\n },\n gen: number\n ): Promise<string> => {\n // Determine the sample range these chunks cover\n const chunkNumbers = indices.map((i) => extractChunkNumber(channelInfo.canvasIds[i]));\n const minChunk = Math.min(...chunkNumbers);\n const maxChunk = Math.max(...chunkNumbers);\n const maxChunkIdx = indices[chunkNumbers.indexOf(maxChunk)];\n const lastChunkWidth = channelInfo.canvasWidths[maxChunkIdx];\n\n const startPx = minChunk * MAX_CANVAS_WIDTH;\n const endPx = maxChunk * MAX_CANVAS_WIDTH + lastChunkWidth;\n\n const windowSize = item.config.fftSize ?? 2048;\n const rangeStartSample = item.offsetSamples + Math.floor(startPx * samplesPerPixel);\n const rangeEndSample = Math.min(\n item.offsetSamples + item.durationSamples,\n item.offsetSamples + Math.ceil(endPx * samplesPerPixel)\n );\n\n // Pad by windowSize to avoid edge artifacts\n const paddedStart = Math.max(item.offsetSamples, rangeStartSample - windowSize);\n const paddedEnd = Math.min(\n item.offsetSamples + item.durationSamples,\n rangeEndSample + windowSize\n );\n\n const { cacheKey } = await api.computeFFT(\n {\n clipId: item.clipId,\n channelDataArrays: item.channelDataArrays,\n config: item.config,\n sampleRate: item.sampleRate,\n offsetSamples: item.offsetSamples,\n durationSamples: item.durationSamples,\n mono: item.monoFlag,\n sampleRange: { start: paddedStart, end: paddedEnd },\n },\n gen\n );\n\n return cacheKey;\n };\n\n // Split indices into contiguous groups based on chunk numbers.\n // E.g., remaining=[0,1,4,5] → [[0,1],[4,5]] — prevents computing\n // an FFT range spanning the gap between 1 and 4.\n const groupContiguousIndices = (\n channelInfo: { canvasIds: string[] },\n indices: number[]\n ): number[][] => {\n if (indices.length === 0) return [];\n const groups: number[][] = [];\n let currentGroup = [indices[0]];\n let prevChunk = extractChunkNumber(channelInfo.canvasIds[indices[0]]);\n for (let i = 1; i < indices.length; i++) {\n const chunk = extractChunkNumber(channelInfo.canvasIds[indices[i]]);\n if (chunk === prevChunk + 1) {\n currentGroup.push(indices[i]);\n } else {\n groups.push(currentGroup);\n currentGroup = [indices[i]];\n }\n prevChunk = chunk;\n }\n groups.push(currentGroup);\n return groups;\n };\n\n const computeAsync = async () => {\n const abortToken = { aborted: false };\n backgroundRenderAbortRef.current = abortToken;\n\n // Render off-screen chunks in idle-callback batches, computing FFT\n // per contiguous group to avoid allocating one giant Float32Array for the\n // full clip (which OOMs on 1hr+ files — e.g., 310K frames × 2048 bins = 2.5GB).\n // Groups remaining indices into contiguous runs (e.g., [0,1,4,5] → [0,1]+[4,5])\n // so each FFT only covers the sample range actually needed.\n // Returns true if aborted (caller should return early).\n const renderBackgroundBatches = async (\n channelRanges: Array<{\n ch: number;\n channelInfo: { canvasIds: string[]; canvasWidths: number[] };\n remainingIndices: number[];\n }>,\n item: {\n clipId: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n monoFlag: boolean;\n colorMap: ColorMapValue;\n }\n ): Promise<boolean> => {\n // Collect all contiguous groups across channels, then render\n // each group for ALL channels before moving to the next group\n // (multi-channel fairness — avoids ch1 starvation).\n const allGroups: Array<{\n group: number[];\n channelRangeEntries: Array<{\n ch: number;\n channelInfo: { canvasIds: string[]; canvasWidths: number[] };\n }>;\n }> = [];\n\n // Build contiguous groups from the first channel's remaining indices\n // (all channels have the same chunk layout).\n if (channelRanges.length > 0) {\n const { channelInfo, remainingIndices } = channelRanges[0];\n const groups = groupContiguousIndices(channelInfo, remainingIndices);\n for (const group of groups) {\n allGroups.push({\n group,\n channelRangeEntries: channelRanges.map(({ ch, channelInfo: ci }) => ({\n ch,\n channelInfo: ci,\n })),\n });\n }\n }\n\n for (const { group, channelRangeEntries } of allGroups) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return true;\n\n await new Promise<void>((resolve) => {\n if (typeof requestIdleCallback === 'function') {\n requestIdleCallback(() => resolve());\n } else {\n setTimeout(resolve, 0);\n }\n });\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return true;\n\n // Compute FFT once for this contiguous group (covers all channels)\n const { channelInfo: firstChannelInfo } = channelRangeEntries[0];\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n firstChannelInfo,\n group,\n item,\n generation\n );\n\n // Render all channels from the cached FFT data\n for (const { ch, channelInfo: ci } of channelRangeEntries) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return true;\n await renderChunkSubset(workerApi!, cacheKey, ci, group, item, ch, generation);\n }\n }\n return false;\n };\n\n for (const item of clipsNeedingFFT) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n try {\n const clipCanvasInfo = spectrogramCanvasRegistryRef.current.get(item.clipId);\n if (clipCanvasInfo && clipCanvasInfo.size > 0) {\n const numChannels = item.monoFlag ? 1 : item.channelDataArrays.length;\n const clipPixelOffset = Math.floor(item.clipStartSample / samplesPerPixel);\n\n // Three-phase rendering:\n // Phase 1a: viewport-only chunks (fast first paint)\n // Phase 1b: buffer-zone chunks (prevents black chunks on scroll)\n // Phase 2: off-screen chunks (background batches)\n const channelRanges: Array<{\n ch: number;\n channelInfo: { canvasIds: string[]; canvasWidths: number[] };\n viewportIndices: number[];\n bufferIndices: number[];\n remainingIndices: number[];\n }> = [];\n\n for (let ch = 0; ch < numChannels; ch++) {\n const channelInfo = clipCanvasInfo.get(ch);\n if (!channelInfo) continue;\n const range = getVisibleChunkRange(channelInfo, clipPixelOffset);\n channelRanges.push({ ch, channelInfo, ...range });\n }\n\n // Phase 1a: Compute FFT for viewport chunks only, render all channels\n if (channelRanges.length > 0 && channelRanges[0].viewportIndices.length > 0) {\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n channelRanges[0].channelInfo,\n channelRanges[0].viewportIndices,\n item,\n generation\n );\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n for (const { ch, channelInfo, viewportIndices } of channelRanges) {\n await renderChunkSubset(\n workerApi!,\n cacheKey,\n channelInfo,\n viewportIndices,\n item,\n ch,\n generation\n );\n }\n }\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n // Phase 1b: Compute FFT for buffer-zone chunks, render all channels.\n // Buffer indices may be non-contiguous (e.g., chunks [10,14,15] from\n // indices [0,3,4,5]), so group them to avoid spanning a huge FFT range.\n if (channelRanges.length > 0 && channelRanges[0].bufferIndices.length > 0) {\n const bufferGroups = groupContiguousIndices(\n channelRanges[0].channelInfo,\n channelRanges[0].bufferIndices\n );\n\n for (const group of bufferGroups) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n channelRanges[0].channelInfo,\n group,\n item,\n generation\n );\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n for (const { ch, channelInfo } of channelRanges) {\n await renderChunkSubset(\n workerApi!,\n cacheKey,\n channelInfo,\n group,\n item,\n ch,\n generation\n );\n }\n }\n }\n\n renderedClipIdsRef.current.add(item.clipId);\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n // Phase 2: Render off-screen chunks in background batches\n // (each batch computes its own bounded FFT range).\n if (await renderBackgroundBatches(channelRanges, item)) return;\n }\n } catch (err) {\n if (err instanceof SpectrogramAbortError) return;\n console.warn(\n `[waveform-playlist] Spectrogram worker error for clip ${item.clipId}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n for (const item of clipsNeedingDisplayOnly) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n const clipCanvasInfo = spectrogramCanvasRegistryRef.current.get(item.clipId);\n if (!clipCanvasInfo || clipCanvasInfo.size === 0) continue;\n\n try {\n const clipPixelOffset = Math.floor(item.clipStartSample / samplesPerPixel);\n\n // Three-phase rendering with per-batch FFT (same as FFT path above).\n // Worker cache provides instant hits for previously computed ranges.\n const channelRanges: Array<{\n ch: number;\n channelInfo: { canvasIds: string[]; canvasWidths: number[] };\n viewportIndices: number[];\n bufferIndices: number[];\n remainingIndices: number[];\n }> = [];\n for (let ch = 0; ch < item.numChannels; ch++) {\n const channelInfo = clipCanvasInfo.get(ch);\n if (!channelInfo) continue;\n const range = getVisibleChunkRange(channelInfo, clipPixelOffset);\n channelRanges.push({ ch, channelInfo, ...range });\n }\n\n // Phase 1a: viewport chunks\n if (channelRanges.length > 0 && channelRanges[0].viewportIndices.length > 0) {\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n channelRanges[0].channelInfo,\n channelRanges[0].viewportIndices,\n item,\n generation\n );\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n for (const { ch, channelInfo, viewportIndices } of channelRanges) {\n await renderChunkSubset(\n workerApi!,\n cacheKey,\n channelInfo,\n viewportIndices,\n item,\n ch,\n generation\n );\n }\n }\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n // Phase 1b: buffer-zone chunks (grouped for contiguous FFT ranges)\n if (channelRanges.length > 0 && channelRanges[0].bufferIndices.length > 0) {\n const bufferGroups = groupContiguousIndices(\n channelRanges[0].channelInfo,\n channelRanges[0].bufferIndices\n );\n for (const group of bufferGroups) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n channelRanges[0].channelInfo,\n group,\n item,\n generation\n );\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n for (const { ch, channelInfo } of channelRanges) {\n await renderChunkSubset(\n workerApi!,\n cacheKey,\n channelInfo,\n group,\n item,\n ch,\n generation\n );\n }\n }\n }\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n // Phase 2: Render off-screen chunks in background batches.\n if (await renderBackgroundBatches(channelRanges, item)) return;\n } catch (err) {\n if (err instanceof SpectrogramAbortError) return;\n console.warn(\n `[waveform-playlist] Spectrogram display re-render error for clip ${item.clipId}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n };\n\n computeAsync().catch((err) => {\n console.error(\n `[waveform-playlist] Spectrogram computation failed: ${err instanceof Error ? err.message : String(err)}`\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps -- workerPoolSize intentionally omitted, pool created once via spectrogramWorkerRef guard\n }, [\n tracks,\n mono,\n spectrogramConfig,\n spectrogramColorMap,\n trackSpectrogramOverrides,\n waveHeight,\n samplesPerPixel,\n spectrogramCanvasVersion,\n scrollContainerRef,\n ]);\n\n // Setters\n const setTrackRenderMode = useCallback((trackId: string, mode: RenderMode) => {\n setTrackSpectrogramOverrides((prev) => {\n const next = new Map(prev);\n const existing = next.get(trackId);\n next.set(trackId, { ...existing, renderMode: mode });\n return next;\n });\n }, []);\n\n const setTrackSpectrogramConfig = useCallback(\n (trackId: string, config: SpectrogramConfig, colorMap?: ColorMapValue) => {\n setTrackSpectrogramOverrides((prev) => {\n const next = new Map(prev);\n const existing = next.get(trackId);\n next.set(trackId, {\n renderMode: existing?.renderMode ?? 'waveform',\n config,\n ...(colorMap !== undefined ? { colorMap } : { colorMap: existing?.colorMap }),\n });\n return next;\n });\n },\n []\n );\n\n // Lazily create the worker pool — keeps the same fallback path as the\n // pre-transfer / FFT effects.\n const ensureWorkerPool = useCallback((): SpectrogramWorkerApi | null => {\n if (spectrogramWorkerRef.current) return spectrogramWorkerRef.current;\n try {\n const pool = createSpectrogramWorkerPool(\n () =>\n new Worker(new URL('@dawcore/spectrogram/worker/spectrogram.worker', import.meta.url), {\n type: 'module',\n }),\n workerPoolSize\n );\n spectrogramWorkerRef.current = pool;\n return pool;\n } catch (err) {\n console.warn(\n `[waveform-playlist] Spectrogram Web Worker unavailable: ${err instanceof Error ? err.message : String(err)}`\n );\n return null;\n }\n }, [workerPoolSize]);\n\n const registerSpectrogramCanvas = useCallback(\n (reg: SpectrogramCanvasRegistration) => {\n const pool = ensureWorkerPool();\n if (!pool) return;\n\n try {\n pool.registerCanvas(reg.canvasId, reg.canvas);\n } catch (err) {\n console.warn(\n `[waveform-playlist] registerCanvas failed for ${reg.canvasId}: ${err instanceof Error ? err.message : String(err)}`\n );\n return;\n }\n\n const registry = spectrogramCanvasRegistryRef.current;\n if (!registry.has(reg.clipId)) {\n registry.set(reg.clipId, new Map());\n }\n const perClip = registry.get(reg.clipId)!;\n const entry = perClip.get(reg.channelIndex) ?? { canvasIds: [], canvasWidths: [] };\n const existingIdx = entry.canvasIds.indexOf(reg.canvasId);\n if (existingIdx >= 0) {\n entry.canvasWidths[existingIdx] = reg.widthPx;\n } else {\n entry.canvasIds.push(reg.canvasId);\n entry.canvasWidths.push(reg.widthPx);\n }\n perClip.set(reg.channelIndex, entry);\n setSpectrogramCanvasVersion((v) => v + 1);\n },\n [ensureWorkerPool]\n );\n\n const unregisterSpectrogramCanvas = useCallback((canvasId: string) => {\n const pool = spectrogramWorkerRef.current;\n if (pool) {\n try {\n pool.unregisterCanvas(canvasId);\n } catch (err) {\n console.warn(\n `[waveform-playlist] unregisterCanvas failed for ${canvasId}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n // Canvas IDs follow the format `${clipId}-ch${channelIndex}-chunk${n}`.\n const match = canvasId.match(/^(.+)-ch(\\d+)-chunk\\d+$/);\n if (!match) return;\n const clipId = match[1];\n const channelIndex = parseInt(match[2], 10);\n\n const registry = spectrogramCanvasRegistryRef.current;\n const perClip = registry.get(clipId);\n if (!perClip) return;\n const entry = perClip.get(channelIndex);\n if (!entry) return;\n const idx = entry.canvasIds.indexOf(canvasId);\n if (idx >= 0) {\n entry.canvasIds.splice(idx, 1);\n entry.canvasWidths.splice(idx, 1);\n }\n if (entry.canvasIds.length === 0) {\n perClip.delete(channelIndex);\n }\n if (perClip.size === 0) {\n registry.delete(clipId);\n }\n setSpectrogramCanvasVersion((v) => v + 1);\n }, []);\n\n const renderMenuItems = useCallback(\n (props: {\n renderMode: string;\n onRenderModeChange: (mode: RenderMode) => void;\n onOpenSettings: () => void;\n onClose?: () => void;\n }) => {\n return SpectrogramMenuItems({\n renderMode: props.renderMode as RenderMode,\n onRenderModeChange: props.onRenderModeChange,\n onOpenSettings: props.onOpenSettings,\n onClose: props.onClose,\n });\n },\n []\n );\n\n const value: SpectrogramIntegration = useMemo(\n () => ({\n trackSpectrogramOverrides,\n spectrogramConfig,\n spectrogramColorMap,\n setTrackRenderMode,\n setTrackSpectrogramConfig,\n registerSpectrogramCanvas,\n unregisterSpectrogramCanvas,\n renderMenuItems,\n SettingsModal: SpectrogramSettingsModal,\n getColorMap,\n getFrequencyScale: getFrequencyScale as (\n name: string\n ) => (f: number, minF: number, maxF: number) => number,\n }),\n [\n trackSpectrogramOverrides,\n spectrogramConfig,\n spectrogramColorMap,\n setTrackRenderMode,\n setTrackSpectrogramConfig,\n registerSpectrogramCanvas,\n unregisterSpectrogramCanvas,\n renderMenuItems,\n ]\n );\n\n return <SpectrogramIntegrationProvider value={value}>{children}</SpectrogramIntegrationProvider>;\n};\n"],"mappings":"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","names":["styled","jsx","jsxs","useState","useEffect","useRef","jsx","useState","useRef","useEffect","channelDataArrays"]}
1
+ {"version":3,"sources":["../src/components/SpectrogramMenuItems.tsx","../src/components/SpectrogramSettingsModal.tsx","../src/SpectrogramProvider.tsx","../src/spectrogram-helpers.ts"],"sourcesContent":["import React from 'react';\nimport styled from 'styled-components';\nimport type { RenderMode } from '@waveform-playlist/core';\nimport type { TrackMenuItem } from './types';\n\nconst SectionLabel = styled.div`\n font-size: 0.65rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n opacity: 0.5;\n margin-bottom: 0.25rem;\n`;\n\nconst RadioLabel = styled.label`\n display: flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.2rem 0;\n font-size: 0.8rem;\n cursor: pointer;\n\n &:hover {\n opacity: 0.8;\n }\n`;\n\nconst SettingsButton = styled.button`\n background: none;\n border: none;\n color: inherit;\n cursor: pointer;\n font-size: 0.8rem;\n padding: 0.35rem 0.75rem;\n width: 100%;\n text-align: left;\n\n &:hover {\n background: rgba(128, 128, 128, 0.15);\n }\n`;\n\nconst DropdownSection = styled.div`\n padding: 0.25rem 0.75rem;\n`;\n\nconst RENDER_MODES: { value: RenderMode; label: string }[] = [\n { value: 'waveform', label: 'Waveform' },\n { value: 'spectrogram', label: 'Spectrogram' },\n { value: 'both', label: 'Both' },\n];\n\nexport interface SpectrogramMenuItemsProps {\n renderMode: RenderMode;\n onRenderModeChange: (mode: RenderMode) => void;\n onOpenSettings: () => void;\n onClose?: () => void;\n}\n\n/**\n * Returns TrackMenuItem[] for the spectrogram display mode radios and settings button.\n */\nexport function SpectrogramMenuItems({\n renderMode,\n onRenderModeChange,\n onOpenSettings,\n onClose,\n}: SpectrogramMenuItemsProps): TrackMenuItem[] {\n return [\n {\n id: 'spectrogram-display',\n label: 'Display',\n content: (\n <DropdownSection>\n <SectionLabel>Display</SectionLabel>\n {RENDER_MODES.map(({ value, label }) => (\n <RadioLabel key={value}>\n <input\n type=\"radio\"\n name=\"render-mode\"\n checked={renderMode === value}\n onChange={() => {\n onRenderModeChange(value);\n onClose?.();\n }}\n />\n {label}\n </RadioLabel>\n ))}\n </DropdownSection>\n ),\n },\n {\n id: 'spectrogram-settings',\n content: (\n <SettingsButton\n onClick={(e) => {\n e.stopPropagation();\n onClose?.();\n onOpenSettings();\n }}\n onMouseDown={(e) => e.stopPropagation()}\n >\n Spectrogram Settings...\n </SettingsButton>\n ),\n },\n ];\n}\n","import React, { useRef, useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport type {\n SpectrogramConfig,\n ColorMapValue,\n ColorMapName,\n FFTSize,\n} from '@waveform-playlist/core';\n\nexport interface SpectrogramSettingsModalProps {\n open: boolean;\n onClose: () => void;\n config: SpectrogramConfig;\n colorMap: ColorMapValue;\n onApply: (config: SpectrogramConfig, colorMap: ColorMapValue) => void;\n}\n\nconst StyledDialog = styled.dialog`\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 8px;\n padding: 1.5rem;\n background: ${(p) => p.theme.timescaleBackgroundColor ?? '#222'};\n color: ${(p) => p.theme.textColor ?? 'inherit'};\n min-width: 380px;\n max-width: 500px;\n\n &::backdrop {\n background: rgba(0, 0, 0, 0.5);\n }\n`;\n\nconst Title = styled.h3`\n margin: 0 0 1rem;\n font-size: 1rem;\n`;\n\nconst FormGrid = styled.div`\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0.75rem;\n`;\n\nconst Field = styled.div<{ $span?: boolean }>`\n display: flex;\n flex-direction: column;\n gap: 0.2rem;\n grid-column: ${(p) => (p.$span ? '1 / -1' : 'auto')};\n`;\n\nconst Label = styled.label`\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n opacity: 0.6;\n`;\n\nconst Select = styled.select`\n padding: 0.3rem 0.4rem;\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 4px;\n background: rgba(128, 128, 128, 0.15);\n color: inherit;\n font-size: 0.85rem;\n`;\n\nconst NumberInput = styled.input`\n padding: 0.3rem 0.4rem;\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 4px;\n background: rgba(128, 128, 128, 0.15);\n color: inherit;\n font-size: 0.85rem;\n width: 100%;\n box-sizing: border-box;\n`;\n\nconst CheckboxLabel = styled.label`\n display: flex;\n align-items: center;\n gap: 0.4rem;\n font-size: 0.85rem;\n cursor: pointer;\n`;\n\nconst ButtonRow = styled.div`\n display: flex;\n justify-content: flex-end;\n gap: 0.5rem;\n margin-top: 1.25rem;\n`;\n\nconst ModalButton = styled.button<{ $primary?: boolean }>`\n padding: 0.4rem 1rem;\n border: 1px solid rgba(128, 128, 128, 0.4);\n border-radius: 4px;\n cursor: pointer;\n font-size: 0.85rem;\n background: ${(p) => (p.$primary ? 'var(--ifm-color-primary, #4a9)' : 'transparent')};\n color: ${(p) => (p.$primary ? '#fff' : 'inherit')};\n\n &:hover {\n opacity: 0.85;\n }\n`;\n\nconst FFT_SIZES = [256, 512, 1024, 2048, 4096, 8192];\nconst WINDOW_FUNCTIONS = [\n 'hann',\n 'hamming',\n 'blackman',\n 'blackman-harris',\n 'bartlett',\n 'rectangular',\n] as const;\nconst FREQ_SCALES = ['linear', 'logarithmic', 'mel', 'bark', 'erb'] as const;\nconst COLOR_MAPS: ColorMapName[] = ['viridis', 'magma', 'inferno', 'grayscale', 'igray', 'roseus'];\n\nexport const SpectrogramSettingsModal: React.FC<SpectrogramSettingsModalProps> = ({\n open,\n onClose,\n config,\n colorMap,\n onApply,\n}) => {\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n // Local form state\n const [fftSize, setFftSize] = useState(config.fftSize ?? 2048);\n const [windowFn, setWindowFn] = useState(config.windowFunction ?? 'hann');\n const [freqScale, setFreqScale] = useState(config.frequencyScale ?? 'mel');\n const [localColorMap, setLocalColorMap] = useState<ColorMapName>(\n typeof colorMap === 'string' ? colorMap : 'viridis'\n );\n const [minFreq, setMinFreq] = useState(config.minFrequency ?? 0);\n const [maxFreq, setMaxFreq] = useState(config.maxFrequency ?? 20000);\n const [gainDb, setGainDb] = useState(config.gainDb ?? 20);\n const [rangeDb, setRangeDb] = useState(config.rangeDb ?? 80);\n const [zeroPadding, setZeroPadding] = useState(config.zeroPaddingFactor ?? 2);\n const [hopSize, setHopSize] = useState(\n config.hopSize ?? Math.floor((config.fftSize ?? 2048) / 4)\n );\n const [showLabels, setShowLabels] = useState(config.labels ?? false);\n\n // Sync local state when props change\n useEffect(() => {\n setFftSize(config.fftSize ?? 2048);\n setWindowFn(config.windowFunction ?? 'hann');\n setFreqScale(config.frequencyScale ?? 'mel');\n setLocalColorMap(typeof colorMap === 'string' ? colorMap : 'viridis');\n setMinFreq(config.minFrequency ?? 0);\n setMaxFreq(config.maxFrequency ?? 20000);\n setGainDb(config.gainDb ?? 20);\n setRangeDb(config.rangeDb ?? 80);\n setZeroPadding(config.zeroPaddingFactor ?? 2);\n setHopSize(config.hopSize ?? Math.floor((config.fftSize ?? 2048) / 4));\n setShowLabels(config.labels ?? false);\n }, [config, colorMap]);\n\n // Open/close dialog + handle native close (Escape key)\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (open && !dialog.open) {\n dialog.showModal();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n\n const handleClose = () => {\n // Only call onClose if dialog was open (avoids double-fire)\n if (open) {\n onClose();\n }\n };\n dialog.addEventListener('close', handleClose);\n return () => dialog.removeEventListener('close', handleClose);\n }, [open, onClose]);\n\n const handleApply = () => {\n onApply(\n {\n fftSize,\n windowFunction: windowFn as SpectrogramConfig['windowFunction'],\n frequencyScale: freqScale as SpectrogramConfig['frequencyScale'],\n minFrequency: minFreq,\n maxFrequency: maxFreq,\n gainDb,\n rangeDb,\n zeroPaddingFactor: zeroPadding,\n hopSize,\n labels: showLabels,\n },\n localColorMap\n );\n onClose();\n };\n\n return (\n <StyledDialog ref={dialogRef}>\n <Title>Spectrogram Settings</Title>\n <FormGrid>\n <Field>\n <Label>FFT Size</Label>\n <Select\n value={fftSize}\n onChange={(e) => {\n const v = Number(e.target.value) as FFTSize;\n setFftSize(v);\n setHopSize(Math.floor(v / 4));\n }}\n >\n {FFT_SIZES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Hop Size</Label>\n <Select value={hopSize} onChange={(e) => setHopSize(Number(e.target.value))}>\n {[\n { label: `${fftSize} (no overlap)`, value: fftSize },\n { label: `${Math.floor(fftSize / 2)} (50%)`, value: Math.floor(fftSize / 2) },\n { label: `${Math.floor(fftSize / 4)} (75%)`, value: Math.floor(fftSize / 4) },\n { label: `${Math.floor(fftSize / 8)} (87.5%)`, value: Math.floor(fftSize / 8) },\n ].map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Zero Padding</Label>\n <Select value={zeroPadding} onChange={(e) => setZeroPadding(Number(e.target.value))}>\n {[1, 2, 4, 8, 16].map((v) => (\n <option key={v} value={v}>\n {v}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Window Function</Label>\n <Select value={windowFn} onChange={(e) => setWindowFn(e.target.value as typeof windowFn)}>\n {WINDOW_FUNCTIONS.map((w) => (\n <option key={w} value={w}>\n {w}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Frequency Scale</Label>\n <Select\n value={freqScale}\n onChange={(e) => setFreqScale(e.target.value as typeof freqScale)}\n >\n {FREQ_SCALES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Color Map</Label>\n <Select\n value={localColorMap}\n onChange={(e) => setLocalColorMap(e.target.value as ColorMapName)}\n >\n {COLOR_MAPS.map((c) => (\n <option key={c} value={c}>\n {c}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field>\n <Label>Min Frequency (Hz)</Label>\n <NumberInput\n type=\"number\"\n min={0}\n max={5000}\n step={50}\n value={minFreq}\n onChange={(e) => setMinFreq(Number(e.target.value))}\n />\n </Field>\n\n <Field>\n <Label>Max Frequency (Hz)</Label>\n <NumberInput\n type=\"number\"\n min={1000}\n max={22050}\n step={50}\n value={maxFreq}\n onChange={(e) => setMaxFreq(Number(e.target.value))}\n />\n </Field>\n\n <Field>\n <Label>Range (dB)</Label>\n <NumberInput\n type=\"number\"\n min={1}\n max={120}\n step={1}\n value={rangeDb}\n onChange={(e) => setRangeDb(Number(e.target.value))}\n />\n </Field>\n\n <Field>\n <Label>Gain (dB)</Label>\n <NumberInput\n type=\"number\"\n min={-20}\n max={60}\n step={1}\n value={gainDb}\n onChange={(e) => setGainDb(Number(e.target.value))}\n />\n </Field>\n\n <Field $span>\n <CheckboxLabel>\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => setShowLabels(e.target.checked)}\n />\n Show Frequency Labels\n </CheckboxLabel>\n </Field>\n </FormGrid>\n\n <ButtonRow>\n <ModalButton onClick={onClose}>Cancel</ModalButton>\n <ModalButton $primary onClick={handleApply}>\n Apply\n </ModalButton>\n </ButtonRow>\n </StyledDialog>\n );\n};\n","import React, { useState, useEffect, useRef, useCallback, useMemo, type ReactNode } from 'react';\nimport {\n MAX_CANVAS_WIDTH,\n type SpectrogramConfig,\n type ColorMapValue,\n type RenderMode,\n type TrackSpectrogramOverrides,\n} from '@waveform-playlist/core';\nimport {\n getColorMap,\n getFrequencyScale,\n createSpectrogramWorkerPool,\n SpectrogramAbortError,\n type SpectrogramWorkerApi,\n} from '@dawcore/spectrogram';\nimport {\n extractChunkNumber,\n parseCanvasId,\n groupContiguousIndices,\n classifyChunkTiers,\n computeChunkSampleRange,\n resolveRenderMode,\n toComputeConfig,\n buildConfigKey,\n buildFFTKey,\n mapsDiffer,\n type ChunkTiers,\n} from './spectrogram-helpers';\nimport { SpectrogramMenuItems } from './components';\nimport { SpectrogramSettingsModal } from './components';\nimport {\n SpectrogramIntegrationProvider,\n type SpectrogramIntegration,\n type SpectrogramCanvasRegistration,\n} from '@waveform-playlist/browser';\nimport { usePlaylistData, usePlaylistControls } from '@waveform-playlist/browser';\n\nexport interface SpectrogramProviderProps {\n config?: SpectrogramConfig;\n colorMap?: ColorMapValue;\n /** Number of Web Workers for parallel FFT computation. Defaults to 2 (one per stereo channel). */\n workerPoolSize?: number;\n children: ReactNode;\n}\n\nexport const SpectrogramProvider: React.FC<SpectrogramProviderProps> = ({\n config: spectrogramConfig,\n colorMap: spectrogramColorMap,\n workerPoolSize,\n children,\n}) => {\n const { tracks, waveHeight, samplesPerPixel, isReady, mono } = usePlaylistData();\n const { scrollContainerRef } = usePlaylistControls();\n\n // State\n const [trackSpectrogramOverrides, setTrackSpectrogramOverrides] = useState<\n Map<string, TrackSpectrogramOverrides>\n >(new Map());\n\n // OffscreenCanvas registry for worker-rendered spectrograms\n const spectrogramCanvasRegistryRef = useRef<\n Map<string, Map<number, { canvasIds: string[]; canvasWidths: number[] }>>\n >(new Map());\n const [spectrogramCanvasVersion, setSpectrogramCanvasVersion] = useState(0);\n\n // Spectrogram refs\n const prevSpectrogramConfigRef = useRef<Map<string, string>>(new Map());\n const prevSpectrogramFFTKeyRef = useRef<Map<string, string>>(new Map());\n const spectrogramWorkerRef = useRef<SpectrogramWorkerApi | null>(null);\n const spectrogramGenerationRef = useRef(0);\n const prevCanvasVersionRef = useRef(0);\n const renderedClipIdsRef = useRef<Set<string>>(new Set());\n const backgroundRenderAbortRef = useRef<{ aborted: boolean } | null>(null);\n const registeredAudioClipIdsRef = useRef<Set<string>>(new Set());\n\n // Terminate worker on unmount\n useEffect(() => {\n return () => {\n spectrogramWorkerRef.current?.terminate();\n spectrogramWorkerRef.current = null;\n };\n }, []);\n\n // Eagerly transfer audio data to worker when tracks load\n useEffect(() => {\n if (!isReady || tracks.length === 0) return;\n\n let workerApi = spectrogramWorkerRef.current;\n if (!workerApi) {\n try {\n workerApi = createSpectrogramWorkerPool(\n () =>\n new Worker(new URL('@dawcore/spectrogram/worker/spectrogram.worker', import.meta.url), {\n type: 'module',\n }),\n workerPoolSize\n );\n spectrogramWorkerRef.current = workerApi;\n } catch (err) {\n console.warn(\n `[waveform-playlist] Spectrogram Web Worker unavailable for pre-transfer: ${err instanceof Error ? err.message : String(err)}`\n );\n return;\n }\n }\n\n const currentClipIds = new Set<string>();\n\n for (const track of tracks) {\n for (const clip of track.clips) {\n if (!clip.audioBuffer) continue;\n currentClipIds.add(clip.id);\n\n if (!registeredAudioClipIdsRef.current.has(clip.id)) {\n const channelDataArrays: Float32Array[] = [];\n for (let ch = 0; ch < clip.audioBuffer.numberOfChannels; ch++) {\n channelDataArrays.push(clip.audioBuffer.getChannelData(ch));\n }\n workerApi.registerAudioData(clip.id, channelDataArrays, clip.audioBuffer.sampleRate);\n registeredAudioClipIdsRef.current.add(clip.id);\n }\n }\n }\n\n for (const clipId of registeredAudioClipIdsRef.current) {\n if (!currentClipIds.has(clipId)) {\n workerApi.unregisterAudioData(clipId);\n registeredAudioClipIdsRef.current.delete(clipId);\n }\n }\n // workerPoolSize intentionally omitted — pool is created once via spectrogramWorkerRef guard\n }, [isReady, tracks]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Main spectrogram computation effect\n useEffect(() => {\n if (tracks.length === 0) return;\n\n const currentKeys = new Map<string, string>();\n const currentFFTKeys = new Map<string, string>();\n tracks.forEach((track) => {\n const override = trackSpectrogramOverrides.get(track.id);\n const mode = resolveRenderMode(override, track.renderMode);\n if (mode === 'waveform') return;\n const cfg = override?.config ?? track.spectrogramConfig ?? spectrogramConfig;\n const cm = override?.colorMap ?? track.spectrogramColorMap ?? spectrogramColorMap;\n currentKeys.set(track.id, buildConfigKey({ mode, cfg, cm, mono }));\n currentFFTKeys.set(\n track.id,\n buildFFTKey({ mode, mono, computeConfig: toComputeConfig(cfg) })\n );\n });\n\n const prevKeys = prevSpectrogramConfigRef.current;\n const prevFFTKeys = prevSpectrogramFFTKeyRef.current;\n\n const configChanged = mapsDiffer(prevKeys, currentKeys);\n const fftKeyChanged = mapsDiffer(prevFFTKeys, currentFFTKeys);\n\n const canvasVersionChanged = spectrogramCanvasVersion !== prevCanvasVersionRef.current;\n prevCanvasVersionRef.current = spectrogramCanvasVersion;\n\n if (!configChanged && !canvasVersionChanged) return;\n\n if (configChanged) {\n prevSpectrogramConfigRef.current = currentKeys;\n prevSpectrogramFFTKeyRef.current = currentFFTKeys;\n }\n\n if (backgroundRenderAbortRef.current) {\n backgroundRenderAbortRef.current.aborted = true;\n }\n\n const generation = ++spectrogramGenerationRef.current;\n\n // Tell the worker to abort any in-flight FFT from previous generations\n if (spectrogramWorkerRef.current) {\n spectrogramWorkerRef.current.abortGeneration(generation);\n }\n\n let workerApi = spectrogramWorkerRef.current;\n if (!workerApi) {\n try {\n workerApi = createSpectrogramWorkerPool(\n () =>\n new Worker(new URL('@dawcore/spectrogram/worker/spectrogram.worker', import.meta.url), {\n type: 'module',\n }),\n workerPoolSize\n );\n spectrogramWorkerRef.current = workerApi;\n } catch (err) {\n console.error(\n `[waveform-playlist] Spectrogram Web Worker required but unavailable: ${err instanceof Error ? err.message : String(err)}`\n );\n return;\n }\n }\n\n const clipsNeedingFFT: Array<{\n clipId: string;\n trackIndex: number;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n clipStartSample: number;\n monoFlag: boolean;\n colorMap: ColorMapValue;\n }> = [];\n const clipsNeedingDisplayOnly: Array<{\n clipId: string;\n trackIndex: number;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n clipStartSample: number;\n monoFlag: boolean;\n colorMap: ColorMapValue;\n numChannels: number;\n }> = [];\n\n tracks.forEach((track, i) => {\n const override = trackSpectrogramOverrides.get(track.id);\n const mode = resolveRenderMode(override, track.renderMode);\n if (mode === 'waveform') return;\n\n const trackConfigChanged =\n configChanged && currentKeys.get(track.id) !== prevKeys.get(track.id);\n const trackFFTChanged =\n fftKeyChanged && currentFFTKeys.get(track.id) !== prevFFTKeys.get(track.id);\n const hasRegisteredCanvases =\n canvasVersionChanged &&\n track.clips.some((clip) => spectrogramCanvasRegistryRef.current.has(clip.id));\n if (!trackConfigChanged && !hasRegisteredCanvases) return;\n\n const cfg = override?.config ?? track.spectrogramConfig ?? spectrogramConfig ?? {};\n const cm =\n override?.colorMap ?? track.spectrogramColorMap ?? spectrogramColorMap ?? 'viridis';\n\n for (const clip of track.clips) {\n if (!clip.audioBuffer) continue;\n\n const monoFlag = mono || clip.audioBuffer.numberOfChannels === 1;\n\n if (!trackFFTChanged && !hasRegisteredCanvases && renderedClipIdsRef.current.has(clip.id)) {\n const channelDataArrays: Float32Array[] = [];\n for (let ch = 0; ch < clip.audioBuffer.numberOfChannels; ch++) {\n channelDataArrays.push(clip.audioBuffer.getChannelData(ch));\n }\n clipsNeedingDisplayOnly.push({\n clipId: clip.id,\n trackIndex: i,\n channelDataArrays,\n config: cfg,\n sampleRate: clip.audioBuffer.sampleRate,\n offsetSamples: clip.offsetSamples,\n durationSamples: clip.durationSamples,\n clipStartSample: clip.startSample,\n monoFlag,\n colorMap: cm,\n numChannels: monoFlag ? 1 : clip.audioBuffer.numberOfChannels,\n });\n continue;\n }\n\n const channelDataArrays: Float32Array[] = [];\n for (let ch = 0; ch < clip.audioBuffer.numberOfChannels; ch++) {\n channelDataArrays.push(clip.audioBuffer.getChannelData(ch));\n }\n\n clipsNeedingFFT.push({\n clipId: clip.id,\n trackIndex: i,\n channelDataArrays,\n config: cfg,\n sampleRate: clip.audioBuffer.sampleRate,\n offsetSamples: clip.offsetSamples,\n durationSamples: clip.durationSamples,\n clipStartSample: clip.startSample,\n monoFlag,\n colorMap: cm,\n });\n }\n });\n\n if (clipsNeedingFFT.length === 0 && clipsNeedingDisplayOnly.length === 0) return;\n\n // Three-tier chunk classification:\n // - viewportIndices: chunks intersecting the exact viewport (phase 1a — fast first paint)\n // - bufferIndices: chunks in the 1.5× overscan buffer but outside viewport (phase 1b)\n // - remainingIndices: chunks outside the buffer (phase 2 — background batches)\n const getVisibleChunkRange = (\n channelInfo: { canvasIds: string[]; canvasWidths: number[] },\n clipPixelOffset = 0\n ): ChunkTiers => {\n const container = scrollContainerRef.current;\n return classifyChunkTiers(\n channelInfo,\n clipPixelOffset,\n container\n ? { scrollLeft: container.scrollLeft, viewportWidth: container.clientWidth }\n : null\n );\n };\n\n const renderChunkSubset = async (\n api: SpectrogramWorkerApi,\n cacheKey: string,\n channelInfo: { canvasIds: string[]; canvasWidths: number[] },\n indices: number[],\n item: { config: SpectrogramConfig; colorMap: ColorMapValue },\n channelIndex: number,\n gen: number\n ) => {\n if (indices.length === 0) return;\n\n const canvasIds = indices.map((i) => channelInfo.canvasIds[i]);\n const canvasWidths = indices.map((i) => channelInfo.canvasWidths[i]);\n\n // Compute correct global pixel offsets by extracting chunk numbers from\n // canvas IDs. With virtual scrolling, the registry may contain non-consecutive\n // chunks (e.g., chunks 50-55), so summing widths from index 0 gives wrong offsets.\n const globalPixelOffsets: number[] = [];\n for (const idx of indices) {\n const chunkNumber = extractChunkNumber(channelInfo.canvasIds[idx]);\n globalPixelOffsets.push(chunkNumber * MAX_CANVAS_WIDTH);\n }\n\n const colorLUT = getColorMap(item.colorMap);\n\n await api.renderChunks(\n {\n cacheKey,\n canvasIds,\n canvasWidths,\n globalPixelOffsets,\n canvasHeight: waveHeight,\n devicePixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n samplesPerPixel,\n colorLUT,\n frequencyScale: item.config.frequencyScale ?? 'mel',\n minFrequency: item.config.minFrequency ?? 0,\n maxFrequency: item.config.maxFrequency ?? 0,\n gainDb: item.config.gainDb ?? 20,\n rangeDb: item.config.rangeDb ?? 80,\n channelIndex,\n },\n gen\n );\n };\n\n // Compute FFT for the sample range covered by a set of chunk indices.\n // Returns the cache key (data covers all channels).\n // This avoids computing a single full-clip FFT (which OOMs on 1hr+ files)\n // by computing per-batch ranges on demand.\n const computeFFTForChunks = async (\n api: SpectrogramWorkerApi,\n channelInfo: { canvasIds: string[]; canvasWidths: number[] },\n indices: number[],\n item: {\n clipId: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n monoFlag: boolean;\n },\n gen: number\n ): Promise<string> => {\n // Determine the (window-padded) sample range these chunks cover.\n const { paddedStart, paddedEnd } = computeChunkSampleRange({\n channelInfo,\n indices,\n fftSize: item.config.fftSize ?? 2048,\n offsetSamples: item.offsetSamples,\n durationSamples: item.durationSamples,\n samplesPerPixel,\n });\n\n const { cacheKey } = await api.computeFFT(\n {\n clipId: item.clipId,\n channelDataArrays: item.channelDataArrays,\n config: item.config,\n sampleRate: item.sampleRate,\n offsetSamples: item.offsetSamples,\n durationSamples: item.durationSamples,\n mono: item.monoFlag,\n sampleRange: { start: paddedStart, end: paddedEnd },\n },\n gen\n );\n\n return cacheKey;\n };\n\n const computeAsync = async () => {\n const abortToken = { aborted: false };\n backgroundRenderAbortRef.current = abortToken;\n\n // Render off-screen chunks in idle-callback batches, computing FFT\n // per contiguous group to avoid allocating one giant Float32Array for the\n // full clip (which OOMs on 1hr+ files — e.g., 310K frames × 2048 bins = 2.5GB).\n // Groups remaining indices into contiguous runs (e.g., [0,1,4,5] → [0,1]+[4,5])\n // so each FFT only covers the sample range actually needed.\n // Returns true if aborted (caller should return early).\n const renderBackgroundBatches = async (\n channelRanges: Array<{\n ch: number;\n channelInfo: { canvasIds: string[]; canvasWidths: number[] };\n remainingIndices: number[];\n }>,\n item: {\n clipId: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n monoFlag: boolean;\n colorMap: ColorMapValue;\n }\n ): Promise<boolean> => {\n // Collect all contiguous groups across channels, then render\n // each group for ALL channels before moving to the next group\n // (multi-channel fairness — avoids ch1 starvation).\n const allGroups: Array<{\n group: number[];\n channelRangeEntries: Array<{\n ch: number;\n channelInfo: { canvasIds: string[]; canvasWidths: number[] };\n }>;\n }> = [];\n\n // Build contiguous groups from the first channel's remaining indices\n // (all channels have the same chunk layout).\n if (channelRanges.length > 0) {\n const { channelInfo, remainingIndices } = channelRanges[0];\n const groups = groupContiguousIndices(channelInfo, remainingIndices);\n for (const group of groups) {\n allGroups.push({\n group,\n channelRangeEntries: channelRanges.map(({ ch, channelInfo: ci }) => ({\n ch,\n channelInfo: ci,\n })),\n });\n }\n }\n\n for (const { group, channelRangeEntries } of allGroups) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return true;\n\n await new Promise<void>((resolve) => {\n if (typeof requestIdleCallback === 'function') {\n requestIdleCallback(() => resolve());\n } else {\n setTimeout(resolve, 0);\n }\n });\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return true;\n\n // Compute FFT once for this contiguous group (covers all channels)\n const { channelInfo: firstChannelInfo } = channelRangeEntries[0];\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n firstChannelInfo,\n group,\n item,\n generation\n );\n\n // Render all channels from the cached FFT data\n for (const { ch, channelInfo: ci } of channelRangeEntries) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return true;\n await renderChunkSubset(workerApi!, cacheKey, ci, group, item, ch, generation);\n }\n }\n return false;\n };\n\n for (const item of clipsNeedingFFT) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n try {\n const clipCanvasInfo = spectrogramCanvasRegistryRef.current.get(item.clipId);\n if (clipCanvasInfo && clipCanvasInfo.size > 0) {\n const numChannels = item.monoFlag ? 1 : item.channelDataArrays.length;\n const clipPixelOffset = Math.floor(item.clipStartSample / samplesPerPixel);\n\n // Three-phase rendering:\n // Phase 1a: viewport-only chunks (fast first paint)\n // Phase 1b: buffer-zone chunks (prevents black chunks on scroll)\n // Phase 2: off-screen chunks (background batches)\n const channelRanges: Array<{\n ch: number;\n channelInfo: { canvasIds: string[]; canvasWidths: number[] };\n viewportIndices: number[];\n bufferIndices: number[];\n remainingIndices: number[];\n }> = [];\n\n for (let ch = 0; ch < numChannels; ch++) {\n const channelInfo = clipCanvasInfo.get(ch);\n if (!channelInfo) continue;\n const range = getVisibleChunkRange(channelInfo, clipPixelOffset);\n channelRanges.push({ ch, channelInfo, ...range });\n }\n\n // Phase 1a: Compute FFT for viewport chunks only, render all channels\n if (channelRanges.length > 0 && channelRanges[0].viewportIndices.length > 0) {\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n channelRanges[0].channelInfo,\n channelRanges[0].viewportIndices,\n item,\n generation\n );\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n for (const { ch, channelInfo, viewportIndices } of channelRanges) {\n await renderChunkSubset(\n workerApi!,\n cacheKey,\n channelInfo,\n viewportIndices,\n item,\n ch,\n generation\n );\n }\n }\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n // Phase 1b: Compute FFT for buffer-zone chunks, render all channels.\n // Buffer indices may be non-contiguous (e.g., chunks [10,14,15] from\n // indices [0,3,4,5]), so group them to avoid spanning a huge FFT range.\n if (channelRanges.length > 0 && channelRanges[0].bufferIndices.length > 0) {\n const bufferGroups = groupContiguousIndices(\n channelRanges[0].channelInfo,\n channelRanges[0].bufferIndices\n );\n\n for (const group of bufferGroups) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n channelRanges[0].channelInfo,\n group,\n item,\n generation\n );\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n for (const { ch, channelInfo } of channelRanges) {\n await renderChunkSubset(\n workerApi!,\n cacheKey,\n channelInfo,\n group,\n item,\n ch,\n generation\n );\n }\n }\n }\n\n renderedClipIdsRef.current.add(item.clipId);\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n // Phase 2: Render off-screen chunks in background batches\n // (each batch computes its own bounded FFT range).\n if (await renderBackgroundBatches(channelRanges, item)) return;\n }\n } catch (err) {\n if (err instanceof SpectrogramAbortError) return;\n console.warn(\n `[waveform-playlist] Spectrogram worker error for clip ${item.clipId}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n for (const item of clipsNeedingDisplayOnly) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n const clipCanvasInfo = spectrogramCanvasRegistryRef.current.get(item.clipId);\n if (!clipCanvasInfo || clipCanvasInfo.size === 0) continue;\n\n try {\n const clipPixelOffset = Math.floor(item.clipStartSample / samplesPerPixel);\n\n // Three-phase rendering with per-batch FFT (same as FFT path above).\n // Worker cache provides instant hits for previously computed ranges.\n const channelRanges: Array<{\n ch: number;\n channelInfo: { canvasIds: string[]; canvasWidths: number[] };\n viewportIndices: number[];\n bufferIndices: number[];\n remainingIndices: number[];\n }> = [];\n for (let ch = 0; ch < item.numChannels; ch++) {\n const channelInfo = clipCanvasInfo.get(ch);\n if (!channelInfo) continue;\n const range = getVisibleChunkRange(channelInfo, clipPixelOffset);\n channelRanges.push({ ch, channelInfo, ...range });\n }\n\n // Phase 1a: viewport chunks\n if (channelRanges.length > 0 && channelRanges[0].viewportIndices.length > 0) {\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n channelRanges[0].channelInfo,\n channelRanges[0].viewportIndices,\n item,\n generation\n );\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n for (const { ch, channelInfo, viewportIndices } of channelRanges) {\n await renderChunkSubset(\n workerApi!,\n cacheKey,\n channelInfo,\n viewportIndices,\n item,\n ch,\n generation\n );\n }\n }\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n // Phase 1b: buffer-zone chunks (grouped for contiguous FFT ranges)\n if (channelRanges.length > 0 && channelRanges[0].bufferIndices.length > 0) {\n const bufferGroups = groupContiguousIndices(\n channelRanges[0].channelInfo,\n channelRanges[0].bufferIndices\n );\n for (const group of bufferGroups) {\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n const cacheKey = await computeFFTForChunks(\n workerApi!,\n channelRanges[0].channelInfo,\n group,\n item,\n generation\n );\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n for (const { ch, channelInfo } of channelRanges) {\n await renderChunkSubset(\n workerApi!,\n cacheKey,\n channelInfo,\n group,\n item,\n ch,\n generation\n );\n }\n }\n }\n\n if (spectrogramGenerationRef.current !== generation || abortToken.aborted) return;\n\n // Phase 2: Render off-screen chunks in background batches.\n if (await renderBackgroundBatches(channelRanges, item)) return;\n } catch (err) {\n if (err instanceof SpectrogramAbortError) return;\n console.warn(\n `[waveform-playlist] Spectrogram display re-render error for clip ${item.clipId}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n };\n\n computeAsync().catch((err) => {\n console.error(\n `[waveform-playlist] Spectrogram computation failed: ${err instanceof Error ? err.message : String(err)}`\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps -- workerPoolSize intentionally omitted, pool created once via spectrogramWorkerRef guard\n }, [\n tracks,\n mono,\n spectrogramConfig,\n spectrogramColorMap,\n trackSpectrogramOverrides,\n waveHeight,\n samplesPerPixel,\n spectrogramCanvasVersion,\n scrollContainerRef,\n ]);\n\n // Setters\n const setTrackRenderMode = useCallback((trackId: string, mode: RenderMode) => {\n setTrackSpectrogramOverrides((prev) => {\n const next = new Map(prev);\n const existing = next.get(trackId);\n next.set(trackId, { ...existing, renderMode: mode });\n return next;\n });\n }, []);\n\n const setTrackSpectrogramConfig = useCallback(\n (trackId: string, config: SpectrogramConfig, colorMap?: ColorMapValue) => {\n setTrackSpectrogramOverrides((prev) => {\n const next = new Map(prev);\n const existing = next.get(trackId);\n next.set(trackId, {\n renderMode: existing?.renderMode ?? 'waveform',\n config,\n ...(colorMap !== undefined ? { colorMap } : { colorMap: existing?.colorMap }),\n });\n return next;\n });\n },\n []\n );\n\n // Lazily create the worker pool — keeps the same fallback path as the\n // pre-transfer / FFT effects.\n const ensureWorkerPool = useCallback((): SpectrogramWorkerApi | null => {\n if (spectrogramWorkerRef.current) return spectrogramWorkerRef.current;\n try {\n const pool = createSpectrogramWorkerPool(\n () =>\n new Worker(new URL('@dawcore/spectrogram/worker/spectrogram.worker', import.meta.url), {\n type: 'module',\n }),\n workerPoolSize\n );\n spectrogramWorkerRef.current = pool;\n return pool;\n } catch (err) {\n console.warn(\n `[waveform-playlist] Spectrogram Web Worker unavailable: ${err instanceof Error ? err.message : String(err)}`\n );\n return null;\n }\n }, [workerPoolSize]);\n\n const registerSpectrogramCanvas = useCallback(\n (reg: SpectrogramCanvasRegistration) => {\n const pool = ensureWorkerPool();\n if (!pool) return;\n\n try {\n pool.registerCanvas(reg.canvasId, reg.canvas);\n } catch (err) {\n console.warn(\n `[waveform-playlist] registerCanvas failed for ${reg.canvasId}: ${err instanceof Error ? err.message : String(err)}`\n );\n return;\n }\n\n const registry = spectrogramCanvasRegistryRef.current;\n if (!registry.has(reg.clipId)) {\n registry.set(reg.clipId, new Map());\n }\n const perClip = registry.get(reg.clipId)!;\n const entry = perClip.get(reg.channelIndex) ?? { canvasIds: [], canvasWidths: [] };\n const existingIdx = entry.canvasIds.indexOf(reg.canvasId);\n if (existingIdx >= 0) {\n entry.canvasWidths[existingIdx] = reg.widthPx;\n } else {\n entry.canvasIds.push(reg.canvasId);\n entry.canvasWidths.push(reg.widthPx);\n }\n perClip.set(reg.channelIndex, entry);\n setSpectrogramCanvasVersion((v) => v + 1);\n },\n [ensureWorkerPool]\n );\n\n const unregisterSpectrogramCanvas = useCallback((canvasId: string) => {\n const pool = spectrogramWorkerRef.current;\n if (pool) {\n try {\n pool.unregisterCanvas(canvasId);\n } catch (err) {\n console.warn(\n `[waveform-playlist] unregisterCanvas failed for ${canvasId}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n // Canvas IDs follow the format `${clipId}-ch${channelIndex}-chunk${n}`.\n const parsed = parseCanvasId(canvasId);\n if (!parsed) return;\n const { clipId, channelIndex } = parsed;\n\n const registry = spectrogramCanvasRegistryRef.current;\n const perClip = registry.get(clipId);\n if (!perClip) return;\n const entry = perClip.get(channelIndex);\n if (!entry) return;\n const idx = entry.canvasIds.indexOf(canvasId);\n if (idx >= 0) {\n entry.canvasIds.splice(idx, 1);\n entry.canvasWidths.splice(idx, 1);\n }\n if (entry.canvasIds.length === 0) {\n perClip.delete(channelIndex);\n }\n if (perClip.size === 0) {\n registry.delete(clipId);\n }\n setSpectrogramCanvasVersion((v) => v + 1);\n }, []);\n\n const renderMenuItems = useCallback(\n (props: {\n renderMode: string;\n onRenderModeChange: (mode: RenderMode) => void;\n onOpenSettings: () => void;\n onClose?: () => void;\n }) => {\n return SpectrogramMenuItems({\n renderMode: props.renderMode as RenderMode,\n onRenderModeChange: props.onRenderModeChange,\n onOpenSettings: props.onOpenSettings,\n onClose: props.onClose,\n });\n },\n []\n );\n\n const value: SpectrogramIntegration = useMemo(\n () => ({\n trackSpectrogramOverrides,\n spectrogramConfig,\n spectrogramColorMap,\n setTrackRenderMode,\n setTrackSpectrogramConfig,\n registerSpectrogramCanvas,\n unregisterSpectrogramCanvas,\n renderMenuItems,\n SettingsModal: SpectrogramSettingsModal,\n getColorMap,\n getFrequencyScale: getFrequencyScale as (\n name: string\n ) => (f: number, minF: number, maxF: number) => number,\n }),\n [\n trackSpectrogramOverrides,\n spectrogramConfig,\n spectrogramColorMap,\n setTrackRenderMode,\n setTrackSpectrogramConfig,\n registerSpectrogramCanvas,\n unregisterSpectrogramCanvas,\n renderMenuItems,\n ]\n );\n\n return <SpectrogramIntegrationProvider value={value}>{children}</SpectrogramIntegrationProvider>;\n};\n","import {\n MAX_CANVAS_WIDTH,\n type SpectrogramConfig,\n type SpectrogramComputeConfig,\n type ColorMapValue,\n type RenderMode,\n type TrackSpectrogramOverrides,\n} from '@waveform-playlist/core';\n\n/**\n * Pure, side-effect-free helpers extracted from `SpectrogramProvider`.\n *\n * These cover the geometry/key logic that the provider's worker-orchestration\n * effect depends on (chunk classification, FFT sample ranges, contiguous\n * grouping, config-change detection). Keeping them DOM-free makes them unit\n * testable in a Node environment — the provider supplies the imperative shell\n * (refs, worker calls, React state) around this functional core.\n */\n\n/** A channel's registered canvas chunks: parallel arrays of IDs and pixel widths. */\nexport interface ChannelChunkInfo {\n canvasIds: string[];\n canvasWidths: number[];\n}\n\n/** Chunk indices grouped by their relationship to the current scroll viewport. */\nexport interface ChunkTiers {\n /** Chunks intersecting the exact viewport — fast first paint. */\n viewportIndices: number[];\n /** Chunks within the 1.5× overscan buffer but outside the viewport. */\n bufferIndices: number[];\n /** Chunks outside the buffer — rendered in background batches. */\n remainingIndices: number[];\n}\n\n/** Scroll-container metrics needed to classify chunks. `null` ⇒ no container yet. */\nexport interface ViewportMetrics {\n scrollLeft: number;\n viewportWidth: number;\n}\n\n/** Extract the chunk number from a canvas ID like `\"clipId-ch0-chunk5\"` → `5`. */\nexport function extractChunkNumber(canvasId: string): number {\n const match = canvasId.match(/chunk(\\d+)$/);\n if (!match) {\n console.warn(`[spectrogram] Unexpected canvas ID format: ${canvasId}`);\n return 0;\n }\n return parseInt(match[1], 10);\n}\n\n/**\n * Parse a canvas ID of the form `${clipId}-ch${channelIndex}-chunk${n}` into its\n * clip ID and channel index. Returns `null` when the ID doesn't match.\n */\nexport function parseCanvasId(canvasId: string): { clipId: string; channelIndex: number } | null {\n const match = canvasId.match(/^(.+)-ch(\\d+)-chunk\\d+$/);\n if (!match) return null;\n return { clipId: match[1], channelIndex: parseInt(match[2], 10) };\n}\n\n/**\n * Split indices into contiguous groups based on their chunk numbers, e.g.\n * indices mapping to chunks `[0,1,4,5]` → `[[0,1],[4,5]]`. Prevents computing an\n * FFT range that spans the gap between non-adjacent chunks.\n */\nexport function groupContiguousIndices(\n channelInfo: { canvasIds: string[] },\n indices: number[]\n): number[][] {\n if (indices.length === 0) return [];\n const groups: number[][] = [];\n let currentGroup = [indices[0]];\n let prevChunk = extractChunkNumber(channelInfo.canvasIds[indices[0]]);\n for (let i = 1; i < indices.length; i++) {\n const chunk = extractChunkNumber(channelInfo.canvasIds[indices[i]]);\n if (chunk === prevChunk + 1) {\n currentGroup.push(indices[i]);\n } else {\n groups.push(currentGroup);\n currentGroup = [indices[i]];\n }\n prevChunk = chunk;\n }\n groups.push(currentGroup);\n return groups;\n}\n\n/**\n * Classify a channel's chunks into viewport / buffer / remaining tiers.\n *\n * The buffer uses a 1.5× viewport-width overscan to match\n * `useVisibleChunkIndices` in ScrollViewport, so FFT covers every mounted\n * canvas. When `viewport` is `null` (no scroll container yet), every chunk is\n * treated as in-viewport.\n */\nexport function classifyChunkTiers(\n channelInfo: ChannelChunkInfo,\n clipPixelOffset = 0,\n viewport: ViewportMetrics | null\n): ChunkTiers {\n if (!viewport) {\n return {\n viewportIndices: channelInfo.canvasWidths.map((_, i) => i),\n bufferIndices: [],\n remainingIndices: [],\n };\n }\n\n const { scrollLeft, viewportWidth } = viewport;\n const buffer = viewportWidth * 1.5;\n const bufferStart = Math.max(0, scrollLeft - buffer);\n const bufferEnd = scrollLeft + viewportWidth + buffer;\n\n const viewportIndices: number[] = [];\n const bufferIndices: number[] = [];\n const remainingIndices: number[] = [];\n\n for (let i = 0; i < channelInfo.canvasWidths.length; i++) {\n const chunkNumber = extractChunkNumber(channelInfo.canvasIds[i]);\n const chunkLeft = chunkNumber * MAX_CANVAS_WIDTH + clipPixelOffset;\n const chunkRight = chunkLeft + channelInfo.canvasWidths[i];\n if (chunkRight > scrollLeft && chunkLeft < scrollLeft + viewportWidth) {\n viewportIndices.push(i);\n } else if (chunkRight > bufferStart && chunkLeft < bufferEnd) {\n bufferIndices.push(i);\n } else {\n remainingIndices.push(i);\n }\n }\n\n return { viewportIndices, bufferIndices, remainingIndices };\n}\n\nexport interface ChunkSampleRangeParams {\n channelInfo: { canvasIds: string[]; canvasWidths: number[] };\n indices: number[];\n /** FFT window size (samples) used to pad the range against edge artifacts. */\n fftSize: number;\n offsetSamples: number;\n durationSamples: number;\n samplesPerPixel: number;\n}\n\n/**\n * Compute the (window-padded, clip-clamped) sample range covered by a set of\n * chunk indices. Computing per-batch ranges on demand avoids allocating one\n * giant FFT array for a full clip (which OOMs on 1hr+ files).\n */\nexport function computeChunkSampleRange({\n channelInfo,\n indices,\n fftSize,\n offsetSamples,\n durationSamples,\n samplesPerPixel,\n}: ChunkSampleRangeParams): { paddedStart: number; paddedEnd: number } {\n const chunkNumbers = indices.map((i) => extractChunkNumber(channelInfo.canvasIds[i]));\n const minChunk = Math.min(...chunkNumbers);\n const maxChunk = Math.max(...chunkNumbers);\n const maxChunkIdx = indices[chunkNumbers.indexOf(maxChunk)];\n const lastChunkWidth = channelInfo.canvasWidths[maxChunkIdx];\n\n const startPx = minChunk * MAX_CANVAS_WIDTH;\n const endPx = maxChunk * MAX_CANVAS_WIDTH + lastChunkWidth;\n\n const rangeStartSample = offsetSamples + Math.floor(startPx * samplesPerPixel);\n const rangeEndSample = Math.min(\n offsetSamples + durationSamples,\n offsetSamples + Math.ceil(endPx * samplesPerPixel)\n );\n\n // Pad by one window on each side to avoid edge artifacts, clamped to the clip.\n const paddedStart = Math.max(offsetSamples, rangeStartSample - fftSize);\n const paddedEnd = Math.min(offsetSamples + durationSamples, rangeEndSample + fftSize);\n\n return { paddedStart, paddedEnd };\n}\n\n/** Resolve a track's effective render mode: override → track → `'waveform'`. */\nexport function resolveRenderMode(\n override: TrackSpectrogramOverrides | undefined,\n trackRenderMode: RenderMode | undefined\n): RenderMode {\n return override?.renderMode ?? trackRenderMode ?? 'waveform';\n}\n\n/** Project a `SpectrogramConfig` down to the fields that affect FFT computation. */\nexport function toComputeConfig(cfg: SpectrogramConfig | undefined): SpectrogramComputeConfig {\n return {\n fftSize: cfg?.fftSize,\n hopSize: cfg?.hopSize,\n windowFunction: cfg?.windowFunction,\n alpha: cfg?.alpha,\n zeroPaddingFactor: cfg?.zeroPaddingFactor,\n };\n}\n\n/**\n * Stable string key for a track's full spectrogram appearance (mode + config +\n * color map + mono). A change means the rendered output must be recomputed.\n */\nexport function buildConfigKey(params: {\n mode: RenderMode;\n cfg: SpectrogramConfig | undefined;\n cm: ColorMapValue | undefined;\n mono: boolean;\n}): string {\n const { mode, cfg, cm, mono } = params;\n return JSON.stringify({ mode, cfg, cm, mono });\n}\n\n/**\n * Stable string key for the inputs that affect the FFT only (mode + mono +\n * compute config). A change here means the cached FFT data is stale; a config\n * change that leaves this key untouched (e.g. color map) only needs re-display.\n */\nexport function buildFFTKey(params: {\n mode: RenderMode;\n mono: boolean;\n computeConfig: SpectrogramComputeConfig;\n}): string {\n const { mode, mono, computeConfig } = params;\n return JSON.stringify({ mode, mono, ...computeConfig });\n}\n\n/**\n * Whether two key maps differ — different size, or any key present in `current`\n * whose value doesn't match `prev`. Used to detect config/FFT changes between\n * effect runs.\n */\nexport function mapsDiffer(prev: Map<string, string>, current: Map<string, string>): boolean {\n if (current.size !== prev.size) return true;\n for (const [key, value] of current) {\n if (prev.get(key) !== value) return true;\n }\n return false;\n}\n"],"mappings":"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","names":["styled","jsx","jsxs","useState","useEffect","useRef","MAX_CANVAS_WIDTH","jsx","useState","useRef","useEffect","channelDataArrays","MAX_CANVAS_WIDTH"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waveform-playlist/spectrogram",
3
- "version": "13.0.1",
3
+ "version": "13.0.3",
4
4
  "description": "React Provider + UI for spectrograms — wraps @dawcore/spectrogram",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -36,26 +36,31 @@
36
36
  "README.md"
37
37
  ],
38
38
  "devDependencies": {
39
- "@types/react": "^18.2.45",
40
- "@types/styled-components": "^5.1.26",
41
- "tsup": "^8.0.1",
42
- "vitest": "^3.0.0",
43
- "typescript": "^5.3.3",
44
- "@waveform-playlist/browser": "13.0.0"
39
+ "@testing-library/react": "^16.3.2",
40
+ "@types/react": "^18.3.31",
41
+ "@types/styled-components": "^5.1.36",
42
+ "jsdom": "^28.1.0",
43
+ "react": "^18.3.1",
44
+ "react-dom": "^18.3.1",
45
+ "styled-components": "^6.4.2",
46
+ "tsup": "^8.5.1",
47
+ "typescript": "^5.9.3",
48
+ "vitest": "^3.2.6",
49
+ "@waveform-playlist/browser": "13.1.2"
45
50
  },
46
51
  "dependencies": {
47
- "@waveform-playlist/core": "12.1.0",
48
- "@dawcore/spectrogram": "0.0.2"
52
+ "@waveform-playlist/core": "12.2.0",
53
+ "@dawcore/spectrogram": "0.0.3"
49
54
  },
50
55
  "peerDependencies": {
51
56
  "react": "^18.0.0",
52
57
  "styled-components": "^6.0.0",
53
- "@waveform-playlist/browser": "13.0.0"
58
+ "@waveform-playlist/browser": "^13.1.2"
54
59
  },
55
60
  "scripts": {
56
61
  "build": "tsup",
57
62
  "dev": "tsup --watch",
58
63
  "typecheck": "tsc --noEmit",
59
- "test": "vitest run --passWithNoTests"
64
+ "test": "vitest run"
60
65
  }
61
66
  }