@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.
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 import_core = require("@waveform-playlist/core");
429
+ var import_core2 = require("@waveform-playlist/core");
430
430
  var import_spectrogram = require("@dawcore/spectrogram");
431
- var import_browser = require("@waveform-playlist/browser");
432
- var import_browser2 = require("@waveform-playlist/browser");
433
- var import_jsx_runtime3 = require("react/jsx-runtime");
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 mode = trackSpectrogramOverrides.get(track.id)?.renderMode ?? track.renderMode ?? "waveform";
622
+ const override = trackSpectrogramOverrides.get(track.id);
623
+ const mode = resolveRenderMode(override, track.renderMode);
515
624
  if (mode === "waveform") return;
516
- const cfg = trackSpectrogramOverrides.get(track.id)?.config ?? track.spectrogramConfig ?? spectrogramConfig;
517
- const cm = trackSpectrogramOverrides.get(track.id)?.colorMap ?? track.spectrogramColorMap ?? spectrogramColorMap;
518
- currentKeys.set(track.id, JSON.stringify({ mode, cfg, cm, mono }));
519
- const computeConfig = {
520
- fftSize: cfg?.fftSize,
521
- hopSize: cfg?.hopSize,
522
- windowFunction: cfg?.windowFunction,
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
- let configChanged = currentKeys.size !== prevKeys.size;
531
- if (!configChanged) {
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 mode = trackSpectrogramOverrides.get(track.id)?.renderMode ?? track.renderMode ?? "waveform";
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 = trackSpectrogramOverrides.get(track.id)?.config ?? track.spectrogramConfig ?? spectrogramConfig ?? {};
589
- const cm = trackSpectrogramOverrides.get(track.id)?.colorMap ?? track.spectrogramColorMap ?? spectrogramColorMap ?? "viridis";
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
- if (!container) {
635
- return {
636
- viewportIndices: channelInfo.canvasWidths.map((_, i) => i),
637
- bufferIndices: [],
638
- remainingIndices: []
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 * import_core.MAX_CANVAS_WIDTH);
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 chunkNumbers = indices.map((i) => extractChunkNumber(channelInfo.canvasIds[i]));
695
- const minChunk = Math.min(...chunkNumbers);
696
- const maxChunk = Math.max(...chunkNumbers);
697
- const maxChunkIdx = indices[chunkNumbers.indexOf(maxChunk)];
698
- const lastChunkWidth = channelInfo.canvasWidths[maxChunkIdx];
699
- const startPx = minChunk * import_core.MAX_CANVAS_WIDTH;
700
- const endPx = maxChunk * import_core.MAX_CANVAS_WIDTH + lastChunkWidth;
701
- const windowSize = item.config.fftSize ?? 2048;
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 match = canvasId.match(/^(.+)-ch(\d+)-chunk\d+$/);
1035
- if (!match) return;
1036
- const clipId = match[1];
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;