@tremolo-ui/react 0.1.5 → 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.
- package/dist/index.cjs +171 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +79 -32
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +80 -56
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +82 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +160 -91
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Knob/index.css +54 -23
- package/src/components/Knob/index.tsx +30 -25
- package/src/components/NumberInput/index.tsx +3 -4
- package/src/components/Piano/context.tsx +1 -1
- package/src/components/Piano/index.css +24 -8
- package/src/components/Piano/index.tsx +253 -217
- package/src/components/Piano/key.tsx +3 -3
- package/src/components/Slider/Track.tsx +2 -2
- package/src/components/Slider/index.tsx +2 -2
- package/src/components/XYPad/index.tsx +2 -2
- package/src/hooks/useAnimationFrame.ts +3 -0
- package/src/hooks/useCallbackRef.ts +2 -2
- package/src/hooks/useDrag.ts +2 -1
- package/src/hooks/useDragWithElement.ts +2 -1
- package/src/hooks/useEventListener.ts +3 -0
- package/src/hooks/useInterval.ts +3 -0
- package/src/hooks/useLongPress.ts +3 -0
- package/src/hooks/useMIDIAccess.ts +40 -0
- package/src/hooks/useMIDIInput.ts +50 -0
- package/src/hooks/useMIDIMessage.ts +24 -0
- package/src/hooks/useRefCallbackEvent.ts +4 -0
- package/src/index.ts +19 -12
- package/src/styles/global.css +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
}) : target, mod));
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
let clsx = require("clsx");
|
|
25
|
+
clsx = __toESM(clsx);
|
|
26
|
+
let react = require("react");
|
|
27
|
+
react = __toESM(react);
|
|
28
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
29
|
+
let __tremolo_ui_functions = require("@tremolo-ui/functions");
|
|
30
|
+
let zustand = require("zustand");
|
|
29
31
|
|
|
30
32
|
//#region src/components/AnimationCanvas/canvas.ts
|
|
31
33
|
const drawingState = [
|
|
@@ -167,8 +169,8 @@ function AnimationCanvas({ draw, init, animate = true, options, width: _width =
|
|
|
167
169
|
//#endregion
|
|
168
170
|
//#region src/hooks/useCallbackRef.ts
|
|
169
171
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
+
* Internal
|
|
173
|
+
* @private
|
|
172
174
|
*/
|
|
173
175
|
function useCallbackRef(callback, deps = []) {
|
|
174
176
|
const callbackRef = (0, react.useRef)(() => {
|
|
@@ -199,8 +201,7 @@ function useEventListener(target, event, handler, options) {
|
|
|
199
201
|
handler
|
|
200
202
|
]);
|
|
201
203
|
return () => {
|
|
202
|
-
|
|
203
|
-
node?.removeEventListener(event, listener, options);
|
|
204
|
+
(typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
|
|
204
205
|
};
|
|
205
206
|
}
|
|
206
207
|
|
|
@@ -225,6 +226,7 @@ function useRefCallbackEvent(event, handler, options, deps = []) {
|
|
|
225
226
|
//#endregion
|
|
226
227
|
//#region src/hooks/useDrag.ts
|
|
227
228
|
/**
|
|
229
|
+
* @category hooks
|
|
228
230
|
* @returns [refCallback, pointerDownHandler]
|
|
229
231
|
*/
|
|
230
232
|
function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
|
|
@@ -291,9 +293,7 @@ const AnimationKnob = (0, react.forwardRef)(({ value, min, max, defaultValue = m
|
|
|
291
293
|
const isRelativeSize = typeof (size ?? width) == "string" || typeof height == "string";
|
|
292
294
|
const onDrag = (0, react.useCallback)((_x, y) => {
|
|
293
295
|
if (!onChange) return;
|
|
294
|
-
|
|
295
|
-
const v2 = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max);
|
|
296
|
-
onChange(v2);
|
|
296
|
+
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(valueRef.current - y / 100, min, max, skew), step), min, max));
|
|
297
297
|
}, [
|
|
298
298
|
max,
|
|
299
299
|
min,
|
|
@@ -313,10 +313,8 @@ const AnimationKnob = (0, react.forwardRef)(({ value, min, max, defaultValue = m
|
|
|
313
313
|
event.preventDefault();
|
|
314
314
|
const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
315
315
|
let v;
|
|
316
|
-
if (keyboard[0] == "normalized")
|
|
317
|
-
|
|
318
|
-
v = (0, __tremolo_ui_functions.rawValue)(n + x, min, max, skew);
|
|
319
|
-
} else v = value + x;
|
|
316
|
+
if (keyboard[0] == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
317
|
+
else v = value + x;
|
|
320
318
|
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max));
|
|
321
319
|
}
|
|
322
320
|
}, [
|
|
@@ -343,10 +341,8 @@ const AnimationKnob = (0, react.forwardRef)(({ value, min, max, defaultValue = m
|
|
|
343
341
|
event.preventDefault();
|
|
344
342
|
const x = event.deltaY > 0 ? -wheel[1] : wheel[1];
|
|
345
343
|
let v;
|
|
346
|
-
if (wheel[0] == "normalized")
|
|
347
|
-
|
|
348
|
-
v = (0, __tremolo_ui_functions.rawValue)(n + x, min, max, skew);
|
|
349
|
-
} else v = value + x;
|
|
344
|
+
if (wheel[0] == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
345
|
+
else v = value + x;
|
|
350
346
|
if (onChange) onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max));
|
|
351
347
|
}, { passive: false }, [
|
|
352
348
|
wheel,
|
|
@@ -458,20 +454,12 @@ function DragObserver(props) {
|
|
|
458
454
|
*
|
|
459
455
|
* @category Knob
|
|
460
456
|
*/
|
|
461
|
-
const Knob = (0, react.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 },
|
|
457
|
+
const Knob = (0, react.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) => {
|
|
462
458
|
const valueRef = (0, react.useRef)(0);
|
|
463
459
|
const elmRef = (0, react.useRef)(null);
|
|
464
|
-
const padding = 8;
|
|
465
|
-
const thumbLineWeight = 6;
|
|
466
|
-
const thumbLineLength = 35;
|
|
467
|
-
const lineWeight = size * .06;
|
|
468
|
-
const p = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
469
|
-
const s = (0, __tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
|
|
470
460
|
const onDrag = (0, react.useCallback)((_x, y) => {
|
|
471
461
|
if (!onChange || readonly) return;
|
|
472
|
-
|
|
473
|
-
const v2 = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max);
|
|
474
|
-
onChange(v2);
|
|
462
|
+
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(valueRef.current - y / 100, min, max, skew), step), min, max));
|
|
475
463
|
}, [
|
|
476
464
|
max,
|
|
477
465
|
min,
|
|
@@ -482,10 +470,8 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
482
470
|
]);
|
|
483
471
|
const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
|
|
484
472
|
let newValue;
|
|
485
|
-
if (eventType == "normalized")
|
|
486
|
-
|
|
487
|
-
newValue = (0, __tremolo_ui_functions.rawValue)(n + x, min, max, skew);
|
|
488
|
-
} else newValue = value + x;
|
|
473
|
+
if (eventType == "normalized") newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
474
|
+
else newValue = value + x;
|
|
489
475
|
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
490
476
|
}, [
|
|
491
477
|
max,
|
|
@@ -535,7 +521,7 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
535
521
|
readonly,
|
|
536
522
|
updateValueByEvent
|
|
537
523
|
]);
|
|
538
|
-
(0, react.useImperativeHandle)(
|
|
524
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
539
525
|
return {
|
|
540
526
|
focus() {
|
|
541
527
|
elmRef.current?.focus();
|
|
@@ -545,11 +531,15 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
545
531
|
}
|
|
546
532
|
};
|
|
547
533
|
}, []);
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
const
|
|
534
|
+
const viewBoxSize = 100;
|
|
535
|
+
const center = viewBoxSize / 2;
|
|
536
|
+
const padding = (viewBoxSize - (0, __tremolo_ui_functions.clamp)(thumbSize, 0, 100)) / 2;
|
|
537
|
+
const p = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
538
|
+
const s = (0, __tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
|
|
539
|
+
const r1 = -angleRange / 2;
|
|
540
|
+
const r2 = r1 + Math.min(p, s) * angleRange;
|
|
541
|
+
const r3 = r1 + Math.max(p, s) * angleRange;
|
|
542
|
+
const r4 = angleRange / 2;
|
|
553
543
|
const x1 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r1 - 90));
|
|
554
544
|
const y1 = center + center * Math.sin((0, __tremolo_ui_functions.radian)(r1 - 90));
|
|
555
545
|
const x2 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r2 - 90));
|
|
@@ -559,12 +549,13 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
559
549
|
const x4 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r4 - 90));
|
|
560
550
|
const y4 = center + center * Math.sin((0, __tremolo_ui_functions.radian)(r4 - 90));
|
|
561
551
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
562
|
-
className: (0, clsx.default)("tremolo-knob", className),
|
|
563
552
|
ref: (div) => {
|
|
564
553
|
elmRef.current = div;
|
|
565
554
|
wheelRefCallback(div);
|
|
566
555
|
touchMoveRefCallback(div);
|
|
567
556
|
},
|
|
557
|
+
className: (0, clsx.default)("tremolo-knob", className),
|
|
558
|
+
viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`,
|
|
568
559
|
width: size,
|
|
569
560
|
height: size,
|
|
570
561
|
tabIndex: 0,
|
|
@@ -614,7 +605,7 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
614
605
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
615
606
|
cx: "50%",
|
|
616
607
|
cy: "50%",
|
|
617
|
-
r: `${
|
|
608
|
+
r: `${thumbSize / 2}%`,
|
|
618
609
|
fill: thumb || "currentColor"
|
|
619
610
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
620
611
|
className: (0, clsx.default)("tremolo-knob-thumb-line", classes?.thumbLine),
|
|
@@ -625,7 +616,7 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
625
616
|
stroke: thumbLine || "currentColor",
|
|
626
617
|
strokeWidth: `${thumbLineWeight}%`,
|
|
627
618
|
style: {
|
|
628
|
-
transform: `rotate(${
|
|
619
|
+
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
629
620
|
transformOrigin: "50% 50%"
|
|
630
621
|
}
|
|
631
622
|
})]
|
|
@@ -698,8 +689,7 @@ const createNumberInputStore = (initProps) => {
|
|
|
698
689
|
valueAsNumber: parseValue(String(initProps?.value || ""), initProps?.units, initProps?.digit).rawValue,
|
|
699
690
|
increment: () => set((state) => {
|
|
700
691
|
if (state.readonly) return {};
|
|
701
|
-
|
|
702
|
-
let next = current + (state.step ?? 1);
|
|
692
|
+
let next = parseValue(state.value, state.units, state.digit).rawValue + (state.step ?? 1);
|
|
703
693
|
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
704
694
|
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
705
695
|
state?.onChange?.(next, v);
|
|
@@ -710,8 +700,7 @@ const createNumberInputStore = (initProps) => {
|
|
|
710
700
|
}),
|
|
711
701
|
decrement: () => set((state) => {
|
|
712
702
|
if (state.readonly) return {};
|
|
713
|
-
|
|
714
|
-
let next = current - (state.step ?? 1);
|
|
703
|
+
let next = parseValue(state.value, state.units, state.digit).rawValue - (state.step ?? 1);
|
|
715
704
|
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
716
705
|
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
717
706
|
state?.onChange?.(next, v);
|
|
@@ -783,8 +772,7 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
|
|
|
783
772
|
let newValue;
|
|
784
773
|
if (eventType == "normalized") {
|
|
785
774
|
if (!min || !max) throw new Error("required parameter: min, max");
|
|
786
|
-
|
|
787
|
-
newValue = (0, __tremolo_ui_functions.rawValue)(n + x, min, max);
|
|
775
|
+
newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(valueAsNumber, min, max) + x, min, max);
|
|
788
776
|
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
789
777
|
} else {
|
|
790
778
|
newValue = valueAsNumber + x;
|
|
@@ -900,6 +888,9 @@ function Stepper({ dynamic = true, children, className,...props }) {
|
|
|
900
888
|
|
|
901
889
|
//#endregion
|
|
902
890
|
//#region src/hooks/useInterval.ts
|
|
891
|
+
/**
|
|
892
|
+
* @category hooks
|
|
893
|
+
*/
|
|
903
894
|
function useInterval(callback, delay) {
|
|
904
895
|
const savedCallback = (0, react.useRef)(callback);
|
|
905
896
|
(0, react.useEffect)(() => {
|
|
@@ -918,6 +909,9 @@ function useInterval(callback, delay) {
|
|
|
918
909
|
|
|
919
910
|
//#endregion
|
|
920
911
|
//#region src/hooks/useLongPress.ts
|
|
912
|
+
/**
|
|
913
|
+
* @category hooks
|
|
914
|
+
*/
|
|
921
915
|
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
922
916
|
const [pressed, setPressed] = (0, react.useState)(false);
|
|
923
917
|
const [delay, setDelay] = (0, react.useState)(initialDelay);
|
|
@@ -943,8 +937,7 @@ function IncrementStepper({ size = 12, children, className,...props }) {
|
|
|
943
937
|
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
944
938
|
const readonly = useNumberInputContext((s) => s.readonly);
|
|
945
939
|
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
946
|
-
const
|
|
947
|
-
const press = useLongPress(increment);
|
|
940
|
+
const press = useLongPress(useNumberInputContext((s) => s.increment));
|
|
948
941
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
949
942
|
className: (0, clsx.default)("tremolo-number-input-increment-stepper", className),
|
|
950
943
|
role: "button",
|
|
@@ -975,8 +968,7 @@ function DecrementStepper({ size = 12, children, className,...props }) {
|
|
|
975
968
|
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
976
969
|
const readonly = useNumberInputContext((s) => s.readonly);
|
|
977
970
|
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
978
|
-
const
|
|
979
|
-
const press = useLongPress(decrement);
|
|
971
|
+
const press = useLongPress(useNumberInputContext((s) => s.decrement));
|
|
980
972
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
981
973
|
className: (0, clsx.default)("tremolo-number-input-decrement-stepper", className),
|
|
982
974
|
role: "button",
|
|
@@ -1005,7 +997,7 @@ function DecrementStepper({ size = 12, children, className,...props }) {
|
|
|
1005
997
|
* Input with some useful functions for entering numerical values.
|
|
1006
998
|
* @category NumberInput
|
|
1007
999
|
*/
|
|
1008
|
-
const NumberInput = (0, react.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 },
|
|
1000
|
+
const NumberInput = (0, react.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) => {
|
|
1009
1001
|
const colors = { "--active-color": activeColor };
|
|
1010
1002
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInputProvider, {
|
|
1011
1003
|
value: String(value),
|
|
@@ -1024,7 +1016,7 @@ const NumberInput = (0, react.forwardRef)(({ value, min, max, step = 1, units, r
|
|
|
1024
1016
|
"data-stepper": !!children,
|
|
1025
1017
|
"data-variant": variant,
|
|
1026
1018
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InternalInput, {
|
|
1027
|
-
ref,
|
|
1019
|
+
ref: forwardedRef,
|
|
1028
1020
|
readonly,
|
|
1029
1021
|
disabled,
|
|
1030
1022
|
selectWithFocus,
|
|
@@ -1045,6 +1037,7 @@ const NumberInput = (0, react.forwardRef)(({ value, min, max, step = 1, units, r
|
|
|
1045
1037
|
//#endregion
|
|
1046
1038
|
//#region src/hooks/useDragWithElement.ts
|
|
1047
1039
|
/**
|
|
1040
|
+
* @category hooks
|
|
1048
1041
|
* @returns [refCallback, pointerDownHandler]
|
|
1049
1042
|
*/
|
|
1050
1043
|
function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
|
|
@@ -1124,9 +1117,7 @@ function usePianoContext(selector) {
|
|
|
1124
1117
|
//#region src/components/Piano/KeyLabel.tsx
|
|
1125
1118
|
/** @category Piano */
|
|
1126
1119
|
function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label,...props }) {
|
|
1127
|
-
const
|
|
1128
|
-
const noteRangeArray = getNoteRangeArray(noteRange);
|
|
1129
|
-
const index = noteRangeArray.indexOf(__note);
|
|
1120
|
+
const index = getNoteRangeArray(usePianoContext((s) => s.noteRange)).indexOf(__note);
|
|
1130
1121
|
const content = label ? label(__note, index) : __label && __label(__note, index);
|
|
1131
1122
|
return (content != void 0 || content != null) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1132
1123
|
className: (0, clsx.default)("tremolo-piano-key-label-wrapper", wrapperClassName),
|
|
@@ -1152,8 +1143,7 @@ const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg,
|
|
|
1152
1143
|
const onPlayNote = usePianoContext((s) => s.onPlayNote);
|
|
1153
1144
|
const onStopNote = usePianoContext((s) => s.onStopNote);
|
|
1154
1145
|
const label = usePianoContext((s) => s.label);
|
|
1155
|
-
const
|
|
1156
|
-
const position = notePosition(noteNumber);
|
|
1146
|
+
const position = usePianoContext((s) => s.notePosition)(noteNumber);
|
|
1157
1147
|
const disabled = noteNumber > midiMax;
|
|
1158
1148
|
const colors = {
|
|
1159
1149
|
"--bg": bg,
|
|
@@ -1163,10 +1153,10 @@ const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg,
|
|
|
1163
1153
|
};
|
|
1164
1154
|
(0, react.useImperativeHandle)(ref, () => {
|
|
1165
1155
|
return {
|
|
1166
|
-
play() {
|
|
1156
|
+
play(velocity) {
|
|
1167
1157
|
if (disabled) return;
|
|
1168
1158
|
setPlayed(true);
|
|
1169
|
-
if (onPlayNote) onPlayNote(noteNumber);
|
|
1159
|
+
if (onPlayNote) onPlayNote(noteNumber, velocity);
|
|
1170
1160
|
},
|
|
1171
1161
|
stop() {
|
|
1172
1162
|
setPlayed(false);
|
|
@@ -1257,12 +1247,9 @@ const SHORTCUTS = { HOME_ROW: { keys: [
|
|
|
1257
1247
|
//#endregion
|
|
1258
1248
|
//#region src/components/Piano/index.tsx
|
|
1259
1249
|
/**
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1262
|
-
* TODO:
|
|
1263
|
-
* - add scale highlight
|
|
1250
|
+
* [noteRange.first, noteRange.first + 1 ..., noteRange.last]
|
|
1251
|
+
* @category Piano
|
|
1264
1252
|
*/
|
|
1265
|
-
/** @category Piano */
|
|
1266
1253
|
function getNoteRangeArray(noteRange) {
|
|
1267
1254
|
return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
|
|
1268
1255
|
}
|
|
@@ -1271,7 +1258,7 @@ const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth;
|
|
|
1271
1258
|
* Customizable piano component.
|
|
1272
1259
|
* @category Piano
|
|
1273
1260
|
*/
|
|
1274
|
-
|
|
1261
|
+
const Piano = (0, react.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) => {
|
|
1275
1262
|
const [whiteNoteWidth, setWhiteNoteWidth] = (0, react.useState)(_whiteNoteWidth);
|
|
1276
1263
|
const keyRefs = (0, react.useRef)([]);
|
|
1277
1264
|
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = (0, react.createRef)();
|
|
@@ -1342,9 +1329,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1342
1329
|
]);
|
|
1343
1330
|
const onDrag = (0, react.useCallback)((perX, perY) => {
|
|
1344
1331
|
if (!pianoRef.current) return;
|
|
1345
|
-
const
|
|
1346
|
-
const y = perY * pianoRef.current.clientHeight;
|
|
1347
|
-
const note = getHitKeyIndex(x, y);
|
|
1332
|
+
const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
|
|
1348
1333
|
const index = noteRangeArray.indexOf(note);
|
|
1349
1334
|
if (index == -1) return;
|
|
1350
1335
|
if (hitKeyIndex.current != index) {
|
|
@@ -1398,6 +1383,25 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1398
1383
|
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
1399
1384
|
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1400
1385
|
});
|
|
1386
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
1387
|
+
return {
|
|
1388
|
+
playNote(note, velocity) {
|
|
1389
|
+
const index = noteRangeArray.indexOf(note);
|
|
1390
|
+
if (index != -1) {
|
|
1391
|
+
if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
|
|
1392
|
+
} else onPlayNote?.(note, velocity);
|
|
1393
|
+
},
|
|
1394
|
+
stopNote(note) {
|
|
1395
|
+
const index = noteRangeArray.indexOf(note);
|
|
1396
|
+
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1397
|
+
else onStopNote?.(note);
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
}, [
|
|
1401
|
+
noteRangeArray,
|
|
1402
|
+
onPlayNote,
|
|
1403
|
+
onStopNote
|
|
1404
|
+
]);
|
|
1401
1405
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PianoProvider, {
|
|
1402
1406
|
notePosition,
|
|
1403
1407
|
noteRange,
|
|
@@ -1434,7 +1438,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1434
1438
|
}, note))
|
|
1435
1439
|
})
|
|
1436
1440
|
});
|
|
1437
|
-
}
|
|
1441
|
+
});
|
|
1438
1442
|
|
|
1439
1443
|
//#endregion
|
|
1440
1444
|
//#region src/components/Slider/context.tsx
|
|
@@ -1628,7 +1632,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
|
|
|
1628
1632
|
"data-vertical": vertical,
|
|
1629
1633
|
style: !defaultStyle ? style : {
|
|
1630
1634
|
...colors,
|
|
1631
|
-
background: (0, __tremolo_ui_functions.xor)(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive
|
|
1635
|
+
background: (0, __tremolo_ui_functions.xor)(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${__percent}%, var(--active) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active) ${__percent}%, var(--inactive) ${__percent}%)`,
|
|
1632
1636
|
borderRadius: (0, __tremolo_ui_functions.styleHelper)(thickness, "/", 2),
|
|
1633
1637
|
width: !vertical ? length : thickness,
|
|
1634
1638
|
height: vertical ? length : thickness,
|
|
@@ -1645,7 +1649,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
|
|
|
1645
1649
|
* Customizable slider
|
|
1646
1650
|
* @category Slider
|
|
1647
1651
|
*/
|
|
1648
|
-
const Slider = (0, react.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 },
|
|
1652
|
+
const Slider = (0, react.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) => {
|
|
1649
1653
|
const trackElementRef = (0, react.useRef)(null);
|
|
1650
1654
|
const thumbRef = (0, react.useRef)(null);
|
|
1651
1655
|
const p = (0, __tremolo_ui_functions.toFixed)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) * 100);
|
|
@@ -1665,9 +1669,7 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1665
1669
|
const onDrag = (0, react.useCallback)((nx, ny) => {
|
|
1666
1670
|
if (!onChange || readonly) return;
|
|
1667
1671
|
const n = vertical ? ny : nx;
|
|
1668
|
-
|
|
1669
|
-
const v2 = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max);
|
|
1670
|
-
onChange(v2);
|
|
1672
|
+
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(displayReversed ? 1 - n : n, min, max, skew), step), min, max));
|
|
1671
1673
|
}, [
|
|
1672
1674
|
displayReversed,
|
|
1673
1675
|
max,
|
|
@@ -1680,10 +1682,8 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1680
1682
|
]);
|
|
1681
1683
|
const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
|
|
1682
1684
|
let newValue;
|
|
1683
|
-
if (eventType == "normalized")
|
|
1684
|
-
|
|
1685
|
-
newValue = (0, __tremolo_ui_functions.rawValue)(n + x, min, max, skew);
|
|
1686
|
-
} else newValue = value + x;
|
|
1685
|
+
if (eventType == "normalized") newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
1686
|
+
else newValue = value + x;
|
|
1687
1687
|
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
1688
1688
|
}, [
|
|
1689
1689
|
max,
|
|
@@ -1746,7 +1746,7 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1746
1746
|
onChange,
|
|
1747
1747
|
updateValueByEvent
|
|
1748
1748
|
]);
|
|
1749
|
-
(0, react.useImperativeHandle)(
|
|
1749
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
1750
1750
|
return {
|
|
1751
1751
|
focus() {
|
|
1752
1752
|
thumbRef.current?.focus();
|
|
@@ -1830,11 +1830,10 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1830
1830
|
/** @category WheelObserver */
|
|
1831
1831
|
function WheelObserver(props) {
|
|
1832
1832
|
const { children, onWheel, as: Component = "div",...attributes } = props;
|
|
1833
|
-
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
1834
|
-
if (onWheel) onWheel(event);
|
|
1835
|
-
}, { passive: false }, [onWheel]);
|
|
1836
1833
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
1837
|
-
ref:
|
|
1834
|
+
ref: useRefCallbackEvent("wheel", (event) => {
|
|
1835
|
+
if (onWheel) onWheel(event);
|
|
1836
|
+
}, { passive: false }, [onWheel]),
|
|
1838
1837
|
...attributes,
|
|
1839
1838
|
children
|
|
1840
1839
|
});
|
|
@@ -1907,7 +1906,7 @@ const defaultValueOptions = {
|
|
|
1907
1906
|
* Simple XYPad
|
|
1908
1907
|
* @category XYPad
|
|
1909
1908
|
*/
|
|
1910
|
-
const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props },
|
|
1909
|
+
const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, forwardedRef) => {
|
|
1911
1910
|
const x = (0, react.useMemo)(() => {
|
|
1912
1911
|
return {
|
|
1913
1912
|
...defaultValueOptions,
|
|
@@ -1938,9 +1937,7 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
1938
1937
|
if (!onChange || readonly) return;
|
|
1939
1938
|
const vx = (0, __tremolo_ui_functions.rawValue)(x.reverse ? 1 - nx$1 : nx$1, x.min, x.max, x.skew);
|
|
1940
1939
|
const vy = (0, __tremolo_ui_functions.rawValue)(y.reverse ? 1 - ny$1 : ny$1, y.min, y.max, y.skew);
|
|
1941
|
-
|
|
1942
|
-
const newY = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(vy, y.step), y.min, y.max);
|
|
1943
|
-
onChange(newX, newY);
|
|
1940
|
+
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(vx, x.step), x.min, x.max), (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(vy, y.step), y.min, y.max));
|
|
1944
1941
|
}, [
|
|
1945
1942
|
onChange,
|
|
1946
1943
|
readonly,
|
|
@@ -1957,10 +1954,8 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
1957
1954
|
]);
|
|
1958
1955
|
const updateValueByEvent = (0, react.useCallback)((eventType, target, x$1) => {
|
|
1959
1956
|
let v;
|
|
1960
|
-
if (eventType == "normalized")
|
|
1961
|
-
|
|
1962
|
-
v = (0, __tremolo_ui_functions.rawValue)(n + x$1, target.min, target.max, target.skew);
|
|
1963
|
-
} else v = target.value + x$1;
|
|
1957
|
+
if (eventType == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(target.value, target.min, target.max, target.skew) + x$1, target.min, target.max, target.skew);
|
|
1958
|
+
else v = target.value + x$1;
|
|
1964
1959
|
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, target.step), target.min, target.max);
|
|
1965
1960
|
}, []);
|
|
1966
1961
|
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
@@ -2027,7 +2022,7 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
2027
2022
|
readonly,
|
|
2028
2023
|
updateValueByEvent
|
|
2029
2024
|
]);
|
|
2030
|
-
(0, react.useImperativeHandle)(
|
|
2025
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
2031
2026
|
return {
|
|
2032
2027
|
focus() {
|
|
2033
2028
|
thumbRef.current?.focus();
|
|
@@ -2090,6 +2085,9 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
2090
2085
|
|
|
2091
2086
|
//#endregion
|
|
2092
2087
|
//#region src/hooks/useAnimationFrame.ts
|
|
2088
|
+
/**
|
|
2089
|
+
* @category hooks
|
|
2090
|
+
*/
|
|
2093
2091
|
function useAnimationFrame(callback = () => {}, deps = []) {
|
|
2094
2092
|
const reqIdRef = (0, react.useRef)(-1);
|
|
2095
2093
|
const loop = (0, react.useCallback)(() => {
|
|
@@ -2102,6 +2100,79 @@ function useAnimationFrame(callback = () => {}, deps = []) {
|
|
|
2102
2100
|
}, [loop]);
|
|
2103
2101
|
}
|
|
2104
2102
|
|
|
2103
|
+
//#endregion
|
|
2104
|
+
//#region src/hooks/useMIDIAccess.ts
|
|
2105
|
+
/** @private */
|
|
2106
|
+
const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
2107
|
+
/** @private */
|
|
2108
|
+
const NOT_SUPPORTED = "NOT_SUPPORTED";
|
|
2109
|
+
/**
|
|
2110
|
+
* Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
|
|
2111
|
+
* @category hooks
|
|
2112
|
+
*/
|
|
2113
|
+
function useMIDIAccess(requestOnMount = true) {
|
|
2114
|
+
const [midiAccess, setMidiAccess] = (0, react.useState)(null);
|
|
2115
|
+
const [error, setError] = (0, react.useState)(null);
|
|
2116
|
+
const request = (0, react.useCallback)(() => {
|
|
2117
|
+
if (navigator.requestMIDIAccess) navigator.requestMIDIAccess().then((access) => {
|
|
2118
|
+
setMidiAccess(access);
|
|
2119
|
+
setError(null);
|
|
2120
|
+
}).catch(() => {
|
|
2121
|
+
setError(PERMISSION_DENIED);
|
|
2122
|
+
});
|
|
2123
|
+
else setError(NOT_SUPPORTED);
|
|
2124
|
+
}, []);
|
|
2125
|
+
(0, react.useEffect)(() => {
|
|
2126
|
+
if (requestOnMount) request();
|
|
2127
|
+
}, [requestOnMount, request]);
|
|
2128
|
+
return {
|
|
2129
|
+
request,
|
|
2130
|
+
midiAccess,
|
|
2131
|
+
error
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
//#endregion
|
|
2136
|
+
//#region src/hooks/useMIDIMessage.ts
|
|
2137
|
+
/**
|
|
2138
|
+
* Hooks for when you want to process MIDI events in more detail than useMIDIInput.
|
|
2139
|
+
* @category hooks
|
|
2140
|
+
*/
|
|
2141
|
+
function useMIDIMessage(midiAccess, onMIDIMessage) {
|
|
2142
|
+
(0, react.useEffect)(() => {
|
|
2143
|
+
if (!midiAccess) return;
|
|
2144
|
+
for (const input of midiAccess.inputs.values()) input.addEventListener("midimessage", onMIDIMessage);
|
|
2145
|
+
return () => {
|
|
2146
|
+
for (const input of midiAccess.inputs.values()) input.removeEventListener("midimessage", onMIDIMessage);
|
|
2147
|
+
};
|
|
2148
|
+
}, [midiAccess, onMIDIMessage]);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
//#endregion
|
|
2152
|
+
//#region src/hooks/useMIDIInput.ts
|
|
2153
|
+
const MIDI_EVENT_TO_NUMBER = {
|
|
2154
|
+
NOTE_ON: 144,
|
|
2155
|
+
NOTE_OFF: 128,
|
|
2156
|
+
PITCH_BEND: 224
|
|
2157
|
+
};
|
|
2158
|
+
/**
|
|
2159
|
+
* Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
|
|
2160
|
+
* @category hooks
|
|
2161
|
+
*/
|
|
2162
|
+
function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
|
|
2163
|
+
useMIDIMessage(midiAccess, (0, react.useCallback)((event) => {
|
|
2164
|
+
if (!event.data) return;
|
|
2165
|
+
const kind = event.data[0] & 240;
|
|
2166
|
+
if (kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF || kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0) onNoteOffEvent?.(event.data[1]);
|
|
2167
|
+
else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) onNoteOnEvent?.(event.data[1], event.data[2]);
|
|
2168
|
+
else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) onPitchBendEvent?.(event.data[1], event.data[2]);
|
|
2169
|
+
}, [
|
|
2170
|
+
onNoteOffEvent,
|
|
2171
|
+
onNoteOnEvent,
|
|
2172
|
+
onPitchBendEvent
|
|
2173
|
+
]));
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2105
2176
|
//#endregion
|
|
2106
2177
|
exports.AnimationCanvas = AnimationCanvas;
|
|
2107
2178
|
exports.AnimationKnob = AnimationKnob;
|
|
@@ -2111,7 +2182,9 @@ exports.DragObserver = DragObserver;
|
|
|
2111
2182
|
exports.IncrementStepper = IncrementStepper;
|
|
2112
2183
|
exports.KeyLabel = KeyLabel;
|
|
2113
2184
|
exports.Knob = Knob;
|
|
2185
|
+
exports.NOT_SUPPORTED = NOT_SUPPORTED;
|
|
2114
2186
|
exports.NumberInput = NumberInput;
|
|
2187
|
+
exports.PERMISSION_DENIED = PERMISSION_DENIED;
|
|
2115
2188
|
exports.Piano = Piano;
|
|
2116
2189
|
exports.SHORTCUTS = SHORTCUTS;
|
|
2117
2190
|
exports.Scale = Scale;
|
|
@@ -2132,5 +2205,8 @@ exports.useDragWithElement = useDragWithElement;
|
|
|
2132
2205
|
exports.useEventListener = useEventListener;
|
|
2133
2206
|
exports.useInterval = useInterval;
|
|
2134
2207
|
exports.useLongPress = useLongPress;
|
|
2208
|
+
exports.useMIDIAccess = useMIDIAccess;
|
|
2209
|
+
exports.useMIDIInput = useMIDIInput;
|
|
2210
|
+
exports.useMIDIMessage = useMIDIMessage;
|
|
2135
2211
|
exports.useSliderContext = useSliderContext;
|
|
2136
2212
|
//# sourceMappingURL=index.cjs.map
|