@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.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,
@@ -1214,6 +1215,7 @@ var FadeOverlay = ({
1214
1215
  };
1215
1216
 
1216
1217
  // src/components/Clip.tsx
1218
+ var import_core2 = require("@waveform-playlist/core");
1217
1219
  var import_jsx_runtime10 = require("react/jsx-runtime");
1218
1220
  var ClipContainer = import_styled_components13.default.div.attrs((props) => ({
1219
1221
  style: props.$isOverlay ? {} : {
@@ -1227,13 +1229,8 @@ var ClipContainer = import_styled_components13.default.div.attrs((props) => ({
1227
1229
  width: ${(props) => props.$isOverlay ? `${props.$width}px` : "auto"};
1228
1230
  display: flex;
1229
1231
  flex-direction: column;
1230
- background: rgba(255, 255, 255, 0.05);
1231
1232
  z-index: 10; /* Above progress overlay (z-index: 2) but below controls/playhead */
1232
1233
  pointer-events: none; /* Let clicks pass through to ClickOverlay for playhead positioning */
1233
-
1234
- &:hover {
1235
- background: rgba(255, 255, 255, 0.08);
1236
- }
1237
1234
  `;
1238
1235
  var ChannelsWrapper = import_styled_components13.default.div`
1239
1236
  flex: 1;
@@ -1263,8 +1260,7 @@ var Clip = ({
1263
1260
  touchOptimized = false
1264
1261
  }) => {
1265
1262
  const left = Math.floor(startSample / samplesPerPixel);
1266
- const endPixel = Math.floor((startSample + durationSamples) / samplesPerPixel);
1267
- const width = endPixel - left;
1263
+ const width = (0, import_core2.clipPixelWidth)(startSample, durationSamples, samplesPerPixel);
1268
1264
  const enableDrag = showHeader && !disableHeaderDrag && !isOverlay;
1269
1265
  const draggableId = `clip-${trackIndex}-${clipIndex}`;
1270
1266
  const {
@@ -1273,20 +1269,20 @@ var Clip = ({
1273
1269
  isDragSource
1274
1270
  } = (0, import_react7.useDraggable)({
1275
1271
  id: draggableId,
1276
- data: { clipId, trackIndex, clipIndex },
1272
+ data: { clipId, trackIndex, clipIndex, startSample, durationSamples },
1277
1273
  disabled: !enableDrag
1278
1274
  });
1279
1275
  const leftBoundaryId = `clip-boundary-left-${trackIndex}-${clipIndex}`;
1280
1276
  const { ref: leftBoundaryRef, isDragSource: isLeftBoundaryDragging } = (0, import_react7.useDraggable)({
1281
1277
  id: leftBoundaryId,
1282
- data: { clipId, trackIndex, clipIndex, boundary: "left" },
1278
+ data: { clipId, trackIndex, clipIndex, boundary: "left", startSample, durationSamples },
1283
1279
  disabled: !enableDrag,
1284
1280
  feedback: "none"
1285
1281
  });
1286
1282
  const rightBoundaryId = `clip-boundary-right-${trackIndex}-${clipIndex}`;
1287
1283
  const { ref: rightBoundaryRef, isDragSource: isRightBoundaryDragging } = (0, import_react7.useDraggable)({
1288
1284
  id: rightBoundaryId,
1289
- data: { clipId, trackIndex, clipIndex, boundary: "right" },
1285
+ data: { clipId, trackIndex, clipIndex, boundary: "right", startSample, durationSamples },
1290
1286
  disabled: !enableDrag,
1291
1287
  feedback: "none"
1292
1288
  });
@@ -1416,7 +1412,7 @@ var MasterVolumeControl = ({
1416
1412
  // src/components/PianoRollChannel.tsx
1417
1413
  var import_react8 = require("react");
1418
1414
  var import_styled_components15 = __toESM(require("styled-components"));
1419
- var import_core2 = require("@waveform-playlist/core");
1415
+ var import_core3 = require("@waveform-playlist/core");
1420
1416
  var import_jsx_runtime12 = require("react/jsx-runtime");
1421
1417
  var NoteCanvas = import_styled_components15.default.canvas.attrs((props) => ({
1422
1418
  style: {
@@ -1459,7 +1455,7 @@ var PianoRollChannel = ({
1459
1455
  }) => {
1460
1456
  const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
1461
1457
  const clipOriginX = useClipViewportOrigin();
1462
- const visibleChunkIndices = useVisibleChunkIndices(length, import_core2.MAX_CANVAS_WIDTH, clipOriginX);
1458
+ const visibleChunkIndices = useVisibleChunkIndices(length, import_core3.MAX_CANVAS_WIDTH, clipOriginX);
1463
1459
  const { minMidi, maxMidi } = (0, import_react8.useMemo)(() => {
1464
1460
  if (midiNotes.length === 0) return { minMidi: 0, maxMidi: 127 };
1465
1461
  let min = 127, max = 0;
@@ -1471,12 +1467,11 @@ var PianoRollChannel = ({
1471
1467
  }, [midiNotes]);
1472
1468
  const color = isSelected ? selectedNoteColor : noteColor;
1473
1469
  (0, import_react8.useEffect)(() => {
1474
- const tDraw = performance.now();
1475
1470
  const noteRange = maxMidi - minMidi + 1;
1476
1471
  const noteHeight = Math.max(2, waveHeight / noteRange);
1477
1472
  const pixelsPerSecond = sampleRate / samplesPerPixel;
1478
1473
  for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
1479
- const chunkPixelStart = canvasIdx * import_core2.MAX_CANVAS_WIDTH;
1474
+ const chunkPixelStart = canvasIdx * import_core3.MAX_CANVAS_WIDTH;
1480
1475
  const canvasWidth = canvas.width / devicePixelRatio;
1481
1476
  const ctx = canvas.getContext("2d");
1482
1477
  if (!ctx) continue;
@@ -1503,9 +1498,6 @@ var PianoRollChannel = ({
1503
1498
  }
1504
1499
  ctx.globalAlpha = 1;
1505
1500
  }
1506
- console.log(
1507
- `[piano-roll] draw ch${index}: ${canvasMapRef.current.size} chunks, ${midiNotes.length} notes, ${(performance.now() - tDraw).toFixed(1)}ms`
1508
- );
1509
1501
  }, [
1510
1502
  canvasMapRef,
1511
1503
  midiNotes,
@@ -1522,8 +1514,8 @@ var PianoRollChannel = ({
1522
1514
  index
1523
1515
  ]);
1524
1516
  const canvases = visibleChunkIndices.map((i) => {
1525
- const chunkLeft = i * import_core2.MAX_CANVAS_WIDTH;
1526
- const currentWidth = Math.min(length - chunkLeft, import_core2.MAX_CANVAS_WIDTH);
1517
+ const chunkLeft = i * import_core3.MAX_CANVAS_WIDTH;
1518
+ const currentWidth = Math.min(length - chunkLeft, import_core3.MAX_CANVAS_WIDTH);
1527
1519
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1528
1520
  NoteCanvas,
1529
1521
  {
@@ -2145,7 +2137,7 @@ function clockFormat(seconds, decimals) {
2145
2137
  const hours = Math.floor(seconds / 3600) % 24;
2146
2138
  const minutes = Math.floor(seconds / 60) % 60;
2147
2139
  const secs = (seconds % 60).toFixed(decimals);
2148
- return String(hours).padStart(2, "0") + ":" + String(minutes).padStart(2, "0") + ":" + secs.padStart(decimals + 3, "0");
2140
+ return String(hours).padStart(2, "0") + ":" + String(minutes).padStart(2, "0") + ":" + secs.padStart(decimals > 0 ? decimals + 3 : 2, "0");
2149
2141
  }
2150
2142
  function formatTime(seconds, format) {
2151
2143
  switch (format) {
@@ -2297,15 +2289,45 @@ var SelectionTimeInputs = ({
2297
2289
  ] });
2298
2290
  };
2299
2291
 
2300
- // src/contexts/DevicePixelRatio.tsx
2292
+ // src/contexts/BeatsAndBars.tsx
2301
2293
  var import_react14 = require("react");
2294
+ var import_core4 = require("@waveform-playlist/core");
2302
2295
  var import_jsx_runtime19 = require("react/jsx-runtime");
2296
+ var BeatsAndBarsContext = (0, import_react14.createContext)(null);
2297
+ function BeatsAndBarsProvider({
2298
+ bpm,
2299
+ timeSignature,
2300
+ snapTo,
2301
+ children
2302
+ }) {
2303
+ const [numerator, denominator] = timeSignature;
2304
+ const value = (0, import_react14.useMemo)(() => {
2305
+ const ts = [numerator, denominator];
2306
+ const tpBeat = (0, import_core4.ticksPerBeat)(ts);
2307
+ const tpBar = (0, import_core4.ticksPerBar)(ts);
2308
+ return {
2309
+ bpm,
2310
+ timeSignature: ts,
2311
+ snapTo,
2312
+ ticksPerBeat: tpBeat,
2313
+ ticksPerBar: tpBar
2314
+ };
2315
+ }, [bpm, numerator, denominator, snapTo]);
2316
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(BeatsAndBarsContext.Provider, { value, children });
2317
+ }
2318
+ function useBeatsAndBars() {
2319
+ return (0, import_react14.useContext)(BeatsAndBarsContext);
2320
+ }
2321
+
2322
+ // src/contexts/DevicePixelRatio.tsx
2323
+ var import_react15 = require("react");
2324
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2303
2325
  function getScale() {
2304
2326
  return window.devicePixelRatio;
2305
2327
  }
2306
- var DevicePixelRatioContext = (0, import_react14.createContext)(getScale());
2328
+ var DevicePixelRatioContext = (0, import_react15.createContext)(getScale());
2307
2329
  var DevicePixelRatioProvider = ({ children }) => {
2308
- const [scale, setScale] = (0, import_react14.useState)(getScale());
2330
+ const [scale, setScale] = (0, import_react15.useState)(getScale());
2309
2331
  matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
2310
2332
  "change",
2311
2333
  () => {
@@ -2313,13 +2335,13 @@ var DevicePixelRatioProvider = ({ children }) => {
2313
2335
  },
2314
2336
  { once: true }
2315
2337
  );
2316
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2338
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2317
2339
  };
2318
- var useDevicePixelRatio = () => (0, import_react14.useContext)(DevicePixelRatioContext);
2340
+ var useDevicePixelRatio = () => (0, import_react15.useContext)(DevicePixelRatioContext);
2319
2341
 
2320
2342
  // src/contexts/PlaylistInfo.tsx
2321
- var import_react15 = require("react");
2322
- var PlaylistInfoContext = (0, import_react15.createContext)({
2343
+ var import_react16 = require("react");
2344
+ var PlaylistInfoContext = (0, import_react16.createContext)({
2323
2345
  sampleRate: 48e3,
2324
2346
  samplesPerPixel: 1e3,
2325
2347
  zoomLevels: [1e3, 1500, 2e3, 2500],
@@ -2333,22 +2355,22 @@ var PlaylistInfoContext = (0, import_react15.createContext)({
2333
2355
  barWidth: 1,
2334
2356
  barGap: 0
2335
2357
  });
2336
- var usePlaylistInfo = () => (0, import_react15.useContext)(PlaylistInfoContext);
2358
+ var usePlaylistInfo = () => (0, import_react16.useContext)(PlaylistInfoContext);
2337
2359
 
2338
2360
  // src/contexts/Theme.tsx
2339
- var import_react16 = require("react");
2361
+ var import_react17 = require("react");
2340
2362
  var import_styled_components20 = require("styled-components");
2341
- var useTheme2 = () => (0, import_react16.useContext)(import_styled_components20.ThemeContext);
2363
+ var useTheme2 = () => (0, import_react17.useContext)(import_styled_components20.ThemeContext);
2342
2364
 
2343
2365
  // src/contexts/TrackControls.tsx
2344
- var import_react17 = require("react");
2345
- var import_jsx_runtime20 = require("react/jsx-runtime");
2346
- var TrackControlsContext = (0, import_react17.createContext)(/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react17.Fragment, {}));
2347
- var useTrackControls = () => (0, import_react17.useContext)(TrackControlsContext);
2348
-
2349
- // src/contexts/Playout.tsx
2350
2366
  var import_react18 = require("react");
2351
2367
  var import_jsx_runtime21 = require("react/jsx-runtime");
2368
+ var TrackControlsContext = (0, import_react18.createContext)(/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react18.Fragment, {}));
2369
+ var useTrackControls = () => (0, import_react18.useContext)(TrackControlsContext);
2370
+
2371
+ // src/contexts/Playout.tsx
2372
+ var import_react19 = require("react");
2373
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2352
2374
  var defaultProgress = 0;
2353
2375
  var defaultIsPlaying = false;
2354
2376
  var defaultSelectionStart = 0;
@@ -2359,8 +2381,8 @@ var defaultPlayout = {
2359
2381
  selectionStart: defaultSelectionStart,
2360
2382
  selectionEnd: defaultSelectionEnd
2361
2383
  };
2362
- var PlayoutStatusContext = (0, import_react18.createContext)(defaultPlayout);
2363
- var PlayoutStatusUpdateContext = (0, import_react18.createContext)({
2384
+ var PlayoutStatusContext = (0, import_react19.createContext)(defaultPlayout);
2385
+ var PlayoutStatusUpdateContext = (0, import_react19.createContext)({
2364
2386
  setIsPlaying: () => {
2365
2387
  },
2366
2388
  setProgress: () => {
@@ -2369,24 +2391,24 @@ var PlayoutStatusUpdateContext = (0, import_react18.createContext)({
2369
2391
  }
2370
2392
  });
2371
2393
  var PlayoutProvider = ({ children }) => {
2372
- const [isPlaying, setIsPlaying] = (0, import_react18.useState)(defaultIsPlaying);
2373
- const [progress, setProgress] = (0, import_react18.useState)(defaultProgress);
2374
- const [selectionStart, setSelectionStart] = (0, import_react18.useState)(defaultSelectionStart);
2375
- const [selectionEnd, setSelectionEnd] = (0, import_react18.useState)(defaultSelectionEnd);
2394
+ const [isPlaying, setIsPlaying] = (0, import_react19.useState)(defaultIsPlaying);
2395
+ const [progress, setProgress] = (0, import_react19.useState)(defaultProgress);
2396
+ const [selectionStart, setSelectionStart] = (0, import_react19.useState)(defaultSelectionStart);
2397
+ const [selectionEnd, setSelectionEnd] = (0, import_react19.useState)(defaultSelectionEnd);
2376
2398
  const setSelection = (start, end) => {
2377
2399
  setSelectionStart(start);
2378
2400
  setSelectionEnd(end);
2379
2401
  };
2380
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2402
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2381
2403
  };
2382
- var usePlayoutStatus = () => (0, import_react18.useContext)(PlayoutStatusContext);
2383
- var usePlayoutStatusUpdate = () => (0, import_react18.useContext)(PlayoutStatusUpdateContext);
2404
+ var usePlayoutStatus = () => (0, import_react19.useContext)(PlayoutStatusContext);
2405
+ var usePlayoutStatusUpdate = () => (0, import_react19.useContext)(PlayoutStatusUpdateContext);
2384
2406
 
2385
2407
  // src/components/SpectrogramChannel.tsx
2386
- var import_react19 = require("react");
2408
+ var import_react20 = require("react");
2387
2409
  var import_styled_components21 = __toESM(require("styled-components"));
2388
- var import_core3 = require("@waveform-playlist/core");
2389
- var import_jsx_runtime22 = require("react/jsx-runtime");
2410
+ var import_core5 = require("@waveform-playlist/core");
2411
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2390
2412
  var LINEAR_FREQUENCY_SCALE = (f, minF, maxF) => (f - minF) / (maxF - minF);
2391
2413
  var Wrapper4 = import_styled_components21.default.div.attrs((props) => ({
2392
2414
  style: {
@@ -2438,24 +2460,24 @@ var SpectrogramChannel = ({
2438
2460
  }) => {
2439
2461
  const channelIndex = channelIndexProp ?? index;
2440
2462
  const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2441
- const registeredIdsRef = (0, import_react19.useRef)([]);
2442
- const transferredCanvasesRef = (0, import_react19.useRef)(/* @__PURE__ */ new WeakSet());
2443
- const workerApiRef = (0, import_react19.useRef)(workerApi);
2444
- const onCanvasesReadyRef = (0, import_react19.useRef)(onCanvasesReady);
2463
+ const registeredIdsRef = (0, import_react20.useRef)([]);
2464
+ const transferredCanvasesRef = (0, import_react20.useRef)(/* @__PURE__ */ new WeakSet());
2465
+ const workerApiRef = (0, import_react20.useRef)(workerApi);
2466
+ const onCanvasesReadyRef = (0, import_react20.useRef)(onCanvasesReady);
2445
2467
  const isWorkerMode = !!(workerApi && clipId);
2446
2468
  const clipOriginX = useClipViewportOrigin();
2447
- const visibleChunkIndices = useVisibleChunkIndices(length, import_core3.MAX_CANVAS_WIDTH, clipOriginX);
2469
+ const visibleChunkIndices = useVisibleChunkIndices(length, import_core5.MAX_CANVAS_WIDTH, clipOriginX);
2448
2470
  const lut = colorLUT ?? DEFAULT_COLOR_LUT;
2449
2471
  const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);
2450
2472
  const scaleFn = frequencyScaleFn ?? LINEAR_FREQUENCY_SCALE;
2451
2473
  const hasCustomFrequencyScale = Boolean(frequencyScaleFn);
2452
- (0, import_react19.useEffect)(() => {
2474
+ (0, import_react20.useEffect)(() => {
2453
2475
  workerApiRef.current = workerApi;
2454
2476
  }, [workerApi]);
2455
- (0, import_react19.useEffect)(() => {
2477
+ (0, import_react20.useEffect)(() => {
2456
2478
  onCanvasesReadyRef.current = onCanvasesReady;
2457
2479
  }, [onCanvasesReady]);
2458
- (0, import_react19.useEffect)(() => {
2480
+ (0, import_react20.useEffect)(() => {
2459
2481
  if (!isWorkerMode) return;
2460
2482
  const currentWorkerApi = workerApiRef.current;
2461
2483
  if (!currentWorkerApi || !clipId) return;
@@ -2510,15 +2532,15 @@ var SpectrogramChannel = ({
2510
2532
  const match = id.match(/chunk(\d+)$/);
2511
2533
  if (!match) {
2512
2534
  console.warn(`[spectrogram] Unexpected canvas ID format: ${id}`);
2513
- return import_core3.MAX_CANVAS_WIDTH;
2535
+ return import_core5.MAX_CANVAS_WIDTH;
2514
2536
  }
2515
2537
  const chunkIdx = parseInt(match[1], 10);
2516
- return Math.min(length - chunkIdx * import_core3.MAX_CANVAS_WIDTH, import_core3.MAX_CANVAS_WIDTH);
2538
+ return Math.min(length - chunkIdx * import_core5.MAX_CANVAS_WIDTH, import_core5.MAX_CANVAS_WIDTH);
2517
2539
  });
2518
2540
  onCanvasesReadyRef.current?.(allIds, allWidths);
2519
2541
  }
2520
2542
  }, [canvasMapRef, isWorkerMode, clipId, channelIndex, length, visibleChunkIndices]);
2521
- (0, import_react19.useEffect)(() => {
2543
+ (0, import_react20.useEffect)(() => {
2522
2544
  return () => {
2523
2545
  const api = workerApiRef.current;
2524
2546
  if (!api) return;
@@ -2532,7 +2554,7 @@ var SpectrogramChannel = ({
2532
2554
  registeredIdsRef.current = [];
2533
2555
  };
2534
2556
  }, []);
2535
- (0, import_react19.useEffect)(() => {
2557
+ (0, import_react20.useEffect)(() => {
2536
2558
  if (isWorkerMode || !data) return;
2537
2559
  const {
2538
2560
  frequencyBinCount,
@@ -2545,7 +2567,7 @@ var SpectrogramChannel = ({
2545
2567
  const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;
2546
2568
  const binToFreq = (bin) => bin / frequencyBinCount * (sampleRate / 2);
2547
2569
  for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
2548
- const globalPixelOffset = canvasIdx * import_core3.MAX_CANVAS_WIDTH;
2570
+ const globalPixelOffset = canvasIdx * import_core5.MAX_CANVAS_WIDTH;
2549
2571
  const ctx = canvas.getContext("2d");
2550
2572
  if (!ctx) continue;
2551
2573
  const canvasWidth = canvas.width / devicePixelRatio;
@@ -2621,9 +2643,9 @@ var SpectrogramChannel = ({
2621
2643
  visibleChunkIndices
2622
2644
  ]);
2623
2645
  const canvases = visibleChunkIndices.map((i) => {
2624
- const chunkLeft = i * import_core3.MAX_CANVAS_WIDTH;
2625
- const currentWidth = Math.min(length - chunkLeft, import_core3.MAX_CANVAS_WIDTH);
2626
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2646
+ const chunkLeft = i * import_core5.MAX_CANVAS_WIDTH;
2647
+ const currentWidth = Math.min(length - chunkLeft, import_core5.MAX_CANVAS_WIDTH);
2648
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2627
2649
  SpectrogramCanvas,
2628
2650
  {
2629
2651
  $cssWidth: currentWidth,
@@ -2637,11 +2659,11 @@ var SpectrogramChannel = ({
2637
2659
  `${length}-${i}`
2638
2660
  );
2639
2661
  });
2640
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Wrapper4, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2662
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Wrapper4, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2641
2663
  };
2642
2664
 
2643
2665
  // src/components/SmartChannel.tsx
2644
- var import_jsx_runtime23 = require("react/jsx-runtime");
2666
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2645
2667
  var SmartChannel = ({
2646
2668
  isSelected,
2647
2669
  transparentBackground,
@@ -2675,7 +2697,7 @@ var SmartChannel = ({
2675
2697
  const drawMode = theme?.waveformDrawMode || "inverted";
2676
2698
  const hasSpectrogram = spectrogramData || spectrogramWorkerApi;
2677
2699
  if (renderMode === "spectrogram" && hasSpectrogram) {
2678
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2700
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2679
2701
  SpectrogramChannel,
2680
2702
  {
2681
2703
  index: props.index,
@@ -2696,8 +2718,8 @@ var SmartChannel = ({
2696
2718
  }
2697
2719
  if (renderMode === "both" && hasSpectrogram) {
2698
2720
  const halfHeight = Math.floor(waveHeight / 2);
2699
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
2700
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2721
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2722
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2701
2723
  SpectrogramChannel,
2702
2724
  {
2703
2725
  index: props.index * 2,
@@ -2716,7 +2738,7 @@ var SmartChannel = ({
2716
2738
  onCanvasesReady: spectrogramOnCanvasesReady
2717
2739
  }
2718
2740
  ),
2719
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2741
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2720
2742
  "div",
2721
2743
  {
2722
2744
  style: {
@@ -2725,7 +2747,7 @@ var SmartChannel = ({
2725
2747
  width: props.length,
2726
2748
  height: halfHeight
2727
2749
  },
2728
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2750
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2729
2751
  Channel,
2730
2752
  {
2731
2753
  ...props,
@@ -2745,7 +2767,7 @@ var SmartChannel = ({
2745
2767
  ] });
2746
2768
  }
2747
2769
  if (renderMode === "piano-roll") {
2748
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2770
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2749
2771
  PianoRollChannel,
2750
2772
  {
2751
2773
  index: props.index,
@@ -2764,7 +2786,7 @@ var SmartChannel = ({
2764
2786
  }
2765
2787
  );
2766
2788
  }
2767
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2789
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2768
2790
  Channel,
2769
2791
  {
2770
2792
  ...props,
@@ -2781,9 +2803,9 @@ var SmartChannel = ({
2781
2803
  };
2782
2804
 
2783
2805
  // src/components/SpectrogramLabels.tsx
2784
- var import_react20 = require("react");
2806
+ var import_react21 = require("react");
2785
2807
  var import_styled_components22 = __toESM(require("styled-components"));
2786
- var import_jsx_runtime24 = require("react/jsx-runtime");
2808
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2787
2809
  var LABELS_WIDTH = 72;
2788
2810
  var LabelsStickyWrapper = import_styled_components22.default.div`
2789
2811
  position: sticky;
@@ -2833,12 +2855,12 @@ var SpectrogramLabels = ({
2833
2855
  renderMode = "spectrogram",
2834
2856
  hasClipHeaders = false
2835
2857
  }) => {
2836
- const canvasRef = (0, import_react20.useRef)(null);
2858
+ const canvasRef = (0, import_react21.useRef)(null);
2837
2859
  const devicePixelRatio = useDevicePixelRatio();
2838
2860
  const spectrogramHeight = renderMode === "both" ? Math.floor(waveHeight / 2) : waveHeight;
2839
2861
  const totalHeight = numChannels * waveHeight;
2840
2862
  const clipHeaderOffset = hasClipHeaders ? 22 : 0;
2841
- (0, import_react20.useLayoutEffect)(() => {
2863
+ (0, import_react21.useLayoutEffect)(() => {
2842
2864
  const canvas = canvasRef.current;
2843
2865
  if (!canvas) return;
2844
2866
  const ctx = canvas.getContext("2d");
@@ -2876,7 +2898,7 @@ var SpectrogramLabels = ({
2876
2898
  spectrogramHeight,
2877
2899
  clipHeaderOffset
2878
2900
  ]);
2879
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2901
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2880
2902
  "canvas",
2881
2903
  {
2882
2904
  ref: canvasRef,
@@ -2892,41 +2914,14 @@ var SpectrogramLabels = ({
2892
2914
  };
2893
2915
 
2894
2916
  // src/components/SmartScale.tsx
2895
- var import_react22 = require("react");
2917
+ var import_react23 = __toESM(require("react"));
2918
+ var import_styled_components24 = __toESM(require("styled-components"));
2896
2919
 
2897
2920
  // src/components/TimeScale.tsx
2898
- var import_react21 = __toESM(require("react"));
2921
+ var import_react22 = require("react");
2899
2922
  var import_styled_components23 = __toESM(require("styled-components"));
2900
-
2901
- // src/utils/conversions.ts
2902
- function samplesToSeconds(samples, sampleRate) {
2903
- return samples / sampleRate;
2904
- }
2905
- function secondsToSamples(seconds, sampleRate) {
2906
- return Math.ceil(seconds * sampleRate);
2907
- }
2908
- function samplesToPixels(samples, samplesPerPixel) {
2909
- return Math.floor(samples / samplesPerPixel);
2910
- }
2911
- function pixelsToSamples(pixels, samplesPerPixel) {
2912
- return Math.floor(pixels * samplesPerPixel);
2913
- }
2914
- function pixelsToSeconds(pixels, samplesPerPixel, sampleRate) {
2915
- return pixels * samplesPerPixel / sampleRate;
2916
- }
2917
- function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
2918
- return Math.ceil(seconds * sampleRate / samplesPerPixel);
2919
- }
2920
-
2921
- // src/components/TimeScale.tsx
2922
- var import_core4 = require("@waveform-playlist/core");
2923
- var import_jsx_runtime25 = require("react/jsx-runtime");
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
- }
2923
+ var import_core6 = require("@waveform-playlist/core");
2924
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2930
2925
  var PlaylistTimeScaleScroll = import_styled_components23.default.div.attrs((props) => ({
2931
2926
  style: {
2932
2927
  width: `${props.$cssWidth}px`,
@@ -2948,70 +2943,20 @@ var TimeTickChunk = import_styled_components23.default.canvas.attrs((props) => (
2948
2943
  position: absolute;
2949
2944
  bottom: 0;
2950
2945
  `;
2951
- var TimeStamp = import_styled_components23.default.div.attrs((props) => ({
2952
- style: {
2953
- left: `${props.$left + 4}px`
2954
- // Offset 4px to the right of the tick
2955
- }
2956
- }))`
2957
- position: absolute;
2958
- font-size: 0.75rem; /* Smaller font to prevent overflow */
2959
- white-space: nowrap; /* Prevent text wrapping */
2960
- color: ${(props) => props.theme.timeColor}; /* Use theme color instead of inheriting */
2961
- `;
2962
2946
  var TimeScale = (props) => {
2963
2947
  const {
2964
2948
  theme: { timeColor },
2965
- duration,
2966
- marker,
2967
- bigStep,
2968
- secondStep,
2969
- renderTimestamp
2949
+ tickData
2970
2950
  } = props;
2971
2951
  const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2972
- const { sampleRate, samplesPerPixel, timeScaleHeight } = (0, import_react21.useContext)(PlaylistInfoContext);
2952
+ const { timeScaleHeight } = (0, import_react22.useContext)(PlaylistInfoContext);
2973
2953
  const devicePixelRatio = useDevicePixelRatio();
2974
- const { widthX, canvasInfo, timeMarkersWithPositions } = (0, import_react21.useMemo)(() => {
2975
- const nextCanvasInfo = /* @__PURE__ */ new Map();
2976
- const nextMarkers = [];
2977
- const nextWidthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
2978
- const pixPerSec = sampleRate / samplesPerPixel;
2979
- let counter = 0;
2980
- for (let i = 0; i < nextWidthX; i += pixPerSec * secondStep / 1e3) {
2981
- const pix = Math.floor(i);
2982
- if (counter % marker === 0) {
2983
- const timeMs = counter;
2984
- const timestamp = formatTime2(timeMs);
2985
- 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);
2986
- nextMarkers.push({ pix, element });
2987
- nextCanvasInfo.set(pix, timeScaleHeight);
2988
- } else if (counter % bigStep === 0) {
2989
- nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
2990
- } else if (counter % secondStep === 0) {
2991
- nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 5));
2992
- }
2993
- counter += secondStep;
2994
- }
2995
- return {
2996
- widthX: nextWidthX,
2997
- canvasInfo: nextCanvasInfo,
2998
- timeMarkersWithPositions: nextMarkers
2999
- };
3000
- }, [
3001
- duration,
3002
- samplesPerPixel,
3003
- sampleRate,
3004
- marker,
3005
- bigStep,
3006
- secondStep,
3007
- renderTimestamp,
3008
- timeScaleHeight
3009
- ]);
3010
- const visibleChunkIndices = useVisibleChunkIndices(widthX, import_core4.MAX_CANVAS_WIDTH);
2954
+ const { widthX, canvasInfo, timeMarkersWithPositions } = tickData;
2955
+ const visibleChunkIndices = useVisibleChunkIndices(widthX, import_core6.MAX_CANVAS_WIDTH);
3011
2956
  const visibleChunks = visibleChunkIndices.map((i) => {
3012
- const chunkLeft = i * import_core4.MAX_CANVAS_WIDTH;
3013
- const chunkWidth = Math.min(widthX - chunkLeft, import_core4.MAX_CANVAS_WIDTH);
3014
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2957
+ const chunkLeft = i * import_core6.MAX_CANVAS_WIDTH;
2958
+ const chunkWidth = Math.min(widthX - chunkLeft, import_core6.MAX_CANVAS_WIDTH);
2959
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3015
2960
  TimeTickChunk,
3016
2961
  {
3017
2962
  $cssWidth: chunkWidth,
@@ -3025,14 +2970,14 @@ var TimeScale = (props) => {
3025
2970
  `timescale-${i}`
3026
2971
  );
3027
2972
  });
3028
- const firstChunkLeft = visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * import_core4.MAX_CANVAS_WIDTH : 0;
3029
- const lastChunkRight = visibleChunkIndices.length > 0 ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * import_core4.MAX_CANVAS_WIDTH : Infinity;
2973
+ const firstChunkLeft = visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * import_core6.MAX_CANVAS_WIDTH : 0;
2974
+ const lastChunkRight = visibleChunkIndices.length > 0 ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * import_core6.MAX_CANVAS_WIDTH : Infinity;
3030
2975
  const visibleMarkers = visibleChunkIndices.length > 0 ? timeMarkersWithPositions.filter(({ pix }) => pix >= firstChunkLeft && pix < lastChunkRight).map(({ element }) => element) : timeMarkersWithPositions.map(({ element }) => element);
3031
- (0, import_react21.useLayoutEffect)(() => {
2976
+ (0, import_react22.useLayoutEffect)(() => {
3032
2977
  for (const [chunkIdx, canvas] of canvasMapRef.current.entries()) {
3033
2978
  const ctx = canvas.getContext("2d");
3034
2979
  if (!ctx) continue;
3035
- const chunkLeft = chunkIdx * import_core4.MAX_CANVAS_WIDTH;
2980
+ const chunkLeft = chunkIdx * import_core6.MAX_CANVAS_WIDTH;
3036
2981
  const chunkWidth = canvas.width / devicePixelRatio;
3037
2982
  ctx.resetTransform();
3038
2983
  ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -3046,16 +2991,8 @@ var TimeScale = (props) => {
3046
2991
  ctx.fillRect(localX, scaleY, 1, scaleHeight);
3047
2992
  }
3048
2993
  }
3049
- }, [
3050
- canvasMapRef,
3051
- duration,
3052
- devicePixelRatio,
3053
- timeColor,
3054
- timeScaleHeight,
3055
- canvasInfo,
3056
- visibleChunkIndices
3057
- ]);
3058
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(PlaylistTimeScaleScroll, { $cssWidth: widthX, $timeScaleHeight: timeScaleHeight, children: [
2994
+ }, [canvasMapRef, devicePixelRatio, timeColor, timeScaleHeight, canvasInfo, visibleChunkIndices]);
2995
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(PlaylistTimeScaleScroll, { $cssWidth: widthX, $timeScaleHeight: timeScaleHeight, children: [
3059
2996
  visibleMarkers,
3060
2997
  visibleChunks
3061
2998
  ] });
@@ -3063,64 +3000,16 @@ var TimeScale = (props) => {
3063
3000
  var StyledTimeScale = (0, import_styled_components23.withTheme)(TimeScale);
3064
3001
 
3065
3002
  // src/components/SmartScale.tsx
3066
- var import_jsx_runtime26 = require("react/jsx-runtime");
3003
+ var import_core7 = require("@waveform-playlist/core");
3004
+ var import_jsx_runtime27 = require("react/jsx-runtime");
3067
3005
  var timeinfo = /* @__PURE__ */ new Map([
3068
- [
3069
- 700,
3070
- {
3071
- marker: 1e3,
3072
- bigStep: 500,
3073
- smallStep: 100
3074
- }
3075
- ],
3076
- [
3077
- 1500,
3078
- {
3079
- marker: 2e3,
3080
- bigStep: 1e3,
3081
- smallStep: 200
3082
- }
3083
- ],
3084
- [
3085
- 2500,
3086
- {
3087
- marker: 2e3,
3088
- bigStep: 1e3,
3089
- smallStep: 500
3090
- }
3091
- ],
3092
- [
3093
- 5e3,
3094
- {
3095
- marker: 5e3,
3096
- bigStep: 1e3,
3097
- smallStep: 500
3098
- }
3099
- ],
3100
- [
3101
- 1e4,
3102
- {
3103
- marker: 1e4,
3104
- bigStep: 5e3,
3105
- smallStep: 1e3
3106
- }
3107
- ],
3108
- [
3109
- 12e3,
3110
- {
3111
- marker: 15e3,
3112
- bigStep: 5e3,
3113
- smallStep: 1e3
3114
- }
3115
- ],
3116
- [
3117
- Infinity,
3118
- {
3119
- marker: 3e4,
3120
- bigStep: 1e4,
3121
- smallStep: 5e3
3122
- }
3123
- ]
3006
+ [700, { marker: 1e3, bigStep: 500, smallStep: 100 }],
3007
+ [1500, { marker: 2e3, bigStep: 1e3, smallStep: 200 }],
3008
+ [2500, { marker: 2e3, bigStep: 1e3, smallStep: 500 }],
3009
+ [5e3, { marker: 5e3, bigStep: 1e3, smallStep: 500 }],
3010
+ [1e4, { marker: 1e4, bigStep: 5e3, smallStep: 1e3 }],
3011
+ [12e3, { marker: 15e3, bigStep: 5e3, smallStep: 1e3 }],
3012
+ [Infinity, { marker: 3e4, bigStep: 1e4, smallStep: 5e3 }]
3124
3013
  ]);
3125
3014
  function getScaleInfo(samplesPerPixel) {
3126
3015
  const keys = timeinfo.keys();
@@ -3136,25 +3025,113 @@ function getScaleInfo(samplesPerPixel) {
3136
3025
  }
3137
3026
  return config;
3138
3027
  }
3139
- var SmartScale = ({ renderTimestamp }) => {
3140
- const { samplesPerPixel, duration } = (0, import_react22.useContext)(PlaylistInfoContext);
3141
- let config = getScaleInfo(samplesPerPixel);
3142
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3143
- StyledTimeScale,
3144
- {
3145
- marker: config.marker,
3146
- bigStep: config.bigStep,
3147
- secondStep: config.smallStep,
3148
- duration,
3149
- renderTimestamp
3028
+ function formatTime2(milliseconds) {
3029
+ const seconds = Math.floor(milliseconds / 1e3);
3030
+ const s = seconds % 60;
3031
+ const m = (seconds - s) / 60;
3032
+ return `${m}:${String(s).padStart(2, "0")}`;
3033
+ }
3034
+ var TimeStamp = import_styled_components24.default.div.attrs((props) => ({
3035
+ style: {
3036
+ left: `${props.$left + 4}px`
3037
+ // Offset 4px to the right of the tick
3038
+ }
3039
+ }))`
3040
+ position: absolute;
3041
+ font-size: 0.75rem; /* Smaller font to prevent overflow */
3042
+ white-space: nowrap; /* Prevent text wrapping */
3043
+ color: ${(props) => props.theme.timeColor}; /* Use theme color instead of inheriting */
3044
+ `;
3045
+ var SmartScale = ({ renderTick }) => {
3046
+ const { samplesPerPixel, sampleRate, duration, timeScaleHeight } = (0, import_react23.useContext)(PlaylistInfoContext);
3047
+ const beatsAndBars = useBeatsAndBars();
3048
+ const tickData = (0, import_react23.useMemo)(() => {
3049
+ const widthX = (0, import_core7.secondsToPixels)(duration / 1e3, samplesPerPixel, sampleRate);
3050
+ if (beatsAndBars) {
3051
+ const { bpm, timeSignature, ticksPerBar: tpBar, ticksPerBeat: tpBeat } = beatsAndBars;
3052
+ const canvasInfo2 = /* @__PURE__ */ new Map();
3053
+ const timeMarkersWithPositions2 = [];
3054
+ const durationSeconds = duration / 1e3;
3055
+ const totalTicks = Math.ceil(durationSeconds * bpm * import_core7.PPQN / 60);
3056
+ const pixelsPerBeat = (0, import_core7.ticksToSamples)(tpBeat, bpm, sampleRate) / samplesPerPixel;
3057
+ const pixelsPerBar = (0, import_core7.ticksToSamples)(tpBar, bpm, sampleRate) / samplesPerPixel;
3058
+ const MIN_TICK_PX = 10;
3059
+ const MIN_LABEL_PX = 30;
3060
+ let tickStep;
3061
+ if (pixelsPerBeat >= MIN_TICK_PX) {
3062
+ tickStep = tpBeat;
3063
+ } else if (pixelsPerBar >= MIN_TICK_PX) {
3064
+ tickStep = tpBar;
3065
+ } else {
3066
+ const barsPerTick = Math.ceil(MIN_TICK_PX / pixelsPerBar);
3067
+ tickStep = tpBar * barsPerTick;
3068
+ }
3069
+ let labelStep;
3070
+ if (pixelsPerBeat >= MIN_LABEL_PX) {
3071
+ labelStep = tpBeat;
3072
+ } else if (pixelsPerBar >= MIN_LABEL_PX) {
3073
+ labelStep = tpBar;
3074
+ } else {
3075
+ const barsPerLabel = Math.ceil(MIN_LABEL_PX / pixelsPerBar);
3076
+ labelStep = tpBar * barsPerLabel;
3077
+ }
3078
+ for (let tick = 0; tick <= totalTicks; tick += tickStep) {
3079
+ const samples = (0, import_core7.ticksToSamples)(tick, bpm, sampleRate);
3080
+ const pix = (0, import_core7.samplesToPixels)(samples, samplesPerPixel);
3081
+ if (pix >= widthX) break;
3082
+ const isBarLine = tick % tpBar === 0;
3083
+ const isLabelTick = tick % labelStep === 0;
3084
+ const tickHeight = isBarLine ? timeScaleHeight : isLabelTick ? Math.floor(timeScaleHeight / 2) : Math.floor(timeScaleHeight / 5);
3085
+ canvasInfo2.set(pix, tickHeight);
3086
+ if (isLabelTick) {
3087
+ const label = (0, import_core7.ticksToBarBeatLabel)(tick, timeSignature);
3088
+ const element = renderTick ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react23.default.Fragment, { children: renderTick(label, pix) }, `bb-${tick}`) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3089
+ "div",
3090
+ {
3091
+ style: {
3092
+ position: "absolute",
3093
+ left: `${pix + 4}px`,
3094
+ fontSize: "0.75rem",
3095
+ whiteSpace: "nowrap"
3096
+ },
3097
+ children: label
3098
+ },
3099
+ `bb-${tick}`
3100
+ );
3101
+ timeMarkersWithPositions2.push({ pix, element });
3102
+ }
3103
+ }
3104
+ return { widthX, canvasInfo: canvasInfo2, timeMarkersWithPositions: timeMarkersWithPositions2 };
3150
3105
  }
3151
- );
3106
+ const config = getScaleInfo(samplesPerPixel);
3107
+ const { marker, bigStep, smallStep } = config;
3108
+ const canvasInfo = /* @__PURE__ */ new Map();
3109
+ const timeMarkersWithPositions = [];
3110
+ const pixPerSec = sampleRate / samplesPerPixel;
3111
+ let counter = 0;
3112
+ for (let i = 0; i < widthX; i += pixPerSec * smallStep / 1e3) {
3113
+ const pix = Math.floor(i);
3114
+ if (counter % marker === 0) {
3115
+ const timestamp = formatTime2(counter);
3116
+ const element = renderTick ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react23.default.Fragment, { children: renderTick(timestamp, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
3117
+ timeMarkersWithPositions.push({ pix, element });
3118
+ canvasInfo.set(pix, timeScaleHeight);
3119
+ } else if (counter % bigStep === 0) {
3120
+ canvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
3121
+ } else if (counter % smallStep === 0) {
3122
+ canvasInfo.set(pix, Math.floor(timeScaleHeight / 5));
3123
+ }
3124
+ counter += smallStep;
3125
+ }
3126
+ return { widthX, canvasInfo, timeMarkersWithPositions };
3127
+ }, [beatsAndBars, duration, samplesPerPixel, sampleRate, timeScaleHeight, renderTick]);
3128
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(StyledTimeScale, { tickData });
3152
3129
  };
3153
3130
 
3154
3131
  // src/components/TimeFormatSelect.tsx
3155
- var import_styled_components24 = __toESM(require("styled-components"));
3156
- var import_jsx_runtime27 = require("react/jsx-runtime");
3157
- var SelectWrapper = import_styled_components24.default.div`
3132
+ var import_styled_components25 = __toESM(require("styled-components"));
3133
+ var import_jsx_runtime28 = require("react/jsx-runtime");
3134
+ var SelectWrapper = import_styled_components25.default.div`
3158
3135
  display: inline-flex;
3159
3136
  align-items: center;
3160
3137
  gap: 0.5rem;
@@ -3176,7 +3153,7 @@ var TimeFormatSelect = ({
3176
3153
  const handleChange = (e) => {
3177
3154
  onChange(e.target.value);
3178
3155
  };
3179
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3156
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3180
3157
  BaseSelect,
3181
3158
  {
3182
3159
  className: "time-format",
@@ -3184,15 +3161,15 @@ var TimeFormatSelect = ({
3184
3161
  onChange: handleChange,
3185
3162
  disabled,
3186
3163
  "aria-label": "Time format selection",
3187
- children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("option", { value: option.value, children: option.label }, option.value))
3164
+ children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("option", { value: option.value, children: option.label }, option.value))
3188
3165
  }
3189
3166
  ) });
3190
3167
  };
3191
3168
 
3192
3169
  // src/components/Track.tsx
3193
- var import_styled_components25 = __toESM(require("styled-components"));
3194
- var import_jsx_runtime28 = require("react/jsx-runtime");
3195
- var Container = import_styled_components25.default.div.attrs((props) => ({
3170
+ var import_styled_components26 = __toESM(require("styled-components"));
3171
+ var import_jsx_runtime29 = require("react/jsx-runtime");
3172
+ var Container = import_styled_components26.default.div.attrs((props) => ({
3196
3173
  style: {
3197
3174
  height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
3198
3175
  }
@@ -3200,7 +3177,7 @@ var Container = import_styled_components25.default.div.attrs((props) => ({
3200
3177
  position: relative;
3201
3178
  ${(props) => props.$width !== void 0 && `width: ${props.$width}px;`}
3202
3179
  `;
3203
- var ChannelContainer = import_styled_components25.default.div.attrs((props) => ({
3180
+ var ChannelContainer = import_styled_components26.default.div.attrs((props) => ({
3204
3181
  style: {
3205
3182
  paddingLeft: `${props.$offset || 0}px`
3206
3183
  }
@@ -3222,7 +3199,7 @@ var Track = ({
3222
3199
  isSelected: _isSelected = false
3223
3200
  }) => {
3224
3201
  const { waveHeight } = usePlaylistInfo();
3225
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3202
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3226
3203
  Container,
3227
3204
  {
3228
3205
  $numChannels: numChannels,
@@ -3230,7 +3207,7 @@ var Track = ({
3230
3207
  $waveHeight: waveHeight,
3231
3208
  $width: width,
3232
3209
  $hasClipHeaders: hasClipHeaders,
3233
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3210
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3234
3211
  ChannelContainer,
3235
3212
  {
3236
3213
  $backgroundColor: backgroundColor,
@@ -3245,8 +3222,8 @@ var Track = ({
3245
3222
  };
3246
3223
 
3247
3224
  // src/components/TrackControls/Button.tsx
3248
- var import_styled_components26 = __toESM(require("styled-components"));
3249
- var Button = import_styled_components26.default.button.attrs({
3225
+ var import_styled_components27 = __toESM(require("styled-components"));
3226
+ var Button = import_styled_components27.default.button.attrs({
3250
3227
  type: "button"
3251
3228
  })`
3252
3229
  display: inline-block;
@@ -3321,8 +3298,8 @@ var Button = import_styled_components26.default.button.attrs({
3321
3298
  `;
3322
3299
 
3323
3300
  // src/components/TrackControls/ButtonGroup.tsx
3324
- var import_styled_components27 = __toESM(require("styled-components"));
3325
- var ButtonGroup = import_styled_components27.default.div`
3301
+ var import_styled_components28 = __toESM(require("styled-components"));
3302
+ var ButtonGroup = import_styled_components28.default.div`
3326
3303
  margin-bottom: 0.3rem;
3327
3304
 
3328
3305
  button:not(:first-child) {
@@ -3337,10 +3314,10 @@ var ButtonGroup = import_styled_components27.default.div`
3337
3314
  `;
3338
3315
 
3339
3316
  // src/components/TrackControls/CloseButton.tsx
3340
- var import_styled_components28 = __toESM(require("styled-components"));
3341
- var import_react23 = require("@phosphor-icons/react");
3342
- var import_jsx_runtime29 = require("react/jsx-runtime");
3343
- var StyledCloseButton = import_styled_components28.default.button`
3317
+ var import_styled_components29 = __toESM(require("styled-components"));
3318
+ var import_react24 = require("@phosphor-icons/react");
3319
+ var import_jsx_runtime30 = require("react/jsx-runtime");
3320
+ var StyledCloseButton = import_styled_components29.default.button`
3344
3321
  position: absolute;
3345
3322
  left: 0;
3346
3323
  top: 0;
@@ -3363,11 +3340,11 @@ var StyledCloseButton = import_styled_components28.default.button`
3363
3340
  color: #dc3545;
3364
3341
  }
3365
3342
  `;
3366
- var CloseButton = ({ onClick, title = "Remove track" }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react23.X, { size: 12, weight: "bold" }) });
3343
+ var CloseButton = ({ onClick, title = "Remove track" }) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react24.X, { size: 12, weight: "bold" }) });
3367
3344
 
3368
3345
  // src/components/TrackControls/Controls.tsx
3369
- var import_styled_components29 = __toESM(require("styled-components"));
3370
- var Controls = import_styled_components29.default.div`
3346
+ var import_styled_components30 = __toESM(require("styled-components"));
3347
+ var Controls = import_styled_components30.default.div`
3371
3348
  background: transparent;
3372
3349
  width: 100%;
3373
3350
  height: 100%;
@@ -3383,8 +3360,8 @@ var Controls = import_styled_components29.default.div`
3383
3360
  `;
3384
3361
 
3385
3362
  // src/components/TrackControls/Header.tsx
3386
- var import_styled_components30 = __toESM(require("styled-components"));
3387
- var Header = import_styled_components30.default.header`
3363
+ var import_styled_components31 = __toESM(require("styled-components"));
3364
+ var Header = import_styled_components31.default.header`
3388
3365
  overflow: hidden;
3389
3366
  height: 26px;
3390
3367
  width: 100%;
@@ -3398,28 +3375,28 @@ var Header = import_styled_components30.default.header`
3398
3375
  `;
3399
3376
 
3400
3377
  // src/components/TrackControls/VolumeDownIcon.tsx
3401
- var import_react24 = require("@phosphor-icons/react");
3402
- var import_jsx_runtime30 = require("react/jsx-runtime");
3403
- var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react24.SpeakerLowIcon, { weight: "light", ...props });
3404
-
3405
- // src/components/TrackControls/VolumeUpIcon.tsx
3406
3378
  var import_react25 = require("@phosphor-icons/react");
3407
3379
  var import_jsx_runtime31 = require("react/jsx-runtime");
3408
- var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react25.SpeakerHighIcon, { weight: "light", ...props });
3380
+ var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react25.SpeakerLowIcon, { weight: "light", ...props });
3409
3381
 
3410
- // src/components/TrackControls/TrashIcon.tsx
3382
+ // src/components/TrackControls/VolumeUpIcon.tsx
3411
3383
  var import_react26 = require("@phosphor-icons/react");
3412
3384
  var import_jsx_runtime32 = require("react/jsx-runtime");
3413
- var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react26.TrashIcon, { weight: "light", ...props });
3385
+ var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react26.SpeakerHighIcon, { weight: "light", ...props });
3414
3386
 
3415
- // src/components/TrackControls/DotsIcon.tsx
3387
+ // src/components/TrackControls/TrashIcon.tsx
3416
3388
  var import_react27 = require("@phosphor-icons/react");
3417
3389
  var import_jsx_runtime33 = require("react/jsx-runtime");
3418
- var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_react27.DotsThreeIcon, { weight: "bold", ...props });
3390
+ var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_react27.TrashIcon, { weight: "light", ...props });
3391
+
3392
+ // src/components/TrackControls/DotsIcon.tsx
3393
+ var import_react28 = require("@phosphor-icons/react");
3394
+ var import_jsx_runtime34 = require("react/jsx-runtime");
3395
+ var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react28.DotsThreeIcon, { weight: "bold", ...props });
3419
3396
 
3420
3397
  // src/components/TrackControls/Slider.tsx
3421
- var import_styled_components31 = __toESM(require("styled-components"));
3422
- var Slider = (0, import_styled_components31.default)(BaseSlider)`
3398
+ var import_styled_components32 = __toESM(require("styled-components"));
3399
+ var Slider = (0, import_styled_components32.default)(BaseSlider)`
3423
3400
  width: 75%;
3424
3401
  height: 5px;
3425
3402
  background: ${(props) => props.theme.sliderTrackColor};
@@ -3471,8 +3448,8 @@ var Slider = (0, import_styled_components31.default)(BaseSlider)`
3471
3448
  `;
3472
3449
 
3473
3450
  // src/components/TrackControls/SliderWrapper.tsx
3474
- var import_styled_components32 = __toESM(require("styled-components"));
3475
- var SliderWrapper = import_styled_components32.default.label`
3451
+ var import_styled_components33 = __toESM(require("styled-components"));
3452
+ var SliderWrapper = import_styled_components33.default.label`
3476
3453
  width: 100%;
3477
3454
  display: flex;
3478
3455
  justify-content: space-between;
@@ -3483,15 +3460,15 @@ var SliderWrapper = import_styled_components32.default.label`
3483
3460
  `;
3484
3461
 
3485
3462
  // src/components/TrackMenu.tsx
3486
- var import_react28 = __toESM(require("react"));
3463
+ var import_react29 = __toESM(require("react"));
3487
3464
  var import_react_dom = require("react-dom");
3488
- var import_styled_components33 = __toESM(require("styled-components"));
3489
- var import_jsx_runtime34 = require("react/jsx-runtime");
3490
- var MenuContainer = import_styled_components33.default.div`
3465
+ var import_styled_components34 = __toESM(require("styled-components"));
3466
+ var import_jsx_runtime35 = require("react/jsx-runtime");
3467
+ var MenuContainer = import_styled_components34.default.div`
3491
3468
  position: relative;
3492
3469
  display: inline-block;
3493
3470
  `;
3494
- var MenuButton = import_styled_components33.default.button`
3471
+ var MenuButton = import_styled_components34.default.button`
3495
3472
  background: none;
3496
3473
  border: none;
3497
3474
  cursor: pointer;
@@ -3507,7 +3484,7 @@ var MenuButton = import_styled_components33.default.button`
3507
3484
  }
3508
3485
  `;
3509
3486
  var DROPDOWN_MIN_WIDTH = 180;
3510
- var Dropdown = import_styled_components33.default.div`
3487
+ var Dropdown = import_styled_components34.default.div`
3511
3488
  position: fixed;
3512
3489
  top: ${(p) => p.$top}px;
3513
3490
  left: ${(p) => p.$left}px;
@@ -3520,19 +3497,19 @@ var Dropdown = import_styled_components33.default.div`
3520
3497
  min-width: ${DROPDOWN_MIN_WIDTH}px;
3521
3498
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
3522
3499
  `;
3523
- var Divider = import_styled_components33.default.hr`
3500
+ var Divider = import_styled_components34.default.hr`
3524
3501
  border: none;
3525
3502
  border-top: 1px solid rgba(128, 128, 128, 0.3);
3526
3503
  margin: 0.35rem 0;
3527
3504
  `;
3528
3505
  var TrackMenu = ({ items: itemsProp }) => {
3529
- const [open, setOpen] = (0, import_react28.useState)(false);
3530
- const close = (0, import_react28.useCallback)(() => setOpen(false), []);
3506
+ const [open, setOpen] = (0, import_react29.useState)(false);
3507
+ const close = (0, import_react29.useCallback)(() => setOpen(false), []);
3531
3508
  const items = typeof itemsProp === "function" ? itemsProp(close) : itemsProp;
3532
- const [dropdownPos, setDropdownPos] = (0, import_react28.useState)({ top: 0, left: 0 });
3533
- const buttonRef = (0, import_react28.useRef)(null);
3534
- const dropdownRef = (0, import_react28.useRef)(null);
3535
- const updatePosition = (0, import_react28.useCallback)(() => {
3509
+ const [dropdownPos, setDropdownPos] = (0, import_react29.useState)({ top: 0, left: 0 });
3510
+ const buttonRef = (0, import_react29.useRef)(null);
3511
+ const dropdownRef = (0, import_react29.useRef)(null);
3512
+ const updatePosition = (0, import_react29.useCallback)(() => {
3536
3513
  if (!buttonRef.current) return;
3537
3514
  const rect = buttonRef.current.getBoundingClientRect();
3538
3515
  const vw = window.innerWidth;
@@ -3549,7 +3526,7 @@ var TrackMenu = ({ items: itemsProp }) => {
3549
3526
  }
3550
3527
  setDropdownPos({ top, left });
3551
3528
  }, []);
3552
- (0, import_react28.useEffect)(() => {
3529
+ (0, import_react29.useEffect)(() => {
3553
3530
  if (!open) return;
3554
3531
  updatePosition();
3555
3532
  const rafId = requestAnimationFrame(() => updatePosition());
@@ -3563,7 +3540,7 @@ var TrackMenu = ({ items: itemsProp }) => {
3563
3540
  window.removeEventListener("resize", onResize);
3564
3541
  };
3565
3542
  }, [open, updatePosition]);
3566
- (0, import_react28.useEffect)(() => {
3543
+ (0, import_react29.useEffect)(() => {
3567
3544
  if (!open) return;
3568
3545
  const handleClick = (e) => {
3569
3546
  const target = e.target;
@@ -3583,8 +3560,8 @@ var TrackMenu = ({ items: itemsProp }) => {
3583
3560
  document.removeEventListener("keydown", handleKeyDown);
3584
3561
  };
3585
3562
  }, [open]);
3586
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(MenuContainer, { children: [
3587
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
3563
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(MenuContainer, { children: [
3564
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3588
3565
  MenuButton,
3589
3566
  {
3590
3567
  ref: buttonRef,
@@ -3595,19 +3572,19 @@ var TrackMenu = ({ items: itemsProp }) => {
3595
3572
  onMouseDown: (e) => e.stopPropagation(),
3596
3573
  title: "Track menu",
3597
3574
  "aria-label": "Track menu",
3598
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(DotsIcon, { size: 16 })
3575
+ children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(DotsIcon, { size: 16 })
3599
3576
  }
3600
3577
  ),
3601
3578
  open && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
3602
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
3579
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3603
3580
  Dropdown,
3604
3581
  {
3605
3582
  ref: dropdownRef,
3606
3583
  $top: dropdownPos.top,
3607
3584
  $left: dropdownPos.left,
3608
3585
  onMouseDown: (e) => e.stopPropagation(),
3609
- children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react28.default.Fragment, { children: [
3610
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Divider, {}),
3586
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_react29.default.Fragment, { children: [
3587
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Divider, {}),
3611
3588
  item.content
3612
3589
  ] }, item.id))
3613
3590
  }
@@ -3616,6 +3593,26 @@ var TrackMenu = ({ items: itemsProp }) => {
3616
3593
  )
3617
3594
  ] });
3618
3595
  };
3596
+
3597
+ // src/utils/conversions.ts
3598
+ function samplesToSeconds(samples, sampleRate) {
3599
+ return samples / sampleRate;
3600
+ }
3601
+ function secondsToSamples(seconds, sampleRate) {
3602
+ return Math.ceil(seconds * sampleRate);
3603
+ }
3604
+ function samplesToPixels2(samples, samplesPerPixel) {
3605
+ return Math.floor(samples / samplesPerPixel);
3606
+ }
3607
+ function pixelsToSamples(pixels, samplesPerPixel) {
3608
+ return Math.floor(pixels * samplesPerPixel);
3609
+ }
3610
+ function pixelsToSeconds(pixels, samplesPerPixel, sampleRate) {
3611
+ return pixels * samplesPerPixel / sampleRate;
3612
+ }
3613
+ function secondsToPixels2(seconds, samplesPerPixel, sampleRate) {
3614
+ return Math.ceil(seconds * sampleRate / samplesPerPixel);
3615
+ }
3619
3616
  // Annotate the CommonJS export names for ESM import in node:
3620
3617
  0 && (module.exports = {
3621
3618
  AudioPosition,
@@ -3626,9 +3623,12 @@ var TrackMenu = ({ items: itemsProp }) => {
3626
3623
  BaseCheckboxWrapper,
3627
3624
  BaseControlButton,
3628
3625
  BaseInput,
3626
+ BaseInputSmall,
3629
3627
  BaseLabel,
3630
3628
  BaseSelect,
3629
+ BaseSelectSmall,
3631
3630
  BaseSlider,
3631
+ BeatsAndBarsProvider,
3632
3632
  Button,
3633
3633
  ButtonGroup,
3634
3634
  CLIP_BOUNDARY_WIDTH,
@@ -3682,6 +3682,7 @@ var TrackMenu = ({ items: itemsProp }) => {
3682
3682
  darkTheme,
3683
3683
  defaultTheme,
3684
3684
  formatTime,
3685
+ getScaleInfo,
3685
3686
  isWaveformGradient,
3686
3687
  parseTime,
3687
3688
  pixelsToSamples,
@@ -3690,6 +3691,7 @@ var TrackMenu = ({ items: itemsProp }) => {
3690
3691
  samplesToSeconds,
3691
3692
  secondsToPixels,
3692
3693
  secondsToSamples,
3694
+ useBeatsAndBars,
3693
3695
  useClipViewportOrigin,
3694
3696
  useDevicePixelRatio,
3695
3697
  usePlaylistInfo,