@waveform-playlist/ui-components 9.1.2 → 9.2.1

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