@waveform-playlist/browser 13.1.2 → 14.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({
@@ -1306,14 +1112,18 @@ function useAnnotationKeyboardControls({
1306
1112
  continuousPlay = false,
1307
1113
  enabled = true,
1308
1114
  scrollContainerRef,
1309
- onPlay
1115
+ onPlay,
1116
+ samplesPerPixel: samplesPerPixelProp,
1117
+ sampleRate: sampleRateProp
1310
1118
  }) {
1311
- const { samplesPerPixel, sampleRate } = usePlaylistData();
1312
- const activeIndex = useMemo3(() => {
1119
+ const playlistData = usePlaylistDataOptional();
1120
+ const samplesPerPixel = samplesPerPixelProp != null ? samplesPerPixelProp : playlistData == null ? void 0 : playlistData.samplesPerPixel;
1121
+ const sampleRate = sampleRateProp != null ? sampleRateProp : playlistData == null ? void 0 : playlistData.sampleRate;
1122
+ const activeIndex = useMemo2(() => {
1313
1123
  if (!activeAnnotationId) return -1;
1314
1124
  return annotations.findIndex((a) => a.id === activeAnnotationId);
1315
1125
  }, [annotations, activeAnnotationId]);
1316
- const scrollToAnnotation = useCallback11(
1126
+ const scrollToAnnotation = useCallback10(
1317
1127
  (annotationId) => {
1318
1128
  if (!(scrollContainerRef == null ? void 0 : scrollContainerRef.current) || !samplesPerPixel || !sampleRate) return;
1319
1129
  const annotation = annotations.find((a) => a.id === annotationId);
@@ -1336,12 +1146,12 @@ function useAnnotationKeyboardControls({
1336
1146
  },
1337
1147
  [annotations, scrollContainerRef, samplesPerPixel, sampleRate]
1338
1148
  );
1339
- useEffect3(() => {
1149
+ useEffect2(() => {
1340
1150
  if (activeAnnotationId && (scrollContainerRef == null ? void 0 : scrollContainerRef.current) && samplesPerPixel && sampleRate) {
1341
1151
  scrollToAnnotation(activeAnnotationId);
1342
1152
  }
1343
1153
  }, [activeAnnotationId, scrollToAnnotation, scrollContainerRef, samplesPerPixel, sampleRate]);
1344
- const moveStartBoundary = useCallback11(
1154
+ const moveStartBoundary = useCallback10(
1345
1155
  (delta) => {
1346
1156
  if (activeIndex < 0) return;
1347
1157
  const annotation = annotations[activeIndex];
@@ -1370,7 +1180,7 @@ function useAnnotationKeyboardControls({
1370
1180
  },
1371
1181
  [annotations, activeIndex, linkEndpoints, onAnnotationsChange]
1372
1182
  );
1373
- const moveEndBoundary = useCallback11(
1183
+ const moveEndBoundary = useCallback10(
1374
1184
  (delta) => {
1375
1185
  if (activeIndex < 0) return;
1376
1186
  const annotation = annotations[activeIndex];
@@ -1430,7 +1240,7 @@ function useAnnotationKeyboardControls({
1430
1240
  },
1431
1241
  [annotations, activeIndex, duration, linkEndpoints, onAnnotationsChange]
1432
1242
  );
1433
- const selectPrevious = useCallback11(() => {
1243
+ const selectPrevious = useCallback10(() => {
1434
1244
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1435
1245
  if (activeIndex <= 0) {
1436
1246
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
@@ -1438,7 +1248,7 @@ function useAnnotationKeyboardControls({
1438
1248
  onActiveAnnotationChange(annotations[activeIndex - 1].id);
1439
1249
  }
1440
1250
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1441
- const selectNext = useCallback11(() => {
1251
+ const selectNext = useCallback10(() => {
1442
1252
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1443
1253
  if (activeIndex < 0 || activeIndex >= annotations.length - 1) {
1444
1254
  onActiveAnnotationChange(annotations[0].id);
@@ -1446,25 +1256,25 @@ function useAnnotationKeyboardControls({
1446
1256
  onActiveAnnotationChange(annotations[activeIndex + 1].id);
1447
1257
  }
1448
1258
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1449
- const selectFirst = useCallback11(() => {
1259
+ const selectFirst = useCallback10(() => {
1450
1260
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1451
1261
  onActiveAnnotationChange(annotations[0].id);
1452
1262
  }, [annotations, onActiveAnnotationChange]);
1453
- const selectLast = useCallback11(() => {
1263
+ const selectLast = useCallback10(() => {
1454
1264
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1455
1265
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
1456
1266
  }, [annotations, onActiveAnnotationChange]);
1457
- const clearSelection = useCallback11(() => {
1267
+ const clearSelection = useCallback10(() => {
1458
1268
  if (!onActiveAnnotationChange) return;
1459
1269
  onActiveAnnotationChange(null);
1460
1270
  }, [onActiveAnnotationChange]);
1461
- const playActiveAnnotation = useCallback11(() => {
1271
+ const playActiveAnnotation = useCallback10(() => {
1462
1272
  if (activeIndex < 0 || !onPlay) return;
1463
1273
  const annotation = annotations[activeIndex];
1464
1274
  const playDuration = !continuousPlay ? annotation.end - annotation.start : void 0;
1465
1275
  onPlay(annotation.start, playDuration);
1466
1276
  }, [annotations, activeIndex, continuousPlay, onPlay]);
1467
- const activeAnnotationShortcuts = useMemo3(
1277
+ const activeAnnotationShortcuts = useMemo2(
1468
1278
  () => [
1469
1279
  {
1470
1280
  key: "[",
@@ -1501,7 +1311,7 @@ function useAnnotationKeyboardControls({
1501
1311
  ],
1502
1312
  [moveStartBoundary, moveEndBoundary, playActiveAnnotation]
1503
1313
  );
1504
- const navigationShortcuts = useMemo3(
1314
+ const navigationShortcuts = useMemo2(
1505
1315
  () => [
1506
1316
  {
1507
1317
  key: "ArrowUp",
@@ -1528,1452 +1338,65 @@ function useAnnotationKeyboardControls({
1528
1338
  preventDefault: true
1529
1339
  },
1530
1340
  {
1531
- key: "Home",
1532
- action: selectFirst,
1533
- description: "Select first annotation",
1534
- preventDefault: true
1535
- },
1536
- {
1537
- key: "End",
1538
- action: selectLast,
1539
- description: "Select last annotation",
1540
- preventDefault: true
1541
- },
1542
- {
1543
- key: "Escape",
1544
- action: clearSelection,
1545
- description: "Deselect annotation",
1546
- preventDefault: true
1547
- }
1548
- ],
1549
- [selectPrevious, selectNext, selectFirst, selectLast, clearSelection]
1550
- );
1551
- useKeyboardShortcuts({
1552
- shortcuts: activeAnnotationShortcuts,
1553
- enabled: enabled && activeIndex >= 0
1554
- });
1555
- useKeyboardShortcuts({
1556
- shortcuts: navigationShortcuts,
1557
- enabled: enabled && annotations.length > 0 && !!onActiveAnnotationChange
1558
- });
1559
- return {
1560
- moveStartBoundary,
1561
- moveEndBoundary,
1562
- selectPrevious,
1563
- selectNext,
1564
- selectFirst,
1565
- selectLast,
1566
- clearSelection,
1567
- scrollToAnnotation,
1568
- playActiveAnnotation
1569
- };
1570
- }
1571
-
1572
- // src/hooks/useDynamicEffects.ts
1573
- import { useState as useState9, useCallback as useCallback12, useRef as useRef9, useEffect as useEffect4 } from "react";
1574
-
1575
- // src/effects/effectDefinitions.ts
1576
- var effectDefinitions = [
1577
- // === REVERB EFFECTS ===
1578
- {
1579
- id: "reverb",
1580
- name: "Reverb",
1581
- category: "reverb",
1582
- description: "Simple convolution reverb with adjustable decay time",
1583
- parameters: [
1584
- {
1585
- name: "decay",
1586
- label: "Decay",
1587
- type: "number",
1588
- min: 0.1,
1589
- max: 10,
1590
- step: 0.1,
1591
- default: 1.5,
1592
- unit: "s"
1593
- },
1594
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1595
- ]
1596
- },
1597
- {
1598
- id: "freeverb",
1599
- name: "Freeverb",
1600
- category: "reverb",
1601
- description: "Classic Schroeder/Moorer reverb with room size and dampening",
1602
- parameters: [
1603
- {
1604
- name: "roomSize",
1605
- label: "Room Size",
1606
- type: "number",
1607
- min: 0,
1608
- max: 1,
1609
- step: 0.01,
1610
- default: 0.7
1611
- },
1612
- {
1613
- name: "dampening",
1614
- label: "Dampening",
1615
- type: "number",
1616
- min: 0,
1617
- max: 1e4,
1618
- step: 100,
1619
- default: 3e3,
1620
- unit: "Hz"
1621
- },
1622
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1623
- ]
1624
- },
1625
- {
1626
- id: "jcReverb",
1627
- name: "JC Reverb",
1628
- category: "reverb",
1629
- description: "Attempt at Roland JC-120 chorus reverb emulation",
1630
- parameters: [
1631
- {
1632
- name: "roomSize",
1633
- label: "Room Size",
1634
- type: "number",
1635
- min: 0,
1636
- max: 1,
1637
- step: 0.01,
1638
- default: 0.5
1639
- },
1640
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1641
- ]
1642
- },
1643
- // === DELAY EFFECTS ===
1644
- {
1645
- id: "feedbackDelay",
1646
- name: "Feedback Delay",
1647
- category: "delay",
1648
- description: "Delay line with feedback for echo effects",
1649
- parameters: [
1650
- {
1651
- name: "delayTime",
1652
- label: "Delay Time",
1653
- type: "number",
1654
- min: 0,
1655
- max: 1,
1656
- step: 0.01,
1657
- default: 0.25,
1658
- unit: "s"
1659
- },
1660
- {
1661
- name: "feedback",
1662
- label: "Feedback",
1663
- type: "number",
1664
- min: 0,
1665
- max: 0.95,
1666
- step: 0.01,
1667
- default: 0.5
1668
- },
1669
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1670
- ]
1671
- },
1672
- {
1673
- id: "pingPongDelay",
1674
- name: "Ping Pong Delay",
1675
- category: "delay",
1676
- description: "Stereo delay bouncing between left and right channels",
1677
- parameters: [
1678
- {
1679
- name: "delayTime",
1680
- label: "Delay Time",
1681
- type: "number",
1682
- min: 0,
1683
- max: 1,
1684
- step: 0.01,
1685
- default: 0.25,
1686
- unit: "s"
1687
- },
1688
- {
1689
- name: "feedback",
1690
- label: "Feedback",
1691
- type: "number",
1692
- min: 0,
1693
- max: 0.95,
1694
- step: 0.01,
1695
- default: 0.5
1696
- },
1697
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1698
- ]
1699
- },
1700
- // === MODULATION EFFECTS ===
1701
- {
1702
- id: "chorus",
1703
- name: "Chorus",
1704
- category: "modulation",
1705
- description: "Creates thickness by layering detuned copies of the signal",
1706
- parameters: [
1707
- {
1708
- name: "frequency",
1709
- label: "Rate",
1710
- type: "number",
1711
- min: 0.1,
1712
- max: 10,
1713
- step: 0.1,
1714
- default: 1.5,
1715
- unit: "Hz"
1716
- },
1717
- {
1718
- name: "delayTime",
1719
- label: "Delay",
1720
- type: "number",
1721
- min: 0,
1722
- max: 20,
1723
- step: 0.5,
1724
- default: 3.5,
1725
- unit: "ms"
1726
- },
1727
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.7 },
1728
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1729
- ]
1730
- },
1731
- {
1732
- id: "phaser",
1733
- name: "Phaser",
1734
- category: "modulation",
1735
- description: "Classic phaser effect using allpass filters",
1736
- parameters: [
1737
- {
1738
- name: "frequency",
1739
- label: "Rate",
1740
- type: "number",
1741
- min: 0.1,
1742
- max: 10,
1743
- step: 0.1,
1744
- default: 0.5,
1745
- unit: "Hz"
1746
- },
1747
- { name: "octaves", label: "Octaves", type: "number", min: 1, max: 6, step: 1, default: 3 },
1748
- {
1749
- name: "baseFrequency",
1750
- label: "Base Freq",
1751
- type: "number",
1752
- min: 100,
1753
- max: 2e3,
1754
- step: 10,
1755
- default: 350,
1756
- unit: "Hz"
1757
- },
1758
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1759
- ]
1760
- },
1761
- {
1762
- id: "tremolo",
1763
- name: "Tremolo",
1764
- category: "modulation",
1765
- description: "Rhythmic volume modulation",
1766
- parameters: [
1767
- {
1768
- name: "frequency",
1769
- label: "Rate",
1770
- type: "number",
1771
- min: 0.1,
1772
- max: 20,
1773
- step: 0.1,
1774
- default: 4,
1775
- unit: "Hz"
1776
- },
1777
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 },
1778
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1779
- ]
1780
- },
1781
- {
1782
- id: "vibrato",
1783
- name: "Vibrato",
1784
- category: "modulation",
1785
- description: "Pitch modulation effect",
1786
- parameters: [
1787
- {
1788
- name: "frequency",
1789
- label: "Rate",
1790
- type: "number",
1791
- min: 0.1,
1792
- max: 20,
1793
- step: 0.1,
1794
- default: 5,
1795
- unit: "Hz"
1796
- },
1797
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.1 },
1798
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1799
- ]
1800
- },
1801
- {
1802
- id: "autoPanner",
1803
- name: "Auto Panner",
1804
- category: "modulation",
1805
- description: "Automatic left-right panning",
1806
- parameters: [
1807
- {
1808
- name: "frequency",
1809
- label: "Rate",
1810
- type: "number",
1811
- min: 0.1,
1812
- max: 10,
1813
- step: 0.1,
1814
- default: 1,
1815
- unit: "Hz"
1816
- },
1817
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 1 },
1818
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1819
- ]
1820
- },
1821
- // === FILTER EFFECTS ===
1822
- {
1823
- id: "autoFilter",
1824
- name: "Auto Filter",
1825
- category: "filter",
1826
- description: "Automated filter sweep with LFO",
1827
- parameters: [
1828
- {
1829
- name: "frequency",
1830
- label: "Rate",
1831
- type: "number",
1832
- min: 0.1,
1833
- max: 10,
1834
- step: 0.1,
1835
- default: 1,
1836
- unit: "Hz"
1837
- },
1838
- {
1839
- name: "baseFrequency",
1840
- label: "Base Freq",
1841
- type: "number",
1842
- min: 20,
1843
- max: 2e3,
1844
- step: 10,
1845
- default: 200,
1846
- unit: "Hz"
1847
- },
1848
- {
1849
- name: "octaves",
1850
- label: "Octaves",
1851
- type: "number",
1852
- min: 0.5,
1853
- max: 8,
1854
- step: 0.5,
1855
- default: 2.6
1856
- },
1857
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 1 },
1858
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1859
- ]
1860
- },
1861
- {
1862
- id: "autoWah",
1863
- name: "Auto Wah",
1864
- category: "filter",
1865
- description: "Envelope follower filter effect",
1866
- parameters: [
1867
- {
1868
- name: "baseFrequency",
1869
- label: "Base Freq",
1870
- type: "number",
1871
- min: 20,
1872
- max: 500,
1873
- step: 10,
1874
- default: 100,
1875
- unit: "Hz"
1876
- },
1877
- { name: "octaves", label: "Octaves", type: "number", min: 1, max: 8, step: 1, default: 6 },
1878
- {
1879
- name: "sensitivity",
1880
- label: "Sensitivity",
1881
- type: "number",
1882
- min: -40,
1883
- max: 0,
1884
- step: 1,
1885
- default: 0,
1886
- unit: "dB"
1887
- },
1888
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1889
- ]
1890
- },
1891
- {
1892
- id: "eq3",
1893
- name: "3-Band EQ",
1894
- category: "filter",
1895
- description: "Three band equalizer with low, mid, and high controls",
1896
- parameters: [
1897
- {
1898
- name: "low",
1899
- label: "Low",
1900
- type: "number",
1901
- min: -24,
1902
- max: 24,
1903
- step: 0.5,
1904
- default: 0,
1905
- unit: "dB"
1906
- },
1907
- {
1908
- name: "mid",
1909
- label: "Mid",
1910
- type: "number",
1911
- min: -24,
1912
- max: 24,
1913
- step: 0.5,
1914
- default: 0,
1915
- unit: "dB"
1916
- },
1917
- {
1918
- name: "high",
1919
- label: "High",
1920
- type: "number",
1921
- min: -24,
1922
- max: 24,
1923
- step: 0.5,
1924
- default: 0,
1925
- unit: "dB"
1926
- },
1927
- {
1928
- name: "lowFrequency",
1929
- label: "Low Freq",
1930
- type: "number",
1931
- min: 20,
1932
- max: 500,
1933
- step: 10,
1934
- default: 400,
1935
- unit: "Hz"
1936
- },
1937
- {
1938
- name: "highFrequency",
1939
- label: "High Freq",
1940
- type: "number",
1941
- min: 1e3,
1942
- max: 1e4,
1943
- step: 100,
1944
- default: 2500,
1945
- unit: "Hz"
1946
- }
1947
- ]
1948
- },
1949
- // === DISTORTION EFFECTS ===
1950
- {
1951
- id: "distortion",
1952
- name: "Distortion",
1953
- category: "distortion",
1954
- description: "Wave shaping distortion effect",
1955
- parameters: [
1956
- {
1957
- name: "distortion",
1958
- label: "Drive",
1959
- type: "number",
1960
- min: 0,
1961
- max: 1,
1962
- step: 0.01,
1963
- default: 0.4
1964
- },
1965
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1966
- ]
1967
- },
1968
- {
1969
- id: "bitCrusher",
1970
- name: "Bit Crusher",
1971
- category: "distortion",
1972
- description: "Reduces bit depth for lo-fi digital texture",
1973
- parameters: [
1974
- { name: "bits", label: "Bits", type: "number", min: 1, max: 16, step: 1, default: 4 },
1975
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1976
- ]
1977
- },
1978
- {
1979
- id: "chebyshev",
1980
- name: "Chebyshev",
1981
- category: "distortion",
1982
- description: "Waveshaping distortion using Chebyshev polynomials",
1983
- parameters: [
1984
- { name: "order", label: "Order", type: "number", min: 1, max: 100, step: 1, default: 50 },
1985
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1986
- ]
1987
- },
1988
- // === DYNAMICS EFFECTS ===
1989
- {
1990
- id: "compressor",
1991
- name: "Compressor",
1992
- category: "dynamics",
1993
- description: "Dynamic range compressor",
1994
- parameters: [
1995
- {
1996
- name: "threshold",
1997
- label: "Threshold",
1998
- type: "number",
1999
- min: -60,
2000
- max: 0,
2001
- step: 1,
2002
- default: -24,
2003
- unit: "dB"
2004
- },
2005
- { name: "ratio", label: "Ratio", type: "number", min: 1, max: 20, step: 0.5, default: 4 },
2006
- {
2007
- name: "attack",
2008
- label: "Attack",
2009
- type: "number",
2010
- min: 0,
2011
- max: 1,
2012
- step: 1e-3,
2013
- default: 3e-3,
2014
- unit: "s"
2015
- },
2016
- {
2017
- name: "release",
2018
- label: "Release",
2019
- type: "number",
2020
- min: 0,
2021
- max: 1,
2022
- step: 0.01,
2023
- default: 0.25,
2024
- unit: "s"
2025
- },
2026
- {
2027
- name: "knee",
2028
- label: "Knee",
2029
- type: "number",
2030
- min: 0,
2031
- max: 40,
2032
- step: 1,
2033
- default: 30,
2034
- unit: "dB"
2035
- }
2036
- ]
2037
- },
2038
- {
2039
- id: "limiter",
2040
- name: "Limiter",
2041
- category: "dynamics",
2042
- description: "Hard limiter to prevent clipping",
2043
- parameters: [
2044
- {
2045
- name: "threshold",
2046
- label: "Threshold",
2047
- type: "number",
2048
- min: -12,
2049
- max: 0,
2050
- step: 0.5,
2051
- default: -6,
2052
- unit: "dB"
2053
- }
2054
- ]
2055
- },
2056
- {
2057
- id: "gate",
2058
- name: "Gate",
2059
- category: "dynamics",
2060
- description: "Noise gate to silence signal below threshold",
2061
- parameters: [
2062
- {
2063
- name: "threshold",
2064
- label: "Threshold",
2065
- type: "number",
2066
- min: -100,
2067
- max: 0,
2068
- step: 1,
2069
- default: -40,
2070
- unit: "dB"
2071
- },
2072
- {
2073
- name: "attack",
2074
- label: "Attack",
2075
- type: "number",
2076
- min: 0,
2077
- max: 0.3,
2078
- step: 1e-3,
2079
- default: 1e-3,
2080
- unit: "s"
2081
- },
2082
- {
2083
- name: "release",
2084
- label: "Release",
2085
- type: "number",
2086
- min: 0,
2087
- max: 0.5,
2088
- step: 0.01,
2089
- default: 0.1,
2090
- unit: "s"
2091
- }
2092
- ]
2093
- },
2094
- // === SPATIAL EFFECTS ===
2095
- {
2096
- id: "stereoWidener",
2097
- name: "Stereo Widener",
2098
- category: "spatial",
2099
- description: "Expands or narrows the stereo image",
2100
- parameters: [
2101
- { name: "width", label: "Width", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
2102
- ]
2103
- }
2104
- ];
2105
- var getEffectDefinition = (id) => {
2106
- return effectDefinitions.find((def) => def.id === id);
2107
- };
2108
- var getEffectsByCategory = (category) => {
2109
- return effectDefinitions.filter((def) => def.category === category);
2110
- };
2111
- var effectCategories = [
2112
- { id: "reverb", name: "Reverb" },
2113
- { id: "delay", name: "Delay" },
2114
- { id: "modulation", name: "Modulation" },
2115
- { id: "filter", name: "Filter" },
2116
- { id: "distortion", name: "Distortion" },
2117
- { id: "dynamics", name: "Dynamics" },
2118
- { id: "spatial", name: "Spatial" }
2119
- ];
2120
-
2121
- // src/effects/effectFactory.ts
2122
- import {
2123
- Reverb,
2124
- Freeverb,
2125
- JCReverb,
2126
- FeedbackDelay,
2127
- PingPongDelay,
2128
- Chorus,
2129
- Phaser,
2130
- Tremolo,
2131
- Vibrato,
2132
- AutoPanner,
2133
- AutoFilter,
2134
- AutoWah,
2135
- EQ3,
2136
- Distortion,
2137
- BitCrusher,
2138
- Chebyshev,
2139
- Compressor,
2140
- Limiter,
2141
- Gate,
2142
- StereoWidener
2143
- } from "tone";
2144
- function asEffectConstructor(ctor) {
2145
- return ctor;
2146
- }
2147
- var effectConstructors = {
2148
- reverb: asEffectConstructor(Reverb),
2149
- freeverb: asEffectConstructor(Freeverb),
2150
- jcReverb: asEffectConstructor(JCReverb),
2151
- feedbackDelay: asEffectConstructor(FeedbackDelay),
2152
- pingPongDelay: asEffectConstructor(PingPongDelay),
2153
- chorus: asEffectConstructor(Chorus),
2154
- phaser: asEffectConstructor(Phaser),
2155
- tremolo: asEffectConstructor(Tremolo),
2156
- vibrato: asEffectConstructor(Vibrato),
2157
- autoPanner: asEffectConstructor(AutoPanner),
2158
- autoFilter: asEffectConstructor(AutoFilter),
2159
- autoWah: asEffectConstructor(AutoWah),
2160
- eq3: asEffectConstructor(EQ3),
2161
- distortion: asEffectConstructor(Distortion),
2162
- bitCrusher: asEffectConstructor(BitCrusher),
2163
- chebyshev: asEffectConstructor(Chebyshev),
2164
- compressor: asEffectConstructor(Compressor),
2165
- limiter: asEffectConstructor(Limiter),
2166
- gate: asEffectConstructor(Gate),
2167
- stereoWidener: asEffectConstructor(StereoWidener)
2168
- };
2169
- var instanceCounter = 0;
2170
- var generateInstanceId = () => {
2171
- return `effect_${Date.now()}_${++instanceCounter}`;
2172
- };
2173
- function createEffectInstance(definition, initialParams) {
2174
- const Constructor = effectConstructors[definition.id];
2175
- if (!Constructor) {
2176
- throw new Error(`Unknown effect type: ${definition.id}`);
2177
- }
2178
- const options = {};
2179
- definition.parameters.forEach((param) => {
2180
- var _a;
2181
- const value = (_a = initialParams == null ? void 0 : initialParams[param.name]) != null ? _a : param.default;
2182
- options[param.name] = value;
2183
- });
2184
- const effect = new Constructor(options);
2185
- const instanceId = generateInstanceId();
2186
- const effectRecord = effect;
2187
- return {
2188
- effect,
2189
- id: definition.id,
2190
- instanceId,
2191
- dispose() {
2192
- try {
2193
- effect.disconnect();
2194
- effect.dispose();
2195
- } catch (e) {
2196
- console.warn(
2197
- `[waveform-playlist] Error disposing effect "${definition.id}" (${instanceId}):`,
2198
- e
2199
- );
2200
- }
2201
- },
2202
- setParameter(name, value) {
2203
- const prop = effectRecord[name];
2204
- if (name === "wet") {
2205
- const wetProp = effectRecord["wet"];
2206
- if (wetProp && typeof wetProp === "object" && "value" in wetProp) {
2207
- wetProp.value = value;
2208
- return;
2209
- }
2210
- }
2211
- if (prop !== void 0) {
2212
- if (prop && typeof prop === "object" && "value" in prop) {
2213
- prop.value = value;
2214
- } else {
2215
- effectRecord[name] = value;
2216
- }
2217
- }
2218
- },
2219
- getParameter(name) {
2220
- if (name === "wet") {
2221
- const wetProp = effectRecord["wet"];
2222
- if (wetProp && typeof wetProp === "object" && "value" in wetProp) {
2223
- return wetProp.value;
2224
- }
2225
- }
2226
- const prop = effectRecord[name];
2227
- if (prop !== void 0) {
2228
- if (prop && typeof prop === "object" && "value" in prop) {
2229
- return prop.value;
2230
- }
2231
- return prop;
2232
- }
2233
- return void 0;
2234
- },
2235
- connect(destination) {
2236
- effect.connect(destination);
2237
- },
2238
- disconnect() {
2239
- try {
2240
- effect.disconnect();
2241
- } catch (e) {
2242
- console.warn(
2243
- `[waveform-playlist] Error disconnecting effect "${definition.id}" (${instanceId}):`,
2244
- e
2245
- );
2246
- }
2247
- }
2248
- };
2249
- }
2250
- function createEffectChain(effects) {
2251
- if (effects.length === 0) {
2252
- throw new Error("Cannot create effect chain with no effects");
2253
- }
2254
- for (let i = 0; i < effects.length - 1; i++) {
2255
- effects[i].effect.connect(effects[i + 1].effect);
2256
- }
2257
- return {
2258
- input: effects[0].effect,
2259
- output: effects[effects.length - 1].effect,
2260
- dispose() {
2261
- effects.forEach((e) => e.dispose());
2262
- }
2263
- };
2264
- }
2265
-
2266
- // src/hooks/useDynamicEffects.ts
2267
- import { Analyser as Analyser2 } from "tone";
2268
- function useDynamicEffects(fftSize = 256) {
2269
- const [activeEffects, setActiveEffects] = useState9([]);
2270
- const activeEffectsRef = useRef9(activeEffects);
2271
- activeEffectsRef.current = activeEffects;
2272
- const effectInstancesRef = useRef9(/* @__PURE__ */ new Map());
2273
- const analyserRef = useRef9(null);
2274
- const graphNodesRef = useRef9(null);
2275
- const rebuildChain = useCallback12((effects) => {
2276
- const nodes = graphNodesRef.current;
2277
- if (!nodes) return;
2278
- const { masterGainNode, destination, analyserNode } = nodes;
2279
- try {
2280
- masterGainNode.disconnect();
2281
- } catch (e) {
2282
- console.warn("[waveform-playlist] Error disconnecting master effects chain:", e);
2283
- }
2284
- const instances = effects.map((ae) => effectInstancesRef.current.get(ae.instanceId)).filter((inst) => inst !== void 0);
2285
- if (instances.length === 0) {
2286
- masterGainNode.connect(analyserNode);
2287
- analyserNode.connect(destination);
2288
- } else {
2289
- let currentNode = masterGainNode;
2290
- instances.forEach((inst) => {
2291
- try {
2292
- inst.disconnect();
2293
- } catch (e) {
2294
- console.warn(`[waveform-playlist] Error disconnecting effect "${inst.id}":`, e);
2295
- }
2296
- currentNode.connect(inst.effect);
2297
- currentNode = inst.effect;
2298
- });
2299
- currentNode.connect(analyserNode);
2300
- analyserNode.connect(destination);
2301
- }
2302
- }, []);
2303
- const addEffect = useCallback12((effectId) => {
2304
- const definition = getEffectDefinition(effectId);
2305
- if (!definition) {
2306
- console.error(`Unknown effect: ${effectId}`);
2307
- return;
2308
- }
2309
- const params = {};
2310
- definition.parameters.forEach((p) => {
2311
- params[p.name] = p.default;
2312
- });
2313
- const instance = createEffectInstance(definition, params);
2314
- effectInstancesRef.current.set(instance.instanceId, instance);
2315
- const newActiveEffect = {
2316
- instanceId: instance.instanceId,
2317
- effectId: definition.id,
2318
- definition,
2319
- params,
2320
- bypassed: false
2321
- };
2322
- setActiveEffects((prev) => [...prev, newActiveEffect]);
2323
- }, []);
2324
- const removeEffect = useCallback12((instanceId) => {
2325
- const instance = effectInstancesRef.current.get(instanceId);
2326
- if (instance) {
2327
- instance.dispose();
2328
- effectInstancesRef.current.delete(instanceId);
2329
- }
2330
- setActiveEffects((prev) => prev.filter((e) => e.instanceId !== instanceId));
2331
- }, []);
2332
- const updateParameter = useCallback12(
2333
- (instanceId, paramName, value) => {
2334
- const instance = effectInstancesRef.current.get(instanceId);
2335
- if (instance) {
2336
- instance.setParameter(paramName, value);
2337
- }
2338
- setActiveEffects(
2339
- (prev) => prev.map(
2340
- (e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { params: __spreadProps(__spreadValues({}, e.params), { [paramName]: value }) }) : e
2341
- )
2342
- );
2343
- },
2344
- []
2345
- );
2346
- const toggleBypass = useCallback12((instanceId) => {
2347
- var _a;
2348
- const effect = activeEffectsRef.current.find((e) => e.instanceId === instanceId);
2349
- if (!effect) return;
2350
- const newBypassed = !effect.bypassed;
2351
- const instance = effectInstancesRef.current.get(instanceId);
2352
- if (instance) {
2353
- const originalWet = (_a = effect.params.wet) != null ? _a : 1;
2354
- instance.setParameter("wet", newBypassed ? 0 : originalWet);
2355
- }
2356
- setActiveEffects(
2357
- (prev) => prev.map((e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { bypassed: newBypassed }) : e)
2358
- );
2359
- }, []);
2360
- const reorderEffects = useCallback12((fromIndex, toIndex) => {
2361
- setActiveEffects((prev) => {
2362
- const newEffects = [...prev];
2363
- const [removed] = newEffects.splice(fromIndex, 1);
2364
- newEffects.splice(toIndex, 0, removed);
2365
- return newEffects;
2366
- });
2367
- }, []);
2368
- const clearAllEffects = useCallback12(() => {
2369
- effectInstancesRef.current.forEach((inst) => inst.dispose());
2370
- effectInstancesRef.current.clear();
2371
- setActiveEffects([]);
2372
- }, []);
2373
- useEffect4(() => {
2374
- rebuildChain(activeEffects);
2375
- }, [activeEffects, rebuildChain]);
2376
- const masterEffects = useCallback12(
2377
- (masterGainNode, destination, _isOffline) => {
2378
- const analyserNode = new Analyser2("fft", fftSize);
2379
- analyserRef.current = analyserNode;
2380
- graphNodesRef.current = {
2381
- masterGainNode,
2382
- destination,
2383
- analyserNode
2384
- };
2385
- const effects = activeEffectsRef.current;
2386
- const instances = effects.map((ae) => effectInstancesRef.current.get(ae.instanceId)).filter((inst) => inst !== void 0);
2387
- if (instances.length === 0) {
2388
- masterGainNode.connect(analyserNode);
2389
- analyserNode.connect(destination);
2390
- } else {
2391
- let currentNode = masterGainNode;
2392
- instances.forEach((inst) => {
2393
- currentNode.connect(inst.effect);
2394
- currentNode = inst.effect;
2395
- });
2396
- currentNode.connect(analyserNode);
2397
- analyserNode.connect(destination);
2398
- }
2399
- return function cleanup() {
2400
- analyserNode.dispose();
2401
- analyserRef.current = null;
2402
- graphNodesRef.current = null;
2403
- };
2404
- },
2405
- [fftSize]
2406
- // Only fftSize - reads effects from ref
2407
- );
2408
- useEffect4(() => {
2409
- const effectInstances = effectInstancesRef.current;
2410
- return () => {
2411
- effectInstances.forEach((inst) => inst.dispose());
2412
- effectInstances.clear();
2413
- };
2414
- }, []);
2415
- const createOfflineEffectsFunction = useCallback12(() => {
2416
- const nonBypassedEffects = activeEffects.filter((e) => !e.bypassed);
2417
- if (nonBypassedEffects.length === 0) {
2418
- return void 0;
2419
- }
2420
- return (masterGainNode, destination, _isOffline) => {
2421
- const offlineInstances = [];
2422
- for (const activeEffect of nonBypassedEffects) {
2423
- const instance = createEffectInstance(activeEffect.definition, activeEffect.params);
2424
- offlineInstances.push(instance);
2425
- }
2426
- if (offlineInstances.length === 0) {
2427
- masterGainNode.connect(destination);
2428
- } else {
2429
- let currentNode = masterGainNode;
2430
- offlineInstances.forEach((inst) => {
2431
- currentNode.connect(inst.effect);
2432
- currentNode = inst.effect;
2433
- });
2434
- currentNode.connect(destination);
2435
- }
2436
- return function cleanup() {
2437
- offlineInstances.forEach((inst) => inst.dispose());
2438
- };
2439
- };
2440
- }, [activeEffects]);
2441
- return {
2442
- activeEffects,
2443
- availableEffects: effectDefinitions,
2444
- addEffect,
2445
- removeEffect,
2446
- updateParameter,
2447
- toggleBypass,
2448
- reorderEffects,
2449
- clearAllEffects,
2450
- masterEffects,
2451
- createOfflineEffectsFunction,
2452
- analyserRef
2453
- };
2454
- }
2455
-
2456
- // src/hooks/useTrackDynamicEffects.ts
2457
- import { useState as useState10, useCallback as useCallback13, useRef as useRef10, useEffect as useEffect5 } from "react";
2458
- function useTrackDynamicEffects() {
2459
- const [trackEffectsState, setTrackEffectsState] = useState10(
2460
- /* @__PURE__ */ new Map()
2461
- );
2462
- const trackEffectInstancesRef = useRef10(/* @__PURE__ */ new Map());
2463
- const trackGraphNodesRef = useRef10(/* @__PURE__ */ new Map());
2464
- const rebuildTrackChain = useCallback13((trackId, trackEffects) => {
2465
- const nodes = trackGraphNodesRef.current.get(trackId);
2466
- if (!nodes) return;
2467
- const { graphEnd, masterGainNode } = nodes;
2468
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2469
- try {
2470
- graphEnd.disconnect();
2471
- } catch (e) {
2472
- console.warn(`[waveform-playlist] Error disconnecting track "${trackId}" effect chain:`, e);
2473
- }
2474
- const instances = trackEffects.map((ae) => instancesMap == null ? void 0 : instancesMap.get(ae.instanceId)).filter((inst) => inst !== void 0);
2475
- if (instances.length === 0) {
2476
- graphEnd.connect(masterGainNode);
2477
- } else {
2478
- let currentNode = graphEnd;
2479
- instances.forEach((inst) => {
2480
- try {
2481
- inst.disconnect();
2482
- } catch (e) {
2483
- console.warn(
2484
- `[waveform-playlist] Error disconnecting effect "${inst.id}" on track "${trackId}":`,
2485
- e
2486
- );
2487
- }
2488
- currentNode.connect(inst.effect);
2489
- currentNode = inst.effect;
2490
- });
2491
- currentNode.connect(masterGainNode);
2492
- }
2493
- }, []);
2494
- const addEffectToTrack = useCallback13((trackId, effectId) => {
2495
- const definition = getEffectDefinition(effectId);
2496
- if (!definition) {
2497
- console.error(`Unknown effect: ${effectId}`);
2498
- return;
2499
- }
2500
- const params = {};
2501
- definition.parameters.forEach((p) => {
2502
- params[p.name] = p.default;
2503
- });
2504
- const instance = createEffectInstance(definition, params);
2505
- if (!trackEffectInstancesRef.current.has(trackId)) {
2506
- trackEffectInstancesRef.current.set(trackId, /* @__PURE__ */ new Map());
2507
- }
2508
- trackEffectInstancesRef.current.get(trackId).set(instance.instanceId, instance);
2509
- const newActiveEffect = {
2510
- instanceId: instance.instanceId,
2511
- effectId: definition.id,
2512
- definition,
2513
- params,
2514
- bypassed: false
2515
- };
2516
- setTrackEffectsState((prev) => {
2517
- const newState = new Map(prev);
2518
- const existing = newState.get(trackId) || [];
2519
- newState.set(trackId, [...existing, newActiveEffect]);
2520
- return newState;
2521
- });
2522
- }, []);
2523
- const removeEffectFromTrack = useCallback13((trackId, instanceId) => {
2524
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2525
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2526
- if (instance) {
2527
- instance.dispose();
2528
- instancesMap == null ? void 0 : instancesMap.delete(instanceId);
2529
- }
2530
- setTrackEffectsState((prev) => {
2531
- const newState = new Map(prev);
2532
- const existing = newState.get(trackId) || [];
2533
- newState.set(
2534
- trackId,
2535
- existing.filter((e) => e.instanceId !== instanceId)
2536
- );
2537
- return newState;
2538
- });
2539
- }, []);
2540
- const updateTrackEffectParameter = useCallback13(
2541
- (trackId, instanceId, paramName, value) => {
2542
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2543
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2544
- if (instance) {
2545
- instance.setParameter(paramName, value);
2546
- }
2547
- setTrackEffectsState((prev) => {
2548
- const newState = new Map(prev);
2549
- const existing = newState.get(trackId) || [];
2550
- newState.set(
2551
- trackId,
2552
- existing.map(
2553
- (e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { params: __spreadProps(__spreadValues({}, e.params), { [paramName]: value }) }) : e
2554
- )
2555
- );
2556
- return newState;
2557
- });
2558
- },
2559
- []
2560
- );
2561
- const toggleBypass = useCallback13((trackId, instanceId) => {
2562
- var _a;
2563
- const trackEffects = trackEffectsStateRef.current.get(trackId) || [];
2564
- const effect = trackEffects.find((e) => e.instanceId === instanceId);
2565
- if (!effect) return;
2566
- const newBypassed = !effect.bypassed;
2567
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2568
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2569
- if (instance) {
2570
- const originalWet = (_a = effect.params.wet) != null ? _a : 1;
2571
- instance.setParameter("wet", newBypassed ? 0 : originalWet);
2572
- }
2573
- setTrackEffectsState((prev) => {
2574
- const newState = new Map(prev);
2575
- const existing = newState.get(trackId) || [];
2576
- newState.set(
2577
- trackId,
2578
- existing.map((e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { bypassed: newBypassed }) : e)
2579
- );
2580
- return newState;
2581
- });
2582
- }, []);
2583
- const clearTrackEffects = useCallback13((trackId) => {
2584
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2585
- if (instancesMap) {
2586
- instancesMap.forEach((inst) => inst.dispose());
2587
- instancesMap.clear();
2588
- }
2589
- setTrackEffectsState((prev) => {
2590
- const newState = new Map(prev);
2591
- newState.set(trackId, []);
2592
- return newState;
2593
- });
2594
- }, []);
2595
- const trackEffectsStateRef = useRef10(trackEffectsState);
2596
- trackEffectsStateRef.current = trackEffectsState;
2597
- const getTrackEffectsFunction = useCallback13(
2598
- (trackId) => {
2599
- return (graphEnd, masterGainNode, _isOffline) => {
2600
- trackGraphNodesRef.current.set(trackId, {
2601
- graphEnd,
2602
- masterGainNode
2603
- });
2604
- const trackEffects = trackEffectsStateRef.current.get(trackId) || [];
2605
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2606
- const instances = trackEffects.map((ae) => instancesMap == null ? void 0 : instancesMap.get(ae.instanceId)).filter((inst) => inst !== void 0);
2607
- if (instances.length === 0) {
2608
- graphEnd.connect(masterGainNode);
2609
- } else {
2610
- let currentNode = graphEnd;
2611
- instances.forEach((inst) => {
2612
- currentNode.connect(inst.effect);
2613
- currentNode = inst.effect;
2614
- });
2615
- currentNode.connect(masterGainNode);
2616
- }
2617
- return function cleanup() {
2618
- trackGraphNodesRef.current.delete(trackId);
2619
- };
2620
- };
2621
- },
2622
- []
2623
- // No dependencies - stable function that reads from refs
2624
- );
2625
- useEffect5(() => {
2626
- trackEffectsState.forEach((effects, trackId) => {
2627
- rebuildTrackChain(trackId, effects);
2628
- });
2629
- }, [trackEffectsState, rebuildTrackChain]);
2630
- useEffect5(() => {
2631
- const trackEffectInstances = trackEffectInstancesRef.current;
2632
- return () => {
2633
- trackEffectInstances.forEach((instancesMap) => {
2634
- instancesMap.forEach((inst) => inst.dispose());
2635
- instancesMap.clear();
2636
- });
2637
- trackEffectInstances.clear();
2638
- };
2639
- }, []);
2640
- const createOfflineTrackEffectsFunction = useCallback13(
2641
- (trackId) => {
2642
- const trackEffects = trackEffectsState.get(trackId) || [];
2643
- const nonBypassedEffects = trackEffects.filter((e) => !e.bypassed);
2644
- if (nonBypassedEffects.length === 0) {
2645
- return void 0;
2646
- }
2647
- return (graphEnd, masterGainNode, _isOffline) => {
2648
- const offlineInstances = [];
2649
- for (const activeEffect of nonBypassedEffects) {
2650
- const instance = createEffectInstance(activeEffect.definition, activeEffect.params);
2651
- offlineInstances.push(instance);
2652
- }
2653
- if (offlineInstances.length === 0) {
2654
- graphEnd.connect(masterGainNode);
2655
- } else {
2656
- let currentNode = graphEnd;
2657
- offlineInstances.forEach((inst) => {
2658
- currentNode.connect(inst.effect);
2659
- currentNode = inst.effect;
2660
- });
2661
- currentNode.connect(masterGainNode);
2662
- }
2663
- return function cleanup() {
2664
- offlineInstances.forEach((inst) => inst.dispose());
2665
- };
2666
- };
2667
- },
2668
- [trackEffectsState]
2669
- );
2670
- return {
2671
- trackEffectsState,
2672
- addEffectToTrack,
2673
- removeEffectFromTrack,
2674
- updateTrackEffectParameter,
2675
- toggleBypass,
2676
- clearTrackEffects,
2677
- getTrackEffectsFunction,
2678
- createOfflineTrackEffectsFunction,
2679
- availableEffects: effectDefinitions
2680
- };
2681
- }
2682
-
2683
- // src/hooks/useExportWav.ts
2684
- import { useState as useState11, useCallback as useCallback14 } from "react";
2685
- import {
2686
- gainToDb,
2687
- trackChannelCount,
2688
- applyFadeIn,
2689
- applyFadeOut
2690
- } from "@waveform-playlist/core";
2691
- import {
2692
- getUnderlyingAudioParam,
2693
- getGlobalAudioContext as getGlobalAudioContext2
2694
- } from "@waveform-playlist/playout";
2695
-
2696
- // src/utils/wavEncoder.ts
2697
- function encodeWav(audioBuffer, options = {}) {
2698
- const { bitDepth = 16 } = options;
2699
- const numChannels = audioBuffer.numberOfChannels;
2700
- const sampleRate = audioBuffer.sampleRate;
2701
- const numSamples = audioBuffer.length;
2702
- const bytesPerSample = bitDepth / 8;
2703
- const blockAlign = numChannels * bytesPerSample;
2704
- const byteRate = sampleRate * blockAlign;
2705
- const dataSize = numSamples * blockAlign;
2706
- const headerSize = 44;
2707
- const totalSize = headerSize + dataSize;
2708
- const buffer = new ArrayBuffer(totalSize);
2709
- const view = new DataView(buffer);
2710
- writeString(view, 0, "RIFF");
2711
- view.setUint32(4, totalSize - 8, true);
2712
- writeString(view, 8, "WAVE");
2713
- writeString(view, 12, "fmt ");
2714
- view.setUint32(16, 16, true);
2715
- view.setUint16(20, bitDepth === 32 ? 3 : 1, true);
2716
- view.setUint16(22, numChannels, true);
2717
- view.setUint32(24, sampleRate, true);
2718
- view.setUint32(28, byteRate, true);
2719
- view.setUint16(32, blockAlign, true);
2720
- view.setUint16(34, bitDepth, true);
2721
- writeString(view, 36, "data");
2722
- view.setUint32(40, dataSize, true);
2723
- const channelData = [];
2724
- for (let ch = 0; ch < numChannels; ch++) {
2725
- channelData.push(audioBuffer.getChannelData(ch));
2726
- }
2727
- let offset = headerSize;
2728
- if (bitDepth === 16) {
2729
- for (let i = 0; i < numSamples; i++) {
2730
- for (let ch = 0; ch < numChannels; ch++) {
2731
- const sample = channelData[ch][i];
2732
- const clampedSample = Math.max(-1, Math.min(1, sample));
2733
- const intSample = clampedSample < 0 ? clampedSample * 32768 : clampedSample * 32767;
2734
- view.setInt16(offset, intSample, true);
2735
- offset += 2;
2736
- }
2737
- }
2738
- } else {
2739
- for (let i = 0; i < numSamples; i++) {
2740
- for (let ch = 0; ch < numChannels; ch++) {
2741
- view.setFloat32(offset, channelData[ch][i], true);
2742
- offset += 4;
2743
- }
2744
- }
2745
- }
2746
- return new Blob([buffer], { type: "audio/wav" });
2747
- }
2748
- function writeString(view, offset, str) {
2749
- for (let i = 0; i < str.length; i++) {
2750
- view.setUint8(offset + i, str.charCodeAt(i));
2751
- }
2752
- }
2753
- function downloadBlob(blob, filename) {
2754
- const url = URL.createObjectURL(blob);
2755
- const a = document.createElement("a");
2756
- a.href = url;
2757
- a.download = filename;
2758
- a.style.display = "none";
2759
- document.body.appendChild(a);
2760
- a.click();
2761
- document.body.removeChild(a);
2762
- URL.revokeObjectURL(url);
2763
- }
2764
-
2765
- // src/hooks/useExportWav.ts
2766
- function useExportWav() {
2767
- const [isExporting, setIsExporting] = useState11(false);
2768
- const [progress, setProgress] = useState11(0);
2769
- const [error, setError] = useState11(null);
2770
- const exportWav = useCallback14(
2771
- (_0, _1, ..._2) => __async(null, [_0, _1, ..._2], function* (tracks, trackStates, options = {}) {
2772
- const {
2773
- filename = "export",
2774
- mode = "master",
2775
- trackIndex,
2776
- autoDownload = true,
2777
- applyEffects = true,
2778
- effectsFunction,
2779
- createOfflineTrackEffects,
2780
- bitDepth = 16,
2781
- onProgress
2782
- } = options;
2783
- setIsExporting(true);
2784
- setProgress(0);
2785
- setError(null);
2786
- try {
2787
- if (tracks.length === 0) {
2788
- throw new Error("No tracks to export");
2789
- }
2790
- if (mode === "individual" && (trackIndex === void 0 || trackIndex < 0 || trackIndex >= tracks.length)) {
2791
- throw new Error("Invalid track index for individual export");
2792
- }
2793
- const sampleRate = getGlobalAudioContext2().sampleRate;
2794
- let totalDurationSamples = 0;
2795
- for (const track of tracks) {
2796
- for (const clip of track.clips) {
2797
- const clipEndSample = clip.startSample + clip.durationSamples;
2798
- totalDurationSamples = Math.max(totalDurationSamples, clipEndSample);
2799
- }
2800
- }
2801
- totalDurationSamples += Math.round(sampleRate * 0.1);
2802
- const duration = totalDurationSamples / sampleRate;
2803
- const tracksToRender = mode === "individual" ? [{ track: tracks[trackIndex], state: trackStates[trackIndex], index: trackIndex }] : tracks.map((track, index) => ({ track, state: trackStates[index], index }));
2804
- const hasSolo = mode === "master" && trackStates.some((state) => state.soloed);
2805
- const reportProgress = (p) => {
2806
- setProgress(p);
2807
- onProgress == null ? void 0 : onProgress(p);
2808
- };
2809
- const renderedBuffer = yield renderOffline(
2810
- tracksToRender,
2811
- hasSolo,
2812
- duration,
2813
- sampleRate,
2814
- applyEffects,
2815
- effectsFunction,
2816
- createOfflineTrackEffects,
2817
- reportProgress
2818
- );
2819
- reportProgress(0.9);
2820
- const blob = encodeWav(renderedBuffer, { bitDepth });
2821
- reportProgress(1);
2822
- if (autoDownload) {
2823
- const exportFilename = mode === "individual" ? `${filename}_${tracks[trackIndex].name}` : filename;
2824
- downloadBlob(blob, `${exportFilename}.wav`);
2825
- }
2826
- return {
2827
- audioBuffer: renderedBuffer,
2828
- blob,
2829
- duration
2830
- };
2831
- } catch (err) {
2832
- const message = err instanceof Error ? err.message : "Export failed";
2833
- setError(message);
2834
- throw err;
2835
- } finally {
2836
- setIsExporting(false);
1341
+ key: "Home",
1342
+ action: selectFirst,
1343
+ description: "Select first annotation",
1344
+ preventDefault: true
1345
+ },
1346
+ {
1347
+ key: "End",
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
2837
1357
  }
2838
- }),
2839
- []
1358
+ ],
1359
+ [selectPrevious, selectNext, selectFirst, selectLast, clearSelection]
2840
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
+ });
2841
1369
  return {
2842
- exportWav,
2843
- isExporting,
2844
- progress,
2845
- error
1370
+ moveStartBoundary,
1371
+ moveEndBoundary,
1372
+ selectPrevious,
1373
+ selectNext,
1374
+ selectFirst,
1375
+ selectLast,
1376
+ clearSelection,
1377
+ scrollToAnnotation,
1378
+ playActiveAnnotation
2846
1379
  };
2847
1380
  }
2848
- function renderOffline(tracksToRender, hasSolo, duration, sampleRate, applyEffects, effectsFunction, createOfflineTrackEffects, onProgress) {
2849
- return __async(this, null, function* () {
2850
- const { Offline, Volume: Volume2, Gain, Panner, Player, ToneAudioBuffer } = yield import("tone");
2851
- onProgress(0.1);
2852
- const audibleTracks = tracksToRender.filter(({ state }) => {
2853
- if (state.muted && !state.soloed) return false;
2854
- if (hasSolo && !state.soloed) return false;
2855
- return true;
2856
- });
2857
- const outputChannels = audibleTracks.reduce(
2858
- (max, { track }) => Math.max(max, trackChannelCount(track)),
2859
- 1
2860
- );
2861
- let buffer;
2862
- try {
2863
- buffer = yield Offline(
2864
- (_0) => __async(null, [_0], function* ({ transport, destination }) {
2865
- const masterVolume = new Volume2(0);
2866
- if (effectsFunction && applyEffects) {
2867
- effectsFunction(masterVolume, destination, true);
2868
- } else {
2869
- masterVolume.connect(destination);
2870
- }
2871
- for (const { track, state } of audibleTracks) {
2872
- const trackVolume = new Volume2(gainToDb(state.volume));
2873
- const trackPan = new Panner({ pan: state.pan, channelCount: trackChannelCount(track) });
2874
- const trackMute = new Gain(state.muted ? 0 : 1);
2875
- const trackEffects = createOfflineTrackEffects == null ? void 0 : createOfflineTrackEffects(track.id);
2876
- if (trackEffects && applyEffects) {
2877
- trackEffects(trackMute, masterVolume, true);
2878
- } else {
2879
- trackMute.connect(masterVolume);
2880
- }
2881
- trackPan.connect(trackMute);
2882
- trackVolume.connect(trackPan);
2883
- for (const clip of track.clips) {
2884
- const {
2885
- audioBuffer,
2886
- startSample,
2887
- durationSamples,
2888
- offsetSamples,
2889
- gain: clipGain,
2890
- fadeIn,
2891
- fadeOut
2892
- } = clip;
2893
- if (!audioBuffer) {
2894
- console.warn(
2895
- '[waveform-playlist] Skipping clip "' + (clip.name || clip.id) + '" - no audioBuffer for export'
2896
- );
2897
- continue;
2898
- }
2899
- const startTime = startSample / sampleRate;
2900
- const clipDuration = durationSamples / sampleRate;
2901
- const offset = offsetSamples / sampleRate;
2902
- const toneBuffer = new ToneAudioBuffer(audioBuffer);
2903
- const player = new Player(toneBuffer);
2904
- const fadeGain = new Gain(clipGain);
2905
- player.connect(fadeGain);
2906
- fadeGain.connect(trackVolume);
2907
- if (applyEffects) {
2908
- const audioParam = getUnderlyingAudioParam(fadeGain.gain);
2909
- if (audioParam) {
2910
- applyClipFades(audioParam, clipGain, startTime, clipDuration, fadeIn, fadeOut);
2911
- } else if (fadeIn || fadeOut) {
2912
- console.warn(
2913
- '[waveform-playlist] Cannot apply fades for clip "' + (clip.name || clip.id) + '" - AudioParam not accessible'
2914
- );
2915
- }
2916
- }
2917
- player.start(startTime, offset, clipDuration);
2918
- }
2919
- }
2920
- transport.start(0);
2921
- }),
2922
- duration,
2923
- outputChannels,
2924
- sampleRate
2925
- );
2926
- } catch (err) {
2927
- if (err instanceof Error) {
2928
- throw err;
2929
- } else {
2930
- throw new Error("Tone.Offline rendering failed: " + String(err));
2931
- }
2932
- }
2933
- onProgress(0.9);
2934
- const result = buffer.get();
2935
- if (!result) {
2936
- throw new Error("Offline rendering produced no audio buffer");
2937
- }
2938
- return result;
2939
- });
2940
- }
2941
- function applyClipFades(gainParam, clipGain, startTime, clipDuration, fadeIn, fadeOut) {
2942
- if (fadeIn) {
2943
- gainParam.setValueAtTime(0, startTime);
2944
- } else {
2945
- gainParam.setValueAtTime(clipGain, startTime);
2946
- }
2947
- if (fadeIn) {
2948
- applyFadeIn(gainParam, startTime, fadeIn.duration, fadeIn.type || "linear", 0, clipGain);
2949
- }
2950
- if (fadeOut) {
2951
- const fadeOutStart = startTime + clipDuration - fadeOut.duration;
2952
- if (!fadeIn || fadeIn.duration < clipDuration - fadeOut.duration) {
2953
- gainParam.setValueAtTime(clipGain, fadeOutStart);
2954
- }
2955
- applyFadeOut(gainParam, fadeOutStart, fadeOut.duration, fadeOut.type || "linear", clipGain, 0);
2956
- }
2957
- }
2958
1381
 
2959
1382
  // src/hooks/useAnimationFrameLoop.ts
2960
- 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";
2961
1384
  var useAnimationFrameLoop = () => {
2962
- const animationFrameRef = useRef11(null);
2963
- const stopAnimationFrameLoop = useCallback15(() => {
1385
+ const animationFrameRef = useRef7(null);
1386
+ const stopAnimationFrameLoop = useCallback11(() => {
2964
1387
  if (animationFrameRef.current !== null) {
2965
1388
  cancelAnimationFrame(animationFrameRef.current);
2966
1389
  animationFrameRef.current = null;
2967
1390
  }
2968
1391
  }, []);
2969
- const startAnimationFrameLoop = useCallback15(
1392
+ const startAnimationFrameLoop = useCallback11(
2970
1393
  (callback) => {
2971
1394
  stopAnimationFrameLoop();
2972
1395
  animationFrameRef.current = requestAnimationFrame(callback);
2973
1396
  },
2974
1397
  [stopAnimationFrameLoop]
2975
1398
  );
2976
- useEffect6(() => {
1399
+ useEffect3(() => {
2977
1400
  return () => {
2978
1401
  stopAnimationFrameLoop();
2979
1402
  };
@@ -2986,7 +1409,7 @@ var useAnimationFrameLoop = () => {
2986
1409
  };
2987
1410
 
2988
1411
  // src/hooks/useWaveformDataCache.ts
2989
- 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";
2990
1413
 
2991
1414
  // src/workers/peaksWorker.ts
2992
1415
  import WaveformData2 from "waveform-data";
@@ -3218,20 +1641,20 @@ function createPeaksWorker() {
3218
1641
 
3219
1642
  // src/hooks/useWaveformDataCache.ts
3220
1643
  function useWaveformDataCache(tracks, baseScale) {
3221
- const [cache, setCache] = useState12(() => /* @__PURE__ */ new Map());
3222
- const [isGenerating, setIsGenerating] = useState12(false);
3223
- const workerRef = useRef12(null);
3224
- const generatedByBufferRef = useRef12(/* @__PURE__ */ new WeakMap());
3225
- const inflightByBufferRef = useRef12(/* @__PURE__ */ new WeakMap());
3226
- const subscribersByBufferRef = useRef12(/* @__PURE__ */ new WeakMap());
3227
- const pendingCountRef = useRef12(0);
3228
- 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(() => {
3229
1652
  if (!workerRef.current) {
3230
1653
  workerRef.current = createPeaksWorker();
3231
1654
  }
3232
1655
  return workerRef.current;
3233
1656
  }, []);
3234
- useEffect7(() => {
1657
+ useEffect4(() => {
3235
1658
  let cancelled = false;
3236
1659
  const generatedByBuffer = generatedByBufferRef.current;
3237
1660
  const inflightByBuffer = inflightByBufferRef.current;
@@ -3336,7 +1759,7 @@ function useWaveformDataCache(tracks, baseScale) {
3336
1759
  setIsGenerating(false);
3337
1760
  };
3338
1761
  }, [tracks, baseScale, getWorker]);
3339
- useEffect7(() => {
1762
+ useEffect4(() => {
3340
1763
  return () => {
3341
1764
  var _a;
3342
1765
  (_a = workerRef.current) == null ? void 0 : _a.terminate();
@@ -3346,226 +1769,6 @@ function useWaveformDataCache(tracks, baseScale) {
3346
1769
  return { cache, isGenerating };
3347
1770
  }
3348
1771
 
3349
- // src/hooks/useDynamicTracks.ts
3350
- import { useState as useState13, useCallback as useCallback17, useRef as useRef13, useEffect as useEffect8 } from "react";
3351
- import { createTrack as createTrack2, createClipFromSeconds as createClipFromSeconds2 } from "@waveform-playlist/core";
3352
- import { getGlobalAudioContext as getGlobalAudioContext3 } from "@waveform-playlist/playout";
3353
- function getSourceName(source) {
3354
- var _a, _b, _c, _d, _e;
3355
- if (source instanceof File) {
3356
- return source.name.replace(/\.[^/.]+$/, "");
3357
- }
3358
- if (source instanceof Blob) {
3359
- return "Untitled";
3360
- }
3361
- if (typeof source === "string") {
3362
- return (_b = (_a = source.split("/").pop()) == null ? void 0 : _a.replace(/\.[^/.]+$/, "")) != null ? _b : "Untitled";
3363
- }
3364
- return (_e = (_d = source.name) != null ? _d : (_c = source.src.split("/").pop()) == null ? void 0 : _c.replace(/\.[^/.]+$/, "")) != null ? _e : "Untitled";
3365
- }
3366
- function decodeSource(source, audioContext, signal) {
3367
- return __async(this, null, function* () {
3368
- const name = getSourceName(source);
3369
- if (source instanceof Blob) {
3370
- const arrayBuffer2 = yield source.arrayBuffer();
3371
- signal == null ? void 0 : signal.throwIfAborted();
3372
- const audioBuffer2 = yield audioContext.decodeAudioData(arrayBuffer2);
3373
- return { audioBuffer: audioBuffer2, name };
3374
- }
3375
- const url = typeof source === "string" ? source : source.src;
3376
- const response = yield fetch(url, { signal });
3377
- if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
3378
- const arrayBuffer = yield response.arrayBuffer();
3379
- signal == null ? void 0 : signal.throwIfAborted();
3380
- const audioBuffer = yield audioContext.decodeAudioData(arrayBuffer);
3381
- return { audioBuffer, name };
3382
- });
3383
- }
3384
- function useDynamicTracks() {
3385
- const [tracks, setTracks] = useState13([]);
3386
- const [loadingCount, setLoadingCount] = useState13(0);
3387
- const [errors, setErrors] = useState13([]);
3388
- const cancelledRef = useRef13(false);
3389
- const loadingIdsRef = useRef13(/* @__PURE__ */ new Set());
3390
- const abortControllersRef = useRef13(/* @__PURE__ */ new Map());
3391
- useEffect8(() => {
3392
- const controllers = abortControllersRef.current;
3393
- return () => {
3394
- cancelledRef.current = true;
3395
- for (const controller of controllers.values()) {
3396
- controller.abort();
3397
- }
3398
- controllers.clear();
3399
- };
3400
- }, []);
3401
- const addTracks = useCallback17((sources) => {
3402
- if (sources.length === 0) return;
3403
- const audioContext = getGlobalAudioContext3();
3404
- const placeholders = sources.map((source) => ({
3405
- track: createTrack2({ name: `${getSourceName(source)} (loading...)`, clips: [] }),
3406
- source
3407
- }));
3408
- setTracks((prev) => [...prev, ...placeholders.map((p) => p.track)]);
3409
- setLoadingCount((prev) => prev + sources.length);
3410
- for (const { track, source } of placeholders) {
3411
- loadingIdsRef.current.add(track.id);
3412
- const controller = new AbortController();
3413
- abortControllersRef.current.set(track.id, controller);
3414
- (() => __async(null, null, function* () {
3415
- try {
3416
- const { audioBuffer, name } = yield decodeSource(source, audioContext, controller.signal);
3417
- const clip = createClipFromSeconds2({
3418
- audioBuffer,
3419
- startTime: 0,
3420
- duration: audioBuffer.duration,
3421
- offset: 0,
3422
- name
3423
- });
3424
- if (!cancelledRef.current && loadingIdsRef.current.has(track.id)) {
3425
- setTracks(
3426
- (prev) => prev.map((t) => t.id === track.id ? __spreadProps(__spreadValues({}, t), { name, clips: [clip] }) : t)
3427
- );
3428
- }
3429
- } catch (error) {
3430
- if (error instanceof DOMException && error.name === "AbortError") return;
3431
- console.warn("[waveform-playlist] Error loading audio:", error);
3432
- if (!cancelledRef.current && loadingIdsRef.current.has(track.id)) {
3433
- setTracks((prev) => prev.filter((t) => t.id !== track.id));
3434
- setErrors((prev) => [
3435
- ...prev,
3436
- {
3437
- name: getSourceName(source),
3438
- error: error instanceof Error ? error : new Error(String(error))
3439
- }
3440
- ]);
3441
- }
3442
- } finally {
3443
- abortControllersRef.current.delete(track.id);
3444
- if (!cancelledRef.current && loadingIdsRef.current.delete(track.id)) {
3445
- setLoadingCount((prev) => prev - 1);
3446
- }
3447
- }
3448
- }))();
3449
- }
3450
- }, []);
3451
- const removeTrack = useCallback17((trackId) => {
3452
- setTracks((prev) => prev.filter((t) => t.id !== trackId));
3453
- const controller = abortControllersRef.current.get(trackId);
3454
- if (controller) {
3455
- controller.abort();
3456
- abortControllersRef.current.delete(trackId);
3457
- }
3458
- if (loadingIdsRef.current.delete(trackId)) {
3459
- setLoadingCount((prev) => prev - 1);
3460
- }
3461
- }, []);
3462
- return {
3463
- tracks,
3464
- addTracks,
3465
- removeTrack,
3466
- loadingCount,
3467
- isLoading: loadingCount > 0,
3468
- errors
3469
- };
3470
- }
3471
-
3472
- // src/hooks/useOutputMeter.ts
3473
- import { useEffect as useEffect9, useState as useState14, useRef as useRef14, useCallback as useCallback18 } from "react";
3474
- import { getGlobalContext } from "@waveform-playlist/playout";
3475
- import { gainToNormalized } from "@waveform-playlist/core";
3476
- import { meterProcessorUrl } from "@waveform-playlist/worklets";
3477
- var PEAK_DECAY = 0.98;
3478
- function useOutputMeter(options = {}) {
3479
- const { channelCount = 2, updateRate = 60, isPlaying = false } = options;
3480
- const [levels, setLevels] = useState14(() => new Array(channelCount).fill(0));
3481
- const [peakLevels, setPeakLevels] = useState14(() => new Array(channelCount).fill(0));
3482
- const [rmsLevels, setRmsLevels] = useState14(() => new Array(channelCount).fill(0));
3483
- const workletNodeRef = useRef14(null);
3484
- const smoothedPeakRef = useRef14(new Array(channelCount).fill(0));
3485
- const [meterError, setMeterError] = useState14(null);
3486
- const resetPeak = useCallback18(
3487
- () => setPeakLevels(new Array(channelCount).fill(0)),
3488
- [channelCount]
3489
- );
3490
- useEffect9(() => {
3491
- if (!isPlaying) {
3492
- const zeros = new Array(channelCount).fill(0);
3493
- smoothedPeakRef.current = new Array(channelCount).fill(0);
3494
- setLevels(zeros);
3495
- setRmsLevels(zeros);
3496
- setPeakLevels(zeros);
3497
- }
3498
- }, [isPlaying, channelCount]);
3499
- useEffect9(() => {
3500
- let isMounted = true;
3501
- const setup = () => __async(null, null, function* () {
3502
- const context = getGlobalContext();
3503
- const rawCtx = context.rawContext;
3504
- yield rawCtx.audioWorklet.addModule(meterProcessorUrl);
3505
- if (!isMounted) return;
3506
- const workletNode = context.createAudioWorkletNode("meter-processor", {
3507
- channelCount,
3508
- channelCountMode: "explicit",
3509
- processorOptions: {
3510
- numberOfChannels: channelCount,
3511
- updateRate
3512
- }
3513
- });
3514
- workletNodeRef.current = workletNode;
3515
- workletNode.onprocessorerror = (event) => {
3516
- console.warn("[waveform-playlist] Output meter worklet processor error:", String(event));
3517
- };
3518
- const destination = context.destination;
3519
- destination.chain(workletNode);
3520
- smoothedPeakRef.current = new Array(channelCount).fill(0);
3521
- workletNode.port.onmessage = (event) => {
3522
- var _a;
3523
- if (!isMounted) return;
3524
- const { peak, rms } = event.data;
3525
- const smoothed = smoothedPeakRef.current;
3526
- const peakValues = [];
3527
- const rmsValues = [];
3528
- for (let ch = 0; ch < peak.length; ch++) {
3529
- smoothed[ch] = Math.max(peak[ch], ((_a = smoothed[ch]) != null ? _a : 0) * PEAK_DECAY);
3530
- peakValues.push(gainToNormalized(smoothed[ch]));
3531
- rmsValues.push(gainToNormalized(rms[ch]));
3532
- }
3533
- setLevels(peakValues);
3534
- setRmsLevels(rmsValues);
3535
- setPeakLevels((prev) => peakValues.map((val, i) => {
3536
- var _a2;
3537
- return Math.max((_a2 = prev[i]) != null ? _a2 : 0, val);
3538
- }));
3539
- };
3540
- });
3541
- setup().catch((err) => {
3542
- console.warn("[waveform-playlist] Failed to set up output meter:", String(err));
3543
- if (isMounted) {
3544
- setMeterError(err instanceof Error ? err : new Error(String(err)));
3545
- }
3546
- });
3547
- return () => {
3548
- isMounted = false;
3549
- if (workletNodeRef.current) {
3550
- try {
3551
- const context = getGlobalContext();
3552
- context.destination.chain();
3553
- } catch (err) {
3554
- console.warn("[waveform-playlist] Failed to restore destination chain:", String(err));
3555
- }
3556
- try {
3557
- workletNodeRef.current.disconnect();
3558
- workletNodeRef.current.port.close();
3559
- } catch (err) {
3560
- console.warn("[waveform-playlist] Output meter disconnect during cleanup:", String(err));
3561
- }
3562
- workletNodeRef.current = null;
3563
- }
3564
- };
3565
- }, [channelCount, updateRate]);
3566
- return { levels, peakLevels, rmsLevels, resetPeak, error: meterError };
3567
- }
3568
-
3569
1772
  // src/WaveformPlaylistContext.tsx
3570
1773
  import { jsx } from "react/jsx-runtime";
3571
1774
  var PlaybackAnimationContext = createContext(null);
@@ -3585,6 +1788,7 @@ var WaveformPlaylistProvider = ({
3585
1788
  annotationList,
3586
1789
  effects,
3587
1790
  onReady,
1791
+ onError,
3588
1792
  onAnnotationUpdate: _onAnnotationUpdate,
3589
1793
  onAnnotationsChange,
3590
1794
  barWidth = 1,
@@ -3595,18 +1799,19 @@ var WaveformPlaylistProvider = ({
3595
1799
  deferEngineRebuild = false,
3596
1800
  indefinitePlayback = false,
3597
1801
  sampleRate: sampleRateProp,
1802
+ createAdapter,
3598
1803
  children
3599
1804
  }) => {
3600
1805
  var _a, _b, _c, _d;
3601
1806
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
3602
- const indefinitePlaybackRef = useRef15(indefinitePlayback);
1807
+ const indefinitePlaybackRef = useRef9(indefinitePlayback);
3603
1808
  indefinitePlaybackRef.current = indefinitePlayback;
3604
- const stableZoomLevels = useMemo4(
1809
+ const stableZoomLevels = useMemo3(
3605
1810
  () => zoomLevels,
3606
1811
  // eslint-disable-next-line react-hooks/exhaustive-deps
3607
1812
  [zoomLevels == null ? void 0 : zoomLevels.join(",")]
3608
1813
  );
3609
- const annotations = useMemo4(() => {
1814
+ const annotations = useMemo3(() => {
3610
1815
  if (!(annotationList == null ? void 0 : annotationList.annotations)) return [];
3611
1816
  if (process.env.NODE_ENV !== "production" && annotationList.annotations.length > 0) {
3612
1817
  const first = annotationList.annotations[0];
@@ -3619,63 +1824,50 @@ var WaveformPlaylistProvider = ({
3619
1824
  }
3620
1825
  return annotationList.annotations;
3621
1826
  }, [annotationList == null ? void 0 : annotationList.annotations]);
3622
- const annotationsRef = useRef15(annotations);
1827
+ const annotationsRef = useRef9(annotations);
3623
1828
  annotationsRef.current = annotations;
3624
- const [activeAnnotationId, setActiveAnnotationIdState] = useState15(null);
3625
- const [isPlaying, setIsPlaying] = useState15(false);
3626
- const [currentTime, setCurrentTime] = useState15(0);
3627
- const [duration, setDuration] = useState15(0);
3628
- const [audioBuffers, setAudioBuffers] = useState15([]);
3629
- const [peaksDataArray, setPeaksDataArray] = useState15([]);
3630
- const [trackStates, setTrackStates] = useState15([]);
3631
- const [isAutomaticScroll, setIsAutomaticScroll] = useState15(automaticScroll);
3632
- 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(
3633
1838
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
3634
1839
  );
3635
- const [linkEndpoints, setLinkEndpoints] = useState15((_b = annotationList == null ? void 0 : annotationList.linkEndpoints) != null ? _b : false);
3636
- const [annotationsEditable, setAnnotationsEditable] = useState15((_c = annotationList == null ? void 0 : annotationList.editable) != null ? _c : false);
3637
- const [isReady, setIsReady] = useState15(false);
3638
- const engineRef = useRef15(null);
3639
- const adapterRef = useRef15(null);
3640
- const audioInitializedRef = useRef15(false);
3641
- 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);
3642
1847
  isPlayingRef.current = isPlaying;
3643
- const playStartPositionRef = useRef15(0);
3644
- const currentTimeRef = useRef15(0);
3645
- const visualTimeRef = useRef15(0);
3646
- const tracksRef = useRef15(tracks);
3647
- 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);
3648
1853
  soundFontCacheRef.current = soundFontCache;
3649
- const trackStatesRef = useRef15(trackStates);
3650
- const playbackStartTimeRef = useRef15(0);
3651
- const audioStartPositionRef = useRef15(0);
3652
- const playbackEndTimeRef = useRef15(null);
3653
- const scrollContainerRef = useRef15(null);
3654
- const isAutomaticScrollRef = useRef15(false);
3655
- const frameCallbacksRef = useRef15(/* @__PURE__ */ new Map());
3656
- const continuousPlayRef = useRef15((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
3657
- const activeAnnotationIdRef = useRef15(null);
3658
- const engineTracksRef = useRef15(null);
3659
- const lastTracksVersionRef = useRef15(0);
3660
- const skipEngineDisposeRef = useRef15(false);
3661
- const isDraggingRef = useRef15(false);
3662
- const prevTracksRef = useRef15([]);
3663
- const samplesPerPixelRef = useRef15(initialSamplesPerPixel);
3664
- const [initialSampleRate] = useState15(() => {
3665
- if (typeof AudioContext === "undefined") return sampleRateProp != null ? sampleRateProp : 48e3;
3666
- try {
3667
- if (sampleRateProp !== void 0) {
3668
- return configureGlobalContext({ sampleRate: sampleRateProp });
3669
- }
3670
- return getGlobalAudioContext4().sampleRate;
3671
- } catch (err) {
3672
- console.warn(
3673
- "[waveform-playlist] Failed to configure AudioContext: " + String(err) + " \u2014 falling back to " + (sampleRateProp != null ? sampleRateProp : 48e3) + " Hz"
3674
- );
3675
- return sampleRateProp != null ? sampleRateProp : 48e3;
3676
- }
3677
- });
3678
- 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);
3679
1871
  const { timeFormat, setTimeFormat, formatTime: formatTime2 } = useTimeFormat();
3680
1872
  const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });
3681
1873
  const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;
@@ -3720,20 +1912,20 @@ var WaveformPlaylistProvider = ({
3720
1912
  onEngineState: onUndoEngineState
3721
1913
  } = useUndoState({ engineRef });
3722
1914
  const { animationFrameRef, startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
3723
- const baseScale = useMemo4(
1915
+ const baseScale = useMemo3(
3724
1916
  () => Math.min(...stableZoomLevels != null ? stableZoomLevels : [256, 512, 1024, 2048, 4096, 8192]),
3725
1917
  [stableZoomLevels]
3726
1918
  );
3727
1919
  const { cache: waveformDataCache } = useWaveformDataCache(tracks, baseScale);
3728
- const setContinuousPlay = useCallback19((value) => {
1920
+ const setContinuousPlay = useCallback13((value) => {
3729
1921
  continuousPlayRef.current = value;
3730
1922
  setContinuousPlayState(value);
3731
1923
  }, []);
3732
- const setActiveAnnotationId = useCallback19((value) => {
1924
+ const setActiveAnnotationId = useCallback13((value) => {
3733
1925
  activeAnnotationIdRef.current = value;
3734
1926
  setActiveAnnotationIdState(value);
3735
1927
  }, []);
3736
- const setLoopRegionFromSelection = useCallback19(() => {
1928
+ const setLoopRegionFromSelection = useCallback13(() => {
3737
1929
  var _a2, _b2;
3738
1930
  const start = (_a2 = selectionStartRef.current) != null ? _a2 : 0;
3739
1931
  const end = (_b2 = selectionEndRef.current) != null ? _b2 : 0;
@@ -3741,10 +1933,10 @@ var WaveformPlaylistProvider = ({
3741
1933
  setLoopRegion(start, end);
3742
1934
  }
3743
1935
  }, [setLoopRegion, selectionStartRef, selectionEndRef]);
3744
- useEffect10(() => {
1936
+ useEffect5(() => {
3745
1937
  isAutomaticScrollRef.current = isAutomaticScroll;
3746
1938
  }, [isAutomaticScroll]);
3747
- useEffect10(() => {
1939
+ useEffect5(() => {
3748
1940
  trackStatesRef.current = trackStates;
3749
1941
  }, [trackStates]);
3750
1942
  tracksRef.current = tracks;
@@ -3755,7 +1947,7 @@ var WaveformPlaylistProvider = ({
3755
1947
  return current === pt;
3756
1948
  });
3757
1949
  skipEngineDisposeRef.current = isEngineTracks || isDraggingRef.current || isIncrementalAdd;
3758
- useEffect10(() => {
1950
+ useEffect5(() => {
3759
1951
  if (!scrollContainerRef.current || duration === 0) return;
3760
1952
  const container = scrollContainerRef.current;
3761
1953
  const oldSamplesPerPixel = samplesPerPixelRef.current;
@@ -3771,8 +1963,8 @@ var WaveformPlaylistProvider = ({
3771
1963
  container.scrollLeft = newScrollLeft;
3772
1964
  samplesPerPixelRef.current = newSamplesPerPixel;
3773
1965
  }, [samplesPerPixel, duration]);
3774
- const pendingResumeRef = useRef15(null);
3775
- useEffect10(() => {
1966
+ const pendingResumeRef = useRef9(null);
1967
+ useEffect5(() => {
3776
1968
  var _a2, _b2, _c2, _d2;
3777
1969
  if (isEngineTracks || isDraggingRef.current) {
3778
1970
  if (isEngineTracks) {
@@ -3877,6 +2069,7 @@ var WaveformPlaylistProvider = ({
3877
2069
  stopAnimationFrameLoop();
3878
2070
  pendingResumeRef.current = { position: resumePosition };
3879
2071
  }
2072
+ let cancelled = false;
3880
2073
  const loadAudio = () => __async(null, null, function* () {
3881
2074
  var _a3, _b3, _c3, _d3, _e;
3882
2075
  try {
@@ -3919,7 +2112,17 @@ var WaveformPlaylistProvider = ({
3919
2112
  lastTracksVersionRef.current = 0;
3920
2113
  engineTracksRef.current = null;
3921
2114
  audioInitializedRef.current = false;
3922
- const adapter = createToneAdapter({ effects, soundFontCache: soundFontCacheRef.current });
2115
+ const adapter = yield resolvePlayoutAdapter({
2116
+ createAdapter,
2117
+ effects,
2118
+ soundFontCache: soundFontCacheRef.current,
2119
+ sampleRate: sampleRateProp
2120
+ });
2121
+ if (cancelled) {
2122
+ adapter.dispose();
2123
+ return;
2124
+ }
2125
+ sampleRateRef.current = adapter.audioContext.sampleRate;
3923
2126
  adapterRef.current = adapter;
3924
2127
  const engine = new PlaylistEngine({
3925
2128
  adapter,
@@ -3977,11 +2180,23 @@ var WaveformPlaylistProvider = ({
3977
2180
  prevTracksRef.current = tracks;
3978
2181
  onReady == null ? void 0 : onReady();
3979
2182
  } catch (error) {
3980
- console.warn("[waveform-playlist] Error loading audio:", String(error));
2183
+ if (!engineRef.current && adapterRef.current) {
2184
+ try {
2185
+ adapterRef.current.dispose();
2186
+ } catch (disposeErr) {
2187
+ console.warn(
2188
+ "[waveform-playlist] adapter dispose after load failure threw: " + String(disposeErr)
2189
+ );
2190
+ }
2191
+ adapterRef.current = null;
2192
+ }
2193
+ console.warn("[waveform-playlist] Error loading audio: " + String(error));
2194
+ onError == null ? void 0 : onError(error instanceof Error ? error : new Error(String(error)));
3981
2195
  }
3982
2196
  });
3983
2197
  loadAudio();
3984
2198
  return () => {
2199
+ cancelled = true;
3985
2200
  if (skipEngineDisposeRef.current) {
3986
2201
  skipEngineDisposeRef.current = false;
3987
2202
  return;
@@ -4004,6 +2219,7 @@ var WaveformPlaylistProvider = ({
4004
2219
  // to the live adapter by the sync effect below; only adapter creation reads it
4005
2220
  // (via soundFontCacheRef).
4006
2221
  onReady,
2222
+ onError,
4007
2223
  effects,
4008
2224
  stopAnimationFrameLoop,
4009
2225
  onSelectionEngineState,
@@ -4022,10 +2238,10 @@ var WaveformPlaylistProvider = ({
4022
2238
  stableZoomLevels,
4023
2239
  deferEngineRebuild
4024
2240
  ]);
4025
- useEffect10(() => {
2241
+ useEffect5(() => {
4026
2242
  syncSoundFontCacheToAdapter(adapterRef.current, soundFontCache);
4027
2243
  }, [soundFontCache]);
4028
- useEffect10(() => {
2244
+ useEffect5(() => {
4029
2245
  if (tracks.length === 0) return;
4030
2246
  const allTrackPeaks = tracks.map((track) => {
4031
2247
  const clipPeaks = track.clips.map((clip) => {
@@ -4106,8 +2322,15 @@ var WaveformPlaylistProvider = ({
4106
2322
  });
4107
2323
  setPeaksDataArray(allTrackPeaks);
4108
2324
  }, [tracks, samplesPerPixel, mono, waveformDataCache, deferEngineRebuild]);
4109
- const getPlaybackTimeFallbackWarnedRef = useRef15(false);
4110
- const getPlaybackTime = useCallback19(() => {
2325
+ const getAudioContextTime = useCallback13(
2326
+ () => {
2327
+ var _a2, _b2;
2328
+ return (_b2 = (_a2 = adapterRef.current) == null ? void 0 : _a2.audioContext.currentTime) != null ? _b2 : 0;
2329
+ },
2330
+ []
2331
+ );
2332
+ const getPlaybackTimeFallbackWarnedRef = useRef9(false);
2333
+ const getPlaybackTime = useCallback13(() => {
4111
2334
  var _a2, _b2;
4112
2335
  if (engineRef.current) {
4113
2336
  return engineRef.current.getCurrentTime();
@@ -4118,30 +2341,35 @@ var WaveformPlaylistProvider = ({
4118
2341
  "[waveform-playlist] getPlaybackTime called without engine. Falling back to manual elapsed time (loop wrapping will not work)."
4119
2342
  );
4120
2343
  }
4121
- const elapsed = getContext2().currentTime - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
2344
+ const elapsed = getAudioContextTime() - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
4122
2345
  return ((_b2 = audioStartPositionRef.current) != null ? _b2 : 0) + elapsed;
4123
- }, []);
4124
- const toVisualTime = useCallback19((rawTime) => {
2346
+ }, [getAudioContextTime]);
2347
+ const toVisualTime = useCallback13((rawTime) => {
4125
2348
  return Number.isFinite(rawTime) ? Math.max(0, rawTime) : 0;
4126
2349
  }, []);
4127
- const setCurrentTimeRefs = useCallback19(
2350
+ const setCurrentTimeRefs = useCallback13(
4128
2351
  (rawTime) => {
4129
2352
  currentTimeRef.current = rawTime;
4130
2353
  visualTimeRef.current = toVisualTime(rawTime);
4131
2354
  },
4132
2355
  [toVisualTime]
4133
2356
  );
4134
- const getLookAhead = useCallback19(() => {
2357
+ const getLookAhead = useCallback13(() => {
4135
2358
  var _a2, _b2;
4136
2359
  return (_b2 = (_a2 = engineRef.current) == null ? void 0 : _a2.lookAhead) != null ? _b2 : 0;
4137
2360
  }, []);
4138
- const registerFrameCallback = useCallback19((id, cb) => {
2361
+ const getOutputLatency = useCallback13(() => {
2362
+ var _a2;
2363
+ const audioCtx = (_a2 = adapterRef.current) == null ? void 0 : _a2.audioContext;
2364
+ return audioCtx && "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
2365
+ }, []);
2366
+ const registerFrameCallback = useCallback13((id, cb) => {
4139
2367
  frameCallbacksRef.current.set(id, cb);
4140
2368
  }, []);
4141
- const unregisterFrameCallback = useCallback19((id) => {
2369
+ const unregisterFrameCallback = useCallback13((id) => {
4142
2370
  frameCallbacksRef.current.delete(id);
4143
2371
  }, []);
4144
- const startAnimationLoop = useCallback19(() => {
2372
+ const startAnimationLoop = useCallback13(() => {
4145
2373
  const updateTime = () => {
4146
2374
  const time = getPlaybackTime();
4147
2375
  currentTimeRef.current = time;
@@ -4229,15 +2457,14 @@ var WaveformPlaylistProvider = ({
4229
2457
  setCurrentTimeRefs
4230
2458
  ]);
4231
2459
  const stopAnimationLoop = stopAnimationFrameLoop;
4232
- useEffect10(() => {
2460
+ useEffect5(() => {
4233
2461
  const reschedulePlayback = () => __async(null, null, function* () {
4234
2462
  if (isPlaying && animationFrameRef.current && engineRef.current) {
4235
2463
  if (continuousPlay) {
4236
2464
  const currentPos = currentTimeRef.current;
4237
2465
  engineRef.current.stop();
4238
2466
  stopAnimationLoop();
4239
- const context = getContext2();
4240
- const timeNow = context.currentTime;
2467
+ const timeNow = getAudioContextTime();
4241
2468
  playbackStartTimeRef.current = timeNow;
4242
2469
  audioStartPositionRef.current = currentPos;
4243
2470
  engineRef.current.play(currentPos);
@@ -4253,14 +2480,20 @@ var WaveformPlaylistProvider = ({
4253
2480
  setIsPlaying(false);
4254
2481
  stopAnimationLoop();
4255
2482
  });
4256
- }, [continuousPlay, isPlaying, startAnimationLoop, stopAnimationLoop, animationFrameRef]);
4257
- useEffect10(() => {
2483
+ }, [
2484
+ continuousPlay,
2485
+ isPlaying,
2486
+ startAnimationLoop,
2487
+ stopAnimationLoop,
2488
+ animationFrameRef,
2489
+ getAudioContextTime
2490
+ ]);
2491
+ useEffect5(() => {
4258
2492
  const resumePlayback = () => __async(null, null, function* () {
4259
2493
  if (pendingResumeRef.current && engineRef.current) {
4260
2494
  const { position } = pendingResumeRef.current;
4261
2495
  pendingResumeRef.current = null;
4262
- const context = getContext2();
4263
- const timeNow = context.currentTime;
2496
+ const timeNow = getAudioContextTime();
4264
2497
  playbackStartTimeRef.current = timeNow;
4265
2498
  audioStartPositionRef.current = position;
4266
2499
  if (!audioInitializedRef.current) {
@@ -4277,8 +2510,8 @@ var WaveformPlaylistProvider = ({
4277
2510
  setIsPlaying(false);
4278
2511
  stopAnimationLoop();
4279
2512
  });
4280
- }, [tracks, startAnimationLoop, stopAnimationLoop]);
4281
- const play = useCallback19(
2513
+ }, [tracks, startAnimationLoop, stopAnimationLoop, getAudioContextTime]);
2514
+ const play = useCallback13(
4282
2515
  (startTime, playDuration) => __async(null, null, function* () {
4283
2516
  if (!engineRef.current) return;
4284
2517
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4287,8 +2520,7 @@ var WaveformPlaylistProvider = ({
4287
2520
  engineRef.current.stop();
4288
2521
  engineRef.current.seek(actualStartTime);
4289
2522
  stopAnimationLoop();
4290
- const context = getContext2();
4291
- const startTimeNow = context.currentTime;
2523
+ const startTimeNow = getAudioContextTime();
4292
2524
  playbackStartTimeRef.current = startTimeNow;
4293
2525
  audioStartPositionRef.current = actualStartTime;
4294
2526
  playbackEndTimeRef.current = playDuration !== void 0 ? actualStartTime + playDuration : null;
@@ -4307,9 +2539,9 @@ var WaveformPlaylistProvider = ({
4307
2539
  setIsPlaying(true);
4308
2540
  startAnimationLoop();
4309
2541
  }),
4310
- [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs]
2542
+ [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs, getAudioContextTime]
4311
2543
  );
4312
- const pause = useCallback19(() => {
2544
+ const pause = useCallback13(() => {
4313
2545
  if (!engineRef.current) return;
4314
2546
  const pauseTime = getPlaybackTime();
4315
2547
  engineRef.current.pause();
@@ -4318,7 +2550,7 @@ var WaveformPlaylistProvider = ({
4318
2550
  setCurrentTimeRefs(pauseTime);
4319
2551
  setCurrentTime(pauseTime);
4320
2552
  }, [stopAnimationLoop, getPlaybackTime, setCurrentTimeRefs]);
4321
- const stop = useCallback19(() => {
2553
+ const stop = useCallback13(() => {
4322
2554
  if (!engineRef.current) return;
4323
2555
  engineRef.current.stop();
4324
2556
  setIsPlaying(false);
@@ -4327,7 +2559,7 @@ var WaveformPlaylistProvider = ({
4327
2559
  setCurrentTime(playStartPositionRef.current);
4328
2560
  setActiveAnnotationId(null);
4329
2561
  }, [stopAnimationLoop, setActiveAnnotationId, setCurrentTimeRefs]);
4330
- const seekTo = useCallback19(
2562
+ const seekTo = useCallback13(
4331
2563
  (time) => {
4332
2564
  const clampedTime = Math.max(0, Math.min(time, duration));
4333
2565
  setCurrentTimeRefs(clampedTime);
@@ -4338,7 +2570,7 @@ var WaveformPlaylistProvider = ({
4338
2570
  },
4339
2571
  [duration, isPlaying, play, setCurrentTimeRefs]
4340
2572
  );
4341
- const setTrackMute = useCallback19(
2573
+ const setTrackMute = useCallback13(
4342
2574
  (trackIndex, muted) => {
4343
2575
  var _a2;
4344
2576
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4352,7 +2584,7 @@ var WaveformPlaylistProvider = ({
4352
2584
  },
4353
2585
  [trackStates]
4354
2586
  );
4355
- const setTrackSolo = useCallback19(
2587
+ const setTrackSolo = useCallback13(
4356
2588
  (trackIndex, soloed) => {
4357
2589
  var _a2;
4358
2590
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4366,7 +2598,7 @@ var WaveformPlaylistProvider = ({
4366
2598
  },
4367
2599
  [trackStates]
4368
2600
  );
4369
- const setTrackVolume = useCallback19(
2601
+ const setTrackVolume = useCallback13(
4370
2602
  (trackIndex, volume2) => {
4371
2603
  var _a2;
4372
2604
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4380,7 +2612,7 @@ var WaveformPlaylistProvider = ({
4380
2612
  },
4381
2613
  [trackStates]
4382
2614
  );
4383
- const setTrackPan = useCallback19(
2615
+ const setTrackPan = useCallback13(
4384
2616
  (trackIndex, pan) => {
4385
2617
  var _a2;
4386
2618
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4394,7 +2626,7 @@ var WaveformPlaylistProvider = ({
4394
2626
  },
4395
2627
  [trackStates]
4396
2628
  );
4397
- const setSelection = useCallback19(
2629
+ const setSelection = useCallback13(
4398
2630
  (start, end) => {
4399
2631
  setSelectionEngine(start, end);
4400
2632
  setCurrentTimeRefs(start);
@@ -4407,12 +2639,12 @@ var WaveformPlaylistProvider = ({
4407
2639
  },
4408
2640
  [isPlaying, setSelectionEngine, setCurrentTimeRefs]
4409
2641
  );
4410
- const setScrollContainer = useCallback19((element) => {
2642
+ const setScrollContainer = useCallback13((element) => {
4411
2643
  scrollContainerRef.current = element;
4412
2644
  }, []);
4413
- const onAnnotationsChangeRef = useRef15(onAnnotationsChange);
2645
+ const onAnnotationsChangeRef = useRef9(onAnnotationsChange);
4414
2646
  onAnnotationsChangeRef.current = onAnnotationsChange;
4415
- const setAnnotations = useCallback19(
2647
+ const setAnnotations = useCallback13(
4416
2648
  (action) => {
4417
2649
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4418
2650
  if (!onAnnotationsChangeRef.current) {
@@ -4430,7 +2662,7 @@ var WaveformPlaylistProvider = ({
4430
2662
  const sampleRate = sampleRateRef.current;
4431
2663
  const timeScaleHeight = timescale ? 30 : 0;
4432
2664
  const minimumPlaylistHeight = tracks.length * waveHeight + timeScaleHeight;
4433
- const animationValue = useMemo4(
2665
+ const animationValue = useMemo3(
4434
2666
  () => ({
4435
2667
  isPlaying,
4436
2668
  currentTime,
@@ -4439,7 +2671,9 @@ var WaveformPlaylistProvider = ({
4439
2671
  playbackStartTimeRef,
4440
2672
  audioStartPositionRef,
4441
2673
  getPlaybackTime,
2674
+ getAudioContextTime,
4442
2675
  getLookAhead,
2676
+ getOutputLatency,
4443
2677
  registerFrameCallback,
4444
2678
  unregisterFrameCallback
4445
2679
  }),
@@ -4451,12 +2685,14 @@ var WaveformPlaylistProvider = ({
4451
2685
  playbackStartTimeRef,
4452
2686
  audioStartPositionRef,
4453
2687
  getPlaybackTime,
2688
+ getAudioContextTime,
4454
2689
  getLookAhead,
2690
+ getOutputLatency,
4455
2691
  registerFrameCallback,
4456
2692
  unregisterFrameCallback
4457
2693
  ]
4458
2694
  );
4459
- const stateValue = useMemo4(
2695
+ const stateValue = useMemo3(
4460
2696
  () => ({
4461
2697
  continuousPlay,
4462
2698
  linkEndpoints,
@@ -4492,17 +2728,17 @@ var WaveformPlaylistProvider = ({
4492
2728
  canRedo
4493
2729
  ]
4494
2730
  );
4495
- const setCurrentTimeControl = useCallback19(
2731
+ const setCurrentTimeControl = useCallback13(
4496
2732
  (time) => {
4497
2733
  setCurrentTimeRefs(time);
4498
2734
  setCurrentTime(time);
4499
2735
  },
4500
2736
  [setCurrentTimeRefs]
4501
2737
  );
4502
- const setAutomaticScrollControl = useCallback19((enabled) => {
2738
+ const setAutomaticScrollControl = useCallback13((enabled) => {
4503
2739
  setIsAutomaticScroll(enabled);
4504
2740
  }, []);
4505
- const controlsValue = useMemo4(
2741
+ const controlsValue = useMemo3(
4506
2742
  () => ({
4507
2743
  // Playback controls
4508
2744
  play,
@@ -4578,7 +2814,7 @@ var WaveformPlaylistProvider = ({
4578
2814
  redo
4579
2815
  ]
4580
2816
  );
4581
- const dataValue = useMemo4(
2817
+ const dataValue = useMemo3(
4582
2818
  () => ({
4583
2819
  duration,
4584
2820
  audioBuffers,
@@ -4661,19 +2897,44 @@ var usePlaylistData = () => {
4661
2897
  }
4662
2898
  return context;
4663
2899
  };
2900
+ var usePlaylistDataOptional = () => useContext(PlaylistDataContext);
4664
2901
 
4665
2902
  // src/MediaElementPlaylistContext.tsx
4666
2903
  import {
4667
2904
  createContext as createContext2,
4668
2905
  useContext as useContext2,
4669
- useState as useState16,
4670
- useEffect as useEffect11,
4671
- useRef as useRef16,
4672
- useCallback as useCallback20,
4673
- useMemo as useMemo5
2906
+ useState as useState10,
2907
+ useEffect as useEffect6,
2908
+ useRef as useRef10,
2909
+ useCallback as useCallback14,
2910
+ useMemo as useMemo4
4674
2911
  } from "react";
4675
2912
  import { ThemeProvider as ThemeProvider2 } from "styled-components";
4676
- import { MediaElementPlayout } from "@waveform-playlist/media-element-playout";
2913
+
2914
+ // src/playout/resolveMediaElementPlayout.ts
2915
+ 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`.";
2916
+ function resolveMediaElementPlayout(opts) {
2917
+ return __async(this, null, function* () {
2918
+ if (opts.createPlayout) {
2919
+ return opts.createPlayout();
2920
+ }
2921
+ let mod;
2922
+ try {
2923
+ mod = yield import("@waveform-playlist/media-element-playout");
2924
+ } catch (originalErr) {
2925
+ console.warn(
2926
+ "[waveform-playlist] @waveform-playlist/media-element-playout dynamic import failed: " + String(originalErr)
2927
+ );
2928
+ throw new Error(INSTALL_HINT2);
2929
+ }
2930
+ return new mod.MediaElementPlayout({
2931
+ playbackRate: opts.playbackRate,
2932
+ preservesPitch: opts.preservesPitch
2933
+ });
2934
+ });
2935
+ }
2936
+
2937
+ // src/MediaElementPlaylistContext.tsx
4677
2938
  import { defaultTheme as defaultTheme2 } from "@waveform-playlist/ui-components";
4678
2939
  import { jsx as jsx2 } from "react/jsx-runtime";
4679
2940
  var MediaElementAnimationContext = createContext2(null);
@@ -4697,16 +2958,18 @@ var MediaElementPlaylistProvider = ({
4697
2958
  audioContext,
4698
2959
  onAnnotationsChange,
4699
2960
  onReady,
2961
+ onError,
2962
+ createPlayout,
4700
2963
  children
4701
2964
  }) => {
4702
2965
  var _a;
4703
2966
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
4704
- const [isPlaying, setIsPlaying] = useState16(false);
4705
- const [currentTime, setCurrentTime] = useState16(0);
4706
- const [duration, setDuration] = useState16(0);
4707
- const [peaksDataArray, setPeaksDataArray] = useState16([]);
4708
- const [playbackRate, setPlaybackRateState] = useState16(initialPlaybackRate);
4709
- const annotations = useMemo5(() => {
2967
+ const [isPlaying, setIsPlaying] = useState10(false);
2968
+ const [currentTime, setCurrentTime] = useState10(0);
2969
+ const [duration, setDuration] = useState10(0);
2970
+ const [peaksDataArray, setPeaksDataArray] = useState10([]);
2971
+ const [playbackRate, setPlaybackRateState] = useState10(initialPlaybackRate);
2972
+ const annotations = useMemo4(() => {
4710
2973
  if (!(annotationList == null ? void 0 : annotationList.annotations)) return [];
4711
2974
  if (process.env.NODE_ENV !== "production" && annotationList.annotations.length > 0) {
4712
2975
  const first = annotationList.annotations[0];
@@ -4719,73 +2982,92 @@ var MediaElementPlaylistProvider = ({
4719
2982
  }
4720
2983
  return annotationList.annotations;
4721
2984
  }, [annotationList == null ? void 0 : annotationList.annotations]);
4722
- const annotationsRef = useRef16(annotations);
2985
+ const annotationsRef = useRef10(annotations);
4723
2986
  annotationsRef.current = annotations;
4724
- const [activeAnnotationId, setActiveAnnotationIdState] = useState16(null);
4725
- const [continuousPlay, setContinuousPlayState] = useState16(
2987
+ const [activeAnnotationId, setActiveAnnotationIdState] = useState10(null);
2988
+ const [continuousPlay, setContinuousPlayState] = useState10(
4726
2989
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
4727
2990
  );
4728
- const [samplesPerPixel] = useState16(initialSamplesPerPixel);
4729
- const [isAutomaticScroll, setIsAutomaticScroll] = useState16(automaticScroll);
4730
- const playoutRef = useRef16(null);
4731
- const currentTimeRef = useRef16(0);
4732
- const continuousPlayRef = useRef16(continuousPlay);
4733
- const activeAnnotationIdRef = useRef16(null);
4734
- const scrollContainerRef = useRef16(null);
4735
- const isAutomaticScrollRef = useRef16(automaticScroll);
4736
- const samplesPerPixelRef = useRef16(initialSamplesPerPixel);
2991
+ const [samplesPerPixel] = useState10(initialSamplesPerPixel);
2992
+ const [isAutomaticScroll, setIsAutomaticScroll] = useState10(automaticScroll);
2993
+ const playoutRef = useRef10(null);
2994
+ const currentTimeRef = useRef10(0);
2995
+ const continuousPlayRef = useRef10(continuousPlay);
2996
+ const activeAnnotationIdRef = useRef10(null);
2997
+ const scrollContainerRef = useRef10(null);
2998
+ const isAutomaticScrollRef = useRef10(automaticScroll);
2999
+ const samplesPerPixelRef = useRef10(initialSamplesPerPixel);
4737
3000
  const { startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
4738
- useEffect11(() => {
3001
+ useEffect6(() => {
4739
3002
  continuousPlayRef.current = continuousPlay;
4740
3003
  }, [continuousPlay]);
4741
- useEffect11(() => {
3004
+ useEffect6(() => {
4742
3005
  isAutomaticScrollRef.current = isAutomaticScroll;
4743
3006
  }, [isAutomaticScroll]);
4744
- const setActiveAnnotationId = useCallback20((value) => {
3007
+ const setActiveAnnotationId = useCallback14((value) => {
4745
3008
  activeAnnotationIdRef.current = value;
4746
3009
  setActiveAnnotationIdState(value);
4747
3010
  }, []);
4748
- const setContinuousPlay = useCallback20((value) => {
3011
+ const setContinuousPlay = useCallback14((value) => {
4749
3012
  continuousPlayRef.current = value;
4750
3013
  setContinuousPlayState(value);
4751
3014
  }, []);
4752
- const setScrollContainer = useCallback20((element) => {
3015
+ const setScrollContainer = useCallback14((element) => {
4753
3016
  scrollContainerRef.current = element;
4754
3017
  }, []);
4755
3018
  const sampleRate = track.waveformData.sample_rate;
4756
- useEffect11(() => {
4757
- var _a2, _b;
4758
- const playout = new MediaElementPlayout({
4759
- playbackRate: initialPlaybackRate,
4760
- preservesPitch
4761
- });
4762
- playout.addTrack({
4763
- source: track.source,
4764
- peaks: track.waveformData,
4765
- name: track.name,
4766
- audioContext,
4767
- fadeIn: track.fadeIn,
4768
- fadeOut: track.fadeOut
4769
- });
4770
- const mediaTrack = playout.getTrack((_b = (_a2 = playout["track"]) == null ? void 0 : _a2.id) != null ? _b : "");
4771
- if (mediaTrack) {
4772
- mediaTrack.setOnTimeUpdateCallback((time) => {
4773
- currentTimeRef.current = time;
4774
- });
4775
- }
4776
- playout.setOnPlaybackComplete(() => {
4777
- stopAnimationFrameLoop();
4778
- setIsPlaying(false);
4779
- setActiveAnnotationId(null);
4780
- currentTimeRef.current = 0;
4781
- setCurrentTime(0);
4782
- });
4783
- playoutRef.current = playout;
4784
- setDuration(track.waveformData.duration);
4785
- onReady == null ? void 0 : onReady();
3019
+ useEffect6(() => {
3020
+ let cancelled = false;
3021
+ let createdPlayout = null;
3022
+ (() => __async(null, null, function* () {
3023
+ var _a2, _b;
3024
+ try {
3025
+ const playout = yield resolveMediaElementPlayout({
3026
+ createPlayout,
3027
+ playbackRate: initialPlaybackRate,
3028
+ preservesPitch
3029
+ });
3030
+ if (cancelled) {
3031
+ playout.dispose();
3032
+ return;
3033
+ }
3034
+ createdPlayout = playout;
3035
+ playout.addTrack({
3036
+ source: track.source,
3037
+ peaks: track.waveformData,
3038
+ name: track.name,
3039
+ audioContext,
3040
+ fadeIn: track.fadeIn,
3041
+ fadeOut: track.fadeOut
3042
+ });
3043
+ const mediaTrack = playout.getTrack((_b = (_a2 = playout["track"]) == null ? void 0 : _a2.id) != null ? _b : "");
3044
+ if (mediaTrack) {
3045
+ mediaTrack.setOnTimeUpdateCallback((time) => {
3046
+ currentTimeRef.current = time;
3047
+ });
3048
+ }
3049
+ playout.setOnPlaybackComplete(() => {
3050
+ stopAnimationFrameLoop();
3051
+ setIsPlaying(false);
3052
+ setActiveAnnotationId(null);
3053
+ currentTimeRef.current = 0;
3054
+ setCurrentTime(0);
3055
+ });
3056
+ playoutRef.current = playout;
3057
+ setDuration(track.waveformData.duration);
3058
+ onReady == null ? void 0 : onReady();
3059
+ } catch (err) {
3060
+ console.warn("[waveform-playlist] MediaElement playout init failed: " + String(err));
3061
+ onError == null ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
3062
+ }
3063
+ }))();
4786
3064
  return () => {
3065
+ cancelled = true;
4787
3066
  stopAnimationFrameLoop();
4788
- playout.dispose();
3067
+ if (createdPlayout) {
3068
+ createdPlayout.dispose();
3069
+ }
3070
+ playoutRef.current = null;
4789
3071
  };
4790
3072
  }, [
4791
3073
  track.source,
@@ -4797,10 +3079,12 @@ var MediaElementPlaylistProvider = ({
4797
3079
  initialPlaybackRate,
4798
3080
  preservesPitch,
4799
3081
  onReady,
3082
+ onError,
4800
3083
  stopAnimationFrameLoop,
4801
- setActiveAnnotationId
3084
+ setActiveAnnotationId,
3085
+ createPlayout
4802
3086
  ]);
4803
- useEffect11(() => {
3087
+ useEffect6(() => {
4804
3088
  var _a2;
4805
3089
  try {
4806
3090
  const extractedPeaks = extractPeaksFromWaveformData(
@@ -4829,7 +3113,7 @@ var MediaElementPlaylistProvider = ({
4829
3113
  console.warn("[waveform-playlist] Failed to extract peaks from waveform data:", err);
4830
3114
  }
4831
3115
  }, [track.waveformData, track.name, samplesPerPixel, sampleRate]);
4832
- const startAnimationLoop = useCallback20(() => {
3116
+ const startAnimationLoop = useCallback14(() => {
4833
3117
  const updateTime = () => {
4834
3118
  var _a2, _b, _c;
4835
3119
  const time = (_b = (_a2 = playoutRef.current) == null ? void 0 : _a2.getCurrentTime()) != null ? _b : 0;
@@ -4872,7 +3156,7 @@ var MediaElementPlaylistProvider = ({
4872
3156
  startAnimationFrameLoop(updateTime);
4873
3157
  }, [setActiveAnnotationId, sampleRate, startAnimationFrameLoop]);
4874
3158
  const stopAnimationLoop = stopAnimationFrameLoop;
4875
- const play = useCallback20(
3159
+ const play = useCallback14(
4876
3160
  (startTime) => {
4877
3161
  if (!playoutRef.current) return;
4878
3162
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4882,14 +3166,14 @@ var MediaElementPlaylistProvider = ({
4882
3166
  },
4883
3167
  [startAnimationLoop]
4884
3168
  );
4885
- const pause = useCallback20(() => {
3169
+ const pause = useCallback14(() => {
4886
3170
  if (!playoutRef.current) return;
4887
3171
  playoutRef.current.pause();
4888
3172
  setIsPlaying(false);
4889
3173
  stopAnimationLoop();
4890
3174
  setCurrentTime(playoutRef.current.getCurrentTime());
4891
3175
  }, [stopAnimationLoop]);
4892
- const stop = useCallback20(() => {
3176
+ const stop = useCallback14(() => {
4893
3177
  if (!playoutRef.current) return;
4894
3178
  playoutRef.current.stop();
4895
3179
  setIsPlaying(false);
@@ -4898,7 +3182,7 @@ var MediaElementPlaylistProvider = ({
4898
3182
  setCurrentTime(0);
4899
3183
  setActiveAnnotationId(null);
4900
3184
  }, [stopAnimationLoop, setActiveAnnotationId]);
4901
- const seekTo = useCallback20(
3185
+ const seekTo = useCallback14(
4902
3186
  (time) => {
4903
3187
  const clampedTime = Math.max(0, Math.min(time, duration));
4904
3188
  currentTimeRef.current = clampedTime;
@@ -4909,7 +3193,7 @@ var MediaElementPlaylistProvider = ({
4909
3193
  },
4910
3194
  [duration]
4911
3195
  );
4912
- const setPlaybackRate = useCallback20((rate) => {
3196
+ const setPlaybackRate = useCallback14((rate) => {
4913
3197
  const clampedRate = Math.max(0.5, Math.min(2, rate));
4914
3198
  setPlaybackRateState(clampedRate);
4915
3199
  if (playoutRef.current) {
@@ -4917,7 +3201,7 @@ var MediaElementPlaylistProvider = ({
4917
3201
  }
4918
3202
  }, []);
4919
3203
  const timeScaleHeight = timescale ? 30 : 0;
4920
- const animationValue = useMemo5(
3204
+ const animationValue = useMemo4(
4921
3205
  () => ({
4922
3206
  isPlaying,
4923
3207
  currentTime,
@@ -4925,7 +3209,7 @@ var MediaElementPlaylistProvider = ({
4925
3209
  }),
4926
3210
  [isPlaying, currentTime]
4927
3211
  );
4928
- const stateValue = useMemo5(
3212
+ const stateValue = useMemo4(
4929
3213
  () => ({
4930
3214
  continuousPlay,
4931
3215
  annotations,
@@ -4935,9 +3219,9 @@ var MediaElementPlaylistProvider = ({
4935
3219
  }),
4936
3220
  [continuousPlay, annotations, activeAnnotationId, playbackRate, isAutomaticScroll]
4937
3221
  );
4938
- const onAnnotationsChangeRef = useRef16(onAnnotationsChange);
3222
+ const onAnnotationsChangeRef = useRef10(onAnnotationsChange);
4939
3223
  onAnnotationsChangeRef.current = onAnnotationsChange;
4940
- const setAnnotations = useCallback20(
3224
+ const setAnnotations = useCallback14(
4941
3225
  (action) => {
4942
3226
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4943
3227
  if (!onAnnotationsChangeRef.current) {
@@ -4952,7 +3236,7 @@ var MediaElementPlaylistProvider = ({
4952
3236
  },
4953
3237
  []
4954
3238
  );
4955
- const controlsValue = useMemo5(
3239
+ const controlsValue = useMemo4(
4956
3240
  () => ({
4957
3241
  play,
4958
3242
  pause,
@@ -4980,7 +3264,7 @@ var MediaElementPlaylistProvider = ({
4980
3264
  setScrollContainer
4981
3265
  ]
4982
3266
  );
4983
- const dataValue = useMemo5(
3267
+ const dataValue = useMemo4(
4984
3268
  () => ({
4985
3269
  duration,
4986
3270
  peaksDataArray,
@@ -5044,7 +3328,7 @@ var useMediaElementData = () => {
5044
3328
  };
5045
3329
 
5046
3330
  // src/components/PlaybackControls.tsx
5047
- import { useCallback as useCallback21 } from "react";
3331
+ import { useCallback as useCallback15 } from "react";
5048
3332
  import { BaseControlButton } from "@waveform-playlist/ui-components";
5049
3333
  import { jsx as jsx3 } from "react/jsx-runtime";
5050
3334
  var PlayButton = ({ className }) => {
@@ -5180,7 +3464,7 @@ var ClearAllButton = ({
5180
3464
  className
5181
3465
  }) => {
5182
3466
  const { stop } = usePlaylistControls();
5183
- const handleClick = useCallback21(() => {
3467
+ const handleClick = useCallback15(() => {
5184
3468
  stop();
5185
3469
  onClearAll();
5186
3470
  }, [stop, onClearAll]);
@@ -5208,7 +3492,7 @@ var ZoomOutButton = ({
5208
3492
  };
5209
3493
 
5210
3494
  // src/components/ContextualControls.tsx
5211
- import { useRef as useRef17, useEffect as useEffect12 } from "react";
3495
+ import { useRef as useRef11, useEffect as useEffect7 } from "react";
5212
3496
  import {
5213
3497
  MasterVolumeControl as BaseMasterVolumeControl,
5214
3498
  TimeFormatSelect as BaseTimeFormatSelect,
@@ -5247,10 +3531,10 @@ var PositionDisplay = styled.span`
5247
3531
  `;
5248
3532
  var AudioPosition = ({ className }) => {
5249
3533
  var _a;
5250
- const timeRef = useRef17(null);
3534
+ const timeRef = useRef11(null);
5251
3535
  const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
5252
3536
  const { timeFormat: format } = usePlaylistData();
5253
- useEffect12(() => {
3537
+ useEffect7(() => {
5254
3538
  const id = "audio-position";
5255
3539
  if (isPlaying) {
5256
3540
  registerFrameCallback(id, ({ time }) => {
@@ -5261,7 +3545,7 @@ var AudioPosition = ({ className }) => {
5261
3545
  }
5262
3546
  return () => unregisterFrameCallback(id);
5263
3547
  }, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);
5264
- useEffect12(() => {
3548
+ useEffect7(() => {
5265
3549
  var _a2;
5266
3550
  if (!isPlaying && timeRef.current) {
5267
3551
  timeRef.current.textContent = formatTime((_a2 = currentTimeRef.current) != null ? _a2 : 0, format);
@@ -5338,53 +3622,6 @@ var DownloadAnnotationsButton = ({
5338
3622
  return /* @__PURE__ */ jsx6(Base, { annotations, filename, className });
5339
3623
  };
5340
3624
 
5341
- // src/components/ExportControls.tsx
5342
- import { BaseControlButton as BaseControlButton3 } from "@waveform-playlist/ui-components";
5343
- import { jsx as jsx7 } from "react/jsx-runtime";
5344
- var ExportWavButton = ({
5345
- label = "Export WAV",
5346
- filename = "export",
5347
- mode = "master",
5348
- trackIndex,
5349
- bitDepth = 16,
5350
- applyEffects = true,
5351
- effectsFunction,
5352
- createOfflineTrackEffects,
5353
- className,
5354
- onExportComplete,
5355
- onExportError
5356
- }) => {
5357
- const { tracks, trackStates } = usePlaylistData();
5358
- const { exportWav, isExporting, progress } = useExportWav();
5359
- const handleExport = () => __async(null, null, function* () {
5360
- try {
5361
- const result = yield exportWav(tracks, trackStates, {
5362
- filename,
5363
- mode,
5364
- trackIndex,
5365
- bitDepth,
5366
- applyEffects,
5367
- effectsFunction,
5368
- createOfflineTrackEffects,
5369
- autoDownload: true
5370
- });
5371
- onExportComplete == null ? void 0 : onExportComplete(result.blob);
5372
- } catch (error) {
5373
- onExportError == null ? void 0 : onExportError(error instanceof Error ? error : new Error("Export failed"));
5374
- }
5375
- });
5376
- const buttonLabel = isExporting ? `Exporting ${Math.round(progress * 100)}%` : label;
5377
- return /* @__PURE__ */ jsx7(
5378
- BaseControlButton3,
5379
- {
5380
- onClick: handleExport,
5381
- disabled: isExporting || tracks.length === 0,
5382
- className,
5383
- children: buttonLabel
5384
- }
5385
- );
5386
- };
5387
-
5388
3625
  // src/contexts/ClipInteractionContext.tsx
5389
3626
  import { createContext as createContext4, useContext as useContext4 } from "react";
5390
3627
  var ClipInteractionContext = createContext4(false);
@@ -5394,10 +3631,9 @@ function useClipInteractionEnabled() {
5394
3631
  }
5395
3632
 
5396
3633
  // src/components/PlaylistVisualization.tsx
5397
- import { useContext as useContext6, useRef as useRef20, useState as useState17, useMemo as useMemo6, useCallback as useCallback22 } from "react";
3634
+ import { useContext as useContext6, useRef as useRef14, useState as useState11, useMemo as useMemo5, useCallback as useCallback16 } from "react";
5398
3635
  import { createPortal } from "react-dom";
5399
3636
  import styled4 from "styled-components";
5400
- import { getGlobalAudioContext as getGlobalAudioContext5 } from "@waveform-playlist/playout";
5401
3637
  import {
5402
3638
  Playlist,
5403
3639
  Track as TrackComponent,
@@ -5425,9 +3661,9 @@ import {
5425
3661
  import { audibleLatencySamples } from "@waveform-playlist/core";
5426
3662
 
5427
3663
  // src/components/AnimatedPlayhead.tsx
5428
- import { useRef as useRef18, useEffect as useEffect13 } from "react";
3664
+ import { useRef as useRef12, useEffect as useEffect8 } from "react";
5429
3665
  import styled2 from "styled-components";
5430
- import { jsx as jsx8 } from "react/jsx-runtime";
3666
+ import { jsx as jsx7 } from "react/jsx-runtime";
5431
3667
  var PlayheadLine = styled2.div.attrs((props) => ({
5432
3668
  style: {
5433
3669
  width: `${props.$width}px`,
@@ -5443,7 +3679,7 @@ var PlayheadLine = styled2.div.attrs((props) => ({
5443
3679
  will-change: transform;
5444
3680
  `;
5445
3681
  var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5446
- const playheadRef = useRef18(null);
3682
+ const playheadRef = useRef12(null);
5447
3683
  const {
5448
3684
  isPlaying,
5449
3685
  currentTimeRef,
@@ -5452,7 +3688,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5452
3688
  unregisterFrameCallback
5453
3689
  } = usePlaybackAnimation();
5454
3690
  const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();
5455
- useEffect13(() => {
3691
+ useEffect8(() => {
5456
3692
  const id = "playhead";
5457
3693
  if (isPlaying) {
5458
3694
  registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {
@@ -5464,7 +3700,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5464
3700
  }
5465
3701
  return () => unregisterFrameCallback(id);
5466
3702
  }, [isPlaying, registerFrameCallback, unregisterFrameCallback]);
5467
- useEffect13(() => {
3703
+ useEffect8(() => {
5468
3704
  var _a, _b;
5469
3705
  if (!isPlaying && playheadRef.current) {
5470
3706
  const time = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
@@ -5472,11 +3708,11 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5472
3708
  playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
5473
3709
  }
5474
3710
  });
5475
- return /* @__PURE__ */ jsx8(PlayheadLine, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
3711
+ return /* @__PURE__ */ jsx7(PlayheadLine, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
5476
3712
  };
5477
3713
 
5478
3714
  // src/components/ChannelWithProgress.tsx
5479
- import { useId, useRef as useRef19, useEffect as useEffect14 } from "react";
3715
+ import { useId, useRef as useRef13, useEffect as useEffect9 } from "react";
5480
3716
  import styled3 from "styled-components";
5481
3717
  import {
5482
3718
  clipPixelWidth as computeClipPixelWidth
@@ -5487,7 +3723,7 @@ import {
5487
3723
  usePlaylistInfo,
5488
3724
  waveformColorToCss
5489
3725
  } from "@waveform-playlist/ui-components";
5490
- import { Fragment, jsx as jsx9, jsxs } from "react/jsx-runtime";
3726
+ import { Fragment, jsx as jsx8, jsxs } from "react/jsx-runtime";
5491
3727
  var ChannelWrapper = styled3.div`
5492
3728
  position: relative;
5493
3729
  `;
@@ -5541,7 +3777,7 @@ var ChannelWithProgress = (_a) => {
5541
3777
  "clipSampleRate",
5542
3778
  "clipOffsetSeconds"
5543
3779
  ]);
5544
- const progressRef = useRef19(null);
3780
+ const progressRef = useRef13(null);
5545
3781
  const callbackId = useId();
5546
3782
  const theme = useTheme();
5547
3783
  const { waveHeight } = usePlaylistInfo();
@@ -5559,7 +3795,7 @@ var ChannelWithProgress = (_a) => {
5559
3795
  clipDurationSamples,
5560
3796
  samplesPerPixel
5561
3797
  );
5562
- useEffect14(() => {
3798
+ useEffect9(() => {
5563
3799
  if (isPlaying) {
5564
3800
  registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {
5565
3801
  if (progressRef.current) {
@@ -5587,7 +3823,7 @@ var ChannelWithProgress = (_a) => {
5587
3823
  registerFrameCallback,
5588
3824
  unregisterFrameCallback
5589
3825
  ]);
5590
- useEffect14(() => {
3826
+ useEffect9(() => {
5591
3827
  var _a2, _b2;
5592
3828
  if (!isPlaying && progressRef.current) {
5593
3829
  const currentTime = (_b2 = (_a2 = visualTimeRef.current) != null ? _a2 : currentTimeRef.current) != null ? _b2 : 0;
@@ -5622,7 +3858,7 @@ var ChannelWithProgress = (_a) => {
5622
3858
  const waveformBackgroundCss = waveformColorToCss(backgroundColor);
5623
3859
  return /* @__PURE__ */ jsxs(ChannelWrapper, { children: [
5624
3860
  isBothMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
5625
- /* @__PURE__ */ jsx9(
3861
+ /* @__PURE__ */ jsx8(
5626
3862
  Background,
5627
3863
  {
5628
3864
  $color: "#000",
@@ -5631,7 +3867,7 @@ var ChannelWithProgress = (_a) => {
5631
3867
  $width: smartChannelProps.length
5632
3868
  }
5633
3869
  ),
5634
- /* @__PURE__ */ jsx9(
3870
+ /* @__PURE__ */ jsx8(
5635
3871
  Background,
5636
3872
  {
5637
3873
  $color: waveformBackgroundCss,
@@ -5640,7 +3876,7 @@ var ChannelWithProgress = (_a) => {
5640
3876
  $width: smartChannelProps.length
5641
3877
  }
5642
3878
  )
5643
- ] }) : /* @__PURE__ */ jsx9(
3879
+ ] }) : /* @__PURE__ */ jsx8(
5644
3880
  Background,
5645
3881
  {
5646
3882
  $color: backgroundCss,
@@ -5649,7 +3885,7 @@ var ChannelWithProgress = (_a) => {
5649
3885
  $width: smartChannelProps.length
5650
3886
  }
5651
3887
  ),
5652
- !isPianoRollMode && /* @__PURE__ */ jsx9(
3888
+ !isPianoRollMode && /* @__PURE__ */ jsx8(
5653
3889
  ProgressOverlay,
5654
3890
  {
5655
3891
  ref: progressRef,
@@ -5659,7 +3895,7 @@ var ChannelWithProgress = (_a) => {
5659
3895
  $width: clipPixelWidth
5660
3896
  }
5661
3897
  ),
5662
- /* @__PURE__ */ jsx9(ChannelContainer, { children: /* @__PURE__ */ jsx9(
3898
+ /* @__PURE__ */ jsx8(ChannelContainer, { children: /* @__PURE__ */ jsx8(
5663
3899
  SmartChannel,
5664
3900
  __spreadProps(__spreadValues({}, smartChannelProps), {
5665
3901
  transparentBackground: true,
@@ -5686,7 +3922,7 @@ function useSpectrogramIntegration() {
5686
3922
  }
5687
3923
 
5688
3924
  // src/components/PlaylistVisualization.tsx
5689
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
3925
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
5690
3926
  var DEFAULT_EMPTY_TRACK_DURATION = 60;
5691
3927
  var ControlSlot = styled4.div.attrs((props) => ({
5692
3928
  style: { height: `${props.$height}px` }
@@ -5705,7 +3941,8 @@ var CustomPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) =>
5705
3941
  visualTimeRef,
5706
3942
  playbackStartTimeRef,
5707
3943
  audioStartPositionRef,
5708
- getPlaybackTime
3944
+ getPlaybackTime,
3945
+ getAudioContextTime
5709
3946
  } = usePlaybackAnimation();
5710
3947
  const visualTime = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
5711
3948
  return renderPlayhead({
@@ -5719,7 +3956,7 @@ var CustomPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) =>
5719
3956
  samplesPerPixel,
5720
3957
  sampleRate,
5721
3958
  controlsOffset: 0,
5722
- getAudioContextTime: () => getGlobalAudioContext5().currentTime,
3959
+ getAudioContextTime,
5723
3960
  getPlaybackTime
5724
3961
  });
5725
3962
  };
@@ -5744,7 +3981,7 @@ var PlaylistVisualization = ({
5744
3981
  }) => {
5745
3982
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
5746
3983
  const theme = useTheme2();
5747
- const { isPlaying, getLookAhead } = usePlaybackAnimation();
3984
+ const { isPlaying, getLookAhead, getOutputLatency } = usePlaybackAnimation();
5748
3985
  const {
5749
3986
  selectionStart,
5750
3987
  selectionEnd,
@@ -5790,7 +4027,7 @@ var PlaylistVisualization = ({
5790
4027
  mono
5791
4028
  } = usePlaylistData();
5792
4029
  const spectrogram = useContext6(SpectrogramIntegrationContext);
5793
- const perTrackSpectrogramHelpers = useMemo6(() => {
4030
+ const perTrackSpectrogramHelpers = useMemo5(() => {
5794
4031
  if (!spectrogram)
5795
4032
  return /* @__PURE__ */ new Map();
5796
4033
  const helpers = /* @__PURE__ */ new Map();
@@ -5809,11 +4046,11 @@ var PlaylistVisualization = ({
5809
4046
  });
5810
4047
  return helpers;
5811
4048
  }, [tracks, spectrogram]);
5812
- const [settingsModalTrackId, setSettingsModalTrackId] = useState17(null);
5813
- const [isSelecting, setIsSelecting] = useState17(false);
5814
- const mouseDownTimeRef = useRef20(0);
5815
- const scrollContainerRef = useRef20(null);
5816
- const handleScrollContainerRef = useCallback22(
4049
+ const [settingsModalTrackId, setSettingsModalTrackId] = useState11(null);
4050
+ const [isSelecting, setIsSelecting] = useState11(false);
4051
+ const mouseDownTimeRef = useRef14(0);
4052
+ const scrollContainerRef = useRef14(null);
4053
+ const handleScrollContainerRef = useCallback16(
5817
4054
  (element) => {
5818
4055
  scrollContainerRef.current = element;
5819
4056
  setScrollContainer(element);
@@ -5851,7 +4088,7 @@ var PlaylistVisualization = ({
5851
4088
  );
5852
4089
  }
5853
4090
  });
5854
- const selectTrack = useCallback22(
4091
+ const selectTrack = useCallback16(
5855
4092
  (trackIndex) => {
5856
4093
  if (trackIndex >= 0 && trackIndex < tracks.length) {
5857
4094
  const track = tracks[trackIndex];
@@ -5921,7 +4158,7 @@ var PlaylistVisualization = ({
5921
4158
  };
5922
4159
  const hasClips = tracks.some((track) => track.clips.length > 0);
5923
4160
  if (hasClips && peaksDataArray.length === 0) {
5924
- return /* @__PURE__ */ jsx10("div", { className, children: "Loading waveform..." });
4161
+ return /* @__PURE__ */ jsx9("div", { className, children: "Loading waveform..." });
5925
4162
  }
5926
4163
  const trackControlsSlots = controls.show ? peaksDataArray.map((trackClipPeaks, trackIndex) => {
5927
4164
  var _a2, _b2, _c2;
@@ -5940,7 +4177,7 @@ var PlaylistVisualization = ({
5940
4177
  const slotHeight = waveHeight * maxChannels + (showClipHeaders ? CLIP_HEADER_HEIGHT : 0);
5941
4178
  const trackControlContent = renderTrackControls ? renderTrackControls(trackIndex) : /* @__PURE__ */ jsxs2(Controls, { onClick: () => selectTrack(trackIndex), children: [
5942
4179
  /* @__PURE__ */ jsxs2(Header, { style: { justifyContent: "center", position: "relative" }, children: [
5943
- onRemoveTrack && /* @__PURE__ */ jsx10(
4180
+ onRemoveTrack && /* @__PURE__ */ jsx9(
5944
4181
  CloseButton,
5945
4182
  {
5946
4183
  onClick: (e) => {
@@ -5949,7 +4186,7 @@ var PlaylistVisualization = ({
5949
4186
  }
5950
4187
  }
5951
4188
  ),
5952
- /* @__PURE__ */ jsx10(
4189
+ /* @__PURE__ */ jsx9(
5953
4190
  "span",
5954
4191
  {
5955
4192
  style: {
@@ -5962,7 +4199,7 @@ var PlaylistVisualization = ({
5962
4199
  children: trackState.name || `Track ${trackIndex + 1}`
5963
4200
  }
5964
4201
  ),
5965
- (spectrogram == null ? void 0 : spectrogram.renderMenuItems) && /* @__PURE__ */ jsx10("span", { style: { position: "absolute", right: 0, top: 0 }, children: /* @__PURE__ */ jsx10(
4202
+ (spectrogram == null ? void 0 : spectrogram.renderMenuItems) && /* @__PURE__ */ jsx9("span", { style: { position: "absolute", right: 0, top: 0 }, children: /* @__PURE__ */ jsx9(
5966
4203
  TrackMenu,
5967
4204
  {
5968
4205
  items: (onClose) => spectrogram.renderMenuItems({
@@ -5975,7 +4212,7 @@ var PlaylistVisualization = ({
5975
4212
  ) })
5976
4213
  ] }),
5977
4214
  /* @__PURE__ */ jsxs2(ButtonGroup, { children: [
5978
- /* @__PURE__ */ jsx10(
4215
+ /* @__PURE__ */ jsx9(
5979
4216
  Button,
5980
4217
  {
5981
4218
  $variant: trackState.muted ? "danger" : "outline",
@@ -5983,7 +4220,7 @@ var PlaylistVisualization = ({
5983
4220
  children: "Mute"
5984
4221
  }
5985
4222
  ),
5986
- /* @__PURE__ */ jsx10(
4223
+ /* @__PURE__ */ jsx9(
5987
4224
  Button,
5988
4225
  {
5989
4226
  $variant: trackState.soloed ? "info" : "outline",
@@ -5993,8 +4230,8 @@ var PlaylistVisualization = ({
5993
4230
  )
5994
4231
  ] }),
5995
4232
  /* @__PURE__ */ jsxs2(SliderWrapper, { children: [
5996
- /* @__PURE__ */ jsx10(VolumeDownIcon, {}),
5997
- /* @__PURE__ */ jsx10(
4233
+ /* @__PURE__ */ jsx9(VolumeDownIcon, {}),
4234
+ /* @__PURE__ */ jsx9(
5998
4235
  Slider,
5999
4236
  {
6000
4237
  min: "0",
@@ -6004,11 +4241,11 @@ var PlaylistVisualization = ({
6004
4241
  onChange: (e) => setTrackVolume(trackIndex, parseFloat(e.target.value))
6005
4242
  }
6006
4243
  ),
6007
- /* @__PURE__ */ jsx10(VolumeUpIcon, {})
4244
+ /* @__PURE__ */ jsx9(VolumeUpIcon, {})
6008
4245
  ] }),
6009
4246
  /* @__PURE__ */ jsxs2(SliderWrapper, { children: [
6010
- /* @__PURE__ */ jsx10("span", { children: "L" }),
6011
- /* @__PURE__ */ jsx10(
4247
+ /* @__PURE__ */ jsx9("span", { children: "L" }),
4248
+ /* @__PURE__ */ jsx9(
6012
4249
  Slider,
6013
4250
  {
6014
4251
  min: "-1",
@@ -6018,10 +4255,10 @@ var PlaylistVisualization = ({
6018
4255
  onChange: (e) => setTrackPan(trackIndex, parseFloat(e.target.value))
6019
4256
  }
6020
4257
  ),
6021
- /* @__PURE__ */ jsx10("span", { children: "R" })
4258
+ /* @__PURE__ */ jsx9("span", { children: "R" })
6022
4259
  ] })
6023
4260
  ] });
6024
- return /* @__PURE__ */ jsx10(
4261
+ return /* @__PURE__ */ jsx9(
6025
4262
  ControlSlot,
6026
4263
  {
6027
4264
  $height: slotHeight,
@@ -6032,7 +4269,7 @@ var PlaylistVisualization = ({
6032
4269
  );
6033
4270
  }) : void 0;
6034
4271
  return /* @__PURE__ */ jsxs2(DevicePixelRatioProvider, { children: [
6035
- /* @__PURE__ */ jsx10(
4272
+ /* @__PURE__ */ jsx9(
6036
4273
  PlaylistInfoContext.Provider,
6037
4274
  {
6038
4275
  value: {
@@ -6046,7 +4283,7 @@ var PlaylistVisualization = ({
6046
4283
  barWidth,
6047
4284
  barGap
6048
4285
  },
6049
- children: /* @__PURE__ */ jsx10(
4286
+ children: /* @__PURE__ */ jsx9(
6050
4287
  Playlist,
6051
4288
  {
6052
4289
  theme,
@@ -6064,8 +4301,8 @@ var PlaylistVisualization = ({
6064
4301
  trackControlsSlots,
6065
4302
  timescaleGapHeight: timeScaleHeight > 0 ? timeScaleHeight + 1 : 0,
6066
4303
  timescale: timeScaleHeight > 0 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
6067
- /* @__PURE__ */ jsx10(SmartScale, { renderTick }),
6068
- isLoopEnabled && /* @__PURE__ */ jsx10(
4304
+ /* @__PURE__ */ jsx9(SmartScale, { renderTick }),
4305
+ isLoopEnabled && /* @__PURE__ */ jsx9(
6069
4306
  TimescaleLoopRegion,
6070
4307
  {
6071
4308
  startPosition: Math.min(loopStart, loopEnd) * sampleRate / samplesPerPixel,
@@ -6111,7 +4348,7 @@ var PlaylistVisualization = ({
6111
4348
  const helpers = perTrackSpectrogramHelpers.get(track.id);
6112
4349
  const trackCfg = helpers == null ? void 0 : helpers.config;
6113
4350
  if (!(trackCfg == null ? void 0 : trackCfg.labels) || !helpers) return null;
6114
- return /* @__PURE__ */ jsx10(
4351
+ return /* @__PURE__ */ jsx9(
6115
4352
  SpectrogramLabels,
6116
4353
  {
6117
4354
  waveHeight,
@@ -6129,7 +4366,7 @@ var PlaylistVisualization = ({
6129
4366
  trackClipPeaks.map((clip, clipIndex) => {
6130
4367
  const peaksData = clip.peaks;
6131
4368
  const width = peaksData.length;
6132
- return /* @__PURE__ */ jsx10(
4369
+ return /* @__PURE__ */ jsx9(
6133
4370
  Clip,
6134
4371
  {
6135
4372
  clipId: clip.clipId,
@@ -6159,7 +4396,7 @@ var PlaylistVisualization = ({
6159
4396
  selectTrack(trackIndex);
6160
4397
  },
6161
4398
  children: peaksData.data.map((channelPeaks, channelIndex) => {
6162
- return /* @__PURE__ */ jsx10(
4399
+ return /* @__PURE__ */ jsx9(
6163
4400
  ChannelWithProgress,
6164
4401
  {
6165
4402
  index: channelIndex,
@@ -6186,8 +4423,7 @@ var PlaylistVisualization = ({
6186
4423
  );
6187
4424
  }),
6188
4425
  (recordingState == null ? void 0 : recordingState.isRecording) && recordingState.trackId === track.id && ((_d2 = recordingState.peaks[0]) == null ? void 0 : _d2.length) > 0 && (() => {
6189
- const audioCtx = getGlobalAudioContext5();
6190
- const outputLatency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
4426
+ const outputLatency = getOutputLatency();
6191
4427
  const lookAhead = getLookAhead();
6192
4428
  const latencyOffsetSamples = audibleLatencySamples(
6193
4429
  outputLatency,
@@ -6203,7 +4439,7 @@ var PlaylistVisualization = ({
6203
4439
  const previewChannels = (mono ? recordingState.peaks.slice(0, 1) : recordingState.peaks).map(
6204
4440
  (channelPeaks) => skipPeakElements > 0 && skipPeakElements < channelPeaks.length ? channelPeaks.subarray(skipPeakElements) : channelPeaks
6205
4441
  );
6206
- return /* @__PURE__ */ jsx10(
4442
+ return /* @__PURE__ */ jsx9(
6207
4443
  Clip,
6208
4444
  {
6209
4445
  clipId: "recording-preview",
@@ -6217,7 +4453,7 @@ var PlaylistVisualization = ({
6217
4453
  disableHeaderDrag: true,
6218
4454
  isSelected: track.id === selectedTrackId,
6219
4455
  trackId: track.id,
6220
- children: previewChannels.map((channelPeaks, chIdx) => /* @__PURE__ */ jsx10(
4456
+ children: previewChannels.map((channelPeaks, chIdx) => /* @__PURE__ */ jsx9(
6221
4457
  ChannelWithProgress,
6222
4458
  {
6223
4459
  index: chIdx,
@@ -6239,11 +4475,11 @@ var PlaylistVisualization = ({
6239
4475
  track.id
6240
4476
  );
6241
4477
  }),
6242
- annotations.length > 0 && annotationIntegration && /* @__PURE__ */ jsx10(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
4478
+ annotations.length > 0 && annotationIntegration && /* @__PURE__ */ jsx9(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
6243
4479
  const startPosition = annotation.start * sampleRate / samplesPerPixel;
6244
4480
  const endPosition = annotation.end * sampleRate / samplesPerPixel;
6245
4481
  const label = getAnnotationBoxLabel ? getAnnotationBoxLabel(annotation, index) : annotation.id;
6246
- return /* @__PURE__ */ jsx10(
4482
+ return /* @__PURE__ */ jsx9(
6247
4483
  annotationIntegration.AnnotationBox,
6248
4484
  {
6249
4485
  annotationId: annotation.id,
@@ -6259,7 +4495,7 @@ var PlaylistVisualization = ({
6259
4495
  annotation.id
6260
4496
  );
6261
4497
  }) }),
6262
- selectionStart !== selectionEnd && /* @__PURE__ */ jsx10(
4498
+ selectionStart !== selectionEnd && /* @__PURE__ */ jsx9(
6263
4499
  Selection,
6264
4500
  {
6265
4501
  startPosition: Math.min(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
@@ -6267,7 +4503,7 @@ var PlaylistVisualization = ({
6267
4503
  color: theme.selectionColor
6268
4504
  }
6269
4505
  ),
6270
- (isPlaying || selectionStart === selectionEnd) && (renderPlayhead ? /* @__PURE__ */ jsx10(
4506
+ (isPlaying || selectionStart === selectionEnd) && (renderPlayhead ? /* @__PURE__ */ jsx9(
6271
4507
  CustomPlayhead,
6272
4508
  {
6273
4509
  renderPlayhead,
@@ -6275,14 +4511,14 @@ var PlaylistVisualization = ({
6275
4511
  samplesPerPixel,
6276
4512
  sampleRate
6277
4513
  }
6278
- ) : /* @__PURE__ */ jsx10(AnimatedPlayhead, { color: theme.playheadColor }))
4514
+ ) : /* @__PURE__ */ jsx9(AnimatedPlayhead, { color: theme.playheadColor }))
6279
4515
  ] })
6280
4516
  }
6281
4517
  )
6282
4518
  }
6283
4519
  ),
6284
4520
  (spectrogram == null ? void 0 : spectrogram.SettingsModal) && typeof document !== "undefined" && createPortal(
6285
- /* @__PURE__ */ jsx10(
4521
+ /* @__PURE__ */ jsx9(
6286
4522
  spectrogram.SettingsModal,
6287
4523
  {
6288
4524
  open: settingsModalTrackId !== null,
@@ -6302,8 +4538,8 @@ var PlaylistVisualization = ({
6302
4538
  };
6303
4539
 
6304
4540
  // src/components/PlaylistAnnotationList.tsx
6305
- import { useCallback as useCallback23 } from "react";
6306
- import { jsx as jsx11 } from "react/jsx-runtime";
4541
+ import { useCallback as useCallback17 } from "react";
4542
+ import { jsx as jsx10 } from "react/jsx-runtime";
6307
4543
  var PlaylistAnnotationList = ({
6308
4544
  height,
6309
4545
  renderAnnotationItem,
@@ -6317,7 +4553,7 @@ var PlaylistAnnotationList = ({
6317
4553
  const integration = useAnnotationIntegration();
6318
4554
  const { setAnnotations } = usePlaylistControls();
6319
4555
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints, continuousPlay };
6320
- const handleAnnotationUpdate = useCallback23(
4556
+ const handleAnnotationUpdate = useCallback17(
6321
4557
  (updatedAnnotations) => {
6322
4558
  setAnnotations(updatedAnnotations);
6323
4559
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6325,7 +4561,7 @@ var PlaylistAnnotationList = ({
6325
4561
  [setAnnotations, onAnnotationUpdate]
6326
4562
  );
6327
4563
  const { AnnotationText } = integration;
6328
- return /* @__PURE__ */ jsx11(
4564
+ return /* @__PURE__ */ jsx10(
6329
4565
  AnnotationText,
6330
4566
  {
6331
4567
  annotations,
@@ -6344,7 +4580,7 @@ var PlaylistAnnotationList = ({
6344
4580
  };
6345
4581
 
6346
4582
  // src/components/Waveform.tsx
6347
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs3 } from "react/jsx-runtime";
4583
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
6348
4584
  var Waveform = ({
6349
4585
  renderTrackControls,
6350
4586
  renderTick,
@@ -6369,7 +4605,7 @@ var Waveform = ({
6369
4605
  const clipInteractionEnabled = useClipInteractionEnabled();
6370
4606
  const effectiveInteractiveClips = interactiveClips || clipInteractionEnabled;
6371
4607
  return /* @__PURE__ */ jsxs3(Fragment3, { children: [
6372
- /* @__PURE__ */ jsx12(
4608
+ /* @__PURE__ */ jsx11(
6373
4609
  PlaylistVisualization,
6374
4610
  {
6375
4611
  renderTrackControls,
@@ -6386,7 +4622,7 @@ var Waveform = ({
6386
4622
  recordingState
6387
4623
  }
6388
4624
  ),
6389
- annotations.length > 0 && /* @__PURE__ */ jsx12(
4625
+ annotations.length > 0 && /* @__PURE__ */ jsx11(
6390
4626
  PlaylistAnnotationList,
6391
4627
  {
6392
4628
  height: annotationTextHeight,
@@ -6401,7 +4637,7 @@ var Waveform = ({
6401
4637
  };
6402
4638
 
6403
4639
  // src/components/MediaElementPlaylist.tsx
6404
- import { useContext as useContext7, useRef as useRef23, useState as useState18, useCallback as useCallback24 } from "react";
4640
+ import { useContext as useContext7, useRef as useRef17, useState as useState12, useCallback as useCallback18 } from "react";
6405
4641
  import { DragDropProvider } from "@dnd-kit/react";
6406
4642
  import { RestrictToHorizontalAxis } from "@dnd-kit/abstract/modifiers";
6407
4643
  import {
@@ -6437,9 +4673,9 @@ var noDropAnimationPlugins = (defaults) => {
6437
4673
  };
6438
4674
 
6439
4675
  // src/components/AnimatedMediaElementPlayhead.tsx
6440
- import { useRef as useRef21, useEffect as useEffect15 } from "react";
4676
+ import { useRef as useRef15, useEffect as useEffect10 } from "react";
6441
4677
  import styled5 from "styled-components";
6442
- import { jsx as jsx13 } from "react/jsx-runtime";
4678
+ import { jsx as jsx12 } from "react/jsx-runtime";
6443
4679
  var PlayheadLine2 = styled5.div`
6444
4680
  position: absolute;
6445
4681
  top: 0;
@@ -6454,11 +4690,11 @@ var PlayheadLine2 = styled5.div`
6454
4690
  var AnimatedMediaElementPlayhead = ({
6455
4691
  color = "#ff0000"
6456
4692
  }) => {
6457
- const playheadRef = useRef21(null);
6458
- const animationFrameRef = useRef21(null);
4693
+ const playheadRef = useRef15(null);
4694
+ const animationFrameRef = useRef15(null);
6459
4695
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
6460
4696
  const { samplesPerPixel, sampleRate, progressBarWidth } = useMediaElementData();
6461
- useEffect15(() => {
4697
+ useEffect10(() => {
6462
4698
  const updatePosition = () => {
6463
4699
  var _a;
6464
4700
  if (playheadRef.current) {
@@ -6482,7 +4718,7 @@ var AnimatedMediaElementPlayhead = ({
6482
4718
  }
6483
4719
  };
6484
4720
  }, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef]);
6485
- useEffect15(() => {
4721
+ useEffect10(() => {
6486
4722
  var _a;
6487
4723
  if (!isPlaying && playheadRef.current) {
6488
4724
  const time = (_a = currentTimeRef.current) != null ? _a : 0;
@@ -6490,11 +4726,11 @@ var AnimatedMediaElementPlayhead = ({
6490
4726
  playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
6491
4727
  }
6492
4728
  });
6493
- return /* @__PURE__ */ jsx13(PlayheadLine2, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
4729
+ return /* @__PURE__ */ jsx12(PlayheadLine2, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
6494
4730
  };
6495
4731
 
6496
4732
  // src/components/ChannelWithMediaElementProgress.tsx
6497
- import { useRef as useRef22, useEffect as useEffect16 } from "react";
4733
+ import { useRef as useRef16, useEffect as useEffect11 } from "react";
6498
4734
  import styled6 from "styled-components";
6499
4735
  import {
6500
4736
  SmartChannel as SmartChannel2,
@@ -6502,7 +4738,7 @@ import {
6502
4738
  usePlaylistInfo as usePlaylistInfo2,
6503
4739
  waveformColorToCss as waveformColorToCss3
6504
4740
  } from "@waveform-playlist/ui-components";
6505
- import { jsx as jsx14, jsxs as jsxs4 } from "react/jsx-runtime";
4741
+ import { jsx as jsx13, jsxs as jsxs4 } from "react/jsx-runtime";
6506
4742
  var ChannelWrapper2 = styled6.div`
6507
4743
  position: relative;
6508
4744
  `;
@@ -6539,14 +4775,14 @@ var ChannelWithMediaElementProgress = (_a) => {
6539
4775
  "clipStartSample",
6540
4776
  "clipDurationSamples"
6541
4777
  ]);
6542
- const progressRef = useRef22(null);
6543
- const animationFrameRef = useRef22(null);
4778
+ const progressRef = useRef16(null);
4779
+ const animationFrameRef = useRef16(null);
6544
4780
  const theme = useTheme3();
6545
4781
  const { waveHeight } = usePlaylistInfo2();
6546
4782
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
6547
4783
  const { samplesPerPixel, sampleRate } = useMediaElementData();
6548
4784
  const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
6549
- useEffect16(() => {
4785
+ useEffect11(() => {
6550
4786
  const updateProgress = () => {
6551
4787
  var _a2;
6552
4788
  if (progressRef.current) {
@@ -6588,7 +4824,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6588
4824
  smartChannelProps.length,
6589
4825
  currentTimeRef
6590
4826
  ]);
6591
- useEffect16(() => {
4827
+ useEffect11(() => {
6592
4828
  var _a2;
6593
4829
  if (!isPlaying && progressRef.current) {
6594
4830
  const currentTime = (_a2 = currentTimeRef.current) != null ? _a2 : 0;
@@ -6615,7 +4851,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6615
4851
  }
6616
4852
  const backgroundCss = waveformColorToCss3(backgroundColor);
6617
4853
  return /* @__PURE__ */ jsxs4(ChannelWrapper2, { children: [
6618
- /* @__PURE__ */ jsx14(
4854
+ /* @__PURE__ */ jsx13(
6619
4855
  Background2,
6620
4856
  {
6621
4857
  $color: backgroundCss,
@@ -6624,7 +4860,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6624
4860
  $width: smartChannelProps.length
6625
4861
  }
6626
4862
  ),
6627
- /* @__PURE__ */ jsx14(
4863
+ /* @__PURE__ */ jsx13(
6628
4864
  ProgressOverlay2,
6629
4865
  {
6630
4866
  ref: progressRef,
@@ -6633,12 +4869,12 @@ var ChannelWithMediaElementProgress = (_a) => {
6633
4869
  $top: smartChannelProps.index * waveHeight
6634
4870
  }
6635
4871
  ),
6636
- /* @__PURE__ */ jsx14(ChannelContainer2, { children: /* @__PURE__ */ jsx14(SmartChannel2, __spreadProps(__spreadValues({}, smartChannelProps), { isSelected: true, transparentBackground: true })) })
4872
+ /* @__PURE__ */ jsx13(ChannelContainer2, { children: /* @__PURE__ */ jsx13(SmartChannel2, __spreadProps(__spreadValues({}, smartChannelProps), { isSelected: true, transparentBackground: true })) })
6637
4873
  ] });
6638
4874
  };
6639
4875
 
6640
4876
  // src/components/MediaElementPlaylist.tsx
6641
- import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs5 } from "react/jsx-runtime";
4877
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
6642
4878
  var ZERO_REF = { current: 0 };
6643
4879
  var CustomMediaElementPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {
6644
4880
  var _a;
@@ -6683,11 +4919,11 @@ var MediaElementPlaylist = ({
6683
4919
  fadeIn,
6684
4920
  fadeOut
6685
4921
  } = useMediaElementData();
6686
- const [selectionStart, setSelectionStart] = useState18(0);
6687
- const [selectionEnd, setSelectionEnd] = useState18(0);
6688
- const [isSelecting, setIsSelecting] = useState18(false);
6689
- const scrollContainerRef = useRef23(null);
6690
- const handleScrollContainerRef = useCallback24(
4922
+ const [selectionStart, setSelectionStart] = useState12(0);
4923
+ const [selectionEnd, setSelectionEnd] = useState12(0);
4924
+ const [isSelecting, setIsSelecting] = useState12(false);
4925
+ const scrollContainerRef = useRef17(null);
4926
+ const handleScrollContainerRef = useCallback18(
6691
4927
  (el) => {
6692
4928
  scrollContainerRef.current = el;
6693
4929
  setScrollContainer(el);
@@ -6695,7 +4931,7 @@ var MediaElementPlaylist = ({
6695
4931
  [setScrollContainer]
6696
4932
  );
6697
4933
  const tracksFullWidth = Math.floor(duration * sampleRate / samplesPerPixel);
6698
- const handleAnnotationClick = useCallback24(
4934
+ const handleAnnotationClick = useCallback18(
6699
4935
  (annotation) => __async(null, null, function* () {
6700
4936
  setActiveAnnotationId(annotation.id);
6701
4937
  try {
@@ -6710,7 +4946,7 @@ var MediaElementPlaylist = ({
6710
4946
  }),
6711
4947
  [setActiveAnnotationId, play]
6712
4948
  );
6713
- const handleAnnotationUpdate = useCallback24(
4949
+ const handleAnnotationUpdate = useCallback18(
6714
4950
  (updatedAnnotations) => {
6715
4951
  setAnnotations(updatedAnnotations);
6716
4952
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6724,8 +4960,8 @@ var MediaElementPlaylist = ({
6724
4960
  duration,
6725
4961
  linkEndpoints: linkEndpointsProp
6726
4962
  });
6727
- const mouseDownTimeRef = useRef23(0);
6728
- const handleMouseDown = useCallback24(
4963
+ const mouseDownTimeRef = useRef17(0);
4964
+ const handleMouseDown = useCallback18(
6729
4965
  (e) => {
6730
4966
  const rect = e.currentTarget.getBoundingClientRect();
6731
4967
  const x = e.clientX - rect.left;
@@ -6737,7 +4973,7 @@ var MediaElementPlaylist = ({
6737
4973
  },
6738
4974
  [samplesPerPixel, sampleRate]
6739
4975
  );
6740
- const handleMouseMove = useCallback24(
4976
+ const handleMouseMove = useCallback18(
6741
4977
  (e) => {
6742
4978
  if (!isSelecting) return;
6743
4979
  const rect = e.currentTarget.getBoundingClientRect();
@@ -6747,7 +4983,7 @@ var MediaElementPlaylist = ({
6747
4983
  },
6748
4984
  [isSelecting, samplesPerPixel, sampleRate]
6749
4985
  );
6750
- const handleMouseUp = useCallback24(
4986
+ const handleMouseUp = useCallback18(
6751
4987
  (e) => {
6752
4988
  if (!isSelecting) return;
6753
4989
  setIsSelecting(false);
@@ -6777,9 +5013,9 @@ var MediaElementPlaylist = ({
6777
5013
  [isSelecting, selectionStart, samplesPerPixel, sampleRate, seekTo, isPlaying, playoutRef, play]
6778
5014
  );
6779
5015
  if (peaksDataArray.length === 0) {
6780
- return /* @__PURE__ */ jsx15("div", { className, children: "Loading waveform..." });
5016
+ return /* @__PURE__ */ jsx14("div", { className, children: "Loading waveform..." });
6781
5017
  }
6782
- return /* @__PURE__ */ jsx15(DevicePixelRatioProvider2, { children: /* @__PURE__ */ jsx15(
5018
+ return /* @__PURE__ */ jsx14(DevicePixelRatioProvider2, { children: /* @__PURE__ */ jsx14(
6783
5019
  PlaylistInfoContext2.Provider,
6784
5020
  {
6785
5021
  value: {
@@ -6793,7 +5029,7 @@ var MediaElementPlaylist = ({
6793
5029
  barWidth,
6794
5030
  barGap
6795
5031
  },
6796
- children: /* @__PURE__ */ jsx15(
5032
+ children: /* @__PURE__ */ jsx14(
6797
5033
  Playlist2,
6798
5034
  {
6799
5035
  theme,
@@ -6807,11 +5043,11 @@ var MediaElementPlaylist = ({
6807
5043
  onTracksMouseUp: handleMouseUp,
6808
5044
  scrollContainerRef: handleScrollContainerRef,
6809
5045
  isSelecting,
6810
- timescale: timeScaleHeight > 0 ? /* @__PURE__ */ jsx15(SmartScale2, {}) : void 0,
5046
+ timescale: timeScaleHeight > 0 ? /* @__PURE__ */ jsx14(SmartScale2, {}) : void 0,
6811
5047
  children: /* @__PURE__ */ jsxs5(Fragment4, { children: [
6812
5048
  peaksDataArray.map((trackClipPeaks, trackIndex) => {
6813
5049
  const maxChannels = trackClipPeaks.length > 0 ? Math.max(...trackClipPeaks.map((clip) => clip.peaks.data.length)) : 1;
6814
- return /* @__PURE__ */ jsx15(
5050
+ return /* @__PURE__ */ jsx14(
6815
5051
  TrackComponent2,
6816
5052
  {
6817
5053
  numChannels: maxChannels,
@@ -6839,7 +5075,7 @@ var MediaElementPlaylist = ({
6839
5075
  isSelected: true,
6840
5076
  trackId: `media-element-track-${trackIndex}`,
6841
5077
  children: [
6842
- peaksData.data.map((channelPeaks, channelIndex) => /* @__PURE__ */ jsx15(
5078
+ peaksData.data.map((channelPeaks, channelIndex) => /* @__PURE__ */ jsx14(
6843
5079
  ChannelWithMediaElementProgress,
6844
5080
  {
6845
5081
  index: channelIndex,
@@ -6851,7 +5087,7 @@ var MediaElementPlaylist = ({
6851
5087
  },
6852
5088
  `${trackIndex}-${clipIndex}-${channelIndex}`
6853
5089
  )),
6854
- showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ jsx15(
5090
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ jsx14(
6855
5091
  FadeOverlay,
6856
5092
  {
6857
5093
  left: 0,
@@ -6860,7 +5096,7 @@ var MediaElementPlaylist = ({
6860
5096
  curveType: fadeIn.type
6861
5097
  }
6862
5098
  ),
6863
- showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ jsx15(
5099
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ jsx14(
6864
5100
  FadeOverlay,
6865
5101
  {
6866
5102
  left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
@@ -6878,7 +5114,7 @@ var MediaElementPlaylist = ({
6878
5114
  trackIndex
6879
5115
  );
6880
5116
  }),
6881
- annotations.length > 0 && annotationIntegration && /* @__PURE__ */ jsx15(
5117
+ annotations.length > 0 && annotationIntegration && /* @__PURE__ */ jsx14(
6882
5118
  DragDropProvider,
6883
5119
  {
6884
5120
  onDragStart,
@@ -6886,11 +5122,11 @@ var MediaElementPlaylist = ({
6886
5122
  onDragEnd,
6887
5123
  modifiers: editable ? [RestrictToHorizontalAxis] : [],
6888
5124
  plugins: noDropAnimationPlugins,
6889
- children: /* @__PURE__ */ jsx15(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
5125
+ children: /* @__PURE__ */ jsx14(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
6890
5126
  const startPosition = annotation.start * sampleRate / samplesPerPixel;
6891
5127
  const endPosition = annotation.end * sampleRate / samplesPerPixel;
6892
5128
  const label = getAnnotationBoxLabel ? getAnnotationBoxLabel(annotation, index) : annotation.id;
6893
- return /* @__PURE__ */ jsx15(
5129
+ return /* @__PURE__ */ jsx14(
6894
5130
  annotationIntegration.AnnotationBox,
6895
5131
  {
6896
5132
  annotationId: annotation.id,
@@ -6908,7 +5144,7 @@ var MediaElementPlaylist = ({
6908
5144
  }) })
6909
5145
  }
6910
5146
  ),
6911
- selectionStart !== selectionEnd && /* @__PURE__ */ jsx15(
5147
+ selectionStart !== selectionEnd && /* @__PURE__ */ jsx14(
6912
5148
  Selection2,
6913
5149
  {
6914
5150
  startPosition: Math.min(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
@@ -6916,7 +5152,7 @@ var MediaElementPlaylist = ({
6916
5152
  color: theme.selectionColor
6917
5153
  }
6918
5154
  ),
6919
- renderPlayhead ? /* @__PURE__ */ jsx15(
5155
+ renderPlayhead ? /* @__PURE__ */ jsx14(
6920
5156
  CustomMediaElementPlayhead,
6921
5157
  {
6922
5158
  renderPlayhead,
@@ -6924,7 +5160,7 @@ var MediaElementPlaylist = ({
6924
5160
  samplesPerPixel,
6925
5161
  sampleRate
6926
5162
  }
6927
- ) : /* @__PURE__ */ jsx15(AnimatedMediaElementPlayhead, { color: theme.playheadColor })
5163
+ ) : /* @__PURE__ */ jsx14(AnimatedMediaElementPlayhead, { color: theme.playheadColor })
6928
5164
  ] })
6929
5165
  }
6930
5166
  )
@@ -6933,8 +5169,8 @@ var MediaElementPlaylist = ({
6933
5169
  };
6934
5170
 
6935
5171
  // src/components/MediaElementAnnotationList.tsx
6936
- import { useCallback as useCallback25 } from "react";
6937
- import { jsx as jsx16 } from "react/jsx-runtime";
5172
+ import { useCallback as useCallback19 } from "react";
5173
+ import { jsx as jsx15 } from "react/jsx-runtime";
6938
5174
  var MediaElementAnnotationList = ({
6939
5175
  height,
6940
5176
  renderAnnotationItem,
@@ -6949,7 +5185,7 @@ var MediaElementAnnotationList = ({
6949
5185
  const integration = useAnnotationIntegration();
6950
5186
  const { setAnnotations } = useMediaElementControls();
6951
5187
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints: false, continuousPlay };
6952
- const handleAnnotationUpdate = useCallback25(
5188
+ const handleAnnotationUpdate = useCallback19(
6953
5189
  (updatedAnnotations) => {
6954
5190
  setAnnotations(updatedAnnotations);
6955
5191
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6957,7 +5193,7 @@ var MediaElementAnnotationList = ({
6957
5193
  [setAnnotations, onAnnotationUpdate]
6958
5194
  );
6959
5195
  const { AnnotationText } = integration;
6960
- return /* @__PURE__ */ jsx16(
5196
+ return /* @__PURE__ */ jsx15(
6961
5197
  AnnotationText,
6962
5198
  {
6963
5199
  annotations,
@@ -6976,7 +5212,7 @@ var MediaElementAnnotationList = ({
6976
5212
  };
6977
5213
 
6978
5214
  // src/components/MediaElementWaveform.tsx
6979
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs6 } from "react/jsx-runtime";
5215
+ import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
6980
5216
  var MediaElementWaveform = ({
6981
5217
  annotationTextHeight,
6982
5218
  getAnnotationBoxLabel,
@@ -6992,7 +5228,7 @@ var MediaElementWaveform = ({
6992
5228
  }) => {
6993
5229
  const { annotations } = useMediaElementState();
6994
5230
  return /* @__PURE__ */ jsxs6(Fragment5, { children: [
6995
- /* @__PURE__ */ jsx17(
5231
+ /* @__PURE__ */ jsx16(
6996
5232
  MediaElementPlaylist,
6997
5233
  {
6998
5234
  getAnnotationBoxLabel,
@@ -7004,7 +5240,7 @@ var MediaElementWaveform = ({
7004
5240
  className
7005
5241
  }
7006
5242
  ),
7007
- annotations.length > 0 && /* @__PURE__ */ jsx17(
5243
+ annotations.length > 0 && /* @__PURE__ */ jsx16(
7008
5244
  MediaElementAnnotationList,
7009
5245
  {
7010
5246
  height: annotationTextHeight,
@@ -7079,7 +5315,7 @@ var KeyboardShortcuts = ({
7079
5315
  };
7080
5316
 
7081
5317
  // src/components/ClipInteractionProvider.tsx
7082
- import React16, { useEffect as useEffect17, useMemo as useMemo7 } from "react";
5318
+ import React16, { useEffect as useEffect12, useMemo as useMemo6 } from "react";
7083
5319
  import { DragDropProvider as DragDropProvider2 } from "@dnd-kit/react";
7084
5320
  import { RestrictToHorizontalAxis as RestrictToHorizontalAxis2 } from "@dnd-kit/abstract/modifiers";
7085
5321
  import {
@@ -7169,7 +5405,7 @@ _SnapToGridModifier.configure = configurator2(_SnapToGridModifier);
7169
5405
  var SnapToGridModifier = _SnapToGridModifier;
7170
5406
 
7171
5407
  // src/components/ClipInteractionProvider.tsx
7172
- import { jsx as jsx18 } from "react/jsx-runtime";
5408
+ import { jsx as jsx17 } from "react/jsx-runtime";
7173
5409
  var NOOP_TRACKS_CHANGE = () => {
7174
5410
  };
7175
5411
  var ClipInteractionProvider = ({
@@ -7182,14 +5418,14 @@ var ClipInteractionProvider = ({
7182
5418
  const beatsAndBars = useBeatsAndBars();
7183
5419
  const useBeatsSnap = snap && beatsAndBars != null && beatsAndBars.scaleMode === "beats" && beatsAndBars.snapTo !== "off";
7184
5420
  const useTimescaleSnap = snap && !useBeatsSnap;
7185
- useEffect17(() => {
5421
+ useEffect12(() => {
7186
5422
  if (onTracksChange == null) {
7187
5423
  console.warn(
7188
5424
  "[waveform-playlist] ClipInteractionProvider: onTracksChange is not set on WaveformPlaylistProvider. Drag and trim edits will not be persisted."
7189
5425
  );
7190
5426
  }
7191
5427
  }, [onTracksChange]);
7192
- const snapSamplePosition = useMemo7(() => {
5428
+ const snapSamplePosition = useMemo6(() => {
7193
5429
  if (useBeatsSnap && beatsAndBars) {
7194
5430
  const { bpm, timeSignature, snapTo } = beatsAndBars;
7195
5431
  const gridTicks = snapTo === "bar" ? ticksPerBar2(timeSignature) : ticksPerBeat2(timeSignature);
@@ -7229,7 +5465,7 @@ var ClipInteractionProvider = ({
7229
5465
  },
7230
5466
  [handleDragStart, tracks, setSelectedTrackId]
7231
5467
  );
7232
- const modifiers = useMemo7(() => {
5468
+ const modifiers = useMemo6(() => {
7233
5469
  const mods = [RestrictToHorizontalAxis2];
7234
5470
  if (useBeatsSnap && beatsAndBars) {
7235
5471
  mods.push(
@@ -7254,7 +5490,7 @@ var ClipInteractionProvider = ({
7254
5490
  mods.push(ClipCollisionModifier.configure({ tracks, samplesPerPixel }));
7255
5491
  return mods;
7256
5492
  }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, tracks, samplesPerPixel, sampleRate]);
7257
- return /* @__PURE__ */ jsx18(ClipInteractionContextProvider, { value: true, children: /* @__PURE__ */ jsx18(
5493
+ return /* @__PURE__ */ jsx17(ClipInteractionContextProvider, { value: true, children: /* @__PURE__ */ jsx17(
7258
5494
  DragDropProvider2,
7259
5495
  {
7260
5496
  sensors,
@@ -7277,7 +5513,6 @@ export {
7277
5513
  ContinuousPlayCheckbox,
7278
5514
  DownloadAnnotationsButton,
7279
5515
  EditableCheckbox,
7280
- ExportWavButton,
7281
5516
  FastForwardButton,
7282
5517
  KeyboardShortcuts,
7283
5518
  LinkEndpointsCheckbox,
@@ -7300,17 +5535,10 @@ export {
7300
5535
  SpectrogramIntegrationProvider,
7301
5536
  StopButton,
7302
5537
  TimeFormatSelect,
7303
- Tone2 as Tone,
7304
5538
  Waveform,
7305
5539
  WaveformPlaylistProvider,
7306
5540
  ZoomInButton,
7307
5541
  ZoomOutButton,
7308
- createEffectChain,
7309
- createEffectInstance,
7310
- effectCategories,
7311
- effectDefinitions,
7312
- getEffectDefinition,
7313
- getEffectsByCategory,
7314
5542
  getShortcutLabel,
7315
5543
  getWaveformDataMetadata,
7316
5544
  loadPeaksFromWaveformData,
@@ -7319,30 +5547,24 @@ export {
7319
5547
  useAnnotationDragHandlers,
7320
5548
  useAnnotationIntegration,
7321
5549
  useAnnotationKeyboardControls,
7322
- useAudioTracks,
7323
5550
  useClipDragHandlers,
7324
5551
  useClipInteractionEnabled,
7325
5552
  useClipSplitting,
7326
5553
  useDragSensors,
7327
- useDynamicEffects,
7328
- useDynamicTracks,
7329
- useExportWav,
7330
5554
  useKeyboardShortcuts,
7331
- useMasterAnalyser,
7332
5555
  useMasterVolume,
7333
5556
  useMediaElementAnimation,
7334
5557
  useMediaElementControls,
7335
5558
  useMediaElementData,
7336
5559
  useMediaElementState,
7337
- useOutputMeter,
7338
5560
  usePlaybackAnimation,
7339
5561
  usePlaybackShortcuts,
7340
5562
  usePlaylistControls,
7341
5563
  usePlaylistData,
5564
+ usePlaylistDataOptional,
7342
5565
  usePlaylistState,
7343
5566
  useSpectrogramIntegration,
7344
5567
  useTimeFormat,
7345
- useTrackDynamicEffects,
7346
5568
  useZoomControls,
7347
5569
  waveformDataToPeaks
7348
5570
  };