@waveform-playlist/spectrogram 13.0.0 → 13.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +145 -107
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +147 -107
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -11
package/dist/index.mjs
CHANGED
|
@@ -389,7 +389,7 @@ var SpectrogramSettingsModal = ({
|
|
|
389
389
|
// src/SpectrogramProvider.tsx
|
|
390
390
|
import { useState as useState2, useEffect as useEffect2, useRef as useRef2, useCallback, useMemo } from "react";
|
|
391
391
|
import {
|
|
392
|
-
MAX_CANVAS_WIDTH
|
|
392
|
+
MAX_CANVAS_WIDTH as MAX_CANVAS_WIDTH2
|
|
393
393
|
} from "@waveform-playlist/core";
|
|
394
394
|
import {
|
|
395
395
|
getColorMap,
|
|
@@ -397,11 +397,11 @@ import {
|
|
|
397
397
|
createSpectrogramWorkerPool,
|
|
398
398
|
SpectrogramAbortError
|
|
399
399
|
} from "@dawcore/spectrogram";
|
|
400
|
+
|
|
401
|
+
// src/spectrogram-helpers.ts
|
|
400
402
|
import {
|
|
401
|
-
|
|
402
|
-
} from "@waveform-playlist/
|
|
403
|
-
import { usePlaylistData, usePlaylistControls } from "@waveform-playlist/browser";
|
|
404
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
403
|
+
MAX_CANVAS_WIDTH
|
|
404
|
+
} from "@waveform-playlist/core";
|
|
405
405
|
function extractChunkNumber(canvasId) {
|
|
406
406
|
const match = canvasId.match(/chunk(\d+)$/);
|
|
407
407
|
if (!match) {
|
|
@@ -410,6 +410,116 @@ function extractChunkNumber(canvasId) {
|
|
|
410
410
|
}
|
|
411
411
|
return parseInt(match[1], 10);
|
|
412
412
|
}
|
|
413
|
+
function parseCanvasId(canvasId) {
|
|
414
|
+
const match = canvasId.match(/^(.+)-ch(\d+)-chunk\d+$/);
|
|
415
|
+
if (!match) return null;
|
|
416
|
+
return { clipId: match[1], channelIndex: parseInt(match[2], 10) };
|
|
417
|
+
}
|
|
418
|
+
function groupContiguousIndices(channelInfo, indices) {
|
|
419
|
+
if (indices.length === 0) return [];
|
|
420
|
+
const groups = [];
|
|
421
|
+
let currentGroup = [indices[0]];
|
|
422
|
+
let prevChunk = extractChunkNumber(channelInfo.canvasIds[indices[0]]);
|
|
423
|
+
for (let i = 1; i < indices.length; i++) {
|
|
424
|
+
const chunk = extractChunkNumber(channelInfo.canvasIds[indices[i]]);
|
|
425
|
+
if (chunk === prevChunk + 1) {
|
|
426
|
+
currentGroup.push(indices[i]);
|
|
427
|
+
} else {
|
|
428
|
+
groups.push(currentGroup);
|
|
429
|
+
currentGroup = [indices[i]];
|
|
430
|
+
}
|
|
431
|
+
prevChunk = chunk;
|
|
432
|
+
}
|
|
433
|
+
groups.push(currentGroup);
|
|
434
|
+
return groups;
|
|
435
|
+
}
|
|
436
|
+
function classifyChunkTiers(channelInfo, clipPixelOffset = 0, viewport) {
|
|
437
|
+
if (!viewport) {
|
|
438
|
+
return {
|
|
439
|
+
viewportIndices: channelInfo.canvasWidths.map((_, i) => i),
|
|
440
|
+
bufferIndices: [],
|
|
441
|
+
remainingIndices: []
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
const { scrollLeft, viewportWidth } = viewport;
|
|
445
|
+
const buffer = viewportWidth * 1.5;
|
|
446
|
+
const bufferStart = Math.max(0, scrollLeft - buffer);
|
|
447
|
+
const bufferEnd = scrollLeft + viewportWidth + buffer;
|
|
448
|
+
const viewportIndices = [];
|
|
449
|
+
const bufferIndices = [];
|
|
450
|
+
const remainingIndices = [];
|
|
451
|
+
for (let i = 0; i < channelInfo.canvasWidths.length; i++) {
|
|
452
|
+
const chunkNumber = extractChunkNumber(channelInfo.canvasIds[i]);
|
|
453
|
+
const chunkLeft = chunkNumber * MAX_CANVAS_WIDTH + clipPixelOffset;
|
|
454
|
+
const chunkRight = chunkLeft + channelInfo.canvasWidths[i];
|
|
455
|
+
if (chunkRight > scrollLeft && chunkLeft < scrollLeft + viewportWidth) {
|
|
456
|
+
viewportIndices.push(i);
|
|
457
|
+
} else if (chunkRight > bufferStart && chunkLeft < bufferEnd) {
|
|
458
|
+
bufferIndices.push(i);
|
|
459
|
+
} else {
|
|
460
|
+
remainingIndices.push(i);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return { viewportIndices, bufferIndices, remainingIndices };
|
|
464
|
+
}
|
|
465
|
+
function computeChunkSampleRange({
|
|
466
|
+
channelInfo,
|
|
467
|
+
indices,
|
|
468
|
+
fftSize,
|
|
469
|
+
offsetSamples,
|
|
470
|
+
durationSamples,
|
|
471
|
+
samplesPerPixel
|
|
472
|
+
}) {
|
|
473
|
+
const chunkNumbers = indices.map((i) => extractChunkNumber(channelInfo.canvasIds[i]));
|
|
474
|
+
const minChunk = Math.min(...chunkNumbers);
|
|
475
|
+
const maxChunk = Math.max(...chunkNumbers);
|
|
476
|
+
const maxChunkIdx = indices[chunkNumbers.indexOf(maxChunk)];
|
|
477
|
+
const lastChunkWidth = channelInfo.canvasWidths[maxChunkIdx];
|
|
478
|
+
const startPx = minChunk * MAX_CANVAS_WIDTH;
|
|
479
|
+
const endPx = maxChunk * MAX_CANVAS_WIDTH + lastChunkWidth;
|
|
480
|
+
const rangeStartSample = offsetSamples + Math.floor(startPx * samplesPerPixel);
|
|
481
|
+
const rangeEndSample = Math.min(
|
|
482
|
+
offsetSamples + durationSamples,
|
|
483
|
+
offsetSamples + Math.ceil(endPx * samplesPerPixel)
|
|
484
|
+
);
|
|
485
|
+
const paddedStart = Math.max(offsetSamples, rangeStartSample - fftSize);
|
|
486
|
+
const paddedEnd = Math.min(offsetSamples + durationSamples, rangeEndSample + fftSize);
|
|
487
|
+
return { paddedStart, paddedEnd };
|
|
488
|
+
}
|
|
489
|
+
function resolveRenderMode(override, trackRenderMode) {
|
|
490
|
+
return override?.renderMode ?? trackRenderMode ?? "waveform";
|
|
491
|
+
}
|
|
492
|
+
function toComputeConfig(cfg) {
|
|
493
|
+
return {
|
|
494
|
+
fftSize: cfg?.fftSize,
|
|
495
|
+
hopSize: cfg?.hopSize,
|
|
496
|
+
windowFunction: cfg?.windowFunction,
|
|
497
|
+
alpha: cfg?.alpha,
|
|
498
|
+
zeroPaddingFactor: cfg?.zeroPaddingFactor
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function buildConfigKey(params) {
|
|
502
|
+
const { mode, cfg, cm, mono } = params;
|
|
503
|
+
return JSON.stringify({ mode, cfg, cm, mono });
|
|
504
|
+
}
|
|
505
|
+
function buildFFTKey(params) {
|
|
506
|
+
const { mode, mono, computeConfig } = params;
|
|
507
|
+
return JSON.stringify({ mode, mono, ...computeConfig });
|
|
508
|
+
}
|
|
509
|
+
function mapsDiffer(prev, current) {
|
|
510
|
+
if (current.size !== prev.size) return true;
|
|
511
|
+
for (const [key, value] of current) {
|
|
512
|
+
if (prev.get(key) !== value) return true;
|
|
513
|
+
}
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/SpectrogramProvider.tsx
|
|
518
|
+
import {
|
|
519
|
+
SpectrogramIntegrationProvider
|
|
520
|
+
} from "@waveform-playlist/browser";
|
|
521
|
+
import { usePlaylistData, usePlaylistControls } from "@waveform-playlist/browser";
|
|
522
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
413
523
|
var SpectrogramProvider = ({
|
|
414
524
|
config: spectrogramConfig,
|
|
415
525
|
colorMap: spectrogramColorMap,
|
|
@@ -481,40 +591,21 @@ var SpectrogramProvider = ({
|
|
|
481
591
|
const currentKeys = /* @__PURE__ */ new Map();
|
|
482
592
|
const currentFFTKeys = /* @__PURE__ */ new Map();
|
|
483
593
|
tracks.forEach((track) => {
|
|
484
|
-
const
|
|
594
|
+
const override = trackSpectrogramOverrides.get(track.id);
|
|
595
|
+
const mode = resolveRenderMode(override, track.renderMode);
|
|
485
596
|
if (mode === "waveform") return;
|
|
486
|
-
const cfg =
|
|
487
|
-
const cm =
|
|
488
|
-
currentKeys.set(track.id,
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
alpha: cfg?.alpha,
|
|
494
|
-
zeroPaddingFactor: cfg?.zeroPaddingFactor
|
|
495
|
-
};
|
|
496
|
-
currentFFTKeys.set(track.id, JSON.stringify({ mode, mono, ...computeConfig }));
|
|
597
|
+
const cfg = override?.config ?? track.spectrogramConfig ?? spectrogramConfig;
|
|
598
|
+
const cm = override?.colorMap ?? track.spectrogramColorMap ?? spectrogramColorMap;
|
|
599
|
+
currentKeys.set(track.id, buildConfigKey({ mode, cfg, cm, mono }));
|
|
600
|
+
currentFFTKeys.set(
|
|
601
|
+
track.id,
|
|
602
|
+
buildFFTKey({ mode, mono, computeConfig: toComputeConfig(cfg) })
|
|
603
|
+
);
|
|
497
604
|
});
|
|
498
605
|
const prevKeys = prevSpectrogramConfigRef.current;
|
|
499
606
|
const prevFFTKeys = prevSpectrogramFFTKeyRef.current;
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
for (const [idx, key] of currentKeys) {
|
|
503
|
-
if (prevKeys.get(idx) !== key) {
|
|
504
|
-
configChanged = true;
|
|
505
|
-
break;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
let fftKeyChanged = currentFFTKeys.size !== prevFFTKeys.size;
|
|
510
|
-
if (!fftKeyChanged) {
|
|
511
|
-
for (const [idx, key] of currentFFTKeys) {
|
|
512
|
-
if (prevFFTKeys.get(idx) !== key) {
|
|
513
|
-
fftKeyChanged = true;
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
607
|
+
const configChanged = mapsDiffer(prevKeys, currentKeys);
|
|
608
|
+
const fftKeyChanged = mapsDiffer(prevFFTKeys, currentFFTKeys);
|
|
518
609
|
const canvasVersionChanged = spectrogramCanvasVersion !== prevCanvasVersionRef.current;
|
|
519
610
|
prevCanvasVersionRef.current = spectrogramCanvasVersion;
|
|
520
611
|
if (!configChanged && !canvasVersionChanged) return;
|
|
@@ -549,14 +640,15 @@ var SpectrogramProvider = ({
|
|
|
549
640
|
const clipsNeedingFFT = [];
|
|
550
641
|
const clipsNeedingDisplayOnly = [];
|
|
551
642
|
tracks.forEach((track, i) => {
|
|
552
|
-
const
|
|
643
|
+
const override = trackSpectrogramOverrides.get(track.id);
|
|
644
|
+
const mode = resolveRenderMode(override, track.renderMode);
|
|
553
645
|
if (mode === "waveform") return;
|
|
554
646
|
const trackConfigChanged = configChanged && currentKeys.get(track.id) !== prevKeys.get(track.id);
|
|
555
647
|
const trackFFTChanged = fftKeyChanged && currentFFTKeys.get(track.id) !== prevFFTKeys.get(track.id);
|
|
556
648
|
const hasRegisteredCanvases = canvasVersionChanged && track.clips.some((clip) => spectrogramCanvasRegistryRef.current.has(clip.id));
|
|
557
649
|
if (!trackConfigChanged && !hasRegisteredCanvases) return;
|
|
558
|
-
const cfg =
|
|
559
|
-
const cm =
|
|
650
|
+
const cfg = override?.config ?? track.spectrogramConfig ?? spectrogramConfig ?? {};
|
|
651
|
+
const cm = override?.colorMap ?? track.spectrogramColorMap ?? spectrogramColorMap ?? "viridis";
|
|
560
652
|
for (const clip of track.clips) {
|
|
561
653
|
if (!clip.audioBuffer) continue;
|
|
562
654
|
const monoFlag = mono || clip.audioBuffer.numberOfChannels === 1;
|
|
@@ -601,34 +693,11 @@ var SpectrogramProvider = ({
|
|
|
601
693
|
if (clipsNeedingFFT.length === 0 && clipsNeedingDisplayOnly.length === 0) return;
|
|
602
694
|
const getVisibleChunkRange = (channelInfo, clipPixelOffset = 0) => {
|
|
603
695
|
const container = scrollContainerRef.current;
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
const scrollLeft = container.scrollLeft;
|
|
612
|
-
const viewportWidth = container.clientWidth;
|
|
613
|
-
const buffer = viewportWidth * 1.5;
|
|
614
|
-
const bufferStart = Math.max(0, scrollLeft - buffer);
|
|
615
|
-
const bufferEnd = scrollLeft + viewportWidth + buffer;
|
|
616
|
-
const viewportIndices = [];
|
|
617
|
-
const bufferIndices = [];
|
|
618
|
-
const remainingIndices = [];
|
|
619
|
-
for (let i = 0; i < channelInfo.canvasWidths.length; i++) {
|
|
620
|
-
const chunkNumber = extractChunkNumber(channelInfo.canvasIds[i]);
|
|
621
|
-
const chunkLeft = chunkNumber * MAX_CANVAS_WIDTH + clipPixelOffset;
|
|
622
|
-
const chunkRight = chunkLeft + channelInfo.canvasWidths[i];
|
|
623
|
-
if (chunkRight > scrollLeft && chunkLeft < scrollLeft + viewportWidth) {
|
|
624
|
-
viewportIndices.push(i);
|
|
625
|
-
} else if (chunkRight > bufferStart && chunkLeft < bufferEnd) {
|
|
626
|
-
bufferIndices.push(i);
|
|
627
|
-
} else {
|
|
628
|
-
remainingIndices.push(i);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
return { viewportIndices, bufferIndices, remainingIndices };
|
|
696
|
+
return classifyChunkTiers(
|
|
697
|
+
channelInfo,
|
|
698
|
+
clipPixelOffset,
|
|
699
|
+
container ? { scrollLeft: container.scrollLeft, viewportWidth: container.clientWidth } : null
|
|
700
|
+
);
|
|
632
701
|
};
|
|
633
702
|
const renderChunkSubset = async (api, cacheKey, channelInfo, indices, item, channelIndex, gen) => {
|
|
634
703
|
if (indices.length === 0) return;
|
|
@@ -637,7 +706,7 @@ var SpectrogramProvider = ({
|
|
|
637
706
|
const globalPixelOffsets = [];
|
|
638
707
|
for (const idx of indices) {
|
|
639
708
|
const chunkNumber = extractChunkNumber(channelInfo.canvasIds[idx]);
|
|
640
|
-
globalPixelOffsets.push(chunkNumber *
|
|
709
|
+
globalPixelOffsets.push(chunkNumber * MAX_CANVAS_WIDTH2);
|
|
641
710
|
}
|
|
642
711
|
const colorLUT = getColorMap(item.colorMap);
|
|
643
712
|
await api.renderChunks(
|
|
@@ -661,24 +730,14 @@ var SpectrogramProvider = ({
|
|
|
661
730
|
);
|
|
662
731
|
};
|
|
663
732
|
const computeFFTForChunks = async (api, channelInfo, indices, item, gen) => {
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const rangeStartSample = item.offsetSamples + Math.floor(startPx * samplesPerPixel);
|
|
673
|
-
const rangeEndSample = Math.min(
|
|
674
|
-
item.offsetSamples + item.durationSamples,
|
|
675
|
-
item.offsetSamples + Math.ceil(endPx * samplesPerPixel)
|
|
676
|
-
);
|
|
677
|
-
const paddedStart = Math.max(item.offsetSamples, rangeStartSample - windowSize);
|
|
678
|
-
const paddedEnd = Math.min(
|
|
679
|
-
item.offsetSamples + item.durationSamples,
|
|
680
|
-
rangeEndSample + windowSize
|
|
681
|
-
);
|
|
733
|
+
const { paddedStart, paddedEnd } = computeChunkSampleRange({
|
|
734
|
+
channelInfo,
|
|
735
|
+
indices,
|
|
736
|
+
fftSize: item.config.fftSize ?? 2048,
|
|
737
|
+
offsetSamples: item.offsetSamples,
|
|
738
|
+
durationSamples: item.durationSamples,
|
|
739
|
+
samplesPerPixel
|
|
740
|
+
});
|
|
682
741
|
const { cacheKey } = await api.computeFFT(
|
|
683
742
|
{
|
|
684
743
|
clipId: item.clipId,
|
|
@@ -694,24 +753,6 @@ var SpectrogramProvider = ({
|
|
|
694
753
|
);
|
|
695
754
|
return cacheKey;
|
|
696
755
|
};
|
|
697
|
-
const groupContiguousIndices = (channelInfo, indices) => {
|
|
698
|
-
if (indices.length === 0) return [];
|
|
699
|
-
const groups = [];
|
|
700
|
-
let currentGroup = [indices[0]];
|
|
701
|
-
let prevChunk = extractChunkNumber(channelInfo.canvasIds[indices[0]]);
|
|
702
|
-
for (let i = 1; i < indices.length; i++) {
|
|
703
|
-
const chunk = extractChunkNumber(channelInfo.canvasIds[indices[i]]);
|
|
704
|
-
if (chunk === prevChunk + 1) {
|
|
705
|
-
currentGroup.push(indices[i]);
|
|
706
|
-
} else {
|
|
707
|
-
groups.push(currentGroup);
|
|
708
|
-
currentGroup = [indices[i]];
|
|
709
|
-
}
|
|
710
|
-
prevChunk = chunk;
|
|
711
|
-
}
|
|
712
|
-
groups.push(currentGroup);
|
|
713
|
-
return groups;
|
|
714
|
-
};
|
|
715
756
|
const computeAsync = async () => {
|
|
716
757
|
const abortToken = { aborted: false };
|
|
717
758
|
backgroundRenderAbortRef.current = abortToken;
|
|
@@ -1001,10 +1042,9 @@ var SpectrogramProvider = ({
|
|
|
1001
1042
|
);
|
|
1002
1043
|
}
|
|
1003
1044
|
}
|
|
1004
|
-
const
|
|
1005
|
-
if (!
|
|
1006
|
-
const clipId =
|
|
1007
|
-
const channelIndex = parseInt(match[2], 10);
|
|
1045
|
+
const parsed = parseCanvasId(canvasId);
|
|
1046
|
+
if (!parsed) return;
|
|
1047
|
+
const { clipId, channelIndex } = parsed;
|
|
1008
1048
|
const registry = spectrogramCanvasRegistryRef.current;
|
|
1009
1049
|
const perClip = registry.get(clipId);
|
|
1010
1050
|
if (!perClip) return;
|