@tremolo-ui/react 0.1.4 → 0.1.6

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.
Files changed (43) hide show
  1. package/dist/index.cjs +285 -155
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +80 -32
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.cts +95 -60
  6. package/dist/index.d.cts.map +1 -1
  7. package/dist/index.d.ts +97 -62
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +262 -139
  10. package/dist/index.js.map +1 -1
  11. package/package.json +14 -9
  12. package/src/components/AnimationCanvas/canvas.ts +16 -0
  13. package/src/components/AnimationCanvas/index.tsx +54 -71
  14. package/src/components/Knob/index.css +54 -23
  15. package/src/components/Knob/index.tsx +30 -25
  16. package/src/components/NumberInput/InternalInput.tsx +1 -1
  17. package/src/components/NumberInput/context.tsx +2 -1
  18. package/src/components/NumberInput/index.css +1 -0
  19. package/src/components/NumberInput/index.tsx +5 -5
  20. package/src/components/NumberInput/type.ts +58 -0
  21. package/src/components/Piano/context.tsx +1 -1
  22. package/src/components/Piano/index.css +24 -8
  23. package/src/components/Piano/index.tsx +254 -219
  24. package/src/components/Piano/key.tsx +3 -3
  25. package/src/components/Slider/Scale.tsx +1 -1
  26. package/src/components/Slider/ScaleOption.tsx +1 -1
  27. package/src/components/Slider/Track.tsx +2 -2
  28. package/src/components/Slider/index.tsx +3 -2
  29. package/src/components/Slider/type.ts +26 -0
  30. package/src/components/XYPad/index.tsx +2 -2
  31. package/src/hooks/useAnimationFrame.ts +3 -0
  32. package/src/hooks/useCallbackRef.ts +2 -2
  33. package/src/hooks/useDrag.ts +2 -1
  34. package/src/hooks/useDragWithElement.ts +2 -1
  35. package/src/hooks/useEventListener.ts +3 -0
  36. package/src/hooks/useInterval.ts +3 -0
  37. package/src/hooks/useLongPress.ts +3 -0
  38. package/src/hooks/useMIDIAccess.ts +40 -0
  39. package/src/hooks/useMIDIInput.ts +50 -0
  40. package/src/hooks/useMIDIMessage.ts +24 -0
  41. package/src/hooks/useRefCallbackEvent.ts +4 -0
  42. package/src/index.ts +21 -13
  43. package/src/styles/{index.css → global.css} +1 -1
package/dist/index.js CHANGED
@@ -1,10 +1,8 @@
1
1
  import clsx from "clsx";
2
2
  import React, { createContext, createRef, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useInsertionEffect, useMemo, useRef, useState } from "react";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
- import { clamp, isBlackKey, isWhiteKey, normalizeValue, noteKey, noteKeys, radian, rawValue, stepValue, styleHelper, toFixed, xor } from "@tremolo-ui/functions";
4
+ import { clamp, decimalPart, isBlackKey, isWhiteKey, normalizeValue, noteKey, noteKeys, radian, rawValue, stepValue, styleHelper, toFixed, xor } from "@tremolo-ui/functions";
5
5
  import { createStore, useStore } from "zustand";
6
- import { parseValue } from "@tremolo-ui/functions/NumberInput";
7
- import { generateOptionsList } from "@tremolo-ui/functions/Slider";
8
6
 
9
7
  //#region src/components/AnimationCanvas/canvas.ts
