@waveform-playlist/ui-components 9.1.1 → 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.mjs CHANGED
@@ -781,7 +781,6 @@ var Channel = (props) => {
781
781
  const clipOriginX = useClipViewportOrigin();
782
782
  const visibleChunkIndices = useVisibleChunkIndices(length, MAX_CANVAS_WIDTH, clipOriginX);
783
783
  useEffect3(() => {
784
- const tDraw = performance.now();
785
784
  const step = barWidth + barGap;
786
785
  for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
787
786
  const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;
@@ -817,9 +816,6 @@ var Channel = (props) => {
817
816
  }
818
817
  }
819
818
  }
820
- console.log(
821
- `[waveform] draw ch${index}: ${canvasMapRef.current.size} chunks, ${(performance.now() - tDraw).toFixed(1)}ms`
822
- );
823
819
  }, [
824
820
  canvasMapRef,
825
821
  data,
@@ -1105,6 +1101,7 @@ var FadeOverlay = ({
1105
1101
  };
1106
1102
 
1107
1103
  // src/components/Clip.tsx
1104
+ import { clipPixelWidth } from "@waveform-playlist/core";
1108
1105
  import { Fragment, jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
1109
1106
  var ClipContainer = styled13.div.attrs((props) => ({
1110
1107
  style: props.$isOverlay ? {} : {
@@ -1118,13 +1115,8 @@ var ClipContainer = styled13.div.attrs((props) => ({
1118
1115
  width: ${(props) => props.$isOverlay ? `${props.$width}px` : "auto"};
1119
1116
  display: flex;
1120
1117
  flex-direction: column;
1121
- background: rgba(255, 255, 255, 0.05);
1122
1118
  z-index: 10; /* Above progress overlay (z-index: 2) but below controls/playhead */
1123
1119
  pointer-events: none; /* Let clicks pass through to ClickOverlay for playhead positioning */
1124
-
1125
- &:hover {
1126
- background: rgba(255, 255, 255, 0.08);
1127
- }
1128
1120
  `;
1129
1121
  var ChannelsWrapper = styled13.div`
1130
1122
  flex: 1;
@@ -1154,8 +1146,7 @@ var Clip = ({
1154
1146
  touchOptimized = false
1155
1147
  }) => {
1156
1148
  const left = Math.floor(startSample / samplesPerPixel);
1157
- const endPixel = Math.floor((startSample + durationSamples) / samplesPerPixel);
1158
- const width = endPixel - left;
1149
+ const width = clipPixelWidth(startSample, durationSamples, samplesPerPixel);
1159
1150
  const enableDrag = showHeader && !disableHeaderDrag && !isOverlay;
1160
1151
  const draggableId = `clip-${trackIndex}-${clipIndex}`;
1161
1152
  const {
@@ -1164,20 +1155,20 @@ var Clip = ({
1164
1155
  isDragSource
1165
1156
  } = useDraggable({
1166
1157
  id: draggableId,
1167
- data: { clipId, trackIndex, clipIndex },
1158
+ data: { clipId, trackIndex, clipIndex, startSample, durationSamples },
1168
1159
  disabled: !enableDrag
1169
1160
  });
1170
1161
  const leftBoundaryId = `clip-boundary-left-${trackIndex}-${clipIndex}`;
1171
1162
  const { ref: leftBoundaryRef, isDragSource: isLeftBoundaryDragging } = useDraggable({
1172
1163
  id: leftBoundaryId,
1173
- data: { clipId, trackIndex, clipIndex, boundary: "left" },
1164
+ data: { clipId, trackIndex, clipIndex, boundary: "left", startSample, durationSamples },
1174
1165
  disabled: !enableDrag,
1175
1166
  feedback: "none"
1176
1167
  });
1177
1168
  const rightBoundaryId = `clip-boundary-right-${trackIndex}-${clipIndex}`;
1178
1169
  const { ref: rightBoundaryRef, isDragSource: isRightBoundaryDragging } = useDraggable({
1179
1170
  id: rightBoundaryId,
1180
- data: { clipId, trackIndex, clipIndex, boundary: "right" },
1171
+ data: { clipId, trackIndex, clipIndex, boundary: "right", startSample, durationSamples },
1181
1172
  disabled: !enableDrag,
1182
1173
  feedback: "none"
1183
1174
  });
@@ -1362,7 +1353,6 @@ var PianoRollChannel = ({
1362
1353
  }, [midiNotes]);
1363
1354
  const color = isSelected ? selectedNoteColor : noteColor;
1364
1355
  useEffect4(() => {
1365
- const tDraw = performance.now();
1366
1356
  const noteRange = maxMidi - minMidi + 1;
1367
1357
  const noteHeight = Math.max(2, waveHeight / noteRange);
1368
1358
  const pixelsPerSecond = sampleRate / samplesPerPixel;
@@ -1394,9 +1384,6 @@ var PianoRollChannel = ({
1394
1384
  }
1395
1385
  ctx.globalAlpha = 1;
1396
1386
  }
1397
- console.log(
1398
- `[piano-roll] draw ch${index}: ${canvasMapRef.current.size} chunks, ${midiNotes.length} notes, ${(performance.now() - tDraw).toFixed(1)}ms`
1399
- );
1400
1387
  }, [
1401
1388
  canvasMapRef,
1402
1389
  midiNotes,
@@ -2036,7 +2023,7 @@ function clockFormat(seconds, decimals) {
2036
2023
  const hours = Math.floor(seconds / 3600) % 24;
2037
2024
  const minutes = Math.floor(seconds / 60) % 60;
2038
2025
  const secs = (seconds % 60).toFixed(decimals);
2039
- return String(hours).padStart(2, "0") + ":" + String(minutes).padStart(2, "0") + ":" + secs.padStart(decimals + 3, "0");
2026
+ return String(hours).padStart(2, "0") + ":" + String(minutes).padStart(2, "0") + ":" + secs.padStart(decimals > 0 ? decimals + 3 : 2, "0");
2040
2027
  }
2041
2028
  function formatTime(seconds, format) {
2042
2029
  switch (format) {
@@ -2188,13 +2175,43 @@ var SelectionTimeInputs = ({
2188
2175
  ] });
2189
2176
  };
2190
2177
 
2191
- // src/contexts/DevicePixelRatio.tsx
2192
- import { useState as useState4, createContext as createContext3, useContext as useContext3 } from "react";
2178
+ // src/contexts/BeatsAndBars.tsx
2179
+ import { createContext as createContext3, useContext as useContext3, useMemo as useMemo3 } from "react";
2180
+ import { ticksPerBeat, ticksPerBar } from "@waveform-playlist/core";
2193
2181
  import { jsx as jsx19 } from "react/jsx-runtime";
2182
+ var BeatsAndBarsContext = createContext3(null);
2183
+ function BeatsAndBarsProvider({
2184
+ bpm,
2185
+ timeSignature,
2186
+ snapTo,
2187
+ children
2188
+ }) {
2189
+ const [numerator, denominator] = timeSignature;
2190
+ const value = useMemo3(() => {
2191
+ const ts = [numerator, denominator];
2192
+ const tpBeat = ticksPerBeat(ts);
2193
+ const tpBar = ticksPerBar(ts);
2194
+ return {
2195
+ bpm,
2196
+ timeSignature: ts,
2197
+ snapTo,
2198
+ ticksPerBeat: tpBeat,
2199
+ ticksPerBar: tpBar
2200
+ };
2201
+ }, [bpm, numerator, denominator, snapTo]);
2202
+ return /* @__PURE__ */ jsx19(BeatsAndBarsContext.Provider, { value, children });
2203
+ }
2204
+ function useBeatsAndBars() {
2205
+ return useContext3(BeatsAndBarsContext);
2206
+ }
2207
+
2208
+ // src/contexts/DevicePixelRatio.tsx
2209
+ import { useState as useState4, createContext as createContext4, useContext as useContext4 } from "react";
2210
+ import { jsx as jsx20 } from "react/jsx-runtime";
2194
2211
  function getScale() {
2195
2212
  return window.devicePixelRatio;
2196
2213
  }
2197
- var DevicePixelRatioContext = createContext3(getScale());
2214
+ var DevicePixelRatioContext = createContext4(getScale());
2198
2215
  var DevicePixelRatioProvider = ({ children }) => {
2199
2216
  const [scale, setScale] = useState4(getScale());
2200
2217
  matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
@@ -2204,13 +2221,13 @@ var DevicePixelRatioProvider = ({ children }) => {
2204
2221
  },
2205
2222
  { once: true }
2206
2223
  );
2207
- return /* @__PURE__ */ jsx19(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2224
+ return /* @__PURE__ */ jsx20(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2208
2225
  };
2209
- var useDevicePixelRatio = () => useContext3(DevicePixelRatioContext);
2226
+ var useDevicePixelRatio = () => useContext4(DevicePixelRatioContext);
2210
2227
 
2211
2228
  // src/contexts/PlaylistInfo.tsx
2212
- import { createContext as createContext4, useContext as useContext4 } from "react";
2213
- var PlaylistInfoContext = createContext4({
2229
+ import { createContext as createContext5, useContext as useContext5 } from "react";
2230
+ var PlaylistInfoContext = createContext5({
2214
2231
  sampleRate: 48e3,
2215
2232
  samplesPerPixel: 1e3,
2216
2233
  zoomLevels: [1e3, 1500, 2e3, 2500],
@@ -2224,26 +2241,26 @@ var PlaylistInfoContext = createContext4({
2224
2241
  barWidth: 1,
2225
2242
  barGap: 0
2226
2243
  });
2227
- var usePlaylistInfo = () => useContext4(PlaylistInfoContext);
2244
+ var usePlaylistInfo = () => useContext5(PlaylistInfoContext);
2228
2245
 
2229
2246
  // src/contexts/Theme.tsx
2230
- import { useContext as useContext5 } from "react";
2247
+ import { useContext as useContext6 } from "react";
2231
2248
  import { ThemeContext } from "styled-components";
2232
- var useTheme2 = () => useContext5(ThemeContext);
2249
+ var useTheme2 = () => useContext6(ThemeContext);
2233
2250
 
2234
2251
  // src/contexts/TrackControls.tsx
2235
- import { createContext as createContext5, useContext as useContext6, Fragment as Fragment4 } from "react";
2236
- import { jsx as jsx20 } from "react/jsx-runtime";
2237
- var TrackControlsContext = createContext5(/* @__PURE__ */ jsx20(Fragment4, {}));
2238
- var useTrackControls = () => useContext6(TrackControlsContext);
2252
+ import { createContext as createContext6, useContext as useContext7, Fragment as Fragment4 } from "react";
2253
+ import { jsx as jsx21 } from "react/jsx-runtime";
2254
+ var TrackControlsContext = createContext6(/* @__PURE__ */ jsx21(Fragment4, {}));
2255
+ var useTrackControls = () => useContext7(TrackControlsContext);
2239
2256
 
2240
2257
  // src/contexts/Playout.tsx
2241
2258
  import {
2242
2259
  useState as useState5,
2243
- createContext as createContext6,
2244
- useContext as useContext7
2260
+ createContext as createContext7,
2261
+ useContext as useContext8
2245
2262
  } from "react";
2246
- import { jsx as jsx21 } from "react/jsx-runtime";
2263
+ import { jsx as jsx22 } from "react/jsx-runtime";
2247
2264
  var defaultProgress = 0;
2248
2265
  var defaultIsPlaying = false;
2249
2266
  var defaultSelectionStart = 0;
@@ -2254,8 +2271,8 @@ var defaultPlayout = {
2254
2271
  selectionStart: defaultSelectionStart,
2255
2272
  selectionEnd: defaultSelectionEnd
2256
2273
  };
2257
- var PlayoutStatusContext = createContext6(defaultPlayout);
2258
- var PlayoutStatusUpdateContext = createContext6({
2274
+ var PlayoutStatusContext = createContext7(defaultPlayout);
2275
+ var PlayoutStatusUpdateContext = createContext7({
2259
2276
  setIsPlaying: () => {
2260
2277
  },
2261
2278
  setProgress: () => {
@@ -2272,16 +2289,16 @@ var PlayoutProvider = ({ children }) => {
2272
2289
  setSelectionStart(start);
2273
2290
  setSelectionEnd(end);
2274
2291
  };
2275
- return /* @__PURE__ */ jsx21(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ jsx21(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2292
+ return /* @__PURE__ */ jsx22(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ jsx22(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2276
2293
  };
2277
- var usePlayoutStatus = () => useContext7(PlayoutStatusContext);
2278
- var usePlayoutStatusUpdate = () => useContext7(PlayoutStatusUpdateContext);
2294
+ var usePlayoutStatus = () => useContext8(PlayoutStatusContext);
2295
+ var usePlayoutStatusUpdate = () => useContext8(PlayoutStatusUpdateContext);
2279
2296
 
2280
2297
  // src/components/SpectrogramChannel.tsx
2281
2298
  import { useRef as useRef6, useEffect as useEffect8 } from "react";
2282
2299
  import styled20 from "styled-components";
2283
2300
  import { MAX_CANVAS_WIDTH as MAX_CANVAS_WIDTH3 } from "@waveform-playlist/core";
2284
- import { jsx as jsx22 } from "react/jsx-runtime";
2301
+ import { jsx as jsx23 } from "react/jsx-runtime";
2285
2302
  var LINEAR_FREQUENCY_SCALE = (f, minF, maxF) => (f - minF) / (maxF - minF);
2286
2303
  var Wrapper4 = styled20.div.attrs((props) => ({
2287
2304
  style: {
@@ -2518,7 +2535,7 @@ var SpectrogramChannel = ({
2518
2535
  const canvases = visibleChunkIndices.map((i) => {
2519
2536
  const chunkLeft = i * MAX_CANVAS_WIDTH3;
2520
2537
  const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH3);
2521
- return /* @__PURE__ */ jsx22(
2538
+ return /* @__PURE__ */ jsx23(
2522
2539
  SpectrogramCanvas,
2523
2540
  {
2524
2541
  $cssWidth: currentWidth,
@@ -2532,11 +2549,11 @@ var SpectrogramChannel = ({
2532
2549
  `${length}-${i}`
2533
2550
  );
2534
2551
  });
2535
- return /* @__PURE__ */ jsx22(Wrapper4, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2552
+ return /* @__PURE__ */ jsx23(Wrapper4, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2536
2553
  };
2537
2554
 
2538
2555
  // src/components/SmartChannel.tsx
2539
- import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs9 } from "react/jsx-runtime";
2556
+ import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs9 } from "react/jsx-runtime";
2540
2557
  var SmartChannel = ({
2541
2558
  isSelected,
2542
2559
  transparentBackground,
@@ -2570,7 +2587,7 @@ var SmartChannel = ({
2570
2587
  const drawMode = theme?.waveformDrawMode || "inverted";
2571
2588
  const hasSpectrogram = spectrogramData || spectrogramWorkerApi;
2572
2589
  if (renderMode === "spectrogram" && hasSpectrogram) {
2573
- return /* @__PURE__ */ jsx23(
2590
+ return /* @__PURE__ */ jsx24(
2574
2591
  SpectrogramChannel,
2575
2592
  {
2576
2593
  index: props.index,
@@ -2592,7 +2609,7 @@ var SmartChannel = ({
2592
2609
  if (renderMode === "both" && hasSpectrogram) {
2593
2610
  const halfHeight = Math.floor(waveHeight / 2);
2594
2611
  return /* @__PURE__ */ jsxs9(Fragment5, { children: [
2595
- /* @__PURE__ */ jsx23(
2612
+ /* @__PURE__ */ jsx24(
2596
2613
  SpectrogramChannel,
2597
2614
  {
2598
2615
  index: props.index * 2,
@@ -2611,7 +2628,7 @@ var SmartChannel = ({
2611
2628
  onCanvasesReady: spectrogramOnCanvasesReady
2612
2629
  }
2613
2630
  ),
2614
- /* @__PURE__ */ jsx23(
2631
+ /* @__PURE__ */ jsx24(
2615
2632
  "div",
2616
2633
  {
2617
2634
  style: {
@@ -2620,7 +2637,7 @@ var SmartChannel = ({
2620
2637
  width: props.length,
2621
2638
  height: halfHeight
2622
2639
  },
2623
- children: /* @__PURE__ */ jsx23(
2640
+ children: /* @__PURE__ */ jsx24(
2624
2641
  Channel,
2625
2642
  {
2626
2643
  ...props,
@@ -2640,7 +2657,7 @@ var SmartChannel = ({
2640
2657
  ] });
2641
2658
  }
2642
2659
  if (renderMode === "piano-roll") {
2643
- return /* @__PURE__ */ jsx23(
2660
+ return /* @__PURE__ */ jsx24(
2644
2661
  PianoRollChannel,
2645
2662
  {
2646
2663
  index: props.index,
@@ -2659,7 +2676,7 @@ var SmartChannel = ({
2659
2676
  }
2660
2677
  );
2661
2678
  }
2662
- return /* @__PURE__ */ jsx23(
2679
+ return /* @__PURE__ */ jsx24(
2663
2680
  Channel,
2664
2681
  {
2665
2682
  ...props,
@@ -2678,7 +2695,7 @@ var SmartChannel = ({
2678
2695
  // src/components/SpectrogramLabels.tsx
2679
2696
  import { useRef as useRef7, useLayoutEffect as useLayoutEffect2 } from "react";
2680
2697
  import styled21 from "styled-components";
2681
- import { jsx as jsx24 } from "react/jsx-runtime";
2698
+ import { jsx as jsx25 } from "react/jsx-runtime";
2682
2699
  var LABELS_WIDTH = 72;
2683
2700
  var LabelsStickyWrapper = styled21.div`
2684
2701
  position: sticky;
@@ -2771,7 +2788,7 @@ var SpectrogramLabels = ({
2771
2788
  spectrogramHeight,
2772
2789
  clipHeaderOffset
2773
2790
  ]);
2774
- return /* @__PURE__ */ jsx24(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ jsx24(
2791
+ return /* @__PURE__ */ jsx25(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ jsx25(
2775
2792
  "canvas",
2776
2793
  {
2777
2794
  ref: canvasRef,
@@ -2787,41 +2804,14 @@ var SpectrogramLabels = ({
2787
2804
  };
2788
2805
 
2789
2806
  // src/components/SmartScale.tsx
2790
- import { useContext as useContext9 } from "react";
2807
+ import React19, { useContext as useContext10, useMemo as useMemo4 } from "react";
2808
+ import styled23 from "styled-components";
2791
2809
 
2792
2810
  // src/components/TimeScale.tsx
2793
- import React17, { useLayoutEffect as useLayoutEffect3, useContext as useContext8, useMemo as useMemo3 } from "react";
2811
+ import { useLayoutEffect as useLayoutEffect3, useContext as useContext9 } from "react";
2794
2812
  import styled22, { withTheme as withTheme2 } from "styled-components";
2795
-
2796
- // src/utils/conversions.ts
2797
- function samplesToSeconds(samples, sampleRate) {
2798
- return samples / sampleRate;
2799
- }
2800
- function secondsToSamples(seconds, sampleRate) {
2801
- return Math.ceil(seconds * sampleRate);
2802
- }
2803
- function samplesToPixels(samples, samplesPerPixel) {
2804
- return Math.floor(samples / samplesPerPixel);
2805
- }
2806
- function pixelsToSamples(pixels, samplesPerPixel) {
2807
- return Math.floor(pixels * samplesPerPixel);
2808
- }
2809
- function pixelsToSeconds(pixels, samplesPerPixel, sampleRate) {
2810
- return pixels * samplesPerPixel / sampleRate;
2811
- }
2812
- function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
2813
- return Math.ceil(seconds * sampleRate / samplesPerPixel);
2814
- }
2815
-
2816
- // src/components/TimeScale.tsx
2817
2813
  import { MAX_CANVAS_WIDTH as MAX_CANVAS_WIDTH4 } from "@waveform-playlist/core";
2818
- import { jsx as jsx25, jsxs as jsxs10 } from "react/jsx-runtime";
2819
- function formatTime2(milliseconds) {
2820
- const seconds = Math.floor(milliseconds / 1e3);
2821
- const s = seconds % 60;
2822
- const m = (seconds - s) / 60;
2823
- return `${m}:${String(s).padStart(2, "0")}`;
2824
- }
2814
+ import { jsx as jsx26, jsxs as jsxs10 } from "react/jsx-runtime";
2825
2815
  var PlaylistTimeScaleScroll = styled22.div.attrs((props) => ({
2826
2816
  style: {
2827
2817
  width: `${props.$cssWidth}px`,
@@ -2843,70 +2833,20 @@ var TimeTickChunk = styled22.canvas.attrs((props) => ({
2843
2833
  position: absolute;
2844
2834
  bottom: 0;
2845
2835
  `;
2846
- var TimeStamp = styled22.div.attrs((props) => ({
2847
- style: {
2848
- left: `${props.$left + 4}px`
2849
- // Offset 4px to the right of the tick
2850
- }
2851
- }))`
2852
- position: absolute;
2853
- font-size: 0.75rem; /* Smaller font to prevent overflow */
2854
- white-space: nowrap; /* Prevent text wrapping */
2855
- color: ${(props) => props.theme.timeColor}; /* Use theme color instead of inheriting */
2856
- `;
2857
2836
  var TimeScale = (props) => {
2858
2837
  const {
2859
2838
  theme: { timeColor },
2860
- duration,
2861
- marker,
2862
- bigStep,
2863
- secondStep,
2864
- renderTimestamp
2839
+ tickData
2865
2840
  } = props;
2866
2841
  const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2867
- const { sampleRate, samplesPerPixel, timeScaleHeight } = useContext8(PlaylistInfoContext);
2842
+ const { timeScaleHeight } = useContext9(PlaylistInfoContext);
2868
2843
  const devicePixelRatio = useDevicePixelRatio();
2869
- const { widthX, canvasInfo, timeMarkersWithPositions } = useMemo3(() => {
2870
- const nextCanvasInfo = /* @__PURE__ */ new Map();
2871
- const nextMarkers = [];
2872
- const nextWidthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
2873
- const pixPerSec = sampleRate / samplesPerPixel;
2874
- let counter = 0;
2875
- for (let i = 0; i < nextWidthX; i += pixPerSec * secondStep / 1e3) {
2876
- const pix = Math.floor(i);
2877
- if (counter % marker === 0) {
2878
- const timeMs = counter;
2879
- const timestamp = formatTime2(timeMs);
2880
- const element = renderTimestamp ? /* @__PURE__ */ jsx25(React17.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ jsx25(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2881
- nextMarkers.push({ pix, element });
2882
- nextCanvasInfo.set(pix, timeScaleHeight);
2883
- } else if (counter % bigStep === 0) {
2884
- nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
2885
- } else if (counter % secondStep === 0) {
2886
- nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 5));
2887
- }
2888
- counter += secondStep;
2889
- }
2890
- return {
2891
- widthX: nextWidthX,
2892
- canvasInfo: nextCanvasInfo,
2893
- timeMarkersWithPositions: nextMarkers
2894
- };
2895
- }, [
2896
- duration,
2897
- samplesPerPixel,
2898
- sampleRate,
2899
- marker,
2900
- bigStep,
2901
- secondStep,
2902
- renderTimestamp,
2903
- timeScaleHeight
2904
- ]);
2844
+ const { widthX, canvasInfo, timeMarkersWithPositions } = tickData;
2905
2845
  const visibleChunkIndices = useVisibleChunkIndices(widthX, MAX_CANVAS_WIDTH4);
2906
2846
  const visibleChunks = visibleChunkIndices.map((i) => {
2907
2847
  const chunkLeft = i * MAX_CANVAS_WIDTH4;
2908
2848
  const chunkWidth = Math.min(widthX - chunkLeft, MAX_CANVAS_WIDTH4);
2909
- return /* @__PURE__ */ jsx25(
2849
+ return /* @__PURE__ */ jsx26(
2910
2850
  TimeTickChunk,
2911
2851
  {
2912
2852
  $cssWidth: chunkWidth,
@@ -2941,15 +2881,7 @@ var TimeScale = (props) => {
2941
2881
  ctx.fillRect(localX, scaleY, 1, scaleHeight);
2942
2882
  }
2943
2883
  }
2944
- }, [
2945
- canvasMapRef,
2946
- duration,
2947
- devicePixelRatio,
2948
- timeColor,
2949
- timeScaleHeight,
2950
- canvasInfo,
2951
- visibleChunkIndices
2952
- ]);
2884
+ }, [canvasMapRef, devicePixelRatio, timeColor, timeScaleHeight, canvasInfo, visibleChunkIndices]);
2953
2885
  return /* @__PURE__ */ jsxs10(PlaylistTimeScaleScroll, { $cssWidth: widthX, $timeScaleHeight: timeScaleHeight, children: [
2954
2886
  visibleMarkers,
2955
2887
  visibleChunks
@@ -2958,64 +2890,22 @@ var TimeScale = (props) => {
2958
2890
  var StyledTimeScale = withTheme2(TimeScale);
2959
2891
 
2960
2892
  // src/components/SmartScale.tsx
2961
- import { jsx as jsx26 } from "react/jsx-runtime";
2893
+ import {
2894
+ PPQN,
2895
+ ticksToSamples,
2896
+ ticksToBarBeatLabel,
2897
+ samplesToPixels,
2898
+ secondsToPixels
2899
+ } from "@waveform-playlist/core";
2900
+ import { jsx as jsx27 } from "react/jsx-runtime";
2962
2901
  var timeinfo = /* @__PURE__ */ new Map([
2963
- [
2964
- 700,
2965
- {
2966
- marker: 1e3,
2967
- bigStep: 500,
2968
- smallStep: 100
2969
- }
2970
- ],
2971
- [
2972
- 1500,
2973
- {
2974
- marker: 2e3,
2975
- bigStep: 1e3,
2976
- smallStep: 200
2977
- }
2978
- ],
2979
- [
2980
- 2500,
2981
- {
2982
- marker: 2e3,
2983
- bigStep: 1e3,
2984
- smallStep: 500
2985
- }
2986
- ],
2987
- [
2988
- 5e3,
2989
- {
2990
- marker: 5e3,
2991
- bigStep: 1e3,
2992
- smallStep: 500
2993
- }
2994
- ],
2995
- [
2996
- 1e4,
2997
- {
2998
- marker: 1e4,
2999
- bigStep: 5e3,
3000
- smallStep: 1e3
3001
- }
3002
- ],
3003
- [
3004
- 12e3,
3005
- {
3006
- marker: 15e3,
3007
- bigStep: 5e3,
3008
- smallStep: 1e3
3009
- }
3010
- ],
3011
- [
3012
- Infinity,
3013
- {
3014
- marker: 3e4,
3015
- bigStep: 1e4,
3016
- smallStep: 5e3
3017
- }
3018
- ]
2902
+ [700, { marker: 1e3, bigStep: 500, smallStep: 100 }],
2903
+ [1500, { marker: 2e3, bigStep: 1e3, smallStep: 200 }],
2904
+ [2500, { marker: 2e3, bigStep: 1e3, smallStep: 500 }],
2905
+ [5e3, { marker: 5e3, bigStep: 1e3, smallStep: 500 }],
2906
+ [1e4, { marker: 1e4, bigStep: 5e3, smallStep: 1e3 }],
2907
+ [12e3, { marker: 15e3, bigStep: 5e3, smallStep: 1e3 }],
2908
+ [Infinity, { marker: 3e4, bigStep: 1e4, smallStep: 5e3 }]
3019
2909
  ]);
3020
2910
  function getScaleInfo(samplesPerPixel) {
3021
2911
  const keys = timeinfo.keys();
@@ -3031,25 +2921,113 @@ function getScaleInfo(samplesPerPixel) {
3031
2921
  }
3032
2922
  return config;
3033
2923
  }
3034
- var SmartScale = ({ renderTimestamp }) => {
3035
- const { samplesPerPixel, duration } = useContext9(PlaylistInfoContext);
3036
- let config = getScaleInfo(samplesPerPixel);
3037
- return /* @__PURE__ */ jsx26(
3038
- StyledTimeScale,
3039
- {
3040
- marker: config.marker,
3041
- bigStep: config.bigStep,
3042
- secondStep: config.smallStep,
3043
- duration,
3044
- renderTimestamp
2924
+ function formatTime2(milliseconds) {
2925
+ const seconds = Math.floor(milliseconds / 1e3);
2926
+ const s = seconds % 60;
2927
+ const m = (seconds - s) / 60;
2928
+ return `${m}:${String(s).padStart(2, "0")}`;
2929
+ }
2930
+ var TimeStamp = styled23.div.attrs((props) => ({
2931
+ style: {
2932
+ left: `${props.$left + 4}px`
2933
+ // Offset 4px to the right of the tick
2934
+ }
2935
+ }))`
2936
+ position: absolute;
2937
+ font-size: 0.75rem; /* Smaller font to prevent overflow */
2938
+ white-space: nowrap; /* Prevent text wrapping */
2939
+ color: ${(props) => props.theme.timeColor}; /* Use theme color instead of inheriting */
2940
+ `;
2941
+ var SmartScale = ({ renderTick }) => {
2942
+ const { samplesPerPixel, sampleRate, duration, timeScaleHeight } = useContext10(PlaylistInfoContext);
2943
+ const beatsAndBars = useBeatsAndBars();
2944
+ const tickData = useMemo4(() => {
2945
+ const widthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
2946
+ if (beatsAndBars) {
2947
+ const { bpm, timeSignature, ticksPerBar: tpBar, ticksPerBeat: tpBeat } = beatsAndBars;
2948
+ const canvasInfo2 = /* @__PURE__ */ new Map();
2949
+ const timeMarkersWithPositions2 = [];
2950
+ const durationSeconds = duration / 1e3;
2951
+ const totalTicks = Math.ceil(durationSeconds * bpm * PPQN / 60);
2952
+ const pixelsPerBeat = ticksToSamples(tpBeat, bpm, sampleRate) / samplesPerPixel;
2953
+ const pixelsPerBar = ticksToSamples(tpBar, bpm, sampleRate) / samplesPerPixel;
2954
+ const MIN_TICK_PX = 10;
2955
+ const MIN_LABEL_PX = 30;
2956
+ let tickStep;
2957
+ if (pixelsPerBeat >= MIN_TICK_PX) {
2958
+ tickStep = tpBeat;
2959
+ } else if (pixelsPerBar >= MIN_TICK_PX) {
2960
+ tickStep = tpBar;
2961
+ } else {
2962
+ const barsPerTick = Math.ceil(MIN_TICK_PX / pixelsPerBar);
2963
+ tickStep = tpBar * barsPerTick;
2964
+ }
2965
+ let labelStep;
2966
+ if (pixelsPerBeat >= MIN_LABEL_PX) {
2967
+ labelStep = tpBeat;
2968
+ } else if (pixelsPerBar >= MIN_LABEL_PX) {
2969
+ labelStep = tpBar;
2970
+ } else {
2971
+ const barsPerLabel = Math.ceil(MIN_LABEL_PX / pixelsPerBar);
2972
+ labelStep = tpBar * barsPerLabel;
2973
+ }
2974
+ for (let tick = 0; tick <= totalTicks; tick += tickStep) {
2975
+ const samples = ticksToSamples(tick, bpm, sampleRate);
2976
+ const pix = samplesToPixels(samples, samplesPerPixel);
2977
+ if (pix >= widthX) break;
2978
+ const isBarLine = tick % tpBar === 0;
2979
+ const isLabelTick = tick % labelStep === 0;
2980
+ const tickHeight = isBarLine ? timeScaleHeight : isLabelTick ? Math.floor(timeScaleHeight / 2) : Math.floor(timeScaleHeight / 5);
2981
+ canvasInfo2.set(pix, tickHeight);
2982
+ if (isLabelTick) {
2983
+ const label = ticksToBarBeatLabel(tick, timeSignature);
2984
+ const element = renderTick ? /* @__PURE__ */ jsx27(React19.Fragment, { children: renderTick(label, pix) }, `bb-${tick}`) : /* @__PURE__ */ jsx27(
2985
+ "div",
2986
+ {
2987
+ style: {
2988
+ position: "absolute",
2989
+ left: `${pix + 4}px`,
2990
+ fontSize: "0.75rem",
2991
+ whiteSpace: "nowrap"
2992
+ },
2993
+ children: label
2994
+ },
2995
+ `bb-${tick}`
2996
+ );
2997
+ timeMarkersWithPositions2.push({ pix, element });
2998
+ }
2999
+ }
3000
+ return { widthX, canvasInfo: canvasInfo2, timeMarkersWithPositions: timeMarkersWithPositions2 };
3045
3001
  }
3046
- );
3002
+ const config = getScaleInfo(samplesPerPixel);
3003
+ const { marker, bigStep, smallStep } = config;
3004
+ const canvasInfo = /* @__PURE__ */ new Map();
3005
+ const timeMarkersWithPositions = [];
3006
+ const pixPerSec = sampleRate / samplesPerPixel;
3007
+ let counter = 0;
3008
+ for (let i = 0; i < widthX; i += pixPerSec * smallStep / 1e3) {
3009
+ const pix = Math.floor(i);
3010
+ if (counter % marker === 0) {
3011
+ const timestamp = formatTime2(counter);
3012
+ const element = renderTick ? /* @__PURE__ */ jsx27(React19.Fragment, { children: renderTick(timestamp, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ jsx27(TimeStamp, { $left: pix, children: timestamp }, timestamp);
3013
+ timeMarkersWithPositions.push({ pix, element });
3014
+ canvasInfo.set(pix, timeScaleHeight);
3015
+ } else if (counter % bigStep === 0) {
3016
+ canvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
3017
+ } else if (counter % smallStep === 0) {
3018
+ canvasInfo.set(pix, Math.floor(timeScaleHeight / 5));
3019
+ }
3020
+ counter += smallStep;
3021
+ }
3022
+ return { widthX, canvasInfo, timeMarkersWithPositions };
3023
+ }, [beatsAndBars, duration, samplesPerPixel, sampleRate, timeScaleHeight, renderTick]);
3024
+ return /* @__PURE__ */ jsx27(StyledTimeScale, { tickData });
3047
3025
  };
3048
3026
 
3049
3027
  // src/components/TimeFormatSelect.tsx
3050
- import styled23 from "styled-components";
3051
- import { jsx as jsx27 } from "react/jsx-runtime";
3052
- var SelectWrapper = styled23.div`
3028
+ import styled24 from "styled-components";
3029
+ import { jsx as jsx28 } from "react/jsx-runtime";
3030
+ var SelectWrapper = styled24.div`
3053
3031
  display: inline-flex;
3054
3032
  align-items: center;
3055
3033
  gap: 0.5rem;
@@ -3071,7 +3049,7 @@ var TimeFormatSelect = ({
3071
3049
  const handleChange = (e) => {
3072
3050
  onChange(e.target.value);
3073
3051
  };
3074
- return /* @__PURE__ */ jsx27(SelectWrapper, { className, children: /* @__PURE__ */ jsx27(
3052
+ return /* @__PURE__ */ jsx28(SelectWrapper, { className, children: /* @__PURE__ */ jsx28(
3075
3053
  BaseSelect,
3076
3054
  {
3077
3055
  className: "time-format",
@@ -3079,15 +3057,15 @@ var TimeFormatSelect = ({
3079
3057
  onChange: handleChange,
3080
3058
  disabled,
3081
3059
  "aria-label": "Time format selection",
3082
- children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ jsx27("option", { value: option.value, children: option.label }, option.value))
3060
+ children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ jsx28("option", { value: option.value, children: option.label }, option.value))
3083
3061
  }
3084
3062
  ) });
3085
3063
  };
3086
3064
 
3087
3065
  // src/components/Track.tsx
3088
- import styled24 from "styled-components";
3089
- import { jsx as jsx28 } from "react/jsx-runtime";
3090
- var Container = styled24.div.attrs((props) => ({
3066
+ import styled25 from "styled-components";
3067
+ import { jsx as jsx29 } from "react/jsx-runtime";
3068
+ var Container = styled25.div.attrs((props) => ({
3091
3069
  style: {
3092
3070
  height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
3093
3071
  }
@@ -3095,7 +3073,7 @@ var Container = styled24.div.attrs((props) => ({
3095
3073
  position: relative;
3096
3074
  ${(props) => props.$width !== void 0 && `width: ${props.$width}px;`}
3097
3075
  `;
3098
- var ChannelContainer = styled24.div.attrs((props) => ({
3076
+ var ChannelContainer = styled25.div.attrs((props) => ({
3099
3077
  style: {
3100
3078
  paddingLeft: `${props.$offset || 0}px`
3101
3079
  }
@@ -3117,7 +3095,7 @@ var Track = ({
3117
3095
  isSelected: _isSelected = false
3118
3096
  }) => {
3119
3097
  const { waveHeight } = usePlaylistInfo();
3120
- return /* @__PURE__ */ jsx28(
3098
+ return /* @__PURE__ */ jsx29(
3121
3099
  Container,
3122
3100
  {
3123
3101
  $numChannels: numChannels,
@@ -3125,7 +3103,7 @@ var Track = ({
3125
3103
  $waveHeight: waveHeight,
3126
3104
  $width: width,
3127
3105
  $hasClipHeaders: hasClipHeaders,
3128
- children: /* @__PURE__ */ jsx28(
3106
+ children: /* @__PURE__ */ jsx29(
3129
3107
  ChannelContainer,
3130
3108
  {
3131
3109
  $backgroundColor: backgroundColor,
@@ -3140,8 +3118,8 @@ var Track = ({
3140
3118
  };
3141
3119
 
3142
3120
  // src/components/TrackControls/Button.tsx
3143
- import styled25 from "styled-components";
3144
- var Button = styled25.button.attrs({
3121
+ import styled26 from "styled-components";
3122
+ var Button = styled26.button.attrs({
3145
3123
  type: "button"
3146
3124
  })`
3147
3125
  display: inline-block;
@@ -3216,8 +3194,8 @@ var Button = styled25.button.attrs({
3216
3194
  `;
3217
3195
 
3218
3196
  // src/components/TrackControls/ButtonGroup.tsx
3219
- import styled26 from "styled-components";
3220
- var ButtonGroup = styled26.div`
3197
+ import styled27 from "styled-components";
3198
+ var ButtonGroup = styled27.div`
3221
3199
  margin-bottom: 0.3rem;
3222
3200
 
3223
3201
  button:not(:first-child) {
@@ -3232,10 +3210,10 @@ var ButtonGroup = styled26.div`
3232
3210
  `;
3233
3211
 
3234
3212
  // src/components/TrackControls/CloseButton.tsx
3235
- import styled27 from "styled-components";
3213
+ import styled28 from "styled-components";
3236
3214
  import { X as XIcon } from "@phosphor-icons/react";
3237
- import { jsx as jsx29 } from "react/jsx-runtime";
3238
- var StyledCloseButton = styled27.button`
3215
+ import { jsx as jsx30 } from "react/jsx-runtime";
3216
+ var StyledCloseButton = styled28.button`
3239
3217
  position: absolute;
3240
3218
  left: 0;
3241
3219
  top: 0;
@@ -3258,11 +3236,11 @@ var StyledCloseButton = styled27.button`
3258
3236
  color: #dc3545;
3259
3237
  }
3260
3238
  `;
3261
- var CloseButton = ({ onClick, title = "Remove track" }) => /* @__PURE__ */ jsx29(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ jsx29(XIcon, { size: 12, weight: "bold" }) });
3239
+ var CloseButton = ({ onClick, title = "Remove track" }) => /* @__PURE__ */ jsx30(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ jsx30(XIcon, { size: 12, weight: "bold" }) });
3262
3240
 
3263
3241
  // src/components/TrackControls/Controls.tsx
3264
- import styled28 from "styled-components";
3265
- var Controls = styled28.div`
3242
+ import styled29 from "styled-components";
3243
+ var Controls = styled29.div`
3266
3244
  background: transparent;
3267
3245
  width: 100%;
3268
3246
  height: 100%;
@@ -3278,8 +3256,8 @@ var Controls = styled28.div`
3278
3256
  `;
3279
3257
 
3280
3258
  // src/components/TrackControls/Header.tsx
3281
- import styled29 from "styled-components";
3282
- var Header = styled29.header`
3259
+ import styled30 from "styled-components";
3260
+ var Header = styled30.header`
3283
3261
  overflow: hidden;
3284
3262
  height: 26px;
3285
3263
  width: 100%;
@@ -3294,27 +3272,27 @@ var Header = styled29.header`
3294
3272
 
3295
3273
  // src/components/TrackControls/VolumeDownIcon.tsx
3296
3274
  import { SpeakerLowIcon } from "@phosphor-icons/react";
3297
- import { jsx as jsx30 } from "react/jsx-runtime";
3298
- var VolumeDownIcon = (props) => /* @__PURE__ */ jsx30(SpeakerLowIcon, { weight: "light", ...props });
3275
+ import { jsx as jsx31 } from "react/jsx-runtime";
3276
+ var VolumeDownIcon = (props) => /* @__PURE__ */ jsx31(SpeakerLowIcon, { weight: "light", ...props });
3299
3277
 
3300
3278
  // src/components/TrackControls/VolumeUpIcon.tsx
3301
3279
  import { SpeakerHighIcon } from "@phosphor-icons/react";
3302
- import { jsx as jsx31 } from "react/jsx-runtime";
3303
- var VolumeUpIcon = (props) => /* @__PURE__ */ jsx31(SpeakerHighIcon, { weight: "light", ...props });
3280
+ import { jsx as jsx32 } from "react/jsx-runtime";
3281
+ var VolumeUpIcon = (props) => /* @__PURE__ */ jsx32(SpeakerHighIcon, { weight: "light", ...props });
3304
3282
 
3305
3283
  // src/components/TrackControls/TrashIcon.tsx
3306
3284
  import { TrashIcon as PhosphorTrashIcon } from "@phosphor-icons/react";
3307
- import { jsx as jsx32 } from "react/jsx-runtime";
3308
- var TrashIcon = (props) => /* @__PURE__ */ jsx32(PhosphorTrashIcon, { weight: "light", ...props });
3285
+ import { jsx as jsx33 } from "react/jsx-runtime";
3286
+ var TrashIcon = (props) => /* @__PURE__ */ jsx33(PhosphorTrashIcon, { weight: "light", ...props });
3309
3287
 
3310
3288
  // src/components/TrackControls/DotsIcon.tsx
3311
3289
  import { DotsThreeIcon } from "@phosphor-icons/react";
3312
- import { jsx as jsx33 } from "react/jsx-runtime";
3313
- var DotsIcon = (props) => /* @__PURE__ */ jsx33(DotsThreeIcon, { weight: "bold", ...props });
3290
+ import { jsx as jsx34 } from "react/jsx-runtime";
3291
+ var DotsIcon = (props) => /* @__PURE__ */ jsx34(DotsThreeIcon, { weight: "bold", ...props });
3314
3292
 
3315
3293
  // src/components/TrackControls/Slider.tsx
3316
- import styled30 from "styled-components";
3317
- var Slider = styled30(BaseSlider)`
3294
+ import styled31 from "styled-components";
3295
+ var Slider = styled31(BaseSlider)`
3318
3296
  width: 75%;
3319
3297
  height: 5px;
3320
3298
  background: ${(props) => props.theme.sliderTrackColor};
@@ -3366,8 +3344,8 @@ var Slider = styled30(BaseSlider)`
3366
3344
  `;
3367
3345
 
3368
3346
  // src/components/TrackControls/SliderWrapper.tsx
3369
- import styled31 from "styled-components";
3370
- var SliderWrapper = styled31.label`
3347
+ import styled32 from "styled-components";
3348
+ var SliderWrapper = styled32.label`
3371
3349
  width: 100%;
3372
3350
  display: flex;
3373
3351
  justify-content: space-between;
@@ -3378,15 +3356,15 @@ var SliderWrapper = styled31.label`
3378
3356
  `;
3379
3357
 
3380
3358
  // src/components/TrackMenu.tsx
3381
- import React19, { useState as useState6, useEffect as useEffect9, useRef as useRef8, useCallback as useCallback5 } from "react";
3359
+ import React20, { useState as useState6, useEffect as useEffect9, useRef as useRef8, useCallback as useCallback5 } from "react";
3382
3360
  import { createPortal } from "react-dom";
3383
- import styled32 from "styled-components";
3384
- import { jsx as jsx34, jsxs as jsxs11 } from "react/jsx-runtime";
3385
- var MenuContainer = styled32.div`
3361
+ import styled33 from "styled-components";
3362
+ import { jsx as jsx35, jsxs as jsxs11 } from "react/jsx-runtime";
3363
+ var MenuContainer = styled33.div`
3386
3364
  position: relative;
3387
3365
  display: inline-block;
3388
3366
  `;
3389
- var MenuButton = styled32.button`
3367
+ var MenuButton = styled33.button`
3390
3368
  background: none;
3391
3369
  border: none;
3392
3370
  cursor: pointer;
@@ -3402,7 +3380,7 @@ var MenuButton = styled32.button`
3402
3380
  }
3403
3381
  `;
3404
3382
  var DROPDOWN_MIN_WIDTH = 180;
3405
- var Dropdown = styled32.div`
3383
+ var Dropdown = styled33.div`
3406
3384
  position: fixed;
3407
3385
  top: ${(p) => p.$top}px;
3408
3386
  left: ${(p) => p.$left}px;
@@ -3415,7 +3393,7 @@ var Dropdown = styled32.div`
3415
3393
  min-width: ${DROPDOWN_MIN_WIDTH}px;
3416
3394
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
3417
3395
  `;
3418
- var Divider = styled32.hr`
3396
+ var Divider = styled33.hr`
3419
3397
  border: none;
3420
3398
  border-top: 1px solid rgba(128, 128, 128, 0.3);
3421
3399
  margin: 0.35rem 0;
@@ -3479,7 +3457,7 @@ var TrackMenu = ({ items: itemsProp }) => {
3479
3457
  };
3480
3458
  }, [open]);
3481
3459
  return /* @__PURE__ */ jsxs11(MenuContainer, { children: [
3482
- /* @__PURE__ */ jsx34(
3460
+ /* @__PURE__ */ jsx35(
3483
3461
  MenuButton,
3484
3462
  {
3485
3463
  ref: buttonRef,
@@ -3490,19 +3468,19 @@ var TrackMenu = ({ items: itemsProp }) => {
3490
3468
  onMouseDown: (e) => e.stopPropagation(),
3491
3469
  title: "Track menu",
3492
3470
  "aria-label": "Track menu",
3493
- children: /* @__PURE__ */ jsx34(DotsIcon, { size: 16 })
3471
+ children: /* @__PURE__ */ jsx35(DotsIcon, { size: 16 })
3494
3472
  }
3495
3473
  ),
3496
3474
  open && typeof document !== "undefined" && createPortal(
3497
- /* @__PURE__ */ jsx34(
3475
+ /* @__PURE__ */ jsx35(
3498
3476
  Dropdown,
3499
3477
  {
3500
3478
  ref: dropdownRef,
3501
3479
  $top: dropdownPos.top,
3502
3480
  $left: dropdownPos.left,
3503
3481
  onMouseDown: (e) => e.stopPropagation(),
3504
- children: items.map((item, index) => /* @__PURE__ */ jsxs11(React19.Fragment, { children: [
3505
- index > 0 && /* @__PURE__ */ jsx34(Divider, {}),
3482
+ children: items.map((item, index) => /* @__PURE__ */ jsxs11(React20.Fragment, { children: [
3483
+ index > 0 && /* @__PURE__ */ jsx35(Divider, {}),
3506
3484
  item.content
3507
3485
  ] }, item.id))
3508
3486
  }
@@ -3511,6 +3489,26 @@ var TrackMenu = ({ items: itemsProp }) => {
3511
3489
  )
3512
3490
  ] });
3513
3491
  };
3492
+
3493
+ // src/utils/conversions.ts
3494
+ function samplesToSeconds(samples, sampleRate) {
3495
+ return samples / sampleRate;
3496
+ }
3497
+ function secondsToSamples(seconds, sampleRate) {
3498
+ return Math.ceil(seconds * sampleRate);
3499
+ }
3500
+ function samplesToPixels2(samples, samplesPerPixel) {
3501
+ return Math.floor(samples / samplesPerPixel);
3502
+ }
3503
+ function pixelsToSamples(pixels, samplesPerPixel) {
3504
+ return Math.floor(pixels * samplesPerPixel);
3505
+ }
3506
+ function pixelsToSeconds(pixels, samplesPerPixel, sampleRate) {
3507
+ return pixels * samplesPerPixel / sampleRate;
3508
+ }
3509
+ function secondsToPixels2(seconds, samplesPerPixel, sampleRate) {
3510
+ return Math.ceil(seconds * sampleRate / samplesPerPixel);
3511
+ }
3514
3512
  export {
3515
3513
  AudioPosition,
3516
3514
  AutomaticScrollCheckbox,
@@ -3520,9 +3518,12 @@ export {
3520
3518
  BaseCheckboxWrapper,
3521
3519
  BaseControlButton,
3522
3520
  BaseInput,
3521
+ BaseInputSmall,
3523
3522
  BaseLabel,
3524
3523
  BaseSelect,
3524
+ BaseSelectSmall,
3525
3525
  BaseSlider,
3526
+ BeatsAndBarsProvider,
3526
3527
  Button,
3527
3528
  ButtonGroup,
3528
3529
  CLIP_BOUNDARY_WIDTH,
@@ -3576,14 +3577,16 @@ export {
3576
3577
  darkTheme,
3577
3578
  defaultTheme,
3578
3579
  formatTime,
3580
+ getScaleInfo,
3579
3581
  isWaveformGradient,
3580
3582
  parseTime,
3581
3583
  pixelsToSamples,
3582
3584
  pixelsToSeconds,
3583
- samplesToPixels,
3585
+ samplesToPixels2 as samplesToPixels,
3584
3586
  samplesToSeconds,
3585
- secondsToPixels,
3587
+ secondsToPixels2 as secondsToPixels,
3586
3588
  secondsToSamples,
3589
+ useBeatsAndBars,
3587
3590
  useClipViewportOrigin,
3588
3591
  useDevicePixelRatio,
3589
3592
  usePlaylistInfo,