@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.js CHANGED
@@ -88,7 +88,6 @@ __export(index_exports, {
88
88
  ContinuousPlayCheckbox: () => ContinuousPlayCheckbox,
89
89
  DownloadAnnotationsButton: () => DownloadAnnotationsButton,
90
90
  EditableCheckbox: () => EditableCheckbox,
91
- ExportWavButton: () => ExportWavButton,
92
91
  FastForwardButton: () => FastForwardButton,
93
92
  KeyboardShortcuts: () => KeyboardShortcuts,
94
93
  LinkEndpointsCheckbox: () => LinkEndpointsCheckbox,
@@ -111,18 +110,11 @@ __export(index_exports, {
111
110
  SpectrogramIntegrationProvider: () => SpectrogramIntegrationProvider,
112
111
  StopButton: () => StopButton,
113
112
  TimeFormatSelect: () => TimeFormatSelect,
114
- Tone: () => Tone2,
115
113
  Waveform: () => Waveform,
116
114
  WaveformPlaylistProvider: () => WaveformPlaylistProvider,
117
115
  ZoomInButton: () => ZoomInButton,
118
116
  ZoomOutButton: () => ZoomOutButton,
119
- createEffectChain: () => createEffectChain,
120
- createEffectInstance: () => createEffectInstance,
121
- effectCategories: () => effectCategories,
122
- effectDefinitions: () => effectDefinitions,
123
- getEffectDefinition: () => getEffectDefinition,
124
- getEffectsByCategory: () => getEffectsByCategory,
125
- getShortcutLabel: () => import_core3.getShortcutLabel,
117
+ getShortcutLabel: () => import_core2.getShortcutLabel,
126
118
  getWaveformDataMetadata: () => getWaveformDataMetadata,
127
119
  loadPeaksFromWaveformData: () => loadPeaksFromWaveformData,
128
120
  loadWaveformData: () => loadWaveformData,
@@ -130,43 +122,65 @@ __export(index_exports, {
130
122
  useAnnotationDragHandlers: () => useAnnotationDragHandlers,
131
123
  useAnnotationIntegration: () => useAnnotationIntegration,
132
124
  useAnnotationKeyboardControls: () => useAnnotationKeyboardControls,
133
- useAudioTracks: () => useAudioTracks,
134
125
  useClipDragHandlers: () => useClipDragHandlers,
135
126
  useClipInteractionEnabled: () => useClipInteractionEnabled,
136
127
  useClipSplitting: () => useClipSplitting,
137
128
  useDragSensors: () => useDragSensors,
138
- useDynamicEffects: () => useDynamicEffects,
139
- useDynamicTracks: () => useDynamicTracks,
140
- useExportWav: () => useExportWav,
141
129
  useKeyboardShortcuts: () => useKeyboardShortcuts,
142
- useMasterAnalyser: () => useMasterAnalyser,
143
130
  useMasterVolume: () => useMasterVolume,
144
131
  useMediaElementAnimation: () => useMediaElementAnimation,
145
132
  useMediaElementControls: () => useMediaElementControls,
146
133
  useMediaElementData: () => useMediaElementData,
147
134
  useMediaElementState: () => useMediaElementState,
148
- useOutputMeter: () => useOutputMeter,
149
135
  usePlaybackAnimation: () => usePlaybackAnimation,
150
136
  usePlaybackShortcuts: () => usePlaybackShortcuts,
151
137
  usePlaylistControls: () => usePlaylistControls,
152
138
  usePlaylistData: () => usePlaylistData,
139
+ usePlaylistDataOptional: () => usePlaylistDataOptional,
153
140
  usePlaylistState: () => usePlaylistState,
154
141
  useSpectrogramIntegration: () => useSpectrogramIntegration,
155
142
  useTimeFormat: () => useTimeFormat,
156
- useTrackDynamicEffects: () => useTrackDynamicEffects,
157
143
  useZoomControls: () => useZoomControls,
158
144
  waveformDataToPeaks: () => waveformDataToPeaks
159
145
  });
160
146
  module.exports = __toCommonJS(index_exports);
161
- var Tone2 = __toESM(require("tone"));
162
147
 
163
148
  // src/WaveformPlaylistContext.tsx
164
- var import_react24 = require("react");
149
+ var import_react17 = require("react");
165
150
  var import_styled_components = require("styled-components");
166
- var import_playout6 = require("@waveform-playlist/playout");
151
+
152
+ // src/playout/resolvePlayoutAdapter.ts
153
+ 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`.";
154
+ function resolvePlayoutAdapter(opts) {
155
+ return __async(this, null, function* () {
156
+ if (opts.createAdapter) {
157
+ return opts.createAdapter();
158
+ }
159
+ let mod;
160
+ try {
161
+ mod = yield import("@waveform-playlist/playout");
162
+ } catch (originalErr) {
163
+ console.warn(
164
+ "[waveform-playlist] @waveform-playlist/playout dynamic import failed: " + String(originalErr)
165
+ );
166
+ throw new Error(INSTALL_HINT);
167
+ }
168
+ if (opts.sampleRate !== void 0) {
169
+ try {
170
+ mod.configureGlobalContext({ sampleRate: opts.sampleRate });
171
+ } catch (ctxErr) {
172
+ console.warn(
173
+ "[waveform-playlist] configureGlobalContext failed (continuing with default rate): " + String(ctxErr)
174
+ );
175
+ }
176
+ }
177
+ return mod.createToneAdapter({ effects: opts.effects, soundFontCache: opts.soundFontCache });
178
+ });
179
+ }
180
+
181
+ // src/WaveformPlaylistContext.tsx
167
182
  var import_engine3 = require("@waveform-playlist/engine");
168
183
  var import_ui_components2 = require("@waveform-playlist/ui-components");
169
- var import_tone4 = require("tone");
170
184
 
171
185
  // src/waveformDataLoader.ts
172
186
  var import_waveform_data = __toESM(require("waveform-data"));
@@ -332,9 +346,11 @@ function extractPeaksFromWaveformDataFull(waveformData, samplesPerPixel, isMono,
332
346
  }
333
347
 
334
348
  // src/soundFontSync.ts
335
- var import_playout = require("@waveform-playlist/playout");
349
+ function supportsSoundFont(adapter) {
350
+ return adapter != null && typeof adapter.setSoundFontCache === "function";
351
+ }
336
352
  function syncSoundFontCacheToAdapter(adapter, cache) {
337
- if (!(0, import_playout.isToneAdapter)(adapter)) return;
353
+ if (!supportsSoundFont(adapter)) return;
338
354
  adapter.setSoundFontCache(cache);
339
355
  }
340
356
 
@@ -591,223 +607,8 @@ function useUndoState({ engineRef }) {
591
607
  };
592
608
  }
593
609
 
594
- // src/hooks/useAudioEffects.ts
595
- var import_react8 = require("react");
596
- var import_tone = require("tone");
597
- var useMasterAnalyser = (fftSize = 256) => {
598
- const analyserRef = (0, import_react8.useRef)(null);
599
- const masterEffects = (0, import_react8.useCallback)(
600
- (masterGainNode, destination, _isOffline) => {
601
- const analyserNode = new import_tone.Analyser("fft", fftSize);
602
- masterGainNode.connect(analyserNode);
603
- masterGainNode.connect(destination);
604
- analyserRef.current = analyserNode;
605
- return function cleanup() {
606
- analyserNode.dispose();
607
- analyserRef.current = null;
608
- };
609
- },
610
- [fftSize]
611
- );
612
- return { analyserRef, masterEffects };
613
- };
614
-
615
- // src/hooks/useAudioTracks.ts
616
- var import_react9 = require("react");
617
- var import_core = require("@waveform-playlist/core");
618
- var Tone = __toESM(require("tone"));
619
- function buildTrackFromConfig(config, index, audioBuffer, stableIds, contextSampleRate = 48e3) {
620
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
621
- const buffer = audioBuffer != null ? audioBuffer : config.audioBuffer;
622
- const sampleRate = (_c = (_b = buffer == null ? void 0 : buffer.sampleRate) != null ? _b : (_a = config.waveformData) == null ? void 0 : _a.sample_rate) != null ? _c : contextSampleRate;
623
- 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;
624
- if (sourceDuration === void 0) {
625
- console.warn(
626
- `[waveform-playlist] Track ${index + 1} ("${(_h = config.name) != null ? _h : "unnamed"}"): Cannot create track \u2014 provide duration, audioBuffer, or waveformData with duration.`
627
- );
628
- return null;
629
- }
630
- const clip = (0, import_core.createClipFromSeconds)({
631
- audioBuffer: buffer,
632
- sampleRate,
633
- sourceDuration,
634
- startTime: (_i = config.startTime) != null ? _i : 0,
635
- duration: (_j = config.duration) != null ? _j : sourceDuration,
636
- offset: (_k = config.offset) != null ? _k : 0,
637
- name: config.name || `Track ${index + 1}`,
638
- fadeIn: config.fadeIn,
639
- fadeOut: config.fadeOut,
640
- waveformData: config.waveformData
641
- });
642
- if (isNaN(clip.startSample) || isNaN(clip.durationSamples) || isNaN(clip.offsetSamples)) {
643
- console.error(
644
- `[waveform-playlist] Invalid clip values for track ${index + 1} ("${(_l = config.name) != null ? _l : "unnamed"}"): startSample=${clip.startSample}, durationSamples=${clip.durationSamples}, offsetSamples=${clip.offsetSamples}`
645
- );
646
- return null;
647
- }
648
- const track = __spreadProps(__spreadValues({}, (0, import_core.createTrack)({
649
- name: config.name || `Track ${index + 1}`,
650
- clips: [clip],
651
- muted: (_m = config.muted) != null ? _m : false,
652
- soloed: (_n = config.soloed) != null ? _n : false,
653
- volume: (_o = config.volume) != null ? _o : 1,
654
- pan: (_p = config.pan) != null ? _p : 0,
655
- color: config.color
656
- })), {
657
- effects: config.effects,
658
- renderMode: config.renderMode,
659
- spectrogramConfig: config.spectrogramConfig,
660
- spectrogramColorMap: config.spectrogramColorMap
661
- });
662
- const existingIds = stableIds.get(index);
663
- if (existingIds) {
664
- track.id = existingIds.trackId;
665
- track.clips[0] = __spreadProps(__spreadValues({}, track.clips[0]), { id: existingIds.clipId });
666
- } else {
667
- stableIds.set(index, { trackId: track.id, clipId: track.clips[0].id });
668
- }
669
- return track;
670
- }
671
- function useAudioTracks(configs, options = {}) {
672
- const { immediate = false, progressive = false } = options;
673
- const isImmediate = immediate || progressive;
674
- const [loading, setLoading] = (0, import_react9.useState)(true);
675
- const [error, setError] = (0, import_react9.useState)(null);
676
- const [loadedCount, setLoadedCount] = (0, import_react9.useState)(0);
677
- const totalCount = configs.length;
678
- const [loadedBuffers, setLoadedBuffers] = (0, import_react9.useState)(/* @__PURE__ */ new Map());
679
- const stableIdsRef = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
680
- const contextSampleRateRef = (0, import_react9.useRef)(48e3);
681
- const derivedTracks = (0, import_react9.useMemo)(() => {
682
- if (!isImmediate) return null;
683
- const result = [];
684
- for (let i = 0; i < configs.length; i++) {
685
- const track = buildTrackFromConfig(
686
- configs[i],
687
- i,
688
- loadedBuffers.get(i),
689
- stableIdsRef.current,
690
- contextSampleRateRef.current
691
- );
692
- if (track) result.push(track);
693
- }
694
- return result;
695
- }, [isImmediate, configs, loadedBuffers]);
696
- const [tracks, setTracks] = (0, import_react9.useState)(derivedTracks != null ? derivedTracks : []);
697
- const prevDerivedRef = (0, import_react9.useRef)(derivedTracks);
698
- if (derivedTracks !== prevDerivedRef.current) {
699
- prevDerivedRef.current = derivedTracks;
700
- if (derivedTracks) setTracks(derivedTracks);
701
- }
702
- (0, import_react9.useEffect)(() => {
703
- if (configs.length === 0) {
704
- setTracks([]);
705
- setLoading(false);
706
- setLoadedCount(0);
707
- return;
708
- }
709
- let cancelled = false;
710
- const abortController = new AbortController();
711
- const loadTracks = () => __async(null, null, function* () {
712
- try {
713
- setLoading(true);
714
- setError(null);
715
- setLoadedCount(0);
716
- if (isImmediate) {
717
- setLoadedBuffers(/* @__PURE__ */ new Map());
718
- }
719
- const audioContext = Tone.getContext().rawContext;
720
- contextSampleRateRef.current = audioContext.sampleRate;
721
- const loadPromises = configs.map((config, index) => __async(null, null, function* () {
722
- if (config.audioBuffer) {
723
- if (isImmediate && !cancelled) {
724
- setLoadedBuffers((prev) => {
725
- const next = new Map(prev);
726
- next.set(index, config.audioBuffer);
727
- return next;
728
- });
729
- setLoadedCount((prev) => prev + 1);
730
- return;
731
- }
732
- return buildTrackFromConfig(
733
- config,
734
- index,
735
- config.audioBuffer,
736
- stableIdsRef.current,
737
- audioContext.sampleRate
738
- );
739
- }
740
- if (!config.src && config.waveformData) {
741
- if (isImmediate && !cancelled) {
742
- setLoadedCount((prev) => prev + 1);
743
- return;
744
- }
745
- return buildTrackFromConfig(
746
- config,
747
- index,
748
- void 0,
749
- stableIdsRef.current,
750
- audioContext.sampleRate
751
- );
752
- }
753
- if (!config.src) {
754
- throw new Error(`Track ${index + 1}: Must provide src, audioBuffer, or waveformData`);
755
- }
756
- const response = yield fetch(config.src, { signal: abortController.signal });
757
- if (!response.ok) {
758
- throw new Error(`Failed to fetch ${config.src}: ${response.statusText}`);
759
- }
760
- const arrayBuffer = yield response.arrayBuffer();
761
- const audioBuffer = yield audioContext.decodeAudioData(arrayBuffer);
762
- if (!audioBuffer || !audioBuffer.sampleRate || !audioBuffer.duration) {
763
- throw new Error(`Invalid audio buffer for ${config.src}`);
764
- }
765
- if (isImmediate && !cancelled) {
766
- setLoadedBuffers((prev) => {
767
- const next = new Map(prev);
768
- next.set(index, audioBuffer);
769
- return next;
770
- });
771
- setLoadedCount((prev) => prev + 1);
772
- return;
773
- }
774
- return buildTrackFromConfig(
775
- config,
776
- index,
777
- audioBuffer,
778
- stableIdsRef.current,
779
- audioContext.sampleRate
780
- );
781
- }));
782
- const loadedTracks = yield Promise.all(loadPromises);
783
- if (!cancelled) {
784
- if (!isImmediate) {
785
- const validTracks = loadedTracks.filter((t) => t != null);
786
- setTracks(validTracks);
787
- setLoadedCount(validTracks.length);
788
- }
789
- setLoading(false);
790
- }
791
- } catch (err) {
792
- if (!cancelled) {
793
- const errorMessage = err instanceof Error ? err.message : "Unknown error loading audio";
794
- setError(errorMessage);
795
- setLoading(false);
796
- console.error(`[waveform-playlist] Error loading audio tracks: ${errorMessage}`);
797
- }
798
- }
799
- });
800
- loadTracks();
801
- return () => {
802
- cancelled = true;
803
- abortController.abort();
804
- };
805
- }, [configs, isImmediate]);
806
- return { tracks, loading, error, loadedCount, totalCount };
807
- }
808
-
809
610
  // src/hooks/useClipDragHandlers.ts
810
- var import_react10 = __toESM(require("react"));
611
+ var import_react8 = __toESM(require("react"));
811
612
 
812
613
  // src/utils/boundaryTrim.ts
813
614
  var import_engine = require("@waveform-playlist/engine");
@@ -861,11 +662,11 @@ function useClipDragHandlers({
861
662
  snapSamplePosition
862
663
  }) {
863
664
  const { sampleRate } = usePlaylistData();
864
- const snapSamplePositionRef = import_react10.default.useRef(snapSamplePosition);
665
+ const snapSamplePositionRef = import_react8.default.useRef(snapSamplePosition);
865
666
  snapSamplePositionRef.current = snapSamplePosition;
866
- const originalClipStateRef = import_react10.default.useRef(null);
867
- const lastBoundaryDeltaRef = import_react10.default.useRef(0);
868
- const onDragStart = import_react10.default.useCallback(
667
+ const originalClipStateRef = import_react8.default.useRef(null);
668
+ const lastBoundaryDeltaRef = import_react8.default.useRef(0);
669
+ const onDragStart = import_react8.default.useCallback(
869
670
  (event) => {
870
671
  var _a;
871
672
  const data = (_a = event.operation.source) == null ? void 0 : _a.data;
@@ -901,7 +702,7 @@ function useClipDragHandlers({
901
702
  },
902
703
  [tracks, isDraggingRef, engineRef]
903
704
  );
904
- const onDragMove = import_react10.default.useCallback(
705
+ const onDragMove = import_react8.default.useCallback(
905
706
  (event) => {
906
707
  var _a, _b, _c;
907
708
  const data = (_a = event.operation.source) == null ? void 0 : _a.data;
@@ -963,7 +764,7 @@ function useClipDragHandlers({
963
764
  },
964
765
  [tracks, onTracksChange, samplesPerPixel, sampleRate]
965
766
  );
966
- const onDragEnd = import_react10.default.useCallback(
767
+ const onDragEnd = import_react8.default.useCallback(
967
768
  (event) => {
968
769
  var _a, _b, _c, _d, _e, _f, _g;
969
770
  if (event.canceled) {
@@ -1041,19 +842,19 @@ function useClipDragHandlers({
1041
842
  }
1042
843
 
1043
844
  // src/hooks/useAnnotationDragHandlers.ts
1044
- var import_react11 = __toESM(require("react"));
1045
- var import_playout2 = require("@waveform-playlist/playout");
845
+ var import_react9 = __toESM(require("react"));
1046
846
  var LINK_THRESHOLD = 0.01;
1047
847
  function useAnnotationDragHandlers({
1048
848
  annotations,
1049
849
  onAnnotationsChange,
1050
850
  samplesPerPixel,
1051
- sampleRate = (0, import_playout2.getGlobalAudioContext)().sampleRate,
851
+ // Default mirrors the engine default; the providers always pass the real rate from context (#510).
852
+ sampleRate = 48e3,
1052
853
  duration,
1053
854
  linkEndpoints
1054
855
  }) {
1055
- const originalAnnotationStateRef = import_react11.default.useRef(null);
1056
- const onDragStart = import_react11.default.useCallback(
856
+ const originalAnnotationStateRef = import_react9.default.useRef(null);
857
+ const onDragStart = import_react9.default.useCallback(
1057
858
  (event) => {
1058
859
  var _a;
1059
860
  const data = (_a = event.operation.source) == null ? void 0 : _a.data;
@@ -1072,7 +873,7 @@ function useAnnotationDragHandlers({
1072
873
  },
1073
874
  [annotations]
1074
875
  );
1075
- const onDragMove = import_react11.default.useCallback(
876
+ const onDragMove = import_react9.default.useCallback(
1076
877
  (event) => {
1077
878
  var _a, _b, _c;
1078
879
  if (!originalAnnotationStateRef.current) {
@@ -1098,7 +899,7 @@ function useAnnotationDragHandlers({
1098
899
  },
1099
900
  [annotations, onAnnotationsChange, samplesPerPixel, sampleRate, duration, linkEndpoints]
1100
901
  );
1101
- const onDragEnd = import_react11.default.useCallback(
902
+ const onDragEnd = import_react9.default.useCallback(
1102
903
  (event) => {
1103
904
  if (event.canceled && originalAnnotationStateRef.current) {
1104
905
  const { annotationIndex, start, end } = originalAnnotationStateRef.current;
@@ -1206,7 +1007,7 @@ function updateAnnotationBoundaries({
1206
1007
  }
1207
1008
 
1208
1009
  // src/hooks/useDragSensors.ts
1209
- var import_react12 = require("react");
1010
+ var import_react10 = require("react");
1210
1011
  var import_dom = require("@dnd-kit/dom");
1211
1012
  function useDragSensors(options = {}) {
1212
1013
  const {
@@ -1215,7 +1016,7 @@ function useDragSensors(options = {}) {
1215
1016
  touchTolerance = 5,
1216
1017
  mouseDistance = 1
1217
1018
  } = options;
1218
- return (0, import_react12.useMemo)(() => {
1019
+ return (0, import_react10.useMemo)(() => {
1219
1020
  if (touchOptimized) {
1220
1021
  return [
1221
1022
  import_dom.PointerSensor.configure({
@@ -1244,14 +1045,14 @@ function useDragSensors(options = {}) {
1244
1045
  }
1245
1046
 
1246
1047
  // src/hooks/useClipSplitting.ts
1247
- var import_react13 = require("react");
1048
+ var import_react11 = require("react");
1248
1049
  var import_engine2 = require("@waveform-playlist/engine");
1249
1050
  var useClipSplitting = (options) => {
1250
1051
  const { tracks, engineRef } = options;
1251
1052
  const { sampleRate } = usePlaylistData();
1252
1053
  const { currentTimeRef } = usePlaybackAnimation();
1253
1054
  const { selectedTrackId } = usePlaylistState();
1254
- const splitClipAt = (0, import_react13.useCallback)(
1055
+ const splitClipAt = (0, import_react11.useCallback)(
1255
1056
  (trackIndex, clipIndex, splitTime) => {
1256
1057
  const { samplesPerPixel } = options;
1257
1058
  const track = tracks[trackIndex];
@@ -1275,7 +1076,7 @@ var useClipSplitting = (options) => {
1275
1076
  },
1276
1077
  [tracks, options, engineRef, sampleRate]
1277
1078
  );
1278
- const splitClipAtPlayhead = (0, import_react13.useCallback)(() => {
1079
+ const splitClipAtPlayhead = (0, import_react11.useCallback)(() => {
1279
1080
  var _a;
1280
1081
  if (!selectedTrackId) {
1281
1082
  console.warn("[waveform-playlist] No track selected \u2014 click a clip to select a track first");
@@ -1306,16 +1107,16 @@ var useClipSplitting = (options) => {
1306
1107
  };
1307
1108
 
1308
1109
  // src/hooks/useKeyboardShortcuts.ts
1309
- var import_react14 = require("react");
1110
+ var import_react12 = require("react");
1111
+ var import_core = require("@waveform-playlist/core");
1310
1112
  var import_core2 = require("@waveform-playlist/core");
1311
- var import_core3 = require("@waveform-playlist/core");
1312
1113
  var useKeyboardShortcuts = (options) => {
1313
1114
  const { shortcuts, enabled = true } = options;
1314
- const handleKeyDown = (0, import_react14.useCallback)(
1315
- (event) => (0, import_core2.handleKeyboardEvent)(event, shortcuts, enabled),
1115
+ const handleKeyDown = (0, import_react12.useCallback)(
1116
+ (event) => (0, import_core.handleKeyboardEvent)(event, shortcuts, enabled),
1316
1117
  [shortcuts, enabled]
1317
1118
  );
1318
- (0, import_react14.useEffect)(() => {
1119
+ (0, import_react12.useEffect)(() => {
1319
1120
  if (!enabled) return;
1320
1121
  window.addEventListener("keydown", handleKeyDown);
1321
1122
  return () => {
@@ -1325,22 +1126,22 @@ var useKeyboardShortcuts = (options) => {
1325
1126
  };
1326
1127
 
1327
1128
  // src/hooks/usePlaybackShortcuts.ts
1328
- var import_react15 = require("react");
1129
+ var import_react13 = require("react");
1329
1130
  var usePlaybackShortcuts = (options = {}) => {
1330
1131
  const { enabled = true, additionalShortcuts = [], shortcuts: overrideShortcuts } = options;
1331
1132
  const { isPlaying } = usePlaybackAnimation();
1332
1133
  const { setCurrentTime, play, pause, stop } = usePlaylistControls();
1333
- const togglePlayPause = (0, import_react15.useCallback)(() => {
1134
+ const togglePlayPause = (0, import_react13.useCallback)(() => {
1334
1135
  if (isPlaying) {
1335
1136
  pause();
1336
1137
  } else {
1337
1138
  play();
1338
1139
  }
1339
1140
  }, [isPlaying, play, pause]);
1340
- const stopPlayback = (0, import_react15.useCallback)(() => {
1141
+ const stopPlayback = (0, import_react13.useCallback)(() => {
1341
1142
  stop();
1342
1143
  }, [stop]);
1343
- const rewindToStart = (0, import_react15.useCallback)(() => {
1144
+ const rewindToStart = (0, import_react13.useCallback)(() => {
1344
1145
  setCurrentTime(0);
1345
1146
  if (isPlaying) {
1346
1147
  play(0);
@@ -1380,7 +1181,7 @@ var usePlaybackShortcuts = (options = {}) => {
1380
1181
  };
1381
1182
 
1382
1183
  // src/hooks/useAnnotationKeyboardControls.ts
1383
- var import_react16 = require("react");
1184
+ var import_react14 = require("react");
1384
1185
  var LINK_THRESHOLD2 = 0.01;
1385
1186
  var TIME_DELTA = 0.01;
1386
1187
  function useAnnotationKeyboardControls({
@@ -1393,14 +1194,18 @@ function useAnnotationKeyboardControls({
1393
1194
  continuousPlay = false,
1394
1195
  enabled = true,
1395
1196
  scrollContainerRef,
1396
- onPlay
1197
+ onPlay,
1198
+ samplesPerPixel: samplesPerPixelProp,
1199
+ sampleRate: sampleRateProp
1397
1200
  }) {
1398
- const { samplesPerPixel, sampleRate } = usePlaylistData();
1399
- const activeIndex = (0, import_react16.useMemo)(() => {
1201
+ const playlistData = usePlaylistDataOptional();
1202
+ const samplesPerPixel = samplesPerPixelProp != null ? samplesPerPixelProp : playlistData == null ? void 0 : playlistData.samplesPerPixel;
1203
+ const sampleRate = sampleRateProp != null ? sampleRateProp : playlistData == null ? void 0 : playlistData.sampleRate;
1204
+ const activeIndex = (0, import_react14.useMemo)(() => {
1400
1205
  if (!activeAnnotationId) return -1;
1401
1206
  return annotations.findIndex((a) => a.id === activeAnnotationId);
1402
1207
  }, [annotations, activeAnnotationId]);
1403
- const scrollToAnnotation = (0, import_react16.useCallback)(
1208
+ const scrollToAnnotation = (0, import_react14.useCallback)(
1404
1209
  (annotationId) => {
1405
1210
  if (!(scrollContainerRef == null ? void 0 : scrollContainerRef.current) || !samplesPerPixel || !sampleRate) return;
1406
1211
  const annotation = annotations.find((a) => a.id === annotationId);
@@ -1423,12 +1228,12 @@ function useAnnotationKeyboardControls({
1423
1228
  },
1424
1229
  [annotations, scrollContainerRef, samplesPerPixel, sampleRate]
1425
1230
  );
1426
- (0, import_react16.useEffect)(() => {
1231
+ (0, import_react14.useEffect)(() => {
1427
1232
  if (activeAnnotationId && (scrollContainerRef == null ? void 0 : scrollContainerRef.current) && samplesPerPixel && sampleRate) {
1428
1233
  scrollToAnnotation(activeAnnotationId);
1429
1234
  }
1430
1235
  }, [activeAnnotationId, scrollToAnnotation, scrollContainerRef, samplesPerPixel, sampleRate]);
1431
- const moveStartBoundary = (0, import_react16.useCallback)(
1236
+ const moveStartBoundary = (0, import_react14.useCallback)(
1432
1237
  (delta) => {
1433
1238
  if (activeIndex < 0) return;
1434
1239
  const annotation = annotations[activeIndex];
@@ -1457,7 +1262,7 @@ function useAnnotationKeyboardControls({
1457
1262
  },
1458
1263
  [annotations, activeIndex, linkEndpoints, onAnnotationsChange]
1459
1264
  );
1460
- const moveEndBoundary = (0, import_react16.useCallback)(
1265
+ const moveEndBoundary = (0, import_react14.useCallback)(
1461
1266
  (delta) => {
1462
1267
  if (activeIndex < 0) return;
1463
1268
  const annotation = annotations[activeIndex];
@@ -1517,7 +1322,7 @@ function useAnnotationKeyboardControls({
1517
1322
  },
1518
1323
  [annotations, activeIndex, duration, linkEndpoints, onAnnotationsChange]
1519
1324
  );
1520
- const selectPrevious = (0, import_react16.useCallback)(() => {
1325
+ const selectPrevious = (0, import_react14.useCallback)(() => {
1521
1326
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1522
1327
  if (activeIndex <= 0) {
1523
1328
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
@@ -1525,7 +1330,7 @@ function useAnnotationKeyboardControls({
1525
1330
  onActiveAnnotationChange(annotations[activeIndex - 1].id);
1526
1331
  }
1527
1332
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1528
- const selectNext = (0, import_react16.useCallback)(() => {
1333
+ const selectNext = (0, import_react14.useCallback)(() => {
1529
1334
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1530
1335
  if (activeIndex < 0 || activeIndex >= annotations.length - 1) {
1531
1336
  onActiveAnnotationChange(annotations[0].id);
@@ -1533,25 +1338,25 @@ function useAnnotationKeyboardControls({
1533
1338
  onActiveAnnotationChange(annotations[activeIndex + 1].id);
1534
1339
  }
1535
1340
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1536
- const selectFirst = (0, import_react16.useCallback)(() => {
1341
+ const selectFirst = (0, import_react14.useCallback)(() => {
1537
1342
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1538
1343
  onActiveAnnotationChange(annotations[0].id);
1539
1344
  }, [annotations, onActiveAnnotationChange]);
1540
- const selectLast = (0, import_react16.useCallback)(() => {
1345
+ const selectLast = (0, import_react14.useCallback)(() => {
1541
1346
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1542
1347
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
1543
1348
  }, [annotations, onActiveAnnotationChange]);
1544
- const clearSelection = (0, import_react16.useCallback)(() => {
1349
+ const clearSelection = (0, import_react14.useCallback)(() => {
1545
1350
  if (!onActiveAnnotationChange) return;
1546
1351
  onActiveAnnotationChange(null);
1547
1352
  }, [onActiveAnnotationChange]);
1548
- const playActiveAnnotation = (0, import_react16.useCallback)(() => {
1353
+ const playActiveAnnotation = (0, import_react14.useCallback)(() => {
1549
1354
  if (activeIndex < 0 || !onPlay) return;
1550
1355
  const annotation = annotations[activeIndex];
1551
1356
  const playDuration = !continuousPlay ? annotation.end - annotation.start : void 0;
1552
1357
  onPlay(annotation.start, playDuration);
1553
1358
  }, [annotations, activeIndex, continuousPlay, onPlay]);
1554
- const activeAnnotationShortcuts = (0, import_react16.useMemo)(
1359
+ const activeAnnotationShortcuts = (0, import_react14.useMemo)(
1555
1360
  () => [
1556
1361
  {
1557
1362
  key: "[",
@@ -1588,7 +1393,7 @@ function useAnnotationKeyboardControls({
1588
1393
  ],
1589
1394
  [moveStartBoundary, moveEndBoundary, playActiveAnnotation]
1590
1395
  );
1591
- const navigationShortcuts = (0, import_react16.useMemo)(
1396
+ const navigationShortcuts = (0, import_react14.useMemo)(
1592
1397
  () => [
1593
1398
  {
1594
1399
  key: "ArrowUp",
@@ -1634,1404 +1439,46 @@ function useAnnotationKeyboardControls({
1634
1439
  }
1635
1440
  ],
1636
1441
  [selectPrevious, selectNext, selectFirst, selectLast, clearSelection]
1637
- );
1638
- useKeyboardShortcuts({
1639
- shortcuts: activeAnnotationShortcuts,
1640
- enabled: enabled && activeIndex >= 0
1641
- });
1642
- useKeyboardShortcuts({
1643
- shortcuts: navigationShortcuts,
1644
- enabled: enabled && annotations.length > 0 && !!onActiveAnnotationChange
1645
- });
1646
- return {
1647
- moveStartBoundary,
1648
- moveEndBoundary,
1649
- selectPrevious,
1650
- selectNext,
1651
- selectFirst,
1652
- selectLast,
1653
- clearSelection,
1654
- scrollToAnnotation,
1655
- playActiveAnnotation
1656
- };
1657
- }
1658
-
1659
- // src/hooks/useDynamicEffects.ts
1660
- var import_react17 = require("react");
1661
-
1662
- // src/effects/effectDefinitions.ts
1663
- var effectDefinitions = [
1664
- // === REVERB EFFECTS ===
1665
- {
1666
- id: "reverb",
1667
- name: "Reverb",
1668
- category: "reverb",
1669
- description: "Simple convolution reverb with adjustable decay time",
1670
- parameters: [
1671
- {
1672
- name: "decay",
1673
- label: "Decay",
1674
- type: "number",
1675
- min: 0.1,
1676
- max: 10,
1677
- step: 0.1,
1678
- default: 1.5,
1679
- unit: "s"
1680
- },
1681
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1682
- ]
1683
- },
1684
- {
1685
- id: "freeverb",
1686
- name: "Freeverb",
1687
- category: "reverb",
1688
- description: "Classic Schroeder/Moorer reverb with room size and dampening",
1689
- parameters: [
1690
- {
1691
- name: "roomSize",
1692
- label: "Room Size",
1693
- type: "number",
1694
- min: 0,
1695
- max: 1,
1696
- step: 0.01,
1697
- default: 0.7
1698
- },
1699
- {
1700
- name: "dampening",
1701
- label: "Dampening",
1702
- type: "number",
1703
- min: 0,
1704
- max: 1e4,
1705
- step: 100,
1706
- default: 3e3,
1707
- unit: "Hz"
1708
- },
1709
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1710
- ]
1711
- },
1712
- {
1713
- id: "jcReverb",
1714
- name: "JC Reverb",
1715
- category: "reverb",
1716
- description: "Attempt at Roland JC-120 chorus reverb emulation",
1717
- parameters: [
1718
- {
1719
- name: "roomSize",
1720
- label: "Room Size",
1721
- type: "number",
1722
- min: 0,
1723
- max: 1,
1724
- step: 0.01,
1725
- default: 0.5
1726
- },
1727
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1728
- ]
1729
- },
1730
- // === DELAY EFFECTS ===
1731
- {
1732
- id: "feedbackDelay",
1733
- name: "Feedback Delay",
1734
- category: "delay",
1735
- description: "Delay line with feedback for echo effects",
1736
- parameters: [
1737
- {
1738
- name: "delayTime",
1739
- label: "Delay Time",
1740
- type: "number",
1741
- min: 0,
1742
- max: 1,
1743
- step: 0.01,
1744
- default: 0.25,
1745
- unit: "s"
1746
- },
1747
- {
1748
- name: "feedback",
1749
- label: "Feedback",
1750
- type: "number",
1751
- min: 0,
1752
- max: 0.95,
1753
- step: 0.01,
1754
- default: 0.5
1755
- },
1756
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1757
- ]
1758
- },
1759
- {
1760
- id: "pingPongDelay",
1761
- name: "Ping Pong Delay",
1762
- category: "delay",
1763
- description: "Stereo delay bouncing between left and right channels",
1764
- parameters: [
1765
- {
1766
- name: "delayTime",
1767
- label: "Delay Time",
1768
- type: "number",
1769
- min: 0,
1770
- max: 1,
1771
- step: 0.01,
1772
- default: 0.25,
1773
- unit: "s"
1774
- },
1775
- {
1776
- name: "feedback",
1777
- label: "Feedback",
1778
- type: "number",
1779
- min: 0,
1780
- max: 0.95,
1781
- step: 0.01,
1782
- default: 0.5
1783
- },
1784
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1785
- ]
1786
- },
1787
- // === MODULATION EFFECTS ===
1788
- {
1789
- id: "chorus",
1790
- name: "Chorus",
1791
- category: "modulation",
1792
- description: "Creates thickness by layering detuned copies of the signal",
1793
- parameters: [
1794
- {
1795
- name: "frequency",
1796
- label: "Rate",
1797
- type: "number",
1798
- min: 0.1,
1799
- max: 10,
1800
- step: 0.1,
1801
- default: 1.5,
1802
- unit: "Hz"
1803
- },
1804
- {
1805
- name: "delayTime",
1806
- label: "Delay",
1807
- type: "number",
1808
- min: 0,
1809
- max: 20,
1810
- step: 0.5,
1811
- default: 3.5,
1812
- unit: "ms"
1813
- },
1814
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.7 },
1815
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1816
- ]
1817
- },
1818
- {
1819
- id: "phaser",
1820
- name: "Phaser",
1821
- category: "modulation",
1822
- description: "Classic phaser effect using allpass filters",
1823
- parameters: [
1824
- {
1825
- name: "frequency",
1826
- label: "Rate",
1827
- type: "number",
1828
- min: 0.1,
1829
- max: 10,
1830
- step: 0.1,
1831
- default: 0.5,
1832
- unit: "Hz"
1833
- },
1834
- { name: "octaves", label: "Octaves", type: "number", min: 1, max: 6, step: 1, default: 3 },
1835
- {
1836
- name: "baseFrequency",
1837
- label: "Base Freq",
1838
- type: "number",
1839
- min: 100,
1840
- max: 2e3,
1841
- step: 10,
1842
- default: 350,
1843
- unit: "Hz"
1844
- },
1845
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1846
- ]
1847
- },
1848
- {
1849
- id: "tremolo",
1850
- name: "Tremolo",
1851
- category: "modulation",
1852
- description: "Rhythmic volume modulation",
1853
- parameters: [
1854
- {
1855
- name: "frequency",
1856
- label: "Rate",
1857
- type: "number",
1858
- min: 0.1,
1859
- max: 20,
1860
- step: 0.1,
1861
- default: 4,
1862
- unit: "Hz"
1863
- },
1864
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 },
1865
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1866
- ]
1867
- },
1868
- {
1869
- id: "vibrato",
1870
- name: "Vibrato",
1871
- category: "modulation",
1872
- description: "Pitch modulation effect",
1873
- parameters: [
1874
- {
1875
- name: "frequency",
1876
- label: "Rate",
1877
- type: "number",
1878
- min: 0.1,
1879
- max: 20,
1880
- step: 0.1,
1881
- default: 5,
1882
- unit: "Hz"
1883
- },
1884
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.1 },
1885
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1886
- ]
1887
- },
1888
- {
1889
- id: "autoPanner",
1890
- name: "Auto Panner",
1891
- category: "modulation",
1892
- description: "Automatic left-right panning",
1893
- parameters: [
1894
- {
1895
- name: "frequency",
1896
- label: "Rate",
1897
- type: "number",
1898
- min: 0.1,
1899
- max: 10,
1900
- step: 0.1,
1901
- default: 1,
1902
- unit: "Hz"
1903
- },
1904
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 1 },
1905
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1906
- ]
1907
- },
1908
- // === FILTER EFFECTS ===
1909
- {
1910
- id: "autoFilter",
1911
- name: "Auto Filter",
1912
- category: "filter",
1913
- description: "Automated filter sweep with LFO",
1914
- parameters: [
1915
- {
1916
- name: "frequency",
1917
- label: "Rate",
1918
- type: "number",
1919
- min: 0.1,
1920
- max: 10,
1921
- step: 0.1,
1922
- default: 1,
1923
- unit: "Hz"
1924
- },
1925
- {
1926
- name: "baseFrequency",
1927
- label: "Base Freq",
1928
- type: "number",
1929
- min: 20,
1930
- max: 2e3,
1931
- step: 10,
1932
- default: 200,
1933
- unit: "Hz"
1934
- },
1935
- {
1936
- name: "octaves",
1937
- label: "Octaves",
1938
- type: "number",
1939
- min: 0.5,
1940
- max: 8,
1941
- step: 0.5,
1942
- default: 2.6
1943
- },
1944
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 1 },
1945
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1946
- ]
1947
- },
1948
- {
1949
- id: "autoWah",
1950
- name: "Auto Wah",
1951
- category: "filter",
1952
- description: "Envelope follower filter effect",
1953
- parameters: [
1954
- {
1955
- name: "baseFrequency",
1956
- label: "Base Freq",
1957
- type: "number",
1958
- min: 20,
1959
- max: 500,
1960
- step: 10,
1961
- default: 100,
1962
- unit: "Hz"
1963
- },
1964
- { name: "octaves", label: "Octaves", type: "number", min: 1, max: 8, step: 1, default: 6 },
1965
- {
1966
- name: "sensitivity",
1967
- label: "Sensitivity",
1968
- type: "number",
1969
- min: -40,
1970
- max: 0,
1971
- step: 1,
1972
- default: 0,
1973
- unit: "dB"
1974
- },
1975
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1976
- ]
1977
- },
1978
- {
1979
- id: "eq3",
1980
- name: "3-Band EQ",
1981
- category: "filter",
1982
- description: "Three band equalizer with low, mid, and high controls",
1983
- parameters: [
1984
- {
1985
- name: "low",
1986
- label: "Low",
1987
- type: "number",
1988
- min: -24,
1989
- max: 24,
1990
- step: 0.5,
1991
- default: 0,
1992
- unit: "dB"
1993
- },
1994
- {
1995
- name: "mid",
1996
- label: "Mid",
1997
- type: "number",
1998
- min: -24,
1999
- max: 24,
2000
- step: 0.5,
2001
- default: 0,
2002
- unit: "dB"
2003
- },
2004
- {
2005
- name: "high",
2006
- label: "High",
2007
- type: "number",
2008
- min: -24,
2009
- max: 24,
2010
- step: 0.5,
2011
- default: 0,
2012
- unit: "dB"
2013
- },
2014
- {
2015
- name: "lowFrequency",
2016
- label: "Low Freq",
2017
- type: "number",
2018
- min: 20,
2019
- max: 500,
2020
- step: 10,
2021
- default: 400,
2022
- unit: "Hz"
2023
- },
2024
- {
2025
- name: "highFrequency",
2026
- label: "High Freq",
2027
- type: "number",
2028
- min: 1e3,
2029
- max: 1e4,
2030
- step: 100,
2031
- default: 2500,
2032
- unit: "Hz"
2033
- }
2034
- ]
2035
- },
2036
- // === DISTORTION EFFECTS ===
2037
- {
2038
- id: "distortion",
2039
- name: "Distortion",
2040
- category: "distortion",
2041
- description: "Wave shaping distortion effect",
2042
- parameters: [
2043
- {
2044
- name: "distortion",
2045
- label: "Drive",
2046
- type: "number",
2047
- min: 0,
2048
- max: 1,
2049
- step: 0.01,
2050
- default: 0.4
2051
- },
2052
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
2053
- ]
2054
- },
2055
- {
2056
- id: "bitCrusher",
2057
- name: "Bit Crusher",
2058
- category: "distortion",
2059
- description: "Reduces bit depth for lo-fi digital texture",
2060
- parameters: [
2061
- { name: "bits", label: "Bits", type: "number", min: 1, max: 16, step: 1, default: 4 },
2062
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
2063
- ]
2064
- },
2065
- {
2066
- id: "chebyshev",
2067
- name: "Chebyshev",
2068
- category: "distortion",
2069
- description: "Waveshaping distortion using Chebyshev polynomials",
2070
- parameters: [
2071
- { name: "order", label: "Order", type: "number", min: 1, max: 100, step: 1, default: 50 },
2072
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
2073
- ]
2074
- },
2075
- // === DYNAMICS EFFECTS ===
2076
- {
2077
- id: "compressor",
2078
- name: "Compressor",
2079
- category: "dynamics",
2080
- description: "Dynamic range compressor",
2081
- parameters: [
2082
- {
2083
- name: "threshold",
2084
- label: "Threshold",
2085
- type: "number",
2086
- min: -60,
2087
- max: 0,
2088
- step: 1,
2089
- default: -24,
2090
- unit: "dB"
2091
- },
2092
- { name: "ratio", label: "Ratio", type: "number", min: 1, max: 20, step: 0.5, default: 4 },
2093
- {
2094
- name: "attack",
2095
- label: "Attack",
2096
- type: "number",
2097
- min: 0,
2098
- max: 1,
2099
- step: 1e-3,
2100
- default: 3e-3,
2101
- unit: "s"
2102
- },
2103
- {
2104
- name: "release",
2105
- label: "Release",
2106
- type: "number",
2107
- min: 0,
2108
- max: 1,
2109
- step: 0.01,
2110
- default: 0.25,
2111
- unit: "s"
2112
- },
2113
- {
2114
- name: "knee",
2115
- label: "Knee",
2116
- type: "number",
2117
- min: 0,
2118
- max: 40,
2119
- step: 1,
2120
- default: 30,
2121
- unit: "dB"
2122
- }
2123
- ]
2124
- },
2125
- {
2126
- id: "limiter",
2127
- name: "Limiter",
2128
- category: "dynamics",
2129
- description: "Hard limiter to prevent clipping",
2130
- parameters: [
2131
- {
2132
- name: "threshold",
2133
- label: "Threshold",
2134
- type: "number",
2135
- min: -12,
2136
- max: 0,
2137
- step: 0.5,
2138
- default: -6,
2139
- unit: "dB"
2140
- }
2141
- ]
2142
- },
2143
- {
2144
- id: "gate",
2145
- name: "Gate",
2146
- category: "dynamics",
2147
- description: "Noise gate to silence signal below threshold",
2148
- parameters: [
2149
- {
2150
- name: "threshold",
2151
- label: "Threshold",
2152
- type: "number",
2153
- min: -100,
2154
- max: 0,
2155
- step: 1,
2156
- default: -40,
2157
- unit: "dB"
2158
- },
2159
- {
2160
- name: "attack",
2161
- label: "Attack",
2162
- type: "number",
2163
- min: 0,
2164
- max: 0.3,
2165
- step: 1e-3,
2166
- default: 1e-3,
2167
- unit: "s"
2168
- },
2169
- {
2170
- name: "release",
2171
- label: "Release",
2172
- type: "number",
2173
- min: 0,
2174
- max: 0.5,
2175
- step: 0.01,
2176
- default: 0.1,
2177
- unit: "s"
2178
- }
2179
- ]
2180
- },
2181
- // === SPATIAL EFFECTS ===
2182
- {
2183
- id: "stereoWidener",
2184
- name: "Stereo Widener",
2185
- category: "spatial",
2186
- description: "Expands or narrows the stereo image",
2187
- parameters: [
2188
- { name: "width", label: "Width", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
2189
- ]
2190
- }
2191
- ];
2192
- var getEffectDefinition = (id) => {
2193
- return effectDefinitions.find((def) => def.id === id);
2194
- };
2195
- var getEffectsByCategory = (category) => {
2196
- return effectDefinitions.filter((def) => def.category === category);
2197
- };
2198
- var effectCategories = [
2199
- { id: "reverb", name: "Reverb" },
2200
- { id: "delay", name: "Delay" },
2201
- { id: "modulation", name: "Modulation" },
2202
- { id: "filter", name: "Filter" },
2203
- { id: "distortion", name: "Distortion" },
2204
- { id: "dynamics", name: "Dynamics" },
2205
- { id: "spatial", name: "Spatial" }
2206
- ];
2207
-
2208
- // src/effects/effectFactory.ts
2209
- var import_tone2 = require("tone");
2210
- function asEffectConstructor(ctor) {
2211
- return ctor;
2212
- }
2213
- var effectConstructors = {
2214
- reverb: asEffectConstructor(import_tone2.Reverb),
2215
- freeverb: asEffectConstructor(import_tone2.Freeverb),
2216
- jcReverb: asEffectConstructor(import_tone2.JCReverb),
2217
- feedbackDelay: asEffectConstructor(import_tone2.FeedbackDelay),
2218
- pingPongDelay: asEffectConstructor(import_tone2.PingPongDelay),
2219
- chorus: asEffectConstructor(import_tone2.Chorus),
2220
- phaser: asEffectConstructor(import_tone2.Phaser),
2221
- tremolo: asEffectConstructor(import_tone2.Tremolo),
2222
- vibrato: asEffectConstructor(import_tone2.Vibrato),
2223
- autoPanner: asEffectConstructor(import_tone2.AutoPanner),
2224
- autoFilter: asEffectConstructor(import_tone2.AutoFilter),
2225
- autoWah: asEffectConstructor(import_tone2.AutoWah),
2226
- eq3: asEffectConstructor(import_tone2.EQ3),
2227
- distortion: asEffectConstructor(import_tone2.Distortion),
2228
- bitCrusher: asEffectConstructor(import_tone2.BitCrusher),
2229
- chebyshev: asEffectConstructor(import_tone2.Chebyshev),
2230
- compressor: asEffectConstructor(import_tone2.Compressor),
2231
- limiter: asEffectConstructor(import_tone2.Limiter),
2232
- gate: asEffectConstructor(import_tone2.Gate),
2233
- stereoWidener: asEffectConstructor(import_tone2.StereoWidener)
2234
- };
2235
- var instanceCounter = 0;
2236
- var generateInstanceId = () => {
2237
- return `effect_${Date.now()}_${++instanceCounter}`;
2238
- };
2239
- function createEffectInstance(definition, initialParams) {
2240
- const Constructor = effectConstructors[definition.id];
2241
- if (!Constructor) {
2242
- throw new Error(`Unknown effect type: ${definition.id}`);
2243
- }
2244
- const options = {};
2245
- definition.parameters.forEach((param) => {
2246
- var _a;
2247
- const value = (_a = initialParams == null ? void 0 : initialParams[param.name]) != null ? _a : param.default;
2248
- options[param.name] = value;
2249
- });
2250
- const effect = new Constructor(options);
2251
- const instanceId = generateInstanceId();
2252
- const effectRecord = effect;
2253
- return {
2254
- effect,
2255
- id: definition.id,
2256
- instanceId,
2257
- dispose() {
2258
- try {
2259
- effect.disconnect();
2260
- effect.dispose();
2261
- } catch (e) {
2262
- console.warn(
2263
- `[waveform-playlist] Error disposing effect "${definition.id}" (${instanceId}):`,
2264
- e
2265
- );
2266
- }
2267
- },
2268
- setParameter(name, value) {
2269
- const prop = effectRecord[name];
2270
- if (name === "wet") {
2271
- const wetProp = effectRecord["wet"];
2272
- if (wetProp && typeof wetProp === "object" && "value" in wetProp) {
2273
- wetProp.value = value;
2274
- return;
2275
- }
2276
- }
2277
- if (prop !== void 0) {
2278
- if (prop && typeof prop === "object" && "value" in prop) {
2279
- prop.value = value;
2280
- } else {
2281
- effectRecord[name] = value;
2282
- }
2283
- }
2284
- },
2285
- getParameter(name) {
2286
- if (name === "wet") {
2287
- const wetProp = effectRecord["wet"];
2288
- if (wetProp && typeof wetProp === "object" && "value" in wetProp) {
2289
- return wetProp.value;
2290
- }
2291
- }
2292
- const prop = effectRecord[name];
2293
- if (prop !== void 0) {
2294
- if (prop && typeof prop === "object" && "value" in prop) {
2295
- return prop.value;
2296
- }
2297
- return prop;
2298
- }
2299
- return void 0;
2300
- },
2301
- connect(destination) {
2302
- effect.connect(destination);
2303
- },
2304
- disconnect() {
2305
- try {
2306
- effect.disconnect();
2307
- } catch (e) {
2308
- console.warn(
2309
- `[waveform-playlist] Error disconnecting effect "${definition.id}" (${instanceId}):`,
2310
- e
2311
- );
2312
- }
2313
- }
2314
- };
2315
- }
2316
- function createEffectChain(effects) {
2317
- if (effects.length === 0) {
2318
- throw new Error("Cannot create effect chain with no effects");
2319
- }
2320
- for (let i = 0; i < effects.length - 1; i++) {
2321
- effects[i].effect.connect(effects[i + 1].effect);
2322
- }
2323
- return {
2324
- input: effects[0].effect,
2325
- output: effects[effects.length - 1].effect,
2326
- dispose() {
2327
- effects.forEach((e) => e.dispose());
2328
- }
2329
- };
2330
- }
2331
-
2332
- // src/hooks/useDynamicEffects.ts
2333
- var import_tone3 = require("tone");
2334
- function useDynamicEffects(fftSize = 256) {
2335
- const [activeEffects, setActiveEffects] = (0, import_react17.useState)([]);
2336
- const activeEffectsRef = (0, import_react17.useRef)(activeEffects);
2337
- activeEffectsRef.current = activeEffects;
2338
- const effectInstancesRef = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
2339
- const analyserRef = (0, import_react17.useRef)(null);
2340
- const graphNodesRef = (0, import_react17.useRef)(null);
2341
- const rebuildChain = (0, import_react17.useCallback)((effects) => {
2342
- const nodes = graphNodesRef.current;
2343
- if (!nodes) return;
2344
- const { masterGainNode, destination, analyserNode } = nodes;
2345
- try {
2346
- masterGainNode.disconnect();
2347
- } catch (e) {
2348
- console.warn("[waveform-playlist] Error disconnecting master effects chain:", e);
2349
- }
2350
- const instances = effects.map((ae) => effectInstancesRef.current.get(ae.instanceId)).filter((inst) => inst !== void 0);
2351
- if (instances.length === 0) {
2352
- masterGainNode.connect(analyserNode);
2353
- analyserNode.connect(destination);
2354
- } else {
2355
- let currentNode = masterGainNode;
2356
- instances.forEach((inst) => {
2357
- try {
2358
- inst.disconnect();
2359
- } catch (e) {
2360
- console.warn(`[waveform-playlist] Error disconnecting effect "${inst.id}":`, e);
2361
- }
2362
- currentNode.connect(inst.effect);
2363
- currentNode = inst.effect;
2364
- });
2365
- currentNode.connect(analyserNode);
2366
- analyserNode.connect(destination);
2367
- }
2368
- }, []);
2369
- const addEffect = (0, import_react17.useCallback)((effectId) => {
2370
- const definition = getEffectDefinition(effectId);
2371
- if (!definition) {
2372
- console.error(`Unknown effect: ${effectId}`);
2373
- return;
2374
- }
2375
- const params = {};
2376
- definition.parameters.forEach((p) => {
2377
- params[p.name] = p.default;
2378
- });
2379
- const instance = createEffectInstance(definition, params);
2380
- effectInstancesRef.current.set(instance.instanceId, instance);
2381
- const newActiveEffect = {
2382
- instanceId: instance.instanceId,
2383
- effectId: definition.id,
2384
- definition,
2385
- params,
2386
- bypassed: false
2387
- };
2388
- setActiveEffects((prev) => [...prev, newActiveEffect]);
2389
- }, []);
2390
- const removeEffect = (0, import_react17.useCallback)((instanceId) => {
2391
- const instance = effectInstancesRef.current.get(instanceId);
2392
- if (instance) {
2393
- instance.dispose();
2394
- effectInstancesRef.current.delete(instanceId);
2395
- }
2396
- setActiveEffects((prev) => prev.filter((e) => e.instanceId !== instanceId));
2397
- }, []);
2398
- const updateParameter = (0, import_react17.useCallback)(
2399
- (instanceId, paramName, value) => {
2400
- const instance = effectInstancesRef.current.get(instanceId);
2401
- if (instance) {
2402
- instance.setParameter(paramName, value);
2403
- }
2404
- setActiveEffects(
2405
- (prev) => prev.map(
2406
- (e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { params: __spreadProps(__spreadValues({}, e.params), { [paramName]: value }) }) : e
2407
- )
2408
- );
2409
- },
2410
- []
2411
- );
2412
- const toggleBypass = (0, import_react17.useCallback)((instanceId) => {
2413
- var _a;
2414
- const effect = activeEffectsRef.current.find((e) => e.instanceId === instanceId);
2415
- if (!effect) return;
2416
- const newBypassed = !effect.bypassed;
2417
- const instance = effectInstancesRef.current.get(instanceId);
2418
- if (instance) {
2419
- const originalWet = (_a = effect.params.wet) != null ? _a : 1;
2420
- instance.setParameter("wet", newBypassed ? 0 : originalWet);
2421
- }
2422
- setActiveEffects(
2423
- (prev) => prev.map((e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { bypassed: newBypassed }) : e)
2424
- );
2425
- }, []);
2426
- const reorderEffects = (0, import_react17.useCallback)((fromIndex, toIndex) => {
2427
- setActiveEffects((prev) => {
2428
- const newEffects = [...prev];
2429
- const [removed] = newEffects.splice(fromIndex, 1);
2430
- newEffects.splice(toIndex, 0, removed);
2431
- return newEffects;
2432
- });
2433
- }, []);
2434
- const clearAllEffects = (0, import_react17.useCallback)(() => {
2435
- effectInstancesRef.current.forEach((inst) => inst.dispose());
2436
- effectInstancesRef.current.clear();
2437
- setActiveEffects([]);
2438
- }, []);
2439
- (0, import_react17.useEffect)(() => {
2440
- rebuildChain(activeEffects);
2441
- }, [activeEffects, rebuildChain]);
2442
- const masterEffects = (0, import_react17.useCallback)(
2443
- (masterGainNode, destination, _isOffline) => {
2444
- const analyserNode = new import_tone3.Analyser("fft", fftSize);
2445
- analyserRef.current = analyserNode;
2446
- graphNodesRef.current = {
2447
- masterGainNode,
2448
- destination,
2449
- analyserNode
2450
- };
2451
- const effects = activeEffectsRef.current;
2452
- const instances = effects.map((ae) => effectInstancesRef.current.get(ae.instanceId)).filter((inst) => inst !== void 0);
2453
- if (instances.length === 0) {
2454
- masterGainNode.connect(analyserNode);
2455
- analyserNode.connect(destination);
2456
- } else {
2457
- let currentNode = masterGainNode;
2458
- instances.forEach((inst) => {
2459
- currentNode.connect(inst.effect);
2460
- currentNode = inst.effect;
2461
- });
2462
- currentNode.connect(analyserNode);
2463
- analyserNode.connect(destination);
2464
- }
2465
- return function cleanup() {
2466
- analyserNode.dispose();
2467
- analyserRef.current = null;
2468
- graphNodesRef.current = null;
2469
- };
2470
- },
2471
- [fftSize]
2472
- // Only fftSize - reads effects from ref
2473
- );
2474
- (0, import_react17.useEffect)(() => {
2475
- const effectInstances = effectInstancesRef.current;
2476
- return () => {
2477
- effectInstances.forEach((inst) => inst.dispose());
2478
- effectInstances.clear();
2479
- };
2480
- }, []);
2481
- const createOfflineEffectsFunction = (0, import_react17.useCallback)(() => {
2482
- const nonBypassedEffects = activeEffects.filter((e) => !e.bypassed);
2483
- if (nonBypassedEffects.length === 0) {
2484
- return void 0;
2485
- }
2486
- return (masterGainNode, destination, _isOffline) => {
2487
- const offlineInstances = [];
2488
- for (const activeEffect of nonBypassedEffects) {
2489
- const instance = createEffectInstance(activeEffect.definition, activeEffect.params);
2490
- offlineInstances.push(instance);
2491
- }
2492
- if (offlineInstances.length === 0) {
2493
- masterGainNode.connect(destination);
2494
- } else {
2495
- let currentNode = masterGainNode;
2496
- offlineInstances.forEach((inst) => {
2497
- currentNode.connect(inst.effect);
2498
- currentNode = inst.effect;
2499
- });
2500
- currentNode.connect(destination);
2501
- }
2502
- return function cleanup() {
2503
- offlineInstances.forEach((inst) => inst.dispose());
2504
- };
2505
- };
2506
- }, [activeEffects]);
2507
- return {
2508
- activeEffects,
2509
- availableEffects: effectDefinitions,
2510
- addEffect,
2511
- removeEffect,
2512
- updateParameter,
2513
- toggleBypass,
2514
- reorderEffects,
2515
- clearAllEffects,
2516
- masterEffects,
2517
- createOfflineEffectsFunction,
2518
- analyserRef
2519
- };
2520
- }
2521
-
2522
- // src/hooks/useTrackDynamicEffects.ts
2523
- var import_react18 = require("react");
2524
- function useTrackDynamicEffects() {
2525
- const [trackEffectsState, setTrackEffectsState] = (0, import_react18.useState)(
2526
- /* @__PURE__ */ new Map()
2527
- );
2528
- const trackEffectInstancesRef = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
2529
- const trackGraphNodesRef = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
2530
- const rebuildTrackChain = (0, import_react18.useCallback)((trackId, trackEffects) => {
2531
- const nodes = trackGraphNodesRef.current.get(trackId);
2532
- if (!nodes) return;
2533
- const { graphEnd, masterGainNode } = nodes;
2534
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2535
- try {
2536
- graphEnd.disconnect();
2537
- } catch (e) {
2538
- console.warn(`[waveform-playlist] Error disconnecting track "${trackId}" effect chain:`, e);
2539
- }
2540
- const instances = trackEffects.map((ae) => instancesMap == null ? void 0 : instancesMap.get(ae.instanceId)).filter((inst) => inst !== void 0);
2541
- if (instances.length === 0) {
2542
- graphEnd.connect(masterGainNode);
2543
- } else {
2544
- let currentNode = graphEnd;
2545
- instances.forEach((inst) => {
2546
- try {
2547
- inst.disconnect();
2548
- } catch (e) {
2549
- console.warn(
2550
- `[waveform-playlist] Error disconnecting effect "${inst.id}" on track "${trackId}":`,
2551
- e
2552
- );
2553
- }
2554
- currentNode.connect(inst.effect);
2555
- currentNode = inst.effect;
2556
- });
2557
- currentNode.connect(masterGainNode);
2558
- }
2559
- }, []);
2560
- const addEffectToTrack = (0, import_react18.useCallback)((trackId, effectId) => {
2561
- const definition = getEffectDefinition(effectId);
2562
- if (!definition) {
2563
- console.error(`Unknown effect: ${effectId}`);
2564
- return;
2565
- }
2566
- const params = {};
2567
- definition.parameters.forEach((p) => {
2568
- params[p.name] = p.default;
2569
- });
2570
- const instance = createEffectInstance(definition, params);
2571
- if (!trackEffectInstancesRef.current.has(trackId)) {
2572
- trackEffectInstancesRef.current.set(trackId, /* @__PURE__ */ new Map());
2573
- }
2574
- trackEffectInstancesRef.current.get(trackId).set(instance.instanceId, instance);
2575
- const newActiveEffect = {
2576
- instanceId: instance.instanceId,
2577
- effectId: definition.id,
2578
- definition,
2579
- params,
2580
- bypassed: false
2581
- };
2582
- setTrackEffectsState((prev) => {
2583
- const newState = new Map(prev);
2584
- const existing = newState.get(trackId) || [];
2585
- newState.set(trackId, [...existing, newActiveEffect]);
2586
- return newState;
2587
- });
2588
- }, []);
2589
- const removeEffectFromTrack = (0, import_react18.useCallback)((trackId, instanceId) => {
2590
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2591
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2592
- if (instance) {
2593
- instance.dispose();
2594
- instancesMap == null ? void 0 : instancesMap.delete(instanceId);
2595
- }
2596
- setTrackEffectsState((prev) => {
2597
- const newState = new Map(prev);
2598
- const existing = newState.get(trackId) || [];
2599
- newState.set(
2600
- trackId,
2601
- existing.filter((e) => e.instanceId !== instanceId)
2602
- );
2603
- return newState;
2604
- });
2605
- }, []);
2606
- const updateTrackEffectParameter = (0, import_react18.useCallback)(
2607
- (trackId, instanceId, paramName, value) => {
2608
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2609
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2610
- if (instance) {
2611
- instance.setParameter(paramName, value);
2612
- }
2613
- setTrackEffectsState((prev) => {
2614
- const newState = new Map(prev);
2615
- const existing = newState.get(trackId) || [];
2616
- newState.set(
2617
- trackId,
2618
- existing.map(
2619
- (e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { params: __spreadProps(__spreadValues({}, e.params), { [paramName]: value }) }) : e
2620
- )
2621
- );
2622
- return newState;
2623
- });
2624
- },
2625
- []
2626
- );
2627
- const toggleBypass = (0, import_react18.useCallback)((trackId, instanceId) => {
2628
- var _a;
2629
- const trackEffects = trackEffectsStateRef.current.get(trackId) || [];
2630
- const effect = trackEffects.find((e) => e.instanceId === instanceId);
2631
- if (!effect) return;
2632
- const newBypassed = !effect.bypassed;
2633
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2634
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2635
- if (instance) {
2636
- const originalWet = (_a = effect.params.wet) != null ? _a : 1;
2637
- instance.setParameter("wet", newBypassed ? 0 : originalWet);
2638
- }
2639
- setTrackEffectsState((prev) => {
2640
- const newState = new Map(prev);
2641
- const existing = newState.get(trackId) || [];
2642
- newState.set(
2643
- trackId,
2644
- existing.map((e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { bypassed: newBypassed }) : e)
2645
- );
2646
- return newState;
2647
- });
2648
- }, []);
2649
- const clearTrackEffects = (0, import_react18.useCallback)((trackId) => {
2650
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2651
- if (instancesMap) {
2652
- instancesMap.forEach((inst) => inst.dispose());
2653
- instancesMap.clear();
2654
- }
2655
- setTrackEffectsState((prev) => {
2656
- const newState = new Map(prev);
2657
- newState.set(trackId, []);
2658
- return newState;
2659
- });
2660
- }, []);
2661
- const trackEffectsStateRef = (0, import_react18.useRef)(trackEffectsState);
2662
- trackEffectsStateRef.current = trackEffectsState;
2663
- const getTrackEffectsFunction = (0, import_react18.useCallback)(
2664
- (trackId) => {
2665
- return (graphEnd, masterGainNode, _isOffline) => {
2666
- trackGraphNodesRef.current.set(trackId, {
2667
- graphEnd,
2668
- masterGainNode
2669
- });
2670
- const trackEffects = trackEffectsStateRef.current.get(trackId) || [];
2671
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2672
- const instances = trackEffects.map((ae) => instancesMap == null ? void 0 : instancesMap.get(ae.instanceId)).filter((inst) => inst !== void 0);
2673
- if (instances.length === 0) {
2674
- graphEnd.connect(masterGainNode);
2675
- } else {
2676
- let currentNode = graphEnd;
2677
- instances.forEach((inst) => {
2678
- currentNode.connect(inst.effect);
2679
- currentNode = inst.effect;
2680
- });
2681
- currentNode.connect(masterGainNode);
2682
- }
2683
- return function cleanup() {
2684
- trackGraphNodesRef.current.delete(trackId);
2685
- };
2686
- };
2687
- },
2688
- []
2689
- // No dependencies - stable function that reads from refs
2690
- );
2691
- (0, import_react18.useEffect)(() => {
2692
- trackEffectsState.forEach((effects, trackId) => {
2693
- rebuildTrackChain(trackId, effects);
2694
- });
2695
- }, [trackEffectsState, rebuildTrackChain]);
2696
- (0, import_react18.useEffect)(() => {
2697
- const trackEffectInstances = trackEffectInstancesRef.current;
2698
- return () => {
2699
- trackEffectInstances.forEach((instancesMap) => {
2700
- instancesMap.forEach((inst) => inst.dispose());
2701
- instancesMap.clear();
2702
- });
2703
- trackEffectInstances.clear();
2704
- };
2705
- }, []);
2706
- const createOfflineTrackEffectsFunction = (0, import_react18.useCallback)(
2707
- (trackId) => {
2708
- const trackEffects = trackEffectsState.get(trackId) || [];
2709
- const nonBypassedEffects = trackEffects.filter((e) => !e.bypassed);
2710
- if (nonBypassedEffects.length === 0) {
2711
- return void 0;
2712
- }
2713
- return (graphEnd, masterGainNode, _isOffline) => {
2714
- const offlineInstances = [];
2715
- for (const activeEffect of nonBypassedEffects) {
2716
- const instance = createEffectInstance(activeEffect.definition, activeEffect.params);
2717
- offlineInstances.push(instance);
2718
- }
2719
- if (offlineInstances.length === 0) {
2720
- graphEnd.connect(masterGainNode);
2721
- } else {
2722
- let currentNode = graphEnd;
2723
- offlineInstances.forEach((inst) => {
2724
- currentNode.connect(inst.effect);
2725
- currentNode = inst.effect;
2726
- });
2727
- currentNode.connect(masterGainNode);
2728
- }
2729
- return function cleanup() {
2730
- offlineInstances.forEach((inst) => inst.dispose());
2731
- };
2732
- };
2733
- },
2734
- [trackEffectsState]
2735
- );
2736
- return {
2737
- trackEffectsState,
2738
- addEffectToTrack,
2739
- removeEffectFromTrack,
2740
- updateTrackEffectParameter,
2741
- toggleBypass,
2742
- clearTrackEffects,
2743
- getTrackEffectsFunction,
2744
- createOfflineTrackEffectsFunction,
2745
- availableEffects: effectDefinitions
2746
- };
2747
- }
2748
-
2749
- // src/hooks/useExportWav.ts
2750
- var import_react19 = require("react");
2751
- var import_core4 = require("@waveform-playlist/core");
2752
- var import_playout3 = require("@waveform-playlist/playout");
2753
-
2754
- // src/utils/wavEncoder.ts
2755
- function encodeWav(audioBuffer, options = {}) {
2756
- const { bitDepth = 16 } = options;
2757
- const numChannels = audioBuffer.numberOfChannels;
2758
- const sampleRate = audioBuffer.sampleRate;
2759
- const numSamples = audioBuffer.length;
2760
- const bytesPerSample = bitDepth / 8;
2761
- const blockAlign = numChannels * bytesPerSample;
2762
- const byteRate = sampleRate * blockAlign;
2763
- const dataSize = numSamples * blockAlign;
2764
- const headerSize = 44;
2765
- const totalSize = headerSize + dataSize;
2766
- const buffer = new ArrayBuffer(totalSize);
2767
- const view = new DataView(buffer);
2768
- writeString(view, 0, "RIFF");
2769
- view.setUint32(4, totalSize - 8, true);
2770
- writeString(view, 8, "WAVE");
2771
- writeString(view, 12, "fmt ");
2772
- view.setUint32(16, 16, true);
2773
- view.setUint16(20, bitDepth === 32 ? 3 : 1, true);
2774
- view.setUint16(22, numChannels, true);
2775
- view.setUint32(24, sampleRate, true);
2776
- view.setUint32(28, byteRate, true);
2777
- view.setUint16(32, blockAlign, true);
2778
- view.setUint16(34, bitDepth, true);
2779
- writeString(view, 36, "data");
2780
- view.setUint32(40, dataSize, true);
2781
- const channelData = [];
2782
- for (let ch = 0; ch < numChannels; ch++) {
2783
- channelData.push(audioBuffer.getChannelData(ch));
2784
- }
2785
- let offset = headerSize;
2786
- if (bitDepth === 16) {
2787
- for (let i = 0; i < numSamples; i++) {
2788
- for (let ch = 0; ch < numChannels; ch++) {
2789
- const sample = channelData[ch][i];
2790
- const clampedSample = Math.max(-1, Math.min(1, sample));
2791
- const intSample = clampedSample < 0 ? clampedSample * 32768 : clampedSample * 32767;
2792
- view.setInt16(offset, intSample, true);
2793
- offset += 2;
2794
- }
2795
- }
2796
- } else {
2797
- for (let i = 0; i < numSamples; i++) {
2798
- for (let ch = 0; ch < numChannels; ch++) {
2799
- view.setFloat32(offset, channelData[ch][i], true);
2800
- offset += 4;
2801
- }
2802
- }
2803
- }
2804
- return new Blob([buffer], { type: "audio/wav" });
2805
- }
2806
- function writeString(view, offset, str) {
2807
- for (let i = 0; i < str.length; i++) {
2808
- view.setUint8(offset + i, str.charCodeAt(i));
2809
- }
2810
- }
2811
- function downloadBlob(blob, filename) {
2812
- const url = URL.createObjectURL(blob);
2813
- const a = document.createElement("a");
2814
- a.href = url;
2815
- a.download = filename;
2816
- a.style.display = "none";
2817
- document.body.appendChild(a);
2818
- a.click();
2819
- document.body.removeChild(a);
2820
- URL.revokeObjectURL(url);
2821
- }
2822
-
2823
- // src/hooks/useExportWav.ts
2824
- function useExportWav() {
2825
- const [isExporting, setIsExporting] = (0, import_react19.useState)(false);
2826
- const [progress, setProgress] = (0, import_react19.useState)(0);
2827
- const [error, setError] = (0, import_react19.useState)(null);
2828
- const exportWav = (0, import_react19.useCallback)(
2829
- (_0, _1, ..._2) => __async(null, [_0, _1, ..._2], function* (tracks, trackStates, options = {}) {
2830
- const {
2831
- filename = "export",
2832
- mode = "master",
2833
- trackIndex,
2834
- autoDownload = true,
2835
- applyEffects = true,
2836
- effectsFunction,
2837
- createOfflineTrackEffects,
2838
- bitDepth = 16,
2839
- onProgress
2840
- } = options;
2841
- setIsExporting(true);
2842
- setProgress(0);
2843
- setError(null);
2844
- try {
2845
- if (tracks.length === 0) {
2846
- throw new Error("No tracks to export");
2847
- }
2848
- if (mode === "individual" && (trackIndex === void 0 || trackIndex < 0 || trackIndex >= tracks.length)) {
2849
- throw new Error("Invalid track index for individual export");
2850
- }
2851
- const sampleRate = (0, import_playout3.getGlobalAudioContext)().sampleRate;
2852
- let totalDurationSamples = 0;
2853
- for (const track of tracks) {
2854
- for (const clip of track.clips) {
2855
- const clipEndSample = clip.startSample + clip.durationSamples;
2856
- totalDurationSamples = Math.max(totalDurationSamples, clipEndSample);
2857
- }
2858
- }
2859
- totalDurationSamples += Math.round(sampleRate * 0.1);
2860
- const duration = totalDurationSamples / sampleRate;
2861
- const tracksToRender = mode === "individual" ? [{ track: tracks[trackIndex], state: trackStates[trackIndex], index: trackIndex }] : tracks.map((track, index) => ({ track, state: trackStates[index], index }));
2862
- const hasSolo = mode === "master" && trackStates.some((state) => state.soloed);
2863
- const reportProgress = (p) => {
2864
- setProgress(p);
2865
- onProgress == null ? void 0 : onProgress(p);
2866
- };
2867
- const renderedBuffer = yield renderOffline(
2868
- tracksToRender,
2869
- hasSolo,
2870
- duration,
2871
- sampleRate,
2872
- applyEffects,
2873
- effectsFunction,
2874
- createOfflineTrackEffects,
2875
- reportProgress
2876
- );
2877
- reportProgress(0.9);
2878
- const blob = encodeWav(renderedBuffer, { bitDepth });
2879
- reportProgress(1);
2880
- if (autoDownload) {
2881
- const exportFilename = mode === "individual" ? `${filename}_${tracks[trackIndex].name}` : filename;
2882
- downloadBlob(blob, `${exportFilename}.wav`);
2883
- }
2884
- return {
2885
- audioBuffer: renderedBuffer,
2886
- blob,
2887
- duration
2888
- };
2889
- } catch (err) {
2890
- const message = err instanceof Error ? err.message : "Export failed";
2891
- setError(message);
2892
- throw err;
2893
- } finally {
2894
- setIsExporting(false);
2895
- }
2896
- }),
2897
- []
2898
- );
2899
- return {
2900
- exportWav,
2901
- isExporting,
2902
- progress,
2903
- error
2904
- };
2905
- }
2906
- function renderOffline(tracksToRender, hasSolo, duration, sampleRate, applyEffects, effectsFunction, createOfflineTrackEffects, onProgress) {
2907
- return __async(this, null, function* () {
2908
- const { Offline, Volume: Volume2, Gain, Panner, Player, ToneAudioBuffer } = yield import("tone");
2909
- onProgress(0.1);
2910
- const audibleTracks = tracksToRender.filter(({ state }) => {
2911
- if (state.muted && !state.soloed) return false;
2912
- if (hasSolo && !state.soloed) return false;
2913
- return true;
2914
- });
2915
- const outputChannels = audibleTracks.reduce(
2916
- (max, { track }) => Math.max(max, (0, import_core4.trackChannelCount)(track)),
2917
- 1
2918
- );
2919
- let buffer;
2920
- try {
2921
- buffer = yield Offline(
2922
- (_0) => __async(null, [_0], function* ({ transport, destination }) {
2923
- const masterVolume = new Volume2(0);
2924
- if (effectsFunction && applyEffects) {
2925
- effectsFunction(masterVolume, destination, true);
2926
- } else {
2927
- masterVolume.connect(destination);
2928
- }
2929
- for (const { track, state } of audibleTracks) {
2930
- const trackVolume = new Volume2((0, import_core4.gainToDb)(state.volume));
2931
- const trackPan = new Panner({ pan: state.pan, channelCount: (0, import_core4.trackChannelCount)(track) });
2932
- const trackMute = new Gain(state.muted ? 0 : 1);
2933
- const trackEffects = createOfflineTrackEffects == null ? void 0 : createOfflineTrackEffects(track.id);
2934
- if (trackEffects && applyEffects) {
2935
- trackEffects(trackMute, masterVolume, true);
2936
- } else {
2937
- trackMute.connect(masterVolume);
2938
- }
2939
- trackPan.connect(trackMute);
2940
- trackVolume.connect(trackPan);
2941
- for (const clip of track.clips) {
2942
- const {
2943
- audioBuffer,
2944
- startSample,
2945
- durationSamples,
2946
- offsetSamples,
2947
- gain: clipGain,
2948
- fadeIn,
2949
- fadeOut
2950
- } = clip;
2951
- if (!audioBuffer) {
2952
- console.warn(
2953
- '[waveform-playlist] Skipping clip "' + (clip.name || clip.id) + '" - no audioBuffer for export'
2954
- );
2955
- continue;
2956
- }
2957
- const startTime = startSample / sampleRate;
2958
- const clipDuration = durationSamples / sampleRate;
2959
- const offset = offsetSamples / sampleRate;
2960
- const toneBuffer = new ToneAudioBuffer(audioBuffer);
2961
- const player = new Player(toneBuffer);
2962
- const fadeGain = new Gain(clipGain);
2963
- player.connect(fadeGain);
2964
- fadeGain.connect(trackVolume);
2965
- if (applyEffects) {
2966
- const audioParam = (0, import_playout3.getUnderlyingAudioParam)(fadeGain.gain);
2967
- if (audioParam) {
2968
- applyClipFades(audioParam, clipGain, startTime, clipDuration, fadeIn, fadeOut);
2969
- } else if (fadeIn || fadeOut) {
2970
- console.warn(
2971
- '[waveform-playlist] Cannot apply fades for clip "' + (clip.name || clip.id) + '" - AudioParam not accessible'
2972
- );
2973
- }
2974
- }
2975
- player.start(startTime, offset, clipDuration);
2976
- }
2977
- }
2978
- transport.start(0);
2979
- }),
2980
- duration,
2981
- outputChannels,
2982
- sampleRate
2983
- );
2984
- } catch (err) {
2985
- if (err instanceof Error) {
2986
- throw err;
2987
- } else {
2988
- throw new Error("Tone.Offline rendering failed: " + String(err));
2989
- }
2990
- }
2991
- onProgress(0.9);
2992
- const result = buffer.get();
2993
- if (!result) {
2994
- throw new Error("Offline rendering produced no audio buffer");
2995
- }
2996
- return result;
1442
+ );
1443
+ useKeyboardShortcuts({
1444
+ shortcuts: activeAnnotationShortcuts,
1445
+ enabled: enabled && activeIndex >= 0
2997
1446
  });
2998
- }
2999
- function applyClipFades(gainParam, clipGain, startTime, clipDuration, fadeIn, fadeOut) {
3000
- if (fadeIn) {
3001
- gainParam.setValueAtTime(0, startTime);
3002
- } else {
3003
- gainParam.setValueAtTime(clipGain, startTime);
3004
- }
3005
- if (fadeIn) {
3006
- (0, import_core4.applyFadeIn)(gainParam, startTime, fadeIn.duration, fadeIn.type || "linear", 0, clipGain);
3007
- }
3008
- if (fadeOut) {
3009
- const fadeOutStart = startTime + clipDuration - fadeOut.duration;
3010
- if (!fadeIn || fadeIn.duration < clipDuration - fadeOut.duration) {
3011
- gainParam.setValueAtTime(clipGain, fadeOutStart);
3012
- }
3013
- (0, import_core4.applyFadeOut)(gainParam, fadeOutStart, fadeOut.duration, fadeOut.type || "linear", clipGain, 0);
3014
- }
1447
+ useKeyboardShortcuts({
1448
+ shortcuts: navigationShortcuts,
1449
+ enabled: enabled && annotations.length > 0 && !!onActiveAnnotationChange
1450
+ });
1451
+ return {
1452
+ moveStartBoundary,
1453
+ moveEndBoundary,
1454
+ selectPrevious,
1455
+ selectNext,
1456
+ selectFirst,
1457
+ selectLast,
1458
+ clearSelection,
1459
+ scrollToAnnotation,
1460
+ playActiveAnnotation
1461
+ };
3015
1462
  }
3016
1463
 
3017
1464
  // src/hooks/useAnimationFrameLoop.ts
3018
- var import_react20 = require("react");
1465
+ var import_react15 = require("react");
3019
1466
  var useAnimationFrameLoop = () => {
3020
- const animationFrameRef = (0, import_react20.useRef)(null);
3021
- const stopAnimationFrameLoop = (0, import_react20.useCallback)(() => {
1467
+ const animationFrameRef = (0, import_react15.useRef)(null);
1468
+ const stopAnimationFrameLoop = (0, import_react15.useCallback)(() => {
3022
1469
  if (animationFrameRef.current !== null) {
3023
1470
  cancelAnimationFrame(animationFrameRef.current);
3024
1471
  animationFrameRef.current = null;
3025
1472
  }
3026
1473
  }, []);
3027
- const startAnimationFrameLoop = (0, import_react20.useCallback)(
1474
+ const startAnimationFrameLoop = (0, import_react15.useCallback)(
3028
1475
  (callback) => {
3029
1476
  stopAnimationFrameLoop();
3030
1477
  animationFrameRef.current = requestAnimationFrame(callback);
3031
1478
  },
3032
1479
  [stopAnimationFrameLoop]
3033
1480
  );
3034
- (0, import_react20.useEffect)(() => {
1481
+ (0, import_react15.useEffect)(() => {
3035
1482
  return () => {
3036
1483
  stopAnimationFrameLoop();
3037
1484
  };
@@ -3044,7 +1491,7 @@ var useAnimationFrameLoop = () => {
3044
1491
  };
3045
1492
 
3046
1493
  // src/hooks/useWaveformDataCache.ts
3047
- var import_react21 = require("react");
1494
+ var import_react16 = require("react");
3048
1495
 
3049
1496
  // src/workers/peaksWorker.ts
3050
1497
  var import_waveform_data2 = __toESM(require("waveform-data"));
@@ -3276,20 +1723,20 @@ function createPeaksWorker() {
3276
1723
 
3277
1724
  // src/hooks/useWaveformDataCache.ts
3278
1725
  function useWaveformDataCache(tracks, baseScale) {
3279
- const [cache, setCache] = (0, import_react21.useState)(() => /* @__PURE__ */ new Map());
3280
- const [isGenerating, setIsGenerating] = (0, import_react21.useState)(false);
3281
- const workerRef = (0, import_react21.useRef)(null);
3282
- const generatedByBufferRef = (0, import_react21.useRef)(/* @__PURE__ */ new WeakMap());
3283
- const inflightByBufferRef = (0, import_react21.useRef)(/* @__PURE__ */ new WeakMap());
3284
- const subscribersByBufferRef = (0, import_react21.useRef)(/* @__PURE__ */ new WeakMap());
3285
- const pendingCountRef = (0, import_react21.useRef)(0);
3286
- const getWorker = (0, import_react21.useCallback)(() => {
1726
+ const [cache, setCache] = (0, import_react16.useState)(() => /* @__PURE__ */ new Map());
1727
+ const [isGenerating, setIsGenerating] = (0, import_react16.useState)(false);
1728
+ const workerRef = (0, import_react16.useRef)(null);
1729
+ const generatedByBufferRef = (0, import_react16.useRef)(/* @__PURE__ */ new WeakMap());
1730
+ const inflightByBufferRef = (0, import_react16.useRef)(/* @__PURE__ */ new WeakMap());
1731
+ const subscribersByBufferRef = (0, import_react16.useRef)(/* @__PURE__ */ new WeakMap());
1732
+ const pendingCountRef = (0, import_react16.useRef)(0);
1733
+ const getWorker = (0, import_react16.useCallback)(() => {
3287
1734
  if (!workerRef.current) {
3288
1735
  workerRef.current = createPeaksWorker();
3289
1736
  }
3290
1737
  return workerRef.current;
3291
1738
  }, []);
3292
- (0, import_react21.useEffect)(() => {
1739
+ (0, import_react16.useEffect)(() => {
3293
1740
  let cancelled = false;
3294
1741
  const generatedByBuffer = generatedByBufferRef.current;
3295
1742
  const inflightByBuffer = inflightByBufferRef.current;
@@ -3394,7 +1841,7 @@ function useWaveformDataCache(tracks, baseScale) {
3394
1841
  setIsGenerating(false);
3395
1842
  };
3396
1843
  }, [tracks, baseScale, getWorker]);
3397
- (0, import_react21.useEffect)(() => {
1844
+ (0, import_react16.useEffect)(() => {
3398
1845
  return () => {
3399
1846
  var _a;
3400
1847
  (_a = workerRef.current) == null ? void 0 : _a.terminate();
@@ -3404,232 +1851,12 @@ function useWaveformDataCache(tracks, baseScale) {
3404
1851
  return { cache, isGenerating };
3405
1852
  }
3406
1853
 
3407
- // src/hooks/useDynamicTracks.ts
3408
- var import_react22 = require("react");
3409
- var import_core5 = require("@waveform-playlist/core");
3410
- var import_playout4 = require("@waveform-playlist/playout");
3411
- function getSourceName(source) {
3412
- var _a, _b, _c, _d, _e;
3413
- if (source instanceof File) {
3414
- return source.name.replace(/\.[^/.]+$/, "");
3415
- }
3416
- if (source instanceof Blob) {
3417
- return "Untitled";
3418
- }
3419
- if (typeof source === "string") {
3420
- return (_b = (_a = source.split("/").pop()) == null ? void 0 : _a.replace(/\.[^/.]+$/, "")) != null ? _b : "Untitled";
3421
- }
3422
- return (_e = (_d = source.name) != null ? _d : (_c = source.src.split("/").pop()) == null ? void 0 : _c.replace(/\.[^/.]+$/, "")) != null ? _e : "Untitled";
3423
- }
3424
- function decodeSource(source, audioContext, signal) {
3425
- return __async(this, null, function* () {
3426
- const name = getSourceName(source);
3427
- if (source instanceof Blob) {
3428
- const arrayBuffer2 = yield source.arrayBuffer();
3429
- signal == null ? void 0 : signal.throwIfAborted();
3430
- const audioBuffer2 = yield audioContext.decodeAudioData(arrayBuffer2);
3431
- return { audioBuffer: audioBuffer2, name };
3432
- }
3433
- const url = typeof source === "string" ? source : source.src;
3434
- const response = yield fetch(url, { signal });
3435
- if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
3436
- const arrayBuffer = yield response.arrayBuffer();
3437
- signal == null ? void 0 : signal.throwIfAborted();
3438
- const audioBuffer = yield audioContext.decodeAudioData(arrayBuffer);
3439
- return { audioBuffer, name };
3440
- });
3441
- }
3442
- function useDynamicTracks() {
3443
- const [tracks, setTracks] = (0, import_react22.useState)([]);
3444
- const [loadingCount, setLoadingCount] = (0, import_react22.useState)(0);
3445
- const [errors, setErrors] = (0, import_react22.useState)([]);
3446
- const cancelledRef = (0, import_react22.useRef)(false);
3447
- const loadingIdsRef = (0, import_react22.useRef)(/* @__PURE__ */ new Set());
3448
- const abortControllersRef = (0, import_react22.useRef)(/* @__PURE__ */ new Map());
3449
- (0, import_react22.useEffect)(() => {
3450
- const controllers = abortControllersRef.current;
3451
- return () => {
3452
- cancelledRef.current = true;
3453
- for (const controller of controllers.values()) {
3454
- controller.abort();
3455
- }
3456
- controllers.clear();
3457
- };
3458
- }, []);
3459
- const addTracks = (0, import_react22.useCallback)((sources) => {
3460
- if (sources.length === 0) return;
3461
- const audioContext = (0, import_playout4.getGlobalAudioContext)();
3462
- const placeholders = sources.map((source) => ({
3463
- track: (0, import_core5.createTrack)({ name: `${getSourceName(source)} (loading...)`, clips: [] }),
3464
- source
3465
- }));
3466
- setTracks((prev) => [...prev, ...placeholders.map((p) => p.track)]);
3467
- setLoadingCount((prev) => prev + sources.length);
3468
- for (const { track, source } of placeholders) {
3469
- loadingIdsRef.current.add(track.id);
3470
- const controller = new AbortController();
3471
- abortControllersRef.current.set(track.id, controller);
3472
- (() => __async(null, null, function* () {
3473
- try {
3474
- const { audioBuffer, name } = yield decodeSource(source, audioContext, controller.signal);
3475
- const clip = (0, import_core5.createClipFromSeconds)({
3476
- audioBuffer,
3477
- startTime: 0,
3478
- duration: audioBuffer.duration,
3479
- offset: 0,
3480
- name
3481
- });
3482
- if (!cancelledRef.current && loadingIdsRef.current.has(track.id)) {
3483
- setTracks(
3484
- (prev) => prev.map((t) => t.id === track.id ? __spreadProps(__spreadValues({}, t), { name, clips: [clip] }) : t)
3485
- );
3486
- }
3487
- } catch (error) {
3488
- if (error instanceof DOMException && error.name === "AbortError") return;
3489
- console.warn("[waveform-playlist] Error loading audio:", error);
3490
- if (!cancelledRef.current && loadingIdsRef.current.has(track.id)) {
3491
- setTracks((prev) => prev.filter((t) => t.id !== track.id));
3492
- setErrors((prev) => [
3493
- ...prev,
3494
- {
3495
- name: getSourceName(source),
3496
- error: error instanceof Error ? error : new Error(String(error))
3497
- }
3498
- ]);
3499
- }
3500
- } finally {
3501
- abortControllersRef.current.delete(track.id);
3502
- if (!cancelledRef.current && loadingIdsRef.current.delete(track.id)) {
3503
- setLoadingCount((prev) => prev - 1);
3504
- }
3505
- }
3506
- }))();
3507
- }
3508
- }, []);
3509
- const removeTrack = (0, import_react22.useCallback)((trackId) => {
3510
- setTracks((prev) => prev.filter((t) => t.id !== trackId));
3511
- const controller = abortControllersRef.current.get(trackId);
3512
- if (controller) {
3513
- controller.abort();
3514
- abortControllersRef.current.delete(trackId);
3515
- }
3516
- if (loadingIdsRef.current.delete(trackId)) {
3517
- setLoadingCount((prev) => prev - 1);
3518
- }
3519
- }, []);
3520
- return {
3521
- tracks,
3522
- addTracks,
3523
- removeTrack,
3524
- loadingCount,
3525
- isLoading: loadingCount > 0,
3526
- errors
3527
- };
3528
- }
3529
-
3530
- // src/hooks/useOutputMeter.ts
3531
- var import_react23 = require("react");
3532
- var import_playout5 = require("@waveform-playlist/playout");
3533
- var import_core6 = require("@waveform-playlist/core");
3534
- var import_worklets = require("@waveform-playlist/worklets");
3535
- var PEAK_DECAY = 0.98;
3536
- function useOutputMeter(options = {}) {
3537
- const { channelCount = 2, updateRate = 60, isPlaying = false } = options;
3538
- const [levels, setLevels] = (0, import_react23.useState)(() => new Array(channelCount).fill(0));
3539
- const [peakLevels, setPeakLevels] = (0, import_react23.useState)(() => new Array(channelCount).fill(0));
3540
- const [rmsLevels, setRmsLevels] = (0, import_react23.useState)(() => new Array(channelCount).fill(0));
3541
- const workletNodeRef = (0, import_react23.useRef)(null);
3542
- const smoothedPeakRef = (0, import_react23.useRef)(new Array(channelCount).fill(0));
3543
- const [meterError, setMeterError] = (0, import_react23.useState)(null);
3544
- const resetPeak = (0, import_react23.useCallback)(
3545
- () => setPeakLevels(new Array(channelCount).fill(0)),
3546
- [channelCount]
3547
- );
3548
- (0, import_react23.useEffect)(() => {
3549
- if (!isPlaying) {
3550
- const zeros = new Array(channelCount).fill(0);
3551
- smoothedPeakRef.current = new Array(channelCount).fill(0);
3552
- setLevels(zeros);
3553
- setRmsLevels(zeros);
3554
- setPeakLevels(zeros);
3555
- }
3556
- }, [isPlaying, channelCount]);
3557
- (0, import_react23.useEffect)(() => {
3558
- let isMounted = true;
3559
- const setup = () => __async(null, null, function* () {
3560
- const context = (0, import_playout5.getGlobalContext)();
3561
- const rawCtx = context.rawContext;
3562
- yield rawCtx.audioWorklet.addModule(import_worklets.meterProcessorUrl);
3563
- if (!isMounted) return;
3564
- const workletNode = context.createAudioWorkletNode("meter-processor", {
3565
- channelCount,
3566
- channelCountMode: "explicit",
3567
- processorOptions: {
3568
- numberOfChannels: channelCount,
3569
- updateRate
3570
- }
3571
- });
3572
- workletNodeRef.current = workletNode;
3573
- workletNode.onprocessorerror = (event) => {
3574
- console.warn("[waveform-playlist] Output meter worklet processor error:", String(event));
3575
- };
3576
- const destination = context.destination;
3577
- destination.chain(workletNode);
3578
- smoothedPeakRef.current = new Array(channelCount).fill(0);
3579
- workletNode.port.onmessage = (event) => {
3580
- var _a;
3581
- if (!isMounted) return;
3582
- const { peak, rms } = event.data;
3583
- const smoothed = smoothedPeakRef.current;
3584
- const peakValues = [];
3585
- const rmsValues = [];
3586
- for (let ch = 0; ch < peak.length; ch++) {
3587
- smoothed[ch] = Math.max(peak[ch], ((_a = smoothed[ch]) != null ? _a : 0) * PEAK_DECAY);
3588
- peakValues.push((0, import_core6.gainToNormalized)(smoothed[ch]));
3589
- rmsValues.push((0, import_core6.gainToNormalized)(rms[ch]));
3590
- }
3591
- setLevels(peakValues);
3592
- setRmsLevels(rmsValues);
3593
- setPeakLevels((prev) => peakValues.map((val, i) => {
3594
- var _a2;
3595
- return Math.max((_a2 = prev[i]) != null ? _a2 : 0, val);
3596
- }));
3597
- };
3598
- });
3599
- setup().catch((err) => {
3600
- console.warn("[waveform-playlist] Failed to set up output meter:", String(err));
3601
- if (isMounted) {
3602
- setMeterError(err instanceof Error ? err : new Error(String(err)));
3603
- }
3604
- });
3605
- return () => {
3606
- isMounted = false;
3607
- if (workletNodeRef.current) {
3608
- try {
3609
- const context = (0, import_playout5.getGlobalContext)();
3610
- context.destination.chain();
3611
- } catch (err) {
3612
- console.warn("[waveform-playlist] Failed to restore destination chain:", String(err));
3613
- }
3614
- try {
3615
- workletNodeRef.current.disconnect();
3616
- workletNodeRef.current.port.close();
3617
- } catch (err) {
3618
- console.warn("[waveform-playlist] Output meter disconnect during cleanup:", String(err));
3619
- }
3620
- workletNodeRef.current = null;
3621
- }
3622
- };
3623
- }, [channelCount, updateRate]);
3624
- return { levels, peakLevels, rmsLevels, resetPeak, error: meterError };
3625
- }
3626
-
3627
1854
  // src/WaveformPlaylistContext.tsx
3628
1855
  var import_jsx_runtime = require("react/jsx-runtime");
3629
- var PlaybackAnimationContext = (0, import_react24.createContext)(null);
3630
- var PlaylistStateContext = (0, import_react24.createContext)(null);
3631
- var PlaylistControlsContext = (0, import_react24.createContext)(null);
3632
- var PlaylistDataContext = (0, import_react24.createContext)(null);
1856
+ var PlaybackAnimationContext = (0, import_react17.createContext)(null);
1857
+ var PlaylistStateContext = (0, import_react17.createContext)(null);
1858
+ var PlaylistControlsContext = (0, import_react17.createContext)(null);
1859
+ var PlaylistDataContext = (0, import_react17.createContext)(null);
3633
1860
  var WaveformPlaylistProvider = ({
3634
1861
  tracks,
3635
1862
  timescale = false,
@@ -3643,6 +1870,7 @@ var WaveformPlaylistProvider = ({
3643
1870
  annotationList,
3644
1871
  effects,
3645
1872
  onReady,
1873
+ onError,
3646
1874
  onAnnotationUpdate: _onAnnotationUpdate,
3647
1875
  onAnnotationsChange,
3648
1876
  barWidth = 1,
@@ -3653,18 +1881,19 @@ var WaveformPlaylistProvider = ({
3653
1881
  deferEngineRebuild = false,
3654
1882
  indefinitePlayback = false,
3655
1883
  sampleRate: sampleRateProp,
1884
+ createAdapter,
3656
1885
  children
3657
1886
  }) => {
3658
1887
  var _a, _b, _c, _d;
3659
1888
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
3660
- const indefinitePlaybackRef = (0, import_react24.useRef)(indefinitePlayback);
1889
+ const indefinitePlaybackRef = (0, import_react17.useRef)(indefinitePlayback);
3661
1890
  indefinitePlaybackRef.current = indefinitePlayback;
3662
- const stableZoomLevels = (0, import_react24.useMemo)(
1891
+ const stableZoomLevels = (0, import_react17.useMemo)(
3663
1892
  () => zoomLevels,
3664
1893
  // eslint-disable-next-line react-hooks/exhaustive-deps
3665
1894
  [zoomLevels == null ? void 0 : zoomLevels.join(",")]
3666
1895
  );
3667
- const annotations = (0, import_react24.useMemo)(() => {
1896
+ const annotations = (0, import_react17.useMemo)(() => {
3668
1897
  if (!(annotationList == null ? void 0 : annotationList.annotations)) return [];
3669
1898
  if (process.env.NODE_ENV !== "production" && annotationList.annotations.length > 0) {
3670
1899
  const first = annotationList.annotations[0];
@@ -3677,63 +1906,50 @@ var WaveformPlaylistProvider = ({
3677
1906
  }
3678
1907
  return annotationList.annotations;
3679
1908
  }, [annotationList == null ? void 0 : annotationList.annotations]);
3680
- const annotationsRef = (0, import_react24.useRef)(annotations);
1909
+ const annotationsRef = (0, import_react17.useRef)(annotations);
3681
1910
  annotationsRef.current = annotations;
3682
- const [activeAnnotationId, setActiveAnnotationIdState] = (0, import_react24.useState)(null);
3683
- const [isPlaying, setIsPlaying] = (0, import_react24.useState)(false);
3684
- const [currentTime, setCurrentTime] = (0, import_react24.useState)(0);
3685
- const [duration, setDuration] = (0, import_react24.useState)(0);
3686
- const [audioBuffers, setAudioBuffers] = (0, import_react24.useState)([]);
3687
- const [peaksDataArray, setPeaksDataArray] = (0, import_react24.useState)([]);
3688
- const [trackStates, setTrackStates] = (0, import_react24.useState)([]);
3689
- const [isAutomaticScroll, setIsAutomaticScroll] = (0, import_react24.useState)(automaticScroll);
3690
- const [continuousPlay, setContinuousPlayState] = (0, import_react24.useState)(
1911
+ const [activeAnnotationId, setActiveAnnotationIdState] = (0, import_react17.useState)(null);
1912
+ const [isPlaying, setIsPlaying] = (0, import_react17.useState)(false);
1913
+ const [currentTime, setCurrentTime] = (0, import_react17.useState)(0);
1914
+ const [duration, setDuration] = (0, import_react17.useState)(0);
1915
+ const [audioBuffers, setAudioBuffers] = (0, import_react17.useState)([]);
1916
+ const [peaksDataArray, setPeaksDataArray] = (0, import_react17.useState)([]);
1917
+ const [trackStates, setTrackStates] = (0, import_react17.useState)([]);
1918
+ const [isAutomaticScroll, setIsAutomaticScroll] = (0, import_react17.useState)(automaticScroll);
1919
+ const [continuousPlay, setContinuousPlayState] = (0, import_react17.useState)(
3691
1920
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
3692
1921
  );
3693
- const [linkEndpoints, setLinkEndpoints] = (0, import_react24.useState)((_b = annotationList == null ? void 0 : annotationList.linkEndpoints) != null ? _b : false);
3694
- const [annotationsEditable, setAnnotationsEditable] = (0, import_react24.useState)((_c = annotationList == null ? void 0 : annotationList.editable) != null ? _c : false);
3695
- const [isReady, setIsReady] = (0, import_react24.useState)(false);
3696
- const engineRef = (0, import_react24.useRef)(null);
3697
- const adapterRef = (0, import_react24.useRef)(null);
3698
- const audioInitializedRef = (0, import_react24.useRef)(false);
3699
- const isPlayingRef = (0, import_react24.useRef)(false);
1922
+ const [linkEndpoints, setLinkEndpoints] = (0, import_react17.useState)((_b = annotationList == null ? void 0 : annotationList.linkEndpoints) != null ? _b : false);
1923
+ const [annotationsEditable, setAnnotationsEditable] = (0, import_react17.useState)((_c = annotationList == null ? void 0 : annotationList.editable) != null ? _c : false);
1924
+ const [isReady, setIsReady] = (0, import_react17.useState)(false);
1925
+ const engineRef = (0, import_react17.useRef)(null);
1926
+ const adapterRef = (0, import_react17.useRef)(null);
1927
+ const audioInitializedRef = (0, import_react17.useRef)(false);
1928
+ const isPlayingRef = (0, import_react17.useRef)(false);
3700
1929
  isPlayingRef.current = isPlaying;
3701
- const playStartPositionRef = (0, import_react24.useRef)(0);
3702
- const currentTimeRef = (0, import_react24.useRef)(0);
3703
- const visualTimeRef = (0, import_react24.useRef)(0);
3704
- const tracksRef = (0, import_react24.useRef)(tracks);
3705
- const soundFontCacheRef = (0, import_react24.useRef)(soundFontCache);
1930
+ const playStartPositionRef = (0, import_react17.useRef)(0);
1931
+ const currentTimeRef = (0, import_react17.useRef)(0);
1932
+ const visualTimeRef = (0, import_react17.useRef)(0);
1933
+ const tracksRef = (0, import_react17.useRef)(tracks);
1934
+ const soundFontCacheRef = (0, import_react17.useRef)(soundFontCache);
3706
1935
  soundFontCacheRef.current = soundFontCache;
3707
- const trackStatesRef = (0, import_react24.useRef)(trackStates);
3708
- const playbackStartTimeRef = (0, import_react24.useRef)(0);
3709
- const audioStartPositionRef = (0, import_react24.useRef)(0);
3710
- const playbackEndTimeRef = (0, import_react24.useRef)(null);
3711
- const scrollContainerRef = (0, import_react24.useRef)(null);
3712
- const isAutomaticScrollRef = (0, import_react24.useRef)(false);
3713
- const frameCallbacksRef = (0, import_react24.useRef)(/* @__PURE__ */ new Map());
3714
- const continuousPlayRef = (0, import_react24.useRef)((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
3715
- const activeAnnotationIdRef = (0, import_react24.useRef)(null);
3716
- const engineTracksRef = (0, import_react24.useRef)(null);
3717
- const lastTracksVersionRef = (0, import_react24.useRef)(0);
3718
- const skipEngineDisposeRef = (0, import_react24.useRef)(false);
3719
- const isDraggingRef = (0, import_react24.useRef)(false);
3720
- const prevTracksRef = (0, import_react24.useRef)([]);
3721
- const samplesPerPixelRef = (0, import_react24.useRef)(initialSamplesPerPixel);
3722
- const [initialSampleRate] = (0, import_react24.useState)(() => {
3723
- if (typeof AudioContext === "undefined") return sampleRateProp != null ? sampleRateProp : 48e3;
3724
- try {
3725
- if (sampleRateProp !== void 0) {
3726
- return (0, import_playout6.configureGlobalContext)({ sampleRate: sampleRateProp });
3727
- }
3728
- return (0, import_playout6.getGlobalAudioContext)().sampleRate;
3729
- } catch (err) {
3730
- console.warn(
3731
- "[waveform-playlist] Failed to configure AudioContext: " + String(err) + " \u2014 falling back to " + (sampleRateProp != null ? sampleRateProp : 48e3) + " Hz"
3732
- );
3733
- return sampleRateProp != null ? sampleRateProp : 48e3;
3734
- }
3735
- });
3736
- const sampleRateRef = (0, import_react24.useRef)(initialSampleRate);
1936
+ const trackStatesRef = (0, import_react17.useRef)(trackStates);
1937
+ const playbackStartTimeRef = (0, import_react17.useRef)(0);
1938
+ const audioStartPositionRef = (0, import_react17.useRef)(0);
1939
+ const playbackEndTimeRef = (0, import_react17.useRef)(null);
1940
+ const scrollContainerRef = (0, import_react17.useRef)(null);
1941
+ const isAutomaticScrollRef = (0, import_react17.useRef)(false);
1942
+ const frameCallbacksRef = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
1943
+ const continuousPlayRef = (0, import_react17.useRef)((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
1944
+ const activeAnnotationIdRef = (0, import_react17.useRef)(null);
1945
+ const engineTracksRef = (0, import_react17.useRef)(null);
1946
+ const lastTracksVersionRef = (0, import_react17.useRef)(0);
1947
+ const skipEngineDisposeRef = (0, import_react17.useRef)(false);
1948
+ const isDraggingRef = (0, import_react17.useRef)(false);
1949
+ const prevTracksRef = (0, import_react17.useRef)([]);
1950
+ const samplesPerPixelRef = (0, import_react17.useRef)(initialSamplesPerPixel);
1951
+ const [initialSampleRate] = (0, import_react17.useState)(() => sampleRateProp != null ? sampleRateProp : 48e3);
1952
+ const sampleRateRef = (0, import_react17.useRef)(initialSampleRate);
3737
1953
  const { timeFormat, setTimeFormat, formatTime: formatTime2 } = useTimeFormat();
3738
1954
  const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });
3739
1955
  const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;
@@ -3778,20 +1994,20 @@ var WaveformPlaylistProvider = ({
3778
1994
  onEngineState: onUndoEngineState
3779
1995
  } = useUndoState({ engineRef });
3780
1996
  const { animationFrameRef, startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
3781
- const baseScale = (0, import_react24.useMemo)(
1997
+ const baseScale = (0, import_react17.useMemo)(
3782
1998
  () => Math.min(...stableZoomLevels != null ? stableZoomLevels : [256, 512, 1024, 2048, 4096, 8192]),
3783
1999
  [stableZoomLevels]
3784
2000
  );
3785
2001
  const { cache: waveformDataCache } = useWaveformDataCache(tracks, baseScale);
3786
- const setContinuousPlay = (0, import_react24.useCallback)((value) => {
2002
+ const setContinuousPlay = (0, import_react17.useCallback)((value) => {
3787
2003
  continuousPlayRef.current = value;
3788
2004
  setContinuousPlayState(value);
3789
2005
  }, []);
3790
- const setActiveAnnotationId = (0, import_react24.useCallback)((value) => {
2006
+ const setActiveAnnotationId = (0, import_react17.useCallback)((value) => {
3791
2007
  activeAnnotationIdRef.current = value;
3792
2008
  setActiveAnnotationIdState(value);
3793
2009
  }, []);
3794
- const setLoopRegionFromSelection = (0, import_react24.useCallback)(() => {
2010
+ const setLoopRegionFromSelection = (0, import_react17.useCallback)(() => {
3795
2011
  var _a2, _b2;
3796
2012
  const start = (_a2 = selectionStartRef.current) != null ? _a2 : 0;
3797
2013
  const end = (_b2 = selectionEndRef.current) != null ? _b2 : 0;
@@ -3799,10 +2015,10 @@ var WaveformPlaylistProvider = ({
3799
2015
  setLoopRegion(start, end);
3800
2016
  }
3801
2017
  }, [setLoopRegion, selectionStartRef, selectionEndRef]);
3802
- (0, import_react24.useEffect)(() => {
2018
+ (0, import_react17.useEffect)(() => {
3803
2019
  isAutomaticScrollRef.current = isAutomaticScroll;
3804
2020
  }, [isAutomaticScroll]);
3805
- (0, import_react24.useEffect)(() => {
2021
+ (0, import_react17.useEffect)(() => {
3806
2022
  trackStatesRef.current = trackStates;
3807
2023
  }, [trackStates]);
3808
2024
  tracksRef.current = tracks;
@@ -3813,7 +2029,7 @@ var WaveformPlaylistProvider = ({
3813
2029
  return current === pt;
3814
2030
  });
3815
2031
  skipEngineDisposeRef.current = isEngineTracks || isDraggingRef.current || isIncrementalAdd;
3816
- (0, import_react24.useEffect)(() => {
2032
+ (0, import_react17.useEffect)(() => {
3817
2033
  if (!scrollContainerRef.current || duration === 0) return;
3818
2034
  const container = scrollContainerRef.current;
3819
2035
  const oldSamplesPerPixel = samplesPerPixelRef.current;
@@ -3829,8 +2045,8 @@ var WaveformPlaylistProvider = ({
3829
2045
  container.scrollLeft = newScrollLeft;
3830
2046
  samplesPerPixelRef.current = newSamplesPerPixel;
3831
2047
  }, [samplesPerPixel, duration]);
3832
- const pendingResumeRef = (0, import_react24.useRef)(null);
3833
- (0, import_react24.useEffect)(() => {
2048
+ const pendingResumeRef = (0, import_react17.useRef)(null);
2049
+ (0, import_react17.useEffect)(() => {
3834
2050
  var _a2, _b2, _c2, _d2;
3835
2051
  if (isEngineTracks || isDraggingRef.current) {
3836
2052
  if (isEngineTracks) {
@@ -3935,6 +2151,7 @@ var WaveformPlaylistProvider = ({
3935
2151
  stopAnimationFrameLoop();
3936
2152
  pendingResumeRef.current = { position: resumePosition };
3937
2153
  }
2154
+ let cancelled = false;
3938
2155
  const loadAudio = () => __async(null, null, function* () {
3939
2156
  var _a3, _b3, _c3, _d3, _e;
3940
2157
  try {
@@ -3977,7 +2194,17 @@ var WaveformPlaylistProvider = ({
3977
2194
  lastTracksVersionRef.current = 0;
3978
2195
  engineTracksRef.current = null;
3979
2196
  audioInitializedRef.current = false;
3980
- const adapter = (0, import_playout6.createToneAdapter)({ effects, soundFontCache: soundFontCacheRef.current });
2197
+ const adapter = yield resolvePlayoutAdapter({
2198
+ createAdapter,
2199
+ effects,
2200
+ soundFontCache: soundFontCacheRef.current,
2201
+ sampleRate: sampleRateProp
2202
+ });
2203
+ if (cancelled) {
2204
+ adapter.dispose();
2205
+ return;
2206
+ }
2207
+ sampleRateRef.current = adapter.audioContext.sampleRate;
3981
2208
  adapterRef.current = adapter;
3982
2209
  const engine = new import_engine3.PlaylistEngine({
3983
2210
  adapter,
@@ -4035,11 +2262,23 @@ var WaveformPlaylistProvider = ({
4035
2262
  prevTracksRef.current = tracks;
4036
2263
  onReady == null ? void 0 : onReady();
4037
2264
  } catch (error) {
4038
- console.warn("[waveform-playlist] Error loading audio:", String(error));
2265
+ if (!engineRef.current && adapterRef.current) {
2266
+ try {
2267
+ adapterRef.current.dispose();
2268
+ } catch (disposeErr) {
2269
+ console.warn(
2270
+ "[waveform-playlist] adapter dispose after load failure threw: " + String(disposeErr)
2271
+ );
2272
+ }
2273
+ adapterRef.current = null;
2274
+ }
2275
+ console.warn("[waveform-playlist] Error loading audio: " + String(error));
2276
+ onError == null ? void 0 : onError(error instanceof Error ? error : new Error(String(error)));
4039
2277
  }
4040
2278
  });
4041
2279
  loadAudio();
4042
2280
  return () => {
2281
+ cancelled = true;
4043
2282
  if (skipEngineDisposeRef.current) {
4044
2283
  skipEngineDisposeRef.current = false;
4045
2284
  return;
@@ -4062,6 +2301,7 @@ var WaveformPlaylistProvider = ({
4062
2301
  // to the live adapter by the sync effect below; only adapter creation reads it
4063
2302
  // (via soundFontCacheRef).
4064
2303
  onReady,
2304
+ onError,
4065
2305
  effects,
4066
2306
  stopAnimationFrameLoop,
4067
2307
  onSelectionEngineState,
@@ -4080,10 +2320,10 @@ var WaveformPlaylistProvider = ({
4080
2320
  stableZoomLevels,
4081
2321
  deferEngineRebuild
4082
2322
  ]);
4083
- (0, import_react24.useEffect)(() => {
2323
+ (0, import_react17.useEffect)(() => {
4084
2324
  syncSoundFontCacheToAdapter(adapterRef.current, soundFontCache);
4085
2325
  }, [soundFontCache]);
4086
- (0, import_react24.useEffect)(() => {
2326
+ (0, import_react17.useEffect)(() => {
4087
2327
  if (tracks.length === 0) return;
4088
2328
  const allTrackPeaks = tracks.map((track) => {
4089
2329
  const clipPeaks = track.clips.map((clip) => {
@@ -4164,8 +2404,15 @@ var WaveformPlaylistProvider = ({
4164
2404
  });
4165
2405
  setPeaksDataArray(allTrackPeaks);
4166
2406
  }, [tracks, samplesPerPixel, mono, waveformDataCache, deferEngineRebuild]);
4167
- const getPlaybackTimeFallbackWarnedRef = (0, import_react24.useRef)(false);
4168
- const getPlaybackTime = (0, import_react24.useCallback)(() => {
2407
+ const getAudioContextTime = (0, import_react17.useCallback)(
2408
+ () => {
2409
+ var _a2, _b2;
2410
+ return (_b2 = (_a2 = adapterRef.current) == null ? void 0 : _a2.audioContext.currentTime) != null ? _b2 : 0;
2411
+ },
2412
+ []
2413
+ );
2414
+ const getPlaybackTimeFallbackWarnedRef = (0, import_react17.useRef)(false);
2415
+ const getPlaybackTime = (0, import_react17.useCallback)(() => {
4169
2416
  var _a2, _b2;
4170
2417
  if (engineRef.current) {
4171
2418
  return engineRef.current.getCurrentTime();
@@ -4176,30 +2423,35 @@ var WaveformPlaylistProvider = ({
4176
2423
  "[waveform-playlist] getPlaybackTime called without engine. Falling back to manual elapsed time (loop wrapping will not work)."
4177
2424
  );
4178
2425
  }
4179
- const elapsed = (0, import_tone4.getContext)().currentTime - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
2426
+ const elapsed = getAudioContextTime() - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
4180
2427
  return ((_b2 = audioStartPositionRef.current) != null ? _b2 : 0) + elapsed;
4181
- }, []);
4182
- const toVisualTime = (0, import_react24.useCallback)((rawTime) => {
2428
+ }, [getAudioContextTime]);
2429
+ const toVisualTime = (0, import_react17.useCallback)((rawTime) => {
4183
2430
  return Number.isFinite(rawTime) ? Math.max(0, rawTime) : 0;
4184
2431
  }, []);
4185
- const setCurrentTimeRefs = (0, import_react24.useCallback)(
2432
+ const setCurrentTimeRefs = (0, import_react17.useCallback)(
4186
2433
  (rawTime) => {
4187
2434
  currentTimeRef.current = rawTime;
4188
2435
  visualTimeRef.current = toVisualTime(rawTime);
4189
2436
  },
4190
2437
  [toVisualTime]
4191
2438
  );
4192
- const getLookAhead = (0, import_react24.useCallback)(() => {
2439
+ const getLookAhead = (0, import_react17.useCallback)(() => {
4193
2440
  var _a2, _b2;
4194
2441
  return (_b2 = (_a2 = engineRef.current) == null ? void 0 : _a2.lookAhead) != null ? _b2 : 0;
4195
2442
  }, []);
4196
- const registerFrameCallback = (0, import_react24.useCallback)((id, cb) => {
2443
+ const getOutputLatency = (0, import_react17.useCallback)(() => {
2444
+ var _a2;
2445
+ const audioCtx = (_a2 = adapterRef.current) == null ? void 0 : _a2.audioContext;
2446
+ return audioCtx && "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
2447
+ }, []);
2448
+ const registerFrameCallback = (0, import_react17.useCallback)((id, cb) => {
4197
2449
  frameCallbacksRef.current.set(id, cb);
4198
2450
  }, []);
4199
- const unregisterFrameCallback = (0, import_react24.useCallback)((id) => {
2451
+ const unregisterFrameCallback = (0, import_react17.useCallback)((id) => {
4200
2452
  frameCallbacksRef.current.delete(id);
4201
2453
  }, []);
4202
- const startAnimationLoop = (0, import_react24.useCallback)(() => {
2454
+ const startAnimationLoop = (0, import_react17.useCallback)(() => {
4203
2455
  const updateTime = () => {
4204
2456
  const time = getPlaybackTime();
4205
2457
  currentTimeRef.current = time;
@@ -4287,15 +2539,14 @@ var WaveformPlaylistProvider = ({
4287
2539
  setCurrentTimeRefs
4288
2540
  ]);
4289
2541
  const stopAnimationLoop = stopAnimationFrameLoop;
4290
- (0, import_react24.useEffect)(() => {
2542
+ (0, import_react17.useEffect)(() => {
4291
2543
  const reschedulePlayback = () => __async(null, null, function* () {
4292
2544
  if (isPlaying && animationFrameRef.current && engineRef.current) {
4293
2545
  if (continuousPlay) {
4294
2546
  const currentPos = currentTimeRef.current;
4295
2547
  engineRef.current.stop();
4296
2548
  stopAnimationLoop();
4297
- const context = (0, import_tone4.getContext)();
4298
- const timeNow = context.currentTime;
2549
+ const timeNow = getAudioContextTime();
4299
2550
  playbackStartTimeRef.current = timeNow;
4300
2551
  audioStartPositionRef.current = currentPos;
4301
2552
  engineRef.current.play(currentPos);
@@ -4311,14 +2562,20 @@ var WaveformPlaylistProvider = ({
4311
2562
  setIsPlaying(false);
4312
2563
  stopAnimationLoop();
4313
2564
  });
4314
- }, [continuousPlay, isPlaying, startAnimationLoop, stopAnimationLoop, animationFrameRef]);
4315
- (0, import_react24.useEffect)(() => {
2565
+ }, [
2566
+ continuousPlay,
2567
+ isPlaying,
2568
+ startAnimationLoop,
2569
+ stopAnimationLoop,
2570
+ animationFrameRef,
2571
+ getAudioContextTime
2572
+ ]);
2573
+ (0, import_react17.useEffect)(() => {
4316
2574
  const resumePlayback = () => __async(null, null, function* () {
4317
2575
  if (pendingResumeRef.current && engineRef.current) {
4318
2576
  const { position } = pendingResumeRef.current;
4319
2577
  pendingResumeRef.current = null;
4320
- const context = (0, import_tone4.getContext)();
4321
- const timeNow = context.currentTime;
2578
+ const timeNow = getAudioContextTime();
4322
2579
  playbackStartTimeRef.current = timeNow;
4323
2580
  audioStartPositionRef.current = position;
4324
2581
  if (!audioInitializedRef.current) {
@@ -4335,8 +2592,8 @@ var WaveformPlaylistProvider = ({
4335
2592
  setIsPlaying(false);
4336
2593
  stopAnimationLoop();
4337
2594
  });
4338
- }, [tracks, startAnimationLoop, stopAnimationLoop]);
4339
- const play = (0, import_react24.useCallback)(
2595
+ }, [tracks, startAnimationLoop, stopAnimationLoop, getAudioContextTime]);
2596
+ const play = (0, import_react17.useCallback)(
4340
2597
  (startTime, playDuration) => __async(null, null, function* () {
4341
2598
  if (!engineRef.current) return;
4342
2599
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4345,8 +2602,7 @@ var WaveformPlaylistProvider = ({
4345
2602
  engineRef.current.stop();
4346
2603
  engineRef.current.seek(actualStartTime);
4347
2604
  stopAnimationLoop();
4348
- const context = (0, import_tone4.getContext)();
4349
- const startTimeNow = context.currentTime;
2605
+ const startTimeNow = getAudioContextTime();
4350
2606
  playbackStartTimeRef.current = startTimeNow;
4351
2607
  audioStartPositionRef.current = actualStartTime;
4352
2608
  playbackEndTimeRef.current = playDuration !== void 0 ? actualStartTime + playDuration : null;
@@ -4365,9 +2621,9 @@ var WaveformPlaylistProvider = ({
4365
2621
  setIsPlaying(true);
4366
2622
  startAnimationLoop();
4367
2623
  }),
4368
- [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs]
2624
+ [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs, getAudioContextTime]
4369
2625
  );
4370
- const pause = (0, import_react24.useCallback)(() => {
2626
+ const pause = (0, import_react17.useCallback)(() => {
4371
2627
  if (!engineRef.current) return;
4372
2628
  const pauseTime = getPlaybackTime();
4373
2629
  engineRef.current.pause();
@@ -4376,7 +2632,7 @@ var WaveformPlaylistProvider = ({
4376
2632
  setCurrentTimeRefs(pauseTime);
4377
2633
  setCurrentTime(pauseTime);
4378
2634
  }, [stopAnimationLoop, getPlaybackTime, setCurrentTimeRefs]);
4379
- const stop = (0, import_react24.useCallback)(() => {
2635
+ const stop = (0, import_react17.useCallback)(() => {
4380
2636
  if (!engineRef.current) return;
4381
2637
  engineRef.current.stop();
4382
2638
  setIsPlaying(false);
@@ -4385,7 +2641,7 @@ var WaveformPlaylistProvider = ({
4385
2641
  setCurrentTime(playStartPositionRef.current);
4386
2642
  setActiveAnnotationId(null);
4387
2643
  }, [stopAnimationLoop, setActiveAnnotationId, setCurrentTimeRefs]);
4388
- const seekTo = (0, import_react24.useCallback)(
2644
+ const seekTo = (0, import_react17.useCallback)(
4389
2645
  (time) => {
4390
2646
  const clampedTime = Math.max(0, Math.min(time, duration));
4391
2647
  setCurrentTimeRefs(clampedTime);
@@ -4396,7 +2652,7 @@ var WaveformPlaylistProvider = ({
4396
2652
  },
4397
2653
  [duration, isPlaying, play, setCurrentTimeRefs]
4398
2654
  );
4399
- const setTrackMute = (0, import_react24.useCallback)(
2655
+ const setTrackMute = (0, import_react17.useCallback)(
4400
2656
  (trackIndex, muted) => {
4401
2657
  var _a2;
4402
2658
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4410,7 +2666,7 @@ var WaveformPlaylistProvider = ({
4410
2666
  },
4411
2667
  [trackStates]
4412
2668
  );
4413
- const setTrackSolo = (0, import_react24.useCallback)(
2669
+ const setTrackSolo = (0, import_react17.useCallback)(
4414
2670
  (trackIndex, soloed) => {
4415
2671
  var _a2;
4416
2672
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4424,7 +2680,7 @@ var WaveformPlaylistProvider = ({
4424
2680
  },
4425
2681
  [trackStates]
4426
2682
  );
4427
- const setTrackVolume = (0, import_react24.useCallback)(
2683
+ const setTrackVolume = (0, import_react17.useCallback)(
4428
2684
  (trackIndex, volume2) => {
4429
2685
  var _a2;
4430
2686
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4438,7 +2694,7 @@ var WaveformPlaylistProvider = ({
4438
2694
  },
4439
2695
  [trackStates]
4440
2696
  );
4441
- const setTrackPan = (0, import_react24.useCallback)(
2697
+ const setTrackPan = (0, import_react17.useCallback)(
4442
2698
  (trackIndex, pan) => {
4443
2699
  var _a2;
4444
2700
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4452,7 +2708,7 @@ var WaveformPlaylistProvider = ({
4452
2708
  },
4453
2709
  [trackStates]
4454
2710
  );
4455
- const setSelection = (0, import_react24.useCallback)(
2711
+ const setSelection = (0, import_react17.useCallback)(
4456
2712
  (start, end) => {
4457
2713
  setSelectionEngine(start, end);
4458
2714
  setCurrentTimeRefs(start);
@@ -4465,12 +2721,12 @@ var WaveformPlaylistProvider = ({
4465
2721
  },
4466
2722
  [isPlaying, setSelectionEngine, setCurrentTimeRefs]
4467
2723
  );
4468
- const setScrollContainer = (0, import_react24.useCallback)((element) => {
2724
+ const setScrollContainer = (0, import_react17.useCallback)((element) => {
4469
2725
  scrollContainerRef.current = element;
4470
2726
  }, []);
4471
- const onAnnotationsChangeRef = (0, import_react24.useRef)(onAnnotationsChange);
2727
+ const onAnnotationsChangeRef = (0, import_react17.useRef)(onAnnotationsChange);
4472
2728
  onAnnotationsChangeRef.current = onAnnotationsChange;
4473
- const setAnnotations = (0, import_react24.useCallback)(
2729
+ const setAnnotations = (0, import_react17.useCallback)(
4474
2730
  (action) => {
4475
2731
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4476
2732
  if (!onAnnotationsChangeRef.current) {
@@ -4488,7 +2744,7 @@ var WaveformPlaylistProvider = ({
4488
2744
  const sampleRate = sampleRateRef.current;
4489
2745
  const timeScaleHeight = timescale ? 30 : 0;
4490
2746
  const minimumPlaylistHeight = tracks.length * waveHeight + timeScaleHeight;
4491
- const animationValue = (0, import_react24.useMemo)(
2747
+ const animationValue = (0, import_react17.useMemo)(
4492
2748
  () => ({
4493
2749
  isPlaying,
4494
2750
  currentTime,
@@ -4497,7 +2753,9 @@ var WaveformPlaylistProvider = ({
4497
2753
  playbackStartTimeRef,
4498
2754
  audioStartPositionRef,
4499
2755
  getPlaybackTime,
2756
+ getAudioContextTime,
4500
2757
  getLookAhead,
2758
+ getOutputLatency,
4501
2759
  registerFrameCallback,
4502
2760
  unregisterFrameCallback
4503
2761
  }),
@@ -4509,12 +2767,14 @@ var WaveformPlaylistProvider = ({
4509
2767
  playbackStartTimeRef,
4510
2768
  audioStartPositionRef,
4511
2769
  getPlaybackTime,
2770
+ getAudioContextTime,
4512
2771
  getLookAhead,
2772
+ getOutputLatency,
4513
2773
  registerFrameCallback,
4514
2774
  unregisterFrameCallback
4515
2775
  ]
4516
2776
  );
4517
- const stateValue = (0, import_react24.useMemo)(
2777
+ const stateValue = (0, import_react17.useMemo)(
4518
2778
  () => ({
4519
2779
  continuousPlay,
4520
2780
  linkEndpoints,
@@ -4550,17 +2810,17 @@ var WaveformPlaylistProvider = ({
4550
2810
  canRedo
4551
2811
  ]
4552
2812
  );
4553
- const setCurrentTimeControl = (0, import_react24.useCallback)(
2813
+ const setCurrentTimeControl = (0, import_react17.useCallback)(
4554
2814
  (time) => {
4555
2815
  setCurrentTimeRefs(time);
4556
2816
  setCurrentTime(time);
4557
2817
  },
4558
2818
  [setCurrentTimeRefs]
4559
2819
  );
4560
- const setAutomaticScrollControl = (0, import_react24.useCallback)((enabled) => {
2820
+ const setAutomaticScrollControl = (0, import_react17.useCallback)((enabled) => {
4561
2821
  setIsAutomaticScroll(enabled);
4562
2822
  }, []);
4563
- const controlsValue = (0, import_react24.useMemo)(
2823
+ const controlsValue = (0, import_react17.useMemo)(
4564
2824
  () => ({
4565
2825
  // Playback controls
4566
2826
  play,
@@ -4636,7 +2896,7 @@ var WaveformPlaylistProvider = ({
4636
2896
  redo
4637
2897
  ]
4638
2898
  );
4639
- const dataValue = (0, import_react24.useMemo)(
2899
+ const dataValue = (0, import_react17.useMemo)(
4640
2900
  () => ({
4641
2901
  duration,
4642
2902
  audioBuffers,
@@ -4692,44 +2952,69 @@ var WaveformPlaylistProvider = ({
4692
2952
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_styled_components.ThemeProvider, { theme: mergedTheme, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlaybackAnimationContext.Provider, { value: animationValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlaylistStateContext.Provider, { value: stateValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlaylistControlsContext.Provider, { value: controlsValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlaylistDataContext.Provider, { value: dataValue, children }) }) }) }) });
4693
2953
  };
4694
2954
  var usePlaybackAnimation = () => {
4695
- const context = (0, import_react24.useContext)(PlaybackAnimationContext);
2955
+ const context = (0, import_react17.useContext)(PlaybackAnimationContext);
4696
2956
  if (!context) {
4697
2957
  throw new Error("usePlaybackAnimation must be used within WaveformPlaylistProvider");
4698
2958
  }
4699
2959
  return context;
4700
2960
  };
4701
2961
  var usePlaylistState = () => {
4702
- const context = (0, import_react24.useContext)(PlaylistStateContext);
2962
+ const context = (0, import_react17.useContext)(PlaylistStateContext);
4703
2963
  if (!context) {
4704
2964
  throw new Error("usePlaylistState must be used within WaveformPlaylistProvider");
4705
2965
  }
4706
2966
  return context;
4707
2967
  };
4708
2968
  var usePlaylistControls = () => {
4709
- const context = (0, import_react24.useContext)(PlaylistControlsContext);
2969
+ const context = (0, import_react17.useContext)(PlaylistControlsContext);
4710
2970
  if (!context) {
4711
2971
  throw new Error("usePlaylistControls must be used within WaveformPlaylistProvider");
4712
2972
  }
4713
2973
  return context;
4714
2974
  };
4715
2975
  var usePlaylistData = () => {
4716
- const context = (0, import_react24.useContext)(PlaylistDataContext);
2976
+ const context = (0, import_react17.useContext)(PlaylistDataContext);
4717
2977
  if (!context) {
4718
2978
  throw new Error("usePlaylistData must be used within WaveformPlaylistProvider");
4719
2979
  }
4720
2980
  return context;
4721
2981
  };
2982
+ var usePlaylistDataOptional = () => (0, import_react17.useContext)(PlaylistDataContext);
4722
2983
 
4723
2984
  // src/MediaElementPlaylistContext.tsx
4724
- var import_react25 = require("react");
2985
+ var import_react18 = require("react");
4725
2986
  var import_styled_components2 = require("styled-components");
4726
- var import_media_element_playout = require("@waveform-playlist/media-element-playout");
2987
+
2988
+ // src/playout/resolveMediaElementPlayout.ts
2989
+ 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`.";
2990
+ function resolveMediaElementPlayout(opts) {
2991
+ return __async(this, null, function* () {
2992
+ if (opts.createPlayout) {
2993
+ return opts.createPlayout();
2994
+ }
2995
+ let mod;
2996
+ try {
2997
+ mod = yield import("@waveform-playlist/media-element-playout");
2998
+ } catch (originalErr) {
2999
+ console.warn(
3000
+ "[waveform-playlist] @waveform-playlist/media-element-playout dynamic import failed: " + String(originalErr)
3001
+ );
3002
+ throw new Error(INSTALL_HINT2);
3003
+ }
3004
+ return new mod.MediaElementPlayout({
3005
+ playbackRate: opts.playbackRate,
3006
+ preservesPitch: opts.preservesPitch
3007
+ });
3008
+ });
3009
+ }
3010
+
3011
+ // src/MediaElementPlaylistContext.tsx
4727
3012
  var import_ui_components3 = require("@waveform-playlist/ui-components");
4728
3013
  var import_jsx_runtime2 = require("react/jsx-runtime");
4729
- var MediaElementAnimationContext = (0, import_react25.createContext)(null);
4730
- var MediaElementStateContext = (0, import_react25.createContext)(null);
4731
- var MediaElementControlsContext = (0, import_react25.createContext)(null);
4732
- var MediaElementDataContext = (0, import_react25.createContext)(null);
3014
+ var MediaElementAnimationContext = (0, import_react18.createContext)(null);
3015
+ var MediaElementStateContext = (0, import_react18.createContext)(null);
3016
+ var MediaElementControlsContext = (0, import_react18.createContext)(null);
3017
+ var MediaElementDataContext = (0, import_react18.createContext)(null);
4733
3018
  var MediaElementPlaylistProvider = ({
4734
3019
  track,
4735
3020
  samplesPerPixel: initialSamplesPerPixel = 1024,
@@ -4747,16 +3032,18 @@ var MediaElementPlaylistProvider = ({
4747
3032
  audioContext,
4748
3033
  onAnnotationsChange,
4749
3034
  onReady,
3035
+ onError,
3036
+ createPlayout,
4750
3037
  children
4751
3038
  }) => {
4752
3039
  var _a;
4753
3040
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
4754
- const [isPlaying, setIsPlaying] = (0, import_react25.useState)(false);
4755
- const [currentTime, setCurrentTime] = (0, import_react25.useState)(0);
4756
- const [duration, setDuration] = (0, import_react25.useState)(0);
4757
- const [peaksDataArray, setPeaksDataArray] = (0, import_react25.useState)([]);
4758
- const [playbackRate, setPlaybackRateState] = (0, import_react25.useState)(initialPlaybackRate);
4759
- const annotations = (0, import_react25.useMemo)(() => {
3041
+ const [isPlaying, setIsPlaying] = (0, import_react18.useState)(false);
3042
+ const [currentTime, setCurrentTime] = (0, import_react18.useState)(0);
3043
+ const [duration, setDuration] = (0, import_react18.useState)(0);
3044
+ const [peaksDataArray, setPeaksDataArray] = (0, import_react18.useState)([]);
3045
+ const [playbackRate, setPlaybackRateState] = (0, import_react18.useState)(initialPlaybackRate);
3046
+ const annotations = (0, import_react18.useMemo)(() => {
4760
3047
  if (!(annotationList == null ? void 0 : annotationList.annotations)) return [];
4761
3048
  if (process.env.NODE_ENV !== "production" && annotationList.annotations.length > 0) {
4762
3049
  const first = annotationList.annotations[0];
@@ -4769,73 +3056,92 @@ var MediaElementPlaylistProvider = ({
4769
3056
  }
4770
3057
  return annotationList.annotations;
4771
3058
  }, [annotationList == null ? void 0 : annotationList.annotations]);
4772
- const annotationsRef = (0, import_react25.useRef)(annotations);
3059
+ const annotationsRef = (0, import_react18.useRef)(annotations);
4773
3060
  annotationsRef.current = annotations;
4774
- const [activeAnnotationId, setActiveAnnotationIdState] = (0, import_react25.useState)(null);
4775
- const [continuousPlay, setContinuousPlayState] = (0, import_react25.useState)(
3061
+ const [activeAnnotationId, setActiveAnnotationIdState] = (0, import_react18.useState)(null);
3062
+ const [continuousPlay, setContinuousPlayState] = (0, import_react18.useState)(
4776
3063
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
4777
3064
  );
4778
- const [samplesPerPixel] = (0, import_react25.useState)(initialSamplesPerPixel);
4779
- const [isAutomaticScroll, setIsAutomaticScroll] = (0, import_react25.useState)(automaticScroll);
4780
- const playoutRef = (0, import_react25.useRef)(null);
4781
- const currentTimeRef = (0, import_react25.useRef)(0);
4782
- const continuousPlayRef = (0, import_react25.useRef)(continuousPlay);
4783
- const activeAnnotationIdRef = (0, import_react25.useRef)(null);
4784
- const scrollContainerRef = (0, import_react25.useRef)(null);
4785
- const isAutomaticScrollRef = (0, import_react25.useRef)(automaticScroll);
4786
- const samplesPerPixelRef = (0, import_react25.useRef)(initialSamplesPerPixel);
3065
+ const [samplesPerPixel] = (0, import_react18.useState)(initialSamplesPerPixel);
3066
+ const [isAutomaticScroll, setIsAutomaticScroll] = (0, import_react18.useState)(automaticScroll);
3067
+ const playoutRef = (0, import_react18.useRef)(null);
3068
+ const currentTimeRef = (0, import_react18.useRef)(0);
3069
+ const continuousPlayRef = (0, import_react18.useRef)(continuousPlay);
3070
+ const activeAnnotationIdRef = (0, import_react18.useRef)(null);
3071
+ const scrollContainerRef = (0, import_react18.useRef)(null);
3072
+ const isAutomaticScrollRef = (0, import_react18.useRef)(automaticScroll);
3073
+ const samplesPerPixelRef = (0, import_react18.useRef)(initialSamplesPerPixel);
4787
3074
  const { startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
4788
- (0, import_react25.useEffect)(() => {
3075
+ (0, import_react18.useEffect)(() => {
4789
3076
  continuousPlayRef.current = continuousPlay;
4790
3077
  }, [continuousPlay]);
4791
- (0, import_react25.useEffect)(() => {
3078
+ (0, import_react18.useEffect)(() => {
4792
3079
  isAutomaticScrollRef.current = isAutomaticScroll;
4793
3080
  }, [isAutomaticScroll]);
4794
- const setActiveAnnotationId = (0, import_react25.useCallback)((value) => {
3081
+ const setActiveAnnotationId = (0, import_react18.useCallback)((value) => {
4795
3082
  activeAnnotationIdRef.current = value;
4796
3083
  setActiveAnnotationIdState(value);
4797
3084
  }, []);
4798
- const setContinuousPlay = (0, import_react25.useCallback)((value) => {
3085
+ const setContinuousPlay = (0, import_react18.useCallback)((value) => {
4799
3086
  continuousPlayRef.current = value;
4800
3087
  setContinuousPlayState(value);
4801
3088
  }, []);
4802
- const setScrollContainer = (0, import_react25.useCallback)((element) => {
3089
+ const setScrollContainer = (0, import_react18.useCallback)((element) => {
4803
3090
  scrollContainerRef.current = element;
4804
3091
  }, []);
4805
3092
  const sampleRate = track.waveformData.sample_rate;
4806
- (0, import_react25.useEffect)(() => {
4807
- var _a2, _b;
4808
- const playout = new import_media_element_playout.MediaElementPlayout({
4809
- playbackRate: initialPlaybackRate,
4810
- preservesPitch
4811
- });
4812
- playout.addTrack({
4813
- source: track.source,
4814
- peaks: track.waveformData,
4815
- name: track.name,
4816
- audioContext,
4817
- fadeIn: track.fadeIn,
4818
- fadeOut: track.fadeOut
4819
- });
4820
- const mediaTrack = playout.getTrack((_b = (_a2 = playout["track"]) == null ? void 0 : _a2.id) != null ? _b : "");
4821
- if (mediaTrack) {
4822
- mediaTrack.setOnTimeUpdateCallback((time) => {
4823
- currentTimeRef.current = time;
4824
- });
4825
- }
4826
- playout.setOnPlaybackComplete(() => {
4827
- stopAnimationFrameLoop();
4828
- setIsPlaying(false);
4829
- setActiveAnnotationId(null);
4830
- currentTimeRef.current = 0;
4831
- setCurrentTime(0);
4832
- });
4833
- playoutRef.current = playout;
4834
- setDuration(track.waveformData.duration);
4835
- onReady == null ? void 0 : onReady();
3093
+ (0, import_react18.useEffect)(() => {
3094
+ let cancelled = false;
3095
+ let createdPlayout = null;
3096
+ (() => __async(null, null, function* () {
3097
+ var _a2, _b;
3098
+ try {
3099
+ const playout = yield resolveMediaElementPlayout({
3100
+ createPlayout,
3101
+ playbackRate: initialPlaybackRate,
3102
+ preservesPitch
3103
+ });
3104
+ if (cancelled) {
3105
+ playout.dispose();
3106
+ return;
3107
+ }
3108
+ createdPlayout = playout;
3109
+ playout.addTrack({
3110
+ source: track.source,
3111
+ peaks: track.waveformData,
3112
+ name: track.name,
3113
+ audioContext,
3114
+ fadeIn: track.fadeIn,
3115
+ fadeOut: track.fadeOut
3116
+ });
3117
+ const mediaTrack = playout.getTrack((_b = (_a2 = playout["track"]) == null ? void 0 : _a2.id) != null ? _b : "");
3118
+ if (mediaTrack) {
3119
+ mediaTrack.setOnTimeUpdateCallback((time) => {
3120
+ currentTimeRef.current = time;
3121
+ });
3122
+ }
3123
+ playout.setOnPlaybackComplete(() => {
3124
+ stopAnimationFrameLoop();
3125
+ setIsPlaying(false);
3126
+ setActiveAnnotationId(null);
3127
+ currentTimeRef.current = 0;
3128
+ setCurrentTime(0);
3129
+ });
3130
+ playoutRef.current = playout;
3131
+ setDuration(track.waveformData.duration);
3132
+ onReady == null ? void 0 : onReady();
3133
+ } catch (err) {
3134
+ console.warn("[waveform-playlist] MediaElement playout init failed: " + String(err));
3135
+ onError == null ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
3136
+ }
3137
+ }))();
4836
3138
  return () => {
3139
+ cancelled = true;
4837
3140
  stopAnimationFrameLoop();
4838
- playout.dispose();
3141
+ if (createdPlayout) {
3142
+ createdPlayout.dispose();
3143
+ }
3144
+ playoutRef.current = null;
4839
3145
  };
4840
3146
  }, [
4841
3147
  track.source,
@@ -4847,10 +3153,12 @@ var MediaElementPlaylistProvider = ({
4847
3153
  initialPlaybackRate,
4848
3154
  preservesPitch,
4849
3155
  onReady,
3156
+ onError,
4850
3157
  stopAnimationFrameLoop,
4851
- setActiveAnnotationId
3158
+ setActiveAnnotationId,
3159
+ createPlayout
4852
3160
  ]);
4853
- (0, import_react25.useEffect)(() => {
3161
+ (0, import_react18.useEffect)(() => {
4854
3162
  var _a2;
4855
3163
  try {
4856
3164
  const extractedPeaks = extractPeaksFromWaveformData(
@@ -4879,7 +3187,7 @@ var MediaElementPlaylistProvider = ({
4879
3187
  console.warn("[waveform-playlist] Failed to extract peaks from waveform data:", err);
4880
3188
  }
4881
3189
  }, [track.waveformData, track.name, samplesPerPixel, sampleRate]);
4882
- const startAnimationLoop = (0, import_react25.useCallback)(() => {
3190
+ const startAnimationLoop = (0, import_react18.useCallback)(() => {
4883
3191
  const updateTime = () => {
4884
3192
  var _a2, _b, _c;
4885
3193
  const time = (_b = (_a2 = playoutRef.current) == null ? void 0 : _a2.getCurrentTime()) != null ? _b : 0;
@@ -4922,7 +3230,7 @@ var MediaElementPlaylistProvider = ({
4922
3230
  startAnimationFrameLoop(updateTime);
4923
3231
  }, [setActiveAnnotationId, sampleRate, startAnimationFrameLoop]);
4924
3232
  const stopAnimationLoop = stopAnimationFrameLoop;
4925
- const play = (0, import_react25.useCallback)(
3233
+ const play = (0, import_react18.useCallback)(
4926
3234
  (startTime) => {
4927
3235
  if (!playoutRef.current) return;
4928
3236
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4932,14 +3240,14 @@ var MediaElementPlaylistProvider = ({
4932
3240
  },
4933
3241
  [startAnimationLoop]
4934
3242
  );
4935
- const pause = (0, import_react25.useCallback)(() => {
3243
+ const pause = (0, import_react18.useCallback)(() => {
4936
3244
  if (!playoutRef.current) return;
4937
3245
  playoutRef.current.pause();
4938
3246
  setIsPlaying(false);
4939
3247
  stopAnimationLoop();
4940
3248
  setCurrentTime(playoutRef.current.getCurrentTime());
4941
3249
  }, [stopAnimationLoop]);
4942
- const stop = (0, import_react25.useCallback)(() => {
3250
+ const stop = (0, import_react18.useCallback)(() => {
4943
3251
  if (!playoutRef.current) return;
4944
3252
  playoutRef.current.stop();
4945
3253
  setIsPlaying(false);
@@ -4948,7 +3256,7 @@ var MediaElementPlaylistProvider = ({
4948
3256
  setCurrentTime(0);
4949
3257
  setActiveAnnotationId(null);
4950
3258
  }, [stopAnimationLoop, setActiveAnnotationId]);
4951
- const seekTo = (0, import_react25.useCallback)(
3259
+ const seekTo = (0, import_react18.useCallback)(
4952
3260
  (time) => {
4953
3261
  const clampedTime = Math.max(0, Math.min(time, duration));
4954
3262
  currentTimeRef.current = clampedTime;
@@ -4959,7 +3267,7 @@ var MediaElementPlaylistProvider = ({
4959
3267
  },
4960
3268
  [duration]
4961
3269
  );
4962
- const setPlaybackRate = (0, import_react25.useCallback)((rate) => {
3270
+ const setPlaybackRate = (0, import_react18.useCallback)((rate) => {
4963
3271
  const clampedRate = Math.max(0.5, Math.min(2, rate));
4964
3272
  setPlaybackRateState(clampedRate);
4965
3273
  if (playoutRef.current) {
@@ -4967,7 +3275,7 @@ var MediaElementPlaylistProvider = ({
4967
3275
  }
4968
3276
  }, []);
4969
3277
  const timeScaleHeight = timescale ? 30 : 0;
4970
- const animationValue = (0, import_react25.useMemo)(
3278
+ const animationValue = (0, import_react18.useMemo)(
4971
3279
  () => ({
4972
3280
  isPlaying,
4973
3281
  currentTime,
@@ -4975,7 +3283,7 @@ var MediaElementPlaylistProvider = ({
4975
3283
  }),
4976
3284
  [isPlaying, currentTime]
4977
3285
  );
4978
- const stateValue = (0, import_react25.useMemo)(
3286
+ const stateValue = (0, import_react18.useMemo)(
4979
3287
  () => ({
4980
3288
  continuousPlay,
4981
3289
  annotations,
@@ -4985,9 +3293,9 @@ var MediaElementPlaylistProvider = ({
4985
3293
  }),
4986
3294
  [continuousPlay, annotations, activeAnnotationId, playbackRate, isAutomaticScroll]
4987
3295
  );
4988
- const onAnnotationsChangeRef = (0, import_react25.useRef)(onAnnotationsChange);
3296
+ const onAnnotationsChangeRef = (0, import_react18.useRef)(onAnnotationsChange);
4989
3297
  onAnnotationsChangeRef.current = onAnnotationsChange;
4990
- const setAnnotations = (0, import_react25.useCallback)(
3298
+ const setAnnotations = (0, import_react18.useCallback)(
4991
3299
  (action) => {
4992
3300
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4993
3301
  if (!onAnnotationsChangeRef.current) {
@@ -5002,7 +3310,7 @@ var MediaElementPlaylistProvider = ({
5002
3310
  },
5003
3311
  []
5004
3312
  );
5005
- const controlsValue = (0, import_react25.useMemo)(
3313
+ const controlsValue = (0, import_react18.useMemo)(
5006
3314
  () => ({
5007
3315
  play,
5008
3316
  pause,
@@ -5030,7 +3338,7 @@ var MediaElementPlaylistProvider = ({
5030
3338
  setScrollContainer
5031
3339
  ]
5032
3340
  );
5033
- const dataValue = (0, import_react25.useMemo)(
3341
+ const dataValue = (0, import_react18.useMemo)(
5034
3342
  () => ({
5035
3343
  duration,
5036
3344
  peaksDataArray,
@@ -5065,28 +3373,28 @@ var MediaElementPlaylistProvider = ({
5065
3373
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_styled_components2.ThemeProvider, { theme: mergedTheme, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MediaElementAnimationContext.Provider, { value: animationValue, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MediaElementStateContext.Provider, { value: stateValue, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MediaElementControlsContext.Provider, { value: controlsValue, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MediaElementDataContext.Provider, { value: dataValue, children }) }) }) }) });
5066
3374
  };
5067
3375
  var useMediaElementAnimation = () => {
5068
- const context = (0, import_react25.useContext)(MediaElementAnimationContext);
3376
+ const context = (0, import_react18.useContext)(MediaElementAnimationContext);
5069
3377
  if (!context) {
5070
3378
  throw new Error("useMediaElementAnimation must be used within MediaElementPlaylistProvider");
5071
3379
  }
5072
3380
  return context;
5073
3381
  };
5074
3382
  var useMediaElementState = () => {
5075
- const context = (0, import_react25.useContext)(MediaElementStateContext);
3383
+ const context = (0, import_react18.useContext)(MediaElementStateContext);
5076
3384
  if (!context) {
5077
3385
  throw new Error("useMediaElementState must be used within MediaElementPlaylistProvider");
5078
3386
  }
5079
3387
  return context;
5080
3388
  };
5081
3389
  var useMediaElementControls = () => {
5082
- const context = (0, import_react25.useContext)(MediaElementControlsContext);
3390
+ const context = (0, import_react18.useContext)(MediaElementControlsContext);
5083
3391
  if (!context) {
5084
3392
  throw new Error("useMediaElementControls must be used within MediaElementPlaylistProvider");
5085
3393
  }
5086
3394
  return context;
5087
3395
  };
5088
3396
  var useMediaElementData = () => {
5089
- const context = (0, import_react25.useContext)(MediaElementDataContext);
3397
+ const context = (0, import_react18.useContext)(MediaElementDataContext);
5090
3398
  if (!context) {
5091
3399
  throw new Error("useMediaElementData must be used within MediaElementPlaylistProvider");
5092
3400
  }
@@ -5094,7 +3402,7 @@ var useMediaElementData = () => {
5094
3402
  };
5095
3403
 
5096
3404
  // src/components/PlaybackControls.tsx
5097
- var import_react26 = require("react");
3405
+ var import_react19 = require("react");
5098
3406
  var import_ui_components4 = require("@waveform-playlist/ui-components");
5099
3407
  var import_jsx_runtime3 = require("react/jsx-runtime");
5100
3408
  var PlayButton = ({ className }) => {
@@ -5230,7 +3538,7 @@ var ClearAllButton = ({
5230
3538
  className
5231
3539
  }) => {
5232
3540
  const { stop } = usePlaylistControls();
5233
- const handleClick = (0, import_react26.useCallback)(() => {
3541
+ const handleClick = (0, import_react19.useCallback)(() => {
5234
3542
  stop();
5235
3543
  onClearAll();
5236
3544
  }, [stop, onClearAll]);
@@ -5258,7 +3566,7 @@ var ZoomOutButton = ({
5258
3566
  };
5259
3567
 
5260
3568
  // src/components/ContextualControls.tsx
5261
- var import_react27 = require("react");
3569
+ var import_react20 = require("react");
5262
3570
  var import_ui_components6 = require("@waveform-playlist/ui-components");
5263
3571
  var import_styled_components3 = __toESM(require("styled-components"));
5264
3572
  var import_jsx_runtime5 = require("react/jsx-runtime");
@@ -5291,10 +3599,10 @@ var PositionDisplay = import_styled_components3.default.span`
5291
3599
  `;
5292
3600
  var AudioPosition = ({ className }) => {
5293
3601
  var _a;
5294
- const timeRef = (0, import_react27.useRef)(null);
3602
+ const timeRef = (0, import_react20.useRef)(null);
5295
3603
  const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
5296
3604
  const { timeFormat: format } = usePlaylistData();
5297
- (0, import_react27.useEffect)(() => {
3605
+ (0, import_react20.useEffect)(() => {
5298
3606
  const id = "audio-position";
5299
3607
  if (isPlaying) {
5300
3608
  registerFrameCallback(id, ({ time }) => {
@@ -5305,7 +3613,7 @@ var AudioPosition = ({ className }) => {
5305
3613
  }
5306
3614
  return () => unregisterFrameCallback(id);
5307
3615
  }, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);
5308
- (0, import_react27.useEffect)(() => {
3616
+ (0, import_react20.useEffect)(() => {
5309
3617
  var _a2;
5310
3618
  if (!isPlaying && timeRef.current) {
5311
3619
  timeRef.current.textContent = (0, import_ui_components6.formatTime)((_a2 = currentTimeRef.current) != null ? _a2 : 0, format);
@@ -5340,11 +3648,11 @@ var AutomaticScrollCheckbox = ({ className }) => {
5340
3648
  };
5341
3649
 
5342
3650
  // src/AnnotationIntegrationContext.tsx
5343
- var import_react28 = require("react");
5344
- var AnnotationIntegrationContext = (0, import_react28.createContext)(null);
3651
+ var import_react21 = require("react");
3652
+ var AnnotationIntegrationContext = (0, import_react21.createContext)(null);
5345
3653
  var AnnotationIntegrationProvider = AnnotationIntegrationContext.Provider;
5346
3654
  function useAnnotationIntegration() {
5347
- const context = (0, import_react28.useContext)(AnnotationIntegrationContext);
3655
+ const context = (0, import_react21.useContext)(AnnotationIntegrationContext);
5348
3656
  if (!context) {
5349
3657
  throw new Error(
5350
3658
  "useAnnotationIntegration must be used within <AnnotationProvider>. Install @waveform-playlist/annotations and wrap your app with <AnnotationProvider>. See: https://waveform-playlist.naomiaro.com/docs/guides/annotations"
@@ -5382,73 +3690,25 @@ var DownloadAnnotationsButton = ({
5382
3690
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Base, { annotations, filename, className });
5383
3691
  };
5384
3692
 
5385
- // src/components/ExportControls.tsx
5386
- var import_ui_components7 = require("@waveform-playlist/ui-components");
5387
- var import_jsx_runtime7 = require("react/jsx-runtime");
5388
- var ExportWavButton = ({
5389
- label = "Export WAV",
5390
- filename = "export",
5391
- mode = "master",
5392
- trackIndex,
5393
- bitDepth = 16,
5394
- applyEffects = true,
5395
- effectsFunction,
5396
- createOfflineTrackEffects,
5397
- className,
5398
- onExportComplete,
5399
- onExportError
5400
- }) => {
5401
- const { tracks, trackStates } = usePlaylistData();
5402
- const { exportWav, isExporting, progress } = useExportWav();
5403
- const handleExport = () => __async(null, null, function* () {
5404
- try {
5405
- const result = yield exportWav(tracks, trackStates, {
5406
- filename,
5407
- mode,
5408
- trackIndex,
5409
- bitDepth,
5410
- applyEffects,
5411
- effectsFunction,
5412
- createOfflineTrackEffects,
5413
- autoDownload: true
5414
- });
5415
- onExportComplete == null ? void 0 : onExportComplete(result.blob);
5416
- } catch (error) {
5417
- onExportError == null ? void 0 : onExportError(error instanceof Error ? error : new Error("Export failed"));
5418
- }
5419
- });
5420
- const buttonLabel = isExporting ? `Exporting ${Math.round(progress * 100)}%` : label;
5421
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5422
- import_ui_components7.BaseControlButton,
5423
- {
5424
- onClick: handleExport,
5425
- disabled: isExporting || tracks.length === 0,
5426
- className,
5427
- children: buttonLabel
5428
- }
5429
- );
5430
- };
5431
-
5432
3693
  // src/contexts/ClipInteractionContext.tsx
5433
- var import_react29 = require("react");
5434
- var ClipInteractionContext = (0, import_react29.createContext)(false);
3694
+ var import_react22 = require("react");
3695
+ var ClipInteractionContext = (0, import_react22.createContext)(false);
5435
3696
  var ClipInteractionContextProvider = ClipInteractionContext.Provider;
5436
3697
  function useClipInteractionEnabled() {
5437
- return (0, import_react29.useContext)(ClipInteractionContext);
3698
+ return (0, import_react22.useContext)(ClipInteractionContext);
5438
3699
  }
5439
3700
 
5440
3701
  // src/components/PlaylistVisualization.tsx
5441
- var import_react33 = require("react");
3702
+ var import_react26 = require("react");
5442
3703
  var import_react_dom = require("react-dom");
5443
3704
  var import_styled_components6 = __toESM(require("styled-components"));
5444
- var import_playout7 = require("@waveform-playlist/playout");
5445
- var import_ui_components9 = require("@waveform-playlist/ui-components");
5446
- var import_core8 = require("@waveform-playlist/core");
3705
+ var import_ui_components8 = require("@waveform-playlist/ui-components");
3706
+ var import_core4 = require("@waveform-playlist/core");
5447
3707
 
5448
3708
  // src/components/AnimatedPlayhead.tsx
5449
- var import_react30 = require("react");
3709
+ var import_react23 = require("react");
5450
3710
  var import_styled_components4 = __toESM(require("styled-components"));
5451
- var import_jsx_runtime8 = require("react/jsx-runtime");
3711
+ var import_jsx_runtime7 = require("react/jsx-runtime");
5452
3712
  var PlayheadLine = import_styled_components4.default.div.attrs((props) => ({
5453
3713
  style: {
5454
3714
  width: `${props.$width}px`,
@@ -5464,7 +3724,7 @@ var PlayheadLine = import_styled_components4.default.div.attrs((props) => ({
5464
3724
  will-change: transform;
5465
3725
  `;
5466
3726
  var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5467
- const playheadRef = (0, import_react30.useRef)(null);
3727
+ const playheadRef = (0, import_react23.useRef)(null);
5468
3728
  const {
5469
3729
  isPlaying,
5470
3730
  currentTimeRef,
@@ -5473,7 +3733,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5473
3733
  unregisterFrameCallback
5474
3734
  } = usePlaybackAnimation();
5475
3735
  const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();
5476
- (0, import_react30.useEffect)(() => {
3736
+ (0, import_react23.useEffect)(() => {
5477
3737
  const id = "playhead";
5478
3738
  if (isPlaying) {
5479
3739
  registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {
@@ -5485,7 +3745,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5485
3745
  }
5486
3746
  return () => unregisterFrameCallback(id);
5487
3747
  }, [isPlaying, registerFrameCallback, unregisterFrameCallback]);
5488
- (0, import_react30.useEffect)(() => {
3748
+ (0, import_react23.useEffect)(() => {
5489
3749
  var _a, _b;
5490
3750
  if (!isPlaying && playheadRef.current) {
5491
3751
  const time = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
@@ -5493,15 +3753,15 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5493
3753
  playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
5494
3754
  }
5495
3755
  });
5496
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PlayheadLine, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
3756
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PlayheadLine, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
5497
3757
  };
5498
3758
 
5499
3759
  // src/components/ChannelWithProgress.tsx
5500
- var import_react31 = require("react");
3760
+ var import_react24 = require("react");
5501
3761
  var import_styled_components5 = __toESM(require("styled-components"));
5502
- var import_core7 = require("@waveform-playlist/core");
5503
- var import_ui_components8 = require("@waveform-playlist/ui-components");
5504
- var import_jsx_runtime9 = require("react/jsx-runtime");
3762
+ var import_core3 = require("@waveform-playlist/core");
3763
+ var import_ui_components7 = require("@waveform-playlist/ui-components");
3764
+ var import_jsx_runtime8 = require("react/jsx-runtime");
5505
3765
  var ChannelWrapper = import_styled_components5.default.div`
5506
3766
  position: relative;
5507
3767
  `;
@@ -5555,10 +3815,10 @@ var ChannelWithProgress = (_a) => {
5555
3815
  "clipSampleRate",
5556
3816
  "clipOffsetSeconds"
5557
3817
  ]);
5558
- const progressRef = (0, import_react31.useRef)(null);
5559
- const callbackId = (0, import_react31.useId)();
5560
- const theme = (0, import_ui_components8.useTheme)();
5561
- const { waveHeight } = (0, import_ui_components8.usePlaylistInfo)();
3818
+ const progressRef = (0, import_react24.useRef)(null);
3819
+ const callbackId = (0, import_react24.useId)();
3820
+ const theme = (0, import_ui_components7.useTheme)();
3821
+ const { waveHeight } = (0, import_ui_components7.usePlaylistInfo)();
5562
3822
  const {
5563
3823
  isPlaying,
5564
3824
  currentTimeRef,
@@ -5568,12 +3828,12 @@ var ChannelWithProgress = (_a) => {
5568
3828
  } = usePlaybackAnimation();
5569
3829
  const { samplesPerPixel, sampleRate } = usePlaylistData();
5570
3830
  const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
5571
- const clipPixelWidth = (0, import_core7.clipPixelWidth)(
3831
+ const clipPixelWidth = (0, import_core3.clipPixelWidth)(
5572
3832
  clipStartSample,
5573
3833
  clipDurationSamples,
5574
3834
  samplesPerPixel
5575
3835
  );
5576
- (0, import_react31.useEffect)(() => {
3836
+ (0, import_react24.useEffect)(() => {
5577
3837
  if (isPlaying) {
5578
3838
  registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {
5579
3839
  if (progressRef.current) {
@@ -5601,7 +3861,7 @@ var ChannelWithProgress = (_a) => {
5601
3861
  registerFrameCallback,
5602
3862
  unregisterFrameCallback
5603
3863
  ]);
5604
- (0, import_react31.useEffect)(() => {
3864
+ (0, import_react24.useEffect)(() => {
5605
3865
  var _a2, _b2;
5606
3866
  if (!isPlaying && progressRef.current) {
5607
3867
  const currentTime = (_b2 = (_a2 = visualTimeRef.current) != null ? _a2 : currentTimeRef.current) != null ? _b2 : 0;
@@ -5629,14 +3889,14 @@ var ChannelWithProgress = (_a) => {
5629
3889
  const isSpectrogramMode = smartChannelProps.renderMode === "spectrogram" || smartChannelProps.renderMode === "both";
5630
3890
  const isPianoRollMode = smartChannelProps.renderMode === "piano-roll";
5631
3891
  const isBothMode = smartChannelProps.renderMode === "both";
5632
- const backgroundCss = isSpectrogramMode ? "#000" : isPianoRollMode ? (theme == null ? void 0 : theme.pianoRollBackgroundColor) || "#1a1a2e" : (0, import_ui_components8.waveformColorToCss)(backgroundColor);
3892
+ const backgroundCss = isSpectrogramMode ? "#000" : isPianoRollMode ? (theme == null ? void 0 : theme.pianoRollBackgroundColor) || "#1a1a2e" : (0, import_ui_components7.waveformColorToCss)(backgroundColor);
5633
3893
  const halfHeight = Math.floor(waveHeight / 2);
5634
3894
  const effectiveHeight = waveHeight;
5635
3895
  const effectiveTop = isBothMode ? smartChannelProps.index * waveHeight : smartChannelProps.index * waveHeight;
5636
- const waveformBackgroundCss = (0, import_ui_components8.waveformColorToCss)(backgroundColor);
5637
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(ChannelWrapper, { children: [
5638
- isBothMode ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
5639
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3896
+ const waveformBackgroundCss = (0, import_ui_components7.waveformColorToCss)(backgroundColor);
3897
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(ChannelWrapper, { children: [
3898
+ isBothMode ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3899
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5640
3900
  Background,
5641
3901
  {
5642
3902
  $color: "#000",
@@ -5645,7 +3905,7 @@ var ChannelWithProgress = (_a) => {
5645
3905
  $width: smartChannelProps.length
5646
3906
  }
5647
3907
  ),
5648
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3908
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5649
3909
  Background,
5650
3910
  {
5651
3911
  $color: waveformBackgroundCss,
@@ -5654,7 +3914,7 @@ var ChannelWithProgress = (_a) => {
5654
3914
  $width: smartChannelProps.length
5655
3915
  }
5656
3916
  )
5657
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3917
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5658
3918
  Background,
5659
3919
  {
5660
3920
  $color: backgroundCss,
@@ -5663,7 +3923,7 @@ var ChannelWithProgress = (_a) => {
5663
3923
  $width: smartChannelProps.length
5664
3924
  }
5665
3925
  ),
5666
- !isPianoRollMode && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3926
+ !isPianoRollMode && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5667
3927
  ProgressOverlay,
5668
3928
  {
5669
3929
  ref: progressRef,
@@ -5673,8 +3933,8 @@ var ChannelWithProgress = (_a) => {
5673
3933
  $width: clipPixelWidth
5674
3934
  }
5675
3935
  ),
5676
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChannelContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5677
- import_ui_components8.SmartChannel,
3936
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ChannelContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3937
+ import_ui_components7.SmartChannel,
5678
3938
  __spreadProps(__spreadValues({}, smartChannelProps), {
5679
3939
  transparentBackground: true,
5680
3940
  midiNotes,
@@ -5686,11 +3946,11 @@ var ChannelWithProgress = (_a) => {
5686
3946
  };
5687
3947
 
5688
3948
  // src/SpectrogramIntegrationContext.tsx
5689
- var import_react32 = require("react");
5690
- var SpectrogramIntegrationContext = (0, import_react32.createContext)(null);
3949
+ var import_react25 = require("react");
3950
+ var SpectrogramIntegrationContext = (0, import_react25.createContext)(null);
5691
3951
  var SpectrogramIntegrationProvider = SpectrogramIntegrationContext.Provider;
5692
3952
  function useSpectrogramIntegration() {
5693
- const context = (0, import_react32.useContext)(SpectrogramIntegrationContext);
3953
+ const context = (0, import_react25.useContext)(SpectrogramIntegrationContext);
5694
3954
  if (!context) {
5695
3955
  throw new Error(
5696
3956
  "useSpectrogramIntegration must be used within <SpectrogramProvider>. Install @waveform-playlist/spectrogram and wrap your app with <SpectrogramProvider>."
@@ -5700,7 +3960,7 @@ function useSpectrogramIntegration() {
5700
3960
  }
5701
3961
 
5702
3962
  // src/components/PlaylistVisualization.tsx
5703
- var import_jsx_runtime10 = require("react/jsx-runtime");
3963
+ var import_jsx_runtime9 = require("react/jsx-runtime");
5704
3964
  var DEFAULT_EMPTY_TRACK_DURATION = 60;
5705
3965
  var ControlSlot = import_styled_components6.default.div.attrs((props) => ({
5706
3966
  style: { height: `${props.$height}px` }
@@ -5719,7 +3979,8 @@ var CustomPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) =>
5719
3979
  visualTimeRef,
5720
3980
  playbackStartTimeRef,
5721
3981
  audioStartPositionRef,
5722
- getPlaybackTime
3982
+ getPlaybackTime,
3983
+ getAudioContextTime
5723
3984
  } = usePlaybackAnimation();
5724
3985
  const visualTime = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
5725
3986
  return renderPlayhead({
@@ -5733,7 +3994,7 @@ var CustomPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) =>
5733
3994
  samplesPerPixel,
5734
3995
  sampleRate,
5735
3996
  controlsOffset: 0,
5736
- getAudioContextTime: () => (0, import_playout7.getGlobalAudioContext)().currentTime,
3997
+ getAudioContextTime,
5737
3998
  getPlaybackTime
5738
3999
  });
5739
4000
  };
@@ -5757,8 +4018,8 @@ var PlaylistVisualization = ({
5757
4018
  recordingState
5758
4019
  }) => {
5759
4020
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
5760
- const theme = (0, import_ui_components9.useTheme)();
5761
- const { isPlaying, getLookAhead } = usePlaybackAnimation();
4021
+ const theme = (0, import_ui_components8.useTheme)();
4022
+ const { isPlaying, getLookAhead, getOutputLatency } = usePlaybackAnimation();
5762
4023
  const {
5763
4024
  selectionStart,
5764
4025
  selectionEnd,
@@ -5773,7 +4034,7 @@ var PlaylistVisualization = ({
5773
4034
  isLoopEnabled,
5774
4035
  indefinitePlayback
5775
4036
  } = usePlaylistState();
5776
- const annotationIntegration = (0, import_react33.useContext)(AnnotationIntegrationContext);
4037
+ const annotationIntegration = (0, import_react26.useContext)(AnnotationIntegrationContext);
5777
4038
  const {
5778
4039
  setAnnotations: _setAnnotations,
5779
4040
  setActiveAnnotationId,
@@ -5803,8 +4064,8 @@ var PlaylistVisualization = ({
5803
4064
  isReady,
5804
4065
  mono
5805
4066
  } = usePlaylistData();
5806
- const spectrogram = (0, import_react33.useContext)(SpectrogramIntegrationContext);
5807
- const perTrackSpectrogramHelpers = (0, import_react33.useMemo)(() => {
4067
+ const spectrogram = (0, import_react26.useContext)(SpectrogramIntegrationContext);
4068
+ const perTrackSpectrogramHelpers = (0, import_react26.useMemo)(() => {
5808
4069
  if (!spectrogram)
5809
4070
  return /* @__PURE__ */ new Map();
5810
4071
  const helpers = /* @__PURE__ */ new Map();
@@ -5823,11 +4084,11 @@ var PlaylistVisualization = ({
5823
4084
  });
5824
4085
  return helpers;
5825
4086
  }, [tracks, spectrogram]);
5826
- const [settingsModalTrackId, setSettingsModalTrackId] = (0, import_react33.useState)(null);
5827
- const [isSelecting, setIsSelecting] = (0, import_react33.useState)(false);
5828
- const mouseDownTimeRef = (0, import_react33.useRef)(0);
5829
- const scrollContainerRef = (0, import_react33.useRef)(null);
5830
- const handleScrollContainerRef = (0, import_react33.useCallback)(
4087
+ const [settingsModalTrackId, setSettingsModalTrackId] = (0, import_react26.useState)(null);
4088
+ const [isSelecting, setIsSelecting] = (0, import_react26.useState)(false);
4089
+ const mouseDownTimeRef = (0, import_react26.useRef)(0);
4090
+ const scrollContainerRef = (0, import_react26.useRef)(null);
4091
+ const handleScrollContainerRef = (0, import_react26.useCallback)(
5831
4092
  (element) => {
5832
4093
  scrollContainerRef.current = element;
5833
4094
  setScrollContainer(element);
@@ -5865,7 +4126,7 @@ var PlaylistVisualization = ({
5865
4126
  );
5866
4127
  }
5867
4128
  });
5868
- const selectTrack = (0, import_react33.useCallback)(
4129
+ const selectTrack = (0, import_react26.useCallback)(
5869
4130
  (trackIndex) => {
5870
4131
  if (trackIndex >= 0 && trackIndex < tracks.length) {
5871
4132
  const track = tracks[trackIndex];
@@ -5935,7 +4196,7 @@ var PlaylistVisualization = ({
5935
4196
  };
5936
4197
  const hasClips = tracks.some((track) => track.clips.length > 0);
5937
4198
  if (hasClips && peaksDataArray.length === 0) {
5938
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className, children: "Loading waveform..." });
4199
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className, children: "Loading waveform..." });
5939
4200
  }
5940
4201
  const trackControlsSlots = controls.show ? peaksDataArray.map((trackClipPeaks, trackIndex) => {
5941
4202
  var _a2, _b2, _c2;
@@ -5951,11 +4212,11 @@ var PlaylistVisualization = ({
5951
4212
  const hasMidiNotes = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);
5952
4213
  const effectiveRenderMode = (_c2 = (_b2 = (_a2 = spectrogram == null ? void 0 : spectrogram.trackSpectrogramOverrides.get(track.id)) == null ? void 0 : _a2.renderMode) != null ? _b2 : track.renderMode) != null ? _c2 : hasMidiNotes ? "piano-roll" : "waveform";
5953
4214
  const maxChannels = getTrackChannelCount(trackClipPeaks, recordingState, track.id, mono);
5954
- const slotHeight = waveHeight * maxChannels + (showClipHeaders ? import_ui_components9.CLIP_HEADER_HEIGHT : 0);
5955
- const trackControlContent = renderTrackControls ? renderTrackControls(trackIndex) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.Controls, { onClick: () => selectTrack(trackIndex), children: [
5956
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.Header, { style: { justifyContent: "center", position: "relative" }, children: [
5957
- onRemoveTrack && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5958
- import_ui_components9.CloseButton,
4215
+ const slotHeight = waveHeight * maxChannels + (showClipHeaders ? import_ui_components8.CLIP_HEADER_HEIGHT : 0);
4216
+ const trackControlContent = renderTrackControls ? renderTrackControls(trackIndex) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ui_components8.Controls, { onClick: () => selectTrack(trackIndex), children: [
4217
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ui_components8.Header, { style: { justifyContent: "center", position: "relative" }, children: [
4218
+ onRemoveTrack && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4219
+ import_ui_components8.CloseButton,
5959
4220
  {
5960
4221
  onClick: (e) => {
5961
4222
  e.stopPropagation();
@@ -5963,7 +4224,7 @@ var PlaylistVisualization = ({
5963
4224
  }
5964
4225
  }
5965
4226
  ),
5966
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4227
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5967
4228
  "span",
5968
4229
  {
5969
4230
  style: {
@@ -5976,8 +4237,8 @@ var PlaylistVisualization = ({
5976
4237
  children: trackState.name || `Track ${trackIndex + 1}`
5977
4238
  }
5978
4239
  ),
5979
- (spectrogram == null ? void 0 : spectrogram.renderMenuItems) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { position: "absolute", right: 0, top: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5980
- import_ui_components9.TrackMenu,
4240
+ (spectrogram == null ? void 0 : spectrogram.renderMenuItems) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { position: "absolute", right: 0, top: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4241
+ import_ui_components8.TrackMenu,
5981
4242
  {
5982
4243
  items: (onClose) => spectrogram.renderMenuItems({
5983
4244
  renderMode: effectiveRenderMode,
@@ -5988,17 +4249,17 @@ var PlaylistVisualization = ({
5988
4249
  }
5989
4250
  ) })
5990
4251
  ] }),
5991
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.ButtonGroup, { children: [
5992
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5993
- import_ui_components9.Button,
4252
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ui_components8.ButtonGroup, { children: [
4253
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4254
+ import_ui_components8.Button,
5994
4255
  {
5995
4256
  $variant: trackState.muted ? "danger" : "outline",
5996
4257
  onClick: () => setTrackMute(trackIndex, !trackState.muted),
5997
4258
  children: "Mute"
5998
4259
  }
5999
4260
  ),
6000
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6001
- import_ui_components9.Button,
4261
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4262
+ import_ui_components8.Button,
6002
4263
  {
6003
4264
  $variant: trackState.soloed ? "info" : "outline",
6004
4265
  onClick: () => setTrackSolo(trackIndex, !trackState.soloed),
@@ -6006,10 +4267,10 @@ var PlaylistVisualization = ({
6006
4267
  }
6007
4268
  )
6008
4269
  ] }),
6009
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.SliderWrapper, { children: [
6010
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ui_components9.VolumeDownIcon, {}),
6011
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6012
- import_ui_components9.Slider,
4270
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ui_components8.SliderWrapper, { children: [
4271
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ui_components8.VolumeDownIcon, {}),
4272
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4273
+ import_ui_components8.Slider,
6013
4274
  {
6014
4275
  min: "0",
6015
4276
  max: "1",
@@ -6018,12 +4279,12 @@ var PlaylistVisualization = ({
6018
4279
  onChange: (e) => setTrackVolume(trackIndex, parseFloat(e.target.value))
6019
4280
  }
6020
4281
  ),
6021
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ui_components9.VolumeUpIcon, {})
4282
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ui_components8.VolumeUpIcon, {})
6022
4283
  ] }),
6023
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.SliderWrapper, { children: [
6024
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "L" }),
6025
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6026
- import_ui_components9.Slider,
4284
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ui_components8.SliderWrapper, { children: [
4285
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "L" }),
4286
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4287
+ import_ui_components8.Slider,
6027
4288
  {
6028
4289
  min: "-1",
6029
4290
  max: "1",
@@ -6032,10 +4293,10 @@ var PlaylistVisualization = ({
6032
4293
  onChange: (e) => setTrackPan(trackIndex, parseFloat(e.target.value))
6033
4294
  }
6034
4295
  ),
6035
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "R" })
4296
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "R" })
6036
4297
  ] })
6037
4298
  ] });
6038
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4299
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6039
4300
  ControlSlot,
6040
4301
  {
6041
4302
  $height: slotHeight,
@@ -6045,9 +4306,9 @@ var PlaylistVisualization = ({
6045
4306
  track.id
6046
4307
  );
6047
4308
  }) : void 0;
6048
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.DevicePixelRatioProvider, { children: [
6049
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6050
- import_ui_components9.PlaylistInfoContext.Provider,
4309
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ui_components8.DevicePixelRatioProvider, { children: [
4310
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4311
+ import_ui_components8.PlaylistInfoContext.Provider,
6051
4312
  {
6052
4313
  value: {
6053
4314
  samplesPerPixel,
@@ -6060,11 +4321,11 @@ var PlaylistVisualization = ({
6060
4321
  barWidth,
6061
4322
  barGap
6062
4323
  },
6063
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6064
- import_ui_components9.Playlist,
4324
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4325
+ import_ui_components8.Playlist,
6065
4326
  {
6066
4327
  theme,
6067
- backgroundColor: theme.playlistBackgroundColor || (0, import_ui_components9.waveformColorToCss)(theme.waveOutlineColor),
4328
+ backgroundColor: theme.playlistBackgroundColor || (0, import_ui_components8.waveformColorToCss)(theme.waveOutlineColor),
6068
4329
  timescaleBackgroundColor: theme.timescaleBackgroundColor,
6069
4330
  timescaleWidth: tracksFullWidth,
6070
4331
  tracksWidth: tracksFullWidth,
@@ -6077,10 +4338,10 @@ var PlaylistVisualization = ({
6077
4338
  "data-playlist-state": isReady ? "ready" : "loading",
6078
4339
  trackControlsSlots,
6079
4340
  timescaleGapHeight: timeScaleHeight > 0 ? timeScaleHeight + 1 : 0,
6080
- timescale: timeScaleHeight > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
6081
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ui_components9.SmartScale, { renderTick }),
6082
- isLoopEnabled && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6083
- import_ui_components9.TimescaleLoopRegion,
4341
+ timescale: timeScaleHeight > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
4342
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ui_components8.SmartScale, { renderTick }),
4343
+ isLoopEnabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4344
+ import_ui_components8.TimescaleLoopRegion,
6084
4345
  {
6085
4346
  startPosition: Math.min(loopStart, loopEnd) * sampleRate / samplesPerPixel,
6086
4347
  endPosition: Math.max(loopStart, loopEnd) * sampleRate / samplesPerPixel,
@@ -6096,7 +4357,7 @@ var PlaylistVisualization = ({
6096
4357
  }
6097
4358
  )
6098
4359
  ] }) : void 0,
6099
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
4360
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
6100
4361
  peaksDataArray.map((trackClipPeaks, trackIndex) => {
6101
4362
  var _a2, _b2, _c2, _d2;
6102
4363
  const track = tracks[trackIndex];
@@ -6109,11 +4370,11 @@ var PlaylistVisualization = ({
6109
4370
  track.id,
6110
4371
  mono
6111
4372
  );
6112
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
6113
- import_ui_components9.Track,
4373
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4374
+ import_ui_components8.Track,
6114
4375
  {
6115
4376
  numChannels: maxChannels,
6116
- backgroundColor: effectiveRenderMode === "piano-roll" ? theme.pianoRollBackgroundColor || "#1a1a2e" : (0, import_ui_components9.waveformColorToCss)(theme.waveOutlineColor),
4377
+ backgroundColor: effectiveRenderMode === "piano-roll" ? theme.pianoRollBackgroundColor || "#1a1a2e" : (0, import_ui_components8.waveformColorToCss)(theme.waveOutlineColor),
6117
4378
  offset: 0,
6118
4379
  width: tracksFullWidth,
6119
4380
  hasClipHeaders: showClipHeaders,
@@ -6125,8 +4386,8 @@ var PlaylistVisualization = ({
6125
4386
  const helpers = perTrackSpectrogramHelpers.get(track.id);
6126
4387
  const trackCfg = helpers == null ? void 0 : helpers.config;
6127
4388
  if (!(trackCfg == null ? void 0 : trackCfg.labels) || !helpers) return null;
6128
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6129
- import_ui_components9.SpectrogramLabels,
4389
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4390
+ import_ui_components8.SpectrogramLabels,
6130
4391
  {
6131
4392
  waveHeight,
6132
4393
  numChannels: maxChannels,
@@ -6143,8 +4404,8 @@ var PlaylistVisualization = ({
6143
4404
  trackClipPeaks.map((clip, clipIndex) => {
6144
4405
  const peaksData = clip.peaks;
6145
4406
  const width = peaksData.length;
6146
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6147
- import_ui_components9.Clip,
4407
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4408
+ import_ui_components8.Clip,
6148
4409
  {
6149
4410
  clipId: clip.clipId,
6150
4411
  trackIndex,
@@ -6173,7 +4434,7 @@ var PlaylistVisualization = ({
6173
4434
  selectTrack(trackIndex);
6174
4435
  },
6175
4436
  children: peaksData.data.map((channelPeaks, channelIndex) => {
6176
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4437
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6177
4438
  ChannelWithProgress,
6178
4439
  {
6179
4440
  index: channelIndex,
@@ -6200,10 +4461,9 @@ var PlaylistVisualization = ({
6200
4461
  );
6201
4462
  }),
6202
4463
  (recordingState == null ? void 0 : recordingState.isRecording) && recordingState.trackId === track.id && ((_d2 = recordingState.peaks[0]) == null ? void 0 : _d2.length) > 0 && (() => {
6203
- const audioCtx = (0, import_playout7.getGlobalAudioContext)();
6204
- const outputLatency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
4464
+ const outputLatency = getOutputLatency();
6205
4465
  const lookAhead = getLookAhead();
6206
- const latencyOffsetSamples = (0, import_core8.audibleLatencySamples)(
4466
+ const latencyOffsetSamples = (0, import_core4.audibleLatencySamples)(
6207
4467
  outputLatency,
6208
4468
  lookAhead,
6209
4469
  sampleRate
@@ -6217,8 +4477,8 @@ var PlaylistVisualization = ({
6217
4477
  const previewChannels = (mono ? recordingState.peaks.slice(0, 1) : recordingState.peaks).map(
6218
4478
  (channelPeaks) => skipPeakElements > 0 && skipPeakElements < channelPeaks.length ? channelPeaks.subarray(skipPeakElements) : channelPeaks
6219
4479
  );
6220
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6221
- import_ui_components9.Clip,
4480
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4481
+ import_ui_components8.Clip,
6222
4482
  {
6223
4483
  clipId: "recording-preview",
6224
4484
  trackIndex,
@@ -6231,7 +4491,7 @@ var PlaylistVisualization = ({
6231
4491
  disableHeaderDrag: true,
6232
4492
  isSelected: track.id === selectedTrackId,
6233
4493
  trackId: track.id,
6234
- children: previewChannels.map((channelPeaks, chIdx) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4494
+ children: previewChannels.map((channelPeaks, chIdx) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6235
4495
  ChannelWithProgress,
6236
4496
  {
6237
4497
  index: chIdx,
@@ -6253,11 +4513,11 @@ var PlaylistVisualization = ({
6253
4513
  track.id
6254
4514
  );
6255
4515
  }),
6256
- annotations.length > 0 && annotationIntegration && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
4516
+ annotations.length > 0 && annotationIntegration && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
6257
4517
  const startPosition = annotation.start * sampleRate / samplesPerPixel;
6258
4518
  const endPosition = annotation.end * sampleRate / samplesPerPixel;
6259
4519
  const label = getAnnotationBoxLabel ? getAnnotationBoxLabel(annotation, index) : annotation.id;
6260
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4520
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6261
4521
  annotationIntegration.AnnotationBox,
6262
4522
  {
6263
4523
  annotationId: annotation.id,
@@ -6273,15 +4533,15 @@ var PlaylistVisualization = ({
6273
4533
  annotation.id
6274
4534
  );
6275
4535
  }) }),
6276
- selectionStart !== selectionEnd && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6277
- import_ui_components9.Selection,
4536
+ selectionStart !== selectionEnd && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4537
+ import_ui_components8.Selection,
6278
4538
  {
6279
4539
  startPosition: Math.min(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
6280
4540
  endPosition: Math.max(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
6281
4541
  color: theme.selectionColor
6282
4542
  }
6283
4543
  ),
6284
- (isPlaying || selectionStart === selectionEnd) && (renderPlayhead ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4544
+ (isPlaying || selectionStart === selectionEnd) && (renderPlayhead ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6285
4545
  CustomPlayhead,
6286
4546
  {
6287
4547
  renderPlayhead,
@@ -6289,14 +4549,14 @@ var PlaylistVisualization = ({
6289
4549
  samplesPerPixel,
6290
4550
  sampleRate
6291
4551
  }
6292
- ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AnimatedPlayhead, { color: theme.playheadColor }))
4552
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AnimatedPlayhead, { color: theme.playheadColor }))
6293
4553
  ] })
6294
4554
  }
6295
4555
  )
6296
4556
  }
6297
4557
  ),
6298
4558
  (spectrogram == null ? void 0 : spectrogram.SettingsModal) && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
6299
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4559
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6300
4560
  spectrogram.SettingsModal,
6301
4561
  {
6302
4562
  open: settingsModalTrackId !== null,
@@ -6316,8 +4576,8 @@ var PlaylistVisualization = ({
6316
4576
  };
6317
4577
 
6318
4578
  // src/components/PlaylistAnnotationList.tsx
6319
- var import_react34 = require("react");
6320
- var import_jsx_runtime11 = require("react/jsx-runtime");
4579
+ var import_react27 = require("react");
4580
+ var import_jsx_runtime10 = require("react/jsx-runtime");
6321
4581
  var PlaylistAnnotationList = ({
6322
4582
  height,
6323
4583
  renderAnnotationItem,
@@ -6331,7 +4591,7 @@ var PlaylistAnnotationList = ({
6331
4591
  const integration = useAnnotationIntegration();
6332
4592
  const { setAnnotations } = usePlaylistControls();
6333
4593
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints, continuousPlay };
6334
- const handleAnnotationUpdate = (0, import_react34.useCallback)(
4594
+ const handleAnnotationUpdate = (0, import_react27.useCallback)(
6335
4595
  (updatedAnnotations) => {
6336
4596
  setAnnotations(updatedAnnotations);
6337
4597
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6339,7 +4599,7 @@ var PlaylistAnnotationList = ({
6339
4599
  [setAnnotations, onAnnotationUpdate]
6340
4600
  );
6341
4601
  const { AnnotationText } = integration;
6342
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4602
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6343
4603
  AnnotationText,
6344
4604
  {
6345
4605
  annotations,
@@ -6358,7 +4618,7 @@ var PlaylistAnnotationList = ({
6358
4618
  };
6359
4619
 
6360
4620
  // src/components/Waveform.tsx
6361
- var import_jsx_runtime12 = require("react/jsx-runtime");
4621
+ var import_jsx_runtime11 = require("react/jsx-runtime");
6362
4622
  var Waveform = ({
6363
4623
  renderTrackControls,
6364
4624
  renderTick,
@@ -6382,8 +4642,8 @@ var Waveform = ({
6382
4642
  const { annotations } = usePlaylistState();
6383
4643
  const clipInteractionEnabled = useClipInteractionEnabled();
6384
4644
  const effectiveInteractiveClips = interactiveClips || clipInteractionEnabled;
6385
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
6386
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4645
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
4646
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
6387
4647
  PlaylistVisualization,
6388
4648
  {
6389
4649
  renderTrackControls,
@@ -6400,7 +4660,7 @@ var Waveform = ({
6400
4660
  recordingState
6401
4661
  }
6402
4662
  ),
6403
- annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4663
+ annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
6404
4664
  PlaylistAnnotationList,
6405
4665
  {
6406
4666
  height: annotationTextHeight,
@@ -6415,10 +4675,10 @@ var Waveform = ({
6415
4675
  };
6416
4676
 
6417
4677
  // src/components/MediaElementPlaylist.tsx
6418
- var import_react37 = require("react");
6419
- var import_react38 = require("@dnd-kit/react");
4678
+ var import_react30 = require("react");
4679
+ var import_react31 = require("@dnd-kit/react");
6420
4680
  var import_modifiers = require("@dnd-kit/abstract/modifiers");
6421
- var import_ui_components11 = require("@waveform-playlist/ui-components");
4681
+ var import_ui_components10 = require("@waveform-playlist/ui-components");
6422
4682
 
6423
4683
  // src/plugins/noDropAnimationPlugins.ts
6424
4684
  var import_dom2 = require("@dnd-kit/dom");
@@ -6440,9 +4700,9 @@ var noDropAnimationPlugins = (defaults) => {
6440
4700
  };
6441
4701
 
6442
4702
  // src/components/AnimatedMediaElementPlayhead.tsx
6443
- var import_react35 = require("react");
4703
+ var import_react28 = require("react");
6444
4704
  var import_styled_components7 = __toESM(require("styled-components"));
6445
- var import_jsx_runtime13 = require("react/jsx-runtime");
4705
+ var import_jsx_runtime12 = require("react/jsx-runtime");
6446
4706
  var PlayheadLine2 = import_styled_components7.default.div`
6447
4707
  position: absolute;
6448
4708
  top: 0;
@@ -6457,11 +4717,11 @@ var PlayheadLine2 = import_styled_components7.default.div`
6457
4717
  var AnimatedMediaElementPlayhead = ({
6458
4718
  color = "#ff0000"
6459
4719
  }) => {
6460
- const playheadRef = (0, import_react35.useRef)(null);
6461
- const animationFrameRef = (0, import_react35.useRef)(null);
4720
+ const playheadRef = (0, import_react28.useRef)(null);
4721
+ const animationFrameRef = (0, import_react28.useRef)(null);
6462
4722
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
6463
4723
  const { samplesPerPixel, sampleRate, progressBarWidth } = useMediaElementData();
6464
- (0, import_react35.useEffect)(() => {
4724
+ (0, import_react28.useEffect)(() => {
6465
4725
  const updatePosition = () => {
6466
4726
  var _a;
6467
4727
  if (playheadRef.current) {
@@ -6485,7 +4745,7 @@ var AnimatedMediaElementPlayhead = ({
6485
4745
  }
6486
4746
  };
6487
4747
  }, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef]);
6488
- (0, import_react35.useEffect)(() => {
4748
+ (0, import_react28.useEffect)(() => {
6489
4749
  var _a;
6490
4750
  if (!isPlaying && playheadRef.current) {
6491
4751
  const time = (_a = currentTimeRef.current) != null ? _a : 0;
@@ -6493,14 +4753,14 @@ var AnimatedMediaElementPlayhead = ({
6493
4753
  playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
6494
4754
  }
6495
4755
  });
6496
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PlayheadLine2, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
4756
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PlayheadLine2, { ref: playheadRef, $color: color, $width: progressBarWidth, "data-playhead": true });
6497
4757
  };
6498
4758
 
6499
4759
  // src/components/ChannelWithMediaElementProgress.tsx
6500
- var import_react36 = require("react");
4760
+ var import_react29 = require("react");
6501
4761
  var import_styled_components8 = __toESM(require("styled-components"));
6502
- var import_ui_components10 = require("@waveform-playlist/ui-components");
6503
- var import_jsx_runtime14 = require("react/jsx-runtime");
4762
+ var import_ui_components9 = require("@waveform-playlist/ui-components");
4763
+ var import_jsx_runtime13 = require("react/jsx-runtime");
6504
4764
  var ChannelWrapper2 = import_styled_components8.default.div`
6505
4765
  position: relative;
6506
4766
  `;
@@ -6537,14 +4797,14 @@ var ChannelWithMediaElementProgress = (_a) => {
6537
4797
  "clipStartSample",
6538
4798
  "clipDurationSamples"
6539
4799
  ]);
6540
- const progressRef = (0, import_react36.useRef)(null);
6541
- const animationFrameRef = (0, import_react36.useRef)(null);
6542
- const theme = (0, import_ui_components10.useTheme)();
6543
- const { waveHeight } = (0, import_ui_components10.usePlaylistInfo)();
4800
+ const progressRef = (0, import_react29.useRef)(null);
4801
+ const animationFrameRef = (0, import_react29.useRef)(null);
4802
+ const theme = (0, import_ui_components9.useTheme)();
4803
+ const { waveHeight } = (0, import_ui_components9.usePlaylistInfo)();
6544
4804
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
6545
4805
  const { samplesPerPixel, sampleRate } = useMediaElementData();
6546
4806
  const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
6547
- (0, import_react36.useEffect)(() => {
4807
+ (0, import_react29.useEffect)(() => {
6548
4808
  const updateProgress = () => {
6549
4809
  var _a2;
6550
4810
  if (progressRef.current) {
@@ -6586,7 +4846,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6586
4846
  smartChannelProps.length,
6587
4847
  currentTimeRef
6588
4848
  ]);
6589
- (0, import_react36.useEffect)(() => {
4849
+ (0, import_react29.useEffect)(() => {
6590
4850
  var _a2;
6591
4851
  if (!isPlaying && progressRef.current) {
6592
4852
  const currentTime = (_a2 = currentTimeRef.current) != null ? _a2 : 0;
@@ -6611,9 +4871,9 @@ var ChannelWithMediaElementProgress = (_a) => {
6611
4871
  } else {
6612
4872
  backgroundColor = (theme == null ? void 0 : theme.selectedWaveOutlineColor) || (theme == null ? void 0 : theme.waveOutlineColor) || "grey";
6613
4873
  }
6614
- const backgroundCss = (0, import_ui_components10.waveformColorToCss)(backgroundColor);
6615
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(ChannelWrapper2, { children: [
6616
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4874
+ const backgroundCss = (0, import_ui_components9.waveformColorToCss)(backgroundColor);
4875
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(ChannelWrapper2, { children: [
4876
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6617
4877
  Background2,
6618
4878
  {
6619
4879
  $color: backgroundCss,
@@ -6622,7 +4882,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6622
4882
  $width: smartChannelProps.length
6623
4883
  }
6624
4884
  ),
6625
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4885
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6626
4886
  ProgressOverlay2,
6627
4887
  {
6628
4888
  ref: progressRef,
@@ -6631,12 +4891,12 @@ var ChannelWithMediaElementProgress = (_a) => {
6631
4891
  $top: smartChannelProps.index * waveHeight
6632
4892
  }
6633
4893
  ),
6634
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChannelContainer2, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui_components10.SmartChannel, __spreadProps(__spreadValues({}, smartChannelProps), { isSelected: true, transparentBackground: true })) })
4894
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChannelContainer2, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ui_components9.SmartChannel, __spreadProps(__spreadValues({}, smartChannelProps), { isSelected: true, transparentBackground: true })) })
6635
4895
  ] });
6636
4896
  };
6637
4897
 
6638
4898
  // src/components/MediaElementPlaylist.tsx
6639
- var import_jsx_runtime15 = require("react/jsx-runtime");
4899
+ var import_jsx_runtime14 = require("react/jsx-runtime");
6640
4900
  var ZERO_REF = { current: 0 };
6641
4901
  var CustomMediaElementPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {
6642
4902
  var _a;
@@ -6662,10 +4922,10 @@ var MediaElementPlaylist = ({
6662
4922
  showFades = false,
6663
4923
  className
6664
4924
  }) => {
6665
- const theme = (0, import_ui_components11.useTheme)();
4925
+ const theme = (0, import_ui_components10.useTheme)();
6666
4926
  const { isPlaying } = useMediaElementAnimation();
6667
4927
  const { annotations, activeAnnotationId } = useMediaElementState();
6668
- const annotationIntegration = (0, import_react37.useContext)(AnnotationIntegrationContext);
4928
+ const annotationIntegration = (0, import_react30.useContext)(AnnotationIntegrationContext);
6669
4929
  const { play, seekTo, setActiveAnnotationId, setAnnotations, setScrollContainer } = useMediaElementControls();
6670
4930
  const {
6671
4931
  duration,
@@ -6681,11 +4941,11 @@ var MediaElementPlaylist = ({
6681
4941
  fadeIn,
6682
4942
  fadeOut
6683
4943
  } = useMediaElementData();
6684
- const [selectionStart, setSelectionStart] = (0, import_react37.useState)(0);
6685
- const [selectionEnd, setSelectionEnd] = (0, import_react37.useState)(0);
6686
- const [isSelecting, setIsSelecting] = (0, import_react37.useState)(false);
6687
- const scrollContainerRef = (0, import_react37.useRef)(null);
6688
- const handleScrollContainerRef = (0, import_react37.useCallback)(
4944
+ const [selectionStart, setSelectionStart] = (0, import_react30.useState)(0);
4945
+ const [selectionEnd, setSelectionEnd] = (0, import_react30.useState)(0);
4946
+ const [isSelecting, setIsSelecting] = (0, import_react30.useState)(false);
4947
+ const scrollContainerRef = (0, import_react30.useRef)(null);
4948
+ const handleScrollContainerRef = (0, import_react30.useCallback)(
6689
4949
  (el) => {
6690
4950
  scrollContainerRef.current = el;
6691
4951
  setScrollContainer(el);
@@ -6693,7 +4953,7 @@ var MediaElementPlaylist = ({
6693
4953
  [setScrollContainer]
6694
4954
  );
6695
4955
  const tracksFullWidth = Math.floor(duration * sampleRate / samplesPerPixel);
6696
- const handleAnnotationClick = (0, import_react37.useCallback)(
4956
+ const handleAnnotationClick = (0, import_react30.useCallback)(
6697
4957
  (annotation) => __async(null, null, function* () {
6698
4958
  setActiveAnnotationId(annotation.id);
6699
4959
  try {
@@ -6708,7 +4968,7 @@ var MediaElementPlaylist = ({
6708
4968
  }),
6709
4969
  [setActiveAnnotationId, play]
6710
4970
  );
6711
- const handleAnnotationUpdate = (0, import_react37.useCallback)(
4971
+ const handleAnnotationUpdate = (0, import_react30.useCallback)(
6712
4972
  (updatedAnnotations) => {
6713
4973
  setAnnotations(updatedAnnotations);
6714
4974
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6722,8 +4982,8 @@ var MediaElementPlaylist = ({
6722
4982
  duration,
6723
4983
  linkEndpoints: linkEndpointsProp
6724
4984
  });
6725
- const mouseDownTimeRef = (0, import_react37.useRef)(0);
6726
- const handleMouseDown = (0, import_react37.useCallback)(
4985
+ const mouseDownTimeRef = (0, import_react30.useRef)(0);
4986
+ const handleMouseDown = (0, import_react30.useCallback)(
6727
4987
  (e) => {
6728
4988
  const rect = e.currentTarget.getBoundingClientRect();
6729
4989
  const x = e.clientX - rect.left;
@@ -6735,7 +4995,7 @@ var MediaElementPlaylist = ({
6735
4995
  },
6736
4996
  [samplesPerPixel, sampleRate]
6737
4997
  );
6738
- const handleMouseMove = (0, import_react37.useCallback)(
4998
+ const handleMouseMove = (0, import_react30.useCallback)(
6739
4999
  (e) => {
6740
5000
  if (!isSelecting) return;
6741
5001
  const rect = e.currentTarget.getBoundingClientRect();
@@ -6745,7 +5005,7 @@ var MediaElementPlaylist = ({
6745
5005
  },
6746
5006
  [isSelecting, samplesPerPixel, sampleRate]
6747
5007
  );
6748
- const handleMouseUp = (0, import_react37.useCallback)(
5008
+ const handleMouseUp = (0, import_react30.useCallback)(
6749
5009
  (e) => {
6750
5010
  if (!isSelecting) return;
6751
5011
  setIsSelecting(false);
@@ -6775,10 +5035,10 @@ var MediaElementPlaylist = ({
6775
5035
  [isSelecting, selectionStart, samplesPerPixel, sampleRate, seekTo, isPlaying, playoutRef, play]
6776
5036
  );
6777
5037
  if (peaksDataArray.length === 0) {
6778
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className, children: "Loading waveform..." });
5038
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className, children: "Loading waveform..." });
6779
5039
  }
6780
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ui_components11.DevicePixelRatioProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6781
- import_ui_components11.PlaylistInfoContext.Provider,
5040
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui_components10.DevicePixelRatioProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5041
+ import_ui_components10.PlaylistInfoContext.Provider,
6782
5042
  {
6783
5043
  value: {
6784
5044
  samplesPerPixel,
@@ -6791,11 +5051,11 @@ var MediaElementPlaylist = ({
6791
5051
  barWidth,
6792
5052
  barGap
6793
5053
  },
6794
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6795
- import_ui_components11.Playlist,
5054
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5055
+ import_ui_components10.Playlist,
6796
5056
  {
6797
5057
  theme,
6798
- backgroundColor: (0, import_ui_components11.waveformColorToCss)(theme.waveOutlineColor),
5058
+ backgroundColor: (0, import_ui_components10.waveformColorToCss)(theme.waveOutlineColor),
6799
5059
  timescaleBackgroundColor: theme.timescaleBackgroundColor,
6800
5060
  timescaleWidth: tracksFullWidth,
6801
5061
  tracksWidth: tracksFullWidth,
@@ -6805,15 +5065,15 @@ var MediaElementPlaylist = ({
6805
5065
  onTracksMouseUp: handleMouseUp,
6806
5066
  scrollContainerRef: handleScrollContainerRef,
6807
5067
  isSelecting,
6808
- timescale: timeScaleHeight > 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ui_components11.SmartScale, {}) : void 0,
6809
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
5068
+ timescale: timeScaleHeight > 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui_components10.SmartScale, {}) : void 0,
5069
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
6810
5070
  peaksDataArray.map((trackClipPeaks, trackIndex) => {
6811
5071
  const maxChannels = trackClipPeaks.length > 0 ? Math.max(...trackClipPeaks.map((clip) => clip.peaks.data.length)) : 1;
6812
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6813
- import_ui_components11.Track,
5072
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5073
+ import_ui_components10.Track,
6814
5074
  {
6815
5075
  numChannels: maxChannels,
6816
- backgroundColor: (0, import_ui_components11.waveformColorToCss)(theme.waveOutlineColor),
5076
+ backgroundColor: (0, import_ui_components10.waveformColorToCss)(theme.waveOutlineColor),
6817
5077
  offset: 0,
6818
5078
  width: tracksFullWidth,
6819
5079
  hasClipHeaders: false,
@@ -6822,8 +5082,8 @@ var MediaElementPlaylist = ({
6822
5082
  children: trackClipPeaks.map((clip, clipIndex) => {
6823
5083
  const peaksData = clip.peaks;
6824
5084
  const width = peaksData.length;
6825
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
6826
- import_ui_components11.Clip,
5085
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
5086
+ import_ui_components10.Clip,
6827
5087
  {
6828
5088
  clipId: clip.clipId,
6829
5089
  trackIndex,
@@ -6837,7 +5097,7 @@ var MediaElementPlaylist = ({
6837
5097
  isSelected: true,
6838
5098
  trackId: `media-element-track-${trackIndex}`,
6839
5099
  children: [
6840
- peaksData.data.map((channelPeaks, channelIndex) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5100
+ peaksData.data.map((channelPeaks, channelIndex) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
6841
5101
  ChannelWithMediaElementProgress,
6842
5102
  {
6843
5103
  index: channelIndex,
@@ -6849,8 +5109,8 @@ var MediaElementPlaylist = ({
6849
5109
  },
6850
5110
  `${trackIndex}-${clipIndex}-${channelIndex}`
6851
5111
  )),
6852
- showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6853
- import_ui_components11.FadeOverlay,
5112
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5113
+ import_ui_components10.FadeOverlay,
6854
5114
  {
6855
5115
  left: 0,
6856
5116
  width: Math.floor(fadeIn.duration * sampleRate / samplesPerPixel),
@@ -6858,8 +5118,8 @@ var MediaElementPlaylist = ({
6858
5118
  curveType: fadeIn.type
6859
5119
  }
6860
5120
  ),
6861
- showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6862
- import_ui_components11.FadeOverlay,
5121
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5122
+ import_ui_components10.FadeOverlay,
6863
5123
  {
6864
5124
  left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
6865
5125
  width: Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
@@ -6876,19 +5136,19 @@ var MediaElementPlaylist = ({
6876
5136
  trackIndex
6877
5137
  );
6878
5138
  }),
6879
- annotations.length > 0 && annotationIntegration && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6880
- import_react38.DragDropProvider,
5139
+ annotations.length > 0 && annotationIntegration && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5140
+ import_react31.DragDropProvider,
6881
5141
  {
6882
5142
  onDragStart,
6883
5143
  onDragMove,
6884
5144
  onDragEnd,
6885
5145
  modifiers: editable ? [import_modifiers.RestrictToHorizontalAxis] : [],
6886
5146
  plugins: noDropAnimationPlugins,
6887
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
5147
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(annotationIntegration.AnnotationBoxesWrapper, { height: 30, width: tracksFullWidth, children: annotations.map((annotation, index) => {
6888
5148
  const startPosition = annotation.start * sampleRate / samplesPerPixel;
6889
5149
  const endPosition = annotation.end * sampleRate / samplesPerPixel;
6890
5150
  const label = getAnnotationBoxLabel ? getAnnotationBoxLabel(annotation, index) : annotation.id;
6891
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5151
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
6892
5152
  annotationIntegration.AnnotationBox,
6893
5153
  {
6894
5154
  annotationId: annotation.id,
@@ -6906,15 +5166,15 @@ var MediaElementPlaylist = ({
6906
5166
  }) })
6907
5167
  }
6908
5168
  ),
6909
- selectionStart !== selectionEnd && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6910
- import_ui_components11.Selection,
5169
+ selectionStart !== selectionEnd && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5170
+ import_ui_components10.Selection,
6911
5171
  {
6912
5172
  startPosition: Math.min(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
6913
5173
  endPosition: Math.max(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
6914
5174
  color: theme.selectionColor
6915
5175
  }
6916
5176
  ),
6917
- renderPlayhead ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5177
+ renderPlayhead ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
6918
5178
  CustomMediaElementPlayhead,
6919
5179
  {
6920
5180
  renderPlayhead,
@@ -6922,7 +5182,7 @@ var MediaElementPlaylist = ({
6922
5182
  samplesPerPixel,
6923
5183
  sampleRate
6924
5184
  }
6925
- ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AnimatedMediaElementPlayhead, { color: theme.playheadColor })
5185
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AnimatedMediaElementPlayhead, { color: theme.playheadColor })
6926
5186
  ] })
6927
5187
  }
6928
5188
  )
@@ -6931,8 +5191,8 @@ var MediaElementPlaylist = ({
6931
5191
  };
6932
5192
 
6933
5193
  // src/components/MediaElementAnnotationList.tsx
6934
- var import_react39 = require("react");
6935
- var import_jsx_runtime16 = require("react/jsx-runtime");
5194
+ var import_react32 = require("react");
5195
+ var import_jsx_runtime15 = require("react/jsx-runtime");
6936
5196
  var MediaElementAnnotationList = ({
6937
5197
  height,
6938
5198
  renderAnnotationItem,
@@ -6947,7 +5207,7 @@ var MediaElementAnnotationList = ({
6947
5207
  const integration = useAnnotationIntegration();
6948
5208
  const { setAnnotations } = useMediaElementControls();
6949
5209
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints: false, continuousPlay };
6950
- const handleAnnotationUpdate = (0, import_react39.useCallback)(
5210
+ const handleAnnotationUpdate = (0, import_react32.useCallback)(
6951
5211
  (updatedAnnotations) => {
6952
5212
  setAnnotations(updatedAnnotations);
6953
5213
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6955,7 +5215,7 @@ var MediaElementAnnotationList = ({
6955
5215
  [setAnnotations, onAnnotationUpdate]
6956
5216
  );
6957
5217
  const { AnnotationText } = integration;
6958
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5218
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6959
5219
  AnnotationText,
6960
5220
  {
6961
5221
  annotations,
@@ -6974,7 +5234,7 @@ var MediaElementAnnotationList = ({
6974
5234
  };
6975
5235
 
6976
5236
  // src/components/MediaElementWaveform.tsx
6977
- var import_jsx_runtime17 = require("react/jsx-runtime");
5237
+ var import_jsx_runtime16 = require("react/jsx-runtime");
6978
5238
  var MediaElementWaveform = ({
6979
5239
  annotationTextHeight,
6980
5240
  getAnnotationBoxLabel,
@@ -6989,8 +5249,8 @@ var MediaElementWaveform = ({
6989
5249
  className
6990
5250
  }) => {
6991
5251
  const { annotations } = useMediaElementState();
6992
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
6993
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5252
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5253
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6994
5254
  MediaElementPlaylist,
6995
5255
  {
6996
5256
  getAnnotationBoxLabel,
@@ -7002,7 +5262,7 @@ var MediaElementWaveform = ({
7002
5262
  className
7003
5263
  }
7004
5264
  ),
7005
- annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5265
+ annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7006
5266
  MediaElementAnnotationList,
7007
5267
  {
7008
5268
  height: annotationTextHeight,
@@ -7077,11 +5337,11 @@ var KeyboardShortcuts = ({
7077
5337
  };
7078
5338
 
7079
5339
  // src/components/ClipInteractionProvider.tsx
7080
- var import_react40 = __toESM(require("react"));
7081
- var import_react41 = require("@dnd-kit/react");
5340
+ var import_react33 = __toESM(require("react"));
5341
+ var import_react34 = require("@dnd-kit/react");
7082
5342
  var import_modifiers2 = require("@dnd-kit/abstract/modifiers");
7083
- var import_core10 = require("@waveform-playlist/core");
7084
- var import_ui_components12 = require("@waveform-playlist/ui-components");
5343
+ var import_core6 = require("@waveform-playlist/core");
5344
+ var import_ui_components11 = require("@waveform-playlist/ui-components");
7085
5345
 
7086
5346
  // src/modifiers/ClipCollisionModifier.ts
7087
5347
  var import_abstract = require("@dnd-kit/abstract");
@@ -7109,7 +5369,7 @@ var ClipCollisionModifier = _ClipCollisionModifier;
7109
5369
 
7110
5370
  // src/modifiers/SnapToGridModifier.ts
7111
5371
  var import_abstract2 = require("@dnd-kit/abstract");
7112
- var import_core9 = require("@waveform-playlist/core");
5372
+ var import_core5 = require("@waveform-playlist/core");
7113
5373
  var _SnapToGridModifier = class _SnapToGridModifier extends import_abstract2.Modifier {
7114
5374
  apply(operation) {
7115
5375
  const { transform, source } = operation;
@@ -7130,18 +5390,18 @@ var _SnapToGridModifier = class _SnapToGridModifier extends import_abstract2.Mod
7130
5390
  }
7131
5391
  const { snapTo, bpm, timeSignature, sampleRate } = this.options;
7132
5392
  if (snapTo === "off") return transform;
7133
- const gridTicks = snapTo === "bar" ? (0, import_core9.ticksPerBar)(timeSignature) : (0, import_core9.ticksPerBeat)(timeSignature);
5393
+ const gridTicks = snapTo === "bar" ? (0, import_core5.ticksPerBar)(timeSignature) : (0, import_core5.ticksPerBeat)(timeSignature);
7134
5394
  if (startSample !== void 0) {
7135
5395
  const proposedSamples = startSample + transform.x * samplesPerPixel;
7136
- const proposedTicks = (0, import_core9.samplesToTicks)(proposedSamples, bpm, sampleRate);
7137
- const snappedTicks2 = (0, import_core9.snapToGrid)(proposedTicks, gridTicks);
7138
- const snappedSamples2 = (0, import_core9.ticksToSamples)(snappedTicks2, bpm, sampleRate);
5396
+ const proposedTicks = (0, import_core5.samplesToTicks)(proposedSamples, bpm, sampleRate);
5397
+ const snappedTicks2 = (0, import_core5.snapToGrid)(proposedTicks, gridTicks);
5398
+ const snappedSamples2 = (0, import_core5.ticksToSamples)(snappedTicks2, bpm, sampleRate);
7139
5399
  return { x: (snappedSamples2 - startSample) / samplesPerPixel, y: 0 };
7140
5400
  }
7141
5401
  const deltaSamples = transform.x * samplesPerPixel;
7142
- const deltaTicks = (0, import_core9.samplesToTicks)(deltaSamples, bpm, sampleRate);
7143
- const snappedTicks = (0, import_core9.snapToGrid)(deltaTicks, gridTicks);
7144
- const snappedSamples = (0, import_core9.ticksToSamples)(snappedTicks, bpm, sampleRate);
5402
+ const deltaTicks = (0, import_core5.samplesToTicks)(deltaSamples, bpm, sampleRate);
5403
+ const snappedTicks = (0, import_core5.snapToGrid)(deltaTicks, gridTicks);
5404
+ const snappedSamples = (0, import_core5.ticksToSamples)(snappedTicks, bpm, sampleRate);
7145
5405
  return { x: snappedSamples / samplesPerPixel, y: 0 };
7146
5406
  }
7147
5407
  };
@@ -7149,7 +5409,7 @@ _SnapToGridModifier.configure = (0, import_abstract2.configurator)(_SnapToGridMo
7149
5409
  var SnapToGridModifier = _SnapToGridModifier;
7150
5410
 
7151
5411
  // src/components/ClipInteractionProvider.tsx
7152
- var import_jsx_runtime18 = require("react/jsx-runtime");
5412
+ var import_jsx_runtime17 = require("react/jsx-runtime");
7153
5413
  var NOOP_TRACKS_CHANGE = () => {
7154
5414
  };
7155
5415
  var ClipInteractionProvider = ({
@@ -7159,28 +5419,28 @@ var ClipInteractionProvider = ({
7159
5419
  }) => {
7160
5420
  const { tracks, samplesPerPixel, sampleRate, playoutRef, isDraggingRef, onTracksChange } = usePlaylistData();
7161
5421
  const { setSelectedTrackId } = usePlaylistControls();
7162
- const beatsAndBars = (0, import_ui_components12.useBeatsAndBars)();
5422
+ const beatsAndBars = (0, import_ui_components11.useBeatsAndBars)();
7163
5423
  const useBeatsSnap = snap && beatsAndBars != null && beatsAndBars.scaleMode === "beats" && beatsAndBars.snapTo !== "off";
7164
5424
  const useTimescaleSnap = snap && !useBeatsSnap;
7165
- (0, import_react40.useEffect)(() => {
5425
+ (0, import_react33.useEffect)(() => {
7166
5426
  if (onTracksChange == null) {
7167
5427
  console.warn(
7168
5428
  "[waveform-playlist] ClipInteractionProvider: onTracksChange is not set on WaveformPlaylistProvider. Drag and trim edits will not be persisted."
7169
5429
  );
7170
5430
  }
7171
5431
  }, [onTracksChange]);
7172
- const snapSamplePosition = (0, import_react40.useMemo)(() => {
5432
+ const snapSamplePosition = (0, import_react33.useMemo)(() => {
7173
5433
  if (useBeatsSnap && beatsAndBars) {
7174
5434
  const { bpm, timeSignature, snapTo } = beatsAndBars;
7175
- const gridTicks = snapTo === "bar" ? (0, import_core10.ticksPerBar)(timeSignature) : (0, import_core10.ticksPerBeat)(timeSignature);
5435
+ const gridTicks = snapTo === "bar" ? (0, import_core6.ticksPerBar)(timeSignature) : (0, import_core6.ticksPerBeat)(timeSignature);
7176
5436
  return (samplePos) => {
7177
- const ticks = (0, import_core10.samplesToTicks)(samplePos, bpm, sampleRate);
7178
- const snapped = (0, import_core10.snapToGrid)(ticks, gridTicks);
7179
- return (0, import_core10.ticksToSamples)(snapped, bpm, sampleRate);
5437
+ const ticks = (0, import_core6.samplesToTicks)(samplePos, bpm, sampleRate);
5438
+ const snapped = (0, import_core6.snapToGrid)(ticks, gridTicks);
5439
+ return (0, import_core6.ticksToSamples)(snapped, bpm, sampleRate);
7180
5440
  };
7181
5441
  }
7182
5442
  if (useTimescaleSnap) {
7183
- const gridSamples = Math.round((0, import_ui_components12.getScaleInfo)(samplesPerPixel).smallStep / 1e3 * sampleRate);
5443
+ const gridSamples = Math.round((0, import_ui_components11.getScaleInfo)(samplesPerPixel).smallStep / 1e3 * sampleRate);
7184
5444
  return (samplePos) => Math.round(samplePos / gridSamples) * gridSamples;
7185
5445
  }
7186
5446
  return void 0;
@@ -7198,7 +5458,7 @@ var ClipInteractionProvider = ({
7198
5458
  isDraggingRef,
7199
5459
  snapSamplePosition
7200
5460
  });
7201
- const onDragStart = import_react40.default.useCallback(
5461
+ const onDragStart = import_react33.default.useCallback(
7202
5462
  (event) => {
7203
5463
  var _a, _b, _c;
7204
5464
  const trackIndex = (_c = (_b = (_a = event.operation) == null ? void 0 : _a.source) == null ? void 0 : _b.data) == null ? void 0 : _c.trackIndex;
@@ -7209,7 +5469,7 @@ var ClipInteractionProvider = ({
7209
5469
  },
7210
5470
  [handleDragStart, tracks, setSelectedTrackId]
7211
5471
  );
7212
- const modifiers = (0, import_react40.useMemo)(() => {
5472
+ const modifiers = (0, import_react33.useMemo)(() => {
7213
5473
  const mods = [import_modifiers2.RestrictToHorizontalAxis];
7214
5474
  if (useBeatsSnap && beatsAndBars) {
7215
5475
  mods.push(
@@ -7226,7 +5486,7 @@ var ClipInteractionProvider = ({
7226
5486
  mods.push(
7227
5487
  SnapToGridModifier.configure({
7228
5488
  mode: "timescale",
7229
- gridSamples: Math.round((0, import_ui_components12.getScaleInfo)(samplesPerPixel).smallStep / 1e3 * sampleRate),
5489
+ gridSamples: Math.round((0, import_ui_components11.getScaleInfo)(samplesPerPixel).smallStep / 1e3 * sampleRate),
7230
5490
  samplesPerPixel
7231
5491
  })
7232
5492
  );
@@ -7234,8 +5494,8 @@ var ClipInteractionProvider = ({
7234
5494
  mods.push(ClipCollisionModifier.configure({ tracks, samplesPerPixel }));
7235
5495
  return mods;
7236
5496
  }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, tracks, samplesPerPixel, sampleRate]);
7237
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ClipInteractionContextProvider, { value: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7238
- import_react41.DragDropProvider,
5497
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ClipInteractionContextProvider, { value: true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5498
+ import_react34.DragDropProvider,
7239
5499
  {
7240
5500
  sensors,
7241
5501
  onDragStart,
@@ -7258,7 +5518,6 @@ var ClipInteractionProvider = ({
7258
5518
  ContinuousPlayCheckbox,
7259
5519
  DownloadAnnotationsButton,
7260
5520
  EditableCheckbox,
7261
- ExportWavButton,
7262
5521
  FastForwardButton,
7263
5522
  KeyboardShortcuts,
7264
5523
  LinkEndpointsCheckbox,
@@ -7281,17 +5540,10 @@ var ClipInteractionProvider = ({
7281
5540
  SpectrogramIntegrationProvider,
7282
5541
  StopButton,
7283
5542
  TimeFormatSelect,
7284
- Tone,
7285
5543
  Waveform,
7286
5544
  WaveformPlaylistProvider,
7287
5545
  ZoomInButton,
7288
5546
  ZoomOutButton,
7289
- createEffectChain,
7290
- createEffectInstance,
7291
- effectCategories,
7292
- effectDefinitions,
7293
- getEffectDefinition,
7294
- getEffectsByCategory,
7295
5547
  getShortcutLabel,
7296
5548
  getWaveformDataMetadata,
7297
5549
  loadPeaksFromWaveformData,
@@ -7300,30 +5552,24 @@ var ClipInteractionProvider = ({
7300
5552
  useAnnotationDragHandlers,
7301
5553
  useAnnotationIntegration,
7302
5554
  useAnnotationKeyboardControls,
7303
- useAudioTracks,
7304
5555
  useClipDragHandlers,
7305
5556
  useClipInteractionEnabled,
7306
5557
  useClipSplitting,
7307
5558
  useDragSensors,
7308
- useDynamicEffects,
7309
- useDynamicTracks,
7310
- useExportWav,
7311
5559
  useKeyboardShortcuts,
7312
- useMasterAnalyser,
7313
5560
  useMasterVolume,
7314
5561
  useMediaElementAnimation,
7315
5562
  useMediaElementControls,
7316
5563
  useMediaElementData,
7317
5564
  useMediaElementState,
7318
- useOutputMeter,
7319
5565
  usePlaybackAnimation,
7320
5566
  usePlaybackShortcuts,
7321
5567
  usePlaylistControls,
7322
5568
  usePlaylistData,
5569
+ usePlaylistDataOptional,
7323
5570
  usePlaylistState,
7324
5571
  useSpectrogramIntegration,
7325
5572
  useTimeFormat,
7326
- useTrackDynamicEffects,
7327
5573
  useZoomControls,
7328
5574
  waveformDataToPeaks
7329
5575
  });