10
8
  const drawingState = [
@@ -27,9 +25,6 @@ const drawingState = [
27
25
  "direction",
28
26
  "imageSmoothingEnabled"
29
27
  ];
30
-
31
- //#endregion
32
- //#region src/components/AnimationCanvas/index.tsx
33
28
  function setDprConfig(canvas, context, width, height, dpr) {
34
29
  canvas.width = width * dpr;
35
30
  canvas.height = height * dpr;
@@ -38,7 +33,10 @@ function setDprConfig(canvas, context, width, height, dpr) {
38
33
  canvas.style.width = `${width}px`;
39
34
  canvas.style.height = `${height}px`;
40
35
  }
41
- function AnimationCanvas({ options, init, draw, width = 100, height = 100, relativeSize, reduceFlickering = true, className, onContextMenu = (event) => event.preventDefault(),...props }) {
36
+
37
+ //#endregion
38
+ //#region src/components/AnimationCanvas/index.tsx
39
+ function AnimationCanvas({ draw, init, animate = true, options, width: _width = 100, height: _height = 100, relativeSize, reduceFlickering = true, className, onContextMenu = (event) => event.preventDefault(),...props }) {
42
40
  const canvasRef = useRef(null);
43
41
  const memoCanvasRef = useRef(null);
44
42
  const reqIdRef = useRef(-1);
@@ -46,27 +44,40 @@ function AnimationCanvas({ options, init, draw, width = 100, height = 100, relat
46
44
  const heightRef = useRef(0);
47
45
  const deltaMemoRef = useRef(-1);
48
46
  const startTimeRef = useRef(-1);
49
- const loop = useCallback((context, width$1, height$1, count) => {
47
+ const loop = useCallback((context, width, height, count) => {
50
48
  const now = performance.now();
51
49
  const deltaTime = now - deltaMemoRef.current;
52
50
  const elapsedTime = now - startTimeRef.current;
53
51
  deltaMemoRef.current = now;
54
- reqIdRef.current = requestAnimationFrame(() => loop(context, width$1, height$1, count + 1));
52
+ if (animate) reqIdRef.current = requestAnimationFrame(() => loop(context, width, height, count + 1));
55
53
  draw(context, {
56
- width: width$1.current,
57
- height: height$1.current,
54
+ width: width.current,
55
+ height: height.current,
58
56
  count: count + 1,
59
57
  deltaTime,
60
58
  elapsedTime,
61
59
  fps: 1e3 / deltaTime
62
60
  });
63
- }, [draw]);
61
+ }, [draw, animate]);
64
62
  useEffect(() => {
65
63
  if (!canvasRef.current) return;
66
64
  const canvas = canvasRef.current;
67
65
  const context = canvas.getContext("2d", options);
68
66
  if (!context) throw new Error("Cannot get canvas context.");
69
67
  const dpr = globalThis.devicePixelRatio;
68
+ const firstRendering = (width, height) => {
69
+ setDprConfig(canvas, context, width, height, dpr);
70
+ widthRef.current = width;
71
+ heightRef.current = height;
72
+ if (init) init(context, {
73
+ width,
74
+ height
75
+ });
76
+ const now = performance.now();
77
+ deltaMemoRef.current = now;
78
+ startTimeRef.current = now;
79
+ loop(context, widthRef, heightRef, -1);
80
+ };
70
81
  if (relativeSize) {
71
82
  const parent = canvas.parentElement;
72
83
  if (!parent) throw new Error("Canvas doesn't have a parent element.");
@@ -74,52 +85,37 @@ function AnimationCanvas({ options, init, draw, width = 100, height = 100, relat
74
85
  const memoContext = memoCanvas?.getContext("2d", options);
75
86
  const ro = new ResizeObserver((entries) => {
76
87
  for (const entry of entries) {
88
+ const w = entry.contentRect.width;
89
+ const h = entry.contentRect.height;
77
90
  const contextMemo = {};
78
- for (const prop of drawingState) contextMemo[prop] = context[prop];
79
- const w$1 = entry.contentRect.width;
80
- const h$1 = entry.contentRect.height;
81
91
  if (reduceFlickering && memoCanvas && memoContext) {
82
- memoCanvas.width = w$1 * dpr;
83
- memoCanvas.height = h$1 * dpr;
92
+ for (const prop of drawingState) contextMemo[prop] = context[prop];
93
+ memoCanvas.width = w * dpr;
94
+ memoCanvas.height = h * dpr;
84
95
  memoContext.scale(1 / dpr, 1 / dpr);
85
96
  if (canvas.width > 0 && canvas.height > 0) memoContext.drawImage(canvas, 0, 0);
86
97
  }
87
- setDprConfig(canvas, context, w$1, h$1, dpr);
88
- widthRef.current = w$1;
89
- heightRef.current = h$1;
90
- for (const prop of drawingState) context[prop] = contextMemo[prop];
91
- if (reduceFlickering && memoContext && memoCanvas && memoCanvas.width > 0 && memoCanvas.height > 0) context.drawImage(memoContext.canvas, 0, 0);
98
+ setDprConfig(canvas, context, w, h, dpr);
99
+ widthRef.current = w;
100
+ heightRef.current = h;
101
+ if (reduceFlickering && memoContext && memoCanvas && memoCanvas.width > 0 && memoCanvas.height > 0) {
102
+ for (const prop of drawingState) context[prop] = contextMemo[prop];
103
+ context.drawImage(memoContext.canvas, 0, 0);
104
+ }
105
+ if (!animate) loop(context, widthRef, heightRef, -1);
92
106
  }
93
107
  });
94
108
  ro.observe(parent);
95
- const w = parent.clientWidth;
96
- const h = parent.clientHeight;
97
- setDprConfig(canvas, context, w, h, dpr);
98
- if (init) init(context, {
99
- width: widthRef.current,
100
- height: heightRef.current
101
- });
102
- const now = performance.now();
103
- deltaMemoRef.current = now;
104
- startTimeRef.current = now;
105
- loop(context, widthRef, heightRef, -1);
109
+ const width = parent.clientWidth;
110
+ const height = parent.clientHeight;
111
+ firstRendering(width, height);
106
112
  return () => {
107
113
  if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current);
108
114
  ro.disconnect();
109
115
  };
110
116
  } else {
111
- const rect = canvas.getBoundingClientRect();
112
- setDprConfig(canvas, context, rect.width, rect.height, dpr);
113
- widthRef.current = rect.width;
114
- heightRef.current = rect.height;
115
- if (init) init(context, {
116
- width: widthRef.current,
117
- height: heightRef.current
118
- });
119
- const now = performance.now();
120
- deltaMemoRef.current = now;
121
- startTimeRef.current = now;
122
- loop(context, widthRef, heightRef, -1);
117
+ const { width, height } = canvas.getBoundingClientRect();
118
+ firstRendering(width, height);
123
119
  return () => {
124
120
  if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current);
125
121
  };
@@ -129,12 +125,13 @@ function AnimationCanvas({ options, init, draw, width = 100, height = 100, relat
129
125
  init,
130
126
  options,
131
127
  reduceFlickering,
132
- relativeSize
128
+ relativeSize,
129
+ animate
133
130
  ]);
134
131
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("canvas", {
135
132
  className: clsx("tremolo-animation-canvas", className),
136
- width: relativeSize ? 0 : width,
137
- height: relativeSize ? 0 : height,
133
+ width: relativeSize ? 0 : _width,
134
+ height: relativeSize ? 0 : _height,
138
135
  ref: canvasRef,
139
136
  onContextMenu,
140
137
  ...props
@@ -147,8 +144,8 @@ function AnimationCanvas({ options, init, draw, width = 100, height = 100, relat
147
144
  //#endregion
148
145
  //#region src/hooks/useCallbackRef.ts
149
146
  /**
150
- * This hook is user-land implementation of the experimental `useEffectEvent` hook.
151
- * React docs: https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event
147
+ * Internal
148
+ * @private
152
149
  */
153
150
  function useCallbackRef(callback, deps = []) {
154
151
  const callbackRef = useRef(() => {
@@ -179,8 +176,7 @@ function useEventListener(target, event, handler, options) {
179
176
  handler
180
177
  ]);
181
178
  return () => {
182
- const node = typeof target === "function" ? target() : target ?? document;
183
- node?.removeEventListener(event, listener, options);
179
+ (typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
184
180
  };
185
181
  }
186
182
 
@@ -205,6 +201,7 @@ function useRefCallbackEvent(event, handler, options, deps = []) {
205
201
  //#endregion
206
202
  //#region src/hooks/useDrag.ts
207
203
  /**
204
+ * @category hooks
208
205
  * @returns [refCallback, pointerDownHandler]
209
206
  */
210
207
  function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
@@ -271,9 +268,7 @@ const AnimationKnob = forwardRef(({ value, min, max, defaultValue = min, startVa
271
268
  const isRelativeSize = typeof (size ?? width) == "string" || typeof height == "string";
272
269
  const onDrag = useCallback((_x, y) => {
273
270
  if (!onChange) return;
274
- const v = rawValue(valueRef.current - y / 100, min, max, skew);
275
- const v2 = clamp(stepValue(v, step), min, max);
276
- onChange(v2);
271
+ onChange(clamp(stepValue(rawValue(valueRef.current - y / 100, min, max, skew), step), min, max));
277
272
  }, [
278
273
  max,
279
274
  min,
@@ -293,10 +288,8 @@ const AnimationKnob = forwardRef(({ value, min, max, defaultValue = min, startVa
293
288
  event.preventDefault();
294
289
  const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
295
290
  let v;
296
- if (keyboard[0] == "normalized") {
297
- const n = normalizeValue(value, min, max, skew);
298
- v = rawValue(n + x, min, max, skew);
299
- } else v = value + x;
291
+ if (keyboard[0] == "normalized") v = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
292
+ else v = value + x;
300
293
  onChange(clamp(stepValue(v, step), min, max));
301
294
  }
302
295
  }, [
@@ -323,10 +316,8 @@ const AnimationKnob = forwardRef(({ value, min, max, defaultValue = min, startVa
323
316
  event.preventDefault();
324
317
  const x = event.deltaY > 0 ? -wheel[1] : wheel[1];
325
318
  let v;
326
- if (wheel[0] == "normalized") {
327
- const n = normalizeValue(value, min, max, skew);
328
- v = rawValue(n + x, min, max, skew);
329
- } else v = value + x;
319
+ if (wheel[0] == "normalized") v = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
320
+ else v = value + x;
330
321
  if (onChange) onChange(clamp(stepValue(v, step), min, max));
331
322
  }, { passive: false }, [
332
323
  wheel,
@@ -438,20 +429,12 @@ function DragObserver(props) {
438
429
  *
439
430
  * @category Knob
440
431
  */
441
- const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = min, startValue = min, size = 50, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], enableDoubleClickDefault = true, disabled = false, readonly = false, activeLine, inactiveLine, thumb, thumbLine, onChange, onKeyDown, onPointerDown, onDoubleClick, className, classes,...props }, ref) => {
432
+ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = min, startValue = min, size = 50, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], enableDoubleClickDefault = true, disabled = false, readonly = false, activeLine, inactiveLine, thumb, thumbLine, thumbSize = 84, thumbLineWeight = 6, thumbLineLength = 35, lineWeight = 6, angleRange = 270, onChange, onKeyDown, onPointerDown, onDoubleClick, className, classes,...props }, forwardedRef) => {
442
433
  const valueRef = useRef(0);
443
434
  const elmRef = useRef(null);
444
- const padding = 8;
445
- const thumbLineWeight = 6;
446
- const thumbLineLength = 35;
447
- const lineWeight = size * .06;
448
- const p = normalizeValue(value, min, max, skew);
449
- const s = normalizeValue(startValue, min, max, skew);
450
435
  const onDrag = useCallback((_x, y) => {
451
436
  if (!onChange || readonly) return;
452
- const v = rawValue(valueRef.current - y / 100, min, max, skew);
453
- const v2 = clamp(stepValue(v, step), min, max);
454
- onChange(v2);
437
+ onChange(clamp(stepValue(rawValue(valueRef.current - y / 100, min, max, skew), step), min, max));
455
438
  }, [
456
439
  max,
457
440
  min,
@@ -462,10 +445,8 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
462
445
  ]);
463
446
  const updateValueByEvent = useCallback((eventType, x) => {
464
447
  let newValue;
465
- if (eventType == "normalized") {
466
- const n = normalizeValue(value, min, max, skew);
467
- newValue = rawValue(n + x, min, max, skew);
468
- } else newValue = value + x;
448
+ if (eventType == "normalized") newValue = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
449
+ else newValue = value + x;
469
450
  return clamp(stepValue(newValue, step), min, max);
470
451
  }, [
471
452
  max,
@@ -515,7 +496,7 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
515
496
  readonly,
516
497
  updateValueByEvent
517
498
  ]);
518
- useImperativeHandle(ref, () => {
499
+ useImperativeHandle(forwardedRef, () => {
519
500
  return {
520
501
  focus() {
521
502
  elmRef.current?.focus();
@@ -525,11 +506,15 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
525
506
  }
526
507
  };
527
508
  }, []);
528
- const center = size / 2;
529
- const r1 = -135;
530
- const r2 = -135 + Math.min(p, s) * 270;
531
- const r3 = -135 + Math.max(p, s) * 270;
532
- const r4 = 135;
509
+ const viewBoxSize = 100;
510
+ const center = viewBoxSize / 2;
511
+ const padding = (viewBoxSize - clamp(thumbSize, 0, 100)) / 2;
512
+ const p = normalizeValue(value, min, max, skew);
513
+ const s = normalizeValue(startValue, min, max, skew);
514
+ const r1 = -angleRange / 2;
515
+ const r2 = r1 + Math.min(p, s) * angleRange;
516
+ const r3 = r1 + Math.max(p, s) * angleRange;
517
+ const r4 = angleRange / 2;
533
518
  const x1 = center + center * Math.cos(radian(r1 - 90));
534
519
  const y1 = center + center * Math.sin(radian(r1 - 90));
535
520
  const x2 = center + center * Math.cos(radian(r2 - 90));
@@ -539,12 +524,13 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
539
524
  const x4 = center + center * Math.cos(radian(r4 - 90));
540
525
  const y4 = center + center * Math.sin(radian(r4 - 90));
541
526
  return /* @__PURE__ */ jsxs("svg", {
542
- className: clsx("tremolo-knob", className),
543
527
  ref: (div) => {
544
528
  elmRef.current = div;
545
529
  wheelRefCallback(div);
546
530
  touchMoveRefCallback(div);
547
531
  },
532
+ className: clsx("tremolo-knob", className),
533
+ viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`,
548
534
  width: size,
549
535
  height: size,
550
536
  tabIndex: 0,
@@ -594,7 +580,7 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
594
580
  children: [/* @__PURE__ */ jsx("circle", {
595
581
  cx: "50%",
596
582
  cy: "50%",
597
- r: `${50 - padding}%`,
583
+ r: `${thumbSize / 2}%`,
598
584
  fill: thumb || "currentColor"
599
585
  }), /* @__PURE__ */ jsx("line", {
600
586
  className: clsx("tremolo-knob-thumb-line", classes?.thumbLine),
@@ -605,7 +591,7 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
605
591
  stroke: thumbLine || "currentColor",
606
592
  strokeWidth: `${thumbLineWeight}%`,
607
593
  style: {
608
- transform: `rotate(${-135 + p * 270}deg)`,
594
+ transform: `rotate(${r1 + p * angleRange}deg)`,
609
595
  transformOrigin: "50% 50%"
610
596
  }
611
597
  })]
@@ -614,6 +600,47 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
614
600
  });
615
601
  });
616
602
 
603
+ //#endregion
604
+ //#region src/components/NumberInput/type.ts
605
+ function selectUnit(units, value) {
606
+ let i = 0;
607
+ for (; i < units.length; i++) if (Math.abs(units[i][1]) > Math.abs(value)) break;
608
+ return units[Math.max(0, i - 1)];
609
+ }
610
+ function parseValue(inputString, units, digit) {
611
+ const str = inputString.trim();
612
+ if (!units || typeof units == "string") {
613
+ const m$1 = str.match(/-?\d+(\.\d+)?/);
614
+ let v = Number(m$1?.[0] ?? "0");
615
+ v = isNaN(v) ? 0 : v;
616
+ return {
617
+ rawValue: v,
618
+ formatValue: (digit != void 0 ? v.toFixed(digit) : v) + (units ?? ""),
619
+ unit: units ?? ""
620
+ };
621
+ }
622
+ const unitList = units.map(([u, _s]) => u);
623
+ const scaleList = units.map(([_u, s]) => s);
624
+ let rawValue$1 = 0;
625
+ let unit = unitList[0];
626
+ let formatValue = (digit != void 0 ? rawValue$1.toFixed(digit) : rawValue$1) + unit;
627
+ const m = str.match(/^(-?\d+(\.\d+)?)\s*(\w*)$/);
628
+ if (m) {
629
+ rawValue$1 = Number(m[1]) || 0;
630
+ const uIndex = unitList.indexOf(m[3]);
631
+ rawValue$1 *= uIndex != -1 ? scaleList[uIndex] : 1;
632
+ const [u, scale] = selectUnit(units, rawValue$1);
633
+ const v = rawValue$1 / scale;
634
+ formatValue = (digit != void 0 ? v.toFixed(digit) : v) + u;
635
+ unit = u;
636
+ }
637
+ return {
638
+ rawValue: rawValue$1,
639
+ formatValue,
640
+ unit
641
+ };
642
+ }
643
+
617
644
  //#endregion
618
645
  //#region src/components/NumberInput/context.tsx
619
646
  function safeClamp(value, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
@@ -637,8 +664,7 @@ const createNumberInputStore = (initProps) => {
637
664
  valueAsNumber: parseValue(String(initProps?.value || ""), initProps?.units, initProps?.digit).rawValue,
638
665
  increment: () => set((state) => {
639
666
  if (state.readonly) return {};
640
- const current = parseValue(state.value, state.units, state.digit).rawValue;
641
- let next = current + (state.step ?? 1);
667
+ let next = parseValue(state.value, state.units, state.digit).rawValue + (state.step ?? 1);
642
668
  if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
643
669
  const v = parseValue(String(next), state.units, state.digit).formatValue;
644
670
  state?.onChange?.(next, v);
@@ -649,8 +675,7 @@ const createNumberInputStore = (initProps) => {
649
675
  }),
650
676
  decrement: () => set((state) => {
651
677
  if (state.readonly) return {};
652
- const current = parseValue(state.value, state.units, state.digit).rawValue;
653
- let next = current - (state.step ?? 1);
678
+ let next = parseValue(state.value, state.units, state.digit).rawValue - (state.step ?? 1);
654
679
  if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
655
680
  const v = parseValue(String(next), state.units, state.digit).formatValue;
656
681
  state?.onChange?.(next, v);
@@ -722,8 +747,7 @@ const InternalInput = forwardRef(({ disabled, readonly, selectWithFocus, blurOnE
722
747
  let newValue;
723
748
  if (eventType == "normalized") {
724
749
  if (!min || !max) throw new Error("required parameter: min, max");
725
- const n = normalizeValue(valueAsNumber, min, max);
726
- newValue = rawValue(n + x, min, max);
750
+ newValue = rawValue(normalizeValue(valueAsNumber, min, max) + x, min, max);
727
751
  return clamp(stepValue(newValue, step), min, max);
728
752
  } else {
729
753
  newValue = valueAsNumber + x;
@@ -839,6 +863,9 @@ function Stepper({ dynamic = true, children, className,...props }) {
839
863
 
840
864
  //#endregion
841
865
  //#region src/hooks/useInterval.ts
866
+ /**
867
+ * @category hooks
868
+ */
842
869
  function useInterval(callback, delay) {
843
870
  const savedCallback = useRef(callback);
844
871
  useEffect(() => {
@@ -857,6 +884,9 @@ function useInterval(callback, delay) {
857
884
 
858
885
  //#endregion
859
886
  //#region src/hooks/useLongPress.ts
887
+ /**
888
+ * @category hooks
889
+ */
860
890
  function useLongPress(callback, initialDelay = 500, interval = 40) {
861
891
  const [pressed, setPressed] = useState(false);
862
892
  const [delay, setDelay] = useState(initialDelay);
@@ -882,8 +912,7 @@ function IncrementStepper({ size = 12, children, className,...props }) {
882
912
  const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
883
913
  const readonly = useNumberInputContext((s) => s.readonly);
884
914
  const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
885
- const increment = useNumberInputContext((s) => s.increment);
886
- const press = useLongPress(increment);
915
+ const press = useLongPress(useNumberInputContext((s) => s.increment));
887
916
  return /* @__PURE__ */ jsx("div", {
888
917
  className: clsx("tremolo-number-input-increment-stepper", className),
889
918
  role: "button",
@@ -914,8 +943,7 @@ function DecrementStepper({ size = 12, children, className,...props }) {
914
943
  const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
915
944
  const readonly = useNumberInputContext((s) => s.readonly);
916
945
  const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
917
- const decrement = useNumberInputContext((s) => s.decrement);
918
- const press = useLongPress(decrement);
946
+ const press = useLongPress(useNumberInputContext((s) => s.decrement));
919
947
  return /* @__PURE__ */ jsx("div", {
920
948
  className: clsx("tremolo-number-input-decrement-stepper", className),
921
949
  role: "button",
@@ -944,7 +972,7 @@ function DecrementStepper({ size = 12, children, className,...props }) {
944
972
  * Input with some useful functions for entering numerical values.
945
973
  * @category NumberInput
946
974
  */
947
- const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = false, disabled = false, digit, variant = "outline", selectWithFocus = "none", blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ["raw", 1], keyboard = ["raw", 1], activeColor, wrapperClassName, className, onChange, onFocus, onBlur, children,...props }, ref) => {
975
+ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = false, disabled = false, digit, variant = "outline", selectWithFocus = "none", blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ["raw", 1], keyboard = ["raw", 1], activeColor, wrapperClassName, className, onChange, onFocus, onBlur, children,...props }, forwardedRef) => {
948
976
  const colors = { "--active-color": activeColor };
949
977
  return /* @__PURE__ */ jsx(NumberInputProvider, {
950
978
  value: String(value),
@@ -963,7 +991,7 @@ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = f
963
991
  "data-stepper": !!children,
964
992
  "data-variant": variant,
965
993
  children: [/* @__PURE__ */ jsx(InternalInput, {
966
- ref,
994
+ ref: forwardedRef,
967
995
  readonly,
968
996
  disabled,
969
997
  selectWithFocus,
@@ -984,6 +1012,7 @@ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = f
984
1012
  //#endregion
985
1013
  //#region src/hooks/useDragWithElement.ts
986
1014
  /**
1015
+ * @category hooks
987
1016
  * @returns [refCallback, pointerDownHandler]
988
1017
  */
989
1018
  function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
@@ -1063,9 +1092,7 @@ function usePianoContext(selector) {
1063
1092
  //#region src/components/Piano/KeyLabel.tsx
1064
1093
  /** @category Piano */
1065
1094
  function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label,...props }) {
1066
- const noteRange = usePianoContext((s) => s.noteRange);
1067
- const noteRangeArray = getNoteRangeArray(noteRange);
1068
- const index = noteRangeArray.indexOf(__note);
1095
+ const index = getNoteRangeArray(usePianoContext((s) => s.noteRange)).indexOf(__note);
1069
1096
  const content = label ? label(__note, index) : __label && __label(__note, index);
1070
1097
  return (content != void 0 || content != null) && /* @__PURE__ */ jsx("div", {
1071
1098
  className: clsx("tremolo-piano-key-label-wrapper", wrapperClassName),
@@ -1091,8 +1118,7 @@ const KeyImpl = forwardRef(({ keyType, noteNumber, width, height, bg, color, act
1091
1118
  const onPlayNote = usePianoContext((s) => s.onPlayNote);
1092
1119
  const onStopNote = usePianoContext((s) => s.onStopNote);
1093
1120
  const label = usePianoContext((s) => s.label);
1094
- const notePosition = usePianoContext((s) => s.notePosition);
1095
- const position = notePosition(noteNumber);
1121
+ const position = usePianoContext((s) => s.notePosition)(noteNumber);
1096
1122
  const disabled = noteNumber > midiMax;
1097
1123
  const colors = {
1098
1124
  "--bg": bg,
@@ -1102,10 +1128,10 @@ const KeyImpl = forwardRef(({ keyType, noteNumber, width, height, bg, color, act
1102
1128
  };
1103
1129
  useImperativeHandle(ref, () => {
1104
1130
  return {
1105
- play() {
1131
+ play(velocity) {
1106
1132
  if (disabled) return;
1107
1133
  setPlayed(true);
1108
- if (onPlayNote) onPlayNote(noteNumber);
1134
+ if (onPlayNote) onPlayNote(noteNumber, velocity);
1109
1135
  },
1110
1136
  stop() {
1111
1137
  setPlayed(false);
@@ -1196,12 +1222,9 @@ const SHORTCUTS = { HOME_ROW: { keys: [
1196
1222
  //#endregion
1197
1223
  //#region src/components/Piano/index.tsx
1198
1224
  /**
1199
- * Piano component
1200
- *
1201
- * TODO:
1202
- * - add scale highlight
1225
+ * [noteRange.first, noteRange.first + 1 ..., noteRange.last]
1226
+ * @category Piano
1203
1227
  */
1204
- /** @category Piano */
1205
1228
  function getNoteRangeArray(noteRange) {
1206
1229
  return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
1207
1230
  }
@@ -1210,7 +1233,7 @@ const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth;
1210
1233
  * Customizable piano component.
1211
1234
  * @category Piano
1212
1235
  */
1213
- function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? "100%" : 160, whiteNoteWidth: _whiteNoteWidth = defaultWhiteKeyWidth, style, className, onPlayNote, onStopNote, label, children, onPointerDown,...props }) {
1236
+ const Piano = forwardRef(({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? "100%" : 160, whiteNoteWidth: _whiteNoteWidth = defaultWhiteKeyWidth, style, className, onPlayNote, onStopNote, label, children, onPointerDown,...props }, forwardedRef) => {
1214
1237
  const [whiteNoteWidth, setWhiteNoteWidth] = useState(_whiteNoteWidth);
1215
1238
  const keyRefs = useRef([]);
1216
1239
  for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = createRef();
@@ -1281,9 +1304,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
1281
1304
  ]);
1282
1305
  const onDrag = useCallback((perX, perY) => {
1283
1306
  if (!pianoRef.current) return;
1284
- const x = perX * staticWidth;
1285
- const y = perY * pianoRef.current.clientHeight;
1286
- const note = getHitKeyIndex(x, y);
1307
+ const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
1287
1308
  const index = noteRangeArray.indexOf(note);
1288
1309
  if (index == -1) return;
1289
1310
  if (hitKeyIndex.current != index) {
@@ -1337,6 +1358,25 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
1337
1358
  const index = keyboardShortcuts.keys.indexOf(e.key);
1338
1359
  if (index != -1) keyRefs.current[index]?.current?.stop();
1339
1360
  });
1361
+ useImperativeHandle(forwardedRef, () => {
1362
+ return {
1363
+ playNote(note, velocity) {
1364
+ const index = noteRangeArray.indexOf(note);
1365
+ if (index != -1) {
1366
+ if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
1367
+ } else onPlayNote?.(note, velocity);
1368
+ },
1369
+ stopNote(note) {
1370
+ const index = noteRangeArray.indexOf(note);
1371
+ if (index != -1) keyRefs.current[index]?.current?.stop();
1372
+ else onStopNote?.(note);
1373
+ }
1374
+ };
1375
+ }, [
1376
+ noteRangeArray,
1377
+ onPlayNote,
1378
+ onStopNote
1379
+ ]);
1340
1380
  return /* @__PURE__ */ jsx(PianoProvider, {
1341
1381
  notePosition,
1342
1382
  noteRange,
@@ -1373,7 +1413,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
1373
1413
  }, note))
1374
1414
  })
1375
1415
  });
1376
- }
1416
+ });
1377
1417
 
1378
1418
  //#endregion
1379
1419
  //#region src/components/Slider/context.tsx
@@ -1461,6 +1501,22 @@ function ScaleOption({ value, type = "mark-number", label, thickness = 1, length
1461
1501
  });
1462
1502
  }
1463
1503
 
1504
+ //#endregion
1505
+ //#region src/components/Slider/type.ts
1506
+ function generateOptionsList(options, min, max, step) {
1507
+ const optionsList = [];
1508
+ const per = options[0] == "step" ? step : options[0];
1509
+ const count = Math.floor(max / per) - Math.ceil(min / per) + 1;
1510
+ for (let i = 0; i < count; i++) {
1511
+ const value = toFixed(per * (Math.ceil(min / per) + i), decimalPart(per)?.length);
1512
+ optionsList.push({
1513
+ value,
1514
+ type: options[1]
1515
+ });
1516
+ }
1517
+ return optionsList;
1518
+ }
1519
+
1464
1520
  //#endregion
1465
1521
  //#region src/components/Slider/Scale.tsx
1466
1522
  /** @category Slider */
@@ -1551,7 +1607,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
1551
1607
  "data-vertical": vertical,
1552
1608
  style: !defaultStyle ? style : {
1553
1609
  ...colors,
1554
- background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive, #eee) ${__percent}%, var(--active, #7998ec) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active, #7998ec) ${__percent}%, var(--inactive, #eee) ${__percent}%)`,
1610
+ background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${__percent}%, var(--active) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active) ${__percent}%, var(--inactive) ${__percent}%)`,
1555
1611
  borderRadius: styleHelper(thickness, "/", 2),
1556
1612
  width: !vertical ? length : thickness,
1557
1613
  height: vertical ? length : thickness,
@@ -1568,7 +1624,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
1568
1624
  * Customizable slider
1569
1625
  * @category Slider
1570
1626
  */
1571
- const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = false, reverse = false, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], disabled = false, readonly = false, onChange, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown,...props }, ref) => {
1627
+ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = false, reverse = false, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], disabled = false, readonly = false, onChange, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown,...props }, forwardedRef) => {
1572
1628
  const trackElementRef = useRef(null);
1573
1629
  const thumbRef = useRef(null);
1574
1630
  const p = toFixed(normalizeValue(value, min, max, skew) * 100);
@@ -1588,9 +1644,7 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
1588
1644
  const onDrag = useCallback((nx, ny) => {
1589
1645
  if (!onChange || readonly) return;
1590
1646
  const n = vertical ? ny : nx;
1591
- const v = rawValue(displayReversed ? 1 - n : n, min, max, skew);
1592
- const v2 = clamp(stepValue(v, step), min, max);
1593
- onChange(v2);
1647
+ onChange(clamp(stepValue(rawValue(displayReversed ? 1 - n : n, min, max, skew), step), min, max));
1594
1648
  }, [
1595
1649
  displayReversed,
1596
1650
  max,
@@ -1603,10 +1657,8 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
1603
1657
  ]);
1604
1658
  const updateValueByEvent = useCallback((eventType, x) => {
1605
1659
  let newValue;
1606
- if (eventType == "normalized") {
1607
- const n = normalizeValue(value, min, max, skew);
1608
- newValue = rawValue(n + x, min, max, skew);
1609
- } else newValue = value + x;
1660
+ if (eventType == "normalized") newValue = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
1661
+ else newValue = value + x;
1610
1662
  return clamp(stepValue(newValue, step), min, max);
1611
1663
  }, [
1612
1664
  max,
@@ -1669,7 +1721,7 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
1669
1721
  onChange,
1670
1722
  updateValueByEvent
1671
1723
  ]);
1672
- useImperativeHandle(ref, () => {
1724
+ useImperativeHandle(forwardedRef, () => {
1673
1725
  return {
1674
1726
  focus() {
1675
1727
  thumbRef.current?.focus();
@@ -1753,11 +1805,10 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
1753
1805
  /** @category WheelObserver */
1754
1806
  function WheelObserver(props) {
1755
1807
  const { children, onWheel, as: Component = "div",...attributes } = props;
1756
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1757
- if (onWheel) onWheel(event);
1758
- }, { passive: false }, [onWheel]);
1759
1808
  return /* @__PURE__ */ jsx(Component, {
1760
- ref: wheelRefCallback,
1809
+ ref: useRefCallbackEvent("wheel", (event) => {
1810
+ if (onWheel) onWheel(event);
1811
+ }, { passive: false }, [onWheel]),
1761
1812
  ...attributes,
1762
1813
  children
1763
1814
  });
@@ -1830,7 +1881,7 @@ const defaultValueOptions = {
1830
1881
  * Simple XYPad
1831
1882
  * @category XYPad
1832
1883
  */
1833
- const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, ref) => {
1884
+ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, forwardedRef) => {
1834
1885
  const x = useMemo(() => {
1835
1886
  return {
1836
1887
  ...defaultValueOptions,
@@ -1861,9 +1912,7 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
1861
1912
  if (!onChange || readonly) return;
1862
1913
  const vx = rawValue(x.reverse ? 1 - nx$1 : nx$1, x.min, x.max, x.skew);
1863
1914
  const vy = rawValue(y.reverse ? 1 - ny$1 : ny$1, y.min, y.max, y.skew);
1864
- const newX = clamp(stepValue(vx, x.step), x.min, x.max);
1865
- const newY = clamp(stepValue(vy, y.step), y.min, y.max);
1866
- onChange(newX, newY);
1915
+ onChange(clamp(stepValue(vx, x.step), x.min, x.max), clamp(stepValue(vy, y.step), y.min, y.max));
1867
1916
  }, [
1868
1917
  onChange,
1869
1918
  readonly,
@@ -1880,10 +1929,8 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
1880
1929
  ]);
1881
1930
  const updateValueByEvent = useCallback((eventType, target, x$1) => {
1882
1931
  let v;
1883
- if (eventType == "normalized") {
1884
- const n = normalizeValue(target.value, target.min, target.max, target.skew);
1885
- v = rawValue(n + x$1, target.min, target.max, target.skew);
1886
- } else v = target.value + x$1;
1932
+ if (eventType == "normalized") v = rawValue(normalizeValue(target.value, target.min, target.max, target.skew) + x$1, target.min, target.max, target.skew);
1933
+ else v = target.value + x$1;
1887
1934
  return clamp(stepValue(v, target.step), target.min, target.max);
1888
1935
  }, []);
1889
1936
  const handleKeyDown = useCallback((event) => {
@@ -1950,7 +1997,7 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
1950
1997
  readonly,
1951
1998
  updateValueByEvent
1952
1999
  ]);
1953
- useImperativeHandle(ref, () => {
2000
+ useImperativeHandle(forwardedRef, () => {
1954
2001
  return {
1955
2002
  focus() {
1956
2003
  thumbRef.current?.focus();
@@ -2013,6 +2060,9 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
2013
2060
 
2014
2061
  //#endregion
2015
2062
  //#region src/hooks/useAnimationFrame.ts
2063
+ /**
2064
+ * @category hooks
2065
+ */
2016
2066
  function useAnimationFrame(callback = () => {}, deps = []) {
2017
2067
  const reqIdRef = useRef(-1);
2018
2068
  const loop = useCallback(() => {
@@ -2026,5 +2076,78 @@ function useAnimationFrame(callback = () => {}, deps = []) {
2026
2076
  }
2027
2077
 
2028
2078
  //#endregion
2029
- export { AnimationCanvas, AnimationKnob, BlackKey, DecrementStepper, DragObserver, IncrementStepper, KeyLabel, Knob, NumberInput, Piano, SHORTCUTS, Scale, ScaleOption, Slider, SliderThumb, SliderTrack, Stepper, WheelObserver, WhiteKey, XYPad, XYPadArea, XYPadThumb, getNoteRangeArray, useAnimationFrame, useDrag, useDragWithElement, useEventListener, useInterval, useLongPress, useSliderContext };
2079
+ //#region src/hooks/useMIDIAccess.ts
2080
+ /** @private */
2081
+ const PERMISSION_DENIED = "PERMISSION_DENIED";
2082
+ /** @private */
2083
+ const NOT_SUPPORTED = "NOT_SUPPORTED";
2084
+ /**
2085
+ * Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
2086
+ * @category hooks
2087
+ */
2088
+ function useMIDIAccess(requestOnMount = true) {
2089
+ const [midiAccess, setMidiAccess] = useState(null);
2090
+ const [error, setError] = useState(null);
2091
+ const request = useCallback(() => {
2092
+ if (navigator.requestMIDIAccess) navigator.requestMIDIAccess().then((access) => {
2093
+ setMidiAccess(access);
2094
+ setError(null);
2095
+ }).catch(() => {
2096
+ setError(PERMISSION_DENIED);
2097
+ });
2098
+ else setError(NOT_SUPPORTED);
2099
+ }, []);
2100
+ useEffect(() => {
2101
+ if (requestOnMount) request();
2102
+ }, [requestOnMount, request]);
2103
+ return {
2104
+ request,
2105
+ midiAccess,
2106
+ error
2107
+ };
2108
+ }
2109
+
2110
+ //#endregion
2111
+ //#region src/hooks/useMIDIMessage.ts
2112
+ /**
2113
+ * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
2114
+ * @category hooks
2115
+ */
2116
+ function useMIDIMessage(midiAccess, onMIDIMessage) {
2117
+ useEffect(() => {
2118
+ if (!midiAccess) return;
2119
+ for (const input of midiAccess.inputs.values()) input.addEventListener("midimessage", onMIDIMessage);
2120
+ return () => {
2121
+ for (const input of midiAccess.inputs.values()) input.removeEventListener("midimessage", onMIDIMessage);
2122
+ };
2123
+ }, [midiAccess, onMIDIMessage]);
2124
+ }
2125
+
2126
+ //#endregion
2127
+ //#region src/hooks/useMIDIInput.ts
2128
+ const MIDI_EVENT_TO_NUMBER = {
2129
+ NOTE_ON: 144,
2130
+ NOTE_OFF: 128,
2131
+ PITCH_BEND: 224
2132
+ };
2133
+ /**
2134
+ * Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
2135
+ * @category hooks
2136
+ */
2137
+ function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
2138
+ useMIDIMessage(midiAccess, useCallback((event) => {
2139
+ if (!event.data) return;
2140
+ const kind = event.data[0] & 240;
2141
+ if (kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF || kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0) onNoteOffEvent?.(event.data[1]);
2142
+ else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) onNoteOnEvent?.(event.data[1], event.data[2]);
2143
+ else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) onPitchBendEvent?.(event.data[1], event.data[2]);
2144
+ }, [
2145
+ onNoteOffEvent,
2146
+ onNoteOnEvent,
2147
+ onPitchBendEvent
2148
+ ]));
2149
+ }
2150
+
2151
+ //#endregion
2152
+ export { AnimationCanvas, AnimationKnob, BlackKey, DecrementStepper, DragObserver, IncrementStepper, KeyLabel, Knob, NOT_SUPPORTED, NumberInput, PERMISSION_DENIED, Piano, SHORTCUTS, Scale, ScaleOption, Slider, SliderThumb, SliderTrack, Stepper, WheelObserver, WhiteKey, XYPad, XYPadArea, XYPadThumb, getNoteRangeArray, useAnimationFrame, useDrag, useDragWithElement, useEventListener, useInterval, useLongPress, useMIDIAccess, useMIDIInput, useMIDIMessage, useSliderContext };
2030
2153
  //# sourceMappingURL=index.js.map