@waveform-playlist/ui-components 9.1.2 → 9.2.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
@@ -38,9 +38,12 @@ __export(index_exports, {
38
38
  BaseCheckboxWrapper: () => BaseCheckboxWrapper,
39
39
  BaseControlButton: () => BaseControlButton,
40
40
  BaseInput: () => BaseInput,
41
+ BaseInputSmall: () => BaseInputSmall,
41
42
  BaseLabel: () => BaseLabel,
42
43
  BaseSelect: () => BaseSelect,
44
+ BaseSelectSmall: () => BaseSelectSmall,
43
45
  BaseSlider: () => BaseSlider,
46
+ BeatsAndBarsProvider: () => BeatsAndBarsProvider,
44
47
  Button: () => Button,
45
48
  ButtonGroup: () => ButtonGroup,
46
49
  CLIP_BOUNDARY_WIDTH: () => CLIP_BOUNDARY_WIDTH,
@@ -94,14 +97,16 @@ __export(index_exports, {
94
97
  darkTheme: () => darkTheme,
95
98
  defaultTheme: () => defaultTheme,
96
99
  formatTime: () => formatTime,
100
+ getScaleInfo: () => getScaleInfo,
97
101
  isWaveformGradient: () => isWaveformGradient,
98
102
  parseTime: () => parseTime,
99
103
  pixelsToSamples: () => pixelsToSamples,
100
104
  pixelsToSeconds: () => pixelsToSeconds,
101
- samplesToPixels: () => samplesToPixels,
105
+ samplesToPixels: () => samplesToPixels2,
102
106
  samplesToSeconds: () => samplesToSeconds,
103
- secondsToPixels: () => secondsToPixels,
107
+ secondsToPixels: () => secondsToPixels2,
104
108
  secondsToSamples: () => secondsToSamples,
109
+ useBeatsAndBars: () => useBeatsAndBars,
105
110
  useClipViewportOrigin: () => useClipViewportOrigin,
106
111
  useDevicePixelRatio: () => useDevicePixelRatio,
107
112
  usePlaylistInfo: () => usePlaylistInfo,
@@ -890,7 +895,6 @@ var Channel = (props) => {
890
895
  const clipOriginX = useClipViewportOrigin();
891
896
  const visibleChunkIndices = useVisibleChunkIndices(length, import_core.MAX_CANVAS_WIDTH, clipOriginX);
892
897
  (0, import_react4.useEffect)(() => {
893
- const tDraw = performance.now();
894
898
  const step = barWidth + barGap;
895
899
  for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
896
900
  const globalPixelOffset = canvasIdx * import_core.MAX_CANVAS_WIDTH;
@@ -926,9 +930,6 @@ var Channel = (props) => {
926
930
  }
927
931
  }
928
932
  }
929
- console.log(
930
- `[waveform] draw ch${index}: ${canvasMapRef.current.size} chunks, ${(performance.now() - tDraw).toFixed(1)}ms`
931
- );
932
933
  }, [
933
934
  canvasMapRef,
934
935
  data,
@@ -1268,20 +1269,20 @@ var Clip = ({
1268
1269
  isDragSource
1269
1270
  } = (0, import_react7.useDraggable)({
1270
1271
  id: draggableId,
1271
- data: { clipId, trackIndex, clipIndex },
1272
+ data: { clipId, trackIndex, clipIndex, startSample, durationSamples },
1272
1273
  disabled: !enableDrag
1273
1274
  });
1274
1275
  const leftBoundaryId = `clip-boundary-left-${trackIndex}-${clipIndex}`;
1275
1276
  const { ref: leftBoundaryRef, isDragSource: isLeftBoundaryDragging } = (0, import_react7.useDraggable)({
1276
1277
  id: leftBoundaryId,
1277
- data: { clipId, trackIndex, clipIndex, boundary: "left" },
1278
+ data: { clipId, trackIndex, clipIndex, boundary: "left", startSample, durationSamples },
1278
1279
  disabled: !enableDrag,
1279
1280
  feedback: "none"
1280
1281
  });
1281
1282
  const rightBoundaryId = `clip-boundary-right-${trackIndex}-${clipIndex}`;
1282
1283
  const { ref: rightBoundaryRef, isDragSource: isRightBoundaryDragging } = (0, import_react7.useDraggable)({
1283
1284
  id: rightBoundaryId,
1284
- data: { clipId, trackIndex, clipIndex, boundary: "right" },
1285
+ data: { clipId, trackIndex, clipIndex, boundary: "right", startSample, durationSamples },
1285
1286
  disabled: !enableDrag,
1286
1287
  feedback: "none"
1287
1288
  });
@@ -1466,7 +1467,6 @@ var PianoRollChannel = ({
1466
1467
  }, [midiNotes]);
1467
1468
  const color = isSelected ? selectedNoteColor : noteColor;
1468
1469
  (0, import_react8.useEffect)(() => {
1469
- const tDraw = performance.now();
1470
1470
  const noteRange = maxMidi - minMidi + 1;
1471
1471
  const noteHeight = Math.max(2, waveHeight / noteRange);
1472
1472
  const pixelsPerSecond = sampleRate / samplesPerPixel;
@@ -1498,9 +1498,6 @@ var PianoRollChannel = ({
1498
1498
  }
1499
1499
  ctx.globalAlpha = 1;
1500
1500
  }
1501
- console.log(
1502
- `[piano-roll] draw ch${index}: ${canvasMapRef.current.size} chunks, ${midiNotes.length} notes, ${(performance.now() - tDraw).toFixed(1)}ms`
1503
- );
1504
1501
  }, [
1505
1502
  canvasMapRef,
1506
1503
  midiNotes,
@@ -2292,15 +2289,45 @@ var SelectionTimeInputs = ({
2292
2289
  ] });
2293
2290
  };
2294
2291
 
2295
- // src/contexts/DevicePixelRatio.tsx
2292
+ // src/contexts/BeatsAndBars.tsx
2296
2293
  var import_react14 = require("react");
2294
+ var import_core4 = require("@waveform-playlist/core");
2297
2295
  var import_jsx_runtime19 = require("react/jsx-runtime");
2296
+ var BeatsAndBarsContext = (0, import_react14.createContext)(null);
2297
+ function BeatsAndBarsProvider({
2298
+ bpm,
2299
+ timeSignature,
2300
+ snapTo,
2301
+ children
2302
+ }) {
2303
+ const [numerator, denominator] = timeSignature;
2304
+ const value = (0, import_react14.useMemo)(() => {
2305
+ const ts = [numerator, denominator];
2306
+ const tpBeat = (0, import_core4.ticksPerBeat)(ts);
2307
+ const tpBar = (0, import_core4.ticksPerBar)(ts);
2308
+ return {
2309
+ bpm,
2310
+ timeSignature: ts,
2311
+ snapTo,
2312
+ ticksPerBeat: tpBeat,
2313
+ ticksPerBar: tpBar
2314
+ };
2315
+ }, [bpm, numerator, denominator, snapTo]);
2316
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(BeatsAndBarsContext.Provider, { value, children });
2317
+ }
2318
+ function useBeatsAndBars() {
2319
+ return (0, import_react14.useContext)(BeatsAndBarsContext);
2320
+ }
2321
+
2322
+ // src/contexts/DevicePixelRatio.tsx
2323
+ var import_react15 = require("react");
2324
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2298
2325
  function getScale() {
2299
2326
  return window.devicePixelRatio;
2300
2327
  }
2301
- var DevicePixelRatioContext = (0, import_react14.createContext)(getScale());
2328
+ var DevicePixelRatioContext = (0, import_react15.createContext)(getScale());
2302
2329
  var DevicePixelRatioProvider = ({ children }) => {
2303
- const [scale, setScale] = (0, import_react14.useState)(getScale());
2330
+ const [scale, setScale] = (0, import_react15.useState)(getScale());
2304
2331
  matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
2305
2332
  "change",
2306
2333
  () => {
@@ -2308,13 +2335,13 @@ var DevicePixelRatioProvider = ({ children }) => {
2308
2335
  },
2309
2336
  { once: true }
2310
2337
  );
2311
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2338
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2312
2339
  };
2313
- var useDevicePixelRatio = () => (0, import_react14.useContext)(DevicePixelRatioContext);
2340
+ var useDevicePixelRatio = () => (0, import_react15.useContext)(DevicePixelRatioContext);
2314
2341
 
2315
2342
  // src/contexts/PlaylistInfo.tsx
2316
- var import_react15 = require("react");
2317
- var PlaylistInfoContext = (0, import_react15.createContext)({
2343
+ var import_react16 = require("react");
2344
+ var PlaylistInfoContext = (0, import_react16.createContext)({
2318
2345
  sampleRate: 48e3,
2319
2346
  samplesPerPixel: 1e3,
2320
2347
  zoomLevels: [1e3, 1500, 2e3, 2500],
@@ -2328,22 +2355,22 @@ var PlaylistInfoContext = (0, import_react15.createContext)({
2328
2355
  barWidth: 1,
2329
2356
  barGap: 0
2330
2357
  });
2331
- var usePlaylistInfo = () => (0, import_react15.useContext)(PlaylistInfoContext);
2358
+ var usePlaylistInfo = () => (0, import_react16.useContext)(PlaylistInfoContext);
2332
2359
 
2333
2360
  // src/contexts/Theme.tsx
2334
- var import_react16 = require("react");
2361
+ var import_react17 = require("react");
2335
2362
  var import_styled_components20 = require("styled-components");
2336
- var useTheme2 = () => (0, import_react16.useContext)(import_styled_components20.ThemeContext);
2363
+ var useTheme2 = () => (0, import_react17.useContext)(import_styled_components20.ThemeContext);
2337
2364
 
2338
2365
  // src/contexts/TrackControls.tsx
2339
- var import_react17 = require("react");
2340
- var import_jsx_runtime20 = require("react/jsx-runtime");
2341
- var TrackControlsContext = (0, import_react17.createContext)(/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react17.Fragment, {}));
2342
- var useTrackControls = () => (0, import_react17.useContext)(TrackControlsContext);
2343
-
2344
- // src/contexts/Playout.tsx
2345
2366
  var import_react18 = require("react");
2346
2367
  var import_jsx_runtime21 = require("react/jsx-runtime");
2368
+ var TrackControlsContext = (0, import_react18.createContext)(/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react18.Fragment, {}));
2369
+ var useTrackControls = () => (0, import_react18.useContext)(TrackControlsContext);
2370
+
2371
+ // src/contexts/Playout.tsx
2372
+ var import_react19 = require("react");
2373
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2347
2374
  var defaultProgress = 0;
2348
2375
  var defaultIsPlaying = false;
2349
2376
  var defaultSelectionStart = 0;
@@ -2354,8 +2381,8 @@ var defaultPlayout = {
2354
2381
  selectionStart: defaultSelectionStart,
2355
2382
  selectionEnd: defaultSelectionEnd
2356
2383
  };
2357
- var PlayoutStatusContext = (0, import_react18.createContext)(defaultPlayout);
2358
- var PlayoutStatusUpdateContext = (0, import_react18.createContext)({
2384
+ var PlayoutStatusContext = (0, import_react19.createContext)(defaultPlayout);
2385
+ var PlayoutStatusUpdateContext = (0, import_react19.createContext)({
2359
2386
  setIsPlaying: () => {
2360
2387
  },
2361
2388
  setProgress: () => {
@@ -2364,24 +2391,24 @@ var PlayoutStatusUpdateContext = (0, import_react18.createContext)({
2364
2391
  }
2365
2392
  });
2366
2393
  var PlayoutProvider = ({ children }) => {
2367
- const [isPlaying, setIsPlaying] = (0, import_react18.useState)(defaultIsPlaying);
2368
- const [progress, setProgress] = (0, import_react18.useState)(defaultProgress);
2369
- const [selectionStart, setSelectionStart] = (0, import_react18.useState)(defaultSelectionStart);
2370
- const [selectionEnd, setSelectionEnd] = (0, import_react18.useState)(defaultSelectionEnd);
2394
+ const [isPlaying, setIsPlaying] = (0, import_react19.useState)(defaultIsPlaying);
2395
+ const [progress, setProgress] = (0, import_react19.useState)(defaultProgress);
2396
+ const [selectionStart, setSelectionStart] = (0, import_react19.useState)(defaultSelectionStart);
2397
+ const [selectionEnd, setSelectionEnd] = (0, import_react19.useState)(defaultSelectionEnd);
2371
2398
  const setSelection = (start, end) => {
2372
2399
  setSelectionStart(start);
2373
2400
  setSelectionEnd(end);
2374
2401
  };
2375
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2402
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2376
2403
  };
2377
- var usePlayoutStatus = () => (0, import_react18.useContext)(PlayoutStatusContext);
2378
- var usePlayoutStatusUpdate = () => (0, import_react18.useContext)(PlayoutStatusUpdateContext);
2404
+ var usePlayoutStatus = () => (0, import_react19.useContext)(PlayoutStatusContext);
2405
+ var usePlayoutStatusUpdate = () => (0, import_react19.useContext)(PlayoutStatusUpdateContext);
2379
2406
 
2380
2407
  // src/components/SpectrogramChannel.tsx
2381
- var import_react19 = require("react");
2408
+ var import_react20 = require("react");
2382
2409
  var import_styled_components21 = __toESM(require("styled-components"));
2383
- var import_core4 = require("@waveform-playlist/core");
2384
- var import_jsx_runtime22 = require("react/jsx-runtime");
2410
+ var import_core5 = require("@waveform-playlist/core");
2411
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2385
2412
  var LINEAR_FREQUENCY_SCALE = (f, minF, maxF) => (f - minF) / (maxF - minF);
2386
2413
  var Wrapper4 = import_styled_components21.default.div.attrs((props) => ({
2387
2414
  style: {
@@ -2433,24 +2460,24 @@ var SpectrogramChannel = ({
2433
2460
  }) => {
2434
2461
  const channelIndex = channelIndexProp ?? index;
2435
2462
  const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2436
- const registeredIdsRef = (0, import_react19.useRef)([]);
2437
- const transferredCanvasesRef = (0, import_react19.useRef)(/* @__PURE__ */ new WeakSet());
2438
- const workerApiRef = (0, import_react19.useRef)(workerApi);
2439
- const onCanvasesReadyRef = (0, import_react19.useRef)(onCanvasesReady);
2463
+ const registeredIdsRef = (0, import_react20.useRef)([]);
2464
+ const transferredCanvasesRef = (0, import_react20.useRef)(/* @__PURE__ */ new WeakSet());
2465
+ const workerApiRef = (0, import_react20.useRef)(workerApi);
2466
+ const onCanvasesReadyRef = (0, import_react20.useRef)(onCanvasesReady);
2440
2467
  const isWorkerMode = !!(workerApi && clipId);
2441
2468
  const clipOriginX = useClipViewportOrigin();
2442
- const visibleChunkIndices = useVisibleChunkIndices(length, import_core4.MAX_CANVAS_WIDTH, clipOriginX);
2469
+ const visibleChunkIndices = useVisibleChunkIndices(length, import_core5.MAX_CANVAS_WIDTH, clipOriginX);
2443
2470
  const lut = colorLUT ?? DEFAULT_COLOR_LUT;
2444
2471
  const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);
2445
2472
  const scaleFn = frequencyScaleFn ?? LINEAR_FREQUENCY_SCALE;
2446
2473
  const hasCustomFrequencyScale = Boolean(frequencyScaleFn);
2447
- (0, import_react19.useEffect)(() => {
2474
+ (0, import_react20.useEffect)(() => {
2448
2475
  workerApiRef.current = workerApi;
2449
2476
  }, [workerApi]);
2450
- (0, import_react19.useEffect)(() => {
2477
+ (0, import_react20.useEffect)(() => {
2451
2478
  onCanvasesReadyRef.current = onCanvasesReady;
2452
2479
  }, [onCanvasesReady]);
2453
- (0, import_react19.useEffect)(() => {
2480
+ (0, import_react20.useEffect)(() => {
2454
2481
  if (!isWorkerMode) return;
2455
2482
  const currentWorkerApi = workerApiRef.current;
2456
2483
  if (!currentWorkerApi || !clipId) return;
@@ -2505,15 +2532,15 @@ var SpectrogramChannel = ({
2505
2532
  const match = id.match(/chunk(\d+)$/);
2506
2533
  if (!match) {
2507
2534
  console.warn(`[spectrogram] Unexpected canvas ID format: ${id}`);
2508
- return import_core4.MAX_CANVAS_WIDTH;
2535
+ return import_core5.MAX_CANVAS_WIDTH;
2509
2536
  }
2510
2537
  const chunkIdx = parseInt(match[1], 10);
2511
- return Math.min(length - chunkIdx * import_core4.MAX_CANVAS_WIDTH, import_core4.MAX_CANVAS_WIDTH);
2538
+ return Math.min(length - chunkIdx * import_core5.MAX_CANVAS_WIDTH, import_core5.MAX_CANVAS_WIDTH);
2512
2539
  });
2513
2540
  onCanvasesReadyRef.current?.(allIds, allWidths);
2514
2541
  }
2515
2542
  }, [canvasMapRef, isWorkerMode, clipId, channelIndex, length, visibleChunkIndices]);
2516
- (0, import_react19.useEffect)(() => {
2543
+ (0, import_react20.useEffect)(() => {
2517
2544
  return () => {
2518
2545
  const api = workerApiRef.current;
2519
2546
  if (!api) return;
@@ -2527,7 +2554,7 @@ var SpectrogramChannel = ({
2527
2554
  registeredIdsRef.current = [];
2528
2555
  };
2529
2556
  }, []);
2530
- (0, import_react19.useEffect)(() => {
2557
+ (0, import_react20.useEffect)(() => {
2531
2558
  if (isWorkerMode || !data) return;
2532
2559
  const {
2533
2560
  frequencyBinCount,
@@ -2540,7 +2567,7 @@ var SpectrogramChannel = ({
2540
2567
  const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;
2541
2568
  const binToFreq = (bin) => bin / frequencyBinCount * (sampleRate / 2);
2542
2569
  for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
2543
- const globalPixelOffset = canvasIdx * import_core4.MAX_CANVAS_WIDTH;
2570
+ const globalPixelOffset = canvasIdx * import_core5.MAX_CANVAS_WIDTH;
2544
2571
  const ctx = canvas.getContext("2d");
2545
2572
  if (!ctx) continue;
2546
2573
  const canvasWidth = canvas.width / devicePixelRatio;
@@ -2616,9 +2643,9 @@ var SpectrogramChannel = ({
2616
2643
  visibleChunkIndices
2617
2644
  ]);
2618
2645
  const canvases = visibleChunkIndices.map((i) => {
2619
- const chunkLeft = i * import_core4.MAX_CANVAS_WIDTH;
2620
- const currentWidth = Math.min(length - chunkLeft, import_core4.MAX_CANVAS_WIDTH);
2621
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2646
+ const chunkLeft = i * import_core5.MAX_CANVAS_WIDTH;
2647
+ const currentWidth = Math.min(length - chunkLeft, import_core5.MAX_CANVAS_WIDTH);
2648
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2622
2649
  SpectrogramCanvas,
2623
2650
  {
2624
2651
  $cssWidth: currentWidth,
@@ -2632,11 +2659,11 @@ var SpectrogramChannel = ({
2632
2659
  `${length}-${i}`
2633
2660
  );
2634
2661
  });
2635
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Wrapper4, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2662
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Wrapper4, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2636
2663
  };
2637
2664
 
2638
2665
  // src/components/SmartChannel.tsx
2639
- var import_jsx_runtime23 = require("react/jsx-runtime");
2666
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2640
2667
  var SmartChannel = ({
2641
2668
  isSelected,
2642
2669
  transparentBackground,
@@ -2670,7 +2697,7 @@ var SmartChannel = ({
2670
2697
  const drawMode = theme?.waveformDrawMode || "inverted";
2671
2698
  const hasSpectrogram = spectrogramData || spectrogramWorkerApi;
2672
2699
  if (renderMode === "spectrogram" && hasSpectrogram) {
2673
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2700
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2674
2701
  SpectrogramChannel,
2675
2702
  {
2676
2703
  index: props.index,
@@ -2691,8 +2718,8 @@ var SmartChannel = ({
2691
2718
  }
2692
2719
  if (renderMode === "both" && hasSpectrogram) {
2693
2720
  const halfHeight = Math.floor(waveHeight / 2);
2694
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
2695
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2721
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2722
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2696
2723
  SpectrogramChannel,
2697
2724
  {
2698
2725
  index: props.index * 2,
@@ -2711,7 +2738,7 @@ var SmartChannel = ({
2711
2738
  onCanvasesReady: spectrogramOnCanvasesReady
2712
2739
  }
2713
2740
  ),
2714
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2741
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2715
2742
  "div",
2716
2743
  {
2717
2744
  style: {
@@ -2720,7 +2747,7 @@ var SmartChannel = ({
2720
2747
  width: props.length,
2721
2748
  height: halfHeight
2722
2749
  },
2723
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2750
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2724
2751
  Channel,
2725
2752
  {
2726
2753
  ...props,
@@ -2740,7 +2767,7 @@ var SmartChannel = ({
2740
2767
  ] });
2741
2768
  }
2742
2769
  if (renderMode === "piano-roll") {
2743
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2770
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2744
2771
  PianoRollChannel,
2745
2772
  {
2746
2773
  index: props.index,
@@ -2759,7 +2786,7 @@ var SmartChannel = ({
2759
2786
  }
2760
2787
  );
2761
2788
  }
2762
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2789
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2763
2790
  Channel,
2764
2791
  {
2765
2792
  ...props,
@@ -2776,9 +2803,9 @@ var SmartChannel = ({
2776
2803
  };
2777
2804
 
2778
2805
  // src/components/SpectrogramLabels.tsx
2779
- var import_react20 = require("react");
2806
+ var import_react21 = require("react");
2780
2807
  var import_styled_components22 = __toESM(require("styled-components"));
2781
- var import_jsx_runtime24 = require("react/jsx-runtime");
2808
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2782
2809
  var LABELS_WIDTH = 72;
2783
2810
  var LabelsStickyWrapper = import_styled_components22.default.div`
2784
2811
  position: sticky;
@@ -2828,12 +2855,12 @@ var SpectrogramLabels = ({
2828
2855
  renderMode = "spectrogram",
2829
2856
  hasClipHeaders = false
2830
2857
  }) => {
2831
- const canvasRef = (0, import_react20.useRef)(null);
2858
+ const canvasRef = (0, import_react21.useRef)(null);
2832
2859
  const devicePixelRatio = useDevicePixelRatio();
2833
2860
  const spectrogramHeight = renderMode === "both" ? Math.floor(waveHeight / 2) : waveHeight;
2834
2861
  const totalHeight = numChannels * waveHeight;
2835
2862
  const clipHeaderOffset = hasClipHeaders ? 22 : 0;
2836
- (0, import_react20.useLayoutEffect)(() => {
2863
+ (0, import_react21.useLayoutEffect)(() => {
2837
2864
  const canvas = canvasRef.current;
2838
2865
  if (!canvas) return;
2839
2866
  const ctx = canvas.getContext("2d");
@@ -2871,7 +2898,7 @@ var SpectrogramLabels = ({
2871
2898
  spectrogramHeight,
2872
2899
  clipHeaderOffset
2873
2900
  ]);
2874
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2901
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2875
2902
  "canvas",
2876
2903
  {
2877
2904
  ref: canvasRef,
@@ -2887,41 +2914,14 @@ var SpectrogramLabels = ({
2887
2914
  };
2888
2915
 
2889
2916
  // src/components/SmartScale.tsx
2890
- var import_react22 = require("react");
2917
+ var import_react23 = __toESM(require("react"));
2918
+ var import_styled_components24 = __toESM(require("styled-components"));
2891
2919
 
2892
2920
  // src/components/TimeScale.tsx
2893
- var import_react21 = __toESM(require("react"));
2921
+ var import_react22 = require("react");
2894
2922
  var import_styled_components23 = __toESM(require("styled-components"));
2895
-
2896
- // src/utils/conversions.ts
2897
- function samplesToSeconds(samples, sampleRate) {
2898
- return samples / sampleRate;
2899
- }
2900
- function secondsToSamples(seconds, sampleRate) {
2901
- return Math.ceil(seconds * sampleRate);
2902
- }
2903
- function samplesToPixels(samples, samplesPerPixel) {
2904
- return Math.floor(samples / samplesPerPixel);
2905
- }
2906
- function pixelsToSamples(pixels, samplesPerPixel) {
2907
- return Math.floor(pixels * samplesPerPixel);
2908
- }
2909
- function pixelsToSeconds(pixels, samplesPerPixel, sampleRate) {
2910
- return pixels * samplesPerPixel / sampleRate;
2911
- }
2912
- function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
2913
- return Math.ceil(seconds * sampleRate / samplesPerPixel);
2914
- }
2915
-
2916
- // src/components/TimeScale.tsx
2917
- var import_core5 = require("@waveform-playlist/core");
2918
- var import_jsx_runtime25 = require("react/jsx-runtime");
2919
- function formatTime2(milliseconds) {
2920
- const seconds = Math.floor(milliseconds / 1e3);
2921
- const s = seconds % 60;
2922
- const m = (seconds - s) / 60;
2923
- return `${m}:${String(s).padStart(2, "0")}`;
2924
- }
2923
+ var import_core6 = require("@waveform-playlist/core");
2924
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2925
2925
  var PlaylistTimeScaleScroll = import_styled_components23.default.div.attrs((props) => ({
2926
2926
  style: {
2927
2927
  width: `${props.$cssWidth}px`,
@@ -2943,70 +2943,20 @@ var TimeTickChunk = import_styled_components23.default.canvas.attrs((props) => (
2943
2943
  position: absolute;
2944
2944
  bottom: 0;
2945
2945
  `;
2946
- var TimeStamp = import_styled_components23.default.div.attrs((props) => ({
2947
- style: {
2948
- left: `${props.$left + 4}px`
2949
- // Offset 4px to the right of the tick
2950
- }
2951
- }))`
2952
- position: absolute;
2953
- font-size: 0.75rem; /* Smaller font to prevent overflow */
2954
- white-space: nowrap; /* Prevent text wrapping */
2955
- color: ${(props) => props.theme.timeColor}; /* Use theme color instead of inheriting */
2956
- `;
2957
2946
  var TimeScale = (props) => {
2958
2947
  const {
2959
2948
  theme: { timeColor },
2960
- duration,
2961
- marker,
2962
- bigStep,
2963
- secondStep,
2964
- renderTimestamp
2949
+ tickData
2965
2950
  } = props;
2966
2951
  const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2967
- const { sampleRate, samplesPerPixel, timeScaleHeight } = (0, import_react21.useContext)(PlaylistInfoContext);
2952
+ const { timeScaleHeight } = (0, import_react22.useContext)(PlaylistInfoContext);
2968
2953
  const devicePixelRatio = useDevicePixelRatio();
2969
- const { widthX, canvasInfo, timeMarkersWithPositions } = (0, import_react21.useMemo)(() => {
2970
- const nextCanvasInfo = /* @__PURE__ */ new Map();
2971
- const nextMarkers = [];
2972
- const nextWidthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
2973
- const pixPerSec = sampleRate / samplesPerPixel;
2974
- let counter = 0;
2975
- for (let i = 0; i < nextWidthX; i += pixPerSec * secondStep / 1e3) {
2976
- const pix = Math.floor(i);
2977
- if (counter % marker === 0) {
2978
- const timeMs = counter;
2979
- const timestamp = formatTime2(timeMs);
2980
- const element = renderTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react21.default.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2981
- nextMarkers.push({ pix, element });
2982
- nextCanvasInfo.set(pix, timeScaleHeight);
2983
- } else if (counter % bigStep === 0) {
2984
- nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
2985
- } else if (counter % secondStep === 0) {
2986
- nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 5));
2987
- }
2988
- counter += secondStep;
2989
- }
2990
- return {
2991
- widthX: nextWidthX,
2992
- canvasInfo: nextCanvasInfo,
2993
- timeMarkersWithPositions: nextMarkers
2994
- };
2995
- }, [
2996
- duration,
2997
- samplesPerPixel,
2998
- sampleRate,
2999
- marker,
3000
- bigStep,
3001
- secondStep,
3002
- renderTimestamp,
3003
- timeScaleHeight
3004
- ]);
3005
- const visibleChunkIndices = useVisibleChunkIndices(widthX, import_core5.MAX_CANVAS_WIDTH);
2954
+ const { widthX, canvasInfo, timeMarkersWithPositions } = tickData;
2955
+ const visibleChunkIndices = useVisibleChunkIndices(widthX, import_core6.MAX_CANVAS_WIDTH);
3006
2956
  const visibleChunks = visibleChunkIndices.map((i) => {
3007
- const chunkLeft = i * import_core5.MAX_CANVAS_WIDTH;
3008
- const chunkWidth = Math.min(widthX - chunkLeft, import_core5.MAX_CANVAS_WIDTH);
3009
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2957
+ const chunkLeft = i * import_core6.MAX_CANVAS_WIDTH;
2958
+ const chunkWidth = Math.min(widthX - chunkLeft, import_core6.MAX_CANVAS_WIDTH);
2959
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3010
2960
  TimeTickChunk,
3011
2961
  {
3012
2962
  $cssWidth: chunkWidth,
@@ -3020,14 +2970,14 @@ var TimeScale = (props) => {
3020
2970
  `timescale-${i}`
3021
2971
  );
3022
2972
  });
3023
- const firstChunkLeft = visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * import_core5.MAX_CANVAS_WIDTH : 0;
3024
- const lastChunkRight = visibleChunkIndices.length > 0 ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * import_core5.MAX_CANVAS_WIDTH : Infinity;
2973
+ const firstChunkLeft = visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * import_core6.MAX_CANVAS_WIDTH : 0;
2974
+ const lastChunkRight = visibleChunkIndices.length > 0 ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * import_core6.MAX_CANVAS_WIDTH : Infinity;
3025
2975
  const visibleMarkers = visibleChunkIndices.length > 0 ? timeMarkersWithPositions.filter(({ pix }) => pix >= firstChunkLeft && pix < lastChunkRight).map(({ element }) => element) : timeMarkersWithPositions.map(({ element }) => element);
3026
- (0, import_react21.useLayoutEffect)(() => {
2976
+ (0, import_react22.useLayoutEffect)(() => {
3027
2977
  for (const [chunkIdx, canvas] of canvasMapRef.current.entries()) {
3028
2978
  const ctx = canvas.getContext("2d");
3029
2979
  if (!ctx) continue;
3030
- const chunkLeft = chunkIdx * import_core5.MAX_CANVAS_WIDTH;
2980
+ const chunkLeft = chunkIdx * import_core6.MAX_CANVAS_WIDTH;
3031
2981
  const chunkWidth = canvas.width / devicePixelRatio;
3032
2982
  ctx.resetTransform();
3033
2983
  ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -3041,16 +2991,8 @@ var TimeScale = (props) => {
3041
2991
  ctx.fillRect(localX, scaleY, 1, scaleHeight);
3042
2992
  }
3043
2993
  }
3044
- }, [
3045
- canvasMapRef,
3046
- duration,
3047
- devicePixelRatio,
3048
- timeColor,
3049
- timeScaleHeight,
3050
- canvasInfo,
3051
- visibleChunkIndices
3052
- ]);
3053
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(PlaylistTimeScaleScroll, { $cssWidth: widthX, $timeScaleHeight: timeScaleHeight, children: [
2994
+ }, [canvasMapRef, devicePixelRatio, timeColor, timeScaleHeight, canvasInfo, visibleChunkIndices]);
2995
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(PlaylistTimeScaleScroll, { $cssWidth: widthX, $timeScaleHeight: timeScaleHeight, children: [
3054
2996
  visibleMarkers,
3055
2997
  visibleChunks
3056
2998
  ] });
@@ -3058,64 +3000,16 @@ var TimeScale = (props) => {
3058
3000
  var StyledTimeScale = (0, import_styled_components23.withTheme)(TimeScale);
3059
3001
 
3060
3002
  // src/components/SmartScale.tsx
3061
- var import_jsx_runtime26 = require("react/jsx-runtime");
3003
+ var import_core7 = require("@waveform-playlist/core");
3004
+ var import_jsx_runtime27 = require("react/jsx-runtime");
3062
3005
  var timeinfo = /* @__PURE__ */ new Map([
3063
- [
3064
- 700,
3065
- {
3066
- marker: 1e3,
3067
- bigStep: 500,
3068
- smallStep: 100
3069
- }
3070
- ],
3071
- [
3072
- 1500,
3073
- {
3074
- marker: 2e3,
3075
- bigStep: 1e3,
3076
- smallStep: 200
3077
- }
3078
- ],
3079
- [
3080
- 2500,
3081
- {
3082
- marker: 2e3,
3083
- bigStep: 1e3,
3084
- smallStep: 500
3085
- }
3086
- ],
3087
- [
3088
- 5e3,
3089
- {
3090
- marker: 5e3,
3091
- bigStep: 1e3,
3092
- smallStep: 500
3093
- }
3094
- ],
3095
- [
3096
- 1e4,
3097
- {
3098
- marker: 1e4,
3099
- bigStep: 5e3,
3100
- smallStep: 1e3
3101
- }
3102
- ],
3103
- [
3104
- 12e3,
3105
- {
3106
- marker: 15e3,
3107
- bigStep: 5e3,
3108
- smallStep: 1e3
3109
- }
3110
- ],
3111
- [
3112
- Infinity,
3113
- {
3114
- marker: 3e4,
3115
- bigStep: 1e4,
3116
- smallStep: 5e3
3117
- }
3118
- ]
3006
+ [700, { marker: 1e3, bigStep: 500, smallStep: 100 }],
3007
+ [1500, { marker: 2e3, bigStep: 1e3, smallStep: 200 }],
3008
+ [2500, { marker: 2e3, bigStep: 1e3, smallStep: 500 }],
3009
+ [5e3, { marker: 5e3, bigStep: 1e3, smallStep: 500 }],
3010
+ [1e4, { marker: 1e4, bigStep: 5e3, smallStep: 1e3 }],
3011
+ [12e3, { marker: 15e3, bigStep: 5e3, smallStep: 1e3 }],
3012
+ [Infinity, { marker: 3e4, bigStep: 1e4, smallStep: 5e3 }]
3119
3013
  ]);
3120
3014
  function getScaleInfo(samplesPerPixel) {
3121
3015
  const keys = timeinfo.keys();
@@ -3131,25 +3025,113 @@ function getScaleInfo(samplesPerPixel) {
3131
3025
  }
3132
3026
  return config;
3133
3027
  }
3134
- var SmartScale = ({ renderTimestamp }) => {
3135
- const { samplesPerPixel, duration } = (0, import_react22.useContext)(PlaylistInfoContext);
3136
- let config = getScaleInfo(samplesPerPixel);
3137
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3138
- StyledTimeScale,
3139
- {
3140
- marker: config.marker,
3141
- bigStep: config.bigStep,
3142
- secondStep: config.smallStep,
3143
- duration,
3144
- renderTimestamp
3028
+ function formatTime2(milliseconds) {
3029
+ const seconds = Math.floor(milliseconds / 1e3);
3030
+ const s = seconds % 60;
3031
+ const m = (seconds - s) / 60;
3032
+ return `${m}:${String(s).padStart(2, "0")}`;
3033
+ }
3034
+ var TimeStamp = import_styled_components24.default.div.attrs((props) => ({
3035
+ style: {
3036
+ left: `${props.$left + 4}px`
3037
+ // Offset 4px to the right of the tick
3038
+ }
3039
+ }))`
3040
+ position: absolute;
3041
+ font-size: 0.75rem; /* Smaller font to prevent overflow */
3042
+ white-space: nowrap; /* Prevent text wrapping */
3043
+ color: ${(props) => props.theme.timeColor}; /* Use theme color instead of inheriting */
3044
+ `;
3045
+ var SmartScale = ({ renderTick }) => {
3046
+ const { samplesPerPixel, sampleRate, duration, timeScaleHeight } = (0, import_react23.useContext)(PlaylistInfoContext);
3047
+ const beatsAndBars = useBeatsAndBars();
3048
+ const tickData = (0, import_react23.useMemo)(() => {
3049
+ const widthX = (0, import_core7.secondsToPixels)(duration / 1e3, samplesPerPixel, sampleRate);
3050
+ if (beatsAndBars) {
3051
+ const { bpm, timeSignature, ticksPerBar: tpBar, ticksPerBeat: tpBeat } = beatsAndBars;
3052
+ const canvasInfo2 = /* @__PURE__ */ new Map();
3053
+ const timeMarkersWithPositions2 = [];
3054
+ const durationSeconds = duration / 1e3;
3055
+ const totalTicks = Math.ceil(durationSeconds * bpm * import_core7.PPQN / 60);
3056
+ const pixelsPerBeat = (0, import_core7.ticksToSamples)(tpBeat, bpm, sampleRate) / samplesPerPixel;
3057
+ const pixelsPerBar = (0, import_core7.ticksToSamples)(tpBar, bpm, sampleRate) / samplesPerPixel;
3058
+ const MIN_TICK_PX = 10;
3059
+ const MIN_LABEL_PX = 30;
3060
+ let tickStep;
3061
+ if (pixelsPerBeat >= MIN_TICK_PX) {
3062
+ tickStep = tpBeat;
3063
+ } else if (pixelsPerBar >= MIN_TICK_PX) {
3064
+ tickStep = tpBar;
3065
+ } else {
3066
+ const barsPerTick = Math.ceil(MIN_TICK_PX / pixelsPerBar);
3067
+ tickStep = tpBar * barsPerTick;
3068
+ }
3069
+ let labelStep;
3070
+ if (pixelsPerBeat >= MIN_LABEL_PX) {
3071
+ labelStep = tpBeat;
3072
+ } else if (pixelsPerBar >= MIN_LABEL_PX) {
3073
+ labelStep = tpBar;
3074
+ } else {
3075
+ const barsPerLabel = Math.ceil(MIN_LABEL_PX / pixelsPerBar);
3076
+ labelStep = tpBar * barsPerLabel;
3077
+ }
3078
+ for (let tick = 0; tick <= totalTicks; tick += tickStep) {
3079
+ const samples = (0, import_core7.ticksToSamples)(tick, bpm, sampleRate);
3080
+ const pix = (0, import_core7.samplesToPixels)(samples, samplesPerPixel);
3081
+ if (pix >= widthX) break;
3082
+ const isBarLine = tick % tpBar === 0;
3083
+ const isLabelTick = tick % labelStep === 0;
3084
+ const tickHeight = isBarLine ? timeScaleHeight : isLabelTick ? Math.floor(timeScaleHeight / 2) : Math.floor(timeScaleHeight / 5);
3085
+ canvasInfo2.set(pix, tickHeight);
3086
+ if (isLabelTick) {
3087
+ const label = (0, import_core7.ticksToBarBeatLabel)(tick, timeSignature);
3088
+ const element = renderTick ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react23.default.Fragment, { children: renderTick(label, pix) }, `bb-${tick}`) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3089
+ "div",
3090
+ {
3091
+ style: {
3092
+ position: "absolute",
3093
+ left: `${pix + 4}px`,
3094
+ fontSize: "0.75rem",
3095
+ whiteSpace: "nowrap"
3096
+ },
3097
+ children: label
3098
+ },
3099
+ `bb-${tick}`
3100
+ );
3101
+ timeMarkersWithPositions2.push({ pix, element });
3102
+ }
3103
+ }
3104
+ return { widthX, canvasInfo: canvasInfo2, timeMarkersWithPositions: timeMarkersWithPositions2 };
3145
3105
  }
3146
- );
3106
+ const config = getScaleInfo(samplesPerPixel);
3107
+ const { marker, bigStep, smallStep } = config;
3108
+ const canvasInfo = /* @__PURE__ */ new Map();
3109
+ const timeMarkersWithPositions = [];
3110
+ const pixPerSec = sampleRate / samplesPerPixel;
3111
+ let counter = 0;
3112
+ for (let i = 0; i < widthX; i += pixPerSec * smallStep / 1e3) {
3113
+ const pix = Math.floor(i);
3114
+ if (counter % marker === 0) {
3115
+ const timestamp = formatTime2(counter);
3116
+ const element = renderTick ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react23.default.Fragment, { children: renderTick(timestamp, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
3117
+ timeMarkersWithPositions.push({ pix, element });
3118
+ canvasInfo.set(pix, timeScaleHeight);
3119
+ } else if (counter % bigStep === 0) {
3120
+ canvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
3121
+ } else if (counter % smallStep === 0) {
3122
+ canvasInfo.set(pix, Math.floor(timeScaleHeight / 5));
3123
+ }
3124
+ counter += smallStep;
3125
+ }
3126
+ return { widthX, canvasInfo, timeMarkersWithPositions };
3127
+ }, [beatsAndBars, duration, samplesPerPixel, sampleRate, timeScaleHeight, renderTick]);
3128
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(StyledTimeScale, { tickData });
3147
3129
  };
3148
3130
 
3149
3131
  // src/components/TimeFormatSelect.tsx
3150
- var import_styled_components24 = __toESM(require("styled-components"));
3151
- var import_jsx_runtime27 = require("react/jsx-runtime");
3152
- var SelectWrapper = import_styled_components24.default.div`
3132
+ var import_styled_components25 = __toESM(require("styled-components"));
3133
+ var import_jsx_runtime28 = require("react/jsx-runtime");
3134
+ var SelectWrapper = import_styled_components25.default.div`
3153
3135
  display: inline-flex;
3154
3136
  align-items: center;
3155
3137
  gap: 0.5rem;
@@ -3171,7 +3153,7 @@ var TimeFormatSelect = ({
3171
3153
  const handleChange = (e) => {
3172
3154
  onChange(e.target.value);
3173
3155
  };
3174
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3156
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3175
3157
  BaseSelect,
3176
3158
  {
3177
3159
  className: "time-format",
@@ -3179,15 +3161,15 @@ var TimeFormatSelect = ({
3179
3161
  onChange: handleChange,
3180
3162
  disabled,
3181
3163
  "aria-label": "Time format selection",
3182
- children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("option", { value: option.value, children: option.label }, option.value))
3164
+ children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("option", { value: option.value, children: option.label }, option.value))
3183
3165
  }
3184
3166
  ) });
3185
3167
  };
3186
3168
 
3187
3169
  // src/components/Track.tsx
3188
- var import_styled_components25 = __toESM(require("styled-components"));
3189
- var import_jsx_runtime28 = require("react/jsx-runtime");
3190
- var Container = import_styled_components25.default.div.attrs((props) => ({
3170
+ var import_styled_components26 = __toESM(require("styled-components"));
3171
+ var import_jsx_runtime29 = require("react/jsx-runtime");
3172
+ var Container = import_styled_components26.default.div.attrs((props) => ({
3191
3173
  style: {
3192
3174
  height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
3193
3175
  }
@@ -3195,7 +3177,7 @@ var Container = import_styled_components25.default.div.attrs((props) => ({
3195
3177
  position: relative;
3196
3178
  ${(props) => props.$width !== void 0 && `width: ${props.$width}px;`}
3197
3179
  `;
3198
- var ChannelContainer = import_styled_components25.default.div.attrs((props) => ({
3180
+ var ChannelContainer = import_styled_components26.default.div.attrs((props) => ({
3199
3181
  style: {
3200
3182
  paddingLeft: `${props.$offset || 0}px`
3201
3183
  }
@@ -3217,7 +3199,7 @@ var Track = ({
3217
3199
  isSelected: _isSelected = false
3218
3200
  }) => {
3219
3201
  const { waveHeight } = usePlaylistInfo();
3220
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3202
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3221
3203
  Container,
3222
3204
  {
3223
3205
  $numChannels: numChannels,
@@ -3225,7 +3207,7 @@ var Track = ({
3225
3207
  $waveHeight: waveHeight,
3226
3208
  $width: width,
3227
3209
  $hasClipHeaders: hasClipHeaders,
3228
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3210
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3229
3211
  ChannelContainer,
3230
3212
  {
3231
3213
  $backgroundColor: backgroundColor,
@@ -3240,8 +3222,8 @@ var Track = ({
3240
3222
  };
3241
3223
 
3242
3224
  // src/components/TrackControls/Button.tsx
3243
- var import_styled_components26 = __toESM(require("styled-components"));
3244
- var Button = import_styled_components26.default.button.attrs({
3225
+ var import_styled_components27 = __toESM(require("styled-components"));
3226
+ var Button = import_styled_components27.default.button.attrs({
3245
3227
  type: "button"
3246
3228
  })`
3247
3229
  display: inline-block;
@@ -3316,8 +3298,8 @@ var Button = import_styled_components26.default.button.attrs({
3316
3298
  `;
3317
3299
 
3318
3300
  // src/components/TrackControls/ButtonGroup.tsx
3319
- var import_styled_components27 = __toESM(require("styled-components"));
3320
- var ButtonGroup = import_styled_components27.default.div`
3301
+ var import_styled_components28 = __toESM(require("styled-components"));
3302
+ var ButtonGroup = import_styled_components28.default.div`
3321
3303
  margin-bottom: 0.3rem;
3322
3304
 
3323
3305
  button:not(:first-child) {
@@ -3332,10 +3314,10 @@ var ButtonGroup = import_styled_components27.default.div`
3332
3314
  `;
3333
3315
 
3334
3316
  // src/components/TrackControls/CloseButton.tsx
3335
- var import_styled_components28 = __toESM(require("styled-components"));
3336
- var import_react23 = require("@phosphor-icons/react");
3337
- var import_jsx_runtime29 = require("react/jsx-runtime");
3338
- var StyledCloseButton = import_styled_components28.default.button`
3317
+ var import_styled_components29 = __toESM(require("styled-components"));
3318
+ var import_react24 = require("@phosphor-icons/react");
3319
+ var import_jsx_runtime30 = require("react/jsx-runtime");
3320
+ var StyledCloseButton = import_styled_components29.default.button`
3339
3321
  position: absolute;
3340
3322
  left: 0;
3341
3323
  top: 0;
@@ -3358,11 +3340,11 @@ var StyledCloseButton = import_styled_components28.default.button`
3358
3340
  color: #dc3545;
3359
3341
  }
3360
3342
  `;
3361
- var CloseButton = ({ onClick, title = "Remove track" }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react23.X, { size: 12, weight: "bold" }) });
3343
+ var CloseButton = ({ onClick, title = "Remove track" }) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react24.X, { size: 12, weight: "bold" }) });
3362
3344
 
3363
3345
  // src/components/TrackControls/Controls.tsx
3364
- var import_styled_components29 = __toESM(require("styled-components"));
3365
- var Controls = import_styled_components29.default.div`
3346
+ var import_styled_components30 = __toESM(require("styled-components"));
3347
+ var Controls = import_styled_components30.default.div`
3366
3348
  background: transparent;
3367
3349
  width: 100%;
3368
3350
  height: 100%;
@@ -3378,8 +3360,8 @@ var Controls = import_styled_components29.default.div`
3378
3360
  `;
3379
3361
 
3380
3362
  // src/components/TrackControls/Header.tsx
3381
- var import_styled_components30 = __toESM(require("styled-components"));
3382
- var Header = import_styled_components30.default.header`
3363
+ var import_styled_components31 = __toESM(require("styled-components"));
3364
+ var Header = import_styled_components31.default.header`
3383
3365
  overflow: hidden;
3384
3366
  height: 26px;
3385
3367
  width: 100%;
@@ -3393,28 +3375,28 @@ var Header = import_styled_components30.default.header`
3393
3375
  `;
3394
3376
 
3395
3377
  // src/components/TrackControls/VolumeDownIcon.tsx
3396
- var import_react24 = require("@phosphor-icons/react");
3397
- var import_jsx_runtime30 = require("react/jsx-runtime");
3398
- var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react24.SpeakerLowIcon, { weight: "light", ...props });
3399
-
3400
- // src/components/TrackControls/VolumeUpIcon.tsx
3401
3378
  var import_react25 = require("@phosphor-icons/react");
3402
3379
  var import_jsx_runtime31 = require("react/jsx-runtime");
3403
- var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react25.SpeakerHighIcon, { weight: "light", ...props });
3380
+ var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react25.SpeakerLowIcon, { weight: "light", ...props });
3404
3381
 
3405
- // src/components/TrackControls/TrashIcon.tsx
3382
+ // src/components/TrackControls/VolumeUpIcon.tsx
3406
3383
  var import_react26 = require("@phosphor-icons/react");
3407
3384
  var import_jsx_runtime32 = require("react/jsx-runtime");
3408
- var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react26.TrashIcon, { weight: "light", ...props });
3385
+ var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react26.SpeakerHighIcon, { weight: "light", ...props });
3409
3386
 
3410
- // src/components/TrackControls/DotsIcon.tsx
3387
+ // src/components/TrackControls/TrashIcon.tsx
3411
3388
  var import_react27 = require("@phosphor-icons/react");
3412
3389
  var import_jsx_runtime33 = require("react/jsx-runtime");
3413
- var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_react27.DotsThreeIcon, { weight: "bold", ...props });
3390
+ var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_react27.TrashIcon, { weight: "light", ...props });
3391
+
3392
+ // src/components/TrackControls/DotsIcon.tsx
3393
+ var import_react28 = require("@phosphor-icons/react");
3394
+ var import_jsx_runtime34 = require("react/jsx-runtime");
3395
+ var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react28.DotsThreeIcon, { weight: "bold", ...props });
3414
3396
 
3415
3397
  // src/components/TrackControls/Slider.tsx
3416
- var import_styled_components31 = __toESM(require("styled-components"));
3417
- var Slider = (0, import_styled_components31.default)(BaseSlider)`
3398
+ var import_styled_components32 = __toESM(require("styled-components"));
3399
+ var Slider = (0, import_styled_components32.default)(BaseSlider)`
3418
3400
  width: 75%;
3419
3401
  height: 5px;
3420
3402
  background: ${(props) => props.theme.sliderTrackColor};
@@ -3466,8 +3448,8 @@ var Slider = (0, import_styled_components31.default)(BaseSlider)`
3466
3448
  `;
3467
3449
 
3468
3450
  // src/components/TrackControls/SliderWrapper.tsx
3469
- var import_styled_components32 = __toESM(require("styled-components"));
3470
- var SliderWrapper = import_styled_components32.default.label`
3451
+ var import_styled_components33 = __toESM(require("styled-components"));
3452
+ var SliderWrapper = import_styled_components33.default.label`
3471
3453
  width: 100%;
3472
3454
  display: flex;
3473
3455
  justify-content: space-between;
@@ -3478,15 +3460,15 @@ var SliderWrapper = import_styled_components32.default.label`
3478
3460
  `;
3479
3461
 
3480
3462
  // src/components/TrackMenu.tsx
3481
- var import_react28 = __toESM(require("react"));
3463
+ var import_react29 = __toESM(require("react"));
3482
3464
  var import_react_dom = require("react-dom");
3483
- var import_styled_components33 = __toESM(require("styled-components"));
3484
- var import_jsx_runtime34 = require("react/jsx-runtime");
3485
- var MenuContainer = import_styled_components33.default.div`
3465
+ var import_styled_components34 = __toESM(require("styled-components"));
3466
+ var import_jsx_runtime35 = require("react/jsx-runtime");
3467
+ var MenuContainer = import_styled_components34.default.div`
3486
3468
  position: relative;
3487
3469
  display: inline-block;
3488
3470
  `;
3489
- var MenuButton = import_styled_components33.default.button`
3471
+ var MenuButton = import_styled_components34.default.button`
3490
3472
  background: none;
3491
3473
  border: none;
3492
3474
  cursor: pointer;
@@ -3502,7 +3484,7 @@ var MenuButton = import_styled_components33.default.button`
3502
3484
  }
3503
3485
  `;
3504
3486
  var DROPDOWN_MIN_WIDTH = 180;
3505
- var Dropdown = import_styled_components33.default.div`
3487
+ var Dropdown = import_styled_components34.default.div`
3506
3488
  position: fixed;
3507
3489
  top: ${(p) => p.$top}px;
3508
3490
  left: ${(p) => p.$left}px;
@@ -3515,19 +3497,19 @@ var Dropdown = import_styled_components33.default.div`
3515
3497
  min-width: ${DROPDOWN_MIN_WIDTH}px;
3516
3498
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
3517
3499
  `;
3518
- var Divider = import_styled_components33.default.hr`
3500
+ var Divider = import_styled_components34.default.hr`
3519
3501
  border: none;
3520
3502
  border-top: 1px solid rgba(128, 128, 128, 0.3);
3521
3503
  margin: 0.35rem 0;
3522
3504
  `;
3523
3505
  var TrackMenu = ({ items: itemsProp }) => {
3524
- const [open, setOpen] = (0, import_react28.useState)(false);
3525
- const close = (0, import_react28.useCallback)(() => setOpen(false), []);
3506
+ const [open, setOpen] = (0, import_react29.useState)(false);
3507
+ const close = (0, import_react29.useCallback)(() => setOpen(false), []);
3526
3508
  const items = typeof itemsProp === "function" ? itemsProp(close) : itemsProp;
3527
- const [dropdownPos, setDropdownPos] = (0, import_react28.useState)({ top: 0, left: 0 });
3528
- const buttonRef = (0, import_react28.useRef)(null);
3529
- const dropdownRef = (0, import_react28.useRef)(null);
3530
- const updatePosition = (0, import_react28.useCallback)(() => {
3509
+ const [dropdownPos, setDropdownPos] = (0, import_react29.useState)({ top: 0, left: 0 });
3510
+ const buttonRef = (0, import_react29.useRef)(null);
3511
+ const dropdownRef = (0, import_react29.useRef)(null);
3512
+ const updatePosition = (0, import_react29.useCallback)(() => {
3531
3513
  if (!buttonRef.current) return;
3532
3514
  const rect = buttonRef.current.getBoundingClientRect();
3533
3515
  const vw = window.innerWidth;
@@ -3544,7 +3526,7 @@ var TrackMenu = ({ items: itemsProp }) => {
3544
3526
  }
3545
3527
  setDropdownPos({ top, left });
3546
3528
  }, []);
3547
- (0, import_react28.useEffect)(() => {
3529
+ (0, import_react29.useEffect)(() => {
3548
3530
  if (!open) return;
3549
3531
  updatePosition();
3550
3532
  const rafId = requestAnimationFrame(() => updatePosition());
@@ -3558,7 +3540,7 @@ var TrackMenu = ({ items: itemsProp }) => {
3558
3540
  window.removeEventListener("resize", onResize);
3559
3541
  };
3560
3542
  }, [open, updatePosition]);
3561
- (0, import_react28.useEffect)(() => {
3543
+ (0, import_react29.useEffect)(() => {
3562
3544
  if (!open) return;
3563
3545
  const handleClick = (e) => {
3564
3546
  const target = e.target;
@@ -3578,8 +3560,8 @@ var TrackMenu = ({ items: itemsProp }) => {
3578
3560
  document.removeEventListener("keydown", handleKeyDown);
3579
3561
  };
3580
3562
  }, [open]);
3581
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(MenuContainer, { children: [
3582
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
3563
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(MenuContainer, { children: [
3564
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3583
3565
  MenuButton,
3584
3566
  {
3585
3567
  ref: buttonRef,
@@ -3590,19 +3572,19 @@ var TrackMenu = ({ items: itemsProp }) => {
3590
3572
  onMouseDown: (e) => e.stopPropagation(),
3591
3573
  title: "Track menu",
3592
3574
  "aria-label": "Track menu",
3593
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(DotsIcon, { size: 16 })
3575
+ children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(DotsIcon, { size: 16 })
3594
3576
  }
3595
3577
  ),
3596
3578
  open && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
3597
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
3579
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3598
3580
  Dropdown,
3599
3581
  {
3600
3582
  ref: dropdownRef,
3601
3583
  $top: dropdownPos.top,
3602
3584
  $left: dropdownPos.left,
3603
3585
  onMouseDown: (e) => e.stopPropagation(),
3604
- children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react28.default.Fragment, { children: [
3605
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Divider, {}),
3586
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_react29.default.Fragment, { children: [
3587
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Divider, {}),
3606
3588
  item.content
3607
3589
  ] }, item.id))
3608
3590
  }
@@ -3611,6 +3593,26 @@ var TrackMenu = ({ items: itemsProp }) => {
3611
3593
  )
3612
3594
  ] });
3613
3595
  };
3596
+
3597
+ // src/utils/conversions.ts
3598
+ function samplesToSeconds(samples, sampleRate) {
3599
+ return samples / sampleRate;
3600
+ }
3601
+ function secondsToSamples(seconds, sampleRate) {
3602
+ return Math.ceil(seconds * sampleRate);
3603
+ }
3604
+ function samplesToPixels2(samples, samplesPerPixel) {
3605
+ return Math.floor(samples / samplesPerPixel);
3606
+ }
3607
+ function pixelsToSamples(pixels, samplesPerPixel) {
3608
+ return Math.floor(pixels * samplesPerPixel);
3609
+ }
3610
+ function pixelsToSeconds(pixels, samplesPerPixel, sampleRate) {
3611
+ return pixels * samplesPerPixel / sampleRate;
3612
+ }
3613
+ function secondsToPixels2(seconds, samplesPerPixel, sampleRate) {
3614
+ return Math.ceil(seconds * sampleRate / samplesPerPixel);
3615
+ }
3614
3616
  // Annotate the CommonJS export names for ESM import in node:
3615
3617
  0 && (module.exports = {
3616
3618
  AudioPosition,
@@ -3621,9 +3623,12 @@ var TrackMenu = ({ items: itemsProp }) => {
3621
3623
  BaseCheckboxWrapper,
3622
3624
  BaseControlButton,
3623
3625
  BaseInput,
3626
+ BaseInputSmall,
3624
3627
  BaseLabel,
3625
3628
  BaseSelect,
3629
+ BaseSelectSmall,
3626
3630
  BaseSlider,
3631
+ BeatsAndBarsProvider,
3627
3632
  Button,
3628
3633
  ButtonGroup,
3629
3634
  CLIP_BOUNDARY_WIDTH,
@@ -3677,6 +3682,7 @@ var TrackMenu = ({ items: itemsProp }) => {
3677
3682
  darkTheme,
3678
3683
  defaultTheme,
3679
3684
  formatTime,
3685
+ getScaleInfo,
3680
3686
  isWaveformGradient,
3681
3687
  parseTime,
3682
3688
  pixelsToSamples,
@@ -3685,6 +3691,7 @@ var TrackMenu = ({ items: itemsProp }) => {
3685
3691
  samplesToSeconds,
3686
3692
  secondsToPixels,
3687
3693
  secondsToSamples,
3694
+ useBeatsAndBars,
3688
3695
  useClipViewportOrigin,
3689
3696
  useDevicePixelRatio,
3690
3697
  usePlaylistInfo,