@waveform-playlist/spectrogram 7.1.2 → 8.0.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.
@@ -183,7 +183,17 @@ function computeFromChannelData(channelData, config, sampleRate, offsetSamples,
183
183
  fftMagnitudeDb(real, dbBuf);
184
184
  data.set(dbBuf, frame * frequencyBinCount);
185
185
  }
186
- return { fftSize: actualFftSize, windowSize, frequencyBinCount, sampleRate, hopSize, frameCount, data, gainDb, rangeDb };
186
+ return {
187
+ fftSize: actualFftSize,
188
+ windowSize,
189
+ frequencyBinCount,
190
+ sampleRate,
191
+ hopSize,
192
+ frameCount,
193
+ data,
194
+ gainDb,
195
+ rangeDb
196
+ };
187
197
  }
188
198
  function computeMonoFromChannels(channels, config, sampleRate, offsetSamples, durationSamples) {
189
199
  if (channels.length === 1) {
@@ -220,7 +230,17 @@ function computeMonoFromChannels(channels, config, sampleRate, offsetSamples, du
220
230
  fftMagnitudeDb(real, dbBuf);
221
231
  data.set(dbBuf, frame * frequencyBinCount);
222
232
  }
223
- return { fftSize: actualFftSize, windowSize, frequencyBinCount, sampleRate, hopSize, frameCount, data, gainDb, rangeDb };
233
+ return {
234
+ fftSize: actualFftSize,
235
+ windowSize,
236
+ frequencyBinCount,
237
+ sampleRate,
238
+ hopSize,
239
+ frameCount,
240
+ data,
241
+ gainDb,
242
+ rangeDb
243
+ };
224
244
  }
225
245
  function renderSpectrogramToCanvas(specData, canvasIds, canvasWidths, canvasHeight, devicePixelRatio, samplesPerPixel, colorLUT, scaleFn, minFrequency, maxFrequency, isNonLinear, globalPixelOffsets, gainDbOverride, rangeDbOverride, sampleOffset = 0) {
226
246
  const { frequencyBinCount, frameCount, hopSize, sampleRate } = specData;
@@ -347,7 +367,15 @@ self.onmessage = (e) => {
347
367
  if (msg.type === "compute-fft") {
348
368
  const { id: id2 } = msg;
349
369
  try {
350
- const { clipId, config: config2, sampleRate: msgSampleRate, offsetSamples: offsetSamples2, durationSamples: durationSamples2, mono: mono2, sampleRange } = msg;
370
+ const {
371
+ clipId,
372
+ config: config2,
373
+ sampleRate: msgSampleRate,
374
+ offsetSamples: offsetSamples2,
375
+ durationSamples: durationSamples2,
376
+ mono: mono2,
377
+ sampleRange
378
+ } = msg;
351
379
  const registered = audioDataRegistry.get(clipId);
352
380
  const channelDataArrays2 = registered && msg.channelDataArrays.length === 0 ? registered.channelDataArrays : msg.channelDataArrays;
353
381
  const sampleRate2 = registered && msg.channelDataArrays.length === 0 ? registered.sampleRate : msgSampleRate;
@@ -376,12 +404,24 @@ self.onmessage = (e) => {
376
404
  const spectrograms = [];
377
405
  if (mono2 || channelDataArrays2.length === 1) {
378
406
  spectrograms.push(
379
- computeMonoFromChannels(channelDataArrays2, config2, sampleRate2, effectiveOffset, effectiveDuration)
407
+ computeMonoFromChannels(
408
+ channelDataArrays2,
409
+ config2,
410
+ sampleRate2,
411
+ effectiveOffset,
412
+ effectiveDuration
413
+ )
380
414
  );
381
415
  } else {
382
416
  for (const channelData of channelDataArrays2) {
383
417
  spectrograms.push(
384
- computeFromChannelData(channelData, config2, sampleRate2, effectiveOffset, effectiveDuration)
418
+ computeFromChannelData(
419
+ channelData,
420
+ config2,
421
+ sampleRate2,
422
+ effectiveOffset,
423
+ effectiveDuration
424
+ )
385
425
  );
386
426
  }
387
427
  }
@@ -450,11 +490,25 @@ self.onmessage = (e) => {
450
490
  if (msg.type === "compute-render") {
451
491
  const { id: id2 } = msg;
452
492
  try {
453
- const { channelDataArrays: channelDataArrays2, config: config2, sampleRate: sampleRate2, offsetSamples: offsetSamples2, durationSamples: durationSamples2, mono: mono2, render } = msg;
493
+ const {
494
+ channelDataArrays: channelDataArrays2,
495
+ config: config2,
496
+ sampleRate: sampleRate2,
497
+ offsetSamples: offsetSamples2,
498
+ durationSamples: durationSamples2,
499
+ mono: mono2,
500
+ render
501
+ } = msg;
454
502
  const spectrograms = [];
455
503
  if (mono2 || channelDataArrays2.length === 1) {
456
504
  spectrograms.push(
457
- computeMonoFromChannels(channelDataArrays2, config2, sampleRate2, offsetSamples2, durationSamples2)
505
+ computeMonoFromChannels(
506
+ channelDataArrays2,
507
+ config2,
508
+ sampleRate2,
509
+ offsetSamples2,
510
+ durationSamples2
511
+ )
458
512
  );
459
513
  } else {
460
514
  for (const channelData of channelDataArrays2) {
@@ -495,7 +549,13 @@ self.onmessage = (e) => {
495
549
  const spectrograms = [];
496
550
  if (mono || channelDataArrays.length === 1) {
497
551
  spectrograms.push(
498
- computeMonoFromChannels(channelDataArrays, config, sampleRate, offsetSamples, durationSamples)
552
+ computeMonoFromChannels(
553
+ channelDataArrays,
554
+ config,
555
+ sampleRate,
556
+ offsetSamples,
557
+ durationSamples
558
+ )
499
559
  );
500
560
  } else {
501
561
  for (const channelData of channelDataArrays) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/computation/fft.ts","../../src/computation/windowFunctions.ts","../../src/computation/frequencyScales.ts","../../src/worker/spectrogram.worker.ts"],"sourcesContent":["import FFT from 'fft.js';\n\n/**\n * Cache fft.js instances per size (pre-computes twiddle factors).\n */\nconst fftInstances = new Map<number, FFT>();\nconst complexBuffers = new Map<number, number[]>();\n\nfunction getFftInstance(size: number): FFT {\n let instance = fftInstances.get(size);\n if (!instance) {\n instance = new FFT(size);\n fftInstances.set(size, instance);\n complexBuffers.set(size, instance.createComplexArray());\n }\n return instance;\n}\n\nfunction getComplexBuffer(size: number): number[] {\n const buffer = complexBuffers.get(size);\n if (!buffer) {\n throw new Error(`No complex buffer for size ${size}. Call getFftInstance first.`);\n }\n return buffer;\n}\n\n/**\n * In-place FFT using fft.js (radix-4).\n * @param real - Real part (modified in place)\n * @param imag - Imaginary part (modified in place)\n */\nexport function fft(real: Float32Array, imag: Float32Array): void {\n const n = real.length;\n const f = getFftInstance(n);\n const input = f.createComplexArray();\n const out = getComplexBuffer(n);\n\n for (let i = 0; i < n; i++) {\n input[i * 2] = real[i];\n input[i * 2 + 1] = imag[i];\n }\n\n f.transform(out, input);\n\n for (let i = 0; i < n; i++) {\n real[i] = out[i * 2];\n imag[i] = out[i * 2 + 1];\n }\n}\n\n/**\n * Fused FFT → magnitude → decibels for real-valued input.\n * Uses fft.js realTransform (radix-4, ~25% faster for real input).\n * Writes dB values for positive frequencies (n/2 bins) into `out`.\n *\n * @param real - Real input (windowed audio frame, length n)\n * @param out - Output array for dB values (length >= n/2)\n */\nexport function fftMagnitudeDb(real: Float32Array, out: Float32Array): void {\n const n = real.length;\n const f = getFftInstance(n);\n const complexOut = getComplexBuffer(n);\n\n f.realTransform(complexOut, real);\n\n const half = n >> 1;\n for (let i = 0; i < half; i++) {\n const re = complexOut[i * 2];\n const im = complexOut[i * 2 + 1];\n let db = 20 * Math.log10(Math.sqrt(re * re + im * im) + 1e-10);\n if (db < -160) db = -160;\n out[i] = db;\n }\n}\n\n/**\n * Compute magnitude spectrum from FFT output.\n * Returns only the first half (positive frequencies).\n */\nexport function magnitudeSpectrum(real: Float32Array, imag: Float32Array): Float32Array {\n const n = real.length >> 1;\n const magnitudes = new Float32Array(n);\n for (let i = 0; i < n; i++) {\n magnitudes[i] = Math.sqrt(real[i] * real[i] + imag[i] * imag[i]);\n }\n return magnitudes;\n}\n\n/**\n * Convert magnitudes to decibels with a fixed -160 dB floor.\n * Gain is applied at render time, not during FFT.\n */\nexport function toDecibels(magnitudes: Float32Array): Float32Array {\n const result = new Float32Array(magnitudes.length);\n for (let i = 0; i < magnitudes.length; i++) {\n let db = 20 * Math.log10(magnitudes[i] + 1e-10);\n if (db < -160) db = -160;\n result[i] = db;\n }\n return result;\n}\n","/**\n * Window functions for spectral analysis.\n */\n\nexport function getWindowFunction(\n name: string,\n size: number,\n alpha?: number\n): Float32Array {\n const window = new Float32Array(size);\n const N = size;\n\n switch (name) {\n case 'rectangular':\n for (let i = 0; i < size; i++) window[i] = 1;\n break;\n\n case 'bartlett':\n for (let i = 0; i < size; i++) {\n window[i] = 1 - Math.abs((2 * i - N) / N);\n }\n break;\n\n case 'hann':\n for (let i = 0; i < size; i++) {\n window[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / N));\n }\n break;\n\n case 'hamming':\n for (let i = 0; i < size; i++) {\n const a = alpha ?? 0.54;\n window[i] = a - (1 - a) * Math.cos((2 * Math.PI * i) / N);\n }\n break;\n\n case 'blackman': {\n const a0 = 0.42;\n const a1 = 0.5;\n const a2 = 0.08;\n for (let i = 0; i < size; i++) {\n window[i] =\n a0 -\n a1 * Math.cos((2 * Math.PI * i) / N) +\n a2 * Math.cos((4 * Math.PI * i) / N);\n }\n break;\n }\n\n case 'blackman-harris': {\n const c0 = 0.35875;\n const c1 = 0.48829;\n const c2 = 0.14128;\n const c3 = 0.01168;\n for (let i = 0; i < size; i++) {\n window[i] =\n c0 -\n c1 * Math.cos((2 * Math.PI * i) / N) +\n c2 * Math.cos((4 * Math.PI * i) / N) -\n c3 * Math.cos((6 * Math.PI * i) / N);\n }\n break;\n }\n\n default:\n console.warn(`[spectrogram] Unknown window function \"${name}\", falling back to hann`);\n for (let i = 0; i < size; i++) {\n window[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / N));\n }\n }\n\n // Amplitude normalization: scale so a 0 dB sine produces a 0 dB spectrum peak.\n // Matches Audacity: scale = 2.0 / sum(window)\n let sum = 0;\n for (let i = 0; i < size; i++) sum += window[i];\n if (sum > 0) {\n const scale = 2.0 / sum;\n for (let i = 0; i < size; i++) window[i] *= scale;\n }\n\n return window;\n}\n","/**\n * Frequency scale mapping functions.\n * Each maps a frequency (Hz) to a normalized position [0, 1].\n */\n\nfunction linearScale(f: number, minF: number, maxF: number): number {\n if (maxF === minF) return 0;\n return (f - minF) / (maxF - minF);\n}\n\nfunction logarithmicScale(f: number, minF: number, maxF: number): number {\n const logMin = Math.log2(Math.max(minF, 1));\n const logMax = Math.log2(maxF);\n if (logMax === logMin) return 0;\n return (Math.log2(Math.max(f, 1)) - logMin) / (logMax - logMin);\n}\n\nfunction hzToMel(f: number): number {\n return 2595 * Math.log10(1 + f / 700);\n}\n\nfunction melScale(f: number, minF: number, maxF: number): number {\n const melMin = hzToMel(minF);\n const melMax = hzToMel(maxF);\n if (melMax === melMin) return 0;\n return (hzToMel(f) - melMin) / (melMax - melMin);\n}\n\nfunction hzToBark(f: number): number {\n return 13 * Math.atan(0.00076 * f) + 3.5 * Math.atan((f / 7500) ** 2);\n}\n\nfunction barkScale(f: number, minF: number, maxF: number): number {\n const barkMin = hzToBark(minF);\n const barkMax = hzToBark(maxF);\n if (barkMax === barkMin) return 0;\n return (hzToBark(f) - barkMin) / (barkMax - barkMin);\n}\n\nfunction hzToErb(f: number): number {\n return 21.4 * Math.log10(1 + 0.00437 * f);\n}\n\nfunction erbScale(f: number, minF: number, maxF: number): number {\n const erbMin = hzToErb(minF);\n const erbMax = hzToErb(maxF);\n if (erbMax === erbMin) return 0;\n return (hzToErb(f) - erbMin) / (erbMax - erbMin);\n}\n\nexport type FrequencyScaleName = 'linear' | 'logarithmic' | 'mel' | 'bark' | 'erb';\n\n/**\n * Returns a mapping function: (frequencyHz, minFrequency, maxFrequency) → [0, 1]\n */\nexport function getFrequencyScale(\n name: FrequencyScaleName\n): (f: number, minF: number, maxF: number) => number {\n switch (name) {\n case 'logarithmic': return logarithmicScale;\n case 'mel': return melScale;\n case 'bark': return barkScale;\n case 'erb': return erbScale;\n case 'linear':\n return linearScale;\n default:\n console.warn(`[spectrogram] Unknown frequency scale \"${name}\", falling back to linear`);\n return linearScale;\n }\n}\n","/**\n * Web Worker for off-main-thread spectrogram computation and rendering.\n *\n * Supports five modes:\n * 1. `compute` — FFT only, returns SpectrogramData to main thread (backward compat)\n * 2. `register-canvas` / `unregister-canvas` — manage OffscreenCanvas ownership\n * 3. `compute-render` — FFT + direct pixel rendering to registered OffscreenCanvases\n * 4. `compute-fft` — FFT with caching, returns cache key (no rendering)\n * 5. `render-chunks` — render specific chunks from cached FFT data\n */\n\nimport type { SpectrogramConfig, SpectrogramComputeConfig, SpectrogramData } from '@waveform-playlist/core';\nimport { fftMagnitudeDb } from '../computation/fft';\nimport { getWindowFunction } from '../computation/windowFunctions';\nimport { getFrequencyScale, type FrequencyScaleName } from '../computation/frequencyScales';\n\n// --- Canvas registry ---\nconst canvasRegistry = new Map<string, OffscreenCanvas>();\n\n// --- Audio data registry ---\n// Pre-transferred audio data keyed by clipId, avoiding re-transfer on compute-fft.\nconst audioDataRegistry = new Map<string, { channelDataArrays: Float32Array[]; sampleRate: number }>();\n\n// --- FFT cache ---\n// Caches raw dB spectrogram data keyed by FFT computation params.\n// Display-only params (gain, range, colormap) don't affect the cache key.\n// sampleOffset: the sample position where this FFT data starts (for range-limited FFT)\ninterface FFTCacheEntry {\n spectrograms: SpectrogramData[];\n sampleOffset: number;\n}\nconst fftCache = new Map<string, FFTCacheEntry>();\n\nfunction generateCacheKey(params: {\n clipId: string;\n channelIndex: number;\n offsetSamples: number;\n durationSamples: number;\n sampleRate: number;\n compute: SpectrogramComputeConfig;\n mono: boolean;\n}): string {\n const { compute: c } = params;\n return `${params.clipId}:${params.channelIndex}:${params.offsetSamples}:${params.durationSamples}:${params.sampleRate}:${c.fftSize ?? ''}:${c.zeroPaddingFactor ?? ''}:${c.hopSize ?? ''}:${c.windowFunction ?? ''}:${c.alpha ?? ''}:${params.mono ? 1 : 0}`;\n}\n\n// --- Message types ---\n\ninterface ComputeRequest {\n type?: 'compute';\n id: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n mono: boolean;\n}\n\ninterface RegisterCanvasMessage {\n type: 'register-canvas';\n canvasId: string;\n canvas: OffscreenCanvas;\n}\n\ninterface UnregisterCanvasMessage {\n type: 'unregister-canvas';\n canvasId: string;\n}\n\ninterface ComputeRenderRequest {\n type: 'compute-render';\n id: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n mono: boolean;\n render: {\n canvasIds: string[][]; // [channel][chunk] → canvasId\n canvasWidths: number[]; // per-chunk CSS widths\n canvasHeight: number;\n devicePixelRatio: number;\n samplesPerPixel: number;\n colorLUT: Uint8Array;\n frequencyScale: string;\n minFrequency: number;\n maxFrequency: number;\n };\n}\n\ninterface RegisterAudioDataMessage {\n type: 'register-audio-data';\n clipId: string;\n channelDataArrays: Float32Array[];\n sampleRate: number;\n}\n\ninterface UnregisterAudioDataMessage {\n type: 'unregister-audio-data';\n clipId: string;\n}\n\ninterface ComputeFFTRequest {\n type: 'compute-fft';\n id: string;\n clipId: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n mono: boolean;\n sampleRange?: { start: number; end: number };\n}\n\ninterface RenderChunksRequest {\n type: 'render-chunks';\n id: string;\n cacheKey: string;\n canvasIds: string[]; // flat list of canvas IDs to render\n canvasWidths: number[]; // per-chunk CSS widths\n globalPixelOffsets: number[]; // pixel offset for each chunk\n canvasHeight: number;\n devicePixelRatio: number;\n samplesPerPixel: number;\n colorLUT: Uint8Array;\n frequencyScale: string;\n minFrequency: number;\n maxFrequency: number;\n gainDb: number;\n rangeDb: number;\n channelIndex: number;\n}\n\ntype WorkerMessage = ComputeRequest | RegisterCanvasMessage | UnregisterCanvasMessage | ComputeRenderRequest | ComputeFFTRequest | RenderChunksRequest | RegisterAudioDataMessage | UnregisterAudioDataMessage;\n\ntype ComputeResponse =\n | { id: string; type: 'spectrograms'; spectrograms: SpectrogramData[] }\n | { id: string; type: 'cache-key'; cacheKey: string }\n | { id: string; type: 'done' }\n | { id: string; type: 'error'; error: string };\n\n// --- FFT computation (unchanged) ---\n\nfunction computeFromChannelData(\n channelData: Float32Array,\n config: SpectrogramConfig,\n sampleRate: number,\n offsetSamples: number,\n durationSamples: number,\n): SpectrogramData {\n const windowSize = config.fftSize ?? 2048;\n const zeroPaddingFactor = config.zeroPaddingFactor ?? 2;\n const actualFftSize = windowSize * zeroPaddingFactor;\n const hopSize = config.hopSize ?? Math.floor(windowSize / 4);\n const windowName = config.windowFunction ?? 'hann';\n const gainDb = config.gainDb ?? 20;\n const rangeDb = config.rangeDb ?? 80;\n const alpha = config.alpha;\n\n const frequencyBinCount = actualFftSize >> 1;\n const totalSamples = durationSamples;\n\n const window = getWindowFunction(windowName, windowSize, alpha);\n const frameCount = Math.max(1, Math.floor((totalSamples - windowSize) / hopSize) + 1);\n const data = new Float32Array(frameCount * frequencyBinCount);\n const real = new Float32Array(actualFftSize);\n const dbBuf = new Float32Array(frequencyBinCount);\n\n for (let frame = 0; frame < frameCount; frame++) {\n const start = offsetSamples + frame * hopSize;\n\n for (let i = 0; i < windowSize; i++) {\n const sampleIdx = start + i;\n real[i] = sampleIdx < channelData.length ? channelData[sampleIdx] * window[i] : 0;\n }\n for (let i = windowSize; i < actualFftSize; i++) {\n real[i] = 0;\n }\n\n fftMagnitudeDb(real, dbBuf);\n data.set(dbBuf, frame * frequencyBinCount);\n }\n\n return { fftSize: actualFftSize, windowSize, frequencyBinCount, sampleRate, hopSize, frameCount, data, gainDb, rangeDb };\n}\n\nfunction computeMonoFromChannels(\n channels: Float32Array[],\n config: SpectrogramConfig,\n sampleRate: number,\n offsetSamples: number,\n durationSamples: number,\n): SpectrogramData {\n if (channels.length === 1) {\n return computeFromChannelData(channels[0], config, sampleRate, offsetSamples, durationSamples);\n }\n\n const windowSize = config.fftSize ?? 2048;\n const zeroPaddingFactor = config.zeroPaddingFactor ?? 2;\n const actualFftSize = windowSize * zeroPaddingFactor;\n const hopSize = config.hopSize ?? Math.floor(windowSize / 4);\n const windowName = config.windowFunction ?? 'hann';\n const gainDb = config.gainDb ?? 20;\n const rangeDb = config.rangeDb ?? 80;\n const alpha = config.alpha;\n\n const frequencyBinCount = actualFftSize >> 1;\n const numChannels = channels.length;\n\n const window = getWindowFunction(windowName, windowSize, alpha);\n const frameCount = Math.max(1, Math.floor((durationSamples - windowSize) / hopSize) + 1);\n const data = new Float32Array(frameCount * frequencyBinCount);\n const real = new Float32Array(actualFftSize);\n const dbBuf = new Float32Array(frequencyBinCount);\n\n for (let frame = 0; frame < frameCount; frame++) {\n const start = offsetSamples + frame * hopSize;\n\n for (let i = 0; i < windowSize; i++) {\n const sampleIdx = start + i;\n let sum = 0;\n for (let ch = 0; ch < numChannels; ch++) {\n sum += sampleIdx < channels[ch].length ? channels[ch][sampleIdx] : 0;\n }\n real[i] = (sum / numChannels) * window[i];\n }\n for (let i = windowSize; i < actualFftSize; i++) {\n real[i] = 0;\n }\n\n fftMagnitudeDb(real, dbBuf);\n data.set(dbBuf, frame * frequencyBinCount);\n }\n\n return { fftSize: actualFftSize, windowSize, frequencyBinCount, sampleRate, hopSize, frameCount, data, gainDb, rangeDb };\n}\n\n// --- Rendering ---\n\nfunction renderSpectrogramToCanvas(\n specData: SpectrogramData,\n canvasIds: string[],\n canvasWidths: number[],\n canvasHeight: number,\n devicePixelRatio: number,\n samplesPerPixel: number,\n colorLUT: Uint8Array,\n scaleFn: (f: number, minF: number, maxF: number) => number,\n minFrequency: number,\n maxFrequency: number,\n isNonLinear: boolean,\n globalPixelOffsets?: number[],\n gainDbOverride?: number,\n rangeDbOverride?: number,\n sampleOffset = 0,\n): void {\n const { frequencyBinCount, frameCount, hopSize, sampleRate } = specData;\n const gainDb = gainDbOverride ?? specData.gainDb;\n const rawRangeDb = rangeDbOverride ?? specData.rangeDb;\n const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;\n const maxF = maxFrequency > 0 ? maxFrequency : sampleRate / 2;\n const binToFreq = (bin: number) => (bin / frequencyBinCount) * (sampleRate / 2);\n\n let accumulatedOffset = 0;\n\n for (let chunkIdx = 0; chunkIdx < canvasIds.length; chunkIdx++) {\n const canvasId = canvasIds[chunkIdx];\n const offscreen = canvasRegistry.get(canvasId);\n if (!offscreen) {\n console.warn(`[spectrogram-worker] Canvas \"${canvasId}\" not found in registry`);\n if (!globalPixelOffsets) accumulatedOffset += canvasWidths[chunkIdx];\n continue;\n }\n\n const canvasWidth = canvasWidths[chunkIdx];\n const globalPixelOffset = globalPixelOffsets ? globalPixelOffsets[chunkIdx] : accumulatedOffset;\n\n // Set physical canvas size for DPR\n offscreen.width = canvasWidth * devicePixelRatio;\n offscreen.height = canvasHeight * devicePixelRatio;\n\n const ctx = offscreen.getContext('2d');\n if (!ctx) {\n console.warn(`[spectrogram-worker] getContext('2d') returned null for canvas \"${canvasId}\"`);\n if (!globalPixelOffsets) accumulatedOffset += canvasWidth;\n continue;\n }\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, offscreen.width, offscreen.height);\n ctx.imageSmoothingEnabled = false;\n\n // Create ImageData at CSS pixel size\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 const samplePos = globalX * samplesPerPixel - sampleOffset;\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 const normalizedY = 1 - y / canvasHeight;\n\n let bin = Math.floor(normalizedY * frequencyBinCount);\n\n if (isNonLinear) {\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 const db = specData.data[frameOffset + bin];\n const normalized = Math.max(0, Math.min(1, (db + rangeDb + gainDb) / rangeDb));\n\n const colorIdx = Math.floor(normalized * 255);\n const pixelIdx = (y * canvasWidth + x) * 4;\n pixels[pixelIdx] = colorLUT[colorIdx * 3];\n pixels[pixelIdx + 1] = colorLUT[colorIdx * 3 + 1];\n pixels[pixelIdx + 2] = colorLUT[colorIdx * 3 + 2];\n pixels[pixelIdx + 3] = 255;\n }\n }\n\n // Put image data and scale up for DPR\n if (devicePixelRatio === 1) {\n ctx.putImageData(imgData, 0, 0);\n } else {\n // Render at CSS size to a temporary OffscreenCanvas, then scale up\n const tmpCanvas = new OffscreenCanvas(canvasWidth, canvasHeight);\n const tmpCtx = tmpCanvas.getContext('2d');\n if (!tmpCtx) continue;\n tmpCtx.putImageData(imgData, 0, 0);\n\n ctx.imageSmoothingEnabled = false;\n ctx.drawImage(tmpCanvas, 0, 0, offscreen.width, offscreen.height);\n }\n\n if (!globalPixelOffsets) accumulatedOffset += canvasWidth;\n }\n}\n\n// --- Message handler ---\n\nself.onmessage = (e: MessageEvent<WorkerMessage>) => {\n const msg = e.data;\n\n // Register canvas\n if (msg.type === 'register-canvas') {\n try {\n canvasRegistry.set(msg.canvasId, msg.canvas);\n } catch (err) {\n console.warn('[spectrogram-worker] register-canvas failed:', err);\n }\n return;\n }\n\n // Unregister canvas\n if (msg.type === 'unregister-canvas') {\n try {\n canvasRegistry.delete(msg.canvasId);\n } catch (err) {\n console.warn('[spectrogram-worker] unregister-canvas failed:', err);\n }\n return;\n }\n\n // Register audio data for a clip (pre-transfer)\n if (msg.type === 'register-audio-data') {\n try {\n audioDataRegistry.set(msg.clipId, {\n channelDataArrays: msg.channelDataArrays,\n sampleRate: msg.sampleRate,\n });\n } catch (err) {\n console.warn('[spectrogram-worker] register-audio-data failed:', err);\n }\n return;\n }\n\n // Unregister audio data for a clip + evict related FFT cache entries\n if (msg.type === 'unregister-audio-data') {\n try {\n audioDataRegistry.delete(msg.clipId);\n const prefix = `${msg.clipId}:`;\n for (const key of fftCache.keys()) {\n if (key.startsWith(prefix)) {\n fftCache.delete(key);\n }\n }\n } catch (err) {\n console.warn('[spectrogram-worker] unregister-audio-data failed:', err);\n }\n return;\n }\n\n // Compute FFT only (with caching), return cache key\n if (msg.type === 'compute-fft') {\n const { id } = msg;\n try {\n const { clipId, config, sampleRate: msgSampleRate, offsetSamples, durationSamples, mono, sampleRange } = msg;\n\n // Use pre-registered audio data if available, otherwise use message payload\n const registered = audioDataRegistry.get(clipId);\n const channelDataArrays = (registered && msg.channelDataArrays.length === 0)\n ? registered.channelDataArrays\n : msg.channelDataArrays;\n const sampleRate = (registered && msg.channelDataArrays.length === 0)\n ? registered.sampleRate\n : msgSampleRate;\n\n const fftSize = config.fftSize ?? 2048;\n const zeroPaddingFactor = config.zeroPaddingFactor ?? 2;\n const hopSize = config.hopSize ?? Math.floor(fftSize / 4);\n const windowFunction = config.windowFunction ?? 'hann';\n\n // Use sampleRange if provided (visible-range-first optimization)\n const effectiveOffset = sampleRange ? sampleRange.start : offsetSamples;\n const effectiveDuration = sampleRange\n ? (sampleRange.end - sampleRange.start)\n : durationSamples;\n\n const cacheKey = generateCacheKey({\n clipId, channelIndex: 0, offsetSamples: effectiveOffset, durationSamples: effectiveDuration, sampleRate,\n compute: { fftSize, zeroPaddingFactor, hopSize, windowFunction, alpha: config.alpha },\n mono,\n });\n\n if (!fftCache.has(cacheKey)) {\n // Evict stale cache entries for this clip (different FFT params)\n const clipPrefix = `${clipId}:`;\n for (const key of fftCache.keys()) {\n if (key.startsWith(clipPrefix) && key !== cacheKey) {\n fftCache.delete(key);\n }\n }\n\n const spectrograms: SpectrogramData[] = [];\n if (mono || channelDataArrays.length === 1) {\n spectrograms.push(\n computeMonoFromChannels(channelDataArrays, config, sampleRate, effectiveOffset, effectiveDuration)\n );\n } else {\n for (const channelData of channelDataArrays) {\n spectrograms.push(\n computeFromChannelData(channelData, config, sampleRate, effectiveOffset, effectiveDuration)\n );\n }\n }\n fftCache.set(cacheKey, { spectrograms, sampleOffset: effectiveOffset });\n }\n\n const response: ComputeResponse = { id, type: 'cache-key', cacheKey };\n (self as unknown as Worker).postMessage(response);\n } catch (err) {\n const response: ComputeResponse = { id, type: 'error', error: String(err) };\n (self as unknown as Worker).postMessage(response);\n }\n return;\n }\n\n // Render specific chunks from cached FFT data\n if (msg.type === 'render-chunks') {\n const { id } = msg;\n try {\n const { cacheKey, canvasIds, canvasWidths, globalPixelOffsets, canvasHeight,\n devicePixelRatio, samplesPerPixel, colorLUT, frequencyScale, minFrequency,\n maxFrequency, gainDb, rangeDb, channelIndex } = msg;\n\n const cacheEntry = fftCache.get(cacheKey);\n if (!cacheEntry || channelIndex >= cacheEntry.spectrograms.length) {\n const response: ComputeResponse = { id, type: 'error', error: 'cache-miss' };\n (self as unknown as Worker).postMessage(response);\n return;\n }\n\n const scaleFn = getFrequencyScale((frequencyScale ?? 'mel') as FrequencyScaleName);\n const isNonLinear = frequencyScale !== 'linear';\n\n renderSpectrogramToCanvas(\n cacheEntry.spectrograms[channelIndex],\n canvasIds,\n canvasWidths,\n canvasHeight,\n devicePixelRatio,\n samplesPerPixel,\n colorLUT,\n scaleFn,\n minFrequency,\n maxFrequency,\n isNonLinear,\n globalPixelOffsets,\n gainDb,\n rangeDb,\n cacheEntry.sampleOffset,\n );\n\n const response: ComputeResponse = { id, type: 'done' };\n (self as unknown as Worker).postMessage(response);\n } catch (err) {\n const response: ComputeResponse = { id, type: 'error', error: String(err) };\n (self as unknown as Worker).postMessage(response);\n }\n return;\n }\n\n // Compute + render to registered canvases (uses cache internally)\n if (msg.type === 'compute-render') {\n const { id } = msg;\n try {\n const { channelDataArrays, config, sampleRate, offsetSamples, durationSamples, mono, render } = msg;\n\n // Compute spectrograms\n const spectrograms: SpectrogramData[] = [];\n if (mono || channelDataArrays.length === 1) {\n spectrograms.push(\n computeMonoFromChannels(channelDataArrays, config, sampleRate, offsetSamples, durationSamples)\n );\n } else {\n for (const channelData of channelDataArrays) {\n spectrograms.push(\n computeFromChannelData(channelData, config, sampleRate, offsetSamples, durationSamples)\n );\n }\n }\n\n // Render each channel's spectrogram to its canvas chunks\n const scaleFn = getFrequencyScale((render.frequencyScale ?? 'mel') as FrequencyScaleName);\n const isNonLinear = render.frequencyScale !== 'linear';\n\n for (let ch = 0; ch < spectrograms.length; ch++) {\n const channelCanvasIds = render.canvasIds[ch];\n if (!channelCanvasIds || channelCanvasIds.length === 0) continue;\n\n renderSpectrogramToCanvas(\n spectrograms[ch],\n channelCanvasIds,\n render.canvasWidths,\n render.canvasHeight,\n render.devicePixelRatio,\n render.samplesPerPixel,\n render.colorLUT,\n scaleFn,\n render.minFrequency,\n render.maxFrequency,\n isNonLinear,\n );\n }\n\n const response: ComputeResponse = { id, type: 'done' };\n (self as unknown as Worker).postMessage(response);\n } catch (err) {\n const response: ComputeResponse = { id, type: 'error', error: String(err) };\n (self as unknown as Worker).postMessage(response);\n }\n return;\n }\n\n // Legacy compute-only (backward compat — no type field or type === 'compute')\n const { id, channelDataArrays, config, sampleRate, offsetSamples, durationSamples, mono } = msg as ComputeRequest;\n try {\n const spectrograms: SpectrogramData[] = [];\n\n if (mono || channelDataArrays.length === 1) {\n spectrograms.push(\n computeMonoFromChannels(channelDataArrays, config, sampleRate, offsetSamples, durationSamples)\n );\n } else {\n for (const channelData of channelDataArrays) {\n spectrograms.push(\n computeFromChannelData(channelData, config, sampleRate, offsetSamples, durationSamples)\n );\n }\n }\n\n // Transfer the data Float32Arrays back (zero-copy)\n const transferables = spectrograms.map(s => s.data.buffer);\n\n const response: ComputeResponse = { id, type: 'spectrograms', spectrograms };\n (self as unknown as Worker).postMessage(response, transferables);\n } catch (err) {\n const response: ComputeResponse = { id, type: 'error', error: String(err) };\n (self as unknown as Worker).postMessage(response);\n }\n};\n"],"mappings":"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","names":["id","config","offsetSamples","durationSamples","mono","channelDataArrays","sampleRate","response"]}
1
+ {"version":3,"sources":["../../src/computation/fft.ts","../../src/computation/windowFunctions.ts","../../src/computation/frequencyScales.ts","../../src/worker/spectrogram.worker.ts"],"sourcesContent":["import FFT from 'fft.js';\n\n/**\n * Cache fft.js instances per size (pre-computes twiddle factors).\n */\nconst fftInstances = new Map<number, FFT>();\nconst complexBuffers = new Map<number, number[]>();\n\nfunction getFftInstance(size: number): FFT {\n let instance = fftInstances.get(size);\n if (!instance) {\n instance = new FFT(size);\n fftInstances.set(size, instance);\n complexBuffers.set(size, instance.createComplexArray());\n }\n return instance;\n}\n\nfunction getComplexBuffer(size: number): number[] {\n const buffer = complexBuffers.get(size);\n if (!buffer) {\n throw new Error(`No complex buffer for size ${size}. Call getFftInstance first.`);\n }\n return buffer;\n}\n\n/**\n * In-place FFT using fft.js (radix-4).\n * @param real - Real part (modified in place)\n * @param imag - Imaginary part (modified in place)\n */\nexport function fft(real: Float32Array, imag: Float32Array): void {\n const n = real.length;\n const f = getFftInstance(n);\n const input = f.createComplexArray();\n const out = getComplexBuffer(n);\n\n for (let i = 0; i < n; i++) {\n input[i * 2] = real[i];\n input[i * 2 + 1] = imag[i];\n }\n\n f.transform(out, input);\n\n for (let i = 0; i < n; i++) {\n real[i] = out[i * 2];\n imag[i] = out[i * 2 + 1];\n }\n}\n\n/**\n * Fused FFT → magnitude → decibels for real-valued input.\n * Uses fft.js realTransform (radix-4, ~25% faster for real input).\n * Writes dB values for positive frequencies (n/2 bins) into `out`.\n *\n * @param real - Real input (windowed audio frame, length n)\n * @param out - Output array for dB values (length >= n/2)\n */\nexport function fftMagnitudeDb(real: Float32Array, out: Float32Array): void {\n const n = real.length;\n const f = getFftInstance(n);\n const complexOut = getComplexBuffer(n);\n\n f.realTransform(complexOut, real);\n\n const half = n >> 1;\n for (let i = 0; i < half; i++) {\n const re = complexOut[i * 2];\n const im = complexOut[i * 2 + 1];\n let db = 20 * Math.log10(Math.sqrt(re * re + im * im) + 1e-10);\n if (db < -160) db = -160;\n out[i] = db;\n }\n}\n\n/**\n * Compute magnitude spectrum from FFT output.\n * Returns only the first half (positive frequencies).\n */\nexport function magnitudeSpectrum(real: Float32Array, imag: Float32Array): Float32Array {\n const n = real.length >> 1;\n const magnitudes = new Float32Array(n);\n for (let i = 0; i < n; i++) {\n magnitudes[i] = Math.sqrt(real[i] * real[i] + imag[i] * imag[i]);\n }\n return magnitudes;\n}\n\n/**\n * Convert magnitudes to decibels with a fixed -160 dB floor.\n * Gain is applied at render time, not during FFT.\n */\nexport function toDecibels(magnitudes: Float32Array): Float32Array {\n const result = new Float32Array(magnitudes.length);\n for (let i = 0; i < magnitudes.length; i++) {\n let db = 20 * Math.log10(magnitudes[i] + 1e-10);\n if (db < -160) db = -160;\n result[i] = db;\n }\n return result;\n}\n","/**\n * Window functions for spectral analysis.\n */\n\nexport function getWindowFunction(name: string, size: number, alpha?: number): Float32Array {\n const window = new Float32Array(size);\n const N = size;\n\n switch (name) {\n case 'rectangular':\n for (let i = 0; i < size; i++) window[i] = 1;\n break;\n\n case 'bartlett':\n for (let i = 0; i < size; i++) {\n window[i] = 1 - Math.abs((2 * i - N) / N);\n }\n break;\n\n case 'hann':\n for (let i = 0; i < size; i++) {\n window[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / N));\n }\n break;\n\n case 'hamming':\n for (let i = 0; i < size; i++) {\n const a = alpha ?? 0.54;\n window[i] = a - (1 - a) * Math.cos((2 * Math.PI * i) / N);\n }\n break;\n\n case 'blackman': {\n const a0 = 0.42;\n const a1 = 0.5;\n const a2 = 0.08;\n for (let i = 0; i < size; i++) {\n window[i] =\n a0 - a1 * Math.cos((2 * Math.PI * i) / N) + a2 * Math.cos((4 * Math.PI * i) / N);\n }\n break;\n }\n\n case 'blackman-harris': {\n const c0 = 0.35875;\n const c1 = 0.48829;\n const c2 = 0.14128;\n const c3 = 0.01168;\n for (let i = 0; i < size; i++) {\n window[i] =\n c0 -\n c1 * Math.cos((2 * Math.PI * i) / N) +\n c2 * Math.cos((4 * Math.PI * i) / N) -\n c3 * Math.cos((6 * Math.PI * i) / N);\n }\n break;\n }\n\n default:\n console.warn(`[spectrogram] Unknown window function \"${name}\", falling back to hann`);\n for (let i = 0; i < size; i++) {\n window[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / N));\n }\n }\n\n // Amplitude normalization: scale so a 0 dB sine produces a 0 dB spectrum peak.\n // Matches Audacity: scale = 2.0 / sum(window)\n let sum = 0;\n for (let i = 0; i < size; i++) sum += window[i];\n if (sum > 0) {\n const scale = 2.0 / sum;\n for (let i = 0; i < size; i++) window[i] *= scale;\n }\n\n return window;\n}\n","/**\n * Frequency scale mapping functions.\n * Each maps a frequency (Hz) to a normalized position [0, 1].\n */\n\nfunction linearScale(f: number, minF: number, maxF: number): number {\n if (maxF === minF) return 0;\n return (f - minF) / (maxF - minF);\n}\n\nfunction logarithmicScale(f: number, minF: number, maxF: number): number {\n const logMin = Math.log2(Math.max(minF, 1));\n const logMax = Math.log2(maxF);\n if (logMax === logMin) return 0;\n return (Math.log2(Math.max(f, 1)) - logMin) / (logMax - logMin);\n}\n\nfunction hzToMel(f: number): number {\n return 2595 * Math.log10(1 + f / 700);\n}\n\nfunction melScale(f: number, minF: number, maxF: number): number {\n const melMin = hzToMel(minF);\n const melMax = hzToMel(maxF);\n if (melMax === melMin) return 0;\n return (hzToMel(f) - melMin) / (melMax - melMin);\n}\n\nfunction hzToBark(f: number): number {\n return 13 * Math.atan(0.00076 * f) + 3.5 * Math.atan((f / 7500) ** 2);\n}\n\nfunction barkScale(f: number, minF: number, maxF: number): number {\n const barkMin = hzToBark(minF);\n const barkMax = hzToBark(maxF);\n if (barkMax === barkMin) return 0;\n return (hzToBark(f) - barkMin) / (barkMax - barkMin);\n}\n\nfunction hzToErb(f: number): number {\n return 21.4 * Math.log10(1 + 0.00437 * f);\n}\n\nfunction erbScale(f: number, minF: number, maxF: number): number {\n const erbMin = hzToErb(minF);\n const erbMax = hzToErb(maxF);\n if (erbMax === erbMin) return 0;\n return (hzToErb(f) - erbMin) / (erbMax - erbMin);\n}\n\nexport type FrequencyScaleName = 'linear' | 'logarithmic' | 'mel' | 'bark' | 'erb';\n\n/**\n * Returns a mapping function: (frequencyHz, minFrequency, maxFrequency) → [0, 1]\n */\nexport function getFrequencyScale(\n name: FrequencyScaleName\n): (f: number, minF: number, maxF: number) => number {\n switch (name) {\n case 'logarithmic':\n return logarithmicScale;\n case 'mel':\n return melScale;\n case 'bark':\n return barkScale;\n case 'erb':\n return erbScale;\n case 'linear':\n return linearScale;\n default:\n console.warn(`[spectrogram] Unknown frequency scale \"${name}\", falling back to linear`);\n return linearScale;\n }\n}\n","/**\n * Web Worker for off-main-thread spectrogram computation and rendering.\n *\n * Supports five modes:\n * 1. `compute` — FFT only, returns SpectrogramData to main thread (backward compat)\n * 2. `register-canvas` / `unregister-canvas` — manage OffscreenCanvas ownership\n * 3. `compute-render` — FFT + direct pixel rendering to registered OffscreenCanvases\n * 4. `compute-fft` — FFT with caching, returns cache key (no rendering)\n * 5. `render-chunks` — render specific chunks from cached FFT data\n */\n\nimport type {\n SpectrogramConfig,\n SpectrogramComputeConfig,\n SpectrogramData,\n} from '@waveform-playlist/core';\nimport { fftMagnitudeDb } from '../computation/fft';\nimport { getWindowFunction } from '../computation/windowFunctions';\nimport { getFrequencyScale, type FrequencyScaleName } from '../computation/frequencyScales';\n\n// --- Canvas registry ---\nconst canvasRegistry = new Map<string, OffscreenCanvas>();\n\n// --- Audio data registry ---\n// Pre-transferred audio data keyed by clipId, avoiding re-transfer on compute-fft.\nconst audioDataRegistry = new Map<\n string,\n { channelDataArrays: Float32Array[]; sampleRate: number }\n>();\n\n// --- FFT cache ---\n// Caches raw dB spectrogram data keyed by FFT computation params.\n// Display-only params (gain, range, colormap) don't affect the cache key.\n// sampleOffset: the sample position where this FFT data starts (for range-limited FFT)\ninterface FFTCacheEntry {\n spectrograms: SpectrogramData[];\n sampleOffset: number;\n}\nconst fftCache = new Map<string, FFTCacheEntry>();\n\nfunction generateCacheKey(params: {\n clipId: string;\n channelIndex: number;\n offsetSamples: number;\n durationSamples: number;\n sampleRate: number;\n compute: SpectrogramComputeConfig;\n mono: boolean;\n}): string {\n const { compute: c } = params;\n return `${params.clipId}:${params.channelIndex}:${params.offsetSamples}:${params.durationSamples}:${params.sampleRate}:${c.fftSize ?? ''}:${c.zeroPaddingFactor ?? ''}:${c.hopSize ?? ''}:${c.windowFunction ?? ''}:${c.alpha ?? ''}:${params.mono ? 1 : 0}`;\n}\n\n// --- Message types ---\n\ninterface ComputeRequest {\n type?: 'compute';\n id: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n mono: boolean;\n}\n\ninterface RegisterCanvasMessage {\n type: 'register-canvas';\n canvasId: string;\n canvas: OffscreenCanvas;\n}\n\ninterface UnregisterCanvasMessage {\n type: 'unregister-canvas';\n canvasId: string;\n}\n\ninterface ComputeRenderRequest {\n type: 'compute-render';\n id: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n mono: boolean;\n render: {\n canvasIds: string[][]; // [channel][chunk] → canvasId\n canvasWidths: number[]; // per-chunk CSS widths\n canvasHeight: number;\n devicePixelRatio: number;\n samplesPerPixel: number;\n colorLUT: Uint8Array;\n frequencyScale: string;\n minFrequency: number;\n maxFrequency: number;\n };\n}\n\ninterface RegisterAudioDataMessage {\n type: 'register-audio-data';\n clipId: string;\n channelDataArrays: Float32Array[];\n sampleRate: number;\n}\n\ninterface UnregisterAudioDataMessage {\n type: 'unregister-audio-data';\n clipId: string;\n}\n\ninterface ComputeFFTRequest {\n type: 'compute-fft';\n id: string;\n clipId: string;\n channelDataArrays: Float32Array[];\n config: SpectrogramConfig;\n sampleRate: number;\n offsetSamples: number;\n durationSamples: number;\n mono: boolean;\n sampleRange?: { start: number; end: number };\n}\n\ninterface RenderChunksRequest {\n type: 'render-chunks';\n id: string;\n cacheKey: string;\n canvasIds: string[]; // flat list of canvas IDs to render\n canvasWidths: number[]; // per-chunk CSS widths\n globalPixelOffsets: number[]; // pixel offset for each chunk\n canvasHeight: number;\n devicePixelRatio: number;\n samplesPerPixel: number;\n colorLUT: Uint8Array;\n frequencyScale: string;\n minFrequency: number;\n maxFrequency: number;\n gainDb: number;\n rangeDb: number;\n channelIndex: number;\n}\n\ntype WorkerMessage =\n | ComputeRequest\n | RegisterCanvasMessage\n | UnregisterCanvasMessage\n | ComputeRenderRequest\n | ComputeFFTRequest\n | RenderChunksRequest\n | RegisterAudioDataMessage\n | UnregisterAudioDataMessage;\n\ntype ComputeResponse =\n | { id: string; type: 'spectrograms'; spectrograms: SpectrogramData[] }\n | { id: string; type: 'cache-key'; cacheKey: string }\n | { id: string; type: 'done' }\n | { id: string; type: 'error'; error: string };\n\n// --- FFT computation (unchanged) ---\n\nfunction computeFromChannelData(\n channelData: Float32Array,\n config: SpectrogramConfig,\n sampleRate: number,\n offsetSamples: number,\n durationSamples: number\n): SpectrogramData {\n const windowSize = config.fftSize ?? 2048;\n const zeroPaddingFactor = config.zeroPaddingFactor ?? 2;\n const actualFftSize = windowSize * zeroPaddingFactor;\n const hopSize = config.hopSize ?? Math.floor(windowSize / 4);\n const windowName = config.windowFunction ?? 'hann';\n const gainDb = config.gainDb ?? 20;\n const rangeDb = config.rangeDb ?? 80;\n const alpha = config.alpha;\n\n const frequencyBinCount = actualFftSize >> 1;\n const totalSamples = durationSamples;\n\n const window = getWindowFunction(windowName, windowSize, alpha);\n const frameCount = Math.max(1, Math.floor((totalSamples - windowSize) / hopSize) + 1);\n const data = new Float32Array(frameCount * frequencyBinCount);\n const real = new Float32Array(actualFftSize);\n const dbBuf = new Float32Array(frequencyBinCount);\n\n for (let frame = 0; frame < frameCount; frame++) {\n const start = offsetSamples + frame * hopSize;\n\n for (let i = 0; i < windowSize; i++) {\n const sampleIdx = start + i;\n real[i] = sampleIdx < channelData.length ? channelData[sampleIdx] * window[i] : 0;\n }\n for (let i = windowSize; i < actualFftSize; i++) {\n real[i] = 0;\n }\n\n fftMagnitudeDb(real, dbBuf);\n data.set(dbBuf, frame * frequencyBinCount);\n }\n\n return {\n fftSize: actualFftSize,\n windowSize,\n frequencyBinCount,\n sampleRate,\n hopSize,\n frameCount,\n data,\n gainDb,\n rangeDb,\n };\n}\n\nfunction computeMonoFromChannels(\n channels: Float32Array[],\n config: SpectrogramConfig,\n sampleRate: number,\n offsetSamples: number,\n durationSamples: number\n): SpectrogramData {\n if (channels.length === 1) {\n return computeFromChannelData(channels[0], config, sampleRate, offsetSamples, durationSamples);\n }\n\n const windowSize = config.fftSize ?? 2048;\n const zeroPaddingFactor = config.zeroPaddingFactor ?? 2;\n const actualFftSize = windowSize * zeroPaddingFactor;\n const hopSize = config.hopSize ?? Math.floor(windowSize / 4);\n const windowName = config.windowFunction ?? 'hann';\n const gainDb = config.gainDb ?? 20;\n const rangeDb = config.rangeDb ?? 80;\n const alpha = config.alpha;\n\n const frequencyBinCount = actualFftSize >> 1;\n const numChannels = channels.length;\n\n const window = getWindowFunction(windowName, windowSize, alpha);\n const frameCount = Math.max(1, Math.floor((durationSamples - windowSize) / hopSize) + 1);\n const data = new Float32Array(frameCount * frequencyBinCount);\n const real = new Float32Array(actualFftSize);\n const dbBuf = new Float32Array(frequencyBinCount);\n\n for (let frame = 0; frame < frameCount; frame++) {\n const start = offsetSamples + frame * hopSize;\n\n for (let i = 0; i < windowSize; i++) {\n const sampleIdx = start + i;\n let sum = 0;\n for (let ch = 0; ch < numChannels; ch++) {\n sum += sampleIdx < channels[ch].length ? channels[ch][sampleIdx] : 0;\n }\n real[i] = (sum / numChannels) * window[i];\n }\n for (let i = windowSize; i < actualFftSize; i++) {\n real[i] = 0;\n }\n\n fftMagnitudeDb(real, dbBuf);\n data.set(dbBuf, frame * frequencyBinCount);\n }\n\n return {\n fftSize: actualFftSize,\n windowSize,\n frequencyBinCount,\n sampleRate,\n hopSize,\n frameCount,\n data,\n gainDb,\n rangeDb,\n };\n}\n\n// --- Rendering ---\n\nfunction renderSpectrogramToCanvas(\n specData: SpectrogramData,\n canvasIds: string[],\n canvasWidths: number[],\n canvasHeight: number,\n devicePixelRatio: number,\n samplesPerPixel: number,\n colorLUT: Uint8Array,\n scaleFn: (f: number, minF: number, maxF: number) => number,\n minFrequency: number,\n maxFrequency: number,\n isNonLinear: boolean,\n globalPixelOffsets?: number[],\n gainDbOverride?: number,\n rangeDbOverride?: number,\n sampleOffset = 0\n): void {\n const { frequencyBinCount, frameCount, hopSize, sampleRate } = specData;\n const gainDb = gainDbOverride ?? specData.gainDb;\n const rawRangeDb = rangeDbOverride ?? specData.rangeDb;\n const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;\n const maxF = maxFrequency > 0 ? maxFrequency : sampleRate / 2;\n const binToFreq = (bin: number) => (bin / frequencyBinCount) * (sampleRate / 2);\n\n let accumulatedOffset = 0;\n\n for (let chunkIdx = 0; chunkIdx < canvasIds.length; chunkIdx++) {\n const canvasId = canvasIds[chunkIdx];\n const offscreen = canvasRegistry.get(canvasId);\n if (!offscreen) {\n console.warn(`[spectrogram-worker] Canvas \"${canvasId}\" not found in registry`);\n if (!globalPixelOffsets) accumulatedOffset += canvasWidths[chunkIdx];\n continue;\n }\n\n const canvasWidth = canvasWidths[chunkIdx];\n const globalPixelOffset = globalPixelOffsets ? globalPixelOffsets[chunkIdx] : accumulatedOffset;\n\n // Set physical canvas size for DPR\n offscreen.width = canvasWidth * devicePixelRatio;\n offscreen.height = canvasHeight * devicePixelRatio;\n\n const ctx = offscreen.getContext('2d');\n if (!ctx) {\n console.warn(`[spectrogram-worker] getContext('2d') returned null for canvas \"${canvasId}\"`);\n if (!globalPixelOffsets) accumulatedOffset += canvasWidth;\n continue;\n }\n\n ctx.resetTransform();\n ctx.clearRect(0, 0, offscreen.width, offscreen.height);\n ctx.imageSmoothingEnabled = false;\n\n // Create ImageData at CSS pixel size\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 const samplePos = globalX * samplesPerPixel - sampleOffset;\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 const normalizedY = 1 - y / canvasHeight;\n\n let bin = Math.floor(normalizedY * frequencyBinCount);\n\n if (isNonLinear) {\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 const db = specData.data[frameOffset + bin];\n const normalized = Math.max(0, Math.min(1, (db + rangeDb + gainDb) / rangeDb));\n\n const colorIdx = Math.floor(normalized * 255);\n const pixelIdx = (y * canvasWidth + x) * 4;\n pixels[pixelIdx] = colorLUT[colorIdx * 3];\n pixels[pixelIdx + 1] = colorLUT[colorIdx * 3 + 1];\n pixels[pixelIdx + 2] = colorLUT[colorIdx * 3 + 2];\n pixels[pixelIdx + 3] = 255;\n }\n }\n\n // Put image data and scale up for DPR\n if (devicePixelRatio === 1) {\n ctx.putImageData(imgData, 0, 0);\n } else {\n // Render at CSS size to a temporary OffscreenCanvas, then scale up\n const tmpCanvas = new OffscreenCanvas(canvasWidth, canvasHeight);\n const tmpCtx = tmpCanvas.getContext('2d');\n if (!tmpCtx) continue;\n tmpCtx.putImageData(imgData, 0, 0);\n\n ctx.imageSmoothingEnabled = false;\n ctx.drawImage(tmpCanvas, 0, 0, offscreen.width, offscreen.height);\n }\n\n if (!globalPixelOffsets) accumulatedOffset += canvasWidth;\n }\n}\n\n// --- Message handler ---\n\nself.onmessage = (e: MessageEvent<WorkerMessage>) => {\n const msg = e.data;\n\n // Register canvas\n if (msg.type === 'register-canvas') {\n try {\n canvasRegistry.set(msg.canvasId, msg.canvas);\n } catch (err) {\n console.warn('[spectrogram-worker] register-canvas failed:', err);\n }\n return;\n }\n\n // Unregister canvas\n if (msg.type === 'unregister-canvas') {\n try {\n canvasRegistry.delete(msg.canvasId);\n } catch (err) {\n console.warn('[spectrogram-worker] unregister-canvas failed:', err);\n }\n return;\n }\n\n // Register audio data for a clip (pre-transfer)\n if (msg.type === 'register-audio-data') {\n try {\n audioDataRegistry.set(msg.clipId, {\n channelDataArrays: msg.channelDataArrays,\n sampleRate: msg.sampleRate,\n });\n } catch (err) {\n console.warn('[spectrogram-worker] register-audio-data failed:', err);\n }\n return;\n }\n\n // Unregister audio data for a clip + evict related FFT cache entries\n if (msg.type === 'unregister-audio-data') {\n try {\n audioDataRegistry.delete(msg.clipId);\n const prefix = `${msg.clipId}:`;\n for (const key of fftCache.keys()) {\n if (key.startsWith(prefix)) {\n fftCache.delete(key);\n }\n }\n } catch (err) {\n console.warn('[spectrogram-worker] unregister-audio-data failed:', err);\n }\n return;\n }\n\n // Compute FFT only (with caching), return cache key\n if (msg.type === 'compute-fft') {\n const { id } = msg;\n try {\n const {\n clipId,\n config,\n sampleRate: msgSampleRate,\n offsetSamples,\n durationSamples,\n mono,\n sampleRange,\n } = msg;\n\n // Use pre-registered audio data if available, otherwise use message payload\n const registered = audioDataRegistry.get(clipId);\n const channelDataArrays =\n registered && msg.channelDataArrays.length === 0\n ? registered.channelDataArrays\n : msg.channelDataArrays;\n const sampleRate =\n registered && msg.channelDataArrays.length === 0 ? registered.sampleRate : msgSampleRate;\n\n const fftSize = config.fftSize ?? 2048;\n const zeroPaddingFactor = config.zeroPaddingFactor ?? 2;\n const hopSize = config.hopSize ?? Math.floor(fftSize / 4);\n const windowFunction = config.windowFunction ?? 'hann';\n\n // Use sampleRange if provided (visible-range-first optimization)\n const effectiveOffset = sampleRange ? sampleRange.start : offsetSamples;\n const effectiveDuration = sampleRange ? sampleRange.end - sampleRange.start : durationSamples;\n\n const cacheKey = generateCacheKey({\n clipId,\n channelIndex: 0,\n offsetSamples: effectiveOffset,\n durationSamples: effectiveDuration,\n sampleRate,\n compute: { fftSize, zeroPaddingFactor, hopSize, windowFunction, alpha: config.alpha },\n mono,\n });\n\n if (!fftCache.has(cacheKey)) {\n // Evict stale cache entries for this clip (different FFT params)\n const clipPrefix = `${clipId}:`;\n for (const key of fftCache.keys()) {\n if (key.startsWith(clipPrefix) && key !== cacheKey) {\n fftCache.delete(key);\n }\n }\n\n const spectrograms: SpectrogramData[] = [];\n if (mono || channelDataArrays.length === 1) {\n spectrograms.push(\n computeMonoFromChannels(\n channelDataArrays,\n config,\n sampleRate,\n effectiveOffset,\n effectiveDuration\n )\n );\n } else {\n for (const channelData of channelDataArrays) {\n spectrograms.push(\n computeFromChannelData(\n channelData,\n config,\n sampleRate,\n effectiveOffset,\n effectiveDuration\n )\n );\n }\n }\n fftCache.set(cacheKey, { spectrograms, sampleOffset: effectiveOffset });\n }\n\n const response: ComputeResponse = { id, type: 'cache-key', cacheKey };\n (self as unknown as Worker).postMessage(response);\n } catch (err) {\n const response: ComputeResponse = { id, type: 'error', error: String(err) };\n (self as unknown as Worker).postMessage(response);\n }\n return;\n }\n\n // Render specific chunks from cached FFT data\n if (msg.type === 'render-chunks') {\n const { id } = msg;\n try {\n const {\n cacheKey,\n canvasIds,\n canvasWidths,\n globalPixelOffsets,\n canvasHeight,\n devicePixelRatio,\n samplesPerPixel,\n colorLUT,\n frequencyScale,\n minFrequency,\n maxFrequency,\n gainDb,\n rangeDb,\n channelIndex,\n } = msg;\n\n const cacheEntry = fftCache.get(cacheKey);\n if (!cacheEntry || channelIndex >= cacheEntry.spectrograms.length) {\n const response: ComputeResponse = { id, type: 'error', error: 'cache-miss' };\n (self as unknown as Worker).postMessage(response);\n return;\n }\n\n const scaleFn = getFrequencyScale((frequencyScale ?? 'mel') as FrequencyScaleName);\n const isNonLinear = frequencyScale !== 'linear';\n\n renderSpectrogramToCanvas(\n cacheEntry.spectrograms[channelIndex],\n canvasIds,\n canvasWidths,\n canvasHeight,\n devicePixelRatio,\n samplesPerPixel,\n colorLUT,\n scaleFn,\n minFrequency,\n maxFrequency,\n isNonLinear,\n globalPixelOffsets,\n gainDb,\n rangeDb,\n cacheEntry.sampleOffset\n );\n\n const response: ComputeResponse = { id, type: 'done' };\n (self as unknown as Worker).postMessage(response);\n } catch (err) {\n const response: ComputeResponse = { id, type: 'error', error: String(err) };\n (self as unknown as Worker).postMessage(response);\n }\n return;\n }\n\n // Compute + render to registered canvases (uses cache internally)\n if (msg.type === 'compute-render') {\n const { id } = msg;\n try {\n const {\n channelDataArrays,\n config,\n sampleRate,\n offsetSamples,\n durationSamples,\n mono,\n render,\n } = msg;\n\n // Compute spectrograms\n const spectrograms: SpectrogramData[] = [];\n if (mono || channelDataArrays.length === 1) {\n spectrograms.push(\n computeMonoFromChannels(\n channelDataArrays,\n config,\n sampleRate,\n offsetSamples,\n durationSamples\n )\n );\n } else {\n for (const channelData of channelDataArrays) {\n spectrograms.push(\n computeFromChannelData(channelData, config, sampleRate, offsetSamples, durationSamples)\n );\n }\n }\n\n // Render each channel's spectrogram to its canvas chunks\n const scaleFn = getFrequencyScale((render.frequencyScale ?? 'mel') as FrequencyScaleName);\n const isNonLinear = render.frequencyScale !== 'linear';\n\n for (let ch = 0; ch < spectrograms.length; ch++) {\n const channelCanvasIds = render.canvasIds[ch];\n if (!channelCanvasIds || channelCanvasIds.length === 0) continue;\n\n renderSpectrogramToCanvas(\n spectrograms[ch],\n channelCanvasIds,\n render.canvasWidths,\n render.canvasHeight,\n render.devicePixelRatio,\n render.samplesPerPixel,\n render.colorLUT,\n scaleFn,\n render.minFrequency,\n render.maxFrequency,\n isNonLinear\n );\n }\n\n const response: ComputeResponse = { id, type: 'done' };\n (self as unknown as Worker).postMessage(response);\n } catch (err) {\n const response: ComputeResponse = { id, type: 'error', error: String(err) };\n (self as unknown as Worker).postMessage(response);\n }\n return;\n }\n\n // Legacy compute-only (backward compat — no type field or type === 'compute')\n const { id, channelDataArrays, config, sampleRate, offsetSamples, durationSamples, mono } =\n msg as ComputeRequest;\n try {\n const spectrograms: SpectrogramData[] = [];\n\n if (mono || channelDataArrays.length === 1) {\n spectrograms.push(\n computeMonoFromChannels(\n channelDataArrays,\n config,\n sampleRate,\n offsetSamples,\n durationSamples\n )\n );\n } else {\n for (const channelData of channelDataArrays) {\n spectrograms.push(\n computeFromChannelData(channelData, config, sampleRate, offsetSamples, durationSamples)\n );\n }\n }\n\n // Transfer the data Float32Arrays back (zero-copy)\n const transferables = spectrograms.map((s) => s.data.buffer);\n\n const response: ComputeResponse = { id, type: 'spectrograms', spectrograms };\n (self as unknown as Worker).postMessage(response, transferables);\n } catch (err) {\n const response: ComputeResponse = { id, type: 'error', error: String(err) };\n (self as unknown as Worker).postMessage(response);\n }\n};\n"],"mappings":"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","names":["id","config","offsetSamples","durationSamples","mono","channelDataArrays","sampleRate","response"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waveform-playlist/spectrogram",
3
- "version": "7.1.2",
3
+ "version": "8.0.0",
4
4
  "description": "Spectrogram computation and UI for waveform-playlist",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -43,16 +43,16 @@
43
43
  "@types/styled-components": "^5.1.26",
44
44
  "tsup": "^8.0.1",
45
45
  "typescript": "^5.3.3",
46
- "@waveform-playlist/browser": "7.1.2"
46
+ "@waveform-playlist/browser": "8.0.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "fft.js": "^4.0.4",
50
- "@waveform-playlist/core": "7.1.2"
50
+ "@waveform-playlist/core": "8.0.0"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": "^18.0.0",
54
54
  "styled-components": "^6.0.0",
55
- "@waveform-playlist/browser": "7.1.2"
55
+ "@waveform-playlist/browser": "8.0.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsup",