@waveform-playlist/browser 13.1.3 → 15.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.
package/dist/index.mjs CHANGED
@@ -50,30 +50,52 @@ var __async = (__this, __arguments, generator) => {
50
50
  });
51
51
  };
52
52
 
53
- // src/index.tsx
54
- import * as Tone2 from "tone";
55
-
56
53
  // src/WaveformPlaylistContext.tsx
57
54
  import {
58
55
  createContext,
59
56
  useContext,
60
- useState as useState15,
61
- useEffect as useEffect10,
62
- useRef as useRef15,
63
- useCallback as useCallback19,
64
- useMemo as useMemo4
57
+ useState as useState9,
58
+ useEffect as useEffect5,
59
+ useRef as useRef9,
60
+ useCallback as useCallback13,
61
+ useMemo as useMemo3
65
62
  } from "react";
66
63
  import { ThemeProvider } from "styled-components";
67
- import {
68
- configureGlobalContext,
69
- createToneAdapter,
70
- getGlobalAudioContext as getGlobalAudioContext4
71
- } from "@waveform-playlist/playout";
64
+
65
+ // src/playout/resolvePlayoutAdapter.ts
66
+ var INSTALL_HINT = "@waveform-playlist/playout (and its peer `tone`) is required for the default WebAudio engine. Install with: npm install @waveform-playlist/playout tone \u2014 or pass a custom `createAdapter`.";
67
+ function resolvePlayoutAdapter(opts) {
68
+ return __async(this, null, function* () {
69
+ if (opts.createAdapter) {
70
+ return opts.createAdapter();
71
+ }
72
+ let mod;
73
+ try {
74
+ mod = yield import("@waveform-playlist/playout");
75
+ } catch (originalErr) {
76
+ console.warn(
77
+ "[waveform-playlist] @waveform-playlist/playout dynamic import failed: " + String(originalErr)
78
+ );
79
+ throw new Error(INSTALL_HINT);
80
+ }
81
+ if (opts.sampleRate !== void 0) {
82
+ try {
83
+ mod.configureGlobalContext({ sampleRate: opts.sampleRate });
84
+ } catch (ctxErr) {
85
+ console.warn(
86
+ "[waveform-playlist] configureGlobalContext failed (continuing with default rate): " + String(ctxErr)
87
+ );
88
+ }
89
+ }
90
+ return mod.createToneAdapter({ effects: opts.effects, soundFontCache: opts.soundFontCache });
91
+ });
92
+ }
93
+
94
+ // src/WaveformPlaylistContext.tsx
72
95
  import { PlaylistEngine } from "@waveform-playlist/engine";
73
96
  import {
74
97
  defaultTheme
75
98
  } from "@waveform-playlist/ui-components";
76
- import { getContext as getContext2 } from "tone";
77
99
 
78
100
  // src/waveformDataLoader.ts
79
101
  import WaveformData from "waveform-data";
@@ -239,9 +261,11 @@ function extractPeaksFromWaveformDataFull(waveformData, samplesPerPixel, isMono,
239
261
  }
240
262
 
241
263
  // src/soundFontSync.ts
242
- import { isToneAdapter } from "@waveform-playlist/playout";
264
+ function supportsSoundFont(adapter) {
265
+ return adapter != null && typeof adapter.setSoundFontCache === "function";
266
+ }
243
267
  function syncSoundFontCacheToAdapter(adapter, cache) {
244
- if (!isToneAdapter(adapter)) return;
268
+ if (!supportsSoundFont(adapter)) return;
245
269
  adapter.setSoundFontCache(cache);
246
270
  }
247
271
 
@@ -501,224 +525,6 @@ function useUndoState({ engineRef }) {
501
525
  };
502
526
  }
503
527
 
504
- // src/hooks/useAudioEffects.ts
505
- import { useRef as useRef7, useCallback as useCallback7 } from "react";
506
- import { Analyser } from "tone";
507
- var useMasterAnalyser = (fftSize = 256) => {
508
- const analyserRef = useRef7(null);
509
- const masterEffects = useCallback7(
510
- (masterGainNode, destination, _isOffline) => {
511
- const analyserNode = new Analyser("fft", fftSize);
512
- masterGainNode.connect(analyserNode);
513
- masterGainNode.connect(destination);
514
- analyserRef.current = analyserNode;
515
- return function cleanup() {
516
- analyserNode.dispose();
517
- analyserRef.current = null;
518
- };
519
- },
520
- [fftSize]
521
- );
522
- return { analyserRef, masterEffects };
523
- };
524
-
525
- // src/hooks/useAudioTracks.ts
526
- import { useState as useState8, useEffect, useRef as useRef8, useMemo } from "react";
527
- import {
528
- createTrack,
529
- createClipFromSeconds
530
- } from "@waveform-playlist/core";
531
- import * as Tone from "tone";
532
- function buildTrackFromConfig(config, index, audioBuffer, stableIds, contextSampleRate = 48e3) {
533
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
534
- const buffer = audioBuffer != null ? audioBuffer : config.audioBuffer;
535
- const sampleRate = (_c = (_b = buffer == null ? void 0 : buffer.sampleRate) != null ? _b : (_a = config.waveformData) == null ? void 0 : _a.sample_rate) != null ? _c : contextSampleRate;
536
- const sourceDuration = (_g = (_e = buffer == null ? void 0 : buffer.duration) != null ? _e : (_d = config.waveformData) == null ? void 0 : _d.duration) != null ? _g : config.duration != null ? config.duration + ((_f = config.offset) != null ? _f : 0) : void 0;
537
- if (sourceDuration === void 0) {
538
- console.warn(
539
- `[waveform-playlist] Track ${index + 1} ("${(_h = config.name) != null ? _h : "unnamed"}"): Cannot create track \u2014 provide duration, audioBuffer, or waveformData with duration.`
540
- );
541
- return null;
542
- }
543
- const clip = createClipFromSeconds({
544
- audioBuffer: buffer,
545
- sampleRate,
546
- sourceDuration,
547
- startTime: (_i = config.startTime) != null ? _i : 0,
548
- duration: (_j = config.duration) != null ? _j : sourceDuration,
549
- offset: (_k = config.offset) != null ? _k : 0,
550
- name: config.name || `Track ${index + 1}`,
551
- fadeIn: config.fadeIn,
552
- fadeOut: config.fadeOut,
553
- waveformData: config.waveformData
554
- });
555
- if (isNaN(clip.startSample) || isNaN(clip.durationSamples) || isNaN(clip.offsetSamples)) {
556
- console.error(
557
- `[waveform-playlist] Invalid clip values for track ${index + 1} ("${(_l = config.name) != null ? _l : "unnamed"}"): startSample=${clip.startSample}, durationSamples=${clip.durationSamples}, offsetSamples=${clip.offsetSamples}`
558
- );
559
- return null;
560
- }
561
- const track = __spreadProps(__spreadValues({}, createTrack({
562
- name: config.name || `Track ${index + 1}`,
563
- clips: [clip],
564
- muted: (_m = config.muted) != null ? _m : false,
565
- soloed: (_n = config.soloed) != null ? _n : false,
566
- volume: (_o = config.volume) != null ? _o : 1,
567
- pan: (_p = config.pan) != null ? _p : 0,
568
- color: config.color
569
- })), {
570
- effects: config.effects,
571
- renderMode: config.renderMode,
572
- spectrogramConfig: config.spectrogramConfig,
573
- spectrogramColorMap: config.spectrogramColorMap
574
- });
575
- const existingIds = stableIds.get(index);
576
- if (existingIds) {
577
- track.id = existingIds.trackId;
578
- track.clips[0] = __spreadProps(__spreadValues({}, track.clips[0]), { id: existingIds.clipId });
579
- } else {
580
- stableIds.set(index, { trackId: track.id, clipId: track.clips[0].id });
581
- }
582
- return track;
583
- }
584
- function useAudioTracks(configs, options = {}) {
585
- const { immediate = false, progressive = false } = options;
586
- const isImmediate = immediate || progressive;
587
- const [loading, setLoading] = useState8(true);
588
- const [error, setError] = useState8(null);
589
- const [loadedCount, setLoadedCount] = useState8(0);
590
- const totalCount = configs.length;
591
- const [loadedBuffers, setLoadedBuffers] = useState8(/* @__PURE__ */ new Map());
592
- const stableIdsRef = useRef8(/* @__PURE__ */ new Map());
593
- const contextSampleRateRef = useRef8(48e3);
594
- const derivedTracks = useMemo(() => {
595
- if (!isImmediate) return null;
596
- const result = [];
597
- for (let i = 0; i < configs.length; i++) {
598
- const track = buildTrackFromConfig(
599
- configs[i],
600
- i,
601
- loadedBuffers.get(i),
602
- stableIdsRef.current,
603
- contextSampleRateRef.current
604
- );
605
- if (track) result.push(track);
606
- }
607
- return result;
608
- }, [isImmediate, configs, loadedBuffers]);
609
- const [tracks, setTracks] = useState8(derivedTracks != null ? derivedTracks : []);
610
- const prevDerivedRef = useRef8(derivedTracks);
611
- if (derivedTracks !== prevDerivedRef.current) {
612
- prevDerivedRef.current = derivedTracks;
613
- if (derivedTracks) setTracks(derivedTracks);
614
- }
615
- useEffect(() => {
616
- if (configs.length === 0) {
617
- setTracks([]);
618
- setLoading(false);
619
- setLoadedCount(0);
620
- return;
621
- }
622
- let cancelled = false;
623
- const abortController = new AbortController();
624
- const loadTracks = () => __async(null, null, function* () {
625
- try {
626
- setLoading(true);
627
- setError(null);
628
- setLoadedCount(0);
629
- if (isImmediate) {
630
- setLoadedBuffers(/* @__PURE__ */ new Map());
631
- }
632
- const audioContext = Tone.getContext().rawContext;
633
- contextSampleRateRef.current = audioContext.sampleRate;
634
- const loadPromises = configs.map((config, index) => __async(null, null, function* () {
635
- if (config.audioBuffer) {
636
- if (isImmediate && !cancelled) {
637
- setLoadedBuffers((prev) => {
638
- const next = new Map(prev);
639
- next.set(index, config.audioBuffer);
640
- return next;
641
- });
642
- setLoadedCount((prev) => prev + 1);
643
- return;
644
- }
645
- return buildTrackFromConfig(
646
- config,
647
- index,
648
- config.audioBuffer,
649
- stableIdsRef.current,
650
- audioContext.sampleRate
651
- );
652
- }
653
- if (!config.src && config.waveformData) {
654
- if (isImmediate && !cancelled) {
655
- setLoadedCount((prev) => prev + 1);
656
- return;
657
- }
658
- return buildTrackFromConfig(
659
- config,
660
- index,
661
- void 0,
662
- stableIdsRef.current,
663
- audioContext.sampleRate
664
- );
665
- }
666
- if (!config.src) {
667
- throw new Error(`Track ${index + 1}: Must provide src, audioBuffer, or waveformData`);
668
- }
669
- const response = yield fetch(config.src, { signal: abortController.signal });
670
- if (!response.ok) {
671
- throw new Error(`Failed to fetch ${config.src}: ${response.statusText}`);
672
- }
673
- const arrayBuffer = yield response.arrayBuffer();
674
- const audioBuffer = yield audioContext.decodeAudioData(arrayBuffer);
675
- if (!audioBuffer || !audioBuffer.sampleRate || !audioBuffer.duration) {
676
- throw new Error(`Invalid audio buffer for ${config.src}`);
677
- }
678
- if (isImmediate && !cancelled) {
679
- setLoadedBuffers((prev) => {
680
- const next = new Map(prev);
681
- next.set(index, audioBuffer);
682
- return next;
683
- });
684
- setLoadedCount((prev) => prev + 1);
685
- return;
686
- }
687
- return buildTrackFromConfig(
688
- config,
689
- index,
690
- audioBuffer,
691
- stableIdsRef.current,
692
- audioContext.sampleRate
693
- );
694
- }));
695
- const loadedTracks = yield Promise.all(loadPromises);
696
- if (!cancelled) {
697
- if (!isImmediate) {
698
- const validTracks = loadedTracks.filter((t) => t != null);
699
- setTracks(validTracks);
700
- setLoadedCount(validTracks.length);
701
- }
702
- setLoading(false);
703
- }
704
- } catch (err) {
705
- if (!cancelled) {
706
- const errorMessage = err instanceof Error ? err.message : "Unknown error loading audio";
707
- setError(errorMessage);
708
- setLoading(false);
709
- console.error(`[waveform-playlist] Error loading audio tracks: ${errorMessage}`);
710
- }
711
- }
712
- });
713
- loadTracks();
714
- return () => {
715
- cancelled = true;
716
- abortController.abort();
717
- };
718
- }, [configs, isImmediate]);
719
- return { tracks, loading, error, loadedCount, totalCount };
720
- }
721
-
722
528
  // src/hooks/useClipDragHandlers.ts
723
529
  import React from "react";
724
530
 
@@ -955,13 +761,13 @@ function useClipDragHandlers({
955
761
 
956
762
  // src/hooks/useAnnotationDragHandlers.ts
957
763
  import React2 from "react";
958
- import { getGlobalAudioContext } from "@waveform-playlist/playout";
959
764
  var LINK_THRESHOLD = 0.01;
960
765
  function useAnnotationDragHandlers({
961
766
  annotations,
962
767
  onAnnotationsChange,
963
768
  samplesPerPixel,
964
- sampleRate = getGlobalAudioContext().sampleRate,
769
+ // Default mirrors the engine default; the providers always pass the real rate from context (#510).
770
+ sampleRate = 48e3,
965
771
  duration,
966
772
  linkEndpoints
967
773
  }) {
@@ -1119,7 +925,7 @@ function updateAnnotationBoundaries({
1119
925
  }
1120
926
 
1121
927
  // src/hooks/useDragSensors.ts
1122
- import { useMemo as useMemo2 } from "react";
928
+ import { useMemo } from "react";
1123
929
  import { PointerSensor, PointerActivationConstraints } from "@dnd-kit/dom";
1124
930
  function useDragSensors(options = {}) {
1125
931
  const {
@@ -1128,7 +934,7 @@ function useDragSensors(options = {}) {
1128
934
  touchTolerance = 5,
1129
935
  mouseDistance = 1
1130
936
  } = options;
1131
- return useMemo2(() => {
937
+ return useMemo(() => {
1132
938
  if (touchOptimized) {
1133
939
  return [
1134
940
  PointerSensor.configure({
@@ -1157,14 +963,14 @@ function useDragSensors(options = {}) {
1157
963
  }
1158
964
 
1159
965
  // src/hooks/useClipSplitting.ts
1160
- import { useCallback as useCallback8 } from "react";
966
+ import { useCallback as useCallback7 } from "react";
1161
967
  import { calculateSplitPoint, canSplitAt } from "@waveform-playlist/engine";
1162
968
  var useClipSplitting = (options) => {
1163
969
  const { tracks, engineRef } = options;
1164
970
  const { sampleRate } = usePlaylistData();
1165
971
  const { currentTimeRef } = usePlaybackAnimation();
1166
972
  const { selectedTrackId } = usePlaylistState();
1167
- const splitClipAt = useCallback8(
973
+ const splitClipAt = useCallback7(
1168
974
  (trackIndex, clipIndex, splitTime) => {
1169
975
  const { samplesPerPixel } = options;
1170
976
  const track = tracks[trackIndex];
@@ -1188,7 +994,7 @@ var useClipSplitting = (options) => {
1188
994
  },
1189
995
  [tracks, options, engineRef, sampleRate]
1190
996
  );
1191
- const splitClipAtPlayhead = useCallback8(() => {
997
+ const splitClipAtPlayhead = useCallback7(() => {
1192
998
  var _a;
1193
999
  if (!selectedTrackId) {
1194
1000
  console.warn("[waveform-playlist] No track selected \u2014 click a clip to select a track first");
@@ -1219,16 +1025,16 @@ var useClipSplitting = (options) => {
1219
1025
  };
1220
1026
 
1221
1027
  // src/hooks/useKeyboardShortcuts.ts
1222
- import { useEffect as useEffect2, useCallback as useCallback9 } from "react";
1028
+ import { useEffect, useCallback as useCallback8 } from "react";
1223
1029
  import { handleKeyboardEvent } from "@waveform-playlist/core";
1224
1030
  import { handleKeyboardEvent as handleKeyboardEvent2, getShortcutLabel } from "@waveform-playlist/core";
1225
1031
  var useKeyboardShortcuts = (options) => {
1226
1032
  const { shortcuts, enabled = true } = options;
1227
- const handleKeyDown = useCallback9(
1033
+ const handleKeyDown = useCallback8(
1228
1034
  (event) => handleKeyboardEvent(event, shortcuts, enabled),
1229
1035
  [shortcuts, enabled]
1230
1036
  );
1231
- useEffect2(() => {
1037
+ useEffect(() => {
1232
1038
  if (!enabled) return;
1233
1039
  window.addEventListener("keydown", handleKeyDown);
1234
1040
  return () => {
@@ -1238,22 +1044,22 @@ var useKeyboardShortcuts = (options) => {
1238
1044
  };
1239
1045
 
1240
1046
  // src/hooks/usePlaybackShortcuts.ts
1241
- import { useCallback as useCallback10 } from "react";
1047
+ import { useCallback as useCallback9 } from "react";
1242
1048
  var usePlaybackShortcuts = (options = {}) => {
1243
1049
  const { enabled = true, additionalShortcuts = [], shortcuts: overrideShortcuts } = options;
1244
1050
  const { isPlaying } = usePlaybackAnimation();
1245
1051
  const { setCurrentTime, play, pause, stop } = usePlaylistControls();
1246
- const togglePlayPause = useCallback10(() => {
1052
+ const togglePlayPause = useCallback9(() => {
1247
1053
  if (isPlaying) {
1248
1054
  pause();
1249
1055
  } else {
1250
1056
  play();
1251
1057
  }
1252
1058
  }, [isPlaying, play, pause]);
1253
- const stopPlayback = useCallback10(() => {
1059
+ const stopPlayback = useCallback9(() => {
1254
1060
  stop();
1255
1061
  }, [stop]);
1256
- const rewindToStart = useCallback10(() => {
1062
+ const rewindToStart = useCallback9(() => {
1257
1063
  setCurrentTime(0);
1258
1064
  if (isPlaying) {
1259
1065
  play(0);
@@ -1293,7 +1099,7 @@ var usePlaybackShortcuts = (options = {}) => {
1293
1099
  };
1294
1100
 
1295
1101
  // src/hooks/useAnnotationKeyboardControls.ts
1296
- import { useCallback as useCallback11, useMemo as useMemo3, useEffect as useEffect3 } from "react";
1102
+ import { useCallback as useCallback10, useMemo as useMemo2, useEffect as useEffect2 } from "react";
1297
1103
  var LINK_THRESHOLD2 = 0.01;
1298
1104
  var TIME_DELTA = 0.01;
1299
1105
  function useAnnotationKeyboardControls({
@@ -1313,11 +1119,11 @@ function useAnnotationKeyboardControls({
1313
1119
  const playlistData = usePlaylistDataOptional();
1314
1120
  const samplesPerPixel = samplesPerPixelProp != null ? samplesPerPixelProp : playlistData == null ? void 0 : playlistData.samplesPerPixel;
1315
1121
  const sampleRate = sampleRateProp != null ? sampleRateProp : playlistData == null ? void 0 : playlistData.sampleRate;
1316
- const activeIndex = useMemo3(() => {
1122
+ const activeIndex = useMemo2(() => {
1317
1123
  if (!activeAnnotationId) return -1;
1318
1124
  return annotations.findIndex((a) => a.id === activeAnnotationId);
1319
1125
  }, [annotations, activeAnnotationId]);
1320
- const scrollToAnnotation = useCallback11(
1126
+ const scrollToAnnotation = useCallback10(
1321
1127
  (annotationId) => {
1322
1128
  if (!(scrollContainerRef == null ? void 0 : scrollContainerRef.current) || !samplesPerPixel || !sampleRate) return;
1323
1129
  const annotation = annotations.find((a) => a.id === annotationId);
@@ -1340,12 +1146,12 @@ function useAnnotationKeyboardControls({
1340
1146
  },
1341
1147
  [annotations, scrollContainerRef, samplesPerPixel, sampleRate]
1342
1148
  );
1343
- useEffect3(() => {
1149
+ useEffect2(() => {
1344
1150
  if (activeAnnotationId && (scrollContainerRef == null ? void 0 : scrollContainerRef.current) && samplesPerPixel && sampleRate) {
1345
1151
  scrollToAnnotation(activeAnnotationId);
1346
1152
  }
1347
1153
  }, [activeAnnotationId, scrollToAnnotation, scrollContainerRef, samplesPerPixel, sampleRate]);
1348
- const moveStartBoundary = useCallback11(
1154
+ const moveStartBoundary = useCallback10(
1349
1155
  (delta) => {
1350
1156
  if (activeIndex < 0) return;
1351
1157
  const annotation = annotations[activeIndex];
@@ -1374,7 +1180,7 @@ function useAnnotationKeyboardControls({
1374
1180
  },
1375
1181
  [annotations, activeIndex, linkEndpoints, onAnnotationsChange]
1376
1182
  );
1377
- const moveEndBoundary = useCallback11(
1183
+ const moveEndBoundary = useCallback10(
1378
1184
  (delta) => {
1379
1185
  if (activeIndex < 0) return;
1380
1186
  const annotation = annotations[activeIndex];
@@ -1434,7 +1240,7 @@ function useAnnotationKeyboardControls({
1434
1240
  },
1435
1241
  [annotations, activeIndex, duration, linkEndpoints, onAnnotationsChange]
1436
1242
  );
1437
- const selectPrevious = useCallback11(() => {
1243
+ const selectPrevious = useCallback10(() => {
1438
1244
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1439
1245
  if (activeIndex <= 0) {
1440
1246
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
@@ -1442,7 +1248,7 @@ function useAnnotationKeyboardControls({
1442
1248
  onActiveAnnotationChange(annotations[activeIndex - 1].id);
1443
1249
  }
1444
1250
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1445
- const selectNext = useCallback11(() => {
1251
+ const selectNext = useCallback10(() => {
1446
1252
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1447
1253
  if (activeIndex < 0 || activeIndex >= annotations.length - 1) {
1448
1254
  onActiveAnnotationChange(annotations[0].id);
@@ -1450,25 +1256,25 @@ function useAnnotationKeyboardControls({
1450
1256
  onActiveAnnotationChange(annotations[activeIndex + 1].id);
1451
1257
  }
1452
1258
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1453
- const selectFirst = useCallback11(() => {
1259
+ const selectFirst = useCallback10(() => {
1454
1260
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1455
1261
  onActiveAnnotationChange(annotations[0].id);
1456
1262
  }, [annotations, onActiveAnnotationChange]);
1457
- const selectLast = useCallback11(() => {
1263
+ const selectLast = useCallback10(() => {
1458
1264
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1459
1265
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
1460
1266
  }, [annotations, onActiveAnnotationChange]);
1461
- const clearSelection = useCallback11(() => {
1267
+ const clearSelection = useCallback10(() => {
1462
1268
  if (!onActiveAnnotationChange) return;
1463
1269
  onActiveAnnotationChange(null);
1464
1270
  }, [onActiveAnnotationChange]);
1465
- const playActiveAnnotation = useCallback11(() => {
1271
+ const playActiveAnnotation = useCallback10(() => {
1466
1272
  if (activeIndex < 0 || !onPlay) return;
1467
1273
  const annotation = annotations[activeIndex];
1468
1274
  const playDuration = !continuousPlay ? annotation.end - annotation.start : void 0;
1469
1275
  onPlay(annotation.start, playDuration);
1470
1276
  }, [annotations, activeIndex, continuousPlay, onPlay]);
1471
- const activeAnnotationShortcuts = useMemo3(
1277
+ const activeAnnotationShortcuts = useMemo2(
1472
1278
  () => [
1473
1279
  {
1474
1280
  key: "[",
@@ -1505,7 +1311,7 @@ function useAnnotationKeyboardControls({
1505
1311
  ],
1506
1312
  [moveStartBoundary, moveEndBoundary, playActiveAnnotation]
1507
1313
  );
1508
- const navigationShortcuts = useMemo3(
1314
+ const navigationShortcuts = useMemo2(
1509
1315
  () => [
1510
1316
  {
1511
1317
  key: "ArrowUp",
@@ -1539,1445 +1345,58 @@ function useAnnotationKeyboardControls({
1539
1345
  },
1540
1346
  {
1541
1347
  key: "End",
1542
- action: selectLast,
1543
- description: "Select last annotation",
1544
- preventDefault: true
1545
- },
1546
- {
1547
- key: "Escape",
1548
- action: clearSelection,
1549
- description: "Deselect annotation",
1550
- preventDefault: true
1551
- }
1552
- ],
1553
- [selectPrevious, selectNext, selectFirst, selectLast, clearSelection]
1554
- );
1555
- useKeyboardShortcuts({
1556
- shortcuts: activeAnnotationShortcuts,
1557
- enabled: enabled && activeIndex >= 0
1558
- });
1559
- useKeyboardShortcuts({
1560
- shortcuts: navigationShortcuts,
1561
- enabled: enabled && annotations.length > 0 && !!onActiveAnnotationChange
1562
- });
1563
- return {
1564
- moveStartBoundary,
1565
- moveEndBoundary,
1566
- selectPrevious,
1567
- selectNext,
1568
- selectFirst,
1569
- selectLast,
1570
- clearSelection,
1571
- scrollToAnnotation,
1572
- playActiveAnnotation
1573
- };
1574
- }
1575
-
1576
- // src/hooks/useDynamicEffects.ts
1577
- import { useState as useState9, useCallback as useCallback12, useRef as useRef9, useEffect as useEffect4 } from "react";
1578
-
1579
- // src/effects/effectDefinitions.ts
1580
- var effectDefinitions = [
1581
- // === REVERB EFFECTS ===
1582
- {
1583
- id: "reverb",
1584
- name: "Reverb",
1585
- category: "reverb",
1586
- description: "Simple convolution reverb with adjustable decay time",
1587
- parameters: [
1588
- {
1589
- name: "decay",
1590
- label: "Decay",
1591
- type: "number",
1592
- min: 0.1,
1593
- max: 10,
1594
- step: 0.1,
1595
- default: 1.5,
1596
- unit: "s"
1597
- },
1598
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1599
- ]
1600
- },
1601
- {
1602
- id: "freeverb",
1603
- name: "Freeverb",
1604
- category: "reverb",
1605
- description: "Classic Schroeder/Moorer reverb with room size and dampening",
1606
- parameters: [
1607
- {
1608
- name: "roomSize",
1609
- label: "Room Size",
1610
- type: "number",
1611
- min: 0,
1612
- max: 1,
1613
- step: 0.01,
1614
- default: 0.7
1615
- },
1616
- {
1617
- name: "dampening",
1618
- label: "Dampening",
1619
- type: "number",
1620
- min: 0,
1621
- max: 1e4,
1622
- step: 100,
1623
- default: 3e3,
1624
- unit: "Hz"
1625
- },
1626
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1627
- ]
1628
- },
1629
- {
1630
- id: "jcReverb",
1631
- name: "JC Reverb",
1632
- category: "reverb",
1633
- description: "Attempt at Roland JC-120 chorus reverb emulation",
1634
- parameters: [
1635
- {
1636
- name: "roomSize",
1637
- label: "Room Size",
1638
- type: "number",
1639
- min: 0,
1640
- max: 1,
1641
- step: 0.01,
1642
- default: 0.5
1643
- },
1644
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1645
- ]
1646
- },
1647
- // === DELAY EFFECTS ===
1648
- {
1649
- id: "feedbackDelay",
1650
- name: "Feedback Delay",
1651
- category: "delay",
1652
- description: "Delay line with feedback for echo effects",
1653
- parameters: [
1654
- {
1655
- name: "delayTime",
1656
- label: "Delay Time",
1657
- type: "number",
1658
- min: 0,
1659
- max: 1,
1660
- step: 0.01,
1661
- default: 0.25,
1662
- unit: "s"
1663
- },
1664
- {
1665
- name: "feedback",
1666
- label: "Feedback",
1667
- type: "number",
1668
- min: 0,
1669
- max: 0.95,
1670
- step: 0.01,
1671
- default: 0.5
1672
- },
1673
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1674
- ]
1675
- },
1676
- {
1677
- id: "pingPongDelay",
1678
- name: "Ping Pong Delay",
1679
- category: "delay",
1680
- description: "Stereo delay bouncing between left and right channels",
1681
- parameters: [
1682
- {
1683
- name: "delayTime",
1684
- label: "Delay Time",
1685
- type: "number",
1686
- min: 0,
1687
- max: 1,
1688
- step: 0.01,
1689
- default: 0.25,
1690
- unit: "s"
1691
- },
1692
- {
1693
- name: "feedback",
1694
- label: "Feedback",
1695
- type: "number",
1696
- min: 0,
1697
- max: 0.95,
1698
- step: 0.01,
1699
- default: 0.5
1700
- },
1701
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1702
- ]
1703
- },
1704
- // === MODULATION EFFECTS ===
1705
- {
1706
- id: "chorus",
1707
- name: "Chorus",
1708
- category: "modulation",
1709
- description: "Creates thickness by layering detuned copies of the signal",
1710
- parameters: [
1711
- {
1712
- name: "frequency",
1713
- label: "Rate",
1714
- type: "number",
1715
- min: 0.1,
1716
- max: 10,
1717
- step: 0.1,
1718
- default: 1.5,
1719
- unit: "Hz"
1720
- },
1721
- {
1722
- name: "delayTime",
1723
- label: "Delay",
1724
- type: "number",
1725
- min: 0,
1726
- max: 20,
1727
- step: 0.5,
1728
- default: 3.5,
1729
- unit: "ms"
1730
- },
1731
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.7 },
1732
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1733
- ]
1734
- },
1735
- {
1736
- id: "phaser",
1737
- name: "Phaser",
1738
- category: "modulation",
1739
- description: "Classic phaser effect using allpass filters",
1740
- parameters: [
1741
- {
1742
- name: "frequency",
1743
- label: "Rate",
1744
- type: "number",
1745
- min: 0.1,
1746
- max: 10,
1747
- step: 0.1,
1748
- default: 0.5,
1749
- unit: "Hz"
1750
- },
1751
- { name: "octaves", label: "Octaves", type: "number", min: 1, max: 6, step: 1, default: 3 },
1752
- {
1753
- name: "baseFrequency",
1754
- label: "Base Freq",
1755
- type: "number",
1756
- min: 100,
1757
- max: 2e3,
1758
- step: 10,
1759
- default: 350,
1760
- unit: "Hz"
1761
- },
1762
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1763
- ]
1764
- },
1765
- {
1766
- id: "tremolo",
1767
- name: "Tremolo",
1768
- category: "modulation",
1769
- description: "Rhythmic volume modulation",
1770
- parameters: [
1771
- {
1772
- name: "frequency",
1773
- label: "Rate",
1774
- type: "number",
1775
- min: 0.1,
1776
- max: 20,
1777
- step: 0.1,
1778
- default: 4,
1779
- unit: "Hz"
1780
- },
1781
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 },
1782
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1783
- ]
1784
- },
1785
- {
1786
- id: "vibrato",
1787
- name: "Vibrato",
1788
- category: "modulation",
1789
- description: "Pitch modulation effect",
1790
- parameters: [
1791
- {
1792
- name: "frequency",
1793
- label: "Rate",
1794
- type: "number",
1795
- min: 0.1,
1796
- max: 20,
1797
- step: 0.1,
1798
- default: 5,
1799
- unit: "Hz"
1800
- },
1801
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.1 },
1802
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1803
- ]
1804
- },
1805
- {
1806
- id: "autoPanner",
1807
- name: "Auto Panner",
1808
- category: "modulation",
1809
- description: "Automatic left-right panning",
1810
- parameters: [
1811
- {
1812
- name: "frequency",
1813
- label: "Rate",
1814
- type: "number",
1815
- min: 0.1,
1816
- max: 10,
1817
- step: 0.1,
1818
- default: 1,
1819
- unit: "Hz"
1820
- },
1821
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 1 },
1822
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1823
- ]
1824
- },
1825
- // === FILTER EFFECTS ===
1826
- {
1827
- id: "autoFilter",
1828
- name: "Auto Filter",
1829
- category: "filter",
1830
- description: "Automated filter sweep with LFO",
1831
- parameters: [
1832
- {
1833
- name: "frequency",
1834
- label: "Rate",
1835
- type: "number",
1836
- min: 0.1,
1837
- max: 10,
1838
- step: 0.1,
1839
- default: 1,
1840
- unit: "Hz"
1841
- },
1842
- {
1843
- name: "baseFrequency",
1844
- label: "Base Freq",
1845
- type: "number",
1846
- min: 20,
1847
- max: 2e3,
1848
- step: 10,
1849
- default: 200,
1850
- unit: "Hz"
1851
- },
1852
- {
1853
- name: "octaves",
1854
- label: "Octaves",
1855
- type: "number",
1856
- min: 0.5,
1857
- max: 8,
1858
- step: 0.5,
1859
- default: 2.6
1860
- },
1861
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 1 },
1862
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1863
- ]
1864
- },
1865
- {
1866
- id: "autoWah",
1867
- name: "Auto Wah",
1868
- category: "filter",
1869
- description: "Envelope follower filter effect",
1870
- parameters: [
1871
- {
1872
- name: "baseFrequency",
1873
- label: "Base Freq",
1874
- type: "number",
1875
- min: 20,
1876
- max: 500,
1877
- step: 10,
1878
- default: 100,
1879
- unit: "Hz"
1880
- },
1881
- { name: "octaves", label: "Octaves", type: "number", min: 1, max: 8, step: 1, default: 6 },
1882
- {
1883
- name: "sensitivity",
1884
- label: "Sensitivity",
1885
- type: "number",
1886
- min: -40,
1887
- max: 0,
1888
- step: 1,
1889
- default: 0,
1890
- unit: "dB"
1891
- },
1892
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1893
- ]
1894
- },
1895
- {
1896
- id: "eq3",
1897
- name: "3-Band EQ",
1898
- category: "filter",
1899
- description: "Three band equalizer with low, mid, and high controls",
1900
- parameters: [
1901
- {
1902
- name: "low",
1903
- label: "Low",
1904
- type: "number",
1905
- min: -24,
1906
- max: 24,
1907
- step: 0.5,
1908
- default: 0,
1909
- unit: "dB"
1910
- },
1911
- {
1912
- name: "mid",
1913
- label: "Mid",
1914
- type: "number",
1915
- min: -24,
1916
- max: 24,
1917
- step: 0.5,
1918
- default: 0,
1919
- unit: "dB"
1920
- },
1921
- {
1922
- name: "high",
1923
- label: "High",
1924
- type: "number",
1925
- min: -24,
1926
- max: 24,
1927
- step: 0.5,
1928
- default: 0,
1929
- unit: "dB"
1930
- },
1931
- {
1932
- name: "lowFrequency",
1933
- label: "Low Freq",
1934
- type: "number",
1935
- min: 20,
1936
- max: 500,
1937
- step: 10,
1938
- default: 400,
1939
- unit: "Hz"
1940
- },
1941
- {
1942
- name: "highFrequency",
1943
- label: "High Freq",
1944
- type: "number",
1945
- min: 1e3,
1946
- max: 1e4,
1947
- step: 100,
1948
- default: 2500,
1949
- unit: "Hz"
1950
- }
1951
- ]
1952
- },
1953
- // === DISTORTION EFFECTS ===
1954
- {
1955
- id: "distortion",
1956
- name: "Distortion",
1957
- category: "distortion",
1958
- description: "Wave shaping distortion effect",
1959
- parameters: [
1960
- {
1961
- name: "distortion",
1962
- label: "Drive",
1963
- type: "number",
1964
- min: 0,
1965
- max: 1,
1966
- step: 0.01,
1967
- default: 0.4
1968
- },
1969
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1970
- ]
1971
- },
1972
- {
1973
- id: "bitCrusher",
1974
- name: "Bit Crusher",
1975
- category: "distortion",
1976
- description: "Reduces bit depth for lo-fi digital texture",
1977
- parameters: [
1978
- { name: "bits", label: "Bits", type: "number", min: 1, max: 16, step: 1, default: 4 },
1979
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1980
- ]
1981
- },
1982
- {
1983
- id: "chebyshev",
1984
- name: "Chebyshev",
1985
- category: "distortion",
1986
- description: "Waveshaping distortion using Chebyshev polynomials",
1987
- parameters: [
1988
- { name: "order", label: "Order", type: "number", min: 1, max: 100, step: 1, default: 50 },
1989
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1990
- ]
1991
- },
1992
- // === DYNAMICS EFFECTS ===
1993
- {
1994
- id: "compressor",
1995
- name: "Compressor",
1996
- category: "dynamics",
1997
- description: "Dynamic range compressor",
1998
- parameters: [
1999
- {
2000
- name: "threshold",
2001
- label: "Threshold",
2002
- type: "number",
2003
- min: -60,
2004
- max: 0,
2005
- step: 1,
2006
- default: -24,
2007
- unit: "dB"
2008
- },
2009
- { name: "ratio", label: "Ratio", type: "number", min: 1, max: 20, step: 0.5, default: 4 },
2010
- {
2011
- name: "attack",
2012
- label: "Attack",
2013
- type: "number",
2014
- min: 0,
2015
- max: 1,
2016
- step: 1e-3,
2017
- default: 3e-3,
2018
- unit: "s"
2019
- },
2020
- {
2021
- name: "release",
2022
- label: "Release",
2023
- type: "number",
2024
- min: 0,
2025
- max: 1,
2026
- step: 0.01,
2027
- default: 0.25,
2028
- unit: "s"
2029
- },
2030
- {
2031
- name: "knee",
2032
- label: "Knee",
2033
- type: "number",
2034
- min: 0,
2035
- max: 40,
2036
- step: 1,
2037
- default: 30,
2038
- unit: "dB"
2039
- }
2040
- ]
2041
- },
2042
- {
2043
- id: "limiter",
2044
- name: "Limiter",
2045
- category: "dynamics",
2046
- description: "Hard limiter to prevent clipping",
2047
- parameters: [
2048
- {
2049
- name: "threshold",
2050
- label: "Threshold",
2051
- type: "number",
2052
- min: -12,
2053
- max: 0,
2054
- step: 0.5,
2055
- default: -6,
2056
- unit: "dB"
2057
- }
2058
- ]
2059
- },
2060
- {
2061
- id: "gate",
2062
- name: "Gate",
2063
- category: "dynamics",
2064
- description: "Noise gate to silence signal below threshold",
2065
- parameters: [
2066
- {
2067
- name: "threshold",
2068
- label: "Threshold",
2069
- type: "number",
2070
- min: -100,
2071
- max: 0,
2072
- step: 1,
2073
- default: -40,
2074
- unit: "dB"
2075
- },
2076
- {
2077
- name: "attack",
2078
- label: "Attack",
2079
- type: "number",
2080
- min: 0,
2081
- max: 0.3,
2082
- step: 1e-3,
2083
- default: 1e-3,
2084
- unit: "s"
2085
- },
2086
- {
2087
- name: "release",
2088
- label: "Release",
2089
- type: "number",
2090
- min: 0,
2091
- max: 0.5,
2092
- step: 0.01,
2093
- default: 0.1,
2094
- unit: "s"
2095
- }
2096
- ]
2097
- },
2098
- // === SPATIAL EFFECTS ===
2099
- {
2100
- id: "stereoWidener",
2101
- name: "Stereo Widener",
2102
- category: "spatial",
2103
- description: "Expands or narrows the stereo image",
2104
- parameters: [
2105
- { name: "width", label: "Width", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
2106
- ]
2107
- }
2108
- ];
2109
- var getEffectDefinition = (id) => {
2110
- return effectDefinitions.find((def) => def.id === id);
2111
- };
2112
- var getEffectsByCategory = (category) => {
2113
- return effectDefinitions.filter((def) => def.category === category);
2114
- };
2115
- var effectCategories = [
2116
- { id: "reverb", name: "Reverb" },
2117
- { id: "delay", name: "Delay" },
2118
- { id: "modulation", name: "Modulation" },
2119
- { id: "filter", name: "Filter" },
2120
- { id: "distortion", name: "Distortion" },
2121
- { id: "dynamics", name: "Dynamics" },
2122
- { id: "spatial", name: "Spatial" }
2123
- ];
2124
-
2125
- // src/effects/effectFactory.ts
2126
- import {
2127
- Reverb,
2128
- Freeverb,
2129
- JCReverb,
2130
- FeedbackDelay,
2131
- PingPongDelay,
2132
- Chorus,
2133
- Phaser,
2134
- Tremolo,
2135
- Vibrato,
2136
- AutoPanner,
2137
- AutoFilter,
2138
- AutoWah,
2139
- EQ3,
2140
- Distortion,
2141
- BitCrusher,
2142
- Chebyshev,
2143
- Compressor,
2144
- Limiter,
2145
- Gate,
2146
- StereoWidener
2147
- } from "tone";
2148
- function asEffectConstructor(ctor) {
2149
- return ctor;
2150
- }
2151
- var effectConstructors = {
2152
- reverb: asEffectConstructor(Reverb),
2153
- freeverb: asEffectConstructor(Freeverb),
2154
- jcReverb: asEffectConstructor(JCReverb),
2155
- feedbackDelay: asEffectConstructor(FeedbackDelay),
2156
- pingPongDelay: asEffectConstructor(PingPongDelay),
2157
- chorus: asEffectConstructor(Chorus),
2158
- phaser: asEffectConstructor(Phaser),
2159
- tremolo: asEffectConstructor(Tremolo),
2160
- vibrato: asEffectConstructor(Vibrato),
2161
- autoPanner: asEffectConstructor(AutoPanner),
2162
- autoFilter: asEffectConstructor(AutoFilter),
2163
- autoWah: asEffectConstructor(AutoWah),
2164
- eq3: asEffectConstructor(EQ3),
2165
- distortion: asEffectConstructor(Distortion),
2166
- bitCrusher: asEffectConstructor(BitCrusher),
2167
- chebyshev: asEffectConstructor(Chebyshev),
2168
- compressor: asEffectConstructor(Compressor),
2169
- limiter: asEffectConstructor(Limiter),
2170
- gate: asEffectConstructor(Gate),
2171
- stereoWidener: asEffectConstructor(StereoWidener)
2172
- };
2173
- var instanceCounter = 0;
2174
- var generateInstanceId = () => {
2175
- return `effect_${Date.now()}_${++instanceCounter}`;
2176
- };
2177
- function createEffectInstance(definition, initialParams) {
2178
- const Constructor = effectConstructors[definition.id];
2179
- if (!Constructor) {
2180
- throw new Error(`Unknown effect type: ${definition.id}`);
2181
- }
2182
- const options = {};
2183
- definition.parameters.forEach((param) => {
2184
- var _a;
2185
- const value = (_a = initialParams == null ? void 0 : initialParams[param.name]) != null ? _a : param.default;
2186
- options[param.name] = value;
2187
- });
2188
- const effect = new Constructor(options);
2189
- const instanceId = generateInstanceId();
2190
- const effectRecord = effect;
2191
- return {
2192
- effect,
2193
- id: definition.id,
2194
- instanceId,
2195
- dispose() {
2196
- try {
2197
- effect.disconnect();
2198
- effect.dispose();
2199
- } catch (e) {
2200
- console.warn(
2201
- `[waveform-playlist] Error disposing effect "${definition.id}" (${instanceId}):`,
2202
- e
2203
- );
2204
- }
2205
- },
2206
- setParameter(name, value) {
2207
- const prop = effectRecord[name];
2208
- if (name === "wet") {
2209
- const wetProp = effectRecord["wet"];
2210
- if (wetProp && typeof wetProp === "object" && "value" in wetProp) {
2211
- wetProp.value = value;
2212
- return;
2213
- }
2214
- }
2215
- if (prop !== void 0) {
2216
- if (prop && typeof prop === "object" && "value" in prop) {
2217
- prop.value = value;
2218
- } else {
2219
- effectRecord[name] = value;
2220
- }
2221
- }
2222
- },
2223
- getParameter(name) {
2224
- if (name === "wet") {
2225
- const wetProp = effectRecord["wet"];
2226
- if (wetProp && typeof wetProp === "object" && "value" in wetProp) {
2227
- return wetProp.value;
2228
- }
2229
- }
2230
- const prop = effectRecord[name];
2231
- if (prop !== void 0) {
2232
- if (prop && typeof prop === "object" && "value" in prop) {
2233
- return prop.value;
2234
- }
2235
- return prop;
2236
- }
2237
- return void 0;
2238
- },
2239
- connect(destination) {
2240
- effect.connect(destination);
2241
- },
2242
- disconnect() {
2243
- try {
2244
- effect.disconnect();
2245
- } catch (e) {
2246
- console.warn(
2247
- `[waveform-playlist] Error disconnecting effect "${definition.id}" (${instanceId}):`,
2248
- e
2249
- );
2250
- }
2251
- }
2252
- };
2253
- }
2254
- function createEffectChain(effects) {
2255
- if (effects.length === 0) {
2256
- throw new Error("Cannot create effect chain with no effects");
2257
- }
2258
- for (let i = 0; i < effects.length - 1; i++) {
2259
- effects[i].effect.connect(effects[i + 1].effect);
2260
- }
2261
- return {
2262
- input: effects[0].effect,
2263
- output: effects[effects.length - 1].effect,
2264
- dispose() {
2265
- effects.forEach((e) => e.dispose());
2266
- }
2267
- };
2268
- }
2269
-
2270
- // src/hooks/useDynamicEffects.ts
2271
- import { Analyser as Analyser2 } from "tone";
2272
- function useDynamicEffects(fftSize = 256) {
2273
- const [activeEffects, setActiveEffects] = useState9([]);
2274
- const activeEffectsRef = useRef9(activeEffects);
2275
- activeEffectsRef.current = activeEffects;
2276
- const effectInstancesRef = useRef9(/* @__PURE__ */ new Map());
2277
- const analyserRef = useRef9(null);
2278
- const graphNodesRef = useRef9(null);
2279
- const rebuildChain = useCallback12((effects) => {
2280
- const nodes = graphNodesRef.current;
2281
- if (!nodes) return;
2282
- const { masterGainNode, destination, analyserNode } = nodes;
2283
- try {
2284
- masterGainNode.disconnect();
2285
- } catch (e) {
2286
- console.warn("[waveform-playlist] Error disconnecting master effects chain:", e);
2287
- }
2288
- const instances = effects.map((ae) => effectInstancesRef.current.get(ae.instanceId)).filter((inst) => inst !== void 0);
2289
- if (instances.length === 0) {
2290
- masterGainNode.connect(analyserNode);
2291
- analyserNode.connect(destination);
2292
- } else {
2293
- let currentNode = masterGainNode;
2294
- instances.forEach((inst) => {
2295
- try {
2296
- inst.disconnect();
2297
- } catch (e) {
2298
- console.warn(`[waveform-playlist] Error disconnecting effect "${inst.id}":`, e);
2299
- }
2300
- currentNode.connect(inst.effect);
2301
- currentNode = inst.effect;
2302
- });
2303
- currentNode.connect(analyserNode);
2304
- analyserNode.connect(destination);
2305
- }
2306
- }, []);
2307
- const addEffect = useCallback12((effectId) => {
2308
- const definition = getEffectDefinition(effectId);
2309
- if (!definition) {
2310
- console.error(`Unknown effect: ${effectId}`);
2311
- return;
2312
- }
2313
- const params = {};
2314
- definition.parameters.forEach((p) => {
2315
- params[p.name] = p.default;
2316
- });
2317
- const instance = createEffectInstance(definition, params);
2318
- effectInstancesRef.current.set(instance.instanceId, instance);
2319
- const newActiveEffect = {
2320
- instanceId: instance.instanceId,
2321
- effectId: definition.id,
2322
- definition,
2323
- params,
2324
- bypassed: false
2325
- };
2326
- setActiveEffects((prev) => [...prev, newActiveEffect]);
2327
- }, []);
2328
- const removeEffect = useCallback12((instanceId) => {
2329
- const instance = effectInstancesRef.current.get(instanceId);
2330
- if (instance) {
2331
- instance.dispose();
2332
- effectInstancesRef.current.delete(instanceId);
2333
- }
2334
- setActiveEffects((prev) => prev.filter((e) => e.instanceId !== instanceId));
2335
- }, []);
2336
- const updateParameter = useCallback12(
2337
- (instanceId, paramName, value) => {
2338
- const instance = effectInstancesRef.current.get(instanceId);
2339
- if (instance) {
2340
- instance.setParameter(paramName, value);
2341
- }
2342
- setActiveEffects(
2343
- (prev) => prev.map(
2344
- (e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { params: __spreadProps(__spreadValues({}, e.params), { [paramName]: value }) }) : e
2345
- )
2346
- );
2347
- },
2348
- []
2349
- );
2350
- const toggleBypass = useCallback12((instanceId) => {
2351
- var _a;
2352
- const effect = activeEffectsRef.current.find((e) => e.instanceId === instanceId);
2353
- if (!effect) return;
2354
- const newBypassed = !effect.bypassed;
2355
- const instance = effectInstancesRef.current.get(instanceId);
2356
- if (instance) {
2357
- const originalWet = (_a = effect.params.wet) != null ? _a : 1;
2358
- instance.setParameter("wet", newBypassed ? 0 : originalWet);
2359
- }
2360
- setActiveEffects(
2361
- (prev) => prev.map((e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { bypassed: newBypassed }) : e)
2362
- );
2363
- }, []);
2364
- const reorderEffects = useCallback12((fromIndex, toIndex) => {
2365
- setActiveEffects((prev) => {
2366
- const newEffects = [...prev];
2367
- const [removed] = newEffects.splice(fromIndex, 1);
2368
- newEffects.splice(toIndex, 0, removed);
2369
- return newEffects;
2370
- });
2371
- }, []);
2372
- const clearAllEffects = useCallback12(() => {
2373
- effectInstancesRef.current.forEach((inst) => inst.dispose());
2374
- effectInstancesRef.current.clear();
2375
- setActiveEffects([]);
2376
- }, []);
2377
- useEffect4(() => {
2378
- rebuildChain(activeEffects);
2379
- }, [activeEffects, rebuildChain]);
2380
- const masterEffects = useCallback12(
2381
- (masterGainNode, destination, _isOffline) => {
2382
- const analyserNode = new Analyser2("fft", fftSize);
2383
- analyserRef.current = analyserNode;
2384
- graphNodesRef.current = {
2385
- masterGainNode,
2386
- destination,
2387
- analyserNode
2388
- };
2389
- const effects = activeEffectsRef.current;
2390
- const instances = effects.map((ae) => effectInstancesRef.current.get(ae.instanceId)).filter((inst) => inst !== void 0);
2391
- if (instances.length === 0) {
2392
- masterGainNode.connect(analyserNode);
2393
- analyserNode.connect(destination);
2394
- } else {
2395
- let currentNode = masterGainNode;
2396
- instances.forEach((inst) => {
2397
- currentNode.connect(inst.effect);
2398
- currentNode = inst.effect;
2399
- });
2400
- currentNode.connect(analyserNode);
2401
- analyserNode.connect(destination);
2402
- }
2403
- return function cleanup() {
2404
- analyserNode.dispose();
2405
- analyserRef.current = null;
2406
- graphNodesRef.current = null;
2407
- };
2408
- },
2409
- [fftSize]
2410
- // Only fftSize - reads effects from ref
2411
- );
2412
- useEffect4(() => {
2413
- const effectInstances = effectInstancesRef.current;
2414
- return () => {
2415
- effectInstances.forEach((inst) => inst.dispose());
2416
- effectInstances.clear();
2417
- };
2418
- }, []);
2419
- const createOfflineEffectsFunction = useCallback12(() => {
2420
- const nonBypassedEffects = activeEffects.filter((e) => !e.bypassed);
2421
- if (nonBypassedEffects.length === 0) {
2422
- return void 0;
2423
- }
2424
- return (masterGainNode, destination, _isOffline) => {
2425
- const offlineInstances = [];
2426
- for (const activeEffect of nonBypassedEffects) {
2427
- const instance = createEffectInstance(activeEffect.definition, activeEffect.params);
2428
- offlineInstances.push(instance);
2429
- }
2430
- if (offlineInstances.length === 0) {
2431
- masterGainNode.connect(destination);
2432
- } else {
2433
- let currentNode = masterGainNode;
2434
- offlineInstances.forEach((inst) => {
2435
- currentNode.connect(inst.effect);
2436
- currentNode = inst.effect;
2437
- });
2438
- currentNode.connect(destination);
2439
- }
2440
- return function cleanup() {
2441
- offlineInstances.forEach((inst) => inst.dispose());
2442
- };
2443
- };
2444
- }, [activeEffects]);
2445
- return {
2446
- activeEffects,
2447
- availableEffects: effectDefinitions,
2448
- addEffect,
2449
- removeEffect,
2450
- updateParameter,
2451
- toggleBypass,
2452
- reorderEffects,
2453
- clearAllEffects,
2454
- masterEffects,
2455
- createOfflineEffectsFunction,
2456
- analyserRef
2457
- };
2458
- }
2459
-
2460
- // src/hooks/useTrackDynamicEffects.ts
2461
- import { useState as useState10, useCallback as useCallback13, useRef as useRef10, useEffect as useEffect5 } from "react";
2462
- function useTrackDynamicEffects() {
2463
- const [trackEffectsState, setTrackEffectsState] = useState10(
2464
- /* @__PURE__ */ new Map()
2465
- );
2466
- const trackEffectInstancesRef = useRef10(/* @__PURE__ */ new Map());
2467
- const trackGraphNodesRef = useRef10(/* @__PURE__ */ new Map());
2468
- const rebuildTrackChain = useCallback13((trackId, trackEffects) => {
2469
- const nodes = trackGraphNodesRef.current.get(trackId);
2470
- if (!nodes) return;
2471
- const { graphEnd, masterGainNode } = nodes;
2472
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2473
- try {
2474
- graphEnd.disconnect();
2475
- } catch (e) {
2476
- console.warn(`[waveform-playlist] Error disconnecting track "${trackId}" effect chain:`, e);
2477
- }
2478
- const instances = trackEffects.map((ae) => instancesMap == null ? void 0 : instancesMap.get(ae.instanceId)).filter((inst) => inst !== void 0);
2479
- if (instances.length === 0) {
2480
- graphEnd.connect(masterGainNode);
2481
- } else {
2482
- let currentNode = graphEnd;
2483
- instances.forEach((inst) => {
2484
- try {
2485
- inst.disconnect();
2486
- } catch (e) {
2487
- console.warn(
2488
- `[waveform-playlist] Error disconnecting effect "${inst.id}" on track "${trackId}":`,
2489
- e
2490
- );
2491
- }
2492
- currentNode.connect(inst.effect);
2493
- currentNode = inst.effect;
2494
- });
2495
- currentNode.connect(masterGainNode);
2496
- }
2497
- }, []);
2498
- const addEffectToTrack = useCallback13((trackId, effectId) => {
2499
- const definition = getEffectDefinition(effectId);
2500
- if (!definition) {
2501
- console.error(`Unknown effect: ${effectId}`);
2502
- return;
2503
- }
2504
- const params = {};
2505
- definition.parameters.forEach((p) => {
2506
- params[p.name] = p.default;
2507
- });
2508
- const instance = createEffectInstance(definition, params);
2509
- if (!trackEffectInstancesRef.current.has(trackId)) {
2510
- trackEffectInstancesRef.current.set(trackId, /* @__PURE__ */ new Map());
2511
- }
2512
- trackEffectInstancesRef.current.get(trackId).set(instance.instanceId, instance);
2513
- const newActiveEffect = {
2514
- instanceId: instance.instanceId,
2515
- effectId: definition.id,
2516
- definition,
2517
- params,
2518
- bypassed: false
2519
- };
2520
- setTrackEffectsState((prev) => {
2521
- const newState = new Map(prev);
2522
- const existing = newState.get(trackId) || [];
2523
- newState.set(trackId, [...existing, newActiveEffect]);
2524
- return newState;
2525
- });
2526
- }, []);
2527
- const removeEffectFromTrack = useCallback13((trackId, instanceId) => {
2528
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2529
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2530
- if (instance) {
2531
- instance.dispose();
2532
- instancesMap == null ? void 0 : instancesMap.delete(instanceId);
2533
- }
2534
- setTrackEffectsState((prev) => {
2535
- const newState = new Map(prev);
2536
- const existing = newState.get(trackId) || [];
2537
- newState.set(
2538
- trackId,
2539
- existing.filter((e) => e.instanceId !== instanceId)
2540
- );
2541
- return newState;
2542
- });
2543
- }, []);
2544
- const updateTrackEffectParameter = useCallback13(
2545
- (trackId, instanceId, paramName, value) => {
2546
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2547
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2548
- if (instance) {
2549
- instance.setParameter(paramName, value);
2550
- }
2551
- setTrackEffectsState((prev) => {
2552
- const newState = new Map(prev);
2553
- const existing = newState.get(trackId) || [];
2554
- newState.set(
2555
- trackId,
2556
- existing.map(
2557
- (e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { params: __spreadProps(__spreadValues({}, e.params), { [paramName]: value }) }) : e
2558
- )
2559
- );
2560
- return newState;
2561
- });
2562
- },
2563
- []
2564
- );
2565
- const toggleBypass = useCallback13((trackId, instanceId) => {
2566
- var _a;
2567
- const trackEffects = trackEffectsStateRef.current.get(trackId) || [];
2568
- const effect = trackEffects.find((e) => e.instanceId === instanceId);
2569
- if (!effect) return;
2570
- const newBypassed = !effect.bypassed;
2571
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2572
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2573
- if (instance) {
2574
- const originalWet = (_a = effect.params.wet) != null ? _a : 1;
2575
- instance.setParameter("wet", newBypassed ? 0 : originalWet);
2576
- }
2577
- setTrackEffectsState((prev) => {
2578
- const newState = new Map(prev);
2579
- const existing = newState.get(trackId) || [];
2580
- newState.set(
2581
- trackId,
2582
- existing.map((e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { bypassed: newBypassed }) : e)
2583
- );
2584
- return newState;
2585
- });
2586
- }, []);
2587
- const clearTrackEffects = useCallback13((trackId) => {
2588
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2589
- if (instancesMap) {
2590
- instancesMap.forEach((inst) => inst.dispose());
2591
- instancesMap.clear();
2592
- }
2593
- setTrackEffectsState((prev) => {
2594
- const newState = new Map(prev);
2595
- newState.set(trackId, []);
2596
- return newState;
2597
- });
2598
- }, []);
2599
- const trackEffectsStateRef = useRef10(trackEffectsState);
2600
- trackEffectsStateRef.current = trackEffectsState;
2601
- const getTrackEffectsFunction = useCallback13(
2602
- (trackId) => {
2603
- return (graphEnd, masterGainNode, _isOffline) => {
2604
- trackGraphNodesRef.current.set(trackId, {
2605
- graphEnd,
2606
- masterGainNode
2607
- });
2608
- const trackEffects = trackEffectsStateRef.current.get(trackId) || [];
2609
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2610
- const instances = trackEffects.map((ae) => instancesMap == null ? void 0 : instancesMap.get(ae.instanceId)).filter((inst) => inst !== void 0);
2611
- if (instances.length === 0) {
2612
- graphEnd.connect(masterGainNode);
2613
- } else {
2614
- let currentNode = graphEnd;
2615
- instances.forEach((inst) => {
2616
- currentNode.connect(inst.effect);
2617
- currentNode = inst.effect;
2618
- });
2619
- currentNode.connect(masterGainNode);
2620
- }
2621
- return function cleanup() {
2622
- trackGraphNodesRef.current.delete(trackId);
2623
- };
2624
- };
2625
- },
2626
- []
2627
- // No dependencies - stable function that reads from refs
2628
- );
2629
- useEffect5(() => {
2630
- trackEffectsState.forEach((effects, trackId) => {
2631
- rebuildTrackChain(trackId, effects);
2632
- });
2633
- }, [trackEffectsState, rebuildTrackChain]);
2634
- useEffect5(() => {
2635
- const trackEffectInstances = trackEffectInstancesRef.current;
2636
- return () => {
2637
- trackEffectInstances.forEach((instancesMap) => {
2638
- instancesMap.forEach((inst) => inst.dispose());
2639
- instancesMap.clear();
2640
- });
2641
- trackEffectInstances.clear();
2642
- };
2643
- }, []);
2644
- const createOfflineTrackEffectsFunction = useCallback13(
2645
- (trackId) => {
2646
- const trackEffects = trackEffectsState.get(trackId) || [];
2647
- const nonBypassedEffects = trackEffects.filter((e) => !e.bypassed);
2648
- if (nonBypassedEffects.length === 0) {
2649
- return void 0;
2650
- }
2651
- return (graphEnd, masterGainNode, _isOffline) => {
2652
- const offlineInstances = [];
2653
- for (const activeEffect of nonBypassedEffects) {
2654
- const instance = createEffectInstance(activeEffect.definition, activeEffect.params);
2655
- offlineInstances.push(instance);
2656
- }
2657
- if (offlineInstances.length === 0) {
2658
- graphEnd.connect(masterGainNode);
2659
- } else {
2660
- let currentNode = graphEnd;
2661
- offlineInstances.forEach((inst) => {
2662
- currentNode.connect(inst.effect);
2663
- currentNode = inst.effect;
2664
- });
2665
- currentNode.connect(masterGainNode);
2666
- }
2667
- return function cleanup() {
2668
- offlineInstances.forEach((inst) => inst.dispose());
2669
- };
2670
- };
2671
- },
2672
- [trackEffectsState]
2673
- );
2674
- return {
2675
- trackEffectsState,
2676
- addEffectToTrack,
2677
- removeEffectFromTrack,
2678
- updateTrackEffectParameter,
2679
- toggleBypass,
2680
- clearTrackEffects,
2681
- getTrackEffectsFunction,
2682
- createOfflineTrackEffectsFunction,
2683
- availableEffects: effectDefinitions
2684
- };
2685
- }
2686
-
2687
- // src/hooks/useExportWav.ts
2688
- import { useState as useState11, useCallback as useCallback14 } from "react";
2689
- import {
2690
- gainToDb,
2691
- trackChannelCount,
2692
- applyFadeIn,
2693
- applyFadeOut
2694
- } from "@waveform-playlist/core";
2695
- import {
2696
- getUnderlyingAudioParam,
2697
- getGlobalAudioContext as getGlobalAudioContext2
2698
- } from "@waveform-playlist/playout";
2699
-
2700
- // src/utils/wavEncoder.ts
2701
- function encodeWav(audioBuffer, options = {}) {
2702
- const { bitDepth = 16 } = options;
2703
- const numChannels = audioBuffer.numberOfChannels;
2704
- const sampleRate = audioBuffer.sampleRate;
2705
- const numSamples = audioBuffer.length;
2706
- const bytesPerSample = bitDepth / 8;
2707
- const blockAlign = numChannels * bytesPerSample;
2708
- const byteRate = sampleRate * blockAlign;
2709
- const dataSize = numSamples * blockAlign;
2710
- const headerSize = 44;
2711
- const totalSize = headerSize + dataSize;
2712
- const buffer = new ArrayBuffer(totalSize);
2713
- const view = new DataView(buffer);
2714
- writeString(view, 0, "RIFF");
2715
- view.setUint32(4, totalSize - 8, true);
2716
- writeString(view, 8, "WAVE");
2717
- writeString(view, 12, "fmt ");
2718
- view.setUint32(16, 16, true);
2719
- view.setUint16(20, bitDepth === 32 ? 3 : 1, true);
2720
- view.setUint16(22, numChannels, true);
2721
- view.setUint32(24, sampleRate, true);
2722
- view.setUint32(28, byteRate, true);
2723
- view.setUint16(32, blockAlign, true);
2724
- view.setUint16(34, bitDepth, true);
2725
- writeString(view, 36, "data");
2726
- view.setUint32(40, dataSize, true);
2727
- const channelData = [];
2728
- for (let ch = 0; ch < numChannels; ch++) {
2729
- channelData.push(audioBuffer.getChannelData(ch));
2730
- }
2731
- let offset = headerSize;
2732
- if (bitDepth === 16) {
2733
- for (let i = 0; i < numSamples; i++) {
2734
- for (let ch = 0; ch < numChannels; ch++) {
2735
- const sample = channelData[ch][i];
2736
- const clampedSample = Math.max(-1, Math.min(1, sample));
2737
- const intSample = clampedSample < 0 ? clampedSample * 32768 : clampedSample * 32767;
2738
- view.setInt16(offset, intSample, true);
2739
- offset += 2;
2740
- }
2741
- }
2742
- } else {
2743
- for (let i = 0; i < numSamples; i++) {
2744
- for (let ch = 0; ch < numChannels; ch++) {
2745
- view.setFloat32(offset, channelData[ch][i], true);
2746
- offset += 4;
2747
- }
2748
- }
2749
- }
2750
- return new Blob([buffer], { type: "audio/wav" });
2751
- }
2752
- function writeString(view, offset, str) {
2753
- for (let i = 0; i < str.length; i++) {
2754
- view.setUint8(offset + i, str.charCodeAt(i));
2755
- }
2756
- }
2757
- function downloadBlob(blob, filename) {
2758
- const url = URL.createObjectURL(blob);
2759
- const a = document.createElement("a");
2760
- a.href = url;
2761
- a.download = filename;
2762
- a.style.display = "none";
2763
- document.body.appendChild(a);
2764
- a.click();
2765
- document.body.removeChild(a);
2766
- URL.revokeObjectURL(url);
2767
- }
2768
-
2769
- // src/hooks/useExportWav.ts
2770
- function useExportWav() {
2771
- const [isExporting, setIsExporting] = useState11(false);
2772
- const [progress, setProgress] = useState11(0);
2773
- const [error, setError] = useState11(null);
2774
- const exportWav = useCallback14(
2775
- (_0, _1, ..._2) => __async(null, [_0, _1, ..._2], function* (tracks, trackStates, options = {}) {
2776
- const {
2777
- filename = "export",
2778
- mode = "master",
2779
- trackIndex,
2780
- autoDownload = true,
2781
- applyEffects = true,
2782
- effectsFunction,
2783
- createOfflineTrackEffects,
2784
- bitDepth = 16,
2785
- onProgress
2786
- } = options;
2787
- setIsExporting(true);
2788
- setProgress(0);
2789
- setError(null);
2790
- try {
2791
- if (tracks.length === 0) {
2792
- throw new Error("No tracks to export");
2793
- }
2794
- if (mode === "individual" && (trackIndex === void 0 || trackIndex < 0 || trackIndex >= tracks.length)) {
2795
- throw new Error("Invalid track index for individual export");
2796
- }
2797
- const sampleRate = getGlobalAudioContext2().sampleRate;
2798
- let totalDurationSamples = 0;
2799
- for (const track of tracks) {
2800
- for (const clip of track.clips) {
2801
- const clipEndSample = clip.startSample + clip.durationSamples;
2802
- totalDurationSamples = Math.max(totalDurationSamples, clipEndSample);
2803
- }
2804
- }
2805
- totalDurationSamples += Math.round(sampleRate * 0.1);
2806
- const duration = totalDurationSamples / sampleRate;
2807
- const tracksToRender = mode === "individual" ? [{ track: tracks[trackIndex], state: trackStates[trackIndex], index: trackIndex }] : tracks.map((track, index) => ({ track, state: trackStates[index], index }));
2808
- const hasSolo = mode === "master" && trackStates.some((state) => state.soloed);
2809
- const reportProgress = (p) => {
2810
- setProgress(p);
2811
- onProgress == null ? void 0 : onProgress(p);
2812
- };
2813
- const renderedBuffer = yield renderOffline(
2814
- tracksToRender,
2815
- hasSolo,
2816
- duration,
2817
- sampleRate,
2818
- applyEffects,
2819
- effectsFunction,
2820
- createOfflineTrackEffects,
2821
- reportProgress
2822
- );
2823
- reportProgress(0.9);
2824
- const blob = encodeWav(renderedBuffer, { bitDepth });
2825
- reportProgress(1);
2826
- if (autoDownload) {
2827
- const exportFilename = mode === "individual" ? `${filename}_${tracks[trackIndex].name}` : filename;
2828
- downloadBlob(blob, `${exportFilename}.wav`);
2829
- }
2830
- return {
2831
- audioBuffer: renderedBuffer,
2832
- blob,
2833
- duration
2834
- };
2835
- } catch (err) {
2836
- const message = err instanceof Error ? err.message : "Export failed";
2837
- setError(message);
2838
- throw err;
2839
- } finally {
2840
- setIsExporting(false);
1348
+ action: selectLast,
1349
+ description: "Select last annotation",
1350
+ preventDefault: true
1351
+ },
1352
+ {
1353
+ key: "Escape",
1354
+ action: clearSelection,
1355
+ description: "Deselect annotation",
1356
+ preventDefault: true
2841
1357
  }
2842
- }),
2843
- []
1358
+ ],
1359
+ [selectPrevious, selectNext, selectFirst, selectLast, clearSelection]
2844
1360
  );
1361
+ useKeyboardShortcuts({
1362
+ shortcuts: activeAnnotationShortcuts,
1363
+ enabled: enabled && activeIndex >= 0
1364
+ });
1365
+ useKeyboardShortcuts({
1366
+ shortcuts: navigationShortcuts,
1367
+ enabled: enabled && annotations.length > 0 && !!onActiveAnnotationChange
1368
+ });
2845
1369
  return {
2846
- exportWav,
2847
- isExporting,
2848
- progress,
2849
- error
1370
+ moveStartBoundary,
1371
+ moveEndBoundary,
1372
+ selectPrevious,
1373
+ selectNext,
1374
+ selectFirst,
1375
+ selectLast,
1376
+ clearSelection,
1377
+ scrollToAnnotation,
1378
+ playActiveAnnotation
2850
1379
  };
2851
1380
  }
2852
- function renderOffline(tracksToRender, hasSolo, duration, sampleRate, applyEffects, effectsFunction, createOfflineTrackEffects, onProgress) {
2853
- return __async(this, null, function* () {
2854
- const { Offline, Volume: Volume2, Gain, Panner, Player, ToneAudioBuffer } = yield import("tone");
2855
- onProgress(0.1);
2856
- const audibleTracks = tracksToRender.filter(({ state }) => {
2857
- if (state.muted && !state.soloed) return false;
2858
- if (hasSolo && !state.soloed) return false;
2859
- return true;
2860
- });
2861
- const outputChannels = audibleTracks.reduce(
2862
- (max, { track }) => Math.max(max, trackChannelCount(track)),
2863
- 1
2864
- );
2865
- let buffer;
2866
- try {
2867
- buffer = yield Offline(
2868
- (_0) => __async(null, [_0], function* ({ transport, destination }) {
2869
- const masterVolume = new Volume2(0);
2870
- if (effectsFunction && applyEffects) {
2871
- effectsFunction(masterVolume, destination, true);
2872
- } else {
2873
- masterVolume.connect(destination);
2874
- }
2875
- for (const { track, state } of audibleTracks) {
2876
- const trackVolume = new Volume2(gainToDb(state.volume));
2877
- const trackPan = new Panner({ pan: state.pan, channelCount: trackChannelCount(track) });
2878
- const trackMute = new Gain(state.muted ? 0 : 1);
2879
- const trackEffects = createOfflineTrackEffects == null ? void 0 : createOfflineTrackEffects(track.id);
2880
- if (trackEffects && applyEffects) {
2881
- trackEffects(trackMute, masterVolume, true);
2882
- } else {
2883
- trackMute.connect(masterVolume);
2884
- }
2885
- trackPan.connect(trackMute);
2886
- trackVolume.connect(trackPan);
2887
- for (const clip of track.clips) {
2888
- const {
2889
- audioBuffer,
2890
- startSample,
2891
- durationSamples,
2892
- offsetSamples,
2893
- gain: clipGain,
2894
- fadeIn,
2895
- fadeOut
2896
- } = clip;
2897
- if (!audioBuffer) {
2898
- console.warn(
2899
- '[waveform-playlist] Skipping clip "' + (clip.name || clip.id) + '" - no audioBuffer for export'
2900
- );
2901
- continue;
2902
- }
2903
- const startTime = startSample / sampleRate;
2904
- const clipDuration = durationSamples / sampleRate;
2905
- const offset = offsetSamples / sampleRate;
2906
- const toneBuffer = new ToneAudioBuffer(audioBuffer);
2907
- const player = new Player(toneBuffer);
2908
- const fadeGain = new Gain(clipGain);
2909
- player.connect(fadeGain);
2910
- fadeGain.connect(trackVolume);
2911
- if (applyEffects) {
2912
- const audioParam = getUnderlyingAudioParam(fadeGain.gain);
2913
- if (audioParam) {
2914
- applyClipFades(audioParam, clipGain, startTime, clipDuration, fadeIn, fadeOut);
2915
- } else if (fadeIn || fadeOut) {
2916
- console.warn(
2917
- '[waveform-playlist] Cannot apply fades for clip "' + (clip.name || clip.id) + '" - AudioParam not accessible'
2918
- );
2919
- }
2920
- }
2921
- player.start(startTime, offset, clipDuration);
2922
- }
2923
- }
2924
- transport.start(0);
2925
- }),
2926
- duration,
2927
- outputChannels,
2928
- sampleRate
2929
- );
2930
- } catch (err) {
2931
- if (err instanceof Error) {
2932
- throw err;
2933
- } else {
2934
- throw new Error("Tone.Offline rendering failed: " + String(err));
2935
- }
2936
- }
2937
- onProgress(0.9);
2938
- const result = buffer.get();
2939
- if (!result) {
2940
- throw new Error("Offline rendering produced no audio buffer");
2941
- }
2942
- return result;
2943
- });
2944
- }
2945
- function applyClipFades(gainParam, clipGain, startTime, clipDuration, fadeIn, fadeOut) {
2946
- if (fadeIn) {
2947
- gainParam.setValueAtTime(0, startTime);
2948
- } else {
2949
- gainParam.setValueAtTime(clipGain, startTime);
2950
- }
2951
- if (fadeIn) {
2952
- applyFadeIn(gainParam, startTime, fadeIn.duration, fadeIn.type || "linear", 0, clipGain);
2953
- }
2954
- if (fadeOut) {
2955
- const fadeOutStart = startTime + clipDuration - fadeOut.duration;
2956
- if (!fadeIn || fadeIn.duration < clipDuration - fadeOut.duration) {
2957
- gainParam.setValueAtTime(clipGain, fadeOutStart);
2958
- }
2959
- applyFadeOut(gainParam, fadeOutStart, fadeOut.duration, fadeOut.type || "linear", clipGain, 0);
2960
- }
2961
- }
2962
1381
 
2963
1382
  // src/hooks/useAnimationFrameLoop.ts
2964
- import { useCallback as useCallback15, useEffect as useEffect6, useRef as useRef11 } from "react";
1383
+ import { useCallback as useCallback11, useEffect as useEffect3, useRef as useRef7 } from "react";
2965
1384
  var useAnimationFrameLoop = () => {
2966
- const animationFrameRef = useRef11(null);
2967
- const stopAnimationFrameLoop = useCallback15(() => {
1385
+ const animationFrameRef = useRef7(null);
1386
+ const stopAnimationFrameLoop = useCallback11(() => {
2968
1387
  if (animationFrameRef.current !== null) {
2969
1388
  cancelAnimationFrame(animationFrameRef.current);
2970
1389
  animationFrameRef.current = null;
2971
1390
  }
2972
1391
  }, []);
2973
- const startAnimationFrameLoop = useCallback15(
1392
+ const startAnimationFrameLoop = useCallback11(
2974
1393
  (callback) => {
2975
1394
  stopAnimationFrameLoop();
2976
1395
  animationFrameRef.current = requestAnimationFrame(callback);
2977
1396
  },
2978
1397
  [stopAnimationFrameLoop]
2979
1398
  );
2980
- useEffect6(() => {
1399
+ useEffect3(() => {
2981
1400
  return () => {
2982
1401
  stopAnimationFrameLoop();
2983
1402
  };
@@ -2990,7 +1409,7 @@ var useAnimationFrameLoop = () => {
2990
1409
  };
2991
1410
 
2992
1411
  // src/hooks/useWaveformDataCache.ts
2993
- import { useState as useState12, useEffect as useEffect7, useRef as useRef12, useCallback as useCallback16 } from "react";
1412
+ import { useState as useState8, useEffect as useEffect4, useRef as useRef8, useCallback as useCallback12 } from "react";
2994
1413
 
2995
1414
  // src/workers/peaksWorker.ts
2996
1415
  import WaveformData2 from "waveform-data";
@@ -3222,20 +1641,20 @@ function createPeaksWorker() {
3222
1641
 
3223
1642
  // src/hooks/useWaveformDataCache.ts
3224
1643
  function useWaveformDataCache(tracks, baseScale) {
3225
- const [cache, setCache] = useState12(() => /* @__PURE__ */ new Map());
3226
- const [isGenerating, setIsGenerating] = useState12(false);
3227
- const workerRef = useRef12(null);
3228
- const generatedByBufferRef = useRef12(/* @__PURE__ */ new WeakMap());
3229
- const inflightByBufferRef = useRef12(/* @__PURE__ */ new WeakMap());
3230
- const subscribersByBufferRef = useRef12(/* @__PURE__ */ new WeakMap());
3231
- const pendingCountRef = useRef12(0);
3232
- const getWorker = useCallback16(() => {
1644
+ const [cache, setCache] = useState8(() => /* @__PURE__ */ new Map());
1645
+ const [isGenerating, setIsGenerating] = useState8(false);
1646
+ const workerRef = useRef8(null);
1647
+ const generatedByBufferRef = useRef8(/* @__PURE__ */ new WeakMap());
1648
+ const inflightByBufferRef = useRef8(/* @__PURE__ */ new WeakMap());
1649
+ const subscribersByBufferRef = useRef8(/* @__PURE__ */ new WeakMap());
1650
+ const pendingCountRef = useRef8(0);
1651
+ const getWorker = useCallback12(() => {
3233
1652
  if (!workerRef.current) {
3234
1653
  workerRef.current = createPeaksWorker();
3235
1654
  }
3236
1655
  return workerRef.current;
3237
1656
  }, []);
3238
- useEffect7(() => {
1657
+ useEffect4(() => {
3239
1658
  let cancelled = false;
3240
1659
  const generatedByBuffer = generatedByBufferRef.current;
3241
1660
  const inflightByBuffer = inflightByBufferRef.current;
@@ -3340,7 +1759,7 @@ function useWaveformDataCache(tracks, baseScale) {
3340
1759
  setIsGenerating(false);
3341
1760
  };
3342
1761
  }, [tracks, baseScale, getWorker]);
3343
- useEffect7(() => {
1762
+ useEffect4(() => {
3344
1763
  return () => {
3345
1764
  var _a;
3346
1765
  (_a = workerRef.current) == null ? void 0 : _a.terminate();
@@ -3350,226 +1769,6 @@ function useWaveformDataCache(tracks, baseScale) {
3350
1769
  return { cache, isGenerating };
3351
1770
  }
3352
1771
 
3353
- // src/hooks/useDynamicTracks.ts
3354
- import { useState as useState13, useCallback as useCallback17, useRef as useRef13, useEffect as useEffect8 } from "react";
3355
- import { createTrack as createTrack2, createClipFromSeconds as createClipFromSeconds2 } from "@waveform-playlist/core";
3356
- import { getGlobalAudioContext as getGlobalAudioContext3 } from "@waveform-playlist/playout";
3357
- function getSourceName(source) {
3358
- var _a, _b, _c, _d, _e;
3359
- if (source instanceof File) {
3360
- return source.name.replace(/\.[^/.]+$/, "");
3361
- }
3362
- if (source instanceof Blob) {
3363
- return "Untitled";
3364
- }
3365
- if (typeof source === "string") {
3366
- return (_b = (_a = source.split("/").pop()) == null ? void 0 : _a.replace(/\.[^/.]+$/, "")) != null ? _b : "Untitled";
3367
- }
3368
- return (_e = (_d = source.name) != null ? _d : (_c = source.src.split("/").pop()) == null ? void 0 : _c.replace(/\.[^/.]+$/, "")) != null ? _e : "Untitled";
3369
- }
3370
- function decodeSource(source, audioContext, signal) {
3371
- return __async(this, null, function* () {
3372
- const name = getSourceName(source);
3373
- if (source instanceof Blob) {
3374
- const arrayBuffer2 = yield source.arrayBuffer();
3375
- signal == null ? void 0 : signal.throwIfAborted();
3376
- const audioBuffer2 = yield audioContext.decodeAudioData(arrayBuffer2);
3377
- return { audioBuffer: audioBuffer2, name };
3378
- }
3379
- const url = typeof source === "string" ? source : source.src;
3380
- const response = yield fetch(url, { signal });
3381
- if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
3382
- const arrayBuffer = yield response.arrayBuffer();
3383
- signal == null ? void 0 : signal.throwIfAborted();
3384
- const audioBuffer = yield audioContext.decodeAudioData(arrayBuffer);
3385
- return { audioBuffer, name };
3386
- });
3387
- }
3388
- function useDynamicTracks() {
3389
- const [tracks, setTracks] = useState13([]);
3390
- const [loadingCount, setLoadingCount] = useState13(0);
3391
- const [errors, setErrors] = useState13([]);
3392
- const cancelledRef = useRef13(false);
3393
- const loadingIdsRef = useRef13(/* @__PURE__ */ new Set());
3394
- const abortControllersRef = useRef13(/* @__PURE__ */ new Map());
3395
- useEffect8(() => {
3396
- const controllers = abortControllersRef.current;
3397
- return () => {
3398
- cancelledRef.current = true;
3399
- for (const controller of controllers.values()) {
3400
- controller.abort();
3401
- }
3402
- controllers.clear();
3403
- };
3404
- }, []);
3405
- const addTracks = useCallback17((sources) => {
3406
- if (sources.length === 0) return;
3407
- const audioContext = getGlobalAudioContext3();
3408
- const placeholders = sources.map((source) => ({
3409
- track: createTrack2({ name: `${getSourceName(source)} (loading...)`, clips: [] }),
3410
- source
3411
- }));
3412
- setTracks((prev) => [...prev, ...placeholders.map((p) => p.track)]);
3413
- setLoadingCount((prev) => prev + sources.length);
3414
- for (const { track, source } of placeholders) {
3415
- loadingIdsRef.current.add(track.id);
3416
- const controller = new AbortController();
3417
- abortControllersRef.current.set(track.id, controller);
3418
- (() => __async(null, null, function* () {
3419
- try {
3420
- const { audioBuffer, name } = yield decodeSource(source, audioContext, controller.signal);
3421
- const clip = createClipFromSeconds2({
3422
- audioBuffer,
3423
- startTime: 0,
3424
- duration: audioBuffer.duration,
3425
- offset: 0,
3426
- name
3427
- });
3428
- if (!cancelledRef.current && loadingIdsRef.current.has(track.id)) {
3429
- setTracks(
3430
- (prev) => prev.map((t) => t.id === track.id ? __spreadProps(__spreadValues({}, t), { name, clips: [clip] }) : t)
3431
- );
3432
- }
3433
- } catch (error) {
3434
- if (error instanceof DOMException && error.name === "AbortError") return;
3435
- console.warn("[waveform-playlist] Error loading audio:", error);
3436
- if (!cancelledRef.current && loadingIdsRef.current.has(track.id)) {
3437
- setTracks((prev) => prev.filter((t) => t.id !== track.id));
3438
- setErrors((prev) => [
3439
- ...prev,
3440
- {
3441
- name: getSourceName(source),
3442
- error: error instanceof Error ? error : new Error(String(error))
3443
- }
3444
- ]);
3445
- }
3446
- } finally {
3447
- abortControllersRef.current.delete(track.id);
3448
- if (!cancelledRef.current && loadingIdsRef.current.delete(track.id)) {
3449
- setLoadingCount((prev) => prev - 1);
3450
- }
3451
- }
3452
- }))();
3453
- }
3454
- }, []);
3455
- const removeTrack = useCallback17((trackId) => {
3456
- setTracks((prev) => prev.filter((t) => t.id !== trackId));
3457
- const controller = abortControllersRef.current.get(trackId);
3458
- if (controller) {
3459
- controller.abort();
3460
- abortControllersRef.current.delete(trackId);
3461
- }
3462
- if (loadingIdsRef.current.delete(trackId)) {
3463
- setLoadingCount((prev) => prev - 1);
3464
- }
3465
- }, []);
3466
- return {
3467
- tracks,
3468
- addTracks,
3469
- removeTrack,
3470
- loadingCount,
3471
- isLoading: loadingCount > 0,
3472
- errors
3473
- };
3474
- }
3475
-
3476
- // src/hooks/useOutputMeter.ts
3477
- import { useEffect as useEffect9, useState as useState14, useRef as useRef14, useCallback as useCallback18 } from "react";
3478
- import { getGlobalContext } from "@waveform-playlist/playout";
3479
- import { gainToNormalized } from "@waveform-playlist/core";
3480
- import { meterProcessorUrl } from "@waveform-playlist/worklets";
3481
- var PEAK_DECAY = 0.98;
3482
- function useOutputMeter(options = {}) {
3483
- const { channelCount = 2, updateRate = 60, isPlaying = false } = options;
3484
- const [levels, setLevels] = useState14(() => new Array(channelCount).fill(0));
3485
- const [peakLevels, setPeakLevels] = useState14(() => new Array(channelCount).fill(0));
3486
- const [rmsLevels, setRmsLevels] = useState14(() => new Array(channelCount).fill(0));
3487
- const workletNodeRef = useRef14(null);
3488
- const smoothedPeakRef = useRef14(new Array(channelCount).fill(0));
3489
- const [meterError, setMeterError] = useState14(null);
3490
- const resetPeak = useCallback18(
3491
- () => setPeakLevels(new Array(channelCount).fill(0)),
3492
- [channelCount]
3493
- );
3494
- useEffect9(() => {
3495
- if (!isPlaying) {
3496
- const zeros = new Array(channelCount).fill(0);
3497
- smoothedPeakRef.current = new Array(channelCount).fill(0);
3498
- setLevels(zeros);
3499
- setRmsLevels(zeros);
3500
- setPeakLevels(zeros);
3501
- }
3502
- }, [isPlaying, channelCount]);
3503
- useEffect9(() => {
3504
- let isMounted = true;
3505
- const setup = () => __async(null, null, function* () {
3506
- const context = getGlobalContext();
3507
- const rawCtx = context.rawContext;
3508
- yield rawCtx.audioWorklet.addModule(meterProcessorUrl);
3509
- if (!isMounted) return;
3510
- const workletNode = context.createAudioWorkletNode("meter-processor", {
3511
- channelCount,
3512
- channelCountMode: "explicit",
3513
- processorOptions: {
3514
- numberOfChannels: channelCount,
3515
- updateRate
3516
- }
3517
- });
3518
- workletNodeRef.current = workletNode;
3519
- workletNode.onprocessorerror = (event) => {
3520
- console.warn("[waveform-playlist] Output meter worklet processor error:", String(event));
3521
- };
3522
- const destination = context.destination;
3523
- destination.chain(workletNode);
3524
- smoothedPeakRef.current = new Array(channelCount).fill(0);
3525
- workletNode.port.onmessage = (event) => {
3526
- var _a;
3527
- if (!isMounted) return;
3528
- const { peak, rms } = event.data;
3529
- const smoothed = smoothedPeakRef.current;
3530
- const peakValues = [];
3531
- const rmsValues = [];
3532
- for (let ch = 0; ch < peak.length; ch++) {
3533
- smoothed[ch] = Math.max(peak[ch], ((_a = smoothed[ch]) != null ? _a : 0) * PEAK_DECAY);
3534
- peakValues.push(gainToNormalized(smoothed[ch]));
3535
- rmsValues.push(gainToNormalized(rms[ch]));
3536
- }
3537
- setLevels(peakValues);
3538
- setRmsLevels(rmsValues);
3539
- setPeakLevels((prev) => peakValues.map((val, i) => {
3540
- var _a2;
3541
- return Math.max((_a2 = prev[i]) != null ? _a2 : 0, val);
3542
- }));
3543
- };
3544
- });
3545
- setup().catch((err) => {
3546
- console.warn("[waveform-playlist] Failed to set up output meter:", String(err));
3547
- if (isMounted) {
3548
- setMeterError(err instanceof Error ? err : new Error(String(err)));
3549
- }
3550
- });
3551
- return () => {
3552
- isMounted = false;
3553
- if (workletNodeRef.current) {
3554
- try {
3555
- const context = getGlobalContext();
3556
- context.destination.chain();
3557
- } catch (err) {
3558
- console.warn("[waveform-playlist] Failed to restore destination chain:", String(err));
3559
- }
3560
- try {
3561
- workletNodeRef.current.disconnect();
3562
- workletNodeRef.current.port.close();
3563
- } catch (err) {
3564
- console.warn("[waveform-playlist] Output meter disconnect during cleanup:", String(err));
3565
- }
3566
- workletNodeRef.current = null;
3567
- }
3568
- };
3569
- }, [channelCount, updateRate]);
3570
- return { levels, peakLevels, rmsLevels, resetPeak, error: meterError };
3571
- }
3572
-
3573
1772
  // src/WaveformPlaylistContext.tsx
3574
1773
  import { jsx } from "react/jsx-runtime";
3575
1774
  var PlaybackAnimationContext = createContext(null);
@@ -3589,6 +1788,7 @@ var WaveformPlaylistProvider = ({
3589
1788
  annotationList,
3590
1789
  effects,
3591
1790
  onReady,
1791
+ onError,
3592
1792
  onAnnotationUpdate: _onAnnotationUpdate,
3593
1793
  onAnnotationsChange,
3594
1794
  barWidth = 1,
@@ -3599,18 +1799,19 @@ var WaveformPlaylistProvider = ({
3599
1799
  deferEngineRebuild = false,
3600
1800
  indefinitePlayback = false,
3601
1801
  sampleRate: sampleRateProp,
1802
+ createAdapter,
3602
1803
  children
3603
1804
  }) => {
3604
1805
  var _a, _b, _c, _d;
3605
1806
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
3606
- const indefinitePlaybackRef = useRef15(indefinitePlayback);
1807
+ const indefinitePlaybackRef = useRef9(indefinitePlayback);
3607
1808
  indefinitePlaybackRef.current = indefinitePlayback;
3608
- const stableZoomLevels = useMemo4(
1809
+ const stableZoomLevels = useMemo3(
3609
1810
  () => zoomLevels,
3610
1811
  // eslint-disable-next-line react-hooks/exhaustive-deps
3611
1812
  [zoomLevels == null ? void 0 : zoomLevels.join(",")]
3612
1813
  );
3613
- const annotations = useMemo4(() => {
1814
+ const annotations = useMemo3(() => {
3614
1815
  if (!(annotationList == null ? void 0 : annotationList.annotations)) return [];
3615
1816
  if (process.env.NODE_ENV !== "production" && annotationList.annotations.length > 0) {
3616
1817
  const first = annotationList.annotations[0];
@@ -3623,63 +1824,50 @@ var WaveformPlaylistProvider = ({
3623
1824
  }
3624
1825
  return annotationList.annotations;
3625
1826
  }, [annotationList == null ? void 0 : annotationList.annotations]);
3626
- const annotationsRef = useRef15(annotations);
1827
+ const annotationsRef = useRef9(annotations);
3627
1828
  annotationsRef.current = annotations;
3628
- const [activeAnnotationId, setActiveAnnotationIdState] = useState15(null);
3629
- const [isPlaying, setIsPlaying] = useState15(false);
3630
- const [currentTime, setCurrentTime] = useState15(0);
3631
- const [duration, setDuration] = useState15(0);
3632
- const [audioBuffers, setAudioBuffers] = useState15([]);
3633
- const [peaksDataArray, setPeaksDataArray] = useState15([]);
3634
- const [trackStates, setTrackStates] = useState15([]);
3635
- const [isAutomaticScroll, setIsAutomaticScroll] = useState15(automaticScroll);
3636
- const [continuousPlay, setContinuousPlayState] = useState15(
1829
+ const [activeAnnotationId, setActiveAnnotationIdState] = useState9(null);
1830
+ const [isPlaying, setIsPlaying] = useState9(false);
1831
+ const [currentTime, setCurrentTime] = useState9(0);
1832
+ const [duration, setDuration] = useState9(0);
1833
+ const [audioBuffers, setAudioBuffers] = useState9([]);
1834
+ const [peaksDataArray, setPeaksDataArray] = useState9([]);
1835
+ const [trackStates, setTrackStates] = useState9([]);
1836
+ const [isAutomaticScroll, setIsAutomaticScroll] = useState9(automaticScroll);
1837
+ const [continuousPlay, setContinuousPlayState] = useState9(
3637
1838
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
3638
1839
  );
3639
- const [linkEndpoints, setLinkEndpoints] = useState15((_b = annotationList == null ? void 0 : annotationList.linkEndpoints) != null ? _b : false);
3640
- const [annotationsEditable, setAnnotationsEditable] = useState15((_c = annotationList == null ? void 0 : annotationList.editable) != null ? _c : false);
3641
- const [isReady, setIsReady] = useState15(false);
3642
- const engineRef = useRef15(null);
3643
- const adapterRef = useRef15(null);
3644
- const audioInitializedRef = useRef15(false);
3645
- const isPlayingRef = useRef15(false);
1840
+ const [linkEndpoints, setLinkEndpoints] = useState9((_b = annotationList == null ? void 0 : annotationList.linkEndpoints) != null ? _b : false);
1841
+ const [annotationsEditable, setAnnotationsEditable] = useState9((_c = annotationList == null ? void 0 : annotationList.editable) != null ? _c : false);
1842
+ const [isReady, setIsReady] = useState9(false);
1843
+ const engineRef = useRef9(null);
1844
+ const adapterRef = useRef9(null);
1845
+ const audioInitializedRef = useRef9(false);
1846
+ const isPlayingRef = useRef9(false);
3646
1847
  isPlayingRef.current = isPlaying;
3647
- const playStartPositionRef = useRef15(0);
3648
- const currentTimeRef = useRef15(0);
3649
- const visualTimeRef = useRef15(0);
3650
- const tracksRef = useRef15(tracks);
3651
- const soundFontCacheRef = useRef15(soundFontCache);
1848
+ const playStartPositionRef = useRef9(0);
1849
+ const currentTimeRef = useRef9(0);
1850
+ const visualTimeRef = useRef9(0);
1851
+ const tracksRef = useRef9(tracks);
1852
+ const soundFontCacheRef = useRef9(soundFontCache);
3652
1853
  soundFontCacheRef.current = soundFontCache;
3653
- const trackStatesRef = useRef15(trackStates);
3654
- const playbackStartTimeRef = useRef15(0);
3655
- const audioStartPositionRef = useRef15(0);
3656
- const playbackEndTimeRef = useRef15(null);
3657
- const scrollContainerRef = useRef15(null);
3658
- const isAutomaticScrollRef = useRef15(false);
3659
- const frameCallbacksRef = useRef15(/* @__PURE__ */ new Map());
3660
- const continuousPlayRef = useRef15((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
3661
- const activeAnnotationIdRef = useRef15(null);
3662
- const engineTracksRef = useRef15(null);
3663
- const lastTracksVersionRef = useRef15(0);
3664
- const skipEngineDisposeRef = useRef15(false);
3665
- const isDraggingRef = useRef15(false);
3666
- const prevTracksRef = useRef15([]);
3667
- const samplesPerPixelRef = useRef15(initialSamplesPerPixel);
3668
- const [initialSampleRate] = useState15(() => {
3669
- if (typeof AudioContext === "undefined") return sampleRateProp != null ? sampleRateProp : 48e3;
3670
- try {
3671
- if (sampleRateProp !== void 0) {
3672
- return configureGlobalContext({ sampleRate: sampleRateProp });
3673
- }
3674
- return getGlobalAudioContext4().sampleRate;
3675
- } catch (err) {
3676
- console.warn(
3677
- "[waveform-playlist] Failed to configure AudioContext: " + String(err) + " \u2014 falling back to " + (sampleRateProp != null ? sampleRateProp : 48e3) + " Hz"
3678
- );
3679
- return sampleRateProp != null ? sampleRateProp : 48e3;
3680
- }
3681
- });
3682
- const sampleRateRef = useRef15(initialSampleRate);
1854
+ const trackStatesRef = useRef9(trackStates);
1855
+ const playbackStartTimeRef = useRef9(0);
1856
+ const audioStartPositionRef = useRef9(0);
1857
+ const playbackEndTimeRef = useRef9(null);
1858
+ const scrollContainerRef = useRef9(null);
1859
+ const isAutomaticScrollRef = useRef9(false);
1860
+ const frameCallbacksRef = useRef9(/* @__PURE__ */ new Map());
1861
+ const continuousPlayRef = useRef9((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
1862
+ const activeAnnotationIdRef = useRef9(null);
1863
+ const engineTracksRef = useRef9(null);
1864
+ const lastTracksVersionRef = useRef9(0);
1865
+ const skipEngineDisposeRef = useRef9(false);
1866
+ const isDraggingRef = useRef9(false);
1867
+ const prevTracksRef = useRef9([]);
1868
+ const samplesPerPixelRef = useRef9(initialSamplesPerPixel);
1869
+ const [initialSampleRate] = useState9(() => sampleRateProp != null ? sampleRateProp : 48e3);
1870
+ const sampleRateRef = useRef9(initialSampleRate);
3683
1871
  const { timeFormat, setTimeFormat, formatTime: formatTime2 } = useTimeFormat();
3684
1872
  const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });
3685
1873
  const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;
@@ -3724,20 +1912,20 @@ var WaveformPlaylistProvider = ({
3724
1912
  onEngineState: onUndoEngineState
3725
1913
  } = useUndoState({ engineRef });
3726
1914
  const { animationFrameRef, startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
3727
- const baseScale = useMemo4(
1915
+ const baseScale = useMemo3(
3728
1916
  () => Math.min(...stableZoomLevels != null ? stableZoomLevels : [256, 512, 1024, 2048, 4096, 8192]),
3729
1917
  [stableZoomLevels]
3730
1918
  );
3731
1919
  const { cache: waveformDataCache } = useWaveformDataCache(tracks, baseScale);
3732
- const setContinuousPlay = useCallback19((value) => {
1920
+ const setContinuousPlay = useCallback13((value) => {
3733
1921
  continuousPlayRef.current = value;
3734
1922
  setContinuousPlayState(value);
3735
1923
  }, []);
3736
- const setActiveAnnotationId = useCallback19((value) => {
1924
+ const setActiveAnnotationId = useCallback13((value) => {
3737
1925
  activeAnnotationIdRef.current = value;
3738
1926
  setActiveAnnotationIdState(value);
3739
1927
  }, []);
3740
- const setLoopRegionFromSelection = useCallback19(() => {
1928
+ const setLoopRegionFromSelection = useCallback13(() => {
3741
1929
  var _a2, _b2;
3742
1930
  const start = (_a2 = selectionStartRef.current) != null ? _a2 : 0;
3743
1931
  const end = (_b2 = selectionEndRef.current) != null ? _b2 : 0;
@@ -3745,21 +1933,21 @@ var WaveformPlaylistProvider = ({
3745
1933
  setLoopRegion(start, end);
3746
1934
  }
3747
1935
  }, [setLoopRegion, selectionStartRef, selectionEndRef]);
3748
- useEffect10(() => {
1936
+ useEffect5(() => {
3749
1937
  isAutomaticScrollRef.current = isAutomaticScroll;
3750
1938
  }, [isAutomaticScroll]);
3751
- useEffect10(() => {
1939
+ useEffect5(() => {
3752
1940
  trackStatesRef.current = trackStates;
3753
1941
  }, [trackStates]);
3754
1942
  tracksRef.current = tracks;
3755
1943
  const isEngineTracks = tracks === engineTracksRef.current;
3756
1944
  const prevTracks = prevTracksRef.current;
3757
- const isIncrementalAdd = engineRef.current !== null && prevTracks.length > 0 && tracks.length > prevTracks.length && prevTracks.every((pt) => {
1945
+ const isIncrementalAdd = engineRef.current !== null && tracks.length > prevTracks.length && prevTracks.every((pt) => {
3758
1946
  const current = tracks.find((t) => t.id === pt.id);
3759
1947
  return current === pt;
3760
1948
  });
3761
1949
  skipEngineDisposeRef.current = isEngineTracks || isDraggingRef.current || isIncrementalAdd;
3762
- useEffect10(() => {
1950
+ useEffect5(() => {
3763
1951
  if (!scrollContainerRef.current || duration === 0) return;
3764
1952
  const container = scrollContainerRef.current;
3765
1953
  const oldSamplesPerPixel = samplesPerPixelRef.current;
@@ -3775,8 +1963,8 @@ var WaveformPlaylistProvider = ({
3775
1963
  container.scrollLeft = newScrollLeft;
3776
1964
  samplesPerPixelRef.current = newSamplesPerPixel;
3777
1965
  }, [samplesPerPixel, duration]);
3778
- const pendingResumeRef = useRef15(null);
3779
- useEffect10(() => {
1966
+ const pendingResumeRef = useRef9(null);
1967
+ useEffect5(() => {
3780
1968
  var _a2, _b2, _c2, _d2;
3781
1969
  if (isEngineTracks || isDraggingRef.current) {
3782
1970
  if (isEngineTracks) {
@@ -3867,12 +2055,10 @@ var WaveformPlaylistProvider = ({
3867
2055
  setTrackStates([]);
3868
2056
  setPeaksDataArray([]);
3869
2057
  if (engineRef.current) {
3870
- engineRef.current.dispose();
3871
- engineRef.current = null;
3872
- adapterRef.current = null;
2058
+ engineRef.current.setTracks([]);
2059
+ prevTracksRef.current = tracks;
2060
+ return;
3873
2061
  }
3874
- prevTracksRef.current = tracks;
3875
- return;
3876
2062
  }
3877
2063
  const wasPlaying = isPlayingRef.current;
3878
2064
  const resumePosition = currentTimeRef.current;
@@ -3881,6 +2067,7 @@ var WaveformPlaylistProvider = ({
3881
2067
  stopAnimationFrameLoop();
3882
2068
  pendingResumeRef.current = { position: resumePosition };
3883
2069
  }
2070
+ let cancelled = false;
3884
2071
  const loadAudio = () => __async(null, null, function* () {
3885
2072
  var _a3, _b3, _c3, _d3, _e;
3886
2073
  try {
@@ -3923,7 +2110,17 @@ var WaveformPlaylistProvider = ({
3923
2110
  lastTracksVersionRef.current = 0;
3924
2111
  engineTracksRef.current = null;
3925
2112
  audioInitializedRef.current = false;
3926
- const adapter = createToneAdapter({ effects, soundFontCache: soundFontCacheRef.current });
2113
+ const adapter = yield resolvePlayoutAdapter({
2114
+ createAdapter,
2115
+ effects,
2116
+ soundFontCache: soundFontCacheRef.current,
2117
+ sampleRate: sampleRateProp
2118
+ });
2119
+ if (cancelled) {
2120
+ adapter.dispose();
2121
+ return;
2122
+ }
2123
+ sampleRateRef.current = adapter.audioContext.sampleRate;
3927
2124
  adapterRef.current = adapter;
3928
2125
  const engine = new PlaylistEngine({
3929
2126
  adapter,
@@ -3981,11 +2178,23 @@ var WaveformPlaylistProvider = ({
3981
2178
  prevTracksRef.current = tracks;
3982
2179
  onReady == null ? void 0 : onReady();
3983
2180
  } catch (error) {
3984
- console.warn("[waveform-playlist] Error loading audio:", String(error));
2181
+ if (!engineRef.current && adapterRef.current) {
2182
+ try {
2183
+ adapterRef.current.dispose();
2184
+ } catch (disposeErr) {
2185
+ console.warn(
2186
+ "[waveform-playlist] adapter dispose after load failure threw: " + String(disposeErr)
2187
+ );
2188
+ }
2189
+ adapterRef.current = null;
2190
+ }
2191
+ console.warn("[waveform-playlist] Error loading audio: " + String(error));
2192
+ onError == null ? void 0 : onError(error instanceof Error ? error : new Error(String(error)));
3985
2193
  }
3986
2194
  });
3987
2195
  loadAudio();
3988
2196
  return () => {
2197
+ cancelled = true;
3989
2198
  if (skipEngineDisposeRef.current) {
3990
2199
  skipEngineDisposeRef.current = false;
3991
2200
  return;
@@ -4008,6 +2217,7 @@ var WaveformPlaylistProvider = ({
4008
2217
  // to the live adapter by the sync effect below; only adapter creation reads it
4009
2218
  // (via soundFontCacheRef).
4010
2219
  onReady,
2220
+ onError,
4011
2221
  effects,
4012
2222
  stopAnimationFrameLoop,
4013
2223
  onSelectionEngineState,
@@ -4026,10 +2236,10 @@ var WaveformPlaylistProvider = ({
4026
2236
  stableZoomLevels,
4027
2237
  deferEngineRebuild
4028
2238
  ]);
4029
- useEffect10(() => {
2239
+ useEffect5(() => {
4030
2240
  syncSoundFontCacheToAdapter(adapterRef.current, soundFontCache);
4031
2241
  }, [soundFontCache]);
4032
- useEffect10(() => {
2242
+ useEffect5(() => {
4033
2243
  if (tracks.length === 0) return;
4034
2244
  const allTrackPeaks = tracks.map((track) => {
4035
2245
  const clipPeaks = track.clips.map((clip) => {
@@ -4110,8 +2320,15 @@ var WaveformPlaylistProvider = ({
4110
2320
  });
4111
2321
  setPeaksDataArray(allTrackPeaks);
4112
2322
  }, [tracks, samplesPerPixel, mono, waveformDataCache, deferEngineRebuild]);
4113
- const getPlaybackTimeFallbackWarnedRef = useRef15(false);
4114
- const getPlaybackTime = useCallback19(() => {
2323
+ const getAudioContextTime = useCallback13(
2324
+ () => {
2325
+ var _a2, _b2;
2326
+ return (_b2 = (_a2 = adapterRef.current) == null ? void 0 : _a2.audioContext.currentTime) != null ? _b2 : 0;
2327
+ },
2328
+ []
2329
+ );
2330
+ const getPlaybackTimeFallbackWarnedRef = useRef9(false);
2331
+ const getPlaybackTime = useCallback13(() => {
4115
2332
  var _a2, _b2;
4116
2333
  if (engineRef.current) {
4117
2334
  return engineRef.current.getCurrentTime();
@@ -4122,30 +2339,35 @@ var WaveformPlaylistProvider = ({
4122
2339
  "[waveform-playlist] getPlaybackTime called without engine. Falling back to manual elapsed time (loop wrapping will not work)."
4123
2340
  );
4124
2341
  }
4125
- const elapsed = getContext2().currentTime - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
2342
+ const elapsed = getAudioContextTime() - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
4126
2343
  return ((_b2 = audioStartPositionRef.current) != null ? _b2 : 0) + elapsed;
4127
- }, []);
4128
- const toVisualTime = useCallback19((rawTime) => {
2344
+ }, [getAudioContextTime]);
2345
+ const toVisualTime = useCallback13((rawTime) => {
4129
2346
  return Number.isFinite(rawTime) ? Math.max(0, rawTime) : 0;
4130
2347
  }, []);
4131
- const setCurrentTimeRefs = useCallback19(
2348
+ const setCurrentTimeRefs = useCallback13(
4132
2349
  (rawTime) => {
4133
2350
  currentTimeRef.current = rawTime;
4134
2351
  visualTimeRef.current = toVisualTime(rawTime);
4135
2352
  },
4136
2353
  [toVisualTime]
4137
2354
  );
4138
- const getLookAhead = useCallback19(() => {
2355
+ const getLookAhead = useCallback13(() => {
4139
2356
  var _a2, _b2;
4140
2357
  return (_b2 = (_a2 = engineRef.current) == null ? void 0 : _a2.lookAhead) != null ? _b2 : 0;
4141
2358
  }, []);
4142
- const registerFrameCallback = useCallback19((id, cb) => {
2359
+ const getOutputLatency = useCallback13(() => {
2360
+ var _a2;
2361
+ const audioCtx = (_a2 = adapterRef.current) == null ? void 0 : _a2.audioContext;
2362
+ return audioCtx && "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
2363
+ }, []);
2364
+ const registerFrameCallback = useCallback13((id, cb) => {
4143
2365
  frameCallbacksRef.current.set(id, cb);
4144
2366
  }, []);
4145
- const unregisterFrameCallback = useCallback19((id) => {
2367
+ const unregisterFrameCallback = useCallback13((id) => {
4146
2368
  frameCallbacksRef.current.delete(id);
4147
2369
  }, []);
4148
- const startAnimationLoop = useCallback19(() => {
2370
+ const startAnimationLoop = useCallback13(() => {
4149
2371
  const updateTime = () => {
4150
2372
  const time = getPlaybackTime();
4151
2373
  currentTimeRef.current = time;
@@ -4233,15 +2455,14 @@ var WaveformPlaylistProvider = ({
4233
2455
  setCurrentTimeRefs
4234
2456
  ]);
4235
2457
  const stopAnimationLoop = stopAnimationFrameLoop;
4236
- useEffect10(() => {
2458
+ useEffect5(() => {
4237
2459
  const reschedulePlayback = () => __async(null, null, function* () {
4238
2460
  if (isPlaying && animationFrameRef.current && engineRef.current) {
4239
2461
  if (continuousPlay) {
4240
2462
  const currentPos = currentTimeRef.current;
4241
2463
  engineRef.current.stop();
4242
2464
  stopAnimationLoop();
4243
- const context = getContext2();
4244
- const timeNow = context.currentTime;
2465
+ const timeNow = getAudioContextTime();
4245
2466
  playbackStartTimeRef.current = timeNow;
4246
2467
  audioStartPositionRef.current = currentPos;
4247
2468
  engineRef.current.play(currentPos);
@@ -4257,14 +2478,20 @@ var WaveformPlaylistProvider = ({
4257
2478
  setIsPlaying(false);
4258
2479
  stopAnimationLoop();
4259
2480
  });
4260
- }, [continuousPlay, isPlaying, startAnimationLoop, stopAnimationLoop, animationFrameRef]);
4261
- useEffect10(() => {
2481
+ }, [
2482
+ continuousPlay,
2483
+ isPlaying,
2484
+ startAnimationLoop,
2485
+ stopAnimationLoop,
2486
+ animationFrameRef,
2487
+ getAudioContextTime
2488
+ ]);
2489
+ useEffect5(() => {
4262
2490
  const resumePlayback = () => __async(null, null, function* () {
4263
2491
  if (pendingResumeRef.current && engineRef.current) {
4264
2492
  const { position } = pendingResumeRef.current;
4265
2493
  pendingResumeRef.current = null;
4266
- const context = getContext2();
4267
- const timeNow = context.currentTime;
2494
+ const timeNow = getAudioContextTime();
4268
2495
  playbackStartTimeRef.current = timeNow;
4269
2496
  audioStartPositionRef.current = position;
4270
2497
  if (!audioInitializedRef.current) {
@@ -4281,8 +2508,8 @@ var WaveformPlaylistProvider = ({
4281
2508
  setIsPlaying(false);
4282
2509
  stopAnimationLoop();
4283
2510
  });
4284
- }, [tracks, startAnimationLoop, stopAnimationLoop]);
4285
- const play = useCallback19(
2511
+ }, [tracks, startAnimationLoop, stopAnimationLoop, getAudioContextTime]);
2512
+ const play = useCallback13(
4286
2513
  (startTime, playDuration) => __async(null, null, function* () {
4287
2514
  if (!engineRef.current) return;
4288
2515
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4291,8 +2518,7 @@ var WaveformPlaylistProvider = ({
4291
2518
  engineRef.current.stop();
4292
2519
  engineRef.current.seek(actualStartTime);
4293
2520
  stopAnimationLoop();
4294
- const context = getContext2();
4295
- const startTimeNow = context.currentTime;
2521
+ const startTimeNow = getAudioContextTime();
4296
2522
  playbackStartTimeRef.current = startTimeNow;
4297
2523
  audioStartPositionRef.current = actualStartTime;
4298
2524
  playbackEndTimeRef.current = playDuration !== void 0 ? actualStartTime + playDuration : null;
@@ -4311,9 +2537,9 @@ var WaveformPlaylistProvider = ({
4311
2537
  setIsPlaying(true);
4312
2538
  startAnimationLoop();
4313
2539
  }),
4314
- [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs]
2540
+ [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs, getAudioContextTime]
4315
2541
  );
4316
- const pause = useCallback19(() => {
2542
+ const pause = useCallback13(() => {
4317
2543
  if (!engineRef.current) return;
4318
2544
  const pauseTime = getPlaybackTime();
4319
2545
  engineRef.current.pause();
@@ -4322,7 +2548,7 @@ var WaveformPlaylistProvider = ({
4322
2548
  setCurrentTimeRefs(pauseTime);
4323
2549
  setCurrentTime(pauseTime);
4324
2550
  }, [stopAnimationLoop, getPlaybackTime, setCurrentTimeRefs]);
4325
- const stop = useCallback19(() => {
2551
+ const stop = useCallback13(() => {
4326
2552
  if (!engineRef.current) return;
4327
2553
  engineRef.current.stop();
4328
2554
  setIsPlaying(false);
@@ -4331,7 +2557,7 @@ var WaveformPlaylistProvider = ({
4331
2557
  setCurrentTime(playStartPositionRef.current);
4332
2558
  setActiveAnnotationId(null);
4333
2559
  }, [stopAnimationLoop, setActiveAnnotationId, setCurrentTimeRefs]);
4334
- const seekTo = useCallback19(
2560
+ const seekTo = useCallback13(
4335
2561
  (time) => {
4336
2562
  const clampedTime = Math.max(0, Math.min(time, duration));
4337
2563
  setCurrentTimeRefs(clampedTime);
@@ -4342,7 +2568,7 @@ var WaveformPlaylistProvider = ({
4342
2568
  },
4343
2569
  [duration, isPlaying, play, setCurrentTimeRefs]
4344
2570
  );
4345
- const setTrackMute = useCallback19(
2571
+ const setTrackMute = useCallback13(
4346
2572
  (trackIndex, muted) => {
4347
2573
  var _a2;
4348
2574
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4356,7 +2582,7 @@ var WaveformPlaylistProvider = ({
4356
2582
  },
4357
2583
  [trackStates]
4358
2584
  );
4359
- const setTrackSolo = useCallback19(
2585
+ const setTrackSolo = useCallback13(
4360
2586
  (trackIndex, soloed) => {
4361
2587
  var _a2;
4362
2588
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4370,7 +2596,7 @@ var WaveformPlaylistProvider = ({
4370
2596
  },
4371
2597
  [trackStates]
4372
2598
  );
4373
- const setTrackVolume = useCallback19(
2599
+ const setTrackVolume = useCallback13(
4374
2600
  (trackIndex, volume2) => {
4375
2601
  var _a2;
4376
2602
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4384,7 +2610,7 @@ var WaveformPlaylistProvider = ({
4384
2610
  },
4385
2611
  [trackStates]
4386
2612
  );
4387
- const setTrackPan = useCallback19(
2613
+ const setTrackPan = useCallback13(
4388
2614
  (trackIndex, pan) => {
4389
2615
  var _a2;
4390
2616
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4398,7 +2624,7 @@ var WaveformPlaylistProvider = ({
4398
2624
  },
4399
2625
  [trackStates]
4400
2626
  );
4401
- const setSelection = useCallback19(
2627
+ const setSelection = useCallback13(
4402
2628
  (start, end) => {
4403
2629
  setSelectionEngine(start, end);
4404
2630
  setCurrentTimeRefs(start);
@@ -4411,12 +2637,12 @@ var WaveformPlaylistProvider = ({
4411
2637
  },
4412
2638
  [isPlaying, setSelectionEngine, setCurrentTimeRefs]
4413
2639
  );
4414
- const setScrollContainer = useCallback19((element) => {
2640
+ const setScrollContainer = useCallback13((element) => {
4415
2641
  scrollContainerRef.current = element;
4416
2642
  }, []);
4417
- const onAnnotationsChangeRef = useRef15(onAnnotationsChange);
2643
+ const onAnnotationsChangeRef = useRef9(onAnnotationsChange);
4418
2644
  onAnnotationsChangeRef.current = onAnnotationsChange;
4419
- const setAnnotations = useCallback19(
2645
+ const setAnnotations = useCallback13(
4420
2646
  (action) => {
4421
2647
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4422
2648
  if (!onAnnotationsChangeRef.current) {
@@ -4434,7 +2660,7 @@ var WaveformPlaylistProvider = ({
4434
2660
  const sampleRate = sampleRateRef.current;
4435
2661
  const timeScaleHeight = timescale ? 30 : 0;
4436
2662
  const minimumPlaylistHeight = tracks.length * waveHeight + timeScaleHeight;
4437
- const animationValue = useMemo4(
2663
+ const animationValue = useMemo3(
4438
2664
  () => ({
4439
2665
  isPlaying,
4440
2666
  currentTime,
@@ -4443,7 +2669,9 @@ var WaveformPlaylistProvider = ({
4443
2669
  playbackStartTimeRef,
4444
2670
  audioStartPositionRef,
4445
2671
  getPlaybackTime,
2672
+ getAudioContextTime,
4446
2673
  getLookAhead,
2674
+ getOutputLatency,
4447
2675
  registerFrameCallback,
4448
2676
  unregisterFrameCallback
4449
2677
  }),
@@ -4455,12 +2683,14 @@ var WaveformPlaylistProvider = ({
4455
2683
  playbackStartTimeRef,
4456
2684
  audioStartPositionRef,
4457
2685
  getPlaybackTime,
2686
+ getAudioContextTime,
4458
2687
  getLookAhead,
2688
+ getOutputLatency,
4459
2689
  registerFrameCallback,
4460
2690
  unregisterFrameCallback
4461
2691
  ]
4462
2692
  );
4463
- const stateValue = useMemo4(
2693
+ const stateValue = useMemo3(
4464
2694
  () => ({
4465
2695
  continuousPlay,
4466
2696
  linkEndpoints,
@@ -4496,17 +2726,17 @@ var WaveformPlaylistProvider = ({
4496
2726
  canRedo
4497
2727
  ]
4498
2728
  );
4499
- const setCurrentTimeControl = useCallback19(
2729
+ const setCurrentTimeControl = useCallback13(
4500
2730
  (time) => {
4501
2731
  setCurrentTimeRefs(time);
4502
2732
  setCurrentTime(time);
4503
2733
  },
4504
2734
  [setCurrentTimeRefs]
4505
2735
  );
4506
- const setAutomaticScrollControl = useCallback19((enabled) => {
2736
+ const setAutomaticScrollControl = useCallback13((enabled) => {
4507
2737
  setIsAutomaticScroll(enabled);
4508
2738
  }, []);
4509
- const controlsValue = useMemo4(
2739
+ const controlsValue = useMemo3(
4510
2740
  () => ({
4511
2741
  // Playback controls
4512
2742
  play,
@@ -4582,7 +2812,7 @@ var WaveformPlaylistProvider = ({
4582
2812
  redo
4583
2813
  ]
4584
2814
  );
4585
- const dataValue = useMemo4(
2815
+ const dataValue = useMemo3(
4586
2816
  () => ({
4587
2817
  duration,
4588
2818
  audioBuffers,
@@ -4671,14 +2901,38 @@ var usePlaylistDataOptional = () => useContext(PlaylistDataContext);
4671
2901
  import {
4672
2902
  createContext as createContext2,
4673
2903
  useContext as useContext2,
4674
- useState as useState16,
4675
- useEffect as useEffect11,
4676
- useRef as useRef16,
4677
- useCallback as useCallback20,
4678
- useMemo as useMemo5
2904
+ useState as useState10,
2905
+ useEffect as useEffect6,
2906
+ useRef as useRef10,
2907
+ useCallback as useCallback14,
2908
+ useMemo as useMemo4
4679
2909
  } from "react";
4680
2910
  import { ThemeProvider as ThemeProvider2 } from "styled-components";
4681
- import { MediaElementPlayout } from "@waveform-playlist/media-element-playout";
2911
+
2912
+ // src/playout/resolveMediaElementPlayout.ts
2913
+ var INSTALL_HINT2 = "@waveform-playlist/media-element-playout is required for the default MediaElement engine. Install with: npm install @waveform-playlist/media-element-playout \u2014 or pass a custom `createPlayout`.";
2914
+ function resolveMediaElementPlayout(opts) {
2915
+ return __async(this, null, function* () {
2916
+ if (opts.createPlayout) {
2917
+ return opts.createPlayout();
2918
+ }
2919
+ let mod;
2920
+ try {
2921
+ mod = yield import("@waveform-playlist/media-element-playout");
2922
+ } catch (originalErr) {
2923
+ console.warn(
2924
+ "[waveform-playlist] @waveform-playlist/media-element-playout dynamic import failed: " + String(originalErr)
2925
+ );
2926
+ throw new Error(INSTALL_HINT2);
2927
+ }
2928
+ return new mod.MediaElementPlayout({
2929
+ playbackRate: opts.playbackRate,
2930
+ preservesPitch: opts.preservesPitch
2931
+ });
2932
+ });
2933
+ }
2934
+
2935
+ // src/MediaElementPlaylistContext.tsx
4682
2936
  import { defaultTheme as defaultTheme2 } from "@waveform-playlist/ui-components";
4683
2937
  import { jsx as jsx2 } from "react/jsx-runtime";
4684
2938
  var MediaElementAnimationContext = createContext2(null);
@@ -4702,16 +2956,18 @@ var MediaElementPlaylistProvider = ({
4702
2956
  audioContext,
4703
2957
  onAnnotationsChange,
4704
2958
  onReady,
2959
+ onError,
2960
+ createPlayout,
4705
2961
  children
4706
2962
  }) => {
4707
2963
  var _a;
4708
2964
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
4709
- const [isPlaying, setIsPlaying] = useState16(false);
4710
- const [currentTime, setCurrentTime] = useState16(0);
4711
- const [duration, setDuration] = useState16(0);
4712
- const [peaksDataArray, setPeaksDataArray] = useState16([]);
4713
- const [playbackRate, setPlaybackRateState] = useState16(initialPlaybackRate);
4714
- const annotations = useMemo5(() => {
2965
+ const [isPlaying, setIsPlaying] = useState10(false);
2966
+ const [currentTime, setCurrentTime] = useState10(0);
2967
+ const [duration, setDuration] = useState10(0);
2968
+ const [peaksDataArray, setPeaksDataArray] = useState10([]);
2969
+ const [playbackRate, setPlaybackRateState] = useState10(initialPlaybackRate);
2970
+ const annotations = useMemo4(() => {
4715
2971
  if (!(annotationList == null ? void 0 : annotationList.annotations)) return [];
4716
2972
  if (process.env.NODE_ENV !== "production" && annotationList.annotations.length > 0) {
4717
2973
  const first = annotationList.annotations[0];
@@ -4724,73 +2980,92 @@ var MediaElementPlaylistProvider = ({
4724
2980
  }
4725
2981
  return annotationList.annotations;
4726
2982
  }, [annotationList == null ? void 0 : annotationList.annotations]);
4727
- const annotationsRef = useRef16(annotations);
2983
+ const annotationsRef = useRef10(annotations);
4728
2984
  annotationsRef.current = annotations;
4729
- const [activeAnnotationId, setActiveAnnotationIdState] = useState16(null);
4730
- const [continuousPlay, setContinuousPlayState] = useState16(
2985
+ const [activeAnnotationId, setActiveAnnotationIdState] = useState10(null);
2986
+ const [continuousPlay, setContinuousPlayState] = useState10(
4731
2987
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
4732
2988
  );
4733
- const [samplesPerPixel] = useState16(initialSamplesPerPixel);
4734
- const [isAutomaticScroll, setIsAutomaticScroll] = useState16(automaticScroll);
4735
- const playoutRef = useRef16(null);
4736
- const currentTimeRef = useRef16(0);
4737
- const continuousPlayRef = useRef16(continuousPlay);
4738
- const activeAnnotationIdRef = useRef16(null);
4739
- const scrollContainerRef = useRef16(null);
4740
- const isAutomaticScrollRef = useRef16(automaticScroll);
4741
- const samplesPerPixelRef = useRef16(initialSamplesPerPixel);
2989
+ const [samplesPerPixel] = useState10(initialSamplesPerPixel);
2990
+ const [isAutomaticScroll, setIsAutomaticScroll] = useState10(automaticScroll);
2991
+ const playoutRef = useRef10(null);
2992
+ const currentTimeRef = useRef10(0);
2993
+ const continuousPlayRef = useRef10(continuousPlay);
2994
+ const activeAnnotationIdRef = useRef10(null);
2995
+ const scrollContainerRef = useRef10(null);
2996
+ const isAutomaticScrollRef = useRef10(automaticScroll);
2997
+ const samplesPerPixelRef = useRef10(initialSamplesPerPixel);
4742
2998
  const { startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
4743
- useEffect11(() => {
2999
+ useEffect6(() => {
4744
3000
  continuousPlayRef.current = continuousPlay;
4745
3001
  }, [continuousPlay]);
4746
- useEffect11(() => {
3002
+ useEffect6(() => {
4747
3003
  isAutomaticScrollRef.current = isAutomaticScroll;
4748
3004
  }, [isAutomaticScroll]);
4749
- const setActiveAnnotationId = useCallback20((value) => {
3005
+ const setActiveAnnotationId = useCallback14((value) => {
4750
3006
  activeAnnotationIdRef.current = value;
4751
3007
  setActiveAnnotationIdState(value);
4752
3008
  }, []);
4753
- const setContinuousPlay = useCallback20((value) => {
3009
+ const setContinuousPlay = useCallback14((value) => {
4754
3010
  continuousPlayRef.current = value;
4755
3011
  setContinuousPlayState(value);
4756
3012
  }, []);
4757
- const setScrollContainer = useCallback20((element) => {
3013
+ const setScrollContainer = useCallback14((element) => {
4758
3014
  scrollContainerRef.current = element;
4759
3015
  }, []);
4760
3016
  const sampleRate = track.waveformData.sample_rate;
4761
- useEffect11(() => {
4762
- var _a2, _b;
4763
- const playout = new MediaElementPlayout({
4764
- playbackRate: initialPlaybackRate,
4765
- preservesPitch
4766
- });
4767
- playout.addTrack({
4768
- source: track.source,
4769
- peaks: track.waveformData,
4770
- name: track.name,
4771
- audioContext,
4772
- fadeIn: track.fadeIn,
4773
- fadeOut: track.fadeOut
4774
- });
4775
- const mediaTrack = playout.getTrack((_b = (_a2 = playout["track"]) == null ? void 0 : _a2.id) != null ? _b : "");
4776
- if (mediaTrack) {
4777
- mediaTrack.setOnTimeUpdateCallback((time) => {
4778
- currentTimeRef.current = time;
4779
- });
4780
- }
4781
- playout.setOnPlaybackComplete(() => {
4782
- stopAnimationFrameLoop();
4783
- setIsPlaying(false);
4784
- setActiveAnnotationId(null);
4785
- currentTimeRef.current = 0;
4786
- setCurrentTime(0);
4787
- });
4788
- playoutRef.current = playout;
4789
- setDuration(track.waveformData.duration);
4790
- onReady == null ? void 0 : onReady();
3017
+ useEffect6(() => {
3018
+ let cancelled = false;
3019
+ let createdPlayout = null;
3020
+ (() => __async(null, null, function* () {
3021
+ var _a2, _b;
3022
+ try {
3023
+ const playout = yield resolveMediaElementPlayout({
3024
+ createPlayout,
3025
+ playbackRate: initialPlaybackRate,
3026
+ preservesPitch
3027
+ });
3028
+ if (cancelled) {
3029
+ playout.dispose();
3030
+ return;
3031
+ }
3032
+ createdPlayout = playout;
3033
+ playout.addTrack({
3034
+ source: track.source,
3035
+ peaks: track.waveformData,
3036
+ name: track.name,
3037
+ audioContext,
3038
+ fadeIn: track.fadeIn,
3039
+ fadeOut: track.fadeOut
3040
+ });
3041
+ const mediaTrack = playout.getTrack((_b = (_a2 = playout["track"]) == null ? void 0 : _a2.id) != null ? _b : "");
3042
+ if (mediaTrack) {
3043
+ mediaTrack.setOnTimeUpdateCallback((time) => {
3044
+ currentTimeRef.current = time;
3045
+ });
3046
+ }
3047
+ playout.setOnPlaybackComplete(() => {
3048
+ stopAnimationFrameLoop();
3049
+ setIsPlaying(false);
3050
+ setActiveAnnotationId(null);
3051
+ currentTimeRef.current = 0;
3052
+ setCurrentTime(0);
3053
+ });
3054
+ playoutRef.current = playout;
3055
+ setDuration(track.waveformData.duration);
3056
+ onReady == null ? void 0 : onReady();
3057
+ } catch (err) {
3058
+ console.warn("[waveform-playlist] MediaElement playout init failed: " + String(err));
3059
+ onError == null ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
3060
+ }
3061
+ }))();
4791
3062
  return () => {
3063
+ cancelled = true;
4792
3064
  stopAnimationFrameLoop();
4793
- playout.dispose();
3065
+ if (createdPlayout) {
3066
+ createdPlayout.dispose();
3067
+ }
3068
+ playoutRef.current = null;
4794
3069
  };
4795
3070
  }, [
4796
3071
  track.source,
@@ -4802,10 +3077,12 @@ var MediaElementPlaylistProvider = ({
4802
3077
  initialPlaybackRate,
4803
3078
  preservesPitch,
4804
3079
  onReady,
3080
+ onError,
4805
3081
  stopAnimationFrameLoop,
4806
- setActiveAnnotationId
3082
+ setActiveAnnotationId,
3083
+ createPlayout
4807
3084
  ]);
4808
- useEffect11(() => {
3085
+ useEffect6(() => {
4809
3086
  var _a2;
4810
3087
  try {
4811
3088
  const extractedPeaks = extractPeaksFromWaveformData(
@@ -4834,7 +3111,7 @@ var MediaElementPlaylistProvider = ({
4834
3111
  console.warn("[waveform-playlist] Failed to extract peaks from waveform data:", err);
4835
3112
  }
4836
3113
  }, [track.waveformData, track.name, samplesPerPixel, sampleRate]);
4837
- const startAnimationLoop = useCallback20(() => {
3114
+ const startAnimationLoop = useCallback14(() => {
4838
3115
  const updateTime = () => {
4839
3116
  var _a2, _b, _c;
4840
3117
  const time = (_b = (_a2 = playoutRef.current) == null ? void 0 : _a2.getCurrentTime()) != null ? _b : 0;
@@ -4877,7 +3154,7 @@ var MediaElementPlaylistProvider = ({
4877
3154
  startAnimationFrameLoop(updateTime);
4878
3155
  }, [setActiveAnnotationId, sampleRate, startAnimationFrameLoop]);
4879
3156
  const stopAnimationLoop = stopAnimationFrameLoop;
4880
- const play = useCallback20(
3157
+ const play = useCallback14(
4881
3158
  (startTime) => {
4882
3159
  if (!playoutRef.current) return;
4883
3160
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4887,14 +3164,14 @@ var MediaElementPlaylistProvider = ({
4887
3164
  },
4888
3165
  [startAnimationLoop]
4889
3166
  );
4890
- const pause = useCallback20(() => {
3167
+ const pause = useCallback14(() => {
4891
3168
  if (!playoutRef.current) return;
4892
3169
  playoutRef.current.pause();
4893
3170
  setIsPlaying(false);
4894
3171
  stopAnimationLoop();
4895
3172
  setCurrentTime(playoutRef.current.getCurrentTime());
4896
3173
  }, [stopAnimationLoop]);
4897
- const stop = useCallback20(() => {
3174
+ const stop = useCallback14(() => {
4898
3175
  if (!playoutRef.current) return;
4899
3176
  playoutRef.current.stop();
4900
3177
  setIsPlaying(false);
@@ -4903,7 +3180,7 @@ var MediaElementPlaylistProvider = ({
4903
3180
  setCurrentTime(0);
4904
3181
  setActiveAnnotationId(null);
4905
3182
  }, [stopAnimationLoop, setActiveAnnotationId]);
4906
- const seekTo = useCallback20(
3183
+ const seekTo = useCallback14(
4907
3184
  (time) => {
4908
3185
  const clampedTime = Math.max(0, Math.min(time, duration));
4909
3186
  currentTimeRef.current = clampedTime;
@@ -4914,7 +3191,7 @@ var MediaElementPlaylistProvider = ({
4914
3191
  },
4915
3192
  [duration]
4916
3193
  );
4917
- const setPlaybackRate = useCallback20((rate) => {
3194
+ const setPlaybackRate = useCallback14((rate) => {
4918
3195
  const clampedRate = Math.max(0.5, Math.min(2, rate));
4919
3196
  setPlaybackRateState(clampedRate);
4920
3197
  if (playoutRef.current) {
@@ -4922,7 +3199,7 @@ var MediaElementPlaylistProvider = ({
4922
3199
  }
4923
3200
  }, []);
4924
3201
  const timeScaleHeight = timescale ? 30 : 0;
4925
- const animationValue = useMemo5(
3202
+ const animationValue = useMemo4(
4926
3203
  () => ({
4927
3204
  isPlaying,
4928
3205
  currentTime,
@@ -4930,7 +3207,7 @@ var MediaElementPlaylistProvider = ({
4930
3207
  }),
4931
3208
  [isPlaying, currentTime]
4932
3209
  );
4933
- const stateValue = useMemo5(
3210
+ const stateValue = useMemo4(
4934
3211
  () => ({
4935
3212
  continuousPlay,
4936
3213
  annotations,
@@ -4940,9 +3217,9 @@ var MediaElementPlaylistProvider = ({
4940
3217
  }),
4941
3218
  [continuousPlay, annotations, activeAnnotationId, playbackRate, isAutomaticScroll]
4942
3219
  );
4943
- const onAnnotationsChangeRef = useRef16(onAnnotationsChange);
3220
+ const onAnnotationsChangeRef = useRef10(onAnnotationsChange);
4944
3221
  onAnnotationsChangeRef.current = onAnnotationsChange;
4945
- const setAnnotations = useCallback20(
3222
+ const setAnnotations = useCallback14(
4946
3223
  (action) => {
4947
3224
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4948
3225
  if (!onAnnotationsChangeRef.current) {
@@ -4957,7 +3234,7 @@ var MediaElementPlaylistProvider = ({
4957
3234
  },
4958
3235
  []
4959
3236
  );
4960
- const controlsValue = useMemo5(
3237
+ const controlsValue = useMemo4(
4961
3238
  () => ({
4962
3239
  play,
4963
3240
  pause,
@@ -4985,7 +3262,7 @@ var MediaElementPlaylistProvider = ({
4985
3262
  setScrollContainer
4986
3263
  ]
4987
3264
  );
4988
- const dataValue = useMemo5(
3265
+ const dataValue = useMemo4(
4989
3266
  () => ({
4990
3267
  duration,
4991
3268
  peaksDataArray,
@@ -5049,7 +3326,7 @@ var useMediaElementData = () => {
5049
3326
  };
5050
3327
 
5051
3328
  // src/components/PlaybackControls.tsx
5052
- import { useCallback as useCallback21 } from "react";
3329
+ import { useCallback as useCallback15 } from "react";
5053
3330
  import { BaseControlButton } from "@waveform-playlist/ui-components";
5054
3331
  import { jsx as jsx3 } from "react/jsx-runtime";
5055
3332
  var PlayButton = ({ className }) => {
@@ -5185,7 +3462,7 @@ var ClearAllButton = ({
5185
3462
  className
5186
3463
  }) => {
5187
3464
  const { stop } = usePlaylistControls();
5188
- const handleClick = useCallback21(() => {
3465
+ const handleClick = useCallback15(() => {
5189
3466
  stop();
5190
3467
  onClearAll();
5191
3468
  }, [stop, onClearAll]);
@@ -5213,7 +3490,7 @@ var ZoomOutButton = ({
5213
3490
  };
5214
3491
 
5215
3492
  // src/components/ContextualControls.tsx
5216
- import { useRef as useRef17, useEffect as useEffect12 } from "react";
3493
+ import { useRef as useRef11, useEffect as useEffect7 } from "react";
5217
3494
  import {
5218
3495
  MasterVolumeControl as BaseMasterVolumeControl,
5219
3496
  TimeFormatSelect as BaseTimeFormatSelect,
@@ -5252,10 +3529,10 @@ var PositionDisplay = styled.span`
5252
3529
  `;
5253
3530
  var AudioPosition = ({ className }) => {
5254
3531
  var _a;
5255
- const timeRef = useRef17(null);
3532
+ const timeRef = useRef11(null);
5256
3533
  const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
5257
3534
  const { timeFormat: format } = usePlaylistData();
5258
- useEffect12(() => {
3535
+ useEffect7(() => {
5259
3536
  const id = "audio-position";
5260
3537
  if (isPlaying) {
5261
3538
  registerFrameCallback(id, ({ time }) => {
@@ -5266,7 +3543,7 @@ var AudioPosition = ({ className }) => {
5266
3543
  }
5267
3544
  return () => unregisterFrameCallback(id);
5268
3545
  }, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);
5269
- useEffect12(() => {
3546
+ useEffect7(() => {
5270
3547
  var _a2;
5271
3548
  if (!isPlaying && timeRef.current) {
5272
3549
  timeRef.current.textContent = formatTime((_a2 = currentTimeRef.current) != null ? _a2 : 0, format);
@@ -5343,53 +3620,6 @@ var DownloadAnnotationsButton = ({
5343
3620
  return /* @__PURE__ */ jsx6(Base, { annotations, filename, className });
5344
3621
  };
5345
3622
 
5346
- // src/components/ExportControls.tsx
5347
- import { BaseControlButton as BaseControlButton3 } from "@waveform-playlist/ui-components";
5348
- import { jsx as jsx7 } from "react/jsx-runtime";
5349
- var ExportWavButton = ({
5350
- label = "Export WAV",
5351
- filename = "export",
5352
- mode = "master",
5353
- trackIndex,
5354
- bitDepth = 16,
5355
- applyEffects = true,
5356
- effectsFunction,
5357
- createOfflineTrackEffects,
5358
- className,
5359
- onExportComplete,
5360
- onExportError
5361
- }) => {
5362
- const { tracks, trackStates } = usePlaylistData();
5363
- const { exportWav, isExporting, progress } = useExportWav();
5364
- const handleExport = () => __async(null, null, function* () {
5365
- try {
5366
- const result = yield exportWav(tracks, trackStates, {
5367
- filename,
5368
- mode,
5369
- trackIndex,
5370
- bitDepth,
5371
- applyEffects,
5372
- effectsFunction,
5373
- createOfflineTrackEffects,
5374
- autoDownload: true
5375
- });
5376
- onExportComplete == null ? void 0 : onExportComplete(result.blob);
5377
- } catch (error) {
5378
- onExportError == null ? void 0 : onExportError(error instanceof Error ? error : new Error("Export failed"));
5379
- }
5380
- });
5381
- const buttonLabel = isExporting ? `Exporting ${Math.round(progress * 100)}%` : label;
5382
- return /* @__PURE__ */ jsx7(
5383
- BaseControlButton3,
5384
- {
5385
- onClick: handleExport,
5386
- disabled: isExporting || tracks.length === 0,
5387
- className,
5388
- children: buttonLabel
5389
- }
5390
- );
5391
- };
5392
-
5393
3623
  // src/contexts/ClipInteractionContext.tsx
5394
3624
  import { createContext as createContext4, useContext as useContext4 } from "react";
5395
3625
  var ClipInteractionContext = createContext4(false);
@@ -5399,10 +3629,9 @@ function useClipInteractionEnabled() {
5399
3629
  }
5400
3630
 
5401
3631
  // src/components/PlaylistVisualization.tsx
5402
- import { useContext as useContext6, useRef as useRef20, useState as useState17, useMemo as useMemo6, useCallback as useCallback22 } from "react";
3632
+ import { useContext as useContext6, useRef as useRef14, useState as useState11, useMemo as useMemo5, useCallback as useCallback16 } from "react";
5403
3633
  import { createPortal } from "react-dom";
5404
3634
  import styled4 from "styled-components";
5405
- import { getGlobalAudioContext as getGlobalAudioContext5 } from "@waveform-playlist/playout";
5406
3635
  import {
5407
3636
  Playlist,
5408
3637
  Track as TrackComponent,
@@ -5427,12 +3656,12 @@ import {
5427
3656
  SpectrogramLabels,
5428
3657
  CLIP_HEADER_HEIGHT
5429
3658
  } from "@waveform-playlist/ui-components";
5430
- import { audibleLatencySamples } from "@waveform-playlist/core";
3659
+ import { resolveRecordingOffsetSamples } from "@waveform-playlist/core";
5431
3660
 
5432
3661
  // src/components/AnimatedPlayhead.tsx
5433
- import { useRef as useRef18, useEffect as useEffect13 } from "react";
3662
+ import { useRef as useRef12, useEffect as useEffect8 } from "react";
5434
3663
  import styled2 from "styled-components";
5435
- import { jsx as jsx8 } from "react/jsx-runtime";
3664
+ import { jsx as jsx7 } from "react/jsx-runtime";
5436
3665
  var PlayheadLine = styled2.div.attrs((props) => ({
5437
3666
  style: {
5438
3667
  width: `${props.$width}px`,
@@ -5448,7 +3677,7 @@ var PlayheadLine = styled2.div.attrs((props) => ({
5448
3677
  will-change: transform;
5449
3678
  `;
5450
3679
  var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5451
- const playheadRef = useRef18(null);
3680
+ const playheadRef = useRef12(null);
5452
3681
  const {
5453
3682
  isPlaying,
5454
3683
  currentTimeRef,
@@ -5457,7 +3686,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5457
3686
  unregisterFrameCallback
5458
3687
  } = usePlaybackAnimation();
5459
3688
  const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();
5460
- useEffect13(() => {
3689
+ useEffect8(() => {
5461
3690
  const id = "playhead";
5462
3691
  if (isPlaying) {
5463
3692
  registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {
@@ -5469,7 +3698,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5469
3698
  }
5470
3699
  return () => unregisterFrameCallback(id);
5471
3700
  }, [isPlaying, registerFrameCallback, unregisterFrameCallback]);
5472
- useEffect13(() => {
3701
+ useEffect8(() => {
5473
3702
  var _a, _b;
5474
3703
  if (!isPlaying && playheadRef.current) {
5475
3704
  const time = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
@@ -5477,11 +3706,11 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5477
3706
  playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
5478
3707
  }
5479
3708
  });
5480
- return /* @__PURE__ */ jsx8(PlayheadLine, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
3709
+ return /* @__PURE__ */ jsx7(PlayheadLine, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
5481
3710
  };
5482
3711
 
5483
3712
  // src/components/ChannelWithProgress.tsx
5484
- import { useId, useRef as useRef19, useEffect as useEffect14 } from "react";
3713
+ import { useId, useRef as useRef13, useEffect as useEffect9 } from "react";
5485
3714
  import styled3 from "styled-components";
5486
3715
  import {
5487
3716
  clipPixelWidth as computeClipPixelWidth
@@ -5492,7 +3721,7 @@ import {
5492
3721
  usePlaylistInfo,
5493
3722
  waveformColorToCss
5494
3723
  } from "@waveform-playlist/ui-components";
5495
- import { Fragment, jsx as jsx9, jsxs } from "react/jsx-runtime";
3724
+ import { Fragment, jsx as jsx8, jsxs } from "react/jsx-runtime";
5496
3725
  var ChannelWrapper = styled3.div`
5497
3726
  position: relative;
5498
3727
  `;
@@ -5546,7 +3775,7 @@ var ChannelWithProgress = (_a) => {
5546
3775
  "clipSampleRate",
5547
3776
  "clipOffsetSeconds"
5548
3777
  ]);
5549
- const progressRef = useRef19(null);
3778
+ const progressRef = useRef13(null);
5550
3779
  const callbackId = useId();
5551
3780
  const theme = useTheme();
5552
3781
  const { waveHeight } = usePlaylistInfo();
@@ -5564,7 +3793,7 @@ var ChannelWithProgress = (_a) => {
5564
3793
  clipDurationSamples,
5565
3794
  samplesPerPixel
5566
3795
  );
5567
- useEffect14(() => {
3796
+ useEffect9(() => {
5568
3797
  if (isPlaying) {
5569
3798
  registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {
5570
3799
  if (progressRef.current) {
@@ -5592,7 +3821,7 @@ var ChannelWithProgress = (_a) => {
5592
3821
  registerFrameCallback,
5593
3822
  unregisterFrameCallback
5594
3823
  ]);
5595
- useEffect14(() => {
3824
+ useEffect9(() => {
5596
3825
  var _a2, _b2;
5597
3826
  if (!isPlaying && progressRef.current) {
5598
3827
  const currentTime = (_b2 = (_a2 = visualTimeRef.current) != null ? _a2 : currentTimeRef.current) != null ? _b2 : 0;
@@ -5627,7 +3856,7 @@ var ChannelWithProgress = (_a) => {
5627
3856
  const waveformBackgroundCss = waveformColorToCss(backgroundColor);
5628
3857
  return /* @__PURE__ */ jsxs(ChannelWrapper, { children: [
5629
3858
  isBothMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
5630
- /* @__PURE__ */ jsx9(
3859
+ /* @__PURE__ */ jsx8(
5631
3860
  Background,
5632
3861
  {
5633
3862
  $color: "#000",
@@ -5636,7 +3865,7 @@ var ChannelWithProgress = (_a) => {
5636
3865
  $width: smartChannelProps.length
5637
3866
  }
5638
3867
  ),
5639
- /* @__PURE__ */ jsx9(
3868
+ /* @__PURE__ */ jsx8(
5640
3869
  Background,
5641
3870
  {
5642
3871
  $color: waveformBackgroundCss,
@@ -5645,7 +3874,7 @@ var ChannelWithProgress = (_a) => {
5645
3874
  $width: smartChannelProps.length
5646
3875
  }
5647
3876
  )
5648
- ] }) : /* @__PURE__ */ jsx9(
3877
+ ] }) : /* @__PURE__ */ jsx8(
5649
3878
  Background,
5650
3879
  {
5651
3880
  $color: backgroundCss,
@@ -5654,7 +3883,7 @@ var ChannelWithProgress = (_a) => {
5654
3883
  $width: smartChannelProps.length
5655
3884
  }
5656
3885
  ),
5657
- !isPianoRollMode && /* @__PURE__ */ jsx9(
3886
+ !isPianoRollMode && /* @__PURE__ */ jsx8(
5658
3887
  ProgressOverlay,
5659
3888
  {
5660
3889
  ref: progressRef,
@@ -5664,7 +3893,7 @@ var ChannelWithProgress = (_a) => {
5664
3893
  $width: clipPixelWidth
5665
3894
  }
5666
3895
  ),
5667
- /* @__PURE__ */ jsx9(ChannelContainer, { children: /* @__PURE__ */ jsx9(
3896
+ /* @__PURE__ */ jsx8(ChannelContainer, { children: /* @__PURE__ */ jsx8(
5668
3897
  SmartChannel,
5669
3898
  __spreadProps(__spreadValues({}, smartChannelProps), {
5670
3899
  transparentBackground: true,
@@ -5691,7 +3920,7 @@ function useSpectrogramIntegration() {
5691
3920
  }
5692
3921
 
5693
3922
  // src/components/PlaylistVisualization.tsx
5694
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
3923
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
5695
3924
  var DEFAULT_EMPTY_TRACK_DURATION = 60;
5696
3925
  var ControlSlot = styled4.div.attrs((props) => ({
5697
3926
  style: { height: `${props.$height}px` }
@@ -5710,7 +3939,8 @@ var CustomPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) =>
5710
3939
  visualTimeRef,
5711
3940
  playbackStartTimeRef,
5712
3941
  audioStartPositionRef,
5713
- getPlaybackTime
3942
+ getPlaybackTime,
3943
+ getAudioContextTime
5714
3944
  } = usePlaybackAnimation();
5715
3945
  const visualTime = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
5716
3946
  return renderPlayhead({
@@ -5724,7 +3954,7 @@ var CustomPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) =>
5724
3954
  samplesPerPixel,
5725
3955
  sampleRate,
5726
3956
  controlsOffset: 0,
5727
- getAudioContextTime: () => getGlobalAudioContext5().currentTime,
3957
+ getAudioContextTime,
5728
3958
  getPlaybackTime
5729
3959
  });
5730
3960
  };
@@ -5749,7 +3979,7 @@ var PlaylistVisualization = ({
5749
3979
  }) => {
5750
3980
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
5751
3981
  const theme = useTheme2();
5752
- const { isPlaying, getLookAhead } = usePlaybackAnimation();
3982
+ const { isPlaying, getLookAhead, getOutputLatency } = usePlaybackAnimation();
5753
3983
  const {
5754
3984
  selectionStart,
5755
3985
  selectionEnd,
@@ -5795,7 +4025,7 @@ var PlaylistVisualization = ({
5795
4025
  mono
5796
4026
  } = usePlaylistData();
5797
4027
  const spectrogram = useContext6(SpectrogramIntegrationContext);
5798
- const perTrackSpectrogramHelpers = useMemo6(() => {
4028
+ const perTrackSpectrogramHelpers = useMemo5(() => {
5799
4029
  if (!spectrogram)
5800
4030
  return /* @__PURE__ */ new Map();
5801
4031
  const helpers = /* @__PURE__ */ new Map();
@@ -5814,11 +4044,11 @@ var PlaylistVisualization = ({
5814
4044
  });
5815
4045
  return helpers;
5816
4046
  }, [tracks, spectrogram]);
5817
- const [settingsModalTrackId, setSettingsModalTrackId] = useState17(null);
5818
- const [isSelecting, setIsSelecting] = useState17(false);
5819
- const mouseDownTimeRef = useRef20(0);
5820
- const scrollContainerRef = useRef20(null);
5821
- const handleScrollContainerRef = useCallback22(
4047
+ const [settingsModalTrackId, setSettingsModalTrackId] = useState11(null);
4048
+ const [isSelecting, setIsSelecting] = useState11(false);
4049
+ const mouseDownTimeRef = useRef14(0);
4050
+ const scrollContainerRef = useRef14(null);
4051
+ const handleScrollContainerRef = useCallback16(
5822
4052
  (element) => {
5823
4053
  scrollContainerRef.current = element;
5824
4054
  setScrollContainer(element);
@@ -5856,7 +4086,7 @@ var PlaylistVisualization = ({
5856
4086
  );
5857
4087
  }
5858
4088
  });
5859
- const selectTrack = useCallback22(
4089
+ const selectTrack = useCallback16(
5860
4090
  (trackIndex) => {
5861
4091
  if (trackIndex >= 0 && trackIndex < tracks.length) {
5862
4092
  const track = tracks[trackIndex];
@@ -5926,7 +4156,7 @@ var PlaylistVisualization = ({
5926
4156
  };
5927
4157
  const hasClips = tracks.some((track) => track.clips.length > 0);
5928
4158
  if (hasClips && peaksDataArray.length === 0) {
5929
- return /* @__PURE__ */ jsx10("div", { className, children: "Loading waveform..." });
4159
+ return /* @__PURE__ */ jsx9("div", { className, children: "Loading waveform..." });
5930
4160
  }
5931
4161
  const trackControlsSlots = controls.show ? peaksDataArray.map((trackClipPeaks, trackIndex) => {
5932
4162
  var _a2, _b2, _c2;
@@ -5945,7 +4175,7 @@ var PlaylistVisualization = ({
5945
4175
  const slotHeight = waveHeight * maxChannels + (showClipHeaders ? CLIP_HEADER_HEIGHT : 0);
5946
4176
  const trackControlContent = renderTrackControls ? renderTrackControls(trackIndex) : /* @__PURE__ */ jsxs2(Controls, { onClick: () => selectTrack(trackIndex), children: [
5947
4177
  /* @__PURE__ */ jsxs2(Header, { style: { justifyContent: "center", position: "relative" }, children: [
5948
- onRemoveTrack && /* @__PURE__ */ jsx10(
4178
+ onRemoveTrack && /* @__PURE__ */ jsx9(
5949
4179
  CloseButton,
5950
4180
  {
5951
4181
  onClick: (e) => {
@@ -5954,7 +4184,7 @@ var PlaylistVisualization = ({
5954
4184
  }
5955
4185
  }
5956
4186
  ),
5957
- /* @__PURE__ */ jsx10(
4187
+ /* @__PURE__ */ jsx9(
5958
4188
  "span",
5959
4189
  {
5960
4190
  style: {
@@ -5967,7 +4197,7 @@ var PlaylistVisualization = ({
5967
4197
  children: trackState.name || `Track ${trackIndex + 1}`
5968
4198
  }
5969
4199
  ),
5970
- (spectrogram == null ? void 0 : spectrogram.renderMenuItems) && /* @__PURE__ */ jsx10("span", { style: { position: "absolute", right: 0, top: 0 }, children: /* @__PURE__ */ jsx10(
4200
+ (spectrogram == null ? void 0 : spectrogram.renderMenuItems) && /* @__PURE__ */ jsx9("span", { style: { position: "absolute", right: 0, top: 0 }, children: /* @__PURE__ */ jsx9(
5971
4201
  TrackMenu,
5972
4202
  {
5973
4203
  items: (onClose) => spectrogram.renderMenuItems({
@@ -5980,7 +4210,7 @@ var PlaylistVisualization = ({
5980
4210
  ) })
5981
4211
  ] }),
5982
4212
  /* @__PURE__ */ jsxs2(ButtonGroup, { children: [
5983
- /* @__PURE__ */ jsx10(
4213
+ /* @__PURE__ */ jsx9(
5984
4214
  Button,
5985
4215
  {
5986
4216
  $variant: trackState.muted ? "danger" : "outline",
@@ -5988,7 +4218,7 @@ var PlaylistVisualization = ({
5988
4218
  children: "Mute"
5989
4219
  }
5990
4220
  ),
5991
- /* @__PURE__ */ jsx10(
4221
+ /* @__PURE__ */ jsx9(
5992
4222
  Button,
5993
4223
  {
5994
4224
  $variant: trackState.soloed ? "info" : "outline",
@@ -5998,8 +4228,8 @@ var PlaylistVisualization = ({
5998
4228
  )
5999
4229
  ] }),
6000
4230
  /* @__PURE__ */ jsxs2(SliderWrapper, { children: [
6001
- /* @__PURE__ */ jsx10(VolumeDownIcon, {}),
6002
- /* @__PURE__ */ jsx10(
4231
+ /* @__PURE__ */ jsx9(VolumeDownIcon, {}),
4232
+ /* @__PURE__ */ jsx9(
6003
4233
  Slider,
6004
4234
  {
6005
4235
  min: "0",
@@ -6009,11 +4239,11 @@ var PlaylistVisualization = ({
6009
4239
  onChange: (e) => setTrackVolume(trackIndex, parseFloat(e.target.value))
6010
4240
  }
6011
4241
  ),
6012
- /* @__PURE__ */ jsx10(VolumeUpIcon, {})
4242
+ /* @__PURE__ */ jsx9(VolumeUpIcon, {})
6013
4243
  ] }),
6014
4244
  /* @__PURE__ */ jsxs2(SliderWrapper, { children: [
6015
- /* @__PURE__ */ jsx10("span", { children: "L" }),
6016
- /* @__PURE__ */ jsx10(
4245
+ /* @__PURE__ */ jsx9("span", { children: "L" }),
4246
+ /* @__PURE__ */ jsx9(
6017
4247
  Slider,
6018
4248
  {
6019
4249
  min: "-1",
@@ -6023,10 +4253,10 @@ var PlaylistVisualization = ({
6023
4253
  onChange: (e) => setTrackPan(trackIndex, parseFloat(e.target.value))
6024
4254
  }
6025
4255
  ),
6026
- /* @__PURE__ */ jsx10("span", { children: "R" })
4256
+ /* @__PURE__ */ jsx9("span", { children: "R" })
6027
4257
  ] })
6028
4258
  ] });
6029
- return /* @__PURE__ */ jsx10(
4259
+ return /* @__PURE__ */ jsx9(
6030
4260
  ControlSlot,
6031
4261
  {
6032
4262
  $height: slotHeight,
@@ -6037,7 +4267,7 @@ var PlaylistVisualization = ({
6037
4267
  );
6038
4268
  }) : void 0;
6039
4269
  return /* @__PURE__ */ jsxs2(DevicePixelRatioProvider, { children: [
6040
- /* @__PURE__ */ jsx10(
4270
+ /* @__PURE__ */ jsx9(
6041
4271
  PlaylistInfoContext.Provider,
6042
4272
  {
6043
4273
  value: {
@@ -6051,7 +4281,7 @@ var PlaylistVisualization = ({
6051
4281
  barWidth,
6052
4282
  barGap
6053
4283
  },
6054
- children: /* @__PURE__ */ jsx10(
4284
+ children: /* @__PURE__ */ jsx9(
6055
4285
  Playlist,
6056
4286
  {
6057
4287
  theme,
@@ -6069,8 +4299,8 @@ var PlaylistVisualization = ({
6069
4299
  trackControlsSlots,
6070
4300
  timescaleGapHeight: timeScaleHeight > 0 ? timeScaleHeight + 1 : 0,
6071
4301
  timescale: timeScaleHeight > 0 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
6072
- /* @__PURE__ */ jsx10(SmartScale, { renderTick }),
6073
- isLoopEnabled && /* @__PURE__ */ jsx10(
4302
+ /* @__PURE__ */ jsx9(SmartScale, { renderTick }),
4303
+ isLoopEnabled && /* @__PURE__ */ jsx9(
6074
4304
  TimescaleLoopRegion,
6075
4305
  {
6076
4306
  startPosition: Math.min(loopStart, loopEnd) * sampleRate / samplesPerPixel,
@@ -6116,7 +4346,7 @@ var PlaylistVisualization = ({
6116
4346
  const helpers = perTrackSpectrogramHelpers.get(track.id);
6117
4347
  const trackCfg = helpers == null ? void 0 : helpers.config;
6118
4348
  if (!(trackCfg == null ? void 0 : trackCfg.labels) || !helpers) return null;
6119
- return /* @__PURE__ */ jsx10(
4349
+ return /* @__PURE__ */ jsx9(
6120
4350
  SpectrogramLabels,
6121
4351
  {
6122
4352
  waveHeight,
@@ -6134,7 +4364,7 @@ var PlaylistVisualization = ({
6134
4364
  trackClipPeaks.map((clip, clipIndex) => {
6135
4365
  const peaksData = clip.peaks;
6136
4366
  const width = peaksData.length;
6137
- return /* @__PURE__ */ jsx10(
4367
+ return /* @__PURE__ */ jsx9(
6138
4368
  Clip,
6139
4369
  {
6140
4370
  clipId: clip.clipId,
@@ -6164,7 +4394,7 @@ var PlaylistVisualization = ({
6164
4394
  selectTrack(trackIndex);
6165
4395
  },
6166
4396
  children: peaksData.data.map((channelPeaks, channelIndex) => {
6167
- return /* @__PURE__ */ jsx10(
4397
+ return /* @__PURE__ */ jsx9(
6168
4398
  ChannelWithProgress,
6169
4399
  {
6170
4400
  index: channelIndex,
@@ -6191,14 +4421,12 @@ var PlaylistVisualization = ({
6191
4421
  );
6192
4422
  }),
6193
4423
  (recordingState == null ? void 0 : recordingState.isRecording) && recordingState.trackId === track.id && ((_d2 = recordingState.peaks[0]) == null ? void 0 : _d2.length) > 0 && (() => {
6194
- const audioCtx = getGlobalAudioContext5();
6195
- const outputLatency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
6196
- const lookAhead = getLookAhead();
6197
- const latencyOffsetSamples = audibleLatencySamples(
6198
- outputLatency,
6199
- lookAhead,
4424
+ const latencyOffsetSamples = resolveRecordingOffsetSamples({
4425
+ overrideSeconds: recordingState.latencyOffset,
4426
+ outputLatency: getOutputLatency(),
4427
+ lookAhead: getLookAhead(),
6200
4428
  sampleRate
6201
- );
4429
+ });
6202
4430
  const latencyPixels = Math.floor(latencyOffsetSamples / samplesPerPixel);
6203
4431
  const skipPeakElements = latencyPixels * 2;
6204
4432
  const previewDuration = Math.max(
@@ -6208,7 +4436,7 @@ var PlaylistVisualization = ({
6208
4436
  const previewChannels = (mono ? recordingState.peaks.slice(0, 1) : recordingState.peaks).map(
6209
4437
  (channelPeaks) => skipPeakElements > 0 && skipPeakElements < channelPeaks.length ? channelPeaks.subarray(skipPeakElements) : channelPeaks
6210
4438
  );
6211
- return /* @__PURE__ */ jsx10(
4439
+ return /* @__PURE__ */ jsx9(
6212
4440
  Clip,
6213
4441
  {
6214
4442
  clipId: "recording-preview",
@@ -6222,7 +4450,7 @@ var PlaylistVisualization = ({
6222
4450
  disableHeaderDrag: true,
6223
4451
  isSelected: track.id === selectedTrackId,
6224
4452
  trackId: track.id,
6225
- children: previewChannels.map((channelPeaks, chIdx) => /* @__PURE__ */ jsx10(
4453
+ children: previewChannels.map((channelPeaks, chIdx) => /* @__PURE__ */ jsx9(
6226
4454
  ChannelWithProgress,
6227
4455
  {
6228
4456
  index: chIdx,
@@ -6244,11 +4472,11 @@ var PlaylistVisualization = ({
6244
4472
  track.id
6245
4473
  );
6246
4474
  }),
6247
- annotations.length > 0 && annotationIntegration && /* @__PURE__ */ jsx10(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
4475
+ annotations.length > 0 && annotationIntegration && /* @__PURE__ */ jsx9(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
6248
4476
  const startPosition = annotation.start * sampleRate / samplesPerPixel;
6249
4477
  const endPosition = annotation.end * sampleRate / samplesPerPixel;
6250
4478
  const label = getAnnotationBoxLabel ? getAnnotationBoxLabel(annotation, index) : annotation.id;
6251
- return /* @__PURE__ */ jsx10(
4479
+ return /* @__PURE__ */ jsx9(
6252
4480
  annotationIntegration.AnnotationBox,
6253
4481
  {
6254
4482
  annotationId: annotation.id,
@@ -6264,7 +4492,7 @@ var PlaylistVisualization = ({
6264
4492
  annotation.id
6265
4493
  );
6266
4494
  }) }),
6267
- selectionStart !== selectionEnd && /* @__PURE__ */ jsx10(
4495
+ selectionStart !== selectionEnd && /* @__PURE__ */ jsx9(
6268
4496
  Selection,
6269
4497
  {
6270
4498
  startPosition: Math.min(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
@@ -6272,7 +4500,7 @@ var PlaylistVisualization = ({
6272
4500
  color: theme.selectionColor
6273
4501
  }
6274
4502
  ),
6275
- (isPlaying || selectionStart === selectionEnd) && (renderPlayhead ? /* @__PURE__ */ jsx10(
4503
+ (isPlaying || selectionStart === selectionEnd) && (renderPlayhead ? /* @__PURE__ */ jsx9(
6276
4504
  CustomPlayhead,
6277
4505
  {
6278
4506
  renderPlayhead,
@@ -6280,14 +4508,14 @@ var PlaylistVisualization = ({
6280
4508
  samplesPerPixel,
6281
4509
  sampleRate
6282
4510
  }
6283
- ) : /* @__PURE__ */ jsx10(AnimatedPlayhead, { color: theme.playheadColor }))
4511
+ ) : /* @__PURE__ */ jsx9(AnimatedPlayhead, { color: theme.playheadColor }))
6284
4512
  ] })
6285
4513
  }
6286
4514
  )
6287
4515
  }
6288
4516
  ),
6289
4517
  (spectrogram == null ? void 0 : spectrogram.SettingsModal) && typeof document !== "undefined" && createPortal(
6290
- /* @__PURE__ */ jsx10(
4518
+ /* @__PURE__ */ jsx9(
6291
4519
  spectrogram.SettingsModal,
6292
4520
  {
6293
4521
  open: settingsModalTrackId !== null,
@@ -6307,8 +4535,8 @@ var PlaylistVisualization = ({
6307
4535
  };
6308
4536
 
6309
4537
  // src/components/PlaylistAnnotationList.tsx
6310
- import { useCallback as useCallback23 } from "react";
6311
- import { jsx as jsx11 } from "react/jsx-runtime";
4538
+ import { useCallback as useCallback17 } from "react";
4539
+ import { jsx as jsx10 } from "react/jsx-runtime";
6312
4540
  var PlaylistAnnotationList = ({
6313
4541
  height,
6314
4542
  renderAnnotationItem,
@@ -6322,7 +4550,7 @@ var PlaylistAnnotationList = ({
6322
4550
  const integration = useAnnotationIntegration();
6323
4551
  const { setAnnotations } = usePlaylistControls();
6324
4552
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints, continuousPlay };
6325
- const handleAnnotationUpdate = useCallback23(
4553
+ const handleAnnotationUpdate = useCallback17(
6326
4554
  (updatedAnnotations) => {
6327
4555
  setAnnotations(updatedAnnotations);
6328
4556
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6330,7 +4558,7 @@ var PlaylistAnnotationList = ({
6330
4558
  [setAnnotations, onAnnotationUpdate]
6331
4559
  );
6332
4560
  const { AnnotationText } = integration;
6333
- return /* @__PURE__ */ jsx11(
4561
+ return /* @__PURE__ */ jsx10(
6334
4562
  AnnotationText,
6335
4563
  {
6336
4564
  annotations,
@@ -6349,7 +4577,7 @@ var PlaylistAnnotationList = ({
6349
4577
  };
6350
4578
 
6351
4579
  // src/components/Waveform.tsx
6352
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs3 } from "react/jsx-runtime";
4580
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
6353
4581
  var Waveform = ({
6354
4582
  renderTrackControls,
6355
4583
  renderTick,
@@ -6374,7 +4602,7 @@ var Waveform = ({
6374
4602
  const clipInteractionEnabled = useClipInteractionEnabled();
6375
4603
  const effectiveInteractiveClips = interactiveClips || clipInteractionEnabled;
6376
4604
  return /* @__PURE__ */ jsxs3(Fragment3, { children: [
6377
- /* @__PURE__ */ jsx12(
4605
+ /* @__PURE__ */ jsx11(
6378
4606
  PlaylistVisualization,
6379
4607
  {
6380
4608
  renderTrackControls,
@@ -6391,7 +4619,7 @@ var Waveform = ({
6391
4619
  recordingState
6392
4620
  }
6393
4621
  ),
6394
- annotations.length > 0 && /* @__PURE__ */ jsx12(
4622
+ annotations.length > 0 && /* @__PURE__ */ jsx11(
6395
4623
  PlaylistAnnotationList,
6396
4624
  {
6397
4625
  height: annotationTextHeight,
@@ -6406,7 +4634,7 @@ var Waveform = ({
6406
4634
  };
6407
4635
 
6408
4636
  // src/components/MediaElementPlaylist.tsx
6409
- import { useContext as useContext7, useRef as useRef23, useState as useState18, useCallback as useCallback24 } from "react";
4637
+ import { useContext as useContext7, useRef as useRef17, useState as useState12, useCallback as useCallback18 } from "react";
6410
4638
  import { DragDropProvider } from "@dnd-kit/react";
6411
4639
  import { RestrictToHorizontalAxis } from "@dnd-kit/abstract/modifiers";
6412
4640
  import {
@@ -6442,9 +4670,9 @@ var noDropAnimationPlugins = (defaults) => {
6442
4670
  };
6443
4671
 
6444
4672
  // src/components/AnimatedMediaElementPlayhead.tsx
6445
- import { useRef as useRef21, useEffect as useEffect15 } from "react";
4673
+ import { useRef as useRef15, useEffect as useEffect10 } from "react";
6446
4674
  import styled5 from "styled-components";
6447
- import { jsx as jsx13 } from "react/jsx-runtime";
4675
+ import { jsx as jsx12 } from "react/jsx-runtime";
6448
4676
  var PlayheadLine2 = styled5.div`
6449
4677
  position: absolute;
6450
4678
  top: 0;
@@ -6459,11 +4687,11 @@ var PlayheadLine2 = styled5.div`
6459
4687
  var AnimatedMediaElementPlayhead = ({
6460
4688
  color = "#ff0000"
6461
4689
  }) => {
6462
- const playheadRef = useRef21(null);
6463
- const animationFrameRef = useRef21(null);
4690
+ const playheadRef = useRef15(null);
4691
+ const animationFrameRef = useRef15(null);
6464
4692
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
6465
4693
  const { samplesPerPixel, sampleRate, progressBarWidth } = useMediaElementData();
6466
- useEffect15(() => {
4694
+ useEffect10(() => {
6467
4695
  const updatePosition = () => {
6468
4696
  var _a;
6469
4697
  if (playheadRef.current) {
@@ -6487,7 +4715,7 @@ var AnimatedMediaElementPlayhead = ({
6487
4715
  }
6488
4716
  };
6489
4717
  }, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef]);
6490
- useEffect15(() => {
4718
+ useEffect10(() => {
6491
4719
  var _a;
6492
4720
  if (!isPlaying && playheadRef.current) {
6493
4721
  const time = (_a = currentTimeRef.current) != null ? _a : 0;
@@ -6495,11 +4723,11 @@ var AnimatedMediaElementPlayhead = ({
6495
4723
  playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
6496
4724
  }
6497
4725
  });
6498
- return /* @__PURE__ */ jsx13(PlayheadLine2, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
4726
+ return /* @__PURE__ */ jsx12(PlayheadLine2, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
6499
4727
  };
6500
4728
 
6501
4729
  // src/components/ChannelWithMediaElementProgress.tsx
6502
- import { useRef as useRef22, useEffect as useEffect16 } from "react";
4730
+ import { useRef as useRef16, useEffect as useEffect11 } from "react";
6503
4731
  import styled6 from "styled-components";
6504
4732
  import {
6505
4733
  SmartChannel as SmartChannel2,
@@ -6507,7 +4735,7 @@ import {
6507
4735
  usePlaylistInfo as usePlaylistInfo2,
6508
4736
  waveformColorToCss as waveformColorToCss3
6509
4737
  } from "@waveform-playlist/ui-components";
6510
- import { jsx as jsx14, jsxs as jsxs4 } from "react/jsx-runtime";
4738
+ import { jsx as jsx13, jsxs as jsxs4 } from "react/jsx-runtime";
6511
4739
  var ChannelWrapper2 = styled6.div`
6512
4740
  position: relative;
6513
4741
  `;
@@ -6544,14 +4772,14 @@ var ChannelWithMediaElementProgress = (_a) => {
6544
4772
  "clipStartSample",
6545
4773
  "clipDurationSamples"
6546
4774
  ]);
6547
- const progressRef = useRef22(null);
6548
- const animationFrameRef = useRef22(null);
4775
+ const progressRef = useRef16(null);
4776
+ const animationFrameRef = useRef16(null);
6549
4777
  const theme = useTheme3();
6550
4778
  const { waveHeight } = usePlaylistInfo2();
6551
4779
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
6552
4780
  const { samplesPerPixel, sampleRate } = useMediaElementData();
6553
4781
  const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
6554
- useEffect16(() => {
4782
+ useEffect11(() => {
6555
4783
  const updateProgress = () => {
6556
4784
  var _a2;
6557
4785
  if (progressRef.current) {
@@ -6593,7 +4821,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6593
4821
  smartChannelProps.length,
6594
4822
  currentTimeRef
6595
4823
  ]);
6596
- useEffect16(() => {
4824
+ useEffect11(() => {
6597
4825
  var _a2;
6598
4826
  if (!isPlaying && progressRef.current) {
6599
4827
  const currentTime = (_a2 = currentTimeRef.current) != null ? _a2 : 0;
@@ -6620,7 +4848,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6620
4848
  }
6621
4849
  const backgroundCss = waveformColorToCss3(backgroundColor);
6622
4850
  return /* @__PURE__ */ jsxs4(ChannelWrapper2, { children: [
6623
- /* @__PURE__ */ jsx14(
4851
+ /* @__PURE__ */ jsx13(
6624
4852
  Background2,
6625
4853
  {
6626
4854
  $color: backgroundCss,
@@ -6629,7 +4857,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6629
4857
  $width: smartChannelProps.length
6630
4858
  }
6631
4859
  ),
6632
- /* @__PURE__ */ jsx14(
4860
+ /* @__PURE__ */ jsx13(
6633
4861
  ProgressOverlay2,
6634
4862
  {
6635
4863
  ref: progressRef,
@@ -6638,12 +4866,12 @@ var ChannelWithMediaElementProgress = (_a) => {
6638
4866
  $top: smartChannelProps.index * waveHeight
6639
4867
  }
6640
4868
  ),
6641
- /* @__PURE__ */ jsx14(ChannelContainer2, { children: /* @__PURE__ */ jsx14(SmartChannel2, __spreadProps(__spreadValues({}, smartChannelProps), { isSelected: true, transparentBackground: true })) })
4869
+ /* @__PURE__ */ jsx13(ChannelContainer2, { children: /* @__PURE__ */ jsx13(SmartChannel2, __spreadProps(__spreadValues({}, smartChannelProps), { isSelected: true, transparentBackground: true })) })
6642
4870
  ] });
6643
4871
  };
6644
4872
 
6645
4873
  // src/components/MediaElementPlaylist.tsx
6646
- import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs5 } from "react/jsx-runtime";
4874
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
6647
4875
  var ZERO_REF = { current: 0 };
6648
4876
  var CustomMediaElementPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {
6649
4877
  var _a;
@@ -6688,11 +4916,11 @@ var MediaElementPlaylist = ({
6688
4916
  fadeIn,
6689
4917
  fadeOut
6690
4918
  } = useMediaElementData();
6691
- const [selectionStart, setSelectionStart] = useState18(0);
6692
- const [selectionEnd, setSelectionEnd] = useState18(0);
6693
- const [isSelecting, setIsSelecting] = useState18(false);
6694
- const scrollContainerRef = useRef23(null);
6695
- const handleScrollContainerRef = useCallback24(
4919
+ const [selectionStart, setSelectionStart] = useState12(0);
4920
+ const [selectionEnd, setSelectionEnd] = useState12(0);
4921
+ const [isSelecting, setIsSelecting] = useState12(false);
4922
+ const scrollContainerRef = useRef17(null);
4923
+ const handleScrollContainerRef = useCallback18(
6696
4924
  (el) => {
6697
4925
  scrollContainerRef.current = el;
6698
4926
  setScrollContainer(el);
@@ -6700,7 +4928,7 @@ var MediaElementPlaylist = ({
6700
4928
  [setScrollContainer]
6701
4929
  );
6702
4930
  const tracksFullWidth = Math.floor(duration * sampleRate / samplesPerPixel);
6703
- const handleAnnotationClick = useCallback24(
4931
+ const handleAnnotationClick = useCallback18(
6704
4932
  (annotation) => __async(null, null, function* () {
6705
4933
  setActiveAnnotationId(annotation.id);
6706
4934
  try {
@@ -6715,7 +4943,7 @@ var MediaElementPlaylist = ({
6715
4943
  }),
6716
4944
  [setActiveAnnotationId, play]
6717
4945
  );
6718
- const handleAnnotationUpdate = useCallback24(
4946
+ const handleAnnotationUpdate = useCallback18(
6719
4947
  (updatedAnnotations) => {
6720
4948
  setAnnotations(updatedAnnotations);
6721
4949
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6729,8 +4957,8 @@ var MediaElementPlaylist = ({
6729
4957
  duration,
6730
4958
  linkEndpoints: linkEndpointsProp
6731
4959
  });
6732
- const mouseDownTimeRef = useRef23(0);
6733
- const handleMouseDown = useCallback24(
4960
+ const mouseDownTimeRef = useRef17(0);
4961
+ const handleMouseDown = useCallback18(
6734
4962
  (e) => {
6735
4963
  const rect = e.currentTarget.getBoundingClientRect();
6736
4964
  const x = e.clientX - rect.left;
@@ -6742,7 +4970,7 @@ var MediaElementPlaylist = ({
6742
4970
  },
6743
4971
  [samplesPerPixel, sampleRate]
6744
4972
  );
6745
- const handleMouseMove = useCallback24(
4973
+ const handleMouseMove = useCallback18(
6746
4974
  (e) => {
6747
4975
  if (!isSelecting) return;
6748
4976
  const rect = e.currentTarget.getBoundingClientRect();
@@ -6752,7 +4980,7 @@ var MediaElementPlaylist = ({
6752
4980
  },
6753
4981
  [isSelecting, samplesPerPixel, sampleRate]
6754
4982
  );
6755
- const handleMouseUp = useCallback24(
4983
+ const handleMouseUp = useCallback18(
6756
4984
  (e) => {
6757
4985
  if (!isSelecting) return;
6758
4986
  setIsSelecting(false);
@@ -6782,9 +5010,9 @@ var MediaElementPlaylist = ({
6782
5010
  [isSelecting, selectionStart, samplesPerPixel, sampleRate, seekTo, isPlaying, playoutRef, play]
6783
5011
  );
6784
5012
  if (peaksDataArray.length === 0) {
6785
- return /* @__PURE__ */ jsx15("div", { className, children: "Loading waveform..." });
5013
+ return /* @__PURE__ */ jsx14("div", { className, children: "Loading waveform..." });
6786
5014
  }
6787
- return /* @__PURE__ */ jsx15(DevicePixelRatioProvider2, { children: /* @__PURE__ */ jsx15(
5015
+ return /* @__PURE__ */ jsx14(DevicePixelRatioProvider2, { children: /* @__PURE__ */ jsx14(
6788
5016
  PlaylistInfoContext2.Provider,
6789
5017
  {
6790
5018
  value: {
@@ -6798,7 +5026,7 @@ var MediaElementPlaylist = ({
6798
5026
  barWidth,
6799
5027
  barGap
6800
5028
  },
6801
- children: /* @__PURE__ */ jsx15(
5029
+ children: /* @__PURE__ */ jsx14(
6802
5030
  Playlist2,
6803
5031
  {
6804
5032
  theme,
@@ -6812,11 +5040,11 @@ var MediaElementPlaylist = ({
6812
5040
  onTracksMouseUp: handleMouseUp,
6813
5041
  scrollContainerRef: handleScrollContainerRef,
6814
5042
  isSelecting,
6815
- timescale: timeScaleHeight > 0 ? /* @__PURE__ */ jsx15(SmartScale2, {}) : void 0,
5043
+ timescale: timeScaleHeight > 0 ? /* @__PURE__ */ jsx14(SmartScale2, {}) : void 0,
6816
5044
  children: /* @__PURE__ */ jsxs5(Fragment4, { children: [
6817
5045
  peaksDataArray.map((trackClipPeaks, trackIndex) => {
6818
5046
  const maxChannels = trackClipPeaks.length > 0 ? Math.max(...trackClipPeaks.map((clip) => clip.peaks.data.length)) : 1;
6819
- return /* @__PURE__ */ jsx15(
5047
+ return /* @__PURE__ */ jsx14(
6820
5048
  TrackComponent2,
6821
5049
  {
6822
5050
  numChannels: maxChannels,
@@ -6844,7 +5072,7 @@ var MediaElementPlaylist = ({
6844
5072
  isSelected: true,
6845
5073
  trackId: `media-element-track-${trackIndex}`,
6846
5074
  children: [
6847
- peaksData.data.map((channelPeaks, channelIndex) => /* @__PURE__ */ jsx15(
5075
+ peaksData.data.map((channelPeaks, channelIndex) => /* @__PURE__ */ jsx14(
6848
5076
  ChannelWithMediaElementProgress,
6849
5077
  {
6850
5078
  index: channelIndex,
@@ -6856,7 +5084,7 @@ var MediaElementPlaylist = ({
6856
5084
  },
6857
5085
  `${trackIndex}-${clipIndex}-${channelIndex}`
6858
5086
  )),
6859
- showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ jsx15(
5087
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ jsx14(
6860
5088
  FadeOverlay,
6861
5089
  {
6862
5090
  left: 0,
@@ -6865,7 +5093,7 @@ var MediaElementPlaylist = ({
6865
5093
  curveType: fadeIn.type
6866
5094
  }
6867
5095
  ),
6868
- showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ jsx15(
5096
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ jsx14(
6869
5097
  FadeOverlay,
6870
5098
  {
6871
5099
  left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
@@ -6883,7 +5111,7 @@ var MediaElementPlaylist = ({
6883
5111
  trackIndex
6884
5112
  );
6885
5113
  }),
6886
- annotations.length > 0 && annotationIntegration && /* @__PURE__ */ jsx15(
5114
+ annotations.length > 0 && annotationIntegration && /* @__PURE__ */ jsx14(
6887
5115
  DragDropProvider,
6888
5116
  {
6889
5117
  onDragStart,
@@ -6891,11 +5119,11 @@ var MediaElementPlaylist = ({
6891
5119
  onDragEnd,
6892
5120
  modifiers: editable ? [RestrictToHorizontalAxis] : [],
6893
5121
  plugins: noDropAnimationPlugins,
6894
- children: /* @__PURE__ */ jsx15(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
5122
+ children: /* @__PURE__ */ jsx14(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
6895
5123
  const startPosition = annotation.start * sampleRate / samplesPerPixel;
6896
5124
  const endPosition = annotation.end * sampleRate / samplesPerPixel;
6897
5125
  const label = getAnnotationBoxLabel ? getAnnotationBoxLabel(annotation, index) : annotation.id;
6898
- return /* @__PURE__ */ jsx15(
5126
+ return /* @__PURE__ */ jsx14(
6899
5127
  annotationIntegration.AnnotationBox,
6900
5128
  {
6901
5129
  annotationId: annotation.id,
@@ -6913,7 +5141,7 @@ var MediaElementPlaylist = ({
6913
5141
  }) })
6914
5142
  }
6915
5143
  ),
6916
- selectionStart !== selectionEnd && /* @__PURE__ */ jsx15(
5144
+ selectionStart !== selectionEnd && /* @__PURE__ */ jsx14(
6917
5145
  Selection2,
6918
5146
  {
6919
5147
  startPosition: Math.min(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
@@ -6921,7 +5149,7 @@ var MediaElementPlaylist = ({
6921
5149
  color: theme.selectionColor
6922
5150
  }
6923
5151
  ),
6924
- renderPlayhead ? /* @__PURE__ */ jsx15(
5152
+ renderPlayhead ? /* @__PURE__ */ jsx14(
6925
5153
  CustomMediaElementPlayhead,
6926
5154
  {
6927
5155
  renderPlayhead,
@@ -6929,7 +5157,7 @@ var MediaElementPlaylist = ({
6929
5157
  samplesPerPixel,
6930
5158
  sampleRate
6931
5159
  }
6932
- ) : /* @__PURE__ */ jsx15(AnimatedMediaElementPlayhead, { color: theme.playheadColor })
5160
+ ) : /* @__PURE__ */ jsx14(AnimatedMediaElementPlayhead, { color: theme.playheadColor })
6933
5161
  ] })
6934
5162
  }
6935
5163
  )
@@ -6938,8 +5166,8 @@ var MediaElementPlaylist = ({
6938
5166
  };
6939
5167
 
6940
5168
  // src/components/MediaElementAnnotationList.tsx
6941
- import { useCallback as useCallback25 } from "react";
6942
- import { jsx as jsx16 } from "react/jsx-runtime";
5169
+ import { useCallback as useCallback19 } from "react";
5170
+ import { jsx as jsx15 } from "react/jsx-runtime";
6943
5171
  var MediaElementAnnotationList = ({
6944
5172
  height,
6945
5173
  renderAnnotationItem,
@@ -6954,7 +5182,7 @@ var MediaElementAnnotationList = ({
6954
5182
  const integration = useAnnotationIntegration();
6955
5183
  const { setAnnotations } = useMediaElementControls();
6956
5184
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints: false, continuousPlay };
6957
- const handleAnnotationUpdate = useCallback25(
5185
+ const handleAnnotationUpdate = useCallback19(
6958
5186
  (updatedAnnotations) => {
6959
5187
  setAnnotations(updatedAnnotations);
6960
5188
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6962,7 +5190,7 @@ var MediaElementAnnotationList = ({
6962
5190
  [setAnnotations, onAnnotationUpdate]
6963
5191
  );
6964
5192
  const { AnnotationText } = integration;
6965
- return /* @__PURE__ */ jsx16(
5193
+ return /* @__PURE__ */ jsx15(
6966
5194
  AnnotationText,
6967
5195
  {
6968
5196
  annotations,
@@ -6981,7 +5209,7 @@ var MediaElementAnnotationList = ({
6981
5209
  };
6982
5210
 
6983
5211
  // src/components/MediaElementWaveform.tsx
6984
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs6 } from "react/jsx-runtime";
5212
+ import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
6985
5213
  var MediaElementWaveform = ({
6986
5214
  annotationTextHeight,
6987
5215
  getAnnotationBoxLabel,
@@ -6997,7 +5225,7 @@ var MediaElementWaveform = ({
6997
5225
  }) => {
6998
5226
  const { annotations } = useMediaElementState();
6999
5227
  return /* @__PURE__ */ jsxs6(Fragment5, { children: [
7000
- /* @__PURE__ */ jsx17(
5228
+ /* @__PURE__ */ jsx16(
7001
5229
  MediaElementPlaylist,
7002
5230
  {
7003
5231
  getAnnotationBoxLabel,
@@ -7009,7 +5237,7 @@ var MediaElementWaveform = ({
7009
5237
  className
7010
5238
  }
7011
5239
  ),
7012
- annotations.length > 0 && /* @__PURE__ */ jsx17(
5240
+ annotations.length > 0 && /* @__PURE__ */ jsx16(
7013
5241
  MediaElementAnnotationList,
7014
5242
  {
7015
5243
  height: annotationTextHeight,
@@ -7084,7 +5312,7 @@ var KeyboardShortcuts = ({
7084
5312
  };
7085
5313
 
7086
5314
  // src/components/ClipInteractionProvider.tsx
7087
- import React16, { useEffect as useEffect17, useMemo as useMemo7 } from "react";
5315
+ import React16, { useEffect as useEffect12, useMemo as useMemo6 } from "react";
7088
5316
  import { DragDropProvider as DragDropProvider2 } from "@dnd-kit/react";
7089
5317
  import { RestrictToHorizontalAxis as RestrictToHorizontalAxis2 } from "@dnd-kit/abstract/modifiers";
7090
5318
  import {
@@ -7174,7 +5402,7 @@ _SnapToGridModifier.configure = configurator2(_SnapToGridModifier);
7174
5402
  var SnapToGridModifier = _SnapToGridModifier;
7175
5403
 
7176
5404
  // src/components/ClipInteractionProvider.tsx
7177
- import { jsx as jsx18 } from "react/jsx-runtime";
5405
+ import { jsx as jsx17 } from "react/jsx-runtime";
7178
5406
  var NOOP_TRACKS_CHANGE = () => {
7179
5407
  };
7180
5408
  var ClipInteractionProvider = ({
@@ -7187,14 +5415,14 @@ var ClipInteractionProvider = ({
7187
5415
  const beatsAndBars = useBeatsAndBars();
7188
5416
  const useBeatsSnap = snap && beatsAndBars != null && beatsAndBars.scaleMode === "beats" && beatsAndBars.snapTo !== "off";
7189
5417
  const useTimescaleSnap = snap && !useBeatsSnap;
7190
- useEffect17(() => {
5418
+ useEffect12(() => {
7191
5419
  if (onTracksChange == null) {
7192
5420
  console.warn(
7193
5421
  "[waveform-playlist] ClipInteractionProvider: onTracksChange is not set on WaveformPlaylistProvider. Drag and trim edits will not be persisted."
7194
5422
  );
7195
5423
  }
7196
5424
  }, [onTracksChange]);
7197
- const snapSamplePosition = useMemo7(() => {
5425
+ const snapSamplePosition = useMemo6(() => {
7198
5426
  if (useBeatsSnap && beatsAndBars) {
7199
5427
  const { bpm, timeSignature, snapTo } = beatsAndBars;
7200
5428
  const gridTicks = snapTo === "bar" ? ticksPerBar2(timeSignature) : ticksPerBeat2(timeSignature);
@@ -7234,7 +5462,7 @@ var ClipInteractionProvider = ({
7234
5462
  },
7235
5463
  [handleDragStart, tracks, setSelectedTrackId]
7236
5464
  );
7237
- const modifiers = useMemo7(() => {
5465
+ const modifiers = useMemo6(() => {
7238
5466
  const mods = [RestrictToHorizontalAxis2];
7239
5467
  if (useBeatsSnap && beatsAndBars) {
7240
5468
  mods.push(
@@ -7259,7 +5487,7 @@ var ClipInteractionProvider = ({
7259
5487
  mods.push(ClipCollisionModifier.configure({ tracks, samplesPerPixel }));
7260
5488
  return mods;
7261
5489
  }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, tracks, samplesPerPixel, sampleRate]);
7262
- return /* @__PURE__ */ jsx18(ClipInteractionContextProvider, { value: true, children: /* @__PURE__ */ jsx18(
5490
+ return /* @__PURE__ */ jsx17(ClipInteractionContextProvider, { value: true, children: /* @__PURE__ */ jsx17(
7263
5491
  DragDropProvider2,
7264
5492
  {
7265
5493
  sensors,
@@ -7282,7 +5510,6 @@ export {
7282
5510
  ContinuousPlayCheckbox,
7283
5511
  DownloadAnnotationsButton,
7284
5512
  EditableCheckbox,
7285
- ExportWavButton,
7286
5513
  FastForwardButton,
7287
5514
  KeyboardShortcuts,
7288
5515
  LinkEndpointsCheckbox,
@@ -7305,17 +5532,10 @@ export {
7305
5532
  SpectrogramIntegrationProvider,
7306
5533
  StopButton,
7307
5534
  TimeFormatSelect,
7308
- Tone2 as Tone,
7309
5535
  Waveform,
7310
5536
  WaveformPlaylistProvider,
7311
5537
  ZoomInButton,
7312
5538
  ZoomOutButton,
7313
- createEffectChain,
7314
- createEffectInstance,
7315
- effectCategories,
7316
- effectDefinitions,
7317
- getEffectDefinition,
7318
- getEffectsByCategory,
7319
5539
  getShortcutLabel,
7320
5540
  getWaveformDataMetadata,
7321
5541
  loadPeaksFromWaveformData,
@@ -7324,22 +5544,16 @@ export {
7324
5544
  useAnnotationDragHandlers,
7325
5545
  useAnnotationIntegration,
7326
5546
  useAnnotationKeyboardControls,
7327
- useAudioTracks,
7328
5547
  useClipDragHandlers,
7329
5548
  useClipInteractionEnabled,
7330
5549
  useClipSplitting,
7331
5550
  useDragSensors,
7332
- useDynamicEffects,
7333
- useDynamicTracks,
7334
- useExportWav,
7335
5551
  useKeyboardShortcuts,
7336
- useMasterAnalyser,
7337
5552
  useMasterVolume,
7338
5553
  useMediaElementAnimation,
7339
5554
  useMediaElementControls,
7340
5555
  useMediaElementData,
7341
5556
  useMediaElementState,
7342
- useOutputMeter,
7343
5557
  usePlaybackAnimation,
7344
5558
  usePlaybackShortcuts,
7345
5559
  usePlaylistControls,
@@ -7348,7 +5562,6 @@ export {
7348
5562
  usePlaylistState,
7349
5563
  useSpectrogramIntegration,
7350
5564
  useTimeFormat,
7351
- useTrackDynamicEffects,
7352
5565
  useZoomControls,
7353
5566
  waveformDataToPeaks
7354
5567
  };