@waveform-playlist/browser 13.1.3 → 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,22 +122,16 @@ __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,
@@ -154,20 +140,47 @@ __export(index_exports, {
154
140
  usePlaylistState: () => usePlaylistState,
155
141
  useSpectrogramIntegration: () => useSpectrogramIntegration,
156
142
  useTimeFormat: () => useTimeFormat,
157
- useTrackDynamicEffects: () => useTrackDynamicEffects,
158
143
  useZoomControls: () => useZoomControls,
159
144
  waveformDataToPeaks: () => waveformDataToPeaks
160
145
  });
161
146
  module.exports = __toCommonJS(index_exports);
162
- var Tone2 = __toESM(require("tone"));
163
147
 
164
148
  // src/WaveformPlaylistContext.tsx
165
- var import_react24 = require("react");
149
+ var import_react17 = require("react");
166
150
  var import_styled_components = require("styled-components");
167
- 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
168
182
  var import_engine3 = require("@waveform-playlist/engine");
169
183
  var import_ui_components2 = require("@waveform-playlist/ui-components");
170
- var import_tone4 = require("tone");
171
184
 
172
185
  // src/waveformDataLoader.ts
173
186
  var import_waveform_data = __toESM(require("waveform-data"));
@@ -333,9 +346,11 @@ function extractPeaksFromWaveformDataFull(waveformData, samplesPerPixel, isMono,
333
346
  }
334
347
 
335
348
  // src/soundFontSync.ts
336
- var import_playout = require("@waveform-playlist/playout");
349
+ function supportsSoundFont(adapter) {
350
+ return adapter != null && typeof adapter.setSoundFontCache === "function";
351
+ }
337
352
  function syncSoundFontCacheToAdapter(adapter, cache) {
338
- if (!(0, import_playout.isToneAdapter)(adapter)) return;
353
+ if (!supportsSoundFont(adapter)) return;
339
354
  adapter.setSoundFontCache(cache);
340
355
  }
341
356
 
@@ -592,223 +607,8 @@ function useUndoState({ engineRef }) {
592
607
  };
593
608
  }
594
609
 
595
- // src/hooks/useAudioEffects.ts
596
- var import_react8 = require("react");
597
- var import_tone = require("tone");
598
- var useMasterAnalyser = (fftSize = 256) => {
599
- const analyserRef = (0, import_react8.useRef)(null);
600
- const masterEffects = (0, import_react8.useCallback)(
601
- (masterGainNode, destination, _isOffline) => {
602
- const analyserNode = new import_tone.Analyser("fft", fftSize);
603
- masterGainNode.connect(analyserNode);
604
- masterGainNode.connect(destination);
605
- analyserRef.current = analyserNode;
606
- return function cleanup() {
607
- analyserNode.dispose();
608
- analyserRef.current = null;
609
- };
610
- },
611
- [fftSize]
612
- );
613
- return { analyserRef, masterEffects };
614
- };
615
-
616
- // src/hooks/useAudioTracks.ts
617
- var import_react9 = require("react");
618
- var import_core = require("@waveform-playlist/core");
619
- var Tone = __toESM(require("tone"));
620
- function buildTrackFromConfig(config, index, audioBuffer, stableIds, contextSampleRate = 48e3) {
621
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
622
- const buffer = audioBuffer != null ? audioBuffer : config.audioBuffer;
623
- const sampleRate = (_c = (_b = buffer == null ? void 0 : buffer.sampleRate) != null ? _b : (_a = config.waveformData) == null ? void 0 : _a.sample_rate) != null ? _c : contextSampleRate;
624
- 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;
625
- if (sourceDuration === void 0) {
626
- console.warn(
627
- `[waveform-playlist] Track ${index + 1} ("${(_h = config.name) != null ? _h : "unnamed"}"): Cannot create track \u2014 provide duration, audioBuffer, or waveformData with duration.`
628
- );
629
- return null;
630
- }
631
- const clip = (0, import_core.createClipFromSeconds)({
632
- audioBuffer: buffer,
633
- sampleRate,
634
- sourceDuration,
635
- startTime: (_i = config.startTime) != null ? _i : 0,
636
- duration: (_j = config.duration) != null ? _j : sourceDuration,
637
- offset: (_k = config.offset) != null ? _k : 0,
638
- name: config.name || `Track ${index + 1}`,
639
- fadeIn: config.fadeIn,
640
- fadeOut: config.fadeOut,
641
- waveformData: config.waveformData
642
- });
643
- if (isNaN(clip.startSample) || isNaN(clip.durationSamples) || isNaN(clip.offsetSamples)) {
644
- console.error(
645
- `[waveform-playlist] Invalid clip values for track ${index + 1} ("${(_l = config.name) != null ? _l : "unnamed"}"): startSample=${clip.startSample}, durationSamples=${clip.durationSamples}, offsetSamples=${clip.offsetSamples}`
646
- );
647
- return null;
648
- }
649
- const track = __spreadProps(__spreadValues({}, (0, import_core.createTrack)({
650
- name: config.name || `Track ${index + 1}`,
651
- clips: [clip],
652
- muted: (_m = config.muted) != null ? _m : false,
653
- soloed: (_n = config.soloed) != null ? _n : false,
654
- volume: (_o = config.volume) != null ? _o : 1,
655
- pan: (_p = config.pan) != null ? _p : 0,
656
- color: config.color
657
- })), {
658
- effects: config.effects,
659
- renderMode: config.renderMode,
660
- spectrogramConfig: config.spectrogramConfig,
661
- spectrogramColorMap: config.spectrogramColorMap
662
- });
663
- const existingIds = stableIds.get(index);
664
- if (existingIds) {
665
- track.id = existingIds.trackId;
666
- track.clips[0] = __spreadProps(__spreadValues({}, track.clips[0]), { id: existingIds.clipId });
667
- } else {
668
- stableIds.set(index, { trackId: track.id, clipId: track.clips[0].id });
669
- }
670
- return track;
671
- }
672
- function useAudioTracks(configs, options = {}) {
673
- const { immediate = false, progressive = false } = options;
674
- const isImmediate = immediate || progressive;
675
- const [loading, setLoading] = (0, import_react9.useState)(true);
676
- const [error, setError] = (0, import_react9.useState)(null);
677
- const [loadedCount, setLoadedCount] = (0, import_react9.useState)(0);
678
- const totalCount = configs.length;
679
- const [loadedBuffers, setLoadedBuffers] = (0, import_react9.useState)(/* @__PURE__ */ new Map());
680
- const stableIdsRef = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
681
- const contextSampleRateRef = (0, import_react9.useRef)(48e3);
682
- const derivedTracks = (0, import_react9.useMemo)(() => {
683
- if (!isImmediate) return null;
684
- const result = [];
685
- for (let i = 0; i < configs.length; i++) {
686
- const track = buildTrackFromConfig(
687
- configs[i],
688
- i,
689
- loadedBuffers.get(i),
690
- stableIdsRef.current,
691
- contextSampleRateRef.current
692
- );
693
- if (track) result.push(track);
694
- }
695
- return result;
696
- }, [isImmediate, configs, loadedBuffers]);
697
- const [tracks, setTracks] = (0, import_react9.useState)(derivedTracks != null ? derivedTracks : []);
698
- const prevDerivedRef = (0, import_react9.useRef)(derivedTracks);
699
- if (derivedTracks !== prevDerivedRef.current) {
700
- prevDerivedRef.current = derivedTracks;
701
- if (derivedTracks) setTracks(derivedTracks);
702
- }
703
- (0, import_react9.useEffect)(() => {
704
- if (configs.length === 0) {
705
- setTracks([]);
706
- setLoading(false);
707
- setLoadedCount(0);
708
- return;
709
- }
710
- let cancelled = false;
711
- const abortController = new AbortController();
712
- const loadTracks = () => __async(null, null, function* () {
713
- try {
714
- setLoading(true);
715
- setError(null);
716
- setLoadedCount(0);
717
- if (isImmediate) {
718
- setLoadedBuffers(/* @__PURE__ */ new Map());
719
- }
720
- const audioContext = Tone.getContext().rawContext;
721
- contextSampleRateRef.current = audioContext.sampleRate;
722
- const loadPromises = configs.map((config, index) => __async(null, null, function* () {
723
- if (config.audioBuffer) {
724
- if (isImmediate && !cancelled) {
725
- setLoadedBuffers((prev) => {
726
- const next = new Map(prev);
727
- next.set(index, config.audioBuffer);
728
- return next;
729
- });
730
- setLoadedCount((prev) => prev + 1);
731
- return;
732
- }
733
- return buildTrackFromConfig(
734
- config,
735
- index,
736
- config.audioBuffer,
737
- stableIdsRef.current,
738
- audioContext.sampleRate
739
- );
740
- }
741
- if (!config.src && config.waveformData) {
742
- if (isImmediate && !cancelled) {
743
- setLoadedCount((prev) => prev + 1);
744
- return;
745
- }
746
- return buildTrackFromConfig(
747
- config,
748
- index,
749
- void 0,
750
- stableIdsRef.current,
751
- audioContext.sampleRate
752
- );
753
- }
754
- if (!config.src) {
755
- throw new Error(`Track ${index + 1}: Must provide src, audioBuffer, or waveformData`);
756
- }
757
- const response = yield fetch(config.src, { signal: abortController.signal });
758
- if (!response.ok) {
759
- throw new Error(`Failed to fetch ${config.src}: ${response.statusText}`);
760
- }
761
- const arrayBuffer = yield response.arrayBuffer();
762
- const audioBuffer = yield audioContext.decodeAudioData(arrayBuffer);
763
- if (!audioBuffer || !audioBuffer.sampleRate || !audioBuffer.duration) {
764
- throw new Error(`Invalid audio buffer for ${config.src}`);
765
- }
766
- if (isImmediate && !cancelled) {
767
- setLoadedBuffers((prev) => {
768
- const next = new Map(prev);
769
- next.set(index, audioBuffer);
770
- return next;
771
- });
772
- setLoadedCount((prev) => prev + 1);
773
- return;
774
- }
775
- return buildTrackFromConfig(
776
- config,
777
- index,
778
- audioBuffer,
779
- stableIdsRef.current,
780
- audioContext.sampleRate
781
- );
782
- }));
783
- const loadedTracks = yield Promise.all(loadPromises);
784
- if (!cancelled) {
785
- if (!isImmediate) {
786
- const validTracks = loadedTracks.filter((t) => t != null);
787
- setTracks(validTracks);
788
- setLoadedCount(validTracks.length);
789
- }
790
- setLoading(false);
791
- }
792
- } catch (err) {
793
- if (!cancelled) {
794
- const errorMessage = err instanceof Error ? err.message : "Unknown error loading audio";
795
- setError(errorMessage);
796
- setLoading(false);
797
- console.error(`[waveform-playlist] Error loading audio tracks: ${errorMessage}`);
798
- }
799
- }
800
- });
801
- loadTracks();
802
- return () => {
803
- cancelled = true;
804
- abortController.abort();
805
- };
806
- }, [configs, isImmediate]);
807
- return { tracks, loading, error, loadedCount, totalCount };
808
- }
809
-
810
610
  // src/hooks/useClipDragHandlers.ts
811
- var import_react10 = __toESM(require("react"));
611
+ var import_react8 = __toESM(require("react"));
812
612
 
813
613
  // src/utils/boundaryTrim.ts
814
614
  var import_engine = require("@waveform-playlist/engine");
@@ -862,11 +662,11 @@ function useClipDragHandlers({
862
662
  snapSamplePosition
863
663
  }) {
864
664
  const { sampleRate } = usePlaylistData();
865
- const snapSamplePositionRef = import_react10.default.useRef(snapSamplePosition);
665
+ const snapSamplePositionRef = import_react8.default.useRef(snapSamplePosition);
866
666
  snapSamplePositionRef.current = snapSamplePosition;
867
- const originalClipStateRef = import_react10.default.useRef(null);
868
- const lastBoundaryDeltaRef = import_react10.default.useRef(0);
869
- 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(
870
670
  (event) => {
871
671
  var _a;
872
672
  const data = (_a = event.operation.source) == null ? void 0 : _a.data;
@@ -902,7 +702,7 @@ function useClipDragHandlers({
902
702
  },
903
703
  [tracks, isDraggingRef, engineRef]
904
704
  );
905
- const onDragMove = import_react10.default.useCallback(
705
+ const onDragMove = import_react8.default.useCallback(
906
706
  (event) => {
907
707
  var _a, _b, _c;
908
708
  const data = (_a = event.operation.source) == null ? void 0 : _a.data;
@@ -964,7 +764,7 @@ function useClipDragHandlers({
964
764
  },
965
765
  [tracks, onTracksChange, samplesPerPixel, sampleRate]
966
766
  );
967
- const onDragEnd = import_react10.default.useCallback(
767
+ const onDragEnd = import_react8.default.useCallback(
968
768
  (event) => {
969
769
  var _a, _b, _c, _d, _e, _f, _g;
970
770
  if (event.canceled) {
@@ -1042,19 +842,19 @@ function useClipDragHandlers({
1042
842
  }
1043
843
 
1044
844
  // src/hooks/useAnnotationDragHandlers.ts
1045
- var import_react11 = __toESM(require("react"));
1046
- var import_playout2 = require("@waveform-playlist/playout");
845
+ var import_react9 = __toESM(require("react"));
1047
846
  var LINK_THRESHOLD = 0.01;
1048
847
  function useAnnotationDragHandlers({
1049
848
  annotations,
1050
849
  onAnnotationsChange,
1051
850
  samplesPerPixel,
1052
- 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,
1053
853
  duration,
1054
854
  linkEndpoints
1055
855
  }) {
1056
- const originalAnnotationStateRef = import_react11.default.useRef(null);
1057
- const onDragStart = import_react11.default.useCallback(
856
+ const originalAnnotationStateRef = import_react9.default.useRef(null);
857
+ const onDragStart = import_react9.default.useCallback(
1058
858
  (event) => {
1059
859
  var _a;
1060
860
  const data = (_a = event.operation.source) == null ? void 0 : _a.data;
@@ -1073,7 +873,7 @@ function useAnnotationDragHandlers({
1073
873
  },
1074
874
  [annotations]
1075
875
  );
1076
- const onDragMove = import_react11.default.useCallback(
876
+ const onDragMove = import_react9.default.useCallback(
1077
877
  (event) => {
1078
878
  var _a, _b, _c;
1079
879
  if (!originalAnnotationStateRef.current) {
@@ -1099,7 +899,7 @@ function useAnnotationDragHandlers({
1099
899
  },
1100
900
  [annotations, onAnnotationsChange, samplesPerPixel, sampleRate, duration, linkEndpoints]
1101
901
  );
1102
- const onDragEnd = import_react11.default.useCallback(
902
+ const onDragEnd = import_react9.default.useCallback(
1103
903
  (event) => {
1104
904
  if (event.canceled && originalAnnotationStateRef.current) {
1105
905
  const { annotationIndex, start, end } = originalAnnotationStateRef.current;
@@ -1207,7 +1007,7 @@ function updateAnnotationBoundaries({
1207
1007
  }
1208
1008
 
1209
1009
  // src/hooks/useDragSensors.ts
1210
- var import_react12 = require("react");
1010
+ var import_react10 = require("react");
1211
1011
  var import_dom = require("@dnd-kit/dom");
1212
1012
  function useDragSensors(options = {}) {
1213
1013
  const {
@@ -1216,7 +1016,7 @@ function useDragSensors(options = {}) {
1216
1016
  touchTolerance = 5,
1217
1017
  mouseDistance = 1
1218
1018
  } = options;
1219
- return (0, import_react12.useMemo)(() => {
1019
+ return (0, import_react10.useMemo)(() => {
1220
1020
  if (touchOptimized) {
1221
1021
  return [
1222
1022
  import_dom.PointerSensor.configure({
@@ -1245,14 +1045,14 @@ function useDragSensors(options = {}) {
1245
1045
  }
1246
1046
 
1247
1047
  // src/hooks/useClipSplitting.ts
1248
- var import_react13 = require("react");
1048
+ var import_react11 = require("react");
1249
1049
  var import_engine2 = require("@waveform-playlist/engine");
1250
1050
  var useClipSplitting = (options) => {
1251
1051
  const { tracks, engineRef } = options;
1252
1052
  const { sampleRate } = usePlaylistData();
1253
1053
  const { currentTimeRef } = usePlaybackAnimation();
1254
1054
  const { selectedTrackId } = usePlaylistState();
1255
- const splitClipAt = (0, import_react13.useCallback)(
1055
+ const splitClipAt = (0, import_react11.useCallback)(
1256
1056
  (trackIndex, clipIndex, splitTime) => {
1257
1057
  const { samplesPerPixel } = options;
1258
1058
  const track = tracks[trackIndex];
@@ -1276,7 +1076,7 @@ var useClipSplitting = (options) => {
1276
1076
  },
1277
1077
  [tracks, options, engineRef, sampleRate]
1278
1078
  );
1279
- const splitClipAtPlayhead = (0, import_react13.useCallback)(() => {
1079
+ const splitClipAtPlayhead = (0, import_react11.useCallback)(() => {
1280
1080
  var _a;
1281
1081
  if (!selectedTrackId) {
1282
1082
  console.warn("[waveform-playlist] No track selected \u2014 click a clip to select a track first");
@@ -1307,16 +1107,16 @@ var useClipSplitting = (options) => {
1307
1107
  };
1308
1108
 
1309
1109
  // src/hooks/useKeyboardShortcuts.ts
1310
- var import_react14 = require("react");
1110
+ var import_react12 = require("react");
1111
+ var import_core = require("@waveform-playlist/core");
1311
1112
  var import_core2 = require("@waveform-playlist/core");
1312
- var import_core3 = require("@waveform-playlist/core");
1313
1113
  var useKeyboardShortcuts = (options) => {
1314
1114
  const { shortcuts, enabled = true } = options;
1315
- const handleKeyDown = (0, import_react14.useCallback)(
1316
- (event) => (0, import_core2.handleKeyboardEvent)(event, shortcuts, enabled),
1115
+ const handleKeyDown = (0, import_react12.useCallback)(
1116
+ (event) => (0, import_core.handleKeyboardEvent)(event, shortcuts, enabled),
1317
1117
  [shortcuts, enabled]
1318
1118
  );
1319
- (0, import_react14.useEffect)(() => {
1119
+ (0, import_react12.useEffect)(() => {
1320
1120
  if (!enabled) return;
1321
1121
  window.addEventListener("keydown", handleKeyDown);
1322
1122
  return () => {
@@ -1326,22 +1126,22 @@ var useKeyboardShortcuts = (options) => {
1326
1126
  };
1327
1127
 
1328
1128
  // src/hooks/usePlaybackShortcuts.ts
1329
- var import_react15 = require("react");
1129
+ var import_react13 = require("react");
1330
1130
  var usePlaybackShortcuts = (options = {}) => {
1331
1131
  const { enabled = true, additionalShortcuts = [], shortcuts: overrideShortcuts } = options;
1332
1132
  const { isPlaying } = usePlaybackAnimation();
1333
1133
  const { setCurrentTime, play, pause, stop } = usePlaylistControls();
1334
- const togglePlayPause = (0, import_react15.useCallback)(() => {
1134
+ const togglePlayPause = (0, import_react13.useCallback)(() => {
1335
1135
  if (isPlaying) {
1336
1136
  pause();
1337
1137
  } else {
1338
1138
  play();
1339
1139
  }
1340
1140
  }, [isPlaying, play, pause]);
1341
- const stopPlayback = (0, import_react15.useCallback)(() => {
1141
+ const stopPlayback = (0, import_react13.useCallback)(() => {
1342
1142
  stop();
1343
1143
  }, [stop]);
1344
- const rewindToStart = (0, import_react15.useCallback)(() => {
1144
+ const rewindToStart = (0, import_react13.useCallback)(() => {
1345
1145
  setCurrentTime(0);
1346
1146
  if (isPlaying) {
1347
1147
  play(0);
@@ -1381,7 +1181,7 @@ var usePlaybackShortcuts = (options = {}) => {
1381
1181
  };
1382
1182
 
1383
1183
  // src/hooks/useAnnotationKeyboardControls.ts
1384
- var import_react16 = require("react");
1184
+ var import_react14 = require("react");
1385
1185
  var LINK_THRESHOLD2 = 0.01;
1386
1186
  var TIME_DELTA = 0.01;
1387
1187
  function useAnnotationKeyboardControls({
@@ -1401,11 +1201,11 @@ function useAnnotationKeyboardControls({
1401
1201
  const playlistData = usePlaylistDataOptional();
1402
1202
  const samplesPerPixel = samplesPerPixelProp != null ? samplesPerPixelProp : playlistData == null ? void 0 : playlistData.samplesPerPixel;
1403
1203
  const sampleRate = sampleRateProp != null ? sampleRateProp : playlistData == null ? void 0 : playlistData.sampleRate;
1404
- const activeIndex = (0, import_react16.useMemo)(() => {
1204
+ const activeIndex = (0, import_react14.useMemo)(() => {
1405
1205
  if (!activeAnnotationId) return -1;
1406
1206
  return annotations.findIndex((a) => a.id === activeAnnotationId);
1407
1207
  }, [annotations, activeAnnotationId]);
1408
- const scrollToAnnotation = (0, import_react16.useCallback)(
1208
+ const scrollToAnnotation = (0, import_react14.useCallback)(
1409
1209
  (annotationId) => {
1410
1210
  if (!(scrollContainerRef == null ? void 0 : scrollContainerRef.current) || !samplesPerPixel || !sampleRate) return;
1411
1211
  const annotation = annotations.find((a) => a.id === annotationId);
@@ -1428,12 +1228,12 @@ function useAnnotationKeyboardControls({
1428
1228
  },
1429
1229
  [annotations, scrollContainerRef, samplesPerPixel, sampleRate]
1430
1230
  );
1431
- (0, import_react16.useEffect)(() => {
1231
+ (0, import_react14.useEffect)(() => {
1432
1232
  if (activeAnnotationId && (scrollContainerRef == null ? void 0 : scrollContainerRef.current) && samplesPerPixel && sampleRate) {
1433
1233
  scrollToAnnotation(activeAnnotationId);
1434
1234
  }
1435
1235
  }, [activeAnnotationId, scrollToAnnotation, scrollContainerRef, samplesPerPixel, sampleRate]);
1436
- const moveStartBoundary = (0, import_react16.useCallback)(
1236
+ const moveStartBoundary = (0, import_react14.useCallback)(
1437
1237
  (delta) => {
1438
1238
  if (activeIndex < 0) return;
1439
1239
  const annotation = annotations[activeIndex];
@@ -1462,7 +1262,7 @@ function useAnnotationKeyboardControls({
1462
1262
  },
1463
1263
  [annotations, activeIndex, linkEndpoints, onAnnotationsChange]
1464
1264
  );
1465
- const moveEndBoundary = (0, import_react16.useCallback)(
1265
+ const moveEndBoundary = (0, import_react14.useCallback)(
1466
1266
  (delta) => {
1467
1267
  if (activeIndex < 0) return;
1468
1268
  const annotation = annotations[activeIndex];
@@ -1522,7 +1322,7 @@ function useAnnotationKeyboardControls({
1522
1322
  },
1523
1323
  [annotations, activeIndex, duration, linkEndpoints, onAnnotationsChange]
1524
1324
  );
1525
- const selectPrevious = (0, import_react16.useCallback)(() => {
1325
+ const selectPrevious = (0, import_react14.useCallback)(() => {
1526
1326
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1527
1327
  if (activeIndex <= 0) {
1528
1328
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
@@ -1530,7 +1330,7 @@ function useAnnotationKeyboardControls({
1530
1330
  onActiveAnnotationChange(annotations[activeIndex - 1].id);
1531
1331
  }
1532
1332
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1533
- const selectNext = (0, import_react16.useCallback)(() => {
1333
+ const selectNext = (0, import_react14.useCallback)(() => {
1534
1334
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1535
1335
  if (activeIndex < 0 || activeIndex >= annotations.length - 1) {
1536
1336
  onActiveAnnotationChange(annotations[0].id);
@@ -1538,25 +1338,25 @@ function useAnnotationKeyboardControls({
1538
1338
  onActiveAnnotationChange(annotations[activeIndex + 1].id);
1539
1339
  }
1540
1340
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1541
- const selectFirst = (0, import_react16.useCallback)(() => {
1341
+ const selectFirst = (0, import_react14.useCallback)(() => {
1542
1342
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1543
1343
  onActiveAnnotationChange(annotations[0].id);
1544
1344
  }, [annotations, onActiveAnnotationChange]);
1545
- const selectLast = (0, import_react16.useCallback)(() => {
1345
+ const selectLast = (0, import_react14.useCallback)(() => {
1546
1346
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1547
1347
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
1548
1348
  }, [annotations, onActiveAnnotationChange]);
1549
- const clearSelection = (0, import_react16.useCallback)(() => {
1349
+ const clearSelection = (0, import_react14.useCallback)(() => {
1550
1350
  if (!onActiveAnnotationChange) return;
1551
1351
  onActiveAnnotationChange(null);
1552
1352
  }, [onActiveAnnotationChange]);
1553
- const playActiveAnnotation = (0, import_react16.useCallback)(() => {
1353
+ const playActiveAnnotation = (0, import_react14.useCallback)(() => {
1554
1354
  if (activeIndex < 0 || !onPlay) return;
1555
1355
  const annotation = annotations[activeIndex];
1556
1356
  const playDuration = !continuousPlay ? annotation.end - annotation.start : void 0;
1557
1357
  onPlay(annotation.start, playDuration);
1558
1358
  }, [annotations, activeIndex, continuousPlay, onPlay]);
1559
- const activeAnnotationShortcuts = (0, import_react16.useMemo)(
1359
+ const activeAnnotationShortcuts = (0, import_react14.useMemo)(
1560
1360
  () => [
1561
1361
  {
1562
1362
  key: "[",
@@ -1593,7 +1393,7 @@ function useAnnotationKeyboardControls({
1593
1393
  ],
1594
1394
  [moveStartBoundary, moveEndBoundary, playActiveAnnotation]
1595
1395
  );
1596
- const navigationShortcuts = (0, import_react16.useMemo)(
1396
+ const navigationShortcuts = (0, import_react14.useMemo)(
1597
1397
  () => [
1598
1398
  {
1599
1399
  key: "ArrowUp",
@@ -1639,1404 +1439,46 @@ function useAnnotationKeyboardControls({
1639
1439
  }
1640
1440
  ],
1641
1441
  [selectPrevious, selectNext, selectFirst, selectLast, clearSelection]
1642
- );
1643
- useKeyboardShortcuts({
1644
- shortcuts: activeAnnotationShortcuts,
1645
- enabled: enabled && activeIndex >= 0
1646
- });
1647
- useKeyboardShortcuts({
1648
- shortcuts: navigationShortcuts,
1649
- enabled: enabled && annotations.length > 0 && !!onActiveAnnotationChange
1650
- });
1651
- return {
1652
- moveStartBoundary,
1653
- moveEndBoundary,
1654
- selectPrevious,
1655
- selectNext,
1656
- selectFirst,
1657
- selectLast,
1658
- clearSelection,
1659
- scrollToAnnotation,
1660
- playActiveAnnotation
1661
- };
1662
- }
1663
-
1664
- // src/hooks/useDynamicEffects.ts
1665
- var import_react17 = require("react");
1666
-
1667
- // src/effects/effectDefinitions.ts
1668
- var effectDefinitions = [
1669
- // === REVERB EFFECTS ===
1670
- {
1671
- id: "reverb",
1672
- name: "Reverb",
1673
- category: "reverb",
1674
- description: "Simple convolution reverb with adjustable decay time",
1675
- parameters: [
1676
- {
1677
- name: "decay",
1678
- label: "Decay",
1679
- type: "number",
1680
- min: 0.1,
1681
- max: 10,
1682
- step: 0.1,
1683
- default: 1.5,
1684
- unit: "s"
1685
- },
1686
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1687
- ]
1688
- },
1689
- {
1690
- id: "freeverb",
1691
- name: "Freeverb",
1692
- category: "reverb",
1693
- description: "Classic Schroeder/Moorer reverb with room size and dampening",
1694
- parameters: [
1695
- {
1696
- name: "roomSize",
1697
- label: "Room Size",
1698
- type: "number",
1699
- min: 0,
1700
- max: 1,
1701
- step: 0.01,
1702
- default: 0.7
1703
- },
1704
- {
1705
- name: "dampening",
1706
- label: "Dampening",
1707
- type: "number",
1708
- min: 0,
1709
- max: 1e4,
1710
- step: 100,
1711
- default: 3e3,
1712
- unit: "Hz"
1713
- },
1714
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1715
- ]
1716
- },
1717
- {
1718
- id: "jcReverb",
1719
- name: "JC Reverb",
1720
- category: "reverb",
1721
- description: "Attempt at Roland JC-120 chorus reverb emulation",
1722
- parameters: [
1723
- {
1724
- name: "roomSize",
1725
- label: "Room Size",
1726
- type: "number",
1727
- min: 0,
1728
- max: 1,
1729
- step: 0.01,
1730
- default: 0.5
1731
- },
1732
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1733
- ]
1734
- },
1735
- // === DELAY EFFECTS ===
1736
- {
1737
- id: "feedbackDelay",
1738
- name: "Feedback Delay",
1739
- category: "delay",
1740
- description: "Delay line with feedback for echo effects",
1741
- parameters: [
1742
- {
1743
- name: "delayTime",
1744
- label: "Delay Time",
1745
- type: "number",
1746
- min: 0,
1747
- max: 1,
1748
- step: 0.01,
1749
- default: 0.25,
1750
- unit: "s"
1751
- },
1752
- {
1753
- name: "feedback",
1754
- label: "Feedback",
1755
- type: "number",
1756
- min: 0,
1757
- max: 0.95,
1758
- step: 0.01,
1759
- default: 0.5
1760
- },
1761
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1762
- ]
1763
- },
1764
- {
1765
- id: "pingPongDelay",
1766
- name: "Ping Pong Delay",
1767
- category: "delay",
1768
- description: "Stereo delay bouncing between left and right channels",
1769
- parameters: [
1770
- {
1771
- name: "delayTime",
1772
- label: "Delay Time",
1773
- type: "number",
1774
- min: 0,
1775
- max: 1,
1776
- step: 0.01,
1777
- default: 0.25,
1778
- unit: "s"
1779
- },
1780
- {
1781
- name: "feedback",
1782
- label: "Feedback",
1783
- type: "number",
1784
- min: 0,
1785
- max: 0.95,
1786
- step: 0.01,
1787
- default: 0.5
1788
- },
1789
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1790
- ]
1791
- },
1792
- // === MODULATION EFFECTS ===
1793
- {
1794
- id: "chorus",
1795
- name: "Chorus",
1796
- category: "modulation",
1797
- description: "Creates thickness by layering detuned copies of the signal",
1798
- parameters: [
1799
- {
1800
- name: "frequency",
1801
- label: "Rate",
1802
- type: "number",
1803
- min: 0.1,
1804
- max: 10,
1805
- step: 0.1,
1806
- default: 1.5,
1807
- unit: "Hz"
1808
- },
1809
- {
1810
- name: "delayTime",
1811
- label: "Delay",
1812
- type: "number",
1813
- min: 0,
1814
- max: 20,
1815
- step: 0.5,
1816
- default: 3.5,
1817
- unit: "ms"
1818
- },
1819
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.7 },
1820
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1821
- ]
1822
- },
1823
- {
1824
- id: "phaser",
1825
- name: "Phaser",
1826
- category: "modulation",
1827
- description: "Classic phaser effect using allpass filters",
1828
- parameters: [
1829
- {
1830
- name: "frequency",
1831
- label: "Rate",
1832
- type: "number",
1833
- min: 0.1,
1834
- max: 10,
1835
- step: 0.1,
1836
- default: 0.5,
1837
- unit: "Hz"
1838
- },
1839
- { name: "octaves", label: "Octaves", type: "number", min: 1, max: 6, step: 1, default: 3 },
1840
- {
1841
- name: "baseFrequency",
1842
- label: "Base Freq",
1843
- type: "number",
1844
- min: 100,
1845
- max: 2e3,
1846
- step: 10,
1847
- default: 350,
1848
- unit: "Hz"
1849
- },
1850
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
1851
- ]
1852
- },
1853
- {
1854
- id: "tremolo",
1855
- name: "Tremolo",
1856
- category: "modulation",
1857
- description: "Rhythmic volume modulation",
1858
- parameters: [
1859
- {
1860
- name: "frequency",
1861
- label: "Rate",
1862
- type: "number",
1863
- min: 0.1,
1864
- max: 20,
1865
- step: 0.1,
1866
- default: 4,
1867
- unit: "Hz"
1868
- },
1869
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 },
1870
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1871
- ]
1872
- },
1873
- {
1874
- id: "vibrato",
1875
- name: "Vibrato",
1876
- category: "modulation",
1877
- description: "Pitch modulation effect",
1878
- parameters: [
1879
- {
1880
- name: "frequency",
1881
- label: "Rate",
1882
- type: "number",
1883
- min: 0.1,
1884
- max: 20,
1885
- step: 0.1,
1886
- default: 5,
1887
- unit: "Hz"
1888
- },
1889
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 0.1 },
1890
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1891
- ]
1892
- },
1893
- {
1894
- id: "autoPanner",
1895
- name: "Auto Panner",
1896
- category: "modulation",
1897
- description: "Automatic left-right panning",
1898
- parameters: [
1899
- {
1900
- name: "frequency",
1901
- label: "Rate",
1902
- type: "number",
1903
- min: 0.1,
1904
- max: 10,
1905
- step: 0.1,
1906
- default: 1,
1907
- unit: "Hz"
1908
- },
1909
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 1 },
1910
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1911
- ]
1912
- },
1913
- // === FILTER EFFECTS ===
1914
- {
1915
- id: "autoFilter",
1916
- name: "Auto Filter",
1917
- category: "filter",
1918
- description: "Automated filter sweep with LFO",
1919
- parameters: [
1920
- {
1921
- name: "frequency",
1922
- label: "Rate",
1923
- type: "number",
1924
- min: 0.1,
1925
- max: 10,
1926
- step: 0.1,
1927
- default: 1,
1928
- unit: "Hz"
1929
- },
1930
- {
1931
- name: "baseFrequency",
1932
- label: "Base Freq",
1933
- type: "number",
1934
- min: 20,
1935
- max: 2e3,
1936
- step: 10,
1937
- default: 200,
1938
- unit: "Hz"
1939
- },
1940
- {
1941
- name: "octaves",
1942
- label: "Octaves",
1943
- type: "number",
1944
- min: 0.5,
1945
- max: 8,
1946
- step: 0.5,
1947
- default: 2.6
1948
- },
1949
- { name: "depth", label: "Depth", type: "number", min: 0, max: 1, step: 0.01, default: 1 },
1950
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1951
- ]
1952
- },
1953
- {
1954
- id: "autoWah",
1955
- name: "Auto Wah",
1956
- category: "filter",
1957
- description: "Envelope follower filter effect",
1958
- parameters: [
1959
- {
1960
- name: "baseFrequency",
1961
- label: "Base Freq",
1962
- type: "number",
1963
- min: 20,
1964
- max: 500,
1965
- step: 10,
1966
- default: 100,
1967
- unit: "Hz"
1968
- },
1969
- { name: "octaves", label: "Octaves", type: "number", min: 1, max: 8, step: 1, default: 6 },
1970
- {
1971
- name: "sensitivity",
1972
- label: "Sensitivity",
1973
- type: "number",
1974
- min: -40,
1975
- max: 0,
1976
- step: 1,
1977
- default: 0,
1978
- unit: "dB"
1979
- },
1980
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
1981
- ]
1982
- },
1983
- {
1984
- id: "eq3",
1985
- name: "3-Band EQ",
1986
- category: "filter",
1987
- description: "Three band equalizer with low, mid, and high controls",
1988
- parameters: [
1989
- {
1990
- name: "low",
1991
- label: "Low",
1992
- type: "number",
1993
- min: -24,
1994
- max: 24,
1995
- step: 0.5,
1996
- default: 0,
1997
- unit: "dB"
1998
- },
1999
- {
2000
- name: "mid",
2001
- label: "Mid",
2002
- type: "number",
2003
- min: -24,
2004
- max: 24,
2005
- step: 0.5,
2006
- default: 0,
2007
- unit: "dB"
2008
- },
2009
- {
2010
- name: "high",
2011
- label: "High",
2012
- type: "number",
2013
- min: -24,
2014
- max: 24,
2015
- step: 0.5,
2016
- default: 0,
2017
- unit: "dB"
2018
- },
2019
- {
2020
- name: "lowFrequency",
2021
- label: "Low Freq",
2022
- type: "number",
2023
- min: 20,
2024
- max: 500,
2025
- step: 10,
2026
- default: 400,
2027
- unit: "Hz"
2028
- },
2029
- {
2030
- name: "highFrequency",
2031
- label: "High Freq",
2032
- type: "number",
2033
- min: 1e3,
2034
- max: 1e4,
2035
- step: 100,
2036
- default: 2500,
2037
- unit: "Hz"
2038
- }
2039
- ]
2040
- },
2041
- // === DISTORTION EFFECTS ===
2042
- {
2043
- id: "distortion",
2044
- name: "Distortion",
2045
- category: "distortion",
2046
- description: "Wave shaping distortion effect",
2047
- parameters: [
2048
- {
2049
- name: "distortion",
2050
- label: "Drive",
2051
- type: "number",
2052
- min: 0,
2053
- max: 1,
2054
- step: 0.01,
2055
- default: 0.4
2056
- },
2057
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
2058
- ]
2059
- },
2060
- {
2061
- id: "bitCrusher",
2062
- name: "Bit Crusher",
2063
- category: "distortion",
2064
- description: "Reduces bit depth for lo-fi digital texture",
2065
- parameters: [
2066
- { name: "bits", label: "Bits", type: "number", min: 1, max: 16, step: 1, default: 4 },
2067
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
2068
- ]
2069
- },
2070
- {
2071
- id: "chebyshev",
2072
- name: "Chebyshev",
2073
- category: "distortion",
2074
- description: "Waveshaping distortion using Chebyshev polynomials",
2075
- parameters: [
2076
- { name: "order", label: "Order", type: "number", min: 1, max: 100, step: 1, default: 50 },
2077
- { name: "wet", label: "Mix", type: "number", min: 0, max: 1, step: 0.01, default: 1 }
2078
- ]
2079
- },
2080
- // === DYNAMICS EFFECTS ===
2081
- {
2082
- id: "compressor",
2083
- name: "Compressor",
2084
- category: "dynamics",
2085
- description: "Dynamic range compressor",
2086
- parameters: [
2087
- {
2088
- name: "threshold",
2089
- label: "Threshold",
2090
- type: "number",
2091
- min: -60,
2092
- max: 0,
2093
- step: 1,
2094
- default: -24,
2095
- unit: "dB"
2096
- },
2097
- { name: "ratio", label: "Ratio", type: "number", min: 1, max: 20, step: 0.5, default: 4 },
2098
- {
2099
- name: "attack",
2100
- label: "Attack",
2101
- type: "number",
2102
- min: 0,
2103
- max: 1,
2104
- step: 1e-3,
2105
- default: 3e-3,
2106
- unit: "s"
2107
- },
2108
- {
2109
- name: "release",
2110
- label: "Release",
2111
- type: "number",
2112
- min: 0,
2113
- max: 1,
2114
- step: 0.01,
2115
- default: 0.25,
2116
- unit: "s"
2117
- },
2118
- {
2119
- name: "knee",
2120
- label: "Knee",
2121
- type: "number",
2122
- min: 0,
2123
- max: 40,
2124
- step: 1,
2125
- default: 30,
2126
- unit: "dB"
2127
- }
2128
- ]
2129
- },
2130
- {
2131
- id: "limiter",
2132
- name: "Limiter",
2133
- category: "dynamics",
2134
- description: "Hard limiter to prevent clipping",
2135
- parameters: [
2136
- {
2137
- name: "threshold",
2138
- label: "Threshold",
2139
- type: "number",
2140
- min: -12,
2141
- max: 0,
2142
- step: 0.5,
2143
- default: -6,
2144
- unit: "dB"
2145
- }
2146
- ]
2147
- },
2148
- {
2149
- id: "gate",
2150
- name: "Gate",
2151
- category: "dynamics",
2152
- description: "Noise gate to silence signal below threshold",
2153
- parameters: [
2154
- {
2155
- name: "threshold",
2156
- label: "Threshold",
2157
- type: "number",
2158
- min: -100,
2159
- max: 0,
2160
- step: 1,
2161
- default: -40,
2162
- unit: "dB"
2163
- },
2164
- {
2165
- name: "attack",
2166
- label: "Attack",
2167
- type: "number",
2168
- min: 0,
2169
- max: 0.3,
2170
- step: 1e-3,
2171
- default: 1e-3,
2172
- unit: "s"
2173
- },
2174
- {
2175
- name: "release",
2176
- label: "Release",
2177
- type: "number",
2178
- min: 0,
2179
- max: 0.5,
2180
- step: 0.01,
2181
- default: 0.1,
2182
- unit: "s"
2183
- }
2184
- ]
2185
- },
2186
- // === SPATIAL EFFECTS ===
2187
- {
2188
- id: "stereoWidener",
2189
- name: "Stereo Widener",
2190
- category: "spatial",
2191
- description: "Expands or narrows the stereo image",
2192
- parameters: [
2193
- { name: "width", label: "Width", type: "number", min: 0, max: 1, step: 0.01, default: 0.5 }
2194
- ]
2195
- }
2196
- ];
2197
- var getEffectDefinition = (id) => {
2198
- return effectDefinitions.find((def) => def.id === id);
2199
- };
2200
- var getEffectsByCategory = (category) => {
2201
- return effectDefinitions.filter((def) => def.category === category);
2202
- };
2203
- var effectCategories = [
2204
- { id: "reverb", name: "Reverb" },
2205
- { id: "delay", name: "Delay" },
2206
- { id: "modulation", name: "Modulation" },
2207
- { id: "filter", name: "Filter" },
2208
- { id: "distortion", name: "Distortion" },
2209
- { id: "dynamics", name: "Dynamics" },
2210
- { id: "spatial", name: "Spatial" }
2211
- ];
2212
-
2213
- // src/effects/effectFactory.ts
2214
- var import_tone2 = require("tone");
2215
- function asEffectConstructor(ctor) {
2216
- return ctor;
2217
- }
2218
- var effectConstructors = {
2219
- reverb: asEffectConstructor(import_tone2.Reverb),
2220
- freeverb: asEffectConstructor(import_tone2.Freeverb),
2221
- jcReverb: asEffectConstructor(import_tone2.JCReverb),
2222
- feedbackDelay: asEffectConstructor(import_tone2.FeedbackDelay),
2223
- pingPongDelay: asEffectConstructor(import_tone2.PingPongDelay),
2224
- chorus: asEffectConstructor(import_tone2.Chorus),
2225
- phaser: asEffectConstructor(import_tone2.Phaser),
2226
- tremolo: asEffectConstructor(import_tone2.Tremolo),
2227
- vibrato: asEffectConstructor(import_tone2.Vibrato),
2228
- autoPanner: asEffectConstructor(import_tone2.AutoPanner),
2229
- autoFilter: asEffectConstructor(import_tone2.AutoFilter),
2230
- autoWah: asEffectConstructor(import_tone2.AutoWah),
2231
- eq3: asEffectConstructor(import_tone2.EQ3),
2232
- distortion: asEffectConstructor(import_tone2.Distortion),
2233
- bitCrusher: asEffectConstructor(import_tone2.BitCrusher),
2234
- chebyshev: asEffectConstructor(import_tone2.Chebyshev),
2235
- compressor: asEffectConstructor(import_tone2.Compressor),
2236
- limiter: asEffectConstructor(import_tone2.Limiter),
2237
- gate: asEffectConstructor(import_tone2.Gate),
2238
- stereoWidener: asEffectConstructor(import_tone2.StereoWidener)
2239
- };
2240
- var instanceCounter = 0;
2241
- var generateInstanceId = () => {
2242
- return `effect_${Date.now()}_${++instanceCounter}`;
2243
- };
2244
- function createEffectInstance(definition, initialParams) {
2245
- const Constructor = effectConstructors[definition.id];
2246
- if (!Constructor) {
2247
- throw new Error(`Unknown effect type: ${definition.id}`);
2248
- }
2249
- const options = {};
2250
- definition.parameters.forEach((param) => {
2251
- var _a;
2252
- const value = (_a = initialParams == null ? void 0 : initialParams[param.name]) != null ? _a : param.default;
2253
- options[param.name] = value;
2254
- });
2255
- const effect = new Constructor(options);
2256
- const instanceId = generateInstanceId();
2257
- const effectRecord = effect;
2258
- return {
2259
- effect,
2260
- id: definition.id,
2261
- instanceId,
2262
- dispose() {
2263
- try {
2264
- effect.disconnect();
2265
- effect.dispose();
2266
- } catch (e) {
2267
- console.warn(
2268
- `[waveform-playlist] Error disposing effect "${definition.id}" (${instanceId}):`,
2269
- e
2270
- );
2271
- }
2272
- },
2273
- setParameter(name, value) {
2274
- const prop = effectRecord[name];
2275
- if (name === "wet") {
2276
- const wetProp = effectRecord["wet"];
2277
- if (wetProp && typeof wetProp === "object" && "value" in wetProp) {
2278
- wetProp.value = value;
2279
- return;
2280
- }
2281
- }
2282
- if (prop !== void 0) {
2283
- if (prop && typeof prop === "object" && "value" in prop) {
2284
- prop.value = value;
2285
- } else {
2286
- effectRecord[name] = value;
2287
- }
2288
- }
2289
- },
2290
- getParameter(name) {
2291
- if (name === "wet") {
2292
- const wetProp = effectRecord["wet"];
2293
- if (wetProp && typeof wetProp === "object" && "value" in wetProp) {
2294
- return wetProp.value;
2295
- }
2296
- }
2297
- const prop = effectRecord[name];
2298
- if (prop !== void 0) {
2299
- if (prop && typeof prop === "object" && "value" in prop) {
2300
- return prop.value;
2301
- }
2302
- return prop;
2303
- }
2304
- return void 0;
2305
- },
2306
- connect(destination) {
2307
- effect.connect(destination);
2308
- },
2309
- disconnect() {
2310
- try {
2311
- effect.disconnect();
2312
- } catch (e) {
2313
- console.warn(
2314
- `[waveform-playlist] Error disconnecting effect "${definition.id}" (${instanceId}):`,
2315
- e
2316
- );
2317
- }
2318
- }
2319
- };
2320
- }
2321
- function createEffectChain(effects) {
2322
- if (effects.length === 0) {
2323
- throw new Error("Cannot create effect chain with no effects");
2324
- }
2325
- for (let i = 0; i < effects.length - 1; i++) {
2326
- effects[i].effect.connect(effects[i + 1].effect);
2327
- }
2328
- return {
2329
- input: effects[0].effect,
2330
- output: effects[effects.length - 1].effect,
2331
- dispose() {
2332
- effects.forEach((e) => e.dispose());
2333
- }
2334
- };
2335
- }
2336
-
2337
- // src/hooks/useDynamicEffects.ts
2338
- var import_tone3 = require("tone");
2339
- function useDynamicEffects(fftSize = 256) {
2340
- const [activeEffects, setActiveEffects] = (0, import_react17.useState)([]);
2341
- const activeEffectsRef = (0, import_react17.useRef)(activeEffects);
2342
- activeEffectsRef.current = activeEffects;
2343
- const effectInstancesRef = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
2344
- const analyserRef = (0, import_react17.useRef)(null);
2345
- const graphNodesRef = (0, import_react17.useRef)(null);
2346
- const rebuildChain = (0, import_react17.useCallback)((effects) => {
2347
- const nodes = graphNodesRef.current;
2348
- if (!nodes) return;
2349
- const { masterGainNode, destination, analyserNode } = nodes;
2350
- try {
2351
- masterGainNode.disconnect();
2352
- } catch (e) {
2353
- console.warn("[waveform-playlist] Error disconnecting master effects chain:", e);
2354
- }
2355
- const instances = effects.map((ae) => effectInstancesRef.current.get(ae.instanceId)).filter((inst) => inst !== void 0);
2356
- if (instances.length === 0) {
2357
- masterGainNode.connect(analyserNode);
2358
- analyserNode.connect(destination);
2359
- } else {
2360
- let currentNode = masterGainNode;
2361
- instances.forEach((inst) => {
2362
- try {
2363
- inst.disconnect();
2364
- } catch (e) {
2365
- console.warn(`[waveform-playlist] Error disconnecting effect "${inst.id}":`, e);
2366
- }
2367
- currentNode.connect(inst.effect);
2368
- currentNode = inst.effect;
2369
- });
2370
- currentNode.connect(analyserNode);
2371
- analyserNode.connect(destination);
2372
- }
2373
- }, []);
2374
- const addEffect = (0, import_react17.useCallback)((effectId) => {
2375
- const definition = getEffectDefinition(effectId);
2376
- if (!definition) {
2377
- console.error(`Unknown effect: ${effectId}`);
2378
- return;
2379
- }
2380
- const params = {};
2381
- definition.parameters.forEach((p) => {
2382
- params[p.name] = p.default;
2383
- });
2384
- const instance = createEffectInstance(definition, params);
2385
- effectInstancesRef.current.set(instance.instanceId, instance);
2386
- const newActiveEffect = {
2387
- instanceId: instance.instanceId,
2388
- effectId: definition.id,
2389
- definition,
2390
- params,
2391
- bypassed: false
2392
- };
2393
- setActiveEffects((prev) => [...prev, newActiveEffect]);
2394
- }, []);
2395
- const removeEffect = (0, import_react17.useCallback)((instanceId) => {
2396
- const instance = effectInstancesRef.current.get(instanceId);
2397
- if (instance) {
2398
- instance.dispose();
2399
- effectInstancesRef.current.delete(instanceId);
2400
- }
2401
- setActiveEffects((prev) => prev.filter((e) => e.instanceId !== instanceId));
2402
- }, []);
2403
- const updateParameter = (0, import_react17.useCallback)(
2404
- (instanceId, paramName, value) => {
2405
- const instance = effectInstancesRef.current.get(instanceId);
2406
- if (instance) {
2407
- instance.setParameter(paramName, value);
2408
- }
2409
- setActiveEffects(
2410
- (prev) => prev.map(
2411
- (e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { params: __spreadProps(__spreadValues({}, e.params), { [paramName]: value }) }) : e
2412
- )
2413
- );
2414
- },
2415
- []
2416
- );
2417
- const toggleBypass = (0, import_react17.useCallback)((instanceId) => {
2418
- var _a;
2419
- const effect = activeEffectsRef.current.find((e) => e.instanceId === instanceId);
2420
- if (!effect) return;
2421
- const newBypassed = !effect.bypassed;
2422
- const instance = effectInstancesRef.current.get(instanceId);
2423
- if (instance) {
2424
- const originalWet = (_a = effect.params.wet) != null ? _a : 1;
2425
- instance.setParameter("wet", newBypassed ? 0 : originalWet);
2426
- }
2427
- setActiveEffects(
2428
- (prev) => prev.map((e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { bypassed: newBypassed }) : e)
2429
- );
2430
- }, []);
2431
- const reorderEffects = (0, import_react17.useCallback)((fromIndex, toIndex) => {
2432
- setActiveEffects((prev) => {
2433
- const newEffects = [...prev];
2434
- const [removed] = newEffects.splice(fromIndex, 1);
2435
- newEffects.splice(toIndex, 0, removed);
2436
- return newEffects;
2437
- });
2438
- }, []);
2439
- const clearAllEffects = (0, import_react17.useCallback)(() => {
2440
- effectInstancesRef.current.forEach((inst) => inst.dispose());
2441
- effectInstancesRef.current.clear();
2442
- setActiveEffects([]);
2443
- }, []);
2444
- (0, import_react17.useEffect)(() => {
2445
- rebuildChain(activeEffects);
2446
- }, [activeEffects, rebuildChain]);
2447
- const masterEffects = (0, import_react17.useCallback)(
2448
- (masterGainNode, destination, _isOffline) => {
2449
- const analyserNode = new import_tone3.Analyser("fft", fftSize);
2450
- analyserRef.current = analyserNode;
2451
- graphNodesRef.current = {
2452
- masterGainNode,
2453
- destination,
2454
- analyserNode
2455
- };
2456
- const effects = activeEffectsRef.current;
2457
- const instances = effects.map((ae) => effectInstancesRef.current.get(ae.instanceId)).filter((inst) => inst !== void 0);
2458
- if (instances.length === 0) {
2459
- masterGainNode.connect(analyserNode);
2460
- analyserNode.connect(destination);
2461
- } else {
2462
- let currentNode = masterGainNode;
2463
- instances.forEach((inst) => {
2464
- currentNode.connect(inst.effect);
2465
- currentNode = inst.effect;
2466
- });
2467
- currentNode.connect(analyserNode);
2468
- analyserNode.connect(destination);
2469
- }
2470
- return function cleanup() {
2471
- analyserNode.dispose();
2472
- analyserRef.current = null;
2473
- graphNodesRef.current = null;
2474
- };
2475
- },
2476
- [fftSize]
2477
- // Only fftSize - reads effects from ref
2478
- );
2479
- (0, import_react17.useEffect)(() => {
2480
- const effectInstances = effectInstancesRef.current;
2481
- return () => {
2482
- effectInstances.forEach((inst) => inst.dispose());
2483
- effectInstances.clear();
2484
- };
2485
- }, []);
2486
- const createOfflineEffectsFunction = (0, import_react17.useCallback)(() => {
2487
- const nonBypassedEffects = activeEffects.filter((e) => !e.bypassed);
2488
- if (nonBypassedEffects.length === 0) {
2489
- return void 0;
2490
- }
2491
- return (masterGainNode, destination, _isOffline) => {
2492
- const offlineInstances = [];
2493
- for (const activeEffect of nonBypassedEffects) {
2494
- const instance = createEffectInstance(activeEffect.definition, activeEffect.params);
2495
- offlineInstances.push(instance);
2496
- }
2497
- if (offlineInstances.length === 0) {
2498
- masterGainNode.connect(destination);
2499
- } else {
2500
- let currentNode = masterGainNode;
2501
- offlineInstances.forEach((inst) => {
2502
- currentNode.connect(inst.effect);
2503
- currentNode = inst.effect;
2504
- });
2505
- currentNode.connect(destination);
2506
- }
2507
- return function cleanup() {
2508
- offlineInstances.forEach((inst) => inst.dispose());
2509
- };
2510
- };
2511
- }, [activeEffects]);
2512
- return {
2513
- activeEffects,
2514
- availableEffects: effectDefinitions,
2515
- addEffect,
2516
- removeEffect,
2517
- updateParameter,
2518
- toggleBypass,
2519
- reorderEffects,
2520
- clearAllEffects,
2521
- masterEffects,
2522
- createOfflineEffectsFunction,
2523
- analyserRef
2524
- };
2525
- }
2526
-
2527
- // src/hooks/useTrackDynamicEffects.ts
2528
- var import_react18 = require("react");
2529
- function useTrackDynamicEffects() {
2530
- const [trackEffectsState, setTrackEffectsState] = (0, import_react18.useState)(
2531
- /* @__PURE__ */ new Map()
2532
- );
2533
- const trackEffectInstancesRef = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
2534
- const trackGraphNodesRef = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
2535
- const rebuildTrackChain = (0, import_react18.useCallback)((trackId, trackEffects) => {
2536
- const nodes = trackGraphNodesRef.current.get(trackId);
2537
- if (!nodes) return;
2538
- const { graphEnd, masterGainNode } = nodes;
2539
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2540
- try {
2541
- graphEnd.disconnect();
2542
- } catch (e) {
2543
- console.warn(`[waveform-playlist] Error disconnecting track "${trackId}" effect chain:`, e);
2544
- }
2545
- const instances = trackEffects.map((ae) => instancesMap == null ? void 0 : instancesMap.get(ae.instanceId)).filter((inst) => inst !== void 0);
2546
- if (instances.length === 0) {
2547
- graphEnd.connect(masterGainNode);
2548
- } else {
2549
- let currentNode = graphEnd;
2550
- instances.forEach((inst) => {
2551
- try {
2552
- inst.disconnect();
2553
- } catch (e) {
2554
- console.warn(
2555
- `[waveform-playlist] Error disconnecting effect "${inst.id}" on track "${trackId}":`,
2556
- e
2557
- );
2558
- }
2559
- currentNode.connect(inst.effect);
2560
- currentNode = inst.effect;
2561
- });
2562
- currentNode.connect(masterGainNode);
2563
- }
2564
- }, []);
2565
- const addEffectToTrack = (0, import_react18.useCallback)((trackId, effectId) => {
2566
- const definition = getEffectDefinition(effectId);
2567
- if (!definition) {
2568
- console.error(`Unknown effect: ${effectId}`);
2569
- return;
2570
- }
2571
- const params = {};
2572
- definition.parameters.forEach((p) => {
2573
- params[p.name] = p.default;
2574
- });
2575
- const instance = createEffectInstance(definition, params);
2576
- if (!trackEffectInstancesRef.current.has(trackId)) {
2577
- trackEffectInstancesRef.current.set(trackId, /* @__PURE__ */ new Map());
2578
- }
2579
- trackEffectInstancesRef.current.get(trackId).set(instance.instanceId, instance);
2580
- const newActiveEffect = {
2581
- instanceId: instance.instanceId,
2582
- effectId: definition.id,
2583
- definition,
2584
- params,
2585
- bypassed: false
2586
- };
2587
- setTrackEffectsState((prev) => {
2588
- const newState = new Map(prev);
2589
- const existing = newState.get(trackId) || [];
2590
- newState.set(trackId, [...existing, newActiveEffect]);
2591
- return newState;
2592
- });
2593
- }, []);
2594
- const removeEffectFromTrack = (0, import_react18.useCallback)((trackId, instanceId) => {
2595
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2596
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2597
- if (instance) {
2598
- instance.dispose();
2599
- instancesMap == null ? void 0 : instancesMap.delete(instanceId);
2600
- }
2601
- setTrackEffectsState((prev) => {
2602
- const newState = new Map(prev);
2603
- const existing = newState.get(trackId) || [];
2604
- newState.set(
2605
- trackId,
2606
- existing.filter((e) => e.instanceId !== instanceId)
2607
- );
2608
- return newState;
2609
- });
2610
- }, []);
2611
- const updateTrackEffectParameter = (0, import_react18.useCallback)(
2612
- (trackId, instanceId, paramName, value) => {
2613
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2614
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2615
- if (instance) {
2616
- instance.setParameter(paramName, value);
2617
- }
2618
- setTrackEffectsState((prev) => {
2619
- const newState = new Map(prev);
2620
- const existing = newState.get(trackId) || [];
2621
- newState.set(
2622
- trackId,
2623
- existing.map(
2624
- (e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { params: __spreadProps(__spreadValues({}, e.params), { [paramName]: value }) }) : e
2625
- )
2626
- );
2627
- return newState;
2628
- });
2629
- },
2630
- []
2631
- );
2632
- const toggleBypass = (0, import_react18.useCallback)((trackId, instanceId) => {
2633
- var _a;
2634
- const trackEffects = trackEffectsStateRef.current.get(trackId) || [];
2635
- const effect = trackEffects.find((e) => e.instanceId === instanceId);
2636
- if (!effect) return;
2637
- const newBypassed = !effect.bypassed;
2638
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2639
- const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2640
- if (instance) {
2641
- const originalWet = (_a = effect.params.wet) != null ? _a : 1;
2642
- instance.setParameter("wet", newBypassed ? 0 : originalWet);
2643
- }
2644
- setTrackEffectsState((prev) => {
2645
- const newState = new Map(prev);
2646
- const existing = newState.get(trackId) || [];
2647
- newState.set(
2648
- trackId,
2649
- existing.map((e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { bypassed: newBypassed }) : e)
2650
- );
2651
- return newState;
2652
- });
2653
- }, []);
2654
- const clearTrackEffects = (0, import_react18.useCallback)((trackId) => {
2655
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2656
- if (instancesMap) {
2657
- instancesMap.forEach((inst) => inst.dispose());
2658
- instancesMap.clear();
2659
- }
2660
- setTrackEffectsState((prev) => {
2661
- const newState = new Map(prev);
2662
- newState.set(trackId, []);
2663
- return newState;
2664
- });
2665
- }, []);
2666
- const trackEffectsStateRef = (0, import_react18.useRef)(trackEffectsState);
2667
- trackEffectsStateRef.current = trackEffectsState;
2668
- const getTrackEffectsFunction = (0, import_react18.useCallback)(
2669
- (trackId) => {
2670
- return (graphEnd, masterGainNode, _isOffline) => {
2671
- trackGraphNodesRef.current.set(trackId, {
2672
- graphEnd,
2673
- masterGainNode
2674
- });
2675
- const trackEffects = trackEffectsStateRef.current.get(trackId) || [];
2676
- const instancesMap = trackEffectInstancesRef.current.get(trackId);
2677
- const instances = trackEffects.map((ae) => instancesMap == null ? void 0 : instancesMap.get(ae.instanceId)).filter((inst) => inst !== void 0);
2678
- if (instances.length === 0) {
2679
- graphEnd.connect(masterGainNode);
2680
- } else {
2681
- let currentNode = graphEnd;
2682
- instances.forEach((inst) => {
2683
- currentNode.connect(inst.effect);
2684
- currentNode = inst.effect;
2685
- });
2686
- currentNode.connect(masterGainNode);
2687
- }
2688
- return function cleanup() {
2689
- trackGraphNodesRef.current.delete(trackId);
2690
- };
2691
- };
2692
- },
2693
- []
2694
- // No dependencies - stable function that reads from refs
2695
- );
2696
- (0, import_react18.useEffect)(() => {
2697
- trackEffectsState.forEach((effects, trackId) => {
2698
- rebuildTrackChain(trackId, effects);
2699
- });
2700
- }, [trackEffectsState, rebuildTrackChain]);
2701
- (0, import_react18.useEffect)(() => {
2702
- const trackEffectInstances = trackEffectInstancesRef.current;
2703
- return () => {
2704
- trackEffectInstances.forEach((instancesMap) => {
2705
- instancesMap.forEach((inst) => inst.dispose());
2706
- instancesMap.clear();
2707
- });
2708
- trackEffectInstances.clear();
2709
- };
2710
- }, []);
2711
- const createOfflineTrackEffectsFunction = (0, import_react18.useCallback)(
2712
- (trackId) => {
2713
- const trackEffects = trackEffectsState.get(trackId) || [];
2714
- const nonBypassedEffects = trackEffects.filter((e) => !e.bypassed);
2715
- if (nonBypassedEffects.length === 0) {
2716
- return void 0;
2717
- }
2718
- return (graphEnd, masterGainNode, _isOffline) => {
2719
- const offlineInstances = [];
2720
- for (const activeEffect of nonBypassedEffects) {
2721
- const instance = createEffectInstance(activeEffect.definition, activeEffect.params);
2722
- offlineInstances.push(instance);
2723
- }
2724
- if (offlineInstances.length === 0) {
2725
- graphEnd.connect(masterGainNode);
2726
- } else {
2727
- let currentNode = graphEnd;
2728
- offlineInstances.forEach((inst) => {
2729
- currentNode.connect(inst.effect);
2730
- currentNode = inst.effect;
2731
- });
2732
- currentNode.connect(masterGainNode);
2733
- }
2734
- return function cleanup() {
2735
- offlineInstances.forEach((inst) => inst.dispose());
2736
- };
2737
- };
2738
- },
2739
- [trackEffectsState]
2740
- );
2741
- return {
2742
- trackEffectsState,
2743
- addEffectToTrack,
2744
- removeEffectFromTrack,
2745
- updateTrackEffectParameter,
2746
- toggleBypass,
2747
- clearTrackEffects,
2748
- getTrackEffectsFunction,
2749
- createOfflineTrackEffectsFunction,
2750
- availableEffects: effectDefinitions
2751
- };
2752
- }
2753
-
2754
- // src/hooks/useExportWav.ts
2755
- var import_react19 = require("react");
2756
- var import_core4 = require("@waveform-playlist/core");
2757
- var import_playout3 = require("@waveform-playlist/playout");
2758
-
2759
- // src/utils/wavEncoder.ts
2760
- function encodeWav(audioBuffer, options = {}) {
2761
- const { bitDepth = 16 } = options;
2762
- const numChannels = audioBuffer.numberOfChannels;
2763
- const sampleRate = audioBuffer.sampleRate;
2764
- const numSamples = audioBuffer.length;
2765
- const bytesPerSample = bitDepth / 8;
2766
- const blockAlign = numChannels * bytesPerSample;
2767
- const byteRate = sampleRate * blockAlign;
2768
- const dataSize = numSamples * blockAlign;
2769
- const headerSize = 44;
2770
- const totalSize = headerSize + dataSize;
2771
- const buffer = new ArrayBuffer(totalSize);
2772
- const view = new DataView(buffer);
2773
- writeString(view, 0, "RIFF");
2774
- view.setUint32(4, totalSize - 8, true);
2775
- writeString(view, 8, "WAVE");
2776
- writeString(view, 12, "fmt ");
2777
- view.setUint32(16, 16, true);
2778
- view.setUint16(20, bitDepth === 32 ? 3 : 1, true);
2779
- view.setUint16(22, numChannels, true);
2780
- view.setUint32(24, sampleRate, true);
2781
- view.setUint32(28, byteRate, true);
2782
- view.setUint16(32, blockAlign, true);
2783
- view.setUint16(34, bitDepth, true);
2784
- writeString(view, 36, "data");
2785
- view.setUint32(40, dataSize, true);
2786
- const channelData = [];
2787
- for (let ch = 0; ch < numChannels; ch++) {
2788
- channelData.push(audioBuffer.getChannelData(ch));
2789
- }
2790
- let offset = headerSize;
2791
- if (bitDepth === 16) {
2792
- for (let i = 0; i < numSamples; i++) {
2793
- for (let ch = 0; ch < numChannels; ch++) {
2794
- const sample = channelData[ch][i];
2795
- const clampedSample = Math.max(-1, Math.min(1, sample));
2796
- const intSample = clampedSample < 0 ? clampedSample * 32768 : clampedSample * 32767;
2797
- view.setInt16(offset, intSample, true);
2798
- offset += 2;
2799
- }
2800
- }
2801
- } else {
2802
- for (let i = 0; i < numSamples; i++) {
2803
- for (let ch = 0; ch < numChannels; ch++) {
2804
- view.setFloat32(offset, channelData[ch][i], true);
2805
- offset += 4;
2806
- }
2807
- }
2808
- }
2809
- return new Blob([buffer], { type: "audio/wav" });
2810
- }
2811
- function writeString(view, offset, str) {
2812
- for (let i = 0; i < str.length; i++) {
2813
- view.setUint8(offset + i, str.charCodeAt(i));
2814
- }
2815
- }
2816
- function downloadBlob(blob, filename) {
2817
- const url = URL.createObjectURL(blob);
2818
- const a = document.createElement("a");
2819
- a.href = url;
2820
- a.download = filename;
2821
- a.style.display = "none";
2822
- document.body.appendChild(a);
2823
- a.click();
2824
- document.body.removeChild(a);
2825
- URL.revokeObjectURL(url);
2826
- }
2827
-
2828
- // src/hooks/useExportWav.ts
2829
- function useExportWav() {
2830
- const [isExporting, setIsExporting] = (0, import_react19.useState)(false);
2831
- const [progress, setProgress] = (0, import_react19.useState)(0);
2832
- const [error, setError] = (0, import_react19.useState)(null);
2833
- const exportWav = (0, import_react19.useCallback)(
2834
- (_0, _1, ..._2) => __async(null, [_0, _1, ..._2], function* (tracks, trackStates, options = {}) {
2835
- const {
2836
- filename = "export",
2837
- mode = "master",
2838
- trackIndex,
2839
- autoDownload = true,
2840
- applyEffects = true,
2841
- effectsFunction,
2842
- createOfflineTrackEffects,
2843
- bitDepth = 16,
2844
- onProgress
2845
- } = options;
2846
- setIsExporting(true);
2847
- setProgress(0);
2848
- setError(null);
2849
- try {
2850
- if (tracks.length === 0) {
2851
- throw new Error("No tracks to export");
2852
- }
2853
- if (mode === "individual" && (trackIndex === void 0 || trackIndex < 0 || trackIndex >= tracks.length)) {
2854
- throw new Error("Invalid track index for individual export");
2855
- }
2856
- const sampleRate = (0, import_playout3.getGlobalAudioContext)().sampleRate;
2857
- let totalDurationSamples = 0;
2858
- for (const track of tracks) {
2859
- for (const clip of track.clips) {
2860
- const clipEndSample = clip.startSample + clip.durationSamples;
2861
- totalDurationSamples = Math.max(totalDurationSamples, clipEndSample);
2862
- }
2863
- }
2864
- totalDurationSamples += Math.round(sampleRate * 0.1);
2865
- const duration = totalDurationSamples / sampleRate;
2866
- const tracksToRender = mode === "individual" ? [{ track: tracks[trackIndex], state: trackStates[trackIndex], index: trackIndex }] : tracks.map((track, index) => ({ track, state: trackStates[index], index }));
2867
- const hasSolo = mode === "master" && trackStates.some((state) => state.soloed);
2868
- const reportProgress = (p) => {
2869
- setProgress(p);
2870
- onProgress == null ? void 0 : onProgress(p);
2871
- };
2872
- const renderedBuffer = yield renderOffline(
2873
- tracksToRender,
2874
- hasSolo,
2875
- duration,
2876
- sampleRate,
2877
- applyEffects,
2878
- effectsFunction,
2879
- createOfflineTrackEffects,
2880
- reportProgress
2881
- );
2882
- reportProgress(0.9);
2883
- const blob = encodeWav(renderedBuffer, { bitDepth });
2884
- reportProgress(1);
2885
- if (autoDownload) {
2886
- const exportFilename = mode === "individual" ? `${filename}_${tracks[trackIndex].name}` : filename;
2887
- downloadBlob(blob, `${exportFilename}.wav`);
2888
- }
2889
- return {
2890
- audioBuffer: renderedBuffer,
2891
- blob,
2892
- duration
2893
- };
2894
- } catch (err) {
2895
- const message = err instanceof Error ? err.message : "Export failed";
2896
- setError(message);
2897
- throw err;
2898
- } finally {
2899
- setIsExporting(false);
2900
- }
2901
- }),
2902
- []
2903
- );
2904
- return {
2905
- exportWav,
2906
- isExporting,
2907
- progress,
2908
- error
2909
- };
2910
- }
2911
- function renderOffline(tracksToRender, hasSolo, duration, sampleRate, applyEffects, effectsFunction, createOfflineTrackEffects, onProgress) {
2912
- return __async(this, null, function* () {
2913
- const { Offline, Volume: Volume2, Gain, Panner, Player, ToneAudioBuffer } = yield import("tone");
2914
- onProgress(0.1);
2915
- const audibleTracks = tracksToRender.filter(({ state }) => {
2916
- if (state.muted && !state.soloed) return false;
2917
- if (hasSolo && !state.soloed) return false;
2918
- return true;
2919
- });
2920
- const outputChannels = audibleTracks.reduce(
2921
- (max, { track }) => Math.max(max, (0, import_core4.trackChannelCount)(track)),
2922
- 1
2923
- );
2924
- let buffer;
2925
- try {
2926
- buffer = yield Offline(
2927
- (_0) => __async(null, [_0], function* ({ transport, destination }) {
2928
- const masterVolume = new Volume2(0);
2929
- if (effectsFunction && applyEffects) {
2930
- effectsFunction(masterVolume, destination, true);
2931
- } else {
2932
- masterVolume.connect(destination);
2933
- }
2934
- for (const { track, state } of audibleTracks) {
2935
- const trackVolume = new Volume2((0, import_core4.gainToDb)(state.volume));
2936
- const trackPan = new Panner({ pan: state.pan, channelCount: (0, import_core4.trackChannelCount)(track) });
2937
- const trackMute = new Gain(state.muted ? 0 : 1);
2938
- const trackEffects = createOfflineTrackEffects == null ? void 0 : createOfflineTrackEffects(track.id);
2939
- if (trackEffects && applyEffects) {
2940
- trackEffects(trackMute, masterVolume, true);
2941
- } else {
2942
- trackMute.connect(masterVolume);
2943
- }
2944
- trackPan.connect(trackMute);
2945
- trackVolume.connect(trackPan);
2946
- for (const clip of track.clips) {
2947
- const {
2948
- audioBuffer,
2949
- startSample,
2950
- durationSamples,
2951
- offsetSamples,
2952
- gain: clipGain,
2953
- fadeIn,
2954
- fadeOut
2955
- } = clip;
2956
- if (!audioBuffer) {
2957
- console.warn(
2958
- '[waveform-playlist] Skipping clip "' + (clip.name || clip.id) + '" - no audioBuffer for export'
2959
- );
2960
- continue;
2961
- }
2962
- const startTime = startSample / sampleRate;
2963
- const clipDuration = durationSamples / sampleRate;
2964
- const offset = offsetSamples / sampleRate;
2965
- const toneBuffer = new ToneAudioBuffer(audioBuffer);
2966
- const player = new Player(toneBuffer);
2967
- const fadeGain = new Gain(clipGain);
2968
- player.connect(fadeGain);
2969
- fadeGain.connect(trackVolume);
2970
- if (applyEffects) {
2971
- const audioParam = (0, import_playout3.getUnderlyingAudioParam)(fadeGain.gain);
2972
- if (audioParam) {
2973
- applyClipFades(audioParam, clipGain, startTime, clipDuration, fadeIn, fadeOut);
2974
- } else if (fadeIn || fadeOut) {
2975
- console.warn(
2976
- '[waveform-playlist] Cannot apply fades for clip "' + (clip.name || clip.id) + '" - AudioParam not accessible'
2977
- );
2978
- }
2979
- }
2980
- player.start(startTime, offset, clipDuration);
2981
- }
2982
- }
2983
- transport.start(0);
2984
- }),
2985
- duration,
2986
- outputChannels,
2987
- sampleRate
2988
- );
2989
- } catch (err) {
2990
- if (err instanceof Error) {
2991
- throw err;
2992
- } else {
2993
- throw new Error("Tone.Offline rendering failed: " + String(err));
2994
- }
2995
- }
2996
- onProgress(0.9);
2997
- const result = buffer.get();
2998
- if (!result) {
2999
- throw new Error("Offline rendering produced no audio buffer");
3000
- }
3001
- return result;
1442
+ );
1443
+ useKeyboardShortcuts({
1444
+ shortcuts: activeAnnotationShortcuts,
1445
+ enabled: enabled && activeIndex >= 0
3002
1446
  });
3003
- }
3004
- function applyClipFades(gainParam, clipGain, startTime, clipDuration, fadeIn, fadeOut) {
3005
- if (fadeIn) {
3006
- gainParam.setValueAtTime(0, startTime);
3007
- } else {
3008
- gainParam.setValueAtTime(clipGain, startTime);
3009
- }
3010
- if (fadeIn) {
3011
- (0, import_core4.applyFadeIn)(gainParam, startTime, fadeIn.duration, fadeIn.type || "linear", 0, clipGain);
3012
- }
3013
- if (fadeOut) {
3014
- const fadeOutStart = startTime + clipDuration - fadeOut.duration;
3015
- if (!fadeIn || fadeIn.duration < clipDuration - fadeOut.duration) {
3016
- gainParam.setValueAtTime(clipGain, fadeOutStart);
3017
- }
3018
- (0, import_core4.applyFadeOut)(gainParam, fadeOutStart, fadeOut.duration, fadeOut.type || "linear", clipGain, 0);
3019
- }
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
+ };
3020
1462
  }
3021
1463
 
3022
1464
  // src/hooks/useAnimationFrameLoop.ts
3023
- var import_react20 = require("react");
1465
+ var import_react15 = require("react");
3024
1466
  var useAnimationFrameLoop = () => {
3025
- const animationFrameRef = (0, import_react20.useRef)(null);
3026
- const stopAnimationFrameLoop = (0, import_react20.useCallback)(() => {
1467
+ const animationFrameRef = (0, import_react15.useRef)(null);
1468
+ const stopAnimationFrameLoop = (0, import_react15.useCallback)(() => {
3027
1469
  if (animationFrameRef.current !== null) {
3028
1470
  cancelAnimationFrame(animationFrameRef.current);
3029
1471
  animationFrameRef.current = null;
3030
1472
  }
3031
1473
  }, []);
3032
- const startAnimationFrameLoop = (0, import_react20.useCallback)(
1474
+ const startAnimationFrameLoop = (0, import_react15.useCallback)(
3033
1475
  (callback) => {
3034
1476
  stopAnimationFrameLoop();
3035
1477
  animationFrameRef.current = requestAnimationFrame(callback);
3036
1478
  },
3037
1479
  [stopAnimationFrameLoop]
3038
1480
  );
3039
- (0, import_react20.useEffect)(() => {
1481
+ (0, import_react15.useEffect)(() => {
3040
1482
  return () => {
3041
1483
  stopAnimationFrameLoop();
3042
1484
  };
@@ -3049,7 +1491,7 @@ var useAnimationFrameLoop = () => {
3049
1491
  };
3050
1492
 
3051
1493
  // src/hooks/useWaveformDataCache.ts
3052
- var import_react21 = require("react");
1494
+ var import_react16 = require("react");
3053
1495
 
3054
1496
  // src/workers/peaksWorker.ts
3055
1497
  var import_waveform_data2 = __toESM(require("waveform-data"));
@@ -3281,20 +1723,20 @@ function createPeaksWorker() {
3281
1723
 
3282
1724
  // src/hooks/useWaveformDataCache.ts
3283
1725
  function useWaveformDataCache(tracks, baseScale) {
3284
- const [cache, setCache] = (0, import_react21.useState)(() => /* @__PURE__ */ new Map());
3285
- const [isGenerating, setIsGenerating] = (0, import_react21.useState)(false);
3286
- const workerRef = (0, import_react21.useRef)(null);
3287
- const generatedByBufferRef = (0, import_react21.useRef)(/* @__PURE__ */ new WeakMap());
3288
- const inflightByBufferRef = (0, import_react21.useRef)(/* @__PURE__ */ new WeakMap());
3289
- const subscribersByBufferRef = (0, import_react21.useRef)(/* @__PURE__ */ new WeakMap());
3290
- const pendingCountRef = (0, import_react21.useRef)(0);
3291
- 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)(() => {
3292
1734
  if (!workerRef.current) {
3293
1735
  workerRef.current = createPeaksWorker();
3294
1736
  }
3295
1737
  return workerRef.current;
3296
1738
  }, []);
3297
- (0, import_react21.useEffect)(() => {
1739
+ (0, import_react16.useEffect)(() => {
3298
1740
  let cancelled = false;
3299
1741
  const generatedByBuffer = generatedByBufferRef.current;
3300
1742
  const inflightByBuffer = inflightByBufferRef.current;
@@ -3399,7 +1841,7 @@ function useWaveformDataCache(tracks, baseScale) {
3399
1841
  setIsGenerating(false);
3400
1842
  };
3401
1843
  }, [tracks, baseScale, getWorker]);
3402
- (0, import_react21.useEffect)(() => {
1844
+ (0, import_react16.useEffect)(() => {
3403
1845
  return () => {
3404
1846
  var _a;
3405
1847
  (_a = workerRef.current) == null ? void 0 : _a.terminate();
@@ -3409,232 +1851,12 @@ function useWaveformDataCache(tracks, baseScale) {
3409
1851
  return { cache, isGenerating };
3410
1852
  }
3411
1853
 
3412
- // src/hooks/useDynamicTracks.ts
3413
- var import_react22 = require("react");
3414
- var import_core5 = require("@waveform-playlist/core");
3415
- var import_playout4 = require("@waveform-playlist/playout");
3416
- function getSourceName(source) {
3417
- var _a, _b, _c, _d, _e;
3418
- if (source instanceof File) {
3419
- return source.name.replace(/\.[^/.]+$/, "");
3420
- }
3421
- if (source instanceof Blob) {
3422
- return "Untitled";
3423
- }
3424
- if (typeof source === "string") {
3425
- return (_b = (_a = source.split("/").pop()) == null ? void 0 : _a.replace(/\.[^/.]+$/, "")) != null ? _b : "Untitled";
3426
- }
3427
- return (_e = (_d = source.name) != null ? _d : (_c = source.src.split("/").pop()) == null ? void 0 : _c.replace(/\.[^/.]+$/, "")) != null ? _e : "Untitled";
3428
- }
3429
- function decodeSource(source, audioContext, signal) {
3430
- return __async(this, null, function* () {
3431
- const name = getSourceName(source);
3432
- if (source instanceof Blob) {
3433
- const arrayBuffer2 = yield source.arrayBuffer();
3434
- signal == null ? void 0 : signal.throwIfAborted();
3435
- const audioBuffer2 = yield audioContext.decodeAudioData(arrayBuffer2);
3436
- return { audioBuffer: audioBuffer2, name };
3437
- }
3438
- const url = typeof source === "string" ? source : source.src;
3439
- const response = yield fetch(url, { signal });
3440
- if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
3441
- const arrayBuffer = yield response.arrayBuffer();
3442
- signal == null ? void 0 : signal.throwIfAborted();
3443
- const audioBuffer = yield audioContext.decodeAudioData(arrayBuffer);
3444
- return { audioBuffer, name };
3445
- });
3446
- }
3447
- function useDynamicTracks() {
3448
- const [tracks, setTracks] = (0, import_react22.useState)([]);
3449
- const [loadingCount, setLoadingCount] = (0, import_react22.useState)(0);
3450
- const [errors, setErrors] = (0, import_react22.useState)([]);
3451
- const cancelledRef = (0, import_react22.useRef)(false);
3452
- const loadingIdsRef = (0, import_react22.useRef)(/* @__PURE__ */ new Set());
3453
- const abortControllersRef = (0, import_react22.useRef)(/* @__PURE__ */ new Map());
3454
- (0, import_react22.useEffect)(() => {
3455
- const controllers = abortControllersRef.current;
3456
- return () => {
3457
- cancelledRef.current = true;
3458
- for (const controller of controllers.values()) {
3459
- controller.abort();
3460
- }
3461
- controllers.clear();
3462
- };
3463
- }, []);
3464
- const addTracks = (0, import_react22.useCallback)((sources) => {
3465
- if (sources.length === 0) return;
3466
- const audioContext = (0, import_playout4.getGlobalAudioContext)();
3467
- const placeholders = sources.map((source) => ({
3468
- track: (0, import_core5.createTrack)({ name: `${getSourceName(source)} (loading...)`, clips: [] }),
3469
- source
3470
- }));
3471
- setTracks((prev) => [...prev, ...placeholders.map((p) => p.track)]);
3472
- setLoadingCount((prev) => prev + sources.length);
3473
- for (const { track, source } of placeholders) {
3474
- loadingIdsRef.current.add(track.id);
3475
- const controller = new AbortController();
3476
- abortControllersRef.current.set(track.id, controller);
3477
- (() => __async(null, null, function* () {
3478
- try {
3479
- const { audioBuffer, name } = yield decodeSource(source, audioContext, controller.signal);
3480
- const clip = (0, import_core5.createClipFromSeconds)({
3481
- audioBuffer,
3482
- startTime: 0,
3483
- duration: audioBuffer.duration,
3484
- offset: 0,
3485
- name
3486
- });
3487
- if (!cancelledRef.current && loadingIdsRef.current.has(track.id)) {
3488
- setTracks(
3489
- (prev) => prev.map((t) => t.id === track.id ? __spreadProps(__spreadValues({}, t), { name, clips: [clip] }) : t)
3490
- );
3491
- }
3492
- } catch (error) {
3493
- if (error instanceof DOMException && error.name === "AbortError") return;
3494
- console.warn("[waveform-playlist] Error loading audio:", error);
3495
- if (!cancelledRef.current && loadingIdsRef.current.has(track.id)) {
3496
- setTracks((prev) => prev.filter((t) => t.id !== track.id));
3497
- setErrors((prev) => [
3498
- ...prev,
3499
- {
3500
- name: getSourceName(source),
3501
- error: error instanceof Error ? error : new Error(String(error))
3502
- }
3503
- ]);
3504
- }
3505
- } finally {
3506
- abortControllersRef.current.delete(track.id);
3507
- if (!cancelledRef.current && loadingIdsRef.current.delete(track.id)) {
3508
- setLoadingCount((prev) => prev - 1);
3509
- }
3510
- }
3511
- }))();
3512
- }
3513
- }, []);
3514
- const removeTrack = (0, import_react22.useCallback)((trackId) => {
3515
- setTracks((prev) => prev.filter((t) => t.id !== trackId));
3516
- const controller = abortControllersRef.current.get(trackId);
3517
- if (controller) {
3518
- controller.abort();
3519
- abortControllersRef.current.delete(trackId);
3520
- }
3521
- if (loadingIdsRef.current.delete(trackId)) {
3522
- setLoadingCount((prev) => prev - 1);
3523
- }
3524
- }, []);
3525
- return {
3526
- tracks,
3527
- addTracks,
3528
- removeTrack,
3529
- loadingCount,
3530
- isLoading: loadingCount > 0,
3531
- errors
3532
- };
3533
- }
3534
-
3535
- // src/hooks/useOutputMeter.ts
3536
- var import_react23 = require("react");
3537
- var import_playout5 = require("@waveform-playlist/playout");
3538
- var import_core6 = require("@waveform-playlist/core");
3539
- var import_worklets = require("@waveform-playlist/worklets");
3540
- var PEAK_DECAY = 0.98;
3541
- function useOutputMeter(options = {}) {
3542
- const { channelCount = 2, updateRate = 60, isPlaying = false } = options;
3543
- const [levels, setLevels] = (0, import_react23.useState)(() => new Array(channelCount).fill(0));
3544
- const [peakLevels, setPeakLevels] = (0, import_react23.useState)(() => new Array(channelCount).fill(0));
3545
- const [rmsLevels, setRmsLevels] = (0, import_react23.useState)(() => new Array(channelCount).fill(0));
3546
- const workletNodeRef = (0, import_react23.useRef)(null);
3547
- const smoothedPeakRef = (0, import_react23.useRef)(new Array(channelCount).fill(0));
3548
- const [meterError, setMeterError] = (0, import_react23.useState)(null);
3549
- const resetPeak = (0, import_react23.useCallback)(
3550
- () => setPeakLevels(new Array(channelCount).fill(0)),
3551
- [channelCount]
3552
- );
3553
- (0, import_react23.useEffect)(() => {
3554
- if (!isPlaying) {
3555
- const zeros = new Array(channelCount).fill(0);
3556
- smoothedPeakRef.current = new Array(channelCount).fill(0);
3557
- setLevels(zeros);
3558
- setRmsLevels(zeros);
3559
- setPeakLevels(zeros);
3560
- }
3561
- }, [isPlaying, channelCount]);
3562
- (0, import_react23.useEffect)(() => {
3563
- let isMounted = true;
3564
- const setup = () => __async(null, null, function* () {
3565
- const context = (0, import_playout5.getGlobalContext)();
3566
- const rawCtx = context.rawContext;
3567
- yield rawCtx.audioWorklet.addModule(import_worklets.meterProcessorUrl);
3568
- if (!isMounted) return;
3569
- const workletNode = context.createAudioWorkletNode("meter-processor", {
3570
- channelCount,
3571
- channelCountMode: "explicit",
3572
- processorOptions: {
3573
- numberOfChannels: channelCount,
3574
- updateRate
3575
- }
3576
- });
3577
- workletNodeRef.current = workletNode;
3578
- workletNode.onprocessorerror = (event) => {
3579
- console.warn("[waveform-playlist] Output meter worklet processor error:", String(event));
3580
- };
3581
- const destination = context.destination;
3582
- destination.chain(workletNode);
3583
- smoothedPeakRef.current = new Array(channelCount).fill(0);
3584
- workletNode.port.onmessage = (event) => {
3585
- var _a;
3586
- if (!isMounted) return;
3587
- const { peak, rms } = event.data;
3588
- const smoothed = smoothedPeakRef.current;
3589
- const peakValues = [];
3590
- const rmsValues = [];
3591
- for (let ch = 0; ch < peak.length; ch++) {
3592
- smoothed[ch] = Math.max(peak[ch], ((_a = smoothed[ch]) != null ? _a : 0) * PEAK_DECAY);
3593
- peakValues.push((0, import_core6.gainToNormalized)(smoothed[ch]));
3594
- rmsValues.push((0, import_core6.gainToNormalized)(rms[ch]));
3595
- }
3596
- setLevels(peakValues);
3597
- setRmsLevels(rmsValues);
3598
- setPeakLevels((prev) => peakValues.map((val, i) => {
3599
- var _a2;
3600
- return Math.max((_a2 = prev[i]) != null ? _a2 : 0, val);
3601
- }));
3602
- };
3603
- });
3604
- setup().catch((err) => {
3605
- console.warn("[waveform-playlist] Failed to set up output meter:", String(err));
3606
- if (isMounted) {
3607
- setMeterError(err instanceof Error ? err : new Error(String(err)));
3608
- }
3609
- });
3610
- return () => {
3611
- isMounted = false;
3612
- if (workletNodeRef.current) {
3613
- try {
3614
- const context = (0, import_playout5.getGlobalContext)();
3615
- context.destination.chain();
3616
- } catch (err) {
3617
- console.warn("[waveform-playlist] Failed to restore destination chain:", String(err));
3618
- }
3619
- try {
3620
- workletNodeRef.current.disconnect();
3621
- workletNodeRef.current.port.close();
3622
- } catch (err) {
3623
- console.warn("[waveform-playlist] Output meter disconnect during cleanup:", String(err));
3624
- }
3625
- workletNodeRef.current = null;
3626
- }
3627
- };
3628
- }, [channelCount, updateRate]);
3629
- return { levels, peakLevels, rmsLevels, resetPeak, error: meterError };
3630
- }
3631
-
3632
1854
  // src/WaveformPlaylistContext.tsx
3633
1855
  var import_jsx_runtime = require("react/jsx-runtime");
3634
- var PlaybackAnimationContext = (0, import_react24.createContext)(null);
3635
- var PlaylistStateContext = (0, import_react24.createContext)(null);
3636
- var PlaylistControlsContext = (0, import_react24.createContext)(null);
3637
- 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);
3638
1860
  var WaveformPlaylistProvider = ({
3639
1861
  tracks,
3640
1862
  timescale = false,
@@ -3648,6 +1870,7 @@ var WaveformPlaylistProvider = ({
3648
1870
  annotationList,
3649
1871
  effects,
3650
1872
  onReady,
1873
+ onError,
3651
1874
  onAnnotationUpdate: _onAnnotationUpdate,
3652
1875
  onAnnotationsChange,
3653
1876
  barWidth = 1,
@@ -3658,18 +1881,19 @@ var WaveformPlaylistProvider = ({
3658
1881
  deferEngineRebuild = false,
3659
1882
  indefinitePlayback = false,
3660
1883
  sampleRate: sampleRateProp,
1884
+ createAdapter,
3661
1885
  children
3662
1886
  }) => {
3663
1887
  var _a, _b, _c, _d;
3664
1888
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
3665
- const indefinitePlaybackRef = (0, import_react24.useRef)(indefinitePlayback);
1889
+ const indefinitePlaybackRef = (0, import_react17.useRef)(indefinitePlayback);
3666
1890
  indefinitePlaybackRef.current = indefinitePlayback;
3667
- const stableZoomLevels = (0, import_react24.useMemo)(
1891
+ const stableZoomLevels = (0, import_react17.useMemo)(
3668
1892
  () => zoomLevels,
3669
1893
  // eslint-disable-next-line react-hooks/exhaustive-deps
3670
1894
  [zoomLevels == null ? void 0 : zoomLevels.join(",")]
3671
1895
  );
3672
- const annotations = (0, import_react24.useMemo)(() => {
1896
+ const annotations = (0, import_react17.useMemo)(() => {
3673
1897
  if (!(annotationList == null ? void 0 : annotationList.annotations)) return [];
3674
1898
  if (process.env.NODE_ENV !== "production" && annotationList.annotations.length > 0) {
3675
1899
  const first = annotationList.annotations[0];
@@ -3682,63 +1906,50 @@ var WaveformPlaylistProvider = ({
3682
1906
  }
3683
1907
  return annotationList.annotations;
3684
1908
  }, [annotationList == null ? void 0 : annotationList.annotations]);
3685
- const annotationsRef = (0, import_react24.useRef)(annotations);
1909
+ const annotationsRef = (0, import_react17.useRef)(annotations);
3686
1910
  annotationsRef.current = annotations;
3687
- const [activeAnnotationId, setActiveAnnotationIdState] = (0, import_react24.useState)(null);
3688
- const [isPlaying, setIsPlaying] = (0, import_react24.useState)(false);
3689
- const [currentTime, setCurrentTime] = (0, import_react24.useState)(0);
3690
- const [duration, setDuration] = (0, import_react24.useState)(0);
3691
- const [audioBuffers, setAudioBuffers] = (0, import_react24.useState)([]);
3692
- const [peaksDataArray, setPeaksDataArray] = (0, import_react24.useState)([]);
3693
- const [trackStates, setTrackStates] = (0, import_react24.useState)([]);
3694
- const [isAutomaticScroll, setIsAutomaticScroll] = (0, import_react24.useState)(automaticScroll);
3695
- 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)(
3696
1920
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
3697
1921
  );
3698
- const [linkEndpoints, setLinkEndpoints] = (0, import_react24.useState)((_b = annotationList == null ? void 0 : annotationList.linkEndpoints) != null ? _b : false);
3699
- const [annotationsEditable, setAnnotationsEditable] = (0, import_react24.useState)((_c = annotationList == null ? void 0 : annotationList.editable) != null ? _c : false);
3700
- const [isReady, setIsReady] = (0, import_react24.useState)(false);
3701
- const engineRef = (0, import_react24.useRef)(null);
3702
- const adapterRef = (0, import_react24.useRef)(null);
3703
- const audioInitializedRef = (0, import_react24.useRef)(false);
3704
- 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);
3705
1929
  isPlayingRef.current = isPlaying;
3706
- const playStartPositionRef = (0, import_react24.useRef)(0);
3707
- const currentTimeRef = (0, import_react24.useRef)(0);
3708
- const visualTimeRef = (0, import_react24.useRef)(0);
3709
- const tracksRef = (0, import_react24.useRef)(tracks);
3710
- 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);
3711
1935
  soundFontCacheRef.current = soundFontCache;
3712
- const trackStatesRef = (0, import_react24.useRef)(trackStates);
3713
- const playbackStartTimeRef = (0, import_react24.useRef)(0);
3714
- const audioStartPositionRef = (0, import_react24.useRef)(0);
3715
- const playbackEndTimeRef = (0, import_react24.useRef)(null);
3716
- const scrollContainerRef = (0, import_react24.useRef)(null);
3717
- const isAutomaticScrollRef = (0, import_react24.useRef)(false);
3718
- const frameCallbacksRef = (0, import_react24.useRef)(/* @__PURE__ */ new Map());
3719
- const continuousPlayRef = (0, import_react24.useRef)((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
3720
- const activeAnnotationIdRef = (0, import_react24.useRef)(null);
3721
- const engineTracksRef = (0, import_react24.useRef)(null);
3722
- const lastTracksVersionRef = (0, import_react24.useRef)(0);
3723
- const skipEngineDisposeRef = (0, import_react24.useRef)(false);
3724
- const isDraggingRef = (0, import_react24.useRef)(false);
3725
- const prevTracksRef = (0, import_react24.useRef)([]);
3726
- const samplesPerPixelRef = (0, import_react24.useRef)(initialSamplesPerPixel);
3727
- const [initialSampleRate] = (0, import_react24.useState)(() => {
3728
- if (typeof AudioContext === "undefined") return sampleRateProp != null ? sampleRateProp : 48e3;
3729
- try {
3730
- if (sampleRateProp !== void 0) {
3731
- return (0, import_playout6.configureGlobalContext)({ sampleRate: sampleRateProp });
3732
- }
3733
- return (0, import_playout6.getGlobalAudioContext)().sampleRate;
3734
- } catch (err) {
3735
- console.warn(
3736
- "[waveform-playlist] Failed to configure AudioContext: " + String(err) + " \u2014 falling back to " + (sampleRateProp != null ? sampleRateProp : 48e3) + " Hz"
3737
- );
3738
- return sampleRateProp != null ? sampleRateProp : 48e3;
3739
- }
3740
- });
3741
- 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);
3742
1953
  const { timeFormat, setTimeFormat, formatTime: formatTime2 } = useTimeFormat();
3743
1954
  const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });
3744
1955
  const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;
@@ -3783,20 +1994,20 @@ var WaveformPlaylistProvider = ({
3783
1994
  onEngineState: onUndoEngineState
3784
1995
  } = useUndoState({ engineRef });
3785
1996
  const { animationFrameRef, startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
3786
- const baseScale = (0, import_react24.useMemo)(
1997
+ const baseScale = (0, import_react17.useMemo)(
3787
1998
  () => Math.min(...stableZoomLevels != null ? stableZoomLevels : [256, 512, 1024, 2048, 4096, 8192]),
3788
1999
  [stableZoomLevels]
3789
2000
  );
3790
2001
  const { cache: waveformDataCache } = useWaveformDataCache(tracks, baseScale);
3791
- const setContinuousPlay = (0, import_react24.useCallback)((value) => {
2002
+ const setContinuousPlay = (0, import_react17.useCallback)((value) => {
3792
2003
  continuousPlayRef.current = value;
3793
2004
  setContinuousPlayState(value);
3794
2005
  }, []);
3795
- const setActiveAnnotationId = (0, import_react24.useCallback)((value) => {
2006
+ const setActiveAnnotationId = (0, import_react17.useCallback)((value) => {
3796
2007
  activeAnnotationIdRef.current = value;
3797
2008
  setActiveAnnotationIdState(value);
3798
2009
  }, []);
3799
- const setLoopRegionFromSelection = (0, import_react24.useCallback)(() => {
2010
+ const setLoopRegionFromSelection = (0, import_react17.useCallback)(() => {
3800
2011
  var _a2, _b2;
3801
2012
  const start = (_a2 = selectionStartRef.current) != null ? _a2 : 0;
3802
2013
  const end = (_b2 = selectionEndRef.current) != null ? _b2 : 0;
@@ -3804,10 +2015,10 @@ var WaveformPlaylistProvider = ({
3804
2015
  setLoopRegion(start, end);
3805
2016
  }
3806
2017
  }, [setLoopRegion, selectionStartRef, selectionEndRef]);
3807
- (0, import_react24.useEffect)(() => {
2018
+ (0, import_react17.useEffect)(() => {
3808
2019
  isAutomaticScrollRef.current = isAutomaticScroll;
3809
2020
  }, [isAutomaticScroll]);
3810
- (0, import_react24.useEffect)(() => {
2021
+ (0, import_react17.useEffect)(() => {
3811
2022
  trackStatesRef.current = trackStates;
3812
2023
  }, [trackStates]);
3813
2024
  tracksRef.current = tracks;
@@ -3818,7 +2029,7 @@ var WaveformPlaylistProvider = ({
3818
2029
  return current === pt;
3819
2030
  });
3820
2031
  skipEngineDisposeRef.current = isEngineTracks || isDraggingRef.current || isIncrementalAdd;
3821
- (0, import_react24.useEffect)(() => {
2032
+ (0, import_react17.useEffect)(() => {
3822
2033
  if (!scrollContainerRef.current || duration === 0) return;
3823
2034
  const container = scrollContainerRef.current;
3824
2035
  const oldSamplesPerPixel = samplesPerPixelRef.current;
@@ -3834,8 +2045,8 @@ var WaveformPlaylistProvider = ({
3834
2045
  container.scrollLeft = newScrollLeft;
3835
2046
  samplesPerPixelRef.current = newSamplesPerPixel;
3836
2047
  }, [samplesPerPixel, duration]);
3837
- const pendingResumeRef = (0, import_react24.useRef)(null);
3838
- (0, import_react24.useEffect)(() => {
2048
+ const pendingResumeRef = (0, import_react17.useRef)(null);
2049
+ (0, import_react17.useEffect)(() => {
3839
2050
  var _a2, _b2, _c2, _d2;
3840
2051
  if (isEngineTracks || isDraggingRef.current) {
3841
2052
  if (isEngineTracks) {
@@ -3940,6 +2151,7 @@ var WaveformPlaylistProvider = ({
3940
2151
  stopAnimationFrameLoop();
3941
2152
  pendingResumeRef.current = { position: resumePosition };
3942
2153
  }
2154
+ let cancelled = false;
3943
2155
  const loadAudio = () => __async(null, null, function* () {
3944
2156
  var _a3, _b3, _c3, _d3, _e;
3945
2157
  try {
@@ -3982,7 +2194,17 @@ var WaveformPlaylistProvider = ({
3982
2194
  lastTracksVersionRef.current = 0;
3983
2195
  engineTracksRef.current = null;
3984
2196
  audioInitializedRef.current = false;
3985
- 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;
3986
2208
  adapterRef.current = adapter;
3987
2209
  const engine = new import_engine3.PlaylistEngine({
3988
2210
  adapter,
@@ -4040,11 +2262,23 @@ var WaveformPlaylistProvider = ({
4040
2262
  prevTracksRef.current = tracks;
4041
2263
  onReady == null ? void 0 : onReady();
4042
2264
  } catch (error) {
4043
- 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)));
4044
2277
  }
4045
2278
  });
4046
2279
  loadAudio();
4047
2280
  return () => {
2281
+ cancelled = true;
4048
2282
  if (skipEngineDisposeRef.current) {
4049
2283
  skipEngineDisposeRef.current = false;
4050
2284
  return;
@@ -4067,6 +2301,7 @@ var WaveformPlaylistProvider = ({
4067
2301
  // to the live adapter by the sync effect below; only adapter creation reads it
4068
2302
  // (via soundFontCacheRef).
4069
2303
  onReady,
2304
+ onError,
4070
2305
  effects,
4071
2306
  stopAnimationFrameLoop,
4072
2307
  onSelectionEngineState,
@@ -4085,10 +2320,10 @@ var WaveformPlaylistProvider = ({
4085
2320
  stableZoomLevels,
4086
2321
  deferEngineRebuild
4087
2322
  ]);
4088
- (0, import_react24.useEffect)(() => {
2323
+ (0, import_react17.useEffect)(() => {
4089
2324
  syncSoundFontCacheToAdapter(adapterRef.current, soundFontCache);
4090
2325
  }, [soundFontCache]);
4091
- (0, import_react24.useEffect)(() => {
2326
+ (0, import_react17.useEffect)(() => {
4092
2327
  if (tracks.length === 0) return;
4093
2328
  const allTrackPeaks = tracks.map((track) => {
4094
2329
  const clipPeaks = track.clips.map((clip) => {
@@ -4169,8 +2404,15 @@ var WaveformPlaylistProvider = ({
4169
2404
  });
4170
2405
  setPeaksDataArray(allTrackPeaks);
4171
2406
  }, [tracks, samplesPerPixel, mono, waveformDataCache, deferEngineRebuild]);
4172
- const getPlaybackTimeFallbackWarnedRef = (0, import_react24.useRef)(false);
4173
- 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)(() => {
4174
2416
  var _a2, _b2;
4175
2417
  if (engineRef.current) {
4176
2418
  return engineRef.current.getCurrentTime();
@@ -4181,30 +2423,35 @@ var WaveformPlaylistProvider = ({
4181
2423
  "[waveform-playlist] getPlaybackTime called without engine. Falling back to manual elapsed time (loop wrapping will not work)."
4182
2424
  );
4183
2425
  }
4184
- const elapsed = (0, import_tone4.getContext)().currentTime - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
2426
+ const elapsed = getAudioContextTime() - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
4185
2427
  return ((_b2 = audioStartPositionRef.current) != null ? _b2 : 0) + elapsed;
4186
- }, []);
4187
- const toVisualTime = (0, import_react24.useCallback)((rawTime) => {
2428
+ }, [getAudioContextTime]);
2429
+ const toVisualTime = (0, import_react17.useCallback)((rawTime) => {
4188
2430
  return Number.isFinite(rawTime) ? Math.max(0, rawTime) : 0;
4189
2431
  }, []);
4190
- const setCurrentTimeRefs = (0, import_react24.useCallback)(
2432
+ const setCurrentTimeRefs = (0, import_react17.useCallback)(
4191
2433
  (rawTime) => {
4192
2434
  currentTimeRef.current = rawTime;
4193
2435
  visualTimeRef.current = toVisualTime(rawTime);
4194
2436
  },
4195
2437
  [toVisualTime]
4196
2438
  );
4197
- const getLookAhead = (0, import_react24.useCallback)(() => {
2439
+ const getLookAhead = (0, import_react17.useCallback)(() => {
4198
2440
  var _a2, _b2;
4199
2441
  return (_b2 = (_a2 = engineRef.current) == null ? void 0 : _a2.lookAhead) != null ? _b2 : 0;
4200
2442
  }, []);
4201
- 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) => {
4202
2449
  frameCallbacksRef.current.set(id, cb);
4203
2450
  }, []);
4204
- const unregisterFrameCallback = (0, import_react24.useCallback)((id) => {
2451
+ const unregisterFrameCallback = (0, import_react17.useCallback)((id) => {
4205
2452
  frameCallbacksRef.current.delete(id);
4206
2453
  }, []);
4207
- const startAnimationLoop = (0, import_react24.useCallback)(() => {
2454
+ const startAnimationLoop = (0, import_react17.useCallback)(() => {
4208
2455
  const updateTime = () => {
4209
2456
  const time = getPlaybackTime();
4210
2457
  currentTimeRef.current = time;
@@ -4292,15 +2539,14 @@ var WaveformPlaylistProvider = ({
4292
2539
  setCurrentTimeRefs
4293
2540
  ]);
4294
2541
  const stopAnimationLoop = stopAnimationFrameLoop;
4295
- (0, import_react24.useEffect)(() => {
2542
+ (0, import_react17.useEffect)(() => {
4296
2543
  const reschedulePlayback = () => __async(null, null, function* () {
4297
2544
  if (isPlaying && animationFrameRef.current && engineRef.current) {
4298
2545
  if (continuousPlay) {
4299
2546
  const currentPos = currentTimeRef.current;
4300
2547
  engineRef.current.stop();
4301
2548
  stopAnimationLoop();
4302
- const context = (0, import_tone4.getContext)();
4303
- const timeNow = context.currentTime;
2549
+ const timeNow = getAudioContextTime();
4304
2550
  playbackStartTimeRef.current = timeNow;
4305
2551
  audioStartPositionRef.current = currentPos;
4306
2552
  engineRef.current.play(currentPos);
@@ -4316,14 +2562,20 @@ var WaveformPlaylistProvider = ({
4316
2562
  setIsPlaying(false);
4317
2563
  stopAnimationLoop();
4318
2564
  });
4319
- }, [continuousPlay, isPlaying, startAnimationLoop, stopAnimationLoop, animationFrameRef]);
4320
- (0, import_react24.useEffect)(() => {
2565
+ }, [
2566
+ continuousPlay,
2567
+ isPlaying,
2568
+ startAnimationLoop,
2569
+ stopAnimationLoop,
2570
+ animationFrameRef,
2571
+ getAudioContextTime
2572
+ ]);
2573
+ (0, import_react17.useEffect)(() => {
4321
2574
  const resumePlayback = () => __async(null, null, function* () {
4322
2575
  if (pendingResumeRef.current && engineRef.current) {
4323
2576
  const { position } = pendingResumeRef.current;
4324
2577
  pendingResumeRef.current = null;
4325
- const context = (0, import_tone4.getContext)();
4326
- const timeNow = context.currentTime;
2578
+ const timeNow = getAudioContextTime();
4327
2579
  playbackStartTimeRef.current = timeNow;
4328
2580
  audioStartPositionRef.current = position;
4329
2581
  if (!audioInitializedRef.current) {
@@ -4340,8 +2592,8 @@ var WaveformPlaylistProvider = ({
4340
2592
  setIsPlaying(false);
4341
2593
  stopAnimationLoop();
4342
2594
  });
4343
- }, [tracks, startAnimationLoop, stopAnimationLoop]);
4344
- const play = (0, import_react24.useCallback)(
2595
+ }, [tracks, startAnimationLoop, stopAnimationLoop, getAudioContextTime]);
2596
+ const play = (0, import_react17.useCallback)(
4345
2597
  (startTime, playDuration) => __async(null, null, function* () {
4346
2598
  if (!engineRef.current) return;
4347
2599
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4350,8 +2602,7 @@ var WaveformPlaylistProvider = ({
4350
2602
  engineRef.current.stop();
4351
2603
  engineRef.current.seek(actualStartTime);
4352
2604
  stopAnimationLoop();
4353
- const context = (0, import_tone4.getContext)();
4354
- const startTimeNow = context.currentTime;
2605
+ const startTimeNow = getAudioContextTime();
4355
2606
  playbackStartTimeRef.current = startTimeNow;
4356
2607
  audioStartPositionRef.current = actualStartTime;
4357
2608
  playbackEndTimeRef.current = playDuration !== void 0 ? actualStartTime + playDuration : null;
@@ -4370,9 +2621,9 @@ var WaveformPlaylistProvider = ({
4370
2621
  setIsPlaying(true);
4371
2622
  startAnimationLoop();
4372
2623
  }),
4373
- [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs]
2624
+ [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs, getAudioContextTime]
4374
2625
  );
4375
- const pause = (0, import_react24.useCallback)(() => {
2626
+ const pause = (0, import_react17.useCallback)(() => {
4376
2627
  if (!engineRef.current) return;
4377
2628
  const pauseTime = getPlaybackTime();
4378
2629
  engineRef.current.pause();
@@ -4381,7 +2632,7 @@ var WaveformPlaylistProvider = ({
4381
2632
  setCurrentTimeRefs(pauseTime);
4382
2633
  setCurrentTime(pauseTime);
4383
2634
  }, [stopAnimationLoop, getPlaybackTime, setCurrentTimeRefs]);
4384
- const stop = (0, import_react24.useCallback)(() => {
2635
+ const stop = (0, import_react17.useCallback)(() => {
4385
2636
  if (!engineRef.current) return;
4386
2637
  engineRef.current.stop();
4387
2638
  setIsPlaying(false);
@@ -4390,7 +2641,7 @@ var WaveformPlaylistProvider = ({
4390
2641
  setCurrentTime(playStartPositionRef.current);
4391
2642
  setActiveAnnotationId(null);
4392
2643
  }, [stopAnimationLoop, setActiveAnnotationId, setCurrentTimeRefs]);
4393
- const seekTo = (0, import_react24.useCallback)(
2644
+ const seekTo = (0, import_react17.useCallback)(
4394
2645
  (time) => {
4395
2646
  const clampedTime = Math.max(0, Math.min(time, duration));
4396
2647
  setCurrentTimeRefs(clampedTime);
@@ -4401,7 +2652,7 @@ var WaveformPlaylistProvider = ({
4401
2652
  },
4402
2653
  [duration, isPlaying, play, setCurrentTimeRefs]
4403
2654
  );
4404
- const setTrackMute = (0, import_react24.useCallback)(
2655
+ const setTrackMute = (0, import_react17.useCallback)(
4405
2656
  (trackIndex, muted) => {
4406
2657
  var _a2;
4407
2658
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4415,7 +2666,7 @@ var WaveformPlaylistProvider = ({
4415
2666
  },
4416
2667
  [trackStates]
4417
2668
  );
4418
- const setTrackSolo = (0, import_react24.useCallback)(
2669
+ const setTrackSolo = (0, import_react17.useCallback)(
4419
2670
  (trackIndex, soloed) => {
4420
2671
  var _a2;
4421
2672
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4429,7 +2680,7 @@ var WaveformPlaylistProvider = ({
4429
2680
  },
4430
2681
  [trackStates]
4431
2682
  );
4432
- const setTrackVolume = (0, import_react24.useCallback)(
2683
+ const setTrackVolume = (0, import_react17.useCallback)(
4433
2684
  (trackIndex, volume2) => {
4434
2685
  var _a2;
4435
2686
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4443,7 +2694,7 @@ var WaveformPlaylistProvider = ({
4443
2694
  },
4444
2695
  [trackStates]
4445
2696
  );
4446
- const setTrackPan = (0, import_react24.useCallback)(
2697
+ const setTrackPan = (0, import_react17.useCallback)(
4447
2698
  (trackIndex, pan) => {
4448
2699
  var _a2;
4449
2700
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4457,7 +2708,7 @@ var WaveformPlaylistProvider = ({
4457
2708
  },
4458
2709
  [trackStates]
4459
2710
  );
4460
- const setSelection = (0, import_react24.useCallback)(
2711
+ const setSelection = (0, import_react17.useCallback)(
4461
2712
  (start, end) => {
4462
2713
  setSelectionEngine(start, end);
4463
2714
  setCurrentTimeRefs(start);
@@ -4470,12 +2721,12 @@ var WaveformPlaylistProvider = ({
4470
2721
  },
4471
2722
  [isPlaying, setSelectionEngine, setCurrentTimeRefs]
4472
2723
  );
4473
- const setScrollContainer = (0, import_react24.useCallback)((element) => {
2724
+ const setScrollContainer = (0, import_react17.useCallback)((element) => {
4474
2725
  scrollContainerRef.current = element;
4475
2726
  }, []);
4476
- const onAnnotationsChangeRef = (0, import_react24.useRef)(onAnnotationsChange);
2727
+ const onAnnotationsChangeRef = (0, import_react17.useRef)(onAnnotationsChange);
4477
2728
  onAnnotationsChangeRef.current = onAnnotationsChange;
4478
- const setAnnotations = (0, import_react24.useCallback)(
2729
+ const setAnnotations = (0, import_react17.useCallback)(
4479
2730
  (action) => {
4480
2731
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4481
2732
  if (!onAnnotationsChangeRef.current) {
@@ -4493,7 +2744,7 @@ var WaveformPlaylistProvider = ({
4493
2744
  const sampleRate = sampleRateRef.current;
4494
2745
  const timeScaleHeight = timescale ? 30 : 0;
4495
2746
  const minimumPlaylistHeight = tracks.length * waveHeight + timeScaleHeight;
4496
- const animationValue = (0, import_react24.useMemo)(
2747
+ const animationValue = (0, import_react17.useMemo)(
4497
2748
  () => ({
4498
2749
  isPlaying,
4499
2750
  currentTime,
@@ -4502,7 +2753,9 @@ var WaveformPlaylistProvider = ({
4502
2753
  playbackStartTimeRef,
4503
2754
  audioStartPositionRef,
4504
2755
  getPlaybackTime,
2756
+ getAudioContextTime,
4505
2757
  getLookAhead,
2758
+ getOutputLatency,
4506
2759
  registerFrameCallback,
4507
2760
  unregisterFrameCallback
4508
2761
  }),
@@ -4514,12 +2767,14 @@ var WaveformPlaylistProvider = ({
4514
2767
  playbackStartTimeRef,
4515
2768
  audioStartPositionRef,
4516
2769
  getPlaybackTime,
2770
+ getAudioContextTime,
4517
2771
  getLookAhead,
2772
+ getOutputLatency,
4518
2773
  registerFrameCallback,
4519
2774
  unregisterFrameCallback
4520
2775
  ]
4521
2776
  );
4522
- const stateValue = (0, import_react24.useMemo)(
2777
+ const stateValue = (0, import_react17.useMemo)(
4523
2778
  () => ({
4524
2779
  continuousPlay,
4525
2780
  linkEndpoints,
@@ -4555,17 +2810,17 @@ var WaveformPlaylistProvider = ({
4555
2810
  canRedo
4556
2811
  ]
4557
2812
  );
4558
- const setCurrentTimeControl = (0, import_react24.useCallback)(
2813
+ const setCurrentTimeControl = (0, import_react17.useCallback)(
4559
2814
  (time) => {
4560
2815
  setCurrentTimeRefs(time);
4561
2816
  setCurrentTime(time);
4562
2817
  },
4563
2818
  [setCurrentTimeRefs]
4564
2819
  );
4565
- const setAutomaticScrollControl = (0, import_react24.useCallback)((enabled) => {
2820
+ const setAutomaticScrollControl = (0, import_react17.useCallback)((enabled) => {
4566
2821
  setIsAutomaticScroll(enabled);
4567
2822
  }, []);
4568
- const controlsValue = (0, import_react24.useMemo)(
2823
+ const controlsValue = (0, import_react17.useMemo)(
4569
2824
  () => ({
4570
2825
  // Playback controls
4571
2826
  play,
@@ -4641,7 +2896,7 @@ var WaveformPlaylistProvider = ({
4641
2896
  redo
4642
2897
  ]
4643
2898
  );
4644
- const dataValue = (0, import_react24.useMemo)(
2899
+ const dataValue = (0, import_react17.useMemo)(
4645
2900
  () => ({
4646
2901
  duration,
4647
2902
  audioBuffers,
@@ -4697,45 +2952,69 @@ var WaveformPlaylistProvider = ({
4697
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 }) }) }) }) });
4698
2953
  };
4699
2954
  var usePlaybackAnimation = () => {
4700
- const context = (0, import_react24.useContext)(PlaybackAnimationContext);
2955
+ const context = (0, import_react17.useContext)(PlaybackAnimationContext);
4701
2956
  if (!context) {
4702
2957
  throw new Error("usePlaybackAnimation must be used within WaveformPlaylistProvider");
4703
2958
  }
4704
2959
  return context;
4705
2960
  };
4706
2961
  var usePlaylistState = () => {
4707
- const context = (0, import_react24.useContext)(PlaylistStateContext);
2962
+ const context = (0, import_react17.useContext)(PlaylistStateContext);
4708
2963
  if (!context) {
4709
2964
  throw new Error("usePlaylistState must be used within WaveformPlaylistProvider");
4710
2965
  }
4711
2966
  return context;
4712
2967
  };
4713
2968
  var usePlaylistControls = () => {
4714
- const context = (0, import_react24.useContext)(PlaylistControlsContext);
2969
+ const context = (0, import_react17.useContext)(PlaylistControlsContext);
4715
2970
  if (!context) {
4716
2971
  throw new Error("usePlaylistControls must be used within WaveformPlaylistProvider");
4717
2972
  }
4718
2973
  return context;
4719
2974
  };
4720
2975
  var usePlaylistData = () => {
4721
- const context = (0, import_react24.useContext)(PlaylistDataContext);
2976
+ const context = (0, import_react17.useContext)(PlaylistDataContext);
4722
2977
  if (!context) {
4723
2978
  throw new Error("usePlaylistData must be used within WaveformPlaylistProvider");
4724
2979
  }
4725
2980
  return context;
4726
2981
  };
4727
- var usePlaylistDataOptional = () => (0, import_react24.useContext)(PlaylistDataContext);
2982
+ var usePlaylistDataOptional = () => (0, import_react17.useContext)(PlaylistDataContext);
4728
2983
 
4729
2984
  // src/MediaElementPlaylistContext.tsx
4730
- var import_react25 = require("react");
2985
+ var import_react18 = require("react");
4731
2986
  var import_styled_components2 = require("styled-components");
4732
- 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
4733
3012
  var import_ui_components3 = require("@waveform-playlist/ui-components");
4734
3013
  var import_jsx_runtime2 = require("react/jsx-runtime");
4735
- var MediaElementAnimationContext = (0, import_react25.createContext)(null);
4736
- var MediaElementStateContext = (0, import_react25.createContext)(null);
4737
- var MediaElementControlsContext = (0, import_react25.createContext)(null);
4738
- 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);
4739
3018
  var MediaElementPlaylistProvider = ({
4740
3019
  track,
4741
3020
  samplesPerPixel: initialSamplesPerPixel = 1024,
@@ -4753,16 +3032,18 @@ var MediaElementPlaylistProvider = ({
4753
3032
  audioContext,
4754
3033
  onAnnotationsChange,
4755
3034
  onReady,
3035
+ onError,
3036
+ createPlayout,
4756
3037
  children
4757
3038
  }) => {
4758
3039
  var _a;
4759
3040
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
4760
- const [isPlaying, setIsPlaying] = (0, import_react25.useState)(false);
4761
- const [currentTime, setCurrentTime] = (0, import_react25.useState)(0);
4762
- const [duration, setDuration] = (0, import_react25.useState)(0);
4763
- const [peaksDataArray, setPeaksDataArray] = (0, import_react25.useState)([]);
4764
- const [playbackRate, setPlaybackRateState] = (0, import_react25.useState)(initialPlaybackRate);
4765
- 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)(() => {
4766
3047
  if (!(annotationList == null ? void 0 : annotationList.annotations)) return [];
4767
3048
  if (process.env.NODE_ENV !== "production" && annotationList.annotations.length > 0) {
4768
3049
  const first = annotationList.annotations[0];
@@ -4775,73 +3056,92 @@ var MediaElementPlaylistProvider = ({
4775
3056
  }
4776
3057
  return annotationList.annotations;
4777
3058
  }, [annotationList == null ? void 0 : annotationList.annotations]);
4778
- const annotationsRef = (0, import_react25.useRef)(annotations);
3059
+ const annotationsRef = (0, import_react18.useRef)(annotations);
4779
3060
  annotationsRef.current = annotations;
4780
- const [activeAnnotationId, setActiveAnnotationIdState] = (0, import_react25.useState)(null);
4781
- const [continuousPlay, setContinuousPlayState] = (0, import_react25.useState)(
3061
+ const [activeAnnotationId, setActiveAnnotationIdState] = (0, import_react18.useState)(null);
3062
+ const [continuousPlay, setContinuousPlayState] = (0, import_react18.useState)(
4782
3063
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
4783
3064
  );
4784
- const [samplesPerPixel] = (0, import_react25.useState)(initialSamplesPerPixel);
4785
- const [isAutomaticScroll, setIsAutomaticScroll] = (0, import_react25.useState)(automaticScroll);
4786
- const playoutRef = (0, import_react25.useRef)(null);
4787
- const currentTimeRef = (0, import_react25.useRef)(0);
4788
- const continuousPlayRef = (0, import_react25.useRef)(continuousPlay);
4789
- const activeAnnotationIdRef = (0, import_react25.useRef)(null);
4790
- const scrollContainerRef = (0, import_react25.useRef)(null);
4791
- const isAutomaticScrollRef = (0, import_react25.useRef)(automaticScroll);
4792
- 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);
4793
3074
  const { startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
4794
- (0, import_react25.useEffect)(() => {
3075
+ (0, import_react18.useEffect)(() => {
4795
3076
  continuousPlayRef.current = continuousPlay;
4796
3077
  }, [continuousPlay]);
4797
- (0, import_react25.useEffect)(() => {
3078
+ (0, import_react18.useEffect)(() => {
4798
3079
  isAutomaticScrollRef.current = isAutomaticScroll;
4799
3080
  }, [isAutomaticScroll]);
4800
- const setActiveAnnotationId = (0, import_react25.useCallback)((value) => {
3081
+ const setActiveAnnotationId = (0, import_react18.useCallback)((value) => {
4801
3082
  activeAnnotationIdRef.current = value;
4802
3083
  setActiveAnnotationIdState(value);
4803
3084
  }, []);
4804
- const setContinuousPlay = (0, import_react25.useCallback)((value) => {
3085
+ const setContinuousPlay = (0, import_react18.useCallback)((value) => {
4805
3086
  continuousPlayRef.current = value;
4806
3087
  setContinuousPlayState(value);
4807
3088
  }, []);
4808
- const setScrollContainer = (0, import_react25.useCallback)((element) => {
3089
+ const setScrollContainer = (0, import_react18.useCallback)((element) => {
4809
3090
  scrollContainerRef.current = element;
4810
3091
  }, []);
4811
3092
  const sampleRate = track.waveformData.sample_rate;
4812
- (0, import_react25.useEffect)(() => {
4813
- var _a2, _b;
4814
- const playout = new import_media_element_playout.MediaElementPlayout({
4815
- playbackRate: initialPlaybackRate,
4816
- preservesPitch
4817
- });
4818
- playout.addTrack({
4819
- source: track.source,
4820
- peaks: track.waveformData,
4821
- name: track.name,
4822
- audioContext,
4823
- fadeIn: track.fadeIn,
4824
- fadeOut: track.fadeOut
4825
- });
4826
- const mediaTrack = playout.getTrack((_b = (_a2 = playout["track"]) == null ? void 0 : _a2.id) != null ? _b : "");
4827
- if (mediaTrack) {
4828
- mediaTrack.setOnTimeUpdateCallback((time) => {
4829
- currentTimeRef.current = time;
4830
- });
4831
- }
4832
- playout.setOnPlaybackComplete(() => {
4833
- stopAnimationFrameLoop();
4834
- setIsPlaying(false);
4835
- setActiveAnnotationId(null);
4836
- currentTimeRef.current = 0;
4837
- setCurrentTime(0);
4838
- });
4839
- playoutRef.current = playout;
4840
- setDuration(track.waveformData.duration);
4841
- 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
+ }))();
4842
3138
  return () => {
3139
+ cancelled = true;
4843
3140
  stopAnimationFrameLoop();
4844
- playout.dispose();
3141
+ if (createdPlayout) {
3142
+ createdPlayout.dispose();
3143
+ }
3144
+ playoutRef.current = null;
4845
3145
  };
4846
3146
  }, [
4847
3147
  track.source,
@@ -4853,10 +3153,12 @@ var MediaElementPlaylistProvider = ({
4853
3153
  initialPlaybackRate,
4854
3154
  preservesPitch,
4855
3155
  onReady,
3156
+ onError,
4856
3157
  stopAnimationFrameLoop,
4857
- setActiveAnnotationId
3158
+ setActiveAnnotationId,
3159
+ createPlayout
4858
3160
  ]);
4859
- (0, import_react25.useEffect)(() => {
3161
+ (0, import_react18.useEffect)(() => {
4860
3162
  var _a2;
4861
3163
  try {
4862
3164
  const extractedPeaks = extractPeaksFromWaveformData(
@@ -4885,7 +3187,7 @@ var MediaElementPlaylistProvider = ({
4885
3187
  console.warn("[waveform-playlist] Failed to extract peaks from waveform data:", err);
4886
3188
  }
4887
3189
  }, [track.waveformData, track.name, samplesPerPixel, sampleRate]);
4888
- const startAnimationLoop = (0, import_react25.useCallback)(() => {
3190
+ const startAnimationLoop = (0, import_react18.useCallback)(() => {
4889
3191
  const updateTime = () => {
4890
3192
  var _a2, _b, _c;
4891
3193
  const time = (_b = (_a2 = playoutRef.current) == null ? void 0 : _a2.getCurrentTime()) != null ? _b : 0;
@@ -4928,7 +3230,7 @@ var MediaElementPlaylistProvider = ({
4928
3230
  startAnimationFrameLoop(updateTime);
4929
3231
  }, [setActiveAnnotationId, sampleRate, startAnimationFrameLoop]);
4930
3232
  const stopAnimationLoop = stopAnimationFrameLoop;
4931
- const play = (0, import_react25.useCallback)(
3233
+ const play = (0, import_react18.useCallback)(
4932
3234
  (startTime) => {
4933
3235
  if (!playoutRef.current) return;
4934
3236
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4938,14 +3240,14 @@ var MediaElementPlaylistProvider = ({
4938
3240
  },
4939
3241
  [startAnimationLoop]
4940
3242
  );
4941
- const pause = (0, import_react25.useCallback)(() => {
3243
+ const pause = (0, import_react18.useCallback)(() => {
4942
3244
  if (!playoutRef.current) return;
4943
3245
  playoutRef.current.pause();
4944
3246
  setIsPlaying(false);
4945
3247
  stopAnimationLoop();
4946
3248
  setCurrentTime(playoutRef.current.getCurrentTime());
4947
3249
  }, [stopAnimationLoop]);
4948
- const stop = (0, import_react25.useCallback)(() => {
3250
+ const stop = (0, import_react18.useCallback)(() => {
4949
3251
  if (!playoutRef.current) return;
4950
3252
  playoutRef.current.stop();
4951
3253
  setIsPlaying(false);
@@ -4954,7 +3256,7 @@ var MediaElementPlaylistProvider = ({
4954
3256
  setCurrentTime(0);
4955
3257
  setActiveAnnotationId(null);
4956
3258
  }, [stopAnimationLoop, setActiveAnnotationId]);
4957
- const seekTo = (0, import_react25.useCallback)(
3259
+ const seekTo = (0, import_react18.useCallback)(
4958
3260
  (time) => {
4959
3261
  const clampedTime = Math.max(0, Math.min(time, duration));
4960
3262
  currentTimeRef.current = clampedTime;
@@ -4965,7 +3267,7 @@ var MediaElementPlaylistProvider = ({
4965
3267
  },
4966
3268
  [duration]
4967
3269
  );
4968
- const setPlaybackRate = (0, import_react25.useCallback)((rate) => {
3270
+ const setPlaybackRate = (0, import_react18.useCallback)((rate) => {
4969
3271
  const clampedRate = Math.max(0.5, Math.min(2, rate));
4970
3272
  setPlaybackRateState(clampedRate);
4971
3273
  if (playoutRef.current) {
@@ -4973,7 +3275,7 @@ var MediaElementPlaylistProvider = ({
4973
3275
  }
4974
3276
  }, []);
4975
3277
  const timeScaleHeight = timescale ? 30 : 0;
4976
- const animationValue = (0, import_react25.useMemo)(
3278
+ const animationValue = (0, import_react18.useMemo)(
4977
3279
  () => ({
4978
3280
  isPlaying,
4979
3281
  currentTime,
@@ -4981,7 +3283,7 @@ var MediaElementPlaylistProvider = ({
4981
3283
  }),
4982
3284
  [isPlaying, currentTime]
4983
3285
  );
4984
- const stateValue = (0, import_react25.useMemo)(
3286
+ const stateValue = (0, import_react18.useMemo)(
4985
3287
  () => ({
4986
3288
  continuousPlay,
4987
3289
  annotations,
@@ -4991,9 +3293,9 @@ var MediaElementPlaylistProvider = ({
4991
3293
  }),
4992
3294
  [continuousPlay, annotations, activeAnnotationId, playbackRate, isAutomaticScroll]
4993
3295
  );
4994
- const onAnnotationsChangeRef = (0, import_react25.useRef)(onAnnotationsChange);
3296
+ const onAnnotationsChangeRef = (0, import_react18.useRef)(onAnnotationsChange);
4995
3297
  onAnnotationsChangeRef.current = onAnnotationsChange;
4996
- const setAnnotations = (0, import_react25.useCallback)(
3298
+ const setAnnotations = (0, import_react18.useCallback)(
4997
3299
  (action) => {
4998
3300
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4999
3301
  if (!onAnnotationsChangeRef.current) {
@@ -5008,7 +3310,7 @@ var MediaElementPlaylistProvider = ({
5008
3310
  },
5009
3311
  []
5010
3312
  );
5011
- const controlsValue = (0, import_react25.useMemo)(
3313
+ const controlsValue = (0, import_react18.useMemo)(
5012
3314
  () => ({
5013
3315
  play,
5014
3316
  pause,
@@ -5036,7 +3338,7 @@ var MediaElementPlaylistProvider = ({
5036
3338
  setScrollContainer
5037
3339
  ]
5038
3340
  );
5039
- const dataValue = (0, import_react25.useMemo)(
3341
+ const dataValue = (0, import_react18.useMemo)(
5040
3342
  () => ({
5041
3343
  duration,
5042
3344
  peaksDataArray,
@@ -5071,28 +3373,28 @@ var MediaElementPlaylistProvider = ({
5071
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 }) }) }) }) });
5072
3374
  };
5073
3375
  var useMediaElementAnimation = () => {
5074
- const context = (0, import_react25.useContext)(MediaElementAnimationContext);
3376
+ const context = (0, import_react18.useContext)(MediaElementAnimationContext);
5075
3377
  if (!context) {
5076
3378
  throw new Error("useMediaElementAnimation must be used within MediaElementPlaylistProvider");
5077
3379
  }
5078
3380
  return context;
5079
3381
  };
5080
3382
  var useMediaElementState = () => {
5081
- const context = (0, import_react25.useContext)(MediaElementStateContext);
3383
+ const context = (0, import_react18.useContext)(MediaElementStateContext);
5082
3384
  if (!context) {
5083
3385
  throw new Error("useMediaElementState must be used within MediaElementPlaylistProvider");
5084
3386
  }
5085
3387
  return context;
5086
3388
  };
5087
3389
  var useMediaElementControls = () => {
5088
- const context = (0, import_react25.useContext)(MediaElementControlsContext);
3390
+ const context = (0, import_react18.useContext)(MediaElementControlsContext);
5089
3391
  if (!context) {
5090
3392
  throw new Error("useMediaElementControls must be used within MediaElementPlaylistProvider");
5091
3393
  }
5092
3394
  return context;
5093
3395
  };
5094
3396
  var useMediaElementData = () => {
5095
- const context = (0, import_react25.useContext)(MediaElementDataContext);
3397
+ const context = (0, import_react18.useContext)(MediaElementDataContext);
5096
3398
  if (!context) {
5097
3399
  throw new Error("useMediaElementData must be used within MediaElementPlaylistProvider");
5098
3400
  }
@@ -5100,7 +3402,7 @@ var useMediaElementData = () => {
5100
3402
  };
5101
3403
 
5102
3404
  // src/components/PlaybackControls.tsx
5103
- var import_react26 = require("react");
3405
+ var import_react19 = require("react");
5104
3406
  var import_ui_components4 = require("@waveform-playlist/ui-components");
5105
3407
  var import_jsx_runtime3 = require("react/jsx-runtime");
5106
3408
  var PlayButton = ({ className }) => {
@@ -5236,7 +3538,7 @@ var ClearAllButton = ({
5236
3538
  className
5237
3539
  }) => {
5238
3540
  const { stop } = usePlaylistControls();
5239
- const handleClick = (0, import_react26.useCallback)(() => {
3541
+ const handleClick = (0, import_react19.useCallback)(() => {
5240
3542
  stop();
5241
3543
  onClearAll();
5242
3544
  }, [stop, onClearAll]);
@@ -5264,7 +3566,7 @@ var ZoomOutButton = ({
5264
3566
  };
5265
3567
 
5266
3568
  // src/components/ContextualControls.tsx
5267
- var import_react27 = require("react");
3569
+ var import_react20 = require("react");
5268
3570
  var import_ui_components6 = require("@waveform-playlist/ui-components");
5269
3571
  var import_styled_components3 = __toESM(require("styled-components"));
5270
3572
  var import_jsx_runtime5 = require("react/jsx-runtime");
@@ -5297,10 +3599,10 @@ var PositionDisplay = import_styled_components3.default.span`
5297
3599
  `;
5298
3600
  var AudioPosition = ({ className }) => {
5299
3601
  var _a;
5300
- const timeRef = (0, import_react27.useRef)(null);
3602
+ const timeRef = (0, import_react20.useRef)(null);
5301
3603
  const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
5302
3604
  const { timeFormat: format } = usePlaylistData();
5303
- (0, import_react27.useEffect)(() => {
3605
+ (0, import_react20.useEffect)(() => {
5304
3606
  const id = "audio-position";
5305
3607
  if (isPlaying) {
5306
3608
  registerFrameCallback(id, ({ time }) => {
@@ -5311,7 +3613,7 @@ var AudioPosition = ({ className }) => {
5311
3613
  }
5312
3614
  return () => unregisterFrameCallback(id);
5313
3615
  }, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);
5314
- (0, import_react27.useEffect)(() => {
3616
+ (0, import_react20.useEffect)(() => {
5315
3617
  var _a2;
5316
3618
  if (!isPlaying && timeRef.current) {
5317
3619
  timeRef.current.textContent = (0, import_ui_components6.formatTime)((_a2 = currentTimeRef.current) != null ? _a2 : 0, format);
@@ -5346,11 +3648,11 @@ var AutomaticScrollCheckbox = ({ className }) => {
5346
3648
  };
5347
3649
 
5348
3650
  // src/AnnotationIntegrationContext.tsx
5349
- var import_react28 = require("react");
5350
- var AnnotationIntegrationContext = (0, import_react28.createContext)(null);
3651
+ var import_react21 = require("react");
3652
+ var AnnotationIntegrationContext = (0, import_react21.createContext)(null);
5351
3653
  var AnnotationIntegrationProvider = AnnotationIntegrationContext.Provider;
5352
3654
  function useAnnotationIntegration() {
5353
- const context = (0, import_react28.useContext)(AnnotationIntegrationContext);
3655
+ const context = (0, import_react21.useContext)(AnnotationIntegrationContext);
5354
3656
  if (!context) {
5355
3657
  throw new Error(
5356
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"
@@ -5388,73 +3690,25 @@ var DownloadAnnotationsButton = ({
5388
3690
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Base, { annotations, filename, className });
5389
3691
  };
5390
3692
 
5391
- // src/components/ExportControls.tsx
5392
- var import_ui_components7 = require("@waveform-playlist/ui-components");
5393
- var import_jsx_runtime7 = require("react/jsx-runtime");
5394
- var ExportWavButton = ({
5395
- label = "Export WAV",
5396
- filename = "export",
5397
- mode = "master",
5398
- trackIndex,
5399
- bitDepth = 16,
5400
- applyEffects = true,
5401
- effectsFunction,
5402
- createOfflineTrackEffects,
5403
- className,
5404
- onExportComplete,
5405
- onExportError
5406
- }) => {
5407
- const { tracks, trackStates } = usePlaylistData();
5408
- const { exportWav, isExporting, progress } = useExportWav();
5409
- const handleExport = () => __async(null, null, function* () {
5410
- try {
5411
- const result = yield exportWav(tracks, trackStates, {
5412
- filename,
5413
- mode,
5414
- trackIndex,
5415
- bitDepth,
5416
- applyEffects,
5417
- effectsFunction,
5418
- createOfflineTrackEffects,
5419
- autoDownload: true
5420
- });
5421
- onExportComplete == null ? void 0 : onExportComplete(result.blob);
5422
- } catch (error) {
5423
- onExportError == null ? void 0 : onExportError(error instanceof Error ? error : new Error("Export failed"));
5424
- }
5425
- });
5426
- const buttonLabel = isExporting ? `Exporting ${Math.round(progress * 100)}%` : label;
5427
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5428
- import_ui_components7.BaseControlButton,
5429
- {
5430
- onClick: handleExport,
5431
- disabled: isExporting || tracks.length === 0,
5432
- className,
5433
- children: buttonLabel
5434
- }
5435
- );
5436
- };
5437
-
5438
3693
  // src/contexts/ClipInteractionContext.tsx
5439
- var import_react29 = require("react");
5440
- var ClipInteractionContext = (0, import_react29.createContext)(false);
3694
+ var import_react22 = require("react");
3695
+ var ClipInteractionContext = (0, import_react22.createContext)(false);
5441
3696
  var ClipInteractionContextProvider = ClipInteractionContext.Provider;
5442
3697
  function useClipInteractionEnabled() {
5443
- return (0, import_react29.useContext)(ClipInteractionContext);
3698
+ return (0, import_react22.useContext)(ClipInteractionContext);
5444
3699
  }
5445
3700
 
5446
3701
  // src/components/PlaylistVisualization.tsx
5447
- var import_react33 = require("react");
3702
+ var import_react26 = require("react");
5448
3703
  var import_react_dom = require("react-dom");
5449
3704
  var import_styled_components6 = __toESM(require("styled-components"));
5450
- var import_playout7 = require("@waveform-playlist/playout");
5451
- var import_ui_components9 = require("@waveform-playlist/ui-components");
5452
- 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");
5453
3707
 
5454
3708
  // src/components/AnimatedPlayhead.tsx
5455
- var import_react30 = require("react");
3709
+ var import_react23 = require("react");
5456
3710
  var import_styled_components4 = __toESM(require("styled-components"));
5457
- var import_jsx_runtime8 = require("react/jsx-runtime");
3711
+ var import_jsx_runtime7 = require("react/jsx-runtime");
5458
3712
  var PlayheadLine = import_styled_components4.default.div.attrs((props) => ({
5459
3713
  style: {
5460
3714
  width: `${props.$width}px`,
@@ -5470,7 +3724,7 @@ var PlayheadLine = import_styled_components4.default.div.attrs((props) => ({
5470
3724
  will-change: transform;
5471
3725
  `;
5472
3726
  var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5473
- const playheadRef = (0, import_react30.useRef)(null);
3727
+ const playheadRef = (0, import_react23.useRef)(null);
5474
3728
  const {
5475
3729
  isPlaying,
5476
3730
  currentTimeRef,
@@ -5479,7 +3733,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5479
3733
  unregisterFrameCallback
5480
3734
  } = usePlaybackAnimation();
5481
3735
  const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();
5482
- (0, import_react30.useEffect)(() => {
3736
+ (0, import_react23.useEffect)(() => {
5483
3737
  const id = "playhead";
5484
3738
  if (isPlaying) {
5485
3739
  registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {
@@ -5491,7 +3745,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5491
3745
  }
5492
3746
  return () => unregisterFrameCallback(id);
5493
3747
  }, [isPlaying, registerFrameCallback, unregisterFrameCallback]);
5494
- (0, import_react30.useEffect)(() => {
3748
+ (0, import_react23.useEffect)(() => {
5495
3749
  var _a, _b;
5496
3750
  if (!isPlaying && playheadRef.current) {
5497
3751
  const time = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
@@ -5499,15 +3753,15 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5499
3753
  playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
5500
3754
  }
5501
3755
  });
5502
- 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 });
5503
3757
  };
5504
3758
 
5505
3759
  // src/components/ChannelWithProgress.tsx
5506
- var import_react31 = require("react");
3760
+ var import_react24 = require("react");
5507
3761
  var import_styled_components5 = __toESM(require("styled-components"));
5508
- var import_core7 = require("@waveform-playlist/core");
5509
- var import_ui_components8 = require("@waveform-playlist/ui-components");
5510
- 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");
5511
3765
  var ChannelWrapper = import_styled_components5.default.div`
5512
3766
  position: relative;
5513
3767
  `;
@@ -5561,10 +3815,10 @@ var ChannelWithProgress = (_a) => {
5561
3815
  "clipSampleRate",
5562
3816
  "clipOffsetSeconds"
5563
3817
  ]);
5564
- const progressRef = (0, import_react31.useRef)(null);
5565
- const callbackId = (0, import_react31.useId)();
5566
- const theme = (0, import_ui_components8.useTheme)();
5567
- 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)();
5568
3822
  const {
5569
3823
  isPlaying,
5570
3824
  currentTimeRef,
@@ -5574,12 +3828,12 @@ var ChannelWithProgress = (_a) => {
5574
3828
  } = usePlaybackAnimation();
5575
3829
  const { samplesPerPixel, sampleRate } = usePlaylistData();
5576
3830
  const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
5577
- const clipPixelWidth = (0, import_core7.clipPixelWidth)(
3831
+ const clipPixelWidth = (0, import_core3.clipPixelWidth)(
5578
3832
  clipStartSample,
5579
3833
  clipDurationSamples,
5580
3834
  samplesPerPixel
5581
3835
  );
5582
- (0, import_react31.useEffect)(() => {
3836
+ (0, import_react24.useEffect)(() => {
5583
3837
  if (isPlaying) {
5584
3838
  registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {
5585
3839
  if (progressRef.current) {
@@ -5607,7 +3861,7 @@ var ChannelWithProgress = (_a) => {
5607
3861
  registerFrameCallback,
5608
3862
  unregisterFrameCallback
5609
3863
  ]);
5610
- (0, import_react31.useEffect)(() => {
3864
+ (0, import_react24.useEffect)(() => {
5611
3865
  var _a2, _b2;
5612
3866
  if (!isPlaying && progressRef.current) {
5613
3867
  const currentTime = (_b2 = (_a2 = visualTimeRef.current) != null ? _a2 : currentTimeRef.current) != null ? _b2 : 0;
@@ -5635,14 +3889,14 @@ var ChannelWithProgress = (_a) => {
5635
3889
  const isSpectrogramMode = smartChannelProps.renderMode === "spectrogram" || smartChannelProps.renderMode === "both";
5636
3890
  const isPianoRollMode = smartChannelProps.renderMode === "piano-roll";
5637
3891
  const isBothMode = smartChannelProps.renderMode === "both";
5638
- 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);
5639
3893
  const halfHeight = Math.floor(waveHeight / 2);
5640
3894
  const effectiveHeight = waveHeight;
5641
3895
  const effectiveTop = isBothMode ? smartChannelProps.index * waveHeight : smartChannelProps.index * waveHeight;
5642
- const waveformBackgroundCss = (0, import_ui_components8.waveformColorToCss)(backgroundColor);
5643
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(ChannelWrapper, { children: [
5644
- isBothMode ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
5645
- /* @__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)(
5646
3900
  Background,
5647
3901
  {
5648
3902
  $color: "#000",
@@ -5651,7 +3905,7 @@ var ChannelWithProgress = (_a) => {
5651
3905
  $width: smartChannelProps.length
5652
3906
  }
5653
3907
  ),
5654
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3908
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5655
3909
  Background,
5656
3910
  {
5657
3911
  $color: waveformBackgroundCss,
@@ -5660,7 +3914,7 @@ var ChannelWithProgress = (_a) => {
5660
3914
  $width: smartChannelProps.length
5661
3915
  }
5662
3916
  )
5663
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3917
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5664
3918
  Background,
5665
3919
  {
5666
3920
  $color: backgroundCss,
@@ -5669,7 +3923,7 @@ var ChannelWithProgress = (_a) => {
5669
3923
  $width: smartChannelProps.length
5670
3924
  }
5671
3925
  ),
5672
- !isPianoRollMode && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3926
+ !isPianoRollMode && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5673
3927
  ProgressOverlay,
5674
3928
  {
5675
3929
  ref: progressRef,
@@ -5679,8 +3933,8 @@ var ChannelWithProgress = (_a) => {
5679
3933
  $width: clipPixelWidth
5680
3934
  }
5681
3935
  ),
5682
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChannelContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5683
- import_ui_components8.SmartChannel,
3936
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ChannelContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3937
+ import_ui_components7.SmartChannel,
5684
3938
  __spreadProps(__spreadValues({}, smartChannelProps), {
5685
3939
  transparentBackground: true,
5686
3940
  midiNotes,
@@ -5692,11 +3946,11 @@ var ChannelWithProgress = (_a) => {
5692
3946
  };
5693
3947
 
5694
3948
  // src/SpectrogramIntegrationContext.tsx
5695
- var import_react32 = require("react");
5696
- var SpectrogramIntegrationContext = (0, import_react32.createContext)(null);
3949
+ var import_react25 = require("react");
3950
+ var SpectrogramIntegrationContext = (0, import_react25.createContext)(null);
5697
3951
  var SpectrogramIntegrationProvider = SpectrogramIntegrationContext.Provider;
5698
3952
  function useSpectrogramIntegration() {
5699
- const context = (0, import_react32.useContext)(SpectrogramIntegrationContext);
3953
+ const context = (0, import_react25.useContext)(SpectrogramIntegrationContext);
5700
3954
  if (!context) {
5701
3955
  throw new Error(
5702
3956
  "useSpectrogramIntegration must be used within <SpectrogramProvider>. Install @waveform-playlist/spectrogram and wrap your app with <SpectrogramProvider>."
@@ -5706,7 +3960,7 @@ function useSpectrogramIntegration() {
5706
3960
  }
5707
3961
 
5708
3962
  // src/components/PlaylistVisualization.tsx
5709
- var import_jsx_runtime10 = require("react/jsx-runtime");
3963
+ var import_jsx_runtime9 = require("react/jsx-runtime");
5710
3964
  var DEFAULT_EMPTY_TRACK_DURATION = 60;
5711
3965
  var ControlSlot = import_styled_components6.default.div.attrs((props) => ({
5712
3966
  style: { height: `${props.$height}px` }
@@ -5725,7 +3979,8 @@ var CustomPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) =>
5725
3979
  visualTimeRef,
5726
3980
  playbackStartTimeRef,
5727
3981
  audioStartPositionRef,
5728
- getPlaybackTime
3982
+ getPlaybackTime,
3983
+ getAudioContextTime
5729
3984
  } = usePlaybackAnimation();
5730
3985
  const visualTime = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
5731
3986
  return renderPlayhead({
@@ -5739,7 +3994,7 @@ var CustomPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) =>
5739
3994
  samplesPerPixel,
5740
3995
  sampleRate,
5741
3996
  controlsOffset: 0,
5742
- getAudioContextTime: () => (0, import_playout7.getGlobalAudioContext)().currentTime,
3997
+ getAudioContextTime,
5743
3998
  getPlaybackTime
5744
3999
  });
5745
4000
  };
@@ -5763,8 +4018,8 @@ var PlaylistVisualization = ({
5763
4018
  recordingState
5764
4019
  }) => {
5765
4020
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
5766
- const theme = (0, import_ui_components9.useTheme)();
5767
- const { isPlaying, getLookAhead } = usePlaybackAnimation();
4021
+ const theme = (0, import_ui_components8.useTheme)();
4022
+ const { isPlaying, getLookAhead, getOutputLatency } = usePlaybackAnimation();
5768
4023
  const {
5769
4024
  selectionStart,
5770
4025
  selectionEnd,
@@ -5779,7 +4034,7 @@ var PlaylistVisualization = ({
5779
4034
  isLoopEnabled,
5780
4035
  indefinitePlayback
5781
4036
  } = usePlaylistState();
5782
- const annotationIntegration = (0, import_react33.useContext)(AnnotationIntegrationContext);
4037
+ const annotationIntegration = (0, import_react26.useContext)(AnnotationIntegrationContext);
5783
4038
  const {
5784
4039
  setAnnotations: _setAnnotations,
5785
4040
  setActiveAnnotationId,
@@ -5809,8 +4064,8 @@ var PlaylistVisualization = ({
5809
4064
  isReady,
5810
4065
  mono
5811
4066
  } = usePlaylistData();
5812
- const spectrogram = (0, import_react33.useContext)(SpectrogramIntegrationContext);
5813
- const perTrackSpectrogramHelpers = (0, import_react33.useMemo)(() => {
4067
+ const spectrogram = (0, import_react26.useContext)(SpectrogramIntegrationContext);
4068
+ const perTrackSpectrogramHelpers = (0, import_react26.useMemo)(() => {
5814
4069
  if (!spectrogram)
5815
4070
  return /* @__PURE__ */ new Map();
5816
4071
  const helpers = /* @__PURE__ */ new Map();
@@ -5829,11 +4084,11 @@ var PlaylistVisualization = ({
5829
4084
  });
5830
4085
  return helpers;
5831
4086
  }, [tracks, spectrogram]);
5832
- const [settingsModalTrackId, setSettingsModalTrackId] = (0, import_react33.useState)(null);
5833
- const [isSelecting, setIsSelecting] = (0, import_react33.useState)(false);
5834
- const mouseDownTimeRef = (0, import_react33.useRef)(0);
5835
- const scrollContainerRef = (0, import_react33.useRef)(null);
5836
- 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)(
5837
4092
  (element) => {
5838
4093
  scrollContainerRef.current = element;
5839
4094
  setScrollContainer(element);
@@ -5871,7 +4126,7 @@ var PlaylistVisualization = ({
5871
4126
  );
5872
4127
  }
5873
4128
  });
5874
- const selectTrack = (0, import_react33.useCallback)(
4129
+ const selectTrack = (0, import_react26.useCallback)(
5875
4130
  (trackIndex) => {
5876
4131
  if (trackIndex >= 0 && trackIndex < tracks.length) {
5877
4132
  const track = tracks[trackIndex];
@@ -5941,7 +4196,7 @@ var PlaylistVisualization = ({
5941
4196
  };
5942
4197
  const hasClips = tracks.some((track) => track.clips.length > 0);
5943
4198
  if (hasClips && peaksDataArray.length === 0) {
5944
- 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..." });
5945
4200
  }
5946
4201
  const trackControlsSlots = controls.show ? peaksDataArray.map((trackClipPeaks, trackIndex) => {
5947
4202
  var _a2, _b2, _c2;
@@ -5957,11 +4212,11 @@ var PlaylistVisualization = ({
5957
4212
  const hasMidiNotes = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);
5958
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";
5959
4214
  const maxChannels = getTrackChannelCount(trackClipPeaks, recordingState, track.id, mono);
5960
- const slotHeight = waveHeight * maxChannels + (showClipHeaders ? import_ui_components9.CLIP_HEADER_HEIGHT : 0);
5961
- const trackControlContent = renderTrackControls ? renderTrackControls(trackIndex) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.Controls, { onClick: () => selectTrack(trackIndex), children: [
5962
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.Header, { style: { justifyContent: "center", position: "relative" }, children: [
5963
- onRemoveTrack && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5964
- 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,
5965
4220
  {
5966
4221
  onClick: (e) => {
5967
4222
  e.stopPropagation();
@@ -5969,7 +4224,7 @@ var PlaylistVisualization = ({
5969
4224
  }
5970
4225
  }
5971
4226
  ),
5972
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4227
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5973
4228
  "span",
5974
4229
  {
5975
4230
  style: {
@@ -5982,8 +4237,8 @@ var PlaylistVisualization = ({
5982
4237
  children: trackState.name || `Track ${trackIndex + 1}`
5983
4238
  }
5984
4239
  ),
5985
- (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)(
5986
- 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,
5987
4242
  {
5988
4243
  items: (onClose) => spectrogram.renderMenuItems({
5989
4244
  renderMode: effectiveRenderMode,
@@ -5994,17 +4249,17 @@ var PlaylistVisualization = ({
5994
4249
  }
5995
4250
  ) })
5996
4251
  ] }),
5997
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.ButtonGroup, { children: [
5998
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5999
- 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,
6000
4255
  {
6001
4256
  $variant: trackState.muted ? "danger" : "outline",
6002
4257
  onClick: () => setTrackMute(trackIndex, !trackState.muted),
6003
4258
  children: "Mute"
6004
4259
  }
6005
4260
  ),
6006
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6007
- import_ui_components9.Button,
4261
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4262
+ import_ui_components8.Button,
6008
4263
  {
6009
4264
  $variant: trackState.soloed ? "info" : "outline",
6010
4265
  onClick: () => setTrackSolo(trackIndex, !trackState.soloed),
@@ -6012,10 +4267,10 @@ var PlaylistVisualization = ({
6012
4267
  }
6013
4268
  )
6014
4269
  ] }),
6015
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.SliderWrapper, { children: [
6016
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ui_components9.VolumeDownIcon, {}),
6017
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6018
- 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,
6019
4274
  {
6020
4275
  min: "0",
6021
4276
  max: "1",
@@ -6024,12 +4279,12 @@ var PlaylistVisualization = ({
6024
4279
  onChange: (e) => setTrackVolume(trackIndex, parseFloat(e.target.value))
6025
4280
  }
6026
4281
  ),
6027
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ui_components9.VolumeUpIcon, {})
4282
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ui_components8.VolumeUpIcon, {})
6028
4283
  ] }),
6029
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.SliderWrapper, { children: [
6030
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "L" }),
6031
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6032
- 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,
6033
4288
  {
6034
4289
  min: "-1",
6035
4290
  max: "1",
@@ -6038,10 +4293,10 @@ var PlaylistVisualization = ({
6038
4293
  onChange: (e) => setTrackPan(trackIndex, parseFloat(e.target.value))
6039
4294
  }
6040
4295
  ),
6041
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "R" })
4296
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "R" })
6042
4297
  ] })
6043
4298
  ] });
6044
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4299
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6045
4300
  ControlSlot,
6046
4301
  {
6047
4302
  $height: slotHeight,
@@ -6051,9 +4306,9 @@ var PlaylistVisualization = ({
6051
4306
  track.id
6052
4307
  );
6053
4308
  }) : void 0;
6054
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ui_components9.DevicePixelRatioProvider, { children: [
6055
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6056
- 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,
6057
4312
  {
6058
4313
  value: {
6059
4314
  samplesPerPixel,
@@ -6066,11 +4321,11 @@ var PlaylistVisualization = ({
6066
4321
  barWidth,
6067
4322
  barGap
6068
4323
  },
6069
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6070
- import_ui_components9.Playlist,
4324
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4325
+ import_ui_components8.Playlist,
6071
4326
  {
6072
4327
  theme,
6073
- backgroundColor: theme.playlistBackgroundColor || (0, import_ui_components9.waveformColorToCss)(theme.waveOutlineColor),
4328
+ backgroundColor: theme.playlistBackgroundColor || (0, import_ui_components8.waveformColorToCss)(theme.waveOutlineColor),
6074
4329
  timescaleBackgroundColor: theme.timescaleBackgroundColor,
6075
4330
  timescaleWidth: tracksFullWidth,
6076
4331
  tracksWidth: tracksFullWidth,
@@ -6083,10 +4338,10 @@ var PlaylistVisualization = ({
6083
4338
  "data-playlist-state": isReady ? "ready" : "loading",
6084
4339
  trackControlsSlots,
6085
4340
  timescaleGapHeight: timeScaleHeight > 0 ? timeScaleHeight + 1 : 0,
6086
- timescale: timeScaleHeight > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
6087
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ui_components9.SmartScale, { renderTick }),
6088
- isLoopEnabled && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6089
- 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,
6090
4345
  {
6091
4346
  startPosition: Math.min(loopStart, loopEnd) * sampleRate / samplesPerPixel,
6092
4347
  endPosition: Math.max(loopStart, loopEnd) * sampleRate / samplesPerPixel,
@@ -6102,7 +4357,7 @@ var PlaylistVisualization = ({
6102
4357
  }
6103
4358
  )
6104
4359
  ] }) : void 0,
6105
- 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: [
6106
4361
  peaksDataArray.map((trackClipPeaks, trackIndex) => {
6107
4362
  var _a2, _b2, _c2, _d2;
6108
4363
  const track = tracks[trackIndex];
@@ -6115,11 +4370,11 @@ var PlaylistVisualization = ({
6115
4370
  track.id,
6116
4371
  mono
6117
4372
  );
6118
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
6119
- import_ui_components9.Track,
4373
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4374
+ import_ui_components8.Track,
6120
4375
  {
6121
4376
  numChannels: maxChannels,
6122
- 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),
6123
4378
  offset: 0,
6124
4379
  width: tracksFullWidth,
6125
4380
  hasClipHeaders: showClipHeaders,
@@ -6131,8 +4386,8 @@ var PlaylistVisualization = ({
6131
4386
  const helpers = perTrackSpectrogramHelpers.get(track.id);
6132
4387
  const trackCfg = helpers == null ? void 0 : helpers.config;
6133
4388
  if (!(trackCfg == null ? void 0 : trackCfg.labels) || !helpers) return null;
6134
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6135
- import_ui_components9.SpectrogramLabels,
4389
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4390
+ import_ui_components8.SpectrogramLabels,
6136
4391
  {
6137
4392
  waveHeight,
6138
4393
  numChannels: maxChannels,
@@ -6149,8 +4404,8 @@ var PlaylistVisualization = ({
6149
4404
  trackClipPeaks.map((clip, clipIndex) => {
6150
4405
  const peaksData = clip.peaks;
6151
4406
  const width = peaksData.length;
6152
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6153
- import_ui_components9.Clip,
4407
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4408
+ import_ui_components8.Clip,
6154
4409
  {
6155
4410
  clipId: clip.clipId,
6156
4411
  trackIndex,
@@ -6179,7 +4434,7 @@ var PlaylistVisualization = ({
6179
4434
  selectTrack(trackIndex);
6180
4435
  },
6181
4436
  children: peaksData.data.map((channelPeaks, channelIndex) => {
6182
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4437
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6183
4438
  ChannelWithProgress,
6184
4439
  {
6185
4440
  index: channelIndex,
@@ -6206,10 +4461,9 @@ var PlaylistVisualization = ({
6206
4461
  );
6207
4462
  }),
6208
4463
  (recordingState == null ? void 0 : recordingState.isRecording) && recordingState.trackId === track.id && ((_d2 = recordingState.peaks[0]) == null ? void 0 : _d2.length) > 0 && (() => {
6209
- const audioCtx = (0, import_playout7.getGlobalAudioContext)();
6210
- const outputLatency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
4464
+ const outputLatency = getOutputLatency();
6211
4465
  const lookAhead = getLookAhead();
6212
- const latencyOffsetSamples = (0, import_core8.audibleLatencySamples)(
4466
+ const latencyOffsetSamples = (0, import_core4.audibleLatencySamples)(
6213
4467
  outputLatency,
6214
4468
  lookAhead,
6215
4469
  sampleRate
@@ -6223,8 +4477,8 @@ var PlaylistVisualization = ({
6223
4477
  const previewChannels = (mono ? recordingState.peaks.slice(0, 1) : recordingState.peaks).map(
6224
4478
  (channelPeaks) => skipPeakElements > 0 && skipPeakElements < channelPeaks.length ? channelPeaks.subarray(skipPeakElements) : channelPeaks
6225
4479
  );
6226
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6227
- import_ui_components9.Clip,
4480
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4481
+ import_ui_components8.Clip,
6228
4482
  {
6229
4483
  clipId: "recording-preview",
6230
4484
  trackIndex,
@@ -6237,7 +4491,7 @@ var PlaylistVisualization = ({
6237
4491
  disableHeaderDrag: true,
6238
4492
  isSelected: track.id === selectedTrackId,
6239
4493
  trackId: track.id,
6240
- children: previewChannels.map((channelPeaks, chIdx) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4494
+ children: previewChannels.map((channelPeaks, chIdx) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6241
4495
  ChannelWithProgress,
6242
4496
  {
6243
4497
  index: chIdx,
@@ -6259,11 +4513,11 @@ var PlaylistVisualization = ({
6259
4513
  track.id
6260
4514
  );
6261
4515
  }),
6262
- 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) => {
6263
4517
  const startPosition = annotation.start * sampleRate / samplesPerPixel;
6264
4518
  const endPosition = annotation.end * sampleRate / samplesPerPixel;
6265
4519
  const label = getAnnotationBoxLabel ? getAnnotationBoxLabel(annotation, index) : annotation.id;
6266
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4520
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6267
4521
  annotationIntegration.AnnotationBox,
6268
4522
  {
6269
4523
  annotationId: annotation.id,
@@ -6279,15 +4533,15 @@ var PlaylistVisualization = ({
6279
4533
  annotation.id
6280
4534
  );
6281
4535
  }) }),
6282
- selectionStart !== selectionEnd && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6283
- import_ui_components9.Selection,
4536
+ selectionStart !== selectionEnd && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4537
+ import_ui_components8.Selection,
6284
4538
  {
6285
4539
  startPosition: Math.min(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
6286
4540
  endPosition: Math.max(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
6287
4541
  color: theme.selectionColor
6288
4542
  }
6289
4543
  ),
6290
- (isPlaying || selectionStart === selectionEnd) && (renderPlayhead ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4544
+ (isPlaying || selectionStart === selectionEnd) && (renderPlayhead ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6291
4545
  CustomPlayhead,
6292
4546
  {
6293
4547
  renderPlayhead,
@@ -6295,14 +4549,14 @@ var PlaylistVisualization = ({
6295
4549
  samplesPerPixel,
6296
4550
  sampleRate
6297
4551
  }
6298
- ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AnimatedPlayhead, { color: theme.playheadColor }))
4552
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AnimatedPlayhead, { color: theme.playheadColor }))
6299
4553
  ] })
6300
4554
  }
6301
4555
  )
6302
4556
  }
6303
4557
  ),
6304
4558
  (spectrogram == null ? void 0 : spectrogram.SettingsModal) && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
6305
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4559
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6306
4560
  spectrogram.SettingsModal,
6307
4561
  {
6308
4562
  open: settingsModalTrackId !== null,
@@ -6322,8 +4576,8 @@ var PlaylistVisualization = ({
6322
4576
  };
6323
4577
 
6324
4578
  // src/components/PlaylistAnnotationList.tsx
6325
- var import_react34 = require("react");
6326
- var import_jsx_runtime11 = require("react/jsx-runtime");
4579
+ var import_react27 = require("react");
4580
+ var import_jsx_runtime10 = require("react/jsx-runtime");
6327
4581
  var PlaylistAnnotationList = ({
6328
4582
  height,
6329
4583
  renderAnnotationItem,
@@ -6337,7 +4591,7 @@ var PlaylistAnnotationList = ({
6337
4591
  const integration = useAnnotationIntegration();
6338
4592
  const { setAnnotations } = usePlaylistControls();
6339
4593
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints, continuousPlay };
6340
- const handleAnnotationUpdate = (0, import_react34.useCallback)(
4594
+ const handleAnnotationUpdate = (0, import_react27.useCallback)(
6341
4595
  (updatedAnnotations) => {
6342
4596
  setAnnotations(updatedAnnotations);
6343
4597
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6345,7 +4599,7 @@ var PlaylistAnnotationList = ({
6345
4599
  [setAnnotations, onAnnotationUpdate]
6346
4600
  );
6347
4601
  const { AnnotationText } = integration;
6348
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4602
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6349
4603
  AnnotationText,
6350
4604
  {
6351
4605
  annotations,
@@ -6364,7 +4618,7 @@ var PlaylistAnnotationList = ({
6364
4618
  };
6365
4619
 
6366
4620
  // src/components/Waveform.tsx
6367
- var import_jsx_runtime12 = require("react/jsx-runtime");
4621
+ var import_jsx_runtime11 = require("react/jsx-runtime");
6368
4622
  var Waveform = ({
6369
4623
  renderTrackControls,
6370
4624
  renderTick,
@@ -6388,8 +4642,8 @@ var Waveform = ({
6388
4642
  const { annotations } = usePlaylistState();
6389
4643
  const clipInteractionEnabled = useClipInteractionEnabled();
6390
4644
  const effectiveInteractiveClips = interactiveClips || clipInteractionEnabled;
6391
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
6392
- /* @__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)(
6393
4647
  PlaylistVisualization,
6394
4648
  {
6395
4649
  renderTrackControls,
@@ -6406,7 +4660,7 @@ var Waveform = ({
6406
4660
  recordingState
6407
4661
  }
6408
4662
  ),
6409
- annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4663
+ annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
6410
4664
  PlaylistAnnotationList,
6411
4665
  {
6412
4666
  height: annotationTextHeight,
@@ -6421,10 +4675,10 @@ var Waveform = ({
6421
4675
  };
6422
4676
 
6423
4677
  // src/components/MediaElementPlaylist.tsx
6424
- var import_react37 = require("react");
6425
- var import_react38 = require("@dnd-kit/react");
4678
+ var import_react30 = require("react");
4679
+ var import_react31 = require("@dnd-kit/react");
6426
4680
  var import_modifiers = require("@dnd-kit/abstract/modifiers");
6427
- var import_ui_components11 = require("@waveform-playlist/ui-components");
4681
+ var import_ui_components10 = require("@waveform-playlist/ui-components");
6428
4682
 
6429
4683
  // src/plugins/noDropAnimationPlugins.ts
6430
4684
  var import_dom2 = require("@dnd-kit/dom");
@@ -6446,9 +4700,9 @@ var noDropAnimationPlugins = (defaults) => {
6446
4700
  };
6447
4701
 
6448
4702
  // src/components/AnimatedMediaElementPlayhead.tsx
6449
- var import_react35 = require("react");
4703
+ var import_react28 = require("react");
6450
4704
  var import_styled_components7 = __toESM(require("styled-components"));
6451
- var import_jsx_runtime13 = require("react/jsx-runtime");
4705
+ var import_jsx_runtime12 = require("react/jsx-runtime");
6452
4706
  var PlayheadLine2 = import_styled_components7.default.div`
6453
4707
  position: absolute;
6454
4708
  top: 0;
@@ -6463,11 +4717,11 @@ var PlayheadLine2 = import_styled_components7.default.div`
6463
4717
  var AnimatedMediaElementPlayhead = ({
6464
4718
  color = "#ff0000"
6465
4719
  }) => {
6466
- const playheadRef = (0, import_react35.useRef)(null);
6467
- const animationFrameRef = (0, import_react35.useRef)(null);
4720
+ const playheadRef = (0, import_react28.useRef)(null);
4721
+ const animationFrameRef = (0, import_react28.useRef)(null);
6468
4722
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
6469
4723
  const { samplesPerPixel, sampleRate, progressBarWidth } = useMediaElementData();
6470
- (0, import_react35.useEffect)(() => {
4724
+ (0, import_react28.useEffect)(() => {
6471
4725
  const updatePosition = () => {
6472
4726
  var _a;
6473
4727
  if (playheadRef.current) {
@@ -6491,7 +4745,7 @@ var AnimatedMediaElementPlayhead = ({
6491
4745
  }
6492
4746
  };
6493
4747
  }, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef]);
6494
- (0, import_react35.useEffect)(() => {
4748
+ (0, import_react28.useEffect)(() => {
6495
4749
  var _a;
6496
4750
  if (!isPlaying && playheadRef.current) {
6497
4751
  const time = (_a = currentTimeRef.current) != null ? _a : 0;
@@ -6499,14 +4753,14 @@ var AnimatedMediaElementPlayhead = ({
6499
4753
  playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
6500
4754
  }
6501
4755
  });
6502
- 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 });
6503
4757
  };
6504
4758
 
6505
4759
  // src/components/ChannelWithMediaElementProgress.tsx
6506
- var import_react36 = require("react");
4760
+ var import_react29 = require("react");
6507
4761
  var import_styled_components8 = __toESM(require("styled-components"));
6508
- var import_ui_components10 = require("@waveform-playlist/ui-components");
6509
- 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");
6510
4764
  var ChannelWrapper2 = import_styled_components8.default.div`
6511
4765
  position: relative;
6512
4766
  `;
@@ -6543,14 +4797,14 @@ var ChannelWithMediaElementProgress = (_a) => {
6543
4797
  "clipStartSample",
6544
4798
  "clipDurationSamples"
6545
4799
  ]);
6546
- const progressRef = (0, import_react36.useRef)(null);
6547
- const animationFrameRef = (0, import_react36.useRef)(null);
6548
- const theme = (0, import_ui_components10.useTheme)();
6549
- 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)();
6550
4804
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
6551
4805
  const { samplesPerPixel, sampleRate } = useMediaElementData();
6552
4806
  const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
6553
- (0, import_react36.useEffect)(() => {
4807
+ (0, import_react29.useEffect)(() => {
6554
4808
  const updateProgress = () => {
6555
4809
  var _a2;
6556
4810
  if (progressRef.current) {
@@ -6592,7 +4846,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6592
4846
  smartChannelProps.length,
6593
4847
  currentTimeRef
6594
4848
  ]);
6595
- (0, import_react36.useEffect)(() => {
4849
+ (0, import_react29.useEffect)(() => {
6596
4850
  var _a2;
6597
4851
  if (!isPlaying && progressRef.current) {
6598
4852
  const currentTime = (_a2 = currentTimeRef.current) != null ? _a2 : 0;
@@ -6617,9 +4871,9 @@ var ChannelWithMediaElementProgress = (_a) => {
6617
4871
  } else {
6618
4872
  backgroundColor = (theme == null ? void 0 : theme.selectedWaveOutlineColor) || (theme == null ? void 0 : theme.waveOutlineColor) || "grey";
6619
4873
  }
6620
- const backgroundCss = (0, import_ui_components10.waveformColorToCss)(backgroundColor);
6621
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(ChannelWrapper2, { children: [
6622
- /* @__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)(
6623
4877
  Background2,
6624
4878
  {
6625
4879
  $color: backgroundCss,
@@ -6628,7 +4882,7 @@ var ChannelWithMediaElementProgress = (_a) => {
6628
4882
  $width: smartChannelProps.length
6629
4883
  }
6630
4884
  ),
6631
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4885
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6632
4886
  ProgressOverlay2,
6633
4887
  {
6634
4888
  ref: progressRef,
@@ -6637,12 +4891,12 @@ var ChannelWithMediaElementProgress = (_a) => {
6637
4891
  $top: smartChannelProps.index * waveHeight
6638
4892
  }
6639
4893
  ),
6640
- /* @__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 })) })
6641
4895
  ] });
6642
4896
  };
6643
4897
 
6644
4898
  // src/components/MediaElementPlaylist.tsx
6645
- var import_jsx_runtime15 = require("react/jsx-runtime");
4899
+ var import_jsx_runtime14 = require("react/jsx-runtime");
6646
4900
  var ZERO_REF = { current: 0 };
6647
4901
  var CustomMediaElementPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {
6648
4902
  var _a;
@@ -6668,10 +4922,10 @@ var MediaElementPlaylist = ({
6668
4922
  showFades = false,
6669
4923
  className
6670
4924
  }) => {
6671
- const theme = (0, import_ui_components11.useTheme)();
4925
+ const theme = (0, import_ui_components10.useTheme)();
6672
4926
  const { isPlaying } = useMediaElementAnimation();
6673
4927
  const { annotations, activeAnnotationId } = useMediaElementState();
6674
- const annotationIntegration = (0, import_react37.useContext)(AnnotationIntegrationContext);
4928
+ const annotationIntegration = (0, import_react30.useContext)(AnnotationIntegrationContext);
6675
4929
  const { play, seekTo, setActiveAnnotationId, setAnnotations, setScrollContainer } = useMediaElementControls();
6676
4930
  const {
6677
4931
  duration,
@@ -6687,11 +4941,11 @@ var MediaElementPlaylist = ({
6687
4941
  fadeIn,
6688
4942
  fadeOut
6689
4943
  } = useMediaElementData();
6690
- const [selectionStart, setSelectionStart] = (0, import_react37.useState)(0);
6691
- const [selectionEnd, setSelectionEnd] = (0, import_react37.useState)(0);
6692
- const [isSelecting, setIsSelecting] = (0, import_react37.useState)(false);
6693
- const scrollContainerRef = (0, import_react37.useRef)(null);
6694
- 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)(
6695
4949
  (el) => {
6696
4950
  scrollContainerRef.current = el;
6697
4951
  setScrollContainer(el);
@@ -6699,7 +4953,7 @@ var MediaElementPlaylist = ({
6699
4953
  [setScrollContainer]
6700
4954
  );
6701
4955
  const tracksFullWidth = Math.floor(duration * sampleRate / samplesPerPixel);
6702
- const handleAnnotationClick = (0, import_react37.useCallback)(
4956
+ const handleAnnotationClick = (0, import_react30.useCallback)(
6703
4957
  (annotation) => __async(null, null, function* () {
6704
4958
  setActiveAnnotationId(annotation.id);
6705
4959
  try {
@@ -6714,7 +4968,7 @@ var MediaElementPlaylist = ({
6714
4968
  }),
6715
4969
  [setActiveAnnotationId, play]
6716
4970
  );
6717
- const handleAnnotationUpdate = (0, import_react37.useCallback)(
4971
+ const handleAnnotationUpdate = (0, import_react30.useCallback)(
6718
4972
  (updatedAnnotations) => {
6719
4973
  setAnnotations(updatedAnnotations);
6720
4974
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6728,8 +4982,8 @@ var MediaElementPlaylist = ({
6728
4982
  duration,
6729
4983
  linkEndpoints: linkEndpointsProp
6730
4984
  });
6731
- const mouseDownTimeRef = (0, import_react37.useRef)(0);
6732
- const handleMouseDown = (0, import_react37.useCallback)(
4985
+ const mouseDownTimeRef = (0, import_react30.useRef)(0);
4986
+ const handleMouseDown = (0, import_react30.useCallback)(
6733
4987
  (e) => {
6734
4988
  const rect = e.currentTarget.getBoundingClientRect();
6735
4989
  const x = e.clientX - rect.left;
@@ -6741,7 +4995,7 @@ var MediaElementPlaylist = ({
6741
4995
  },
6742
4996
  [samplesPerPixel, sampleRate]
6743
4997
  );
6744
- const handleMouseMove = (0, import_react37.useCallback)(
4998
+ const handleMouseMove = (0, import_react30.useCallback)(
6745
4999
  (e) => {
6746
5000
  if (!isSelecting) return;
6747
5001
  const rect = e.currentTarget.getBoundingClientRect();
@@ -6751,7 +5005,7 @@ var MediaElementPlaylist = ({
6751
5005
  },
6752
5006
  [isSelecting, samplesPerPixel, sampleRate]
6753
5007
  );
6754
- const handleMouseUp = (0, import_react37.useCallback)(
5008
+ const handleMouseUp = (0, import_react30.useCallback)(
6755
5009
  (e) => {
6756
5010
  if (!isSelecting) return;
6757
5011
  setIsSelecting(false);
@@ -6781,10 +5035,10 @@ var MediaElementPlaylist = ({
6781
5035
  [isSelecting, selectionStart, samplesPerPixel, sampleRate, seekTo, isPlaying, playoutRef, play]
6782
5036
  );
6783
5037
  if (peaksDataArray.length === 0) {
6784
- 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..." });
6785
5039
  }
6786
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ui_components11.DevicePixelRatioProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6787
- 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,
6788
5042
  {
6789
5043
  value: {
6790
5044
  samplesPerPixel,
@@ -6797,11 +5051,11 @@ var MediaElementPlaylist = ({
6797
5051
  barWidth,
6798
5052
  barGap
6799
5053
  },
6800
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6801
- import_ui_components11.Playlist,
5054
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5055
+ import_ui_components10.Playlist,
6802
5056
  {
6803
5057
  theme,
6804
- backgroundColor: (0, import_ui_components11.waveformColorToCss)(theme.waveOutlineColor),
5058
+ backgroundColor: (0, import_ui_components10.waveformColorToCss)(theme.waveOutlineColor),
6805
5059
  timescaleBackgroundColor: theme.timescaleBackgroundColor,
6806
5060
  timescaleWidth: tracksFullWidth,
6807
5061
  tracksWidth: tracksFullWidth,
@@ -6811,15 +5065,15 @@ var MediaElementPlaylist = ({
6811
5065
  onTracksMouseUp: handleMouseUp,
6812
5066
  scrollContainerRef: handleScrollContainerRef,
6813
5067
  isSelecting,
6814
- timescale: timeScaleHeight > 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ui_components11.SmartScale, {}) : void 0,
6815
- 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: [
6816
5070
  peaksDataArray.map((trackClipPeaks, trackIndex) => {
6817
5071
  const maxChannels = trackClipPeaks.length > 0 ? Math.max(...trackClipPeaks.map((clip) => clip.peaks.data.length)) : 1;
6818
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6819
- import_ui_components11.Track,
5072
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5073
+ import_ui_components10.Track,
6820
5074
  {
6821
5075
  numChannels: maxChannels,
6822
- backgroundColor: (0, import_ui_components11.waveformColorToCss)(theme.waveOutlineColor),
5076
+ backgroundColor: (0, import_ui_components10.waveformColorToCss)(theme.waveOutlineColor),
6823
5077
  offset: 0,
6824
5078
  width: tracksFullWidth,
6825
5079
  hasClipHeaders: false,
@@ -6828,8 +5082,8 @@ var MediaElementPlaylist = ({
6828
5082
  children: trackClipPeaks.map((clip, clipIndex) => {
6829
5083
  const peaksData = clip.peaks;
6830
5084
  const width = peaksData.length;
6831
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
6832
- import_ui_components11.Clip,
5085
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
5086
+ import_ui_components10.Clip,
6833
5087
  {
6834
5088
  clipId: clip.clipId,
6835
5089
  trackIndex,
@@ -6843,7 +5097,7 @@ var MediaElementPlaylist = ({
6843
5097
  isSelected: true,
6844
5098
  trackId: `media-element-track-${trackIndex}`,
6845
5099
  children: [
6846
- peaksData.data.map((channelPeaks, channelIndex) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5100
+ peaksData.data.map((channelPeaks, channelIndex) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
6847
5101
  ChannelWithMediaElementProgress,
6848
5102
  {
6849
5103
  index: channelIndex,
@@ -6855,8 +5109,8 @@ var MediaElementPlaylist = ({
6855
5109
  },
6856
5110
  `${trackIndex}-${clipIndex}-${channelIndex}`
6857
5111
  )),
6858
- showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6859
- import_ui_components11.FadeOverlay,
5112
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5113
+ import_ui_components10.FadeOverlay,
6860
5114
  {
6861
5115
  left: 0,
6862
5116
  width: Math.floor(fadeIn.duration * sampleRate / samplesPerPixel),
@@ -6864,8 +5118,8 @@ var MediaElementPlaylist = ({
6864
5118
  curveType: fadeIn.type
6865
5119
  }
6866
5120
  ),
6867
- showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6868
- import_ui_components11.FadeOverlay,
5121
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5122
+ import_ui_components10.FadeOverlay,
6869
5123
  {
6870
5124
  left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
6871
5125
  width: Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
@@ -6882,19 +5136,19 @@ var MediaElementPlaylist = ({
6882
5136
  trackIndex
6883
5137
  );
6884
5138
  }),
6885
- annotations.length > 0 && annotationIntegration && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6886
- import_react38.DragDropProvider,
5139
+ annotations.length > 0 && annotationIntegration && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5140
+ import_react31.DragDropProvider,
6887
5141
  {
6888
5142
  onDragStart,
6889
5143
  onDragMove,
6890
5144
  onDragEnd,
6891
5145
  modifiers: editable ? [import_modifiers.RestrictToHorizontalAxis] : [],
6892
5146
  plugins: noDropAnimationPlugins,
6893
- 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) => {
6894
5148
  const startPosition = annotation.start * sampleRate / samplesPerPixel;
6895
5149
  const endPosition = annotation.end * sampleRate / samplesPerPixel;
6896
5150
  const label = getAnnotationBoxLabel ? getAnnotationBoxLabel(annotation, index) : annotation.id;
6897
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5151
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
6898
5152
  annotationIntegration.AnnotationBox,
6899
5153
  {
6900
5154
  annotationId: annotation.id,
@@ -6912,15 +5166,15 @@ var MediaElementPlaylist = ({
6912
5166
  }) })
6913
5167
  }
6914
5168
  ),
6915
- selectionStart !== selectionEnd && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6916
- import_ui_components11.Selection,
5169
+ selectionStart !== selectionEnd && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5170
+ import_ui_components10.Selection,
6917
5171
  {
6918
5172
  startPosition: Math.min(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
6919
5173
  endPosition: Math.max(selectionStart, selectionEnd) * sampleRate / samplesPerPixel,
6920
5174
  color: theme.selectionColor
6921
5175
  }
6922
5176
  ),
6923
- renderPlayhead ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5177
+ renderPlayhead ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
6924
5178
  CustomMediaElementPlayhead,
6925
5179
  {
6926
5180
  renderPlayhead,
@@ -6928,7 +5182,7 @@ var MediaElementPlaylist = ({
6928
5182
  samplesPerPixel,
6929
5183
  sampleRate
6930
5184
  }
6931
- ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AnimatedMediaElementPlayhead, { color: theme.playheadColor })
5185
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AnimatedMediaElementPlayhead, { color: theme.playheadColor })
6932
5186
  ] })
6933
5187
  }
6934
5188
  )
@@ -6937,8 +5191,8 @@ var MediaElementPlaylist = ({
6937
5191
  };
6938
5192
 
6939
5193
  // src/components/MediaElementAnnotationList.tsx
6940
- var import_react39 = require("react");
6941
- var import_jsx_runtime16 = require("react/jsx-runtime");
5194
+ var import_react32 = require("react");
5195
+ var import_jsx_runtime15 = require("react/jsx-runtime");
6942
5196
  var MediaElementAnnotationList = ({
6943
5197
  height,
6944
5198
  renderAnnotationItem,
@@ -6953,7 +5207,7 @@ var MediaElementAnnotationList = ({
6953
5207
  const integration = useAnnotationIntegration();
6954
5208
  const { setAnnotations } = useMediaElementControls();
6955
5209
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints: false, continuousPlay };
6956
- const handleAnnotationUpdate = (0, import_react39.useCallback)(
5210
+ const handleAnnotationUpdate = (0, import_react32.useCallback)(
6957
5211
  (updatedAnnotations) => {
6958
5212
  setAnnotations(updatedAnnotations);
6959
5213
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6961,7 +5215,7 @@ var MediaElementAnnotationList = ({
6961
5215
  [setAnnotations, onAnnotationUpdate]
6962
5216
  );
6963
5217
  const { AnnotationText } = integration;
6964
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5218
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6965
5219
  AnnotationText,
6966
5220
  {
6967
5221
  annotations,
@@ -6980,7 +5234,7 @@ var MediaElementAnnotationList = ({
6980
5234
  };
6981
5235
 
6982
5236
  // src/components/MediaElementWaveform.tsx
6983
- var import_jsx_runtime17 = require("react/jsx-runtime");
5237
+ var import_jsx_runtime16 = require("react/jsx-runtime");
6984
5238
  var MediaElementWaveform = ({
6985
5239
  annotationTextHeight,
6986
5240
  getAnnotationBoxLabel,
@@ -6995,8 +5249,8 @@ var MediaElementWaveform = ({
6995
5249
  className
6996
5250
  }) => {
6997
5251
  const { annotations } = useMediaElementState();
6998
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
6999
- /* @__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)(
7000
5254
  MediaElementPlaylist,
7001
5255
  {
7002
5256
  getAnnotationBoxLabel,
@@ -7008,7 +5262,7 @@ var MediaElementWaveform = ({
7008
5262
  className
7009
5263
  }
7010
5264
  ),
7011
- annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5265
+ annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7012
5266
  MediaElementAnnotationList,
7013
5267
  {
7014
5268
  height: annotationTextHeight,
@@ -7083,11 +5337,11 @@ var KeyboardShortcuts = ({
7083
5337
  };
7084
5338
 
7085
5339
  // src/components/ClipInteractionProvider.tsx
7086
- var import_react40 = __toESM(require("react"));
7087
- var import_react41 = require("@dnd-kit/react");
5340
+ var import_react33 = __toESM(require("react"));
5341
+ var import_react34 = require("@dnd-kit/react");
7088
5342
  var import_modifiers2 = require("@dnd-kit/abstract/modifiers");
7089
- var import_core10 = require("@waveform-playlist/core");
7090
- 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");
7091
5345
 
7092
5346
  // src/modifiers/ClipCollisionModifier.ts
7093
5347
  var import_abstract = require("@dnd-kit/abstract");
@@ -7115,7 +5369,7 @@ var ClipCollisionModifier = _ClipCollisionModifier;
7115
5369
 
7116
5370
  // src/modifiers/SnapToGridModifier.ts
7117
5371
  var import_abstract2 = require("@dnd-kit/abstract");
7118
- var import_core9 = require("@waveform-playlist/core");
5372
+ var import_core5 = require("@waveform-playlist/core");
7119
5373
  var _SnapToGridModifier = class _SnapToGridModifier extends import_abstract2.Modifier {
7120
5374
  apply(operation) {
7121
5375
  const { transform, source } = operation;
@@ -7136,18 +5390,18 @@ var _SnapToGridModifier = class _SnapToGridModifier extends import_abstract2.Mod
7136
5390
  }
7137
5391
  const { snapTo, bpm, timeSignature, sampleRate } = this.options;
7138
5392
  if (snapTo === "off") return transform;
7139
- 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);
7140
5394
  if (startSample !== void 0) {
7141
5395
  const proposedSamples = startSample + transform.x * samplesPerPixel;
7142
- const proposedTicks = (0, import_core9.samplesToTicks)(proposedSamples, bpm, sampleRate);
7143
- const snappedTicks2 = (0, import_core9.snapToGrid)(proposedTicks, gridTicks);
7144
- 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);
7145
5399
  return { x: (snappedSamples2 - startSample) / samplesPerPixel, y: 0 };
7146
5400
  }
7147
5401
  const deltaSamples = transform.x * samplesPerPixel;
7148
- const deltaTicks = (0, import_core9.samplesToTicks)(deltaSamples, bpm, sampleRate);
7149
- const snappedTicks = (0, import_core9.snapToGrid)(deltaTicks, gridTicks);
7150
- 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);
7151
5405
  return { x: snappedSamples / samplesPerPixel, y: 0 };
7152
5406
  }
7153
5407
  };
@@ -7155,7 +5409,7 @@ _SnapToGridModifier.configure = (0, import_abstract2.configurator)(_SnapToGridMo
7155
5409
  var SnapToGridModifier = _SnapToGridModifier;
7156
5410
 
7157
5411
  // src/components/ClipInteractionProvider.tsx
7158
- var import_jsx_runtime18 = require("react/jsx-runtime");
5412
+ var import_jsx_runtime17 = require("react/jsx-runtime");
7159
5413
  var NOOP_TRACKS_CHANGE = () => {
7160
5414
  };
7161
5415
  var ClipInteractionProvider = ({
@@ -7165,28 +5419,28 @@ var ClipInteractionProvider = ({
7165
5419
  }) => {
7166
5420
  const { tracks, samplesPerPixel, sampleRate, playoutRef, isDraggingRef, onTracksChange } = usePlaylistData();
7167
5421
  const { setSelectedTrackId } = usePlaylistControls();
7168
- const beatsAndBars = (0, import_ui_components12.useBeatsAndBars)();
5422
+ const beatsAndBars = (0, import_ui_components11.useBeatsAndBars)();
7169
5423
  const useBeatsSnap = snap && beatsAndBars != null && beatsAndBars.scaleMode === "beats" && beatsAndBars.snapTo !== "off";
7170
5424
  const useTimescaleSnap = snap && !useBeatsSnap;
7171
- (0, import_react40.useEffect)(() => {
5425
+ (0, import_react33.useEffect)(() => {
7172
5426
  if (onTracksChange == null) {
7173
5427
  console.warn(
7174
5428
  "[waveform-playlist] ClipInteractionProvider: onTracksChange is not set on WaveformPlaylistProvider. Drag and trim edits will not be persisted."
7175
5429
  );
7176
5430
  }
7177
5431
  }, [onTracksChange]);
7178
- const snapSamplePosition = (0, import_react40.useMemo)(() => {
5432
+ const snapSamplePosition = (0, import_react33.useMemo)(() => {
7179
5433
  if (useBeatsSnap && beatsAndBars) {
7180
5434
  const { bpm, timeSignature, snapTo } = beatsAndBars;
7181
- 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);
7182
5436
  return (samplePos) => {
7183
- const ticks = (0, import_core10.samplesToTicks)(samplePos, bpm, sampleRate);
7184
- const snapped = (0, import_core10.snapToGrid)(ticks, gridTicks);
7185
- 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);
7186
5440
  };
7187
5441
  }
7188
5442
  if (useTimescaleSnap) {
7189
- 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);
7190
5444
  return (samplePos) => Math.round(samplePos / gridSamples) * gridSamples;
7191
5445
  }
7192
5446
  return void 0;
@@ -7204,7 +5458,7 @@ var ClipInteractionProvider = ({
7204
5458
  isDraggingRef,
7205
5459
  snapSamplePosition
7206
5460
  });
7207
- const onDragStart = import_react40.default.useCallback(
5461
+ const onDragStart = import_react33.default.useCallback(
7208
5462
  (event) => {
7209
5463
  var _a, _b, _c;
7210
5464
  const trackIndex = (_c = (_b = (_a = event.operation) == null ? void 0 : _a.source) == null ? void 0 : _b.data) == null ? void 0 : _c.trackIndex;
@@ -7215,7 +5469,7 @@ var ClipInteractionProvider = ({
7215
5469
  },
7216
5470
  [handleDragStart, tracks, setSelectedTrackId]
7217
5471
  );
7218
- const modifiers = (0, import_react40.useMemo)(() => {
5472
+ const modifiers = (0, import_react33.useMemo)(() => {
7219
5473
  const mods = [import_modifiers2.RestrictToHorizontalAxis];
7220
5474
  if (useBeatsSnap && beatsAndBars) {
7221
5475
  mods.push(
@@ -7232,7 +5486,7 @@ var ClipInteractionProvider = ({
7232
5486
  mods.push(
7233
5487
  SnapToGridModifier.configure({
7234
5488
  mode: "timescale",
7235
- 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),
7236
5490
  samplesPerPixel
7237
5491
  })
7238
5492
  );
@@ -7240,8 +5494,8 @@ var ClipInteractionProvider = ({
7240
5494
  mods.push(ClipCollisionModifier.configure({ tracks, samplesPerPixel }));
7241
5495
  return mods;
7242
5496
  }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, tracks, samplesPerPixel, sampleRate]);
7243
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ClipInteractionContextProvider, { value: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7244
- 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,
7245
5499
  {
7246
5500
  sensors,
7247
5501
  onDragStart,
@@ -7264,7 +5518,6 @@ var ClipInteractionProvider = ({
7264
5518
  ContinuousPlayCheckbox,
7265
5519
  DownloadAnnotationsButton,
7266
5520
  EditableCheckbox,
7267
- ExportWavButton,
7268
5521
  FastForwardButton,
7269
5522
  KeyboardShortcuts,
7270
5523
  LinkEndpointsCheckbox,
@@ -7287,17 +5540,10 @@ var ClipInteractionProvider = ({
7287
5540
  SpectrogramIntegrationProvider,
7288
5541
  StopButton,
7289
5542
  TimeFormatSelect,
7290
- Tone,
7291
5543
  Waveform,
7292
5544
  WaveformPlaylistProvider,
7293
5545
  ZoomInButton,
7294
5546
  ZoomOutButton,
7295
- createEffectChain,
7296
- createEffectInstance,
7297
- effectCategories,
7298
- effectDefinitions,
7299
- getEffectDefinition,
7300
- getEffectsByCategory,
7301
5547
  getShortcutLabel,
7302
5548
  getWaveformDataMetadata,
7303
5549
  loadPeaksFromWaveformData,
@@ -7306,22 +5552,16 @@ var ClipInteractionProvider = ({
7306
5552
  useAnnotationDragHandlers,
7307
5553
  useAnnotationIntegration,
7308
5554
  useAnnotationKeyboardControls,
7309
- useAudioTracks,
7310
5555
  useClipDragHandlers,
7311
5556
  useClipInteractionEnabled,
7312
5557
  useClipSplitting,
7313
5558
  useDragSensors,
7314
- useDynamicEffects,
7315
- useDynamicTracks,
7316
- useExportWav,
7317
5559
  useKeyboardShortcuts,
7318
- useMasterAnalyser,
7319
5560
  useMasterVolume,
7320
5561
  useMediaElementAnimation,
7321
5562
  useMediaElementControls,
7322
5563
  useMediaElementData,
7323
5564
  useMediaElementState,
7324
- useOutputMeter,
7325
5565
  usePlaybackAnimation,
7326
5566
  usePlaybackShortcuts,
7327
5567
  usePlaylistControls,
@@ -7330,7 +5570,6 @@ var ClipInteractionProvider = ({
7330
5570
  usePlaylistState,
7331
5571
  useSpectrogramIntegration,
7332
5572
  useTimeFormat,
7333
- useTrackDynamicEffects,
7334
5573
  useZoomControls,
7335
5574
  waveformDataToPeaks
7336
5575
  });