@waveform-playlist/spectrogram 13.0.1 → 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.js
CHANGED
|
@@ -426,12 +426,11 @@ var SpectrogramSettingsModal = ({
|
|
|
426
426
|
|
|
427
427
|
// src/SpectrogramProvider.tsx
|
|
428
428
|
var import_react2 = require("react");
|
|
429
|
-
var
|
|
429
|
+
var import_core2 = require("@waveform-playlist/core");
|
|
430
430
|
var import_spectrogram = require("@dawcore/spectrogram");
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
var
|
|
434
|
-
var import_meta = {};
|
|
431
|
+
|
|
432
|
+
// src/spectrogram-helpers.ts
|
|
433
|
+
var import_core = require("@waveform-playlist/core");
|
|
435
434
|
function extractChunkNumber(canvasId) {
|
|
436
435
|
const match = canvasId.match(/chunk(\d+)$/);
|
|
437
436
|
if (!match) {
|
|
@@ -440,6 +439,115 @@ function extractChunkNumber(canvasId) {
|
|
|
440
439
|
}
|
|
441
440
|
return parseInt(match[1], 10);
|
|
442
441
|
}
|
|
442
|
+
function parseCanvasId(canvasId) {
|
|
443
|
+
const match = canvasId.match(/^(.+)-ch(\d+)-chunk\d+$/);
|
|
444
|
+
if (!match) return null;
|
|
445
|
+
return { clipId: match[1], channelIndex: parseInt(match[2], 10) };
|
|
446
|
+
}
|
|
447
|
+
function groupContiguousIndices(channelInfo, indices) {
|
|
448
|
+
if (indices.length === 0) return [];
|
|
449
|
+
const groups = [];
|
|
450
|
+
let currentGroup = [indices[0]];
|
|
451
|
+
let prevChunk = extractChunkNumber(channelInfo.canvasIds[indices[0]]);
|
|
452
|
+
for (let i = 1; i < indices.length; i++) {
|
|
453
|
+
const chunk = extractChunkNumber(channelInfo.canvasIds[indices[i]]);
|
|
454
|
+
if (chunk === prevChunk + 1) {
|
|
455
|
+
currentGroup.push(indices[i]);
|
|
456
|
+
} else {
|
|
457
|
+
groups.push(currentGroup);
|
|
458
|
+
currentGroup = [indices[i]];
|
|
459
|
+
}
|
|
460
|
+
prevChunk = chunk;
|
|
461
|
+
}
|
|
462
|
+
groups.push(currentGroup);
|
|
463
|
+
return groups;
|
|
464
|
+
}
|
|
465
|
+
function classifyChunkTiers(channelInfo, clipPixelOffset = 0, viewport) {
|
|
466
|
+
if (!viewport) {
|
|
467
|
+
return {
|
|
468
|
+
viewportIndices: channelInfo.canvasWidths.map((_, i) => i),
|
|
469
|
+
bufferIndices: [],
|
|
470
|
+
remainingIndices: []
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
const { scrollLeft, viewportWidth } = viewport;
|
|
474
|
+
const buffer = viewportWidth * 1.5;
|
|
475
|
+
const bufferStart = Math.max(0, scrollLeft - buffer);
|
|
476
|
+
const bufferEnd = scrollLeft + viewportWidth + buffer;
|
|
477
|
+
const viewportIndices = [];
|
|
478
|
+
const bufferIndices = [];
|
|
479
|
+
const remainingIndices = [];
|
|
480
|
+
for (let i = 0; i < channelInfo.canvasWidths.length; i++) {
|
|
481
|
+
const chunkNumber = extractChunkNumber(channelInfo.canvasIds[i]);
|
|
482
|
+
const chunkLeft = chunkNumber * import_core.MAX_CANVAS_WIDTH + clipPixelOffset;
|
|
483
|
+
const chunkRight = chunkLeft + channelInfo.canvasWidths[i];
|
|
484
|
+
if (chunkRight > scrollLeft && chunkLeft < scrollLeft + viewportWidth) {
|
|
485
|
+
viewportIndices.push(i);
|
|
486
|
+
} else if (chunkRight > bufferStart && chunkLeft < bufferEnd) {
|
|
487
|
+
bufferIndices.push(i);
|
|
488
|
+
} else {
|
|
489
|
+
remainingIndices.push(i);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return { viewportIndices, bufferIndices, remainingIndices };
|
|
493
|
+
}
|
|
494
|
+
function computeChunkSampleRange({
|
|
495
|
+
channelInfo,
|
|
496
|
+
indices,
|
|
497
|
+
fftSize,
|
|
498
|
+
offsetSamples,
|
|
499
|
+
durationSamples,
|
|
500
|
+
samplesPerPixel
|
|
501
|
+
}) {
|
|
502
|
+
const chunkNumbers = indices.map((i) => extractChunkNumber(channelInfo.canvasIds[i]));
|
|
503
|
+
const minChunk = Math.min(...chunkNumbers);
|
|
504
|
+
const maxChunk = Math.max(...chunkNumbers);
|
|
505
|
+
const maxChunkIdx = indices[chunkNumbers.indexOf(maxChunk)];
|
|
506
|
+
const lastChunkWidth = channelInfo.canvasWidths[maxChunkIdx];
|
|
507
|
+
const startPx = minChunk * import_core.MAX_CANVAS_WIDTH;
|
|
508
|
+
const endPx = maxChunk * import_core.MAX_CANVAS_WIDTH + lastChunkWidth;
|
|
509
|
+
const rangeStartSample = offsetSamples + Math.floor(startPx * samplesPerPixel);
|
|
510
|
+
const rangeEndSample = Math.min(
|
|
511
|
+
offsetSamples + durationSamples,
|
|
512
|
+
offsetSamples + Math.ceil(endPx * samplesPerPixel)
|
|
513
|
+
);
|
|
514
|
+
const paddedStart = Math.max(offsetSamples, rangeStartSample - fftSize);
|
|
515
|
+
const paddedEnd = Math.min(offsetSamples + durationSamples, rangeEndSample + fftSize);
|
|
516
|
+
return { paddedStart, paddedEnd };
|
|
517
|
+
}
|
|
518
|
+
function resolveRenderMode(override, trackRenderMode) {
|
|
519
|
+
return override?.renderMode ?? trackRenderMode ?? "waveform";
|
|
520
|
+
}
|
|
521
|
+
function toComputeConfig(cfg) {
|
|
522
|
+
return {
|
|
523
|
+
fftSize: cfg?.fftSize,
|
|
524
|
+
hopSize: cfg?.hopSize,
|
|
525
|
+
windowFunction: cfg?.windowFunction,
|
|
526
|
+
alpha: cfg?.alpha,
|
|
527
|
+
zeroPaddingFactor: cfg?.zeroPaddingFactor
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function buildConfigKey(params) {
|
|
531
|
+
const { mode, cfg, cm, mono } = params;
|
|
532
|
+
return JSON.stringify({ mode, cfg, cm, mono });
|
|
533
|
+
}
|
|
534
|
+
function buildFFTKey(params) {
|
|
535
|
+
const { mode, mono, computeConfig } = params;
|
|
536
|
+
return JSON.stringify({ mode, mono, ...computeConfig });
|
|
537
|
+
}
|
|
538
|
+
function mapsDiffer(prev, current) {
|
|
539
|
+
if (current.size !== prev.size) return true;
|
|
540
|
+
for (const [key, value] of current) {
|
|
541
|
+
if (prev.get(key) !== value) return true;
|
|
542
|
+
}
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/SpectrogramProvider.tsx
|
|
547
|
+
var import_browser = require("@waveform-playlist/browser");
|
|
548
|
+
var import_browser2 = require("@waveform-playlist/browser");
|
|
549
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
550
|
+
var import_meta = {};
|
|
443
551
|
var SpectrogramProvider = ({
|
|
444
552
|
config: spectrogramConfig,
|
|
445
553
|
colorMap: spectrogramColorMap,
|
|
@@ -511,40 +619,21 @@ var SpectrogramProvider = ({
|
|
|
511
619
|
const currentKeys = /* @__PURE__ */ new Map();
|
|
512
620
|
const currentFFTKeys = /* @__PURE__ */ new Map();
|
|
513
621
|
tracks.forEach((track) => {
|
|
514
|
-
const
|
|
622
|
+
const override = trackSpectrogramOverrides.get(track.id);
|
|
623
|
+
const mode = resolveRenderMode(override, track.renderMode);
|
|
515
624
|
if (mode === "waveform") return;
|
|
516
|
-
const cfg =
|
|
517
|
-
const cm =
|
|
518
|
-
currentKeys.set(track.id,
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
alpha: cfg?.alpha,
|
|
524
|
-
zeroPaddingFactor: cfg?.zeroPaddingFactor
|
|
525
|
-
};
|
|
526
|
-
currentFFTKeys.set(track.id, JSON.stringify({ mode, mono, ...computeConfig }));
|
|
625
|
+
const cfg = override?.config ?? track.spectrogramConfig ?? spectrogramConfig;
|
|
626
|
+
const cm = override?.colorMap ?? track.spectrogramColorMap ?? spectrogramColorMap;
|
|
627
|
+
currentKeys.set(track.id, buildConfigKey({ mode, cfg, cm, mono }));
|
|
628
|
+
currentFFTKeys.set(
|
|
629
|
+
track.id,
|
|
630
|
+
buildFFTKey({ mode, mono, computeConfig: toComputeConfig(cfg) })
|
|
631
|
+
);
|
|
527
632
|
});
|
|
528
633
|
const prevKeys = prevSpectrogramConfigRef.current;
|
|
529
634
|
const prevFFTKeys = prevSpectrogramFFTKeyRef.current;
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
for (const [idx, key] of currentKeys) {
|
|
533
|
-
if (prevKeys.get(idx) !== key) {
|
|
534
|
-
configChanged = true;
|
|
535
|
-
break;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
let fftKeyChanged = currentFFTKeys.size !== prevFFTKeys.size;
|
|
540
|
-
if (!fftKeyChanged) {
|
|
541
|
-
for (const [idx, key] of currentFFTKeys) {
|
|
542
|
-
if (prevFFTKeys.get(idx) !== key) {
|
|
543
|
-
fftKeyChanged = true;
|
|
544
|
-
break;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
635
|
+
const configChanged = mapsDiffer(prevKeys, currentKeys);
|
|
636
|
+
const fftKeyChanged = mapsDiffer(prevFFTKeys, currentFFTKeys);
|
|
548
637
|
const canvasVersionChanged = spectrogramCanvasVersion !== prevCanvasVersionRef.current;
|
|
549
638
|
prevCanvasVersionRef.current = spectrogramCanvasVersion;
|
|
550
639
|
if (!configChanged && !canvasVersionChanged) return;
|
|
@@ -579,14 +668,15 @@ var SpectrogramProvider = ({
|
|
|
579
668
|
const clipsNeedingFFT = [];
|
|
580
669
|
const clipsNeedingDisplayOnly = [];
|
|
581
670
|
tracks.forEach((track, i) => {
|
|
582
|
-
const
|
|
671
|
+
const override = trackSpectrogramOverrides.get(track.id);
|
|
672
|
+
const mode = resolveRenderMode(override, track.renderMode);
|
|
583
673
|
if (mode === "waveform") return;
|
|
584
674
|
const trackConfigChanged = configChanged && currentKeys.get(track.id) !== prevKeys.get(track.id);
|
|
585
675
|
const trackFFTChanged = fftKeyChanged && currentFFTKeys.get(track.id) !== prevFFTKeys.get(track.id);
|
|
586
676
|
const hasRegisteredCanvases = canvasVersionChanged && track.clips.some((clip) => spectrogramCanvasRegistryRef.current.has(clip.id));
|
|
587
677
|
if (!trackConfigChanged && !hasRegisteredCanvases) return;
|
|
588
|
-
const cfg =
|
|
589
|
-
const cm =
|
|
678
|
+
const cfg = override?.config ?? track.spectrogramConfig ?? spectrogramConfig ?? {};
|
|
679
|
+
const cm = override?.colorMap ?? track.spectrogramColorMap ?? spectrogramColorMap ?? "viridis";
|
|
590
680
|
for (const clip of track.clips) {
|
|
591
681
|
if (!clip.audioBuffer) continue;
|
|
592
682
|
const monoFlag = mono || clip.audioBuffer.numberOfChannels === 1;
|
|
@@ -631,34 +721,11 @@ var SpectrogramProvider = ({
|
|
|
631
721
|
if (clipsNeedingFFT.length === 0 && clipsNeedingDisplayOnly.length === 0) return;
|
|
632
722
|
const getVisibleChunkRange = (channelInfo, clipPixelOffset = 0) => {
|
|
633
723
|
const container = scrollContainerRef.current;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
const scrollLeft = container.scrollLeft;
|
|
642
|
-
const viewportWidth = container.clientWidth;
|
|
643
|
-
const buffer = viewportWidth * 1.5;
|
|
644
|
-
const bufferStart = Math.max(0, scrollLeft - buffer);
|
|
645
|
-
const bufferEnd = scrollLeft + viewportWidth + buffer;
|
|
646
|
-
const viewportIndices = [];
|
|
647
|
-
const bufferIndices = [];
|
|
648
|
-
const remainingIndices = [];
|
|
649
|
-
for (let i = 0; i < channelInfo.canvasWidths.length; i++) {
|
|
650
|
-
const chunkNumber = extractChunkNumber(channelInfo.canvasIds[i]);
|
|
651
|
-
const chunkLeft = chunkNumber * import_core.MAX_CANVAS_WIDTH + clipPixelOffset;
|
|
652
|
-
const chunkRight = chunkLeft + channelInfo.canvasWidths[i];
|
|
653
|
-
if (chunkRight > scrollLeft && chunkLeft < scrollLeft + viewportWidth) {
|
|
654
|
-
viewportIndices.push(i);
|
|
655
|
-
} else if (chunkRight > bufferStart && chunkLeft < bufferEnd) {
|
|
656
|
-
bufferIndices.push(i);
|
|
657
|
-
} else {
|
|
658
|
-
remainingIndices.push(i);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
return { viewportIndices, bufferIndices, remainingIndices };
|
|
724
|
+
return classifyChunkTiers(
|
|
725
|
+
channelInfo,
|
|
726
|
+
clipPixelOffset,
|
|
727
|
+
container ? { scrollLeft: container.scrollLeft, viewportWidth: container.clientWidth } : null
|
|
728
|
+
);
|
|
662
729
|
};
|
|
663
730
|
const renderChunkSubset = async (api, cacheKey, channelInfo, indices, item, channelIndex, gen) => {
|
|
664
731
|
if (indices.length === 0) return;
|
|
@@ -667,7 +734,7 @@ var SpectrogramProvider = ({
|
|
|
667
734
|
const globalPixelOffsets = [];
|
|
668
735
|
for (const idx of indices) {
|
|
669
736
|
const chunkNumber = extractChunkNumber(channelInfo.canvasIds[idx]);
|
|
670
|
-
globalPixelOffsets.push(chunkNumber *
|
|
737
|
+
globalPixelOffsets.push(chunkNumber * import_core2.MAX_CANVAS_WIDTH);
|
|
671
738
|
}
|
|
672
739
|
const colorLUT = (0, import_spectrogram.getColorMap)(item.colorMap);
|
|
673
740
|
await api.renderChunks(
|
|
@@ -691,24 +758,14 @@ var SpectrogramProvider = ({
|
|
|
691
758
|
);
|
|
692
759
|
};
|
|
693
760
|
const computeFFTForChunks = async (api, channelInfo, indices, item, gen) => {
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const rangeStartSample = item.offsetSamples + Math.floor(startPx * samplesPerPixel);
|
|
703
|
-
const rangeEndSample = Math.min(
|
|
704
|
-
item.offsetSamples + item.durationSamples,
|
|
705
|
-
item.offsetSamples + Math.ceil(endPx * samplesPerPixel)
|
|
706
|
-
);
|
|
707
|
-
const paddedStart = Math.max(item.offsetSamples, rangeStartSample - windowSize);
|
|
708
|
-
const paddedEnd = Math.min(
|
|
709
|
-
item.offsetSamples + item.durationSamples,
|
|
710
|
-
rangeEndSample + windowSize
|
|
711
|
-
);
|
|
761
|
+
const { paddedStart, paddedEnd } = computeChunkSampleRange({
|
|
762
|
+
channelInfo,
|
|
763
|
+
indices,
|
|
764
|
+
fftSize: item.config.fftSize ?? 2048,
|
|
765
|
+
offsetSamples: item.offsetSamples,
|
|
766
|
+
durationSamples: item.durationSamples,
|
|
767
|
+
samplesPerPixel
|
|
768
|
+
});
|
|
712
769
|
const { cacheKey } = await api.computeFFT(
|
|
713
770
|
{
|
|
714
771
|
clipId: item.clipId,
|
|
@@ -724,24 +781,6 @@ var SpectrogramProvider = ({
|
|
|
724
781
|
);
|
|
725
782
|
return cacheKey;
|
|
726
783
|
};
|
|
727
|
-
const groupContiguousIndices = (channelInfo, indices) => {
|
|
728
|
-
if (indices.length === 0) return [];
|
|
729
|
-
const groups = [];
|
|
730
|
-
let currentGroup = [indices[0]];
|
|
731
|
-
let prevChunk = extractChunkNumber(channelInfo.canvasIds[indices[0]]);
|
|
732
|
-
for (let i = 1; i < indices.length; i++) {
|
|
733
|
-
const chunk = extractChunkNumber(channelInfo.canvasIds[indices[i]]);
|
|
734
|
-
if (chunk === prevChunk + 1) {
|
|
735
|
-
currentGroup.push(indices[i]);
|
|
736
|
-
} else {
|
|
737
|
-
groups.push(currentGroup);
|
|
738
|
-
currentGroup = [indices[i]];
|
|
739
|
-
}
|
|
740
|
-
prevChunk = chunk;
|
|
741
|
-
}
|
|
742
|
-
groups.push(currentGroup);
|
|
743
|
-
return groups;
|
|
744
|
-
};
|
|
745
784
|
const computeAsync = async () => {
|
|
746
785
|
const abortToken = { aborted: false };
|
|
747
786
|
backgroundRenderAbortRef.current = abortToken;
|
|
@@ -1031,10 +1070,9 @@ var SpectrogramProvider = ({
|
|
|
1031
1070
|
);
|
|
1032
1071
|
}
|
|
1033
1072
|
}
|
|
1034
|
-
const
|
|
1035
|
-
if (!
|
|
1036
|
-
const clipId =
|
|
1037
|
-
const channelIndex = parseInt(match[2], 10);
|
|
1073
|
+
const parsed = parseCanvasId(canvasId);
|
|
1074
|
+
if (!parsed) return;
|
|
1075
|
+
const { clipId, channelIndex } = parsed;
|
|
1038
1076
|
const registry = spectrogramCanvasRegistryRef.current;
|
|
1039
1077
|
const perClip = registry.get(clipId);
|
|
1040
1078
|
if (!perClip) return;
|