@cutoff/audio-ui-core 1.0.0-preview.20260123.1125
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/LICENSE.md +690 -0
- package/dist/constants/cssVars.d.ts +42 -0
- package/dist/constants/cssVars.d.ts.map +1 -0
- package/dist/constants/cursors.d.ts +2 -0
- package/dist/constants/cursors.d.ts.map +1 -0
- package/dist/constants/interaction.d.ts +24 -0
- package/dist/constants/interaction.d.ts.map +1 -0
- package/dist/constants/styles.d.ts +17 -0
- package/dist/constants/styles.d.ts.map +1 -0
- package/dist/constants/theme.d.ts +66 -0
- package/dist/constants/theme.d.ts.map +1 -0
- package/dist/constants/themeDefaults.d.ts +16 -0
- package/dist/constants/themeDefaults.d.ts.map +1 -0
- package/dist/controller/BooleanInteractionController.d.ts +141 -0
- package/dist/controller/BooleanInteractionController.d.ts.map +1 -0
- package/dist/controller/ContinuousInteractionController.d.ts +114 -0
- package/dist/controller/ContinuousInteractionController.d.ts.map +1 -0
- package/dist/controller/DiscreteInteractionController.d.ts +51 -0
- package/dist/controller/DiscreteInteractionController.d.ts.map +1 -0
- package/dist/controller/NoteInteractionController.d.ts +88 -0
- package/dist/controller/NoteInteractionController.d.ts.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1404 -0
- package/dist/index.js.map +1 -0
- package/dist/model/AudioParameter.d.ts +437 -0
- package/dist/model/AudioParameter.d.ts.map +1 -0
- package/dist/styles/styles.css +256 -0
- package/dist/styles/themes.css +193 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/colorUtils.d.ts +102 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/math.d.ts +108 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/normalizedProps.d.ts +22 -0
- package/dist/utils/normalizedProps.d.ts.map +1 -0
- package/dist/utils/noteUtils.d.ts +71 -0
- package/dist/utils/noteUtils.d.ts.map +1 -0
- package/dist/utils/propCompare.d.ts +22 -0
- package/dist/utils/propCompare.d.ts.map +1 -0
- package/dist/utils/sizing.d.ts +39 -0
- package/dist/utils/sizing.d.ts.map +1 -0
- package/dist/utils/svg.d.ts +27 -0
- package/dist/utils/svg.d.ts.map +1 -0
- package/dist/utils/valueFormatters.d.ts +167 -0
- package/dist/utils/valueFormatters.d.ts.map +1 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1404 @@
|
|
|
1
|
+
var N = Object.defineProperty;
|
|
2
|
+
var z = (i, t, e) => t in i ? N(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
|
|
3
|
+
var l = (i, t, e) => z(i, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import T from "fast-deep-equal";
|
|
5
|
+
const W = {
|
|
6
|
+
/** Root class applied to all AudioUI components */
|
|
7
|
+
root: "audioui",
|
|
8
|
+
/** Container helper class for components that manage their own SVG sizing */
|
|
9
|
+
container: "audioui-component-container",
|
|
10
|
+
/** Highlight class used for interactive states */
|
|
11
|
+
highlight: "audioui-highlight",
|
|
12
|
+
/** Icon wrapper class for HTML overlay - ensures SVG icons fill their container */
|
|
13
|
+
iconWrapper: "audioui-icon-wrapper"
|
|
14
|
+
}, J = {
|
|
15
|
+
/** Default adaptive color (white in dark mode, black in light mode) */
|
|
16
|
+
default: "var(--audioui-theme-default)",
|
|
17
|
+
/** Blue theme color */
|
|
18
|
+
blue: "var(--audioui-theme-blue)",
|
|
19
|
+
/** Orange theme color */
|
|
20
|
+
orange: "var(--audioui-theme-orange)",
|
|
21
|
+
/** Pink theme color */
|
|
22
|
+
pink: "var(--audioui-theme-pink)",
|
|
23
|
+
/** Green theme color */
|
|
24
|
+
green: "var(--audioui-theme-green)",
|
|
25
|
+
/** Purple theme color */
|
|
26
|
+
purple: "var(--audioui-theme-purple)",
|
|
27
|
+
/** Yellow theme color */
|
|
28
|
+
yellow: "var(--audioui-theme-yellow)"
|
|
29
|
+
}, Q = {
|
|
30
|
+
default: {
|
|
31
|
+
light: "hsl(0, 0%, 10%)",
|
|
32
|
+
dark: "hsl(0, 0%, 96%)"
|
|
33
|
+
},
|
|
34
|
+
blue: {
|
|
35
|
+
light: "hsl(204, 88%, 52%)",
|
|
36
|
+
dark: "hsl(204, 88%, 53%)"
|
|
37
|
+
},
|
|
38
|
+
orange: {
|
|
39
|
+
light: "hsl(29, 100%, 48%)",
|
|
40
|
+
dark: "hsl(29, 100%, 50%)"
|
|
41
|
+
},
|
|
42
|
+
pink: {
|
|
43
|
+
light: "hsl(332, 92%, 52%)",
|
|
44
|
+
dark: "hsl(332, 95%, 54%)"
|
|
45
|
+
},
|
|
46
|
+
green: {
|
|
47
|
+
light: "hsl(160, 95%, 44%)",
|
|
48
|
+
dark: "hsl(160, 98%, 37%)"
|
|
49
|
+
},
|
|
50
|
+
purple: {
|
|
51
|
+
light: "hsl(252, 96%, 54%)",
|
|
52
|
+
dark: "hsl(252, 100%, 67%)"
|
|
53
|
+
},
|
|
54
|
+
yellow: {
|
|
55
|
+
light: "hsl(50, 100%, 50%)",
|
|
56
|
+
dark: "hsl(50, 100%, 50%)"
|
|
57
|
+
}
|
|
58
|
+
}, tt = 0.3, et = 0.4, P = {
|
|
59
|
+
// Theming parameters
|
|
60
|
+
roundnessBase: "--audioui-roundness-base",
|
|
61
|
+
primaryColor: "--audioui-primary-color",
|
|
62
|
+
adaptiveDefaultColor: "--audioui-adaptive-default-color",
|
|
63
|
+
adaptive50: "--audioui-adaptive-50",
|
|
64
|
+
adaptive20: "--audioui-adaptive-20",
|
|
65
|
+
adaptiveLight: "--audioui-adaptive-light",
|
|
66
|
+
adaptiveDark: "--audioui-adaptive-dark",
|
|
67
|
+
primary50: "--audioui-primary-50",
|
|
68
|
+
primary20: "--audioui-primary-20",
|
|
69
|
+
primaryLight: "--audioui-primary-light",
|
|
70
|
+
primaryDark: "--audioui-primary-dark",
|
|
71
|
+
// Text and typography
|
|
72
|
+
textColor: "--audioui-text-color",
|
|
73
|
+
defaultFontSize: "--audioui-default-font-size",
|
|
74
|
+
// Interactive highlight
|
|
75
|
+
highlightEffect: "--audioui-highlight-effect",
|
|
76
|
+
highlightTransitionDuration: "--audioui-highlight-transition-duration",
|
|
77
|
+
// Cursor styles
|
|
78
|
+
cursorClickable: "--audioui-cursor-clickable",
|
|
79
|
+
cursorBidirectional: "--audioui-cursor-bidirectional",
|
|
80
|
+
cursorHorizontal: "--audioui-cursor-horizontal",
|
|
81
|
+
cursorVertical: "--audioui-cursor-vertical",
|
|
82
|
+
cursorCircular: "--audioui-cursor-circular",
|
|
83
|
+
cursorNoneditable: "--audioui-cursor-noneditable",
|
|
84
|
+
cursorDisabled: "--audioui-cursor-disabled",
|
|
85
|
+
// Keys colors
|
|
86
|
+
keysIvory: "--audioui-keys-ivory",
|
|
87
|
+
keysIvoryStroke: "--audioui-keys-ivory-stroke",
|
|
88
|
+
keysEbony: "--audioui-keys-ebony",
|
|
89
|
+
keysEbonyStroke: "--audioui-keys-ebony-stroke",
|
|
90
|
+
// Component style customization
|
|
91
|
+
knobCapFill: "--audioui-knob-cap-fill",
|
|
92
|
+
buttonStrokeWidth: "--audioui-button-stroke-width",
|
|
93
|
+
// Slider component colors
|
|
94
|
+
sliderTrackColor: "--audioui-slider-track-color",
|
|
95
|
+
sliderStripColor: "--audioui-slider-strip-color",
|
|
96
|
+
sliderCursorColor: "--audioui-slider-cursor-color",
|
|
97
|
+
sliderCursorBorderColor: "--audioui-slider-cursor-border-color",
|
|
98
|
+
sliderCursorBorderWidth: "--audioui-slider-cursor-border-width"
|
|
99
|
+
}, it = "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIxMCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSI0Ii8+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTAiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg==') 16 16, move", L = 5e-3, $ = 5e-3, ot = 30, R = 0.05;
|
|
100
|
+
class nt {
|
|
101
|
+
constructor(t) {
|
|
102
|
+
l(this, "config");
|
|
103
|
+
l(this, "startX", 0);
|
|
104
|
+
l(this, "startY", 0);
|
|
105
|
+
l(this, "centerX", 0);
|
|
106
|
+
l(this, "centerY", 0);
|
|
107
|
+
l(this, "isDragging", !1);
|
|
108
|
+
l(this, "wheelAccumulator", 0);
|
|
109
|
+
l(this, "dragAccumulator", 0);
|
|
110
|
+
/**
|
|
111
|
+
* Handles the start of a mouse drag interaction.
|
|
112
|
+
* Should be called from the component's onMouseDown handler.
|
|
113
|
+
* @param clientX The X coordinate of the mouse event.
|
|
114
|
+
* @param clientY The Y coordinate of the mouse event.
|
|
115
|
+
* @param target The event target (used for circular center calculation).
|
|
116
|
+
*/
|
|
117
|
+
l(this, "handleMouseDown", (t, e, o) => {
|
|
118
|
+
this.config.interactionMode !== "wheel" && this.startDrag(t, e, o);
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* Handles the start of a touch interaction.
|
|
122
|
+
* Should be called from the component's onTouchStart handler.
|
|
123
|
+
* @param clientX The X coordinate of the touch event.
|
|
124
|
+
* @param clientY The Y coordinate of the touch event.
|
|
125
|
+
* @param target The event target.
|
|
126
|
+
*/
|
|
127
|
+
l(this, "handleTouchStart", (t, e, o) => {
|
|
128
|
+
this.config.interactionMode !== "wheel" && this.startDrag(t, e, o);
|
|
129
|
+
});
|
|
130
|
+
/**
|
|
131
|
+
* Handles wheel events.
|
|
132
|
+
* Should be called from the component's onWheel handler.
|
|
133
|
+
* @param e The wheel event.
|
|
134
|
+
*/
|
|
135
|
+
l(this, "handleWheel", (t) => {
|
|
136
|
+
if (this.config.disabled || this.config.interactionMode !== "wheel" && this.config.interactionMode !== "both") return;
|
|
137
|
+
t.preventDefault && t.preventDefault(), t.stopPropagation && t.stopPropagation();
|
|
138
|
+
const e = t.deltaY, o = this.config.wheelSensitivity ?? $;
|
|
139
|
+
if (this.config.step) {
|
|
140
|
+
if (this.wheelAccumulator += e * o, Math.abs(this.wheelAccumulator) >= this.config.step) {
|
|
141
|
+
const n = Math.trunc(this.wheelAccumulator / this.config.step);
|
|
142
|
+
this.config.adjustValue(n * this.config.step, 1), this.wheelAccumulator -= n * this.config.step;
|
|
143
|
+
}
|
|
144
|
+
} else
|
|
145
|
+
this.config.adjustValue(e, o);
|
|
146
|
+
});
|
|
147
|
+
/**
|
|
148
|
+
* Handles keyboard events (Arrow keys, Home, End).
|
|
149
|
+
* Should be called from the component's onKeyDown handler.
|
|
150
|
+
* @param e The keyboard event.
|
|
151
|
+
*/
|
|
152
|
+
l(this, "handleKeyDown", (t) => {
|
|
153
|
+
if (this.config.disabled) return;
|
|
154
|
+
let e = 0;
|
|
155
|
+
switch (t.key) {
|
|
156
|
+
case "ArrowUp":
|
|
157
|
+
case "ArrowRight":
|
|
158
|
+
e = 1;
|
|
159
|
+
break;
|
|
160
|
+
case "ArrowDown":
|
|
161
|
+
case "ArrowLeft":
|
|
162
|
+
e = -1;
|
|
163
|
+
break;
|
|
164
|
+
case "Home":
|
|
165
|
+
e = -1 / this.config.sensitivity;
|
|
166
|
+
break;
|
|
167
|
+
case "End":
|
|
168
|
+
e = 1 / this.config.sensitivity;
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
t.preventDefault();
|
|
174
|
+
const o = e * (this.config.keyboardStep / this.config.sensitivity);
|
|
175
|
+
this.config.adjustValue(o, this.config.sensitivity);
|
|
176
|
+
});
|
|
177
|
+
this.config = {
|
|
178
|
+
interactionMode: "both",
|
|
179
|
+
direction: "both",
|
|
180
|
+
sensitivity: L,
|
|
181
|
+
keyboardStep: R,
|
|
182
|
+
disabled: !1,
|
|
183
|
+
// adjustValue is provided in config
|
|
184
|
+
...t
|
|
185
|
+
}, this.handleGlobalMouseMove = this.handleGlobalMouseMove.bind(this), this.handleGlobalMouseUp = this.handleGlobalMouseUp.bind(this), this.handleGlobalTouchMove = this.handleGlobalTouchMove.bind(this);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Updates the configuration of the controller.
|
|
189
|
+
* @param config Partial configuration to update.
|
|
190
|
+
*/
|
|
191
|
+
updateConfig(t) {
|
|
192
|
+
Object.assign(this.config, t);
|
|
193
|
+
}
|
|
194
|
+
startDrag(t, e, o) {
|
|
195
|
+
if (this.config.disabled) return;
|
|
196
|
+
if (this.startX = t, this.startY = e, this.isDragging = !0, this.dragAccumulator = 0, this.config.onDragStart?.(), this.config.direction === "circular" && o && o.getBoundingClientRect) {
|
|
197
|
+
const s = o.getBoundingClientRect();
|
|
198
|
+
this.centerX = s.left + s.width / 2, this.centerY = s.top + s.height / 2;
|
|
199
|
+
}
|
|
200
|
+
document.body.style.userSelect = "none";
|
|
201
|
+
let n = "var(--audioui-cursor-vertical)";
|
|
202
|
+
this.config.direction === "horizontal" && (n = "var(--audioui-cursor-horizontal)"), this.config.direction === "both" && (n = "var(--audioui-cursor-bidirectional)"), this.config.direction === "circular" && (n = "var(--audioui-cursor-circular)"), document.body.style.cursor = n, window.addEventListener("mousemove", this.handleGlobalMouseMove), window.addEventListener("mouseup", this.handleGlobalMouseUp), window.addEventListener("touchmove", this.handleGlobalTouchMove, { passive: !1 }), window.addEventListener("touchend", this.handleGlobalMouseUp);
|
|
203
|
+
}
|
|
204
|
+
handleGlobalMouseMove(t) {
|
|
205
|
+
this.isDragging && (t.preventDefault(), this.processDrag(t.clientX, t.clientY));
|
|
206
|
+
}
|
|
207
|
+
handleGlobalTouchMove(t) {
|
|
208
|
+
if (!this.isDragging) return;
|
|
209
|
+
t.preventDefault();
|
|
210
|
+
const e = t.touches[0];
|
|
211
|
+
this.processDrag(e.clientX, e.clientY);
|
|
212
|
+
}
|
|
213
|
+
processDrag(t, e) {
|
|
214
|
+
let o = 0;
|
|
215
|
+
if (this.config.direction === "vertical")
|
|
216
|
+
o = this.startY - e;
|
|
217
|
+
else if (this.config.direction === "horizontal")
|
|
218
|
+
o = t - this.startX;
|
|
219
|
+
else if (this.config.direction === "both")
|
|
220
|
+
o = t - this.startX + (this.startY - e);
|
|
221
|
+
else if (this.config.direction === "circular") {
|
|
222
|
+
const n = Math.atan2(e - this.centerY, t - this.centerX), s = Math.atan2(this.startY - this.centerY, this.startX - this.centerX);
|
|
223
|
+
let r = n - s;
|
|
224
|
+
r > Math.PI ? r -= 2 * Math.PI : r < -Math.PI && (r += 2 * Math.PI), o = r * (180 / Math.PI);
|
|
225
|
+
}
|
|
226
|
+
if (o !== 0) {
|
|
227
|
+
const n = o * this.config.sensitivity;
|
|
228
|
+
if (this.config.step) {
|
|
229
|
+
if (this.dragAccumulator += n, Math.abs(this.dragAccumulator) >= this.config.step) {
|
|
230
|
+
const r = Math.trunc(this.dragAccumulator / this.config.step) * this.config.step;
|
|
231
|
+
this.config.adjustValue(r, 1), this.dragAccumulator -= r;
|
|
232
|
+
}
|
|
233
|
+
} else
|
|
234
|
+
this.config.adjustValue(o, this.config.sensitivity);
|
|
235
|
+
this.startX = t, this.startY = e;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Handles global mouse up event to end drag.
|
|
240
|
+
*/
|
|
241
|
+
handleGlobalMouseUp() {
|
|
242
|
+
this.isDragging && (this.isDragging = !1, this.config.onDragEnd?.(), document.body.style.userSelect = "", document.body.style.cursor = "", window.removeEventListener("mousemove", this.handleGlobalMouseMove), window.removeEventListener("mouseup", this.handleGlobalMouseUp), window.removeEventListener("touchmove", this.handleGlobalTouchMove), window.removeEventListener("touchend", this.handleGlobalMouseUp));
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Cleans up event listeners.
|
|
246
|
+
*/
|
|
247
|
+
dispose() {
|
|
248
|
+
this.handleGlobalMouseUp();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
class st {
|
|
252
|
+
constructor(t) {
|
|
253
|
+
/**
|
|
254
|
+
* Handles click events.
|
|
255
|
+
* Cycles to the next value if the event was not already prevented (e.g. by a drag).
|
|
256
|
+
* @param defaultPrevented Whether the event's default action has been prevented.
|
|
257
|
+
*/
|
|
258
|
+
l(this, "handleClick", (t) => {
|
|
259
|
+
this.config.disabled || t || this.cycleNext();
|
|
260
|
+
});
|
|
261
|
+
/**
|
|
262
|
+
* Handles keyboard events for discrete controls.
|
|
263
|
+
* Returns true if the event was handled (and thus should be prevented), false otherwise.
|
|
264
|
+
*/
|
|
265
|
+
l(this, "handleKeyDown", (t) => {
|
|
266
|
+
if (this.config.disabled) return !1;
|
|
267
|
+
switch (t) {
|
|
268
|
+
case " ":
|
|
269
|
+
case "Enter":
|
|
270
|
+
return this.cycleNext(), !0;
|
|
271
|
+
case "ArrowUp":
|
|
272
|
+
case "ArrowRight":
|
|
273
|
+
return this.stepNext(), !0;
|
|
274
|
+
case "ArrowDown":
|
|
275
|
+
case "ArrowLeft":
|
|
276
|
+
return this.stepPrev(), !0;
|
|
277
|
+
}
|
|
278
|
+
return !1;
|
|
279
|
+
});
|
|
280
|
+
this.config = t;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Updates the configuration.
|
|
284
|
+
* @param config New configuration object.
|
|
285
|
+
*/
|
|
286
|
+
updateConfig(t) {
|
|
287
|
+
this.config = t;
|
|
288
|
+
}
|
|
289
|
+
getCurrentIndex() {
|
|
290
|
+
const t = this.config.options.findIndex((e) => e.value === this.config.value);
|
|
291
|
+
return t === -1 ? 0 : t;
|
|
292
|
+
}
|
|
293
|
+
setValueAtIndex(t) {
|
|
294
|
+
if (this.config.disabled) return;
|
|
295
|
+
const e = this.config.options[t];
|
|
296
|
+
e && this.config.onValueChange(e.value);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Cycles to the next value, wrapping around to the start if needed.
|
|
300
|
+
*/
|
|
301
|
+
cycleNext() {
|
|
302
|
+
if (this.config.options.length <= 1) return;
|
|
303
|
+
const e = (this.getCurrentIndex() + 1) % this.config.options.length;
|
|
304
|
+
this.setValueAtIndex(e);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Steps to the next value, clamping at the end.
|
|
308
|
+
*/
|
|
309
|
+
stepNext() {
|
|
310
|
+
if (this.config.options.length <= 1) return;
|
|
311
|
+
const t = this.getCurrentIndex();
|
|
312
|
+
t < this.config.options.length - 1 && this.setValueAtIndex(t + 1);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Steps to the previous value, clamping at the start.
|
|
316
|
+
*/
|
|
317
|
+
stepPrev() {
|
|
318
|
+
if (this.config.options.length <= 1) return;
|
|
319
|
+
const t = this.getCurrentIndex();
|
|
320
|
+
t > 0 && this.setValueAtIndex(t - 1);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
class rt {
|
|
324
|
+
constructor(t) {
|
|
325
|
+
l(this, "isPressed", !1);
|
|
326
|
+
l(this, "isGlobalPointerDown", !1);
|
|
327
|
+
/**
|
|
328
|
+
* Handles global pointer down events (mouse or touch).
|
|
329
|
+
*
|
|
330
|
+
* This tracks when a pointer is pressed anywhere on the page, not just on this button.
|
|
331
|
+
* This allows the button to respond to drag-in behavior even when the press starts outside.
|
|
332
|
+
*
|
|
333
|
+
* @param {boolean} defaultPrevented - Whether the event's default action has been prevented. If true, the handler does nothing.
|
|
334
|
+
*/
|
|
335
|
+
l(this, "handleGlobalPointerDown", (t) => {
|
|
336
|
+
this.config.disabled || t || (this.isGlobalPointerDown = !0);
|
|
337
|
+
});
|
|
338
|
+
/**
|
|
339
|
+
* Handles mouse down events on the button element.
|
|
340
|
+
*
|
|
341
|
+
* Behavior depends on interaction mode:
|
|
342
|
+
* - **Toggle mode**: Flips the current value (true ↔ false)
|
|
343
|
+
* - **Momentary mode**: Sets value to true and tracks press state for later release
|
|
344
|
+
*
|
|
345
|
+
* @param {boolean} defaultPrevented - Whether the event's default action has been prevented. If true, the handler does nothing.
|
|
346
|
+
*/
|
|
347
|
+
l(this, "handleMouseDown", (t) => {
|
|
348
|
+
this.config.disabled || t || (this.isGlobalPointerDown = !0, this.config.mode === "toggle" ? this.config.onValueChange(!this.config.value) : (this.isPressed = !0, this.config.onValueChange(!0)));
|
|
349
|
+
});
|
|
350
|
+
/**
|
|
351
|
+
* Handles mouse up events on the button element.
|
|
352
|
+
*
|
|
353
|
+
* For momentary mode: Sets value to false if the button is currently pressed.
|
|
354
|
+
* This prevents false releases if the button wasn't actually pressed.
|
|
355
|
+
* Toggle mode: No action (toggle happens on mousedown only).
|
|
356
|
+
*
|
|
357
|
+
* Note: This is called when mouse is released on the button. The global pointer up
|
|
358
|
+
* handler will also be called to reset global state.
|
|
359
|
+
*
|
|
360
|
+
* @param {boolean} defaultPrevented - Whether the event's default action has been prevented. If true, the handler does nothing.
|
|
361
|
+
*/
|
|
362
|
+
l(this, "handleMouseUp", (t) => {
|
|
363
|
+
this.config.disabled || t || this.config.mode === "momentary" && this.isPressed && (this.isPressed = !1, this.config.onValueChange(!1));
|
|
364
|
+
});
|
|
365
|
+
/**
|
|
366
|
+
* Handles global pointer up events (mouse or touch).
|
|
367
|
+
*
|
|
368
|
+
* This method is called when a pointer up event occurs anywhere on the page.
|
|
369
|
+
* It resets the global pointer state and handles release for momentary buttons.
|
|
370
|
+
*
|
|
371
|
+
* Only affects momentary mode buttons that are currently pressed.
|
|
372
|
+
*/
|
|
373
|
+
l(this, "handleGlobalPointerUp", () => {
|
|
374
|
+
this.config.disabled || (this.isGlobalPointerDown = !1, this.config.mode === "momentary" && this.isPressed && (this.isPressed = !1, this.config.onValueChange(!1)));
|
|
375
|
+
});
|
|
376
|
+
/**
|
|
377
|
+
* Handles mouse enter events (pointer enters the button element).
|
|
378
|
+
*
|
|
379
|
+
* When a pointer enters the button while globally pressed:
|
|
380
|
+
* - **Momentary mode**: Sets value to true
|
|
381
|
+
* - **Toggle mode**: Toggles the value
|
|
382
|
+
*
|
|
383
|
+
* This enables drag-in behavior: pressing outside and dragging into the button activates it.
|
|
384
|
+
*/
|
|
385
|
+
l(this, "handleMouseEnter", () => {
|
|
386
|
+
this.config.disabled || this.isGlobalPointerDown && (this.config.mode === "toggle" ? this.config.onValueChange(!this.config.value) : (this.isPressed = !0, this.config.onValueChange(!0)));
|
|
387
|
+
});
|
|
388
|
+
/**
|
|
389
|
+
* Handles mouse leave events (pointer leaves the button element).
|
|
390
|
+
*
|
|
391
|
+
* When a pointer leaves the button while globally pressed:
|
|
392
|
+
* - **Momentary mode**: Sets value to false
|
|
393
|
+
* - **Toggle mode**: No action (state remains as-is)
|
|
394
|
+
*
|
|
395
|
+
* This enables drag-out behavior: pressing inside and dragging out deactivates momentary buttons.
|
|
396
|
+
*/
|
|
397
|
+
l(this, "handleMouseLeave", () => {
|
|
398
|
+
this.config.disabled || this.isGlobalPointerDown && this.config.mode === "momentary" && this.isPressed && (this.isPressed = !1, this.config.onValueChange(!1));
|
|
399
|
+
});
|
|
400
|
+
/**
|
|
401
|
+
* Handles keyboard key down events.
|
|
402
|
+
*
|
|
403
|
+
* Supported keys:
|
|
404
|
+
* - `Enter` or `Space`: Activates the button
|
|
405
|
+
* - Toggle mode: Flips the value
|
|
406
|
+
* - Momentary mode: Sets value to true
|
|
407
|
+
*
|
|
408
|
+
* @param {string} key - The keyboard key that was pressed
|
|
409
|
+
* @returns {boolean} `true` if the event was handled (and should be prevented), `false` otherwise
|
|
410
|
+
*/
|
|
411
|
+
l(this, "handleKeyDown", (t) => this.config.disabled ? !1 : t === "Enter" || t === " " ? (this.config.mode === "toggle" ? this.config.onValueChange(!this.config.value) : this.config.onValueChange(!0), !0) : !1);
|
|
412
|
+
/**
|
|
413
|
+
* Handles keyboard key up events.
|
|
414
|
+
*
|
|
415
|
+
* Supported keys (momentary mode only):
|
|
416
|
+
* - `Enter` or `Space`: Releases the button (sets value to false)
|
|
417
|
+
*
|
|
418
|
+
* Toggle mode: No action (toggle happens on keydown only).
|
|
419
|
+
*
|
|
420
|
+
* @param {string} key - The keyboard key that was released
|
|
421
|
+
* @returns {boolean} `true` if the event was handled (and should be prevented), `false` otherwise
|
|
422
|
+
*/
|
|
423
|
+
l(this, "handleKeyUp", (t) => this.config.disabled ? !1 : this.config.mode === "momentary" && (t === "Enter" || t === " ") ? (this.config.onValueChange(!1), !0) : !1);
|
|
424
|
+
this.config = t;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Updates the controller configuration.
|
|
428
|
+
*
|
|
429
|
+
* This method should be called whenever the configuration changes (e.g., value, mode, disabled state).
|
|
430
|
+
* The controller will use the new configuration for all subsequent interactions.
|
|
431
|
+
*
|
|
432
|
+
* @param {BooleanInteractionConfig} config - New configuration object
|
|
433
|
+
*/
|
|
434
|
+
updateConfig(t) {
|
|
435
|
+
this.config = t;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Gets the current press state (for momentary buttons).
|
|
439
|
+
*
|
|
440
|
+
* This is primarily used for internal state tracking. External code typically
|
|
441
|
+
* doesn't need to check this, as the value reflects the button state.
|
|
442
|
+
*
|
|
443
|
+
* @returns {boolean} `true` if the button is currently pressed, `false` otherwise
|
|
444
|
+
*/
|
|
445
|
+
getIsPressed() {
|
|
446
|
+
return this.isPressed;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
class at {
|
|
450
|
+
/**
|
|
451
|
+
* Creates a new NoteInteractionController.
|
|
452
|
+
* @param config Initial configuration
|
|
453
|
+
*/
|
|
454
|
+
constructor(t) {
|
|
455
|
+
l(this, "pointerNotes", /* @__PURE__ */ new Map());
|
|
456
|
+
this.config = t;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Updates the controller configuration.
|
|
460
|
+
* @param config New configuration object
|
|
461
|
+
*/
|
|
462
|
+
updateConfig(t) {
|
|
463
|
+
this.config = t;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Handles the start of a pointer interaction (down).
|
|
467
|
+
*
|
|
468
|
+
* Tracks the pointer state globally and triggers onNoteOn if the pointer
|
|
469
|
+
* is over a specific note.
|
|
470
|
+
*
|
|
471
|
+
* @param pointerId Unique identifier for the pointer
|
|
472
|
+
* @param note The MIDI note number at the pointer location, or null if none
|
|
473
|
+
*/
|
|
474
|
+
handlePointerDown(t, e) {
|
|
475
|
+
this.config.disabled || (this.handlePointerUp(t), this.pointerNotes.set(t, e), e !== null && this.config.onNoteOn(e, t));
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Handles pointer movement (move).
|
|
479
|
+
*
|
|
480
|
+
* Enables glissando behavior by detecting when a tracked pointer moves
|
|
481
|
+
* from one note to another.
|
|
482
|
+
*
|
|
483
|
+
* @param pointerId Unique identifier for the pointer
|
|
484
|
+
* @param note The MIDI note number at the current pointer location, or null if none
|
|
485
|
+
*/
|
|
486
|
+
handlePointerMove(t, e) {
|
|
487
|
+
if (this.config.disabled || !this.pointerNotes.has(t)) return;
|
|
488
|
+
const o = this.pointerNotes.get(t);
|
|
489
|
+
e !== o && (o != null && this.config.onNoteOff(o, t), this.pointerNotes.set(t, e), e !== null && this.config.onNoteOn(e, t));
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Handles the end of a pointer interaction (up).
|
|
493
|
+
*
|
|
494
|
+
* Releases any active note associated with the pointer and removes it
|
|
495
|
+
* from the tracking map.
|
|
496
|
+
*
|
|
497
|
+
* @param pointerId Unique identifier for the pointer
|
|
498
|
+
*/
|
|
499
|
+
handlePointerUp(t) {
|
|
500
|
+
const e = this.pointerNotes.get(t);
|
|
501
|
+
e !== void 0 && (e !== null && this.config.onNoteOff(e, t), this.pointerNotes.delete(t));
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Cancels all active pointer interactions.
|
|
505
|
+
* Useful when the component is unmounted or focus is lost.
|
|
506
|
+
*/
|
|
507
|
+
cancelAll() {
|
|
508
|
+
this.pointerNotes.forEach((t, e) => {
|
|
509
|
+
t !== null && this.config.onNoteOff(t, e);
|
|
510
|
+
}), this.pointerNotes.clear();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const v = {
|
|
514
|
+
forward: (i) => i,
|
|
515
|
+
inverse: (i) => i,
|
|
516
|
+
name: "linear"
|
|
517
|
+
}, F = {
|
|
518
|
+
forward: (i) => i <= 0 ? 0 : i >= 1 ? 1 : Math.log(i * (Math.E - 1) + 1) / Math.log(Math.E),
|
|
519
|
+
inverse: (i) => i <= 0 ? 0 : i >= 1 ? 1 : (Math.pow(Math.E, i) - 1) / (Math.E - 1),
|
|
520
|
+
name: "log"
|
|
521
|
+
}, V = {
|
|
522
|
+
forward: (i) => i <= 0 ? 0 : i >= 1 ? 1 : (Math.pow(Math.E, i) - 1) / (Math.E - 1),
|
|
523
|
+
inverse: (i) => i <= 0 ? 0 : i >= 1 ? 1 : Math.log(i * (Math.E - 1) + 1) / Math.log(Math.E),
|
|
524
|
+
name: "exp"
|
|
525
|
+
};
|
|
526
|
+
class lt {
|
|
527
|
+
constructor(t) {
|
|
528
|
+
l(this, "maxMidi");
|
|
529
|
+
l(this, "scaleFunction", null);
|
|
530
|
+
this.config = t;
|
|
531
|
+
const e = t.midiResolution ?? 32;
|
|
532
|
+
if (this.maxMidi = Math.pow(2, e) - 1, t.type === "continuous") {
|
|
533
|
+
const o = t.scale;
|
|
534
|
+
this.scaleFunction = this.resolveScale(o);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
resolveScale(t) {
|
|
538
|
+
if (!t) return v;
|
|
539
|
+
if (typeof t == "string")
|
|
540
|
+
switch (t) {
|
|
541
|
+
case "linear":
|
|
542
|
+
return v;
|
|
543
|
+
case "log":
|
|
544
|
+
return F;
|
|
545
|
+
case "exp":
|
|
546
|
+
return V;
|
|
547
|
+
default:
|
|
548
|
+
return v;
|
|
549
|
+
}
|
|
550
|
+
return t;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* [Internal] Pure math normalization from Real to 0..1.
|
|
554
|
+
*
|
|
555
|
+
* This is the first step in the conversion pipeline. It normalizes the real value
|
|
556
|
+
* to 0..1 in the real domain, then applies scale transformation (if not linear).
|
|
557
|
+
* The scale transformation happens in the normalized domain to create non-linear
|
|
558
|
+
* mappings (e.g., logarithmic for volume, exponential for envelope curves).
|
|
559
|
+
*/
|
|
560
|
+
_normalizeReal(t) {
|
|
561
|
+
switch (this.config.type) {
|
|
562
|
+
case "continuous": {
|
|
563
|
+
const e = this.config, n = Math.max(0, Math.min(1, (t - e.min) / (e.max - e.min)));
|
|
564
|
+
return this.scaleFunction && this.scaleFunction !== v ? this.scaleFunction.forward(n) : n;
|
|
565
|
+
}
|
|
566
|
+
case "boolean":
|
|
567
|
+
return t ? 1 : 0;
|
|
568
|
+
case "discrete": {
|
|
569
|
+
const e = this.config, o = e.options.findIndex((s) => s.value === t);
|
|
570
|
+
if (o === -1) return 0;
|
|
571
|
+
const n = e.options.length;
|
|
572
|
+
return n > 1 ? o / (n - 1) : 0;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* [Internal] Pure math denormalization from 0..1 to Real.
|
|
578
|
+
*
|
|
579
|
+
* This is the inverse of `_normalizeReal()`. It first applies the inverse scale
|
|
580
|
+
* transformation (if not linear), then denormalizes to the real value domain.
|
|
581
|
+
* Finally, it applies step quantization in the real domain (step is always linear,
|
|
582
|
+
* regardless of scale type).
|
|
583
|
+
*/
|
|
584
|
+
_denormalizeReal(t) {
|
|
585
|
+
const e = Math.max(0, Math.min(1, t));
|
|
586
|
+
switch (this.config.type) {
|
|
587
|
+
case "continuous": {
|
|
588
|
+
const o = this.config;
|
|
589
|
+
let n = e;
|
|
590
|
+
this.scaleFunction && this.scaleFunction !== v && (n = this.scaleFunction.inverse(e));
|
|
591
|
+
let s = o.min + n * (o.max - o.min);
|
|
592
|
+
if (o.step) {
|
|
593
|
+
const r = Math.round((s - o.min) / o.step);
|
|
594
|
+
s = o.min + r * o.step, s = Math.round(s * 1e10) / 1e10;
|
|
595
|
+
}
|
|
596
|
+
return Math.max(o.min, Math.min(o.max, s));
|
|
597
|
+
}
|
|
598
|
+
case "boolean":
|
|
599
|
+
return e >= 0.5;
|
|
600
|
+
case "discrete": {
|
|
601
|
+
const o = this.config, n = o.options.length;
|
|
602
|
+
if (n === 0) return 0;
|
|
603
|
+
const s = Math.round(e * (n - 1));
|
|
604
|
+
return o.options[s].value;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Convert Real Value to MIDI Integer (The Pivot).
|
|
610
|
+
*
|
|
611
|
+
* This is the central conversion method that quantizes real-world values to MIDI integers.
|
|
612
|
+
* The MIDI integer serves as the source of truth, ensuring deterministic behavior and
|
|
613
|
+
* alignment with hardware standards.
|
|
614
|
+
*
|
|
615
|
+
* The conversion flow:
|
|
616
|
+
* 1. Normalize the real value to 0..1 (applying scale transformation if needed)
|
|
617
|
+
* 2. Quantize to the configured MIDI resolution (7-bit = 0-127, 14-bit = 0-16383, etc.)
|
|
618
|
+
*
|
|
619
|
+
* @param realValue The real-world value (number, boolean, or string depending on parameter type)
|
|
620
|
+
* @returns The quantized MIDI integer value
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```ts
|
|
624
|
+
* const converter = new AudioParameterConverter({
|
|
625
|
+
* type: "continuous",
|
|
626
|
+
* min: 0,
|
|
627
|
+
* max: 100,
|
|
628
|
+
* midiResolution: 7
|
|
629
|
+
* });
|
|
630
|
+
* converter.toMidi(50); // 64 (50% of 0-100 maps to 64 in 0-127 range)
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
633
|
+
toMidi(t) {
|
|
634
|
+
switch (this.config.type) {
|
|
635
|
+
case "continuous": {
|
|
636
|
+
const e = this._normalizeReal(t);
|
|
637
|
+
return Math.round(e * this.maxMidi);
|
|
638
|
+
}
|
|
639
|
+
case "boolean":
|
|
640
|
+
return t ? this.maxMidi : 0;
|
|
641
|
+
case "discrete": {
|
|
642
|
+
const e = this.config, o = e.midiMapping ?? "spread";
|
|
643
|
+
if (o === "custom") {
|
|
644
|
+
const s = e.options.find((r) => r.value === t);
|
|
645
|
+
if (s?.midiValue !== void 0) return s.midiValue;
|
|
646
|
+
}
|
|
647
|
+
if (o === "sequential") {
|
|
648
|
+
const s = e.options.findIndex((r) => r.value === t);
|
|
649
|
+
return s === -1 ? 0 : s;
|
|
650
|
+
}
|
|
651
|
+
const n = this._normalizeReal(t);
|
|
652
|
+
return Math.round(n * this.maxMidi);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Convert MIDI Integer to Real Value.
|
|
658
|
+
*
|
|
659
|
+
* This method performs the inverse of `toMidi()`, converting a quantized MIDI integer
|
|
660
|
+
* back to a real-world value. The conversion flow:
|
|
661
|
+
* 1. Normalize the MIDI integer to 0..1
|
|
662
|
+
* 2. Apply inverse scale transformation (if not linear)
|
|
663
|
+
* 3. Denormalize to the real value domain
|
|
664
|
+
* 4. Apply step quantization (if configured)
|
|
665
|
+
*
|
|
666
|
+
* @param midiValue The MIDI integer value (will be clamped to valid range)
|
|
667
|
+
* @returns The real-world value (number, boolean, or string depending on parameter type)
|
|
668
|
+
*
|
|
669
|
+
* @example
|
|
670
|
+
* ```ts
|
|
671
|
+
* const converter = new AudioParameterConverter({
|
|
672
|
+
* type: "continuous",
|
|
673
|
+
* min: 0,
|
|
674
|
+
* max: 100,
|
|
675
|
+
* step: 1,
|
|
676
|
+
* midiResolution: 7
|
|
677
|
+
* });
|
|
678
|
+
* converter.fromMidi(64); // 50 (64/127 ≈ 0.5, maps to 50 in 0-100 range)
|
|
679
|
+
* ```
|
|
680
|
+
*/
|
|
681
|
+
fromMidi(t) {
|
|
682
|
+
const e = Math.max(0, Math.min(this.maxMidi, t));
|
|
683
|
+
switch (this.config.type) {
|
|
684
|
+
case "continuous": {
|
|
685
|
+
const o = e / this.maxMidi;
|
|
686
|
+
return this._denormalizeReal(o);
|
|
687
|
+
}
|
|
688
|
+
case "boolean": {
|
|
689
|
+
const o = this.maxMidi / 2;
|
|
690
|
+
return e >= o;
|
|
691
|
+
}
|
|
692
|
+
case "discrete": {
|
|
693
|
+
const o = this.config, n = o.midiMapping ?? "spread";
|
|
694
|
+
if (n === "custom") {
|
|
695
|
+
let r = o.options[0], c = 1 / 0;
|
|
696
|
+
for (const a of o.options)
|
|
697
|
+
if (a.midiValue !== void 0) {
|
|
698
|
+
const u = Math.abs(a.midiValue - e);
|
|
699
|
+
u < c && (c = u, r = a);
|
|
700
|
+
}
|
|
701
|
+
return r.value;
|
|
702
|
+
}
|
|
703
|
+
if (n === "sequential") {
|
|
704
|
+
const r = e;
|
|
705
|
+
return r >= 0 && r < o.options.length ? o.options[r].value : o.options[o.options.length - 1]?.value ?? 0;
|
|
706
|
+
}
|
|
707
|
+
const s = e / this.maxMidi;
|
|
708
|
+
return this._denormalizeReal(s);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
normalize(t) {
|
|
713
|
+
return this.toMidi(t) / this.maxMidi;
|
|
714
|
+
}
|
|
715
|
+
denormalize(t) {
|
|
716
|
+
const e = Math.round(t * this.maxMidi);
|
|
717
|
+
return this.fromMidi(e);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Format a value for display as a string.
|
|
721
|
+
*
|
|
722
|
+
* This method generates a human-readable string representation of the value,
|
|
723
|
+
* including appropriate units and precision based on the parameter configuration.
|
|
724
|
+
*
|
|
725
|
+
* - Continuous parameters: Includes unit suffix and precision based on step size
|
|
726
|
+
* - Boolean parameters: Uses trueLabel/falseLabel or defaults to "On"/"Off"
|
|
727
|
+
* - Discrete parameters: Returns the label of the matching option
|
|
728
|
+
*
|
|
729
|
+
* @param value The value to format (number, boolean, or string)
|
|
730
|
+
* @returns Formatted string representation
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* ```ts
|
|
734
|
+
* const converter = new AudioParameterConverter({
|
|
735
|
+
* type: "continuous",
|
|
736
|
+
* min: 0,
|
|
737
|
+
* max: 100,
|
|
738
|
+
* step: 0.1,
|
|
739
|
+
* unit: "dB"
|
|
740
|
+
* });
|
|
741
|
+
* converter.format(50.5); // "50.5 dB"
|
|
742
|
+
*
|
|
743
|
+
* const boolConverter = new AudioParameterConverter({
|
|
744
|
+
* type: "boolean",
|
|
745
|
+
* trueLabel: "Enabled",
|
|
746
|
+
* falseLabel: "Disabled"
|
|
747
|
+
* });
|
|
748
|
+
* boolConverter.format(true); // "Enabled"
|
|
749
|
+
* ```
|
|
750
|
+
*/
|
|
751
|
+
format(t) {
|
|
752
|
+
switch (this.config.type) {
|
|
753
|
+
case "continuous": {
|
|
754
|
+
const e = this.config, o = t, n = e.step ? Math.max(0, Math.ceil(Math.log10(1 / e.step))) : 1, s = o.toFixed(n);
|
|
755
|
+
return `${parseFloat(s).toString()}${e.unit ? " " + e.unit : ""}`;
|
|
756
|
+
}
|
|
757
|
+
case "boolean": {
|
|
758
|
+
const e = this.config;
|
|
759
|
+
return (t ? e.trueLabel : e.falseLabel) ?? (t ? "On" : "Off");
|
|
760
|
+
}
|
|
761
|
+
case "discrete":
|
|
762
|
+
return this.config.options.find((n) => n.value === t)?.label ?? String(t);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Get the maximum display text for sizing purposes.
|
|
767
|
+
* Returns the longest formatted string among all possible values.
|
|
768
|
+
* Useful for RadialText referenceText prop to ensure consistent sizing.
|
|
769
|
+
*
|
|
770
|
+
* @param options - Optional configuration
|
|
771
|
+
* @param options.includeUnit - Whether to include the unit in the result (default: true).
|
|
772
|
+
* Set to false when displaying value and unit on separate lines.
|
|
773
|
+
* @returns The longest formatted display string
|
|
774
|
+
*
|
|
775
|
+
* @example
|
|
776
|
+
* ```ts
|
|
777
|
+
* const converter = new AudioParameterConverter(volumeParam);
|
|
778
|
+
* // Single line with unit
|
|
779
|
+
* <RadialText text={currentValue} referenceText={converter.getMaxDisplayText()} />
|
|
780
|
+
*
|
|
781
|
+
* // Multiline: value on first line, unit on second
|
|
782
|
+
* <RadialText
|
|
783
|
+
* text={[formattedValue, unit]}
|
|
784
|
+
* referenceText={[converter.getMaxDisplayText({ includeUnit: false }), unit]}
|
|
785
|
+
* />
|
|
786
|
+
* ```
|
|
787
|
+
*/
|
|
788
|
+
getMaxDisplayText(t) {
|
|
789
|
+
const e = t?.includeUnit ?? !0;
|
|
790
|
+
switch (this.config.type) {
|
|
791
|
+
case "continuous": {
|
|
792
|
+
const o = this.config;
|
|
793
|
+
if (e) {
|
|
794
|
+
const n = this.format(o.min), s = this.format(o.max);
|
|
795
|
+
return n.length >= s.length ? n : s;
|
|
796
|
+
} else {
|
|
797
|
+
const n = o.step ? Math.max(0, Math.ceil(Math.log10(1 / o.step))) : 1, s = o.min.toFixed(n), r = o.max.toFixed(n), c = parseFloat(s).toString(), a = parseFloat(r).toString();
|
|
798
|
+
return c.length >= a.length ? c : a;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
case "boolean": {
|
|
802
|
+
const o = this.config, n = o.trueLabel ?? "On", s = o.falseLabel ?? "Off";
|
|
803
|
+
return n.length >= s.length ? n : s;
|
|
804
|
+
}
|
|
805
|
+
case "discrete": {
|
|
806
|
+
const o = this.config;
|
|
807
|
+
let n = "";
|
|
808
|
+
for (const s of o.options) {
|
|
809
|
+
const r = s.label ?? String(s.value);
|
|
810
|
+
r.length > n.length && (n = r);
|
|
811
|
+
}
|
|
812
|
+
return n;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const ct = {
|
|
818
|
+
/**
|
|
819
|
+
* Creates a standard 7-bit MIDI CC parameter (0-127).
|
|
820
|
+
*
|
|
821
|
+
* This is the most common MIDI control change format, used for most hardware controllers
|
|
822
|
+
* and software synthesizers. The parameter uses 7-bit resolution (128 steps).
|
|
823
|
+
*
|
|
824
|
+
* @param name The parameter name (used to generate ID and name fields)
|
|
825
|
+
* @returns A ContinuousParameter configured for 7-bit MIDI CC
|
|
826
|
+
*
|
|
827
|
+
* @example
|
|
828
|
+
* ```ts
|
|
829
|
+
* const volumeParam = AudioParameterFactory.createMidiStandard7Bit("Volume");
|
|
830
|
+
* // { type: "continuous", min: 0, max: 127, step: 1, midiResolution: 7, ... }
|
|
831
|
+
* ```
|
|
832
|
+
*/
|
|
833
|
+
createMidiStandard7Bit: (i) => ({
|
|
834
|
+
id: `cc-${i.toLowerCase().replace(/\s+/g, "-")}`,
|
|
835
|
+
name: i,
|
|
836
|
+
type: "continuous",
|
|
837
|
+
min: 0,
|
|
838
|
+
max: 127,
|
|
839
|
+
step: 1,
|
|
840
|
+
midiResolution: 7,
|
|
841
|
+
unit: ""
|
|
842
|
+
}),
|
|
843
|
+
/**
|
|
844
|
+
* Creates a standard 14-bit MIDI CC parameter (0-16383).
|
|
845
|
+
*
|
|
846
|
+
* This provides higher resolution than 7-bit CC, useful for parameters that require
|
|
847
|
+
* fine-grained control. The parameter uses 14-bit resolution (16,384 steps).
|
|
848
|
+
*
|
|
849
|
+
* @param name The parameter name (used to generate ID and name fields)
|
|
850
|
+
* @returns A ContinuousParameter configured for 14-bit MIDI CC
|
|
851
|
+
*
|
|
852
|
+
* @example
|
|
853
|
+
* ```ts
|
|
854
|
+
* const fineTuneParam = AudioParameterFactory.createMidiStandard14Bit("Fine Tune");
|
|
855
|
+
* // { type: "continuous", min: 0, max: 16383, step: 1, midiResolution: 14, ... }
|
|
856
|
+
* ```
|
|
857
|
+
*/
|
|
858
|
+
createMidiStandard14Bit: (i) => ({
|
|
859
|
+
id: `cc14-${i.toLowerCase().replace(/\s+/g, "-")}`,
|
|
860
|
+
name: i,
|
|
861
|
+
type: "continuous",
|
|
862
|
+
min: 0,
|
|
863
|
+
max: 16383,
|
|
864
|
+
step: 1,
|
|
865
|
+
midiResolution: 14,
|
|
866
|
+
unit: ""
|
|
867
|
+
}),
|
|
868
|
+
/**
|
|
869
|
+
* Creates a bipolar 7-bit MIDI CC parameter (-64 to 63, centered at 0).
|
|
870
|
+
*
|
|
871
|
+
* This is useful for parameters that have a center point, such as pan controls or
|
|
872
|
+
* modulation depth. The range is symmetric around zero, with 64 steps in each direction.
|
|
873
|
+
*
|
|
874
|
+
* @param name The parameter name (used to generate ID and name fields)
|
|
875
|
+
* @returns A ContinuousParameter configured for bipolar 7-bit MIDI CC
|
|
876
|
+
*
|
|
877
|
+
* @example
|
|
878
|
+
* ```ts
|
|
879
|
+
* const panParam = AudioParameterFactory.createMidiBipolar7Bit("Pan");
|
|
880
|
+
* // { type: "continuous", min: -64, max: 63, step: 1, defaultValue: 0, ... }
|
|
881
|
+
* ```
|
|
882
|
+
*/
|
|
883
|
+
createMidiBipolar7Bit: (i) => ({
|
|
884
|
+
id: `cc-bipolar-${i.toLowerCase().replace(/\s+/g, "-")}`,
|
|
885
|
+
name: i,
|
|
886
|
+
type: "continuous",
|
|
887
|
+
min: -64,
|
|
888
|
+
max: 63,
|
|
889
|
+
step: 1,
|
|
890
|
+
midiResolution: 7,
|
|
891
|
+
unit: "",
|
|
892
|
+
defaultValue: 0,
|
|
893
|
+
bipolar: !0
|
|
894
|
+
}),
|
|
895
|
+
/**
|
|
896
|
+
* Creates a bipolar 14-bit MIDI CC parameter (-8192 to 8191, centered at 0).
|
|
897
|
+
*
|
|
898
|
+
* This provides high-resolution bipolar control, useful for parameters that require
|
|
899
|
+
* fine-grained adjustment around a center point. The range is symmetric around zero,
|
|
900
|
+
* with 8,192 steps in each direction.
|
|
901
|
+
*
|
|
902
|
+
* @param name The parameter name (used to generate ID and name fields)
|
|
903
|
+
* @returns A ContinuousParameter configured for bipolar 14-bit MIDI CC
|
|
904
|
+
*
|
|
905
|
+
* @example
|
|
906
|
+
* ```ts
|
|
907
|
+
* const finePanParam = AudioParameterFactory.createMidiBipolar14Bit("Fine Pan");
|
|
908
|
+
* // { type: "continuous", min: -8192, max: 8191, step: 1, defaultValue: 0, ... }
|
|
909
|
+
* ```
|
|
910
|
+
*/
|
|
911
|
+
createMidiBipolar14Bit: (i) => ({
|
|
912
|
+
id: `cc14-bipolar-${i.toLowerCase().replace(/\s+/g, "-")}`,
|
|
913
|
+
name: i,
|
|
914
|
+
type: "continuous",
|
|
915
|
+
min: -8192,
|
|
916
|
+
max: 8191,
|
|
917
|
+
step: 1,
|
|
918
|
+
midiResolution: 14,
|
|
919
|
+
unit: "",
|
|
920
|
+
defaultValue: 0,
|
|
921
|
+
bipolar: !0
|
|
922
|
+
}),
|
|
923
|
+
/**
|
|
924
|
+
* Creates a bipolar parameter with custom range (centered at 0).
|
|
925
|
+
*
|
|
926
|
+
* This is a flexible factory method for creating bipolar parameters with any range.
|
|
927
|
+
* The parameter is symmetric around zero, useful for pan controls, modulation depth,
|
|
928
|
+
* or any parameter that has a neutral center point.
|
|
929
|
+
*
|
|
930
|
+
* @param name The parameter name (used to generate ID and name fields)
|
|
931
|
+
* @param range The range value (default: 100). The parameter will span from -range to +range
|
|
932
|
+
* @param unit Optional unit suffix (e.g., "dB", "%")
|
|
933
|
+
* @returns A ContinuousParameter configured for bipolar control
|
|
934
|
+
*
|
|
935
|
+
* @example
|
|
936
|
+
* ```ts
|
|
937
|
+
* const panParam = AudioParameterFactory.createBipolar("Pan", 100, "%");
|
|
938
|
+
* // { type: "continuous", min: -100, max: 100, step: 1, defaultValue: 0, unit: "%", ... }
|
|
939
|
+
*
|
|
940
|
+
* const modDepthParam = AudioParameterFactory.createBipolar("Mod Depth", 50);
|
|
941
|
+
* // { type: "continuous", min: -50, max: 50, step: 1, defaultValue: 0, ... }
|
|
942
|
+
* ```
|
|
943
|
+
*/
|
|
944
|
+
createBipolar: (i, t = 100, e = "") => ({
|
|
945
|
+
id: `bipolar-${i.toLowerCase().replace(/\s+/g, "-")}`,
|
|
946
|
+
name: i,
|
|
947
|
+
type: "continuous",
|
|
948
|
+
min: -t,
|
|
949
|
+
max: t,
|
|
950
|
+
step: 1,
|
|
951
|
+
unit: e,
|
|
952
|
+
defaultValue: 0,
|
|
953
|
+
bipolar: !0
|
|
954
|
+
}),
|
|
955
|
+
/**
|
|
956
|
+
* Creates a boolean switch parameter (Off/On).
|
|
957
|
+
*
|
|
958
|
+
* This factory method creates a boolean parameter suitable for on/off controls,
|
|
959
|
+
* with support for both toggle (latch) and momentary modes.
|
|
960
|
+
*
|
|
961
|
+
* @param name The parameter name (used to generate ID and name fields)
|
|
962
|
+
* @param mode The switch mode: "toggle" (latch) or "momentary" (only active while pressed)
|
|
963
|
+
* @returns A BooleanParameter configured as a switch
|
|
964
|
+
*
|
|
965
|
+
* @example
|
|
966
|
+
* ```ts
|
|
967
|
+
* const powerParam = AudioParameterFactory.createSwitch("Power", "toggle");
|
|
968
|
+
* // { type: "boolean", mode: "toggle", trueLabel: "On", falseLabel: "Off", ... }
|
|
969
|
+
*
|
|
970
|
+
* const recordParam = AudioParameterFactory.createSwitch("Record", "momentary");
|
|
971
|
+
* // { type: "boolean", mode: "momentary", ... }
|
|
972
|
+
* ```
|
|
973
|
+
*/
|
|
974
|
+
createSwitch: (i, t = "toggle") => ({
|
|
975
|
+
id: `sw-${i.toLowerCase().replace(/\s+/g, "-")}`,
|
|
976
|
+
name: i,
|
|
977
|
+
type: "boolean",
|
|
978
|
+
mode: t,
|
|
979
|
+
defaultValue: !1,
|
|
980
|
+
trueLabel: "On",
|
|
981
|
+
falseLabel: "Off",
|
|
982
|
+
midiResolution: 7
|
|
983
|
+
}),
|
|
984
|
+
/**
|
|
985
|
+
* Creates a discrete (selector) parameter.
|
|
986
|
+
*
|
|
987
|
+
* This factory method creates a discrete parameter suitable for mode selectors,
|
|
988
|
+
* preset switches, or any control that cycles through discrete options.
|
|
989
|
+
*
|
|
990
|
+
* @param name The parameter name (used to generate ID and name fields)
|
|
991
|
+
* @param options Array of option objects, each with a value and label
|
|
992
|
+
* @returns A DiscreteParameter configured as a selector
|
|
993
|
+
*
|
|
994
|
+
* @example
|
|
995
|
+
* ```ts
|
|
996
|
+
* const waveParam = AudioParameterFactory.createSelector("Waveform", [
|
|
997
|
+
* { value: "sine", label: "Sine" },
|
|
998
|
+
* { value: "square", label: "Square" },
|
|
999
|
+
* { value: "sawtooth", label: "Sawtooth" }
|
|
1000
|
+
* ]);
|
|
1001
|
+
* ```
|
|
1002
|
+
*/
|
|
1003
|
+
createSelector: (i, t) => ({
|
|
1004
|
+
id: `sel-${i.toLowerCase().replace(/\s+/g, "-")}`,
|
|
1005
|
+
name: i,
|
|
1006
|
+
type: "discrete",
|
|
1007
|
+
options: t,
|
|
1008
|
+
defaultValue: t[0]?.value,
|
|
1009
|
+
midiResolution: 7,
|
|
1010
|
+
midiMapping: "spread"
|
|
1011
|
+
}),
|
|
1012
|
+
/**
|
|
1013
|
+
* Creates a generic continuous control parameter (for ad-hoc UI controls like Knob/Slider).
|
|
1014
|
+
*
|
|
1015
|
+
* This is the most flexible factory method, allowing you to specify all aspects of a
|
|
1016
|
+
* continuous parameter. It's useful when you need a parameter that doesn't fit the
|
|
1017
|
+
* standard MIDI presets.
|
|
1018
|
+
*
|
|
1019
|
+
* The method handles bipolar mode automatically: if `bipolar` is true, it adjusts
|
|
1020
|
+
* min/max to be symmetric around zero when only one boundary is provided. For default values:
|
|
1021
|
+
* - If `defaultValue` is provided, it is used (even when `bipolar=true`)
|
|
1022
|
+
* - If `defaultValue` is not provided and `bipolar=true`, the center of the range is calculated
|
|
1023
|
+
* (0 for symmetric ranges, otherwise the midpoint)
|
|
1024
|
+
* - If `defaultValue` is not provided and `bipolar=false`, falls back to `min` or 0
|
|
1025
|
+
*
|
|
1026
|
+
* @param config Configuration object with optional fields for all parameter properties
|
|
1027
|
+
* @returns A ContinuousParameter configured according to the provided config
|
|
1028
|
+
*
|
|
1029
|
+
* @example
|
|
1030
|
+
* ```ts
|
|
1031
|
+
* const volumeParam = AudioParameterFactory.createControl({
|
|
1032
|
+
* name: "Volume",
|
|
1033
|
+
* min: 0,
|
|
1034
|
+
* max: 100,
|
|
1035
|
+
* step: 0.1,
|
|
1036
|
+
* unit: "dB",
|
|
1037
|
+
* scale: "log"
|
|
1038
|
+
* });
|
|
1039
|
+
*
|
|
1040
|
+
* const panParam = AudioParameterFactory.createControl({
|
|
1041
|
+
* name: "Pan",
|
|
1042
|
+
* bipolar: true,
|
|
1043
|
+
* unit: "%"
|
|
1044
|
+
* });
|
|
1045
|
+
* // Automatically sets min: -100, max: 100, defaultValue: 0
|
|
1046
|
+
*
|
|
1047
|
+
* const customBipolarParam = AudioParameterFactory.createControl({
|
|
1048
|
+
* name: "Custom",
|
|
1049
|
+
* min: 0,
|
|
1050
|
+
* max: 127,
|
|
1051
|
+
* bipolar: true,
|
|
1052
|
+
* defaultValue: 64
|
|
1053
|
+
* });
|
|
1054
|
+
* // Uses provided defaultValue: 64 (center of 0-127 range)
|
|
1055
|
+
* ```
|
|
1056
|
+
*/
|
|
1057
|
+
createControl: (i) => {
|
|
1058
|
+
const { id: t, name: e, label: o, min: n, max: s, step: r, bipolar: c, unit: a = "", defaultValue: u, scale: g, midiResolution: m } = i;
|
|
1059
|
+
let d = n, f = s, h = u;
|
|
1060
|
+
if (c) {
|
|
1061
|
+
if (n === void 0 && s === void 0 ? (d = -100, f = 100) : n === void 0 && s !== void 0 ? d = -s : n !== void 0 && s === void 0 && (f = -n), h === void 0) {
|
|
1062
|
+
const b = d ?? 0, M = f ?? 100;
|
|
1063
|
+
b === -M ? h = 0 : h = (b + M) / 2;
|
|
1064
|
+
}
|
|
1065
|
+
} else
|
|
1066
|
+
h = h ?? n ?? 0;
|
|
1067
|
+
return {
|
|
1068
|
+
id: t ?? "adhoc-control",
|
|
1069
|
+
type: "continuous",
|
|
1070
|
+
name: e ?? o ?? "",
|
|
1071
|
+
min: d ?? 0,
|
|
1072
|
+
max: f ?? 100,
|
|
1073
|
+
step: r,
|
|
1074
|
+
unit: a,
|
|
1075
|
+
defaultValue: h,
|
|
1076
|
+
scale: g,
|
|
1077
|
+
midiResolution: m,
|
|
1078
|
+
bipolar: c ?? !1
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
}, x = (i, t, e, o) => {
|
|
1082
|
+
const n = (o - 90) * Math.PI / 180;
|
|
1083
|
+
return {
|
|
1084
|
+
x: i + e * Math.cos(n),
|
|
1085
|
+
y: t + e * Math.sin(n)
|
|
1086
|
+
};
|
|
1087
|
+
};
|
|
1088
|
+
function ut(i, t = 90, e = 0, o = !1, n) {
|
|
1089
|
+
const s = Math.max(0, Math.min(1, i));
|
|
1090
|
+
let r = s;
|
|
1091
|
+
n !== void 0 && n >= 1 && (n === 1 ? r = 0.5 : r = Math.round(s * (n - 1)) / (n - 1));
|
|
1092
|
+
const c = Math.max(0, Math.min(360, t)), a = 180 + c / 2, u = 540 - c / 2, g = a, m = u, d = u - a, f = r * d + g, h = g - e, b = m - e;
|
|
1093
|
+
let M = h;
|
|
1094
|
+
o && (M = 360 - e);
|
|
1095
|
+
const A = f - e;
|
|
1096
|
+
return {
|
|
1097
|
+
normalizedValue: r,
|
|
1098
|
+
openness: c,
|
|
1099
|
+
startAngle: h,
|
|
1100
|
+
endAngle: b,
|
|
1101
|
+
valueToAngle: A,
|
|
1102
|
+
valueStartAngle: M
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function ht(i, t, e) {
|
|
1106
|
+
const o = Math.max(0, Math.min(1, e));
|
|
1107
|
+
return i + t / 2 - o * t;
|
|
1108
|
+
}
|
|
1109
|
+
const dt = (i, t, e, o, n, s = "counter-clockwise") => {
|
|
1110
|
+
e > o && ([e, o] = [o, e]);
|
|
1111
|
+
const r = (f) => Math.round(f * 1e4) / 1e4, c = x(i, t, n, e), a = x(i, t, n, o), u = o - e <= 180 ? "0" : "1", [g, m, d] = s === "counter-clockwise" ? [a, c, 0] : [c, a, 1];
|
|
1112
|
+
return ["M", r(g.x), r(g.y), "A", r(n), r(n), 0, u, d, r(m.x), r(m.y)].join(" ");
|
|
1113
|
+
}, G = {
|
|
1114
|
+
xsmall: "audioui-size-square-xsmall",
|
|
1115
|
+
small: "audioui-size-square-small",
|
|
1116
|
+
normal: "audioui-size-square-normal",
|
|
1117
|
+
large: "audioui-size-square-large",
|
|
1118
|
+
xlarge: "audioui-size-square-xlarge"
|
|
1119
|
+
}, _ = {
|
|
1120
|
+
xsmall: "audioui-size-hslider-xsmall",
|
|
1121
|
+
small: "audioui-size-hslider-small",
|
|
1122
|
+
normal: "audioui-size-hslider-normal",
|
|
1123
|
+
large: "audioui-size-hslider-large",
|
|
1124
|
+
xlarge: "audioui-size-hslider-xlarge"
|
|
1125
|
+
}, O = {
|
|
1126
|
+
xsmall: "audioui-size-vslider-xsmall",
|
|
1127
|
+
small: "audioui-size-vslider-small",
|
|
1128
|
+
normal: "audioui-size-vslider-normal",
|
|
1129
|
+
large: "audioui-size-vslider-large",
|
|
1130
|
+
xlarge: "audioui-size-vslider-xlarge"
|
|
1131
|
+
}, U = {
|
|
1132
|
+
xsmall: "audioui-size-keys-xsmall",
|
|
1133
|
+
small: "audioui-size-keys-small",
|
|
1134
|
+
normal: "audioui-size-keys-normal",
|
|
1135
|
+
large: "audioui-size-keys-large",
|
|
1136
|
+
xlarge: "audioui-size-keys-xlarge"
|
|
1137
|
+
};
|
|
1138
|
+
function ft(i, t = "normal", e = "vertical") {
|
|
1139
|
+
switch (i) {
|
|
1140
|
+
case "knob":
|
|
1141
|
+
case "button":
|
|
1142
|
+
return G[t];
|
|
1143
|
+
case "keys":
|
|
1144
|
+
return U[t];
|
|
1145
|
+
case "slider":
|
|
1146
|
+
return e === "horizontal" ? _[t] : O[t];
|
|
1147
|
+
default:
|
|
1148
|
+
throw new Error(`Unknown component type: ${i}`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
function gt(i, t = "normal", e = "vertical") {
|
|
1152
|
+
switch (i) {
|
|
1153
|
+
case "knob":
|
|
1154
|
+
case "button":
|
|
1155
|
+
return {
|
|
1156
|
+
width: `var(--audioui-size-square-${t})`,
|
|
1157
|
+
height: `var(--audioui-size-square-${t})`
|
|
1158
|
+
};
|
|
1159
|
+
case "keys":
|
|
1160
|
+
return {
|
|
1161
|
+
width: `var(--audioui-size-keys-width-${t})`,
|
|
1162
|
+
height: `var(--audioui-size-keys-height-${t})`
|
|
1163
|
+
};
|
|
1164
|
+
case "slider":
|
|
1165
|
+
return e === "horizontal" ? {
|
|
1166
|
+
width: `var(--audioui-size-hslider-width-${t})`,
|
|
1167
|
+
height: `var(--audioui-size-hslider-height-${t})`
|
|
1168
|
+
} : {
|
|
1169
|
+
width: `var(--audioui-size-vslider-width-${t})`,
|
|
1170
|
+
height: `var(--audioui-size-vslider-height-${t})`
|
|
1171
|
+
};
|
|
1172
|
+
default:
|
|
1173
|
+
throw new Error(`Unknown component type: ${i}`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
const w = {
|
|
1177
|
+
blue: "hsl(204, 88%, 53%)",
|
|
1178
|
+
orange: "hsl(29, 100%, 50%)",
|
|
1179
|
+
pink: "hsl(332, 95%, 54%)",
|
|
1180
|
+
green: "hsl(160, 98%, 37%)",
|
|
1181
|
+
purple: "hsl(252, 100%, 67%)",
|
|
1182
|
+
yellow: "hsl(50, 100%, 50%)",
|
|
1183
|
+
white: "hsl(0, 0%, 100%)"
|
|
1184
|
+
}, I = /hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/;
|
|
1185
|
+
function mt() {
|
|
1186
|
+
return typeof window > "u" ? !1 : document.documentElement.classList.contains("dark") || window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
1187
|
+
}
|
|
1188
|
+
function pt() {
|
|
1189
|
+
return `var(${P.adaptiveDefaultColor})`;
|
|
1190
|
+
}
|
|
1191
|
+
function C(i, t) {
|
|
1192
|
+
const e = w[i.toLowerCase()];
|
|
1193
|
+
if (e) {
|
|
1194
|
+
const n = e.match(I);
|
|
1195
|
+
if (n) {
|
|
1196
|
+
const s = n[1], r = n[2], c = parseInt(n[3], 10), a = Math.max(0, Math.min(100, c * (t / 100)));
|
|
1197
|
+
return `hsl(${s}, ${r}%, ${a}%)`;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
const o = 100 - t;
|
|
1201
|
+
return `color-mix(in srgb, ${i}, black ${o}%)`;
|
|
1202
|
+
}
|
|
1203
|
+
function S(i, t) {
|
|
1204
|
+
return `color-mix(in srgb, ${i} ${t}%, transparent)`;
|
|
1205
|
+
}
|
|
1206
|
+
function Y(i) {
|
|
1207
|
+
const t = w[i.toLowerCase()];
|
|
1208
|
+
t && (i = t);
|
|
1209
|
+
const e = i.match(I);
|
|
1210
|
+
if (e) {
|
|
1211
|
+
const o = parseInt(e[1], 10), n = Math.min(100, parseInt(e[2], 10) + 10), s = Math.min(70, parseInt(e[3], 10) + 10);
|
|
1212
|
+
return `hsl(${o}, ${n}%, ${s}%)`;
|
|
1213
|
+
}
|
|
1214
|
+
return `color-mix(in srgb, ${i} 80%, white 20%)`;
|
|
1215
|
+
}
|
|
1216
|
+
function Mt(i, t = "luminosity") {
|
|
1217
|
+
const o = w[i.toLowerCase()] ?? i, n = Y(o);
|
|
1218
|
+
return t === "luminosity" ? {
|
|
1219
|
+
primary: o,
|
|
1220
|
+
primary50: C(o, 50),
|
|
1221
|
+
primary20: C(o, 20),
|
|
1222
|
+
highlight: n
|
|
1223
|
+
} : {
|
|
1224
|
+
primary: o,
|
|
1225
|
+
primary50: S(o, 50),
|
|
1226
|
+
primary20: S(o, 20),
|
|
1227
|
+
highlight: n
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
function p(i) {
|
|
1231
|
+
return Math.max(0, Math.min(1, i));
|
|
1232
|
+
}
|
|
1233
|
+
function vt(i) {
|
|
1234
|
+
return p(i) === 0 ? 0 : 1;
|
|
1235
|
+
}
|
|
1236
|
+
function bt(i) {
|
|
1237
|
+
const t = p(i);
|
|
1238
|
+
return Math.round(1 + t * 19);
|
|
1239
|
+
}
|
|
1240
|
+
function wt(i) {
|
|
1241
|
+
const t = p(i);
|
|
1242
|
+
return Math.round(t * 20);
|
|
1243
|
+
}
|
|
1244
|
+
function yt(i) {
|
|
1245
|
+
const t = p(i);
|
|
1246
|
+
return Math.round(1 + t * 49);
|
|
1247
|
+
}
|
|
1248
|
+
function xt(i) {
|
|
1249
|
+
const t = p(i);
|
|
1250
|
+
return Math.round(t * 50);
|
|
1251
|
+
}
|
|
1252
|
+
function Ct(i) {
|
|
1253
|
+
const t = p(i);
|
|
1254
|
+
return Math.round(t * 12);
|
|
1255
|
+
}
|
|
1256
|
+
const y = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"], St = ["C", "D", "E", "F", "G", "A", "B"], It = [0, 2, 4, 5, 7, 9, 11], kt = {
|
|
1257
|
+
C: 0,
|
|
1258
|
+
D: 2,
|
|
1259
|
+
E: 4,
|
|
1260
|
+
F: 5,
|
|
1261
|
+
G: 7,
|
|
1262
|
+
A: 9,
|
|
1263
|
+
B: 11
|
|
1264
|
+
}, B = /* @__PURE__ */ new Set([0, 2, 4, 5, 7, 9, 11]), k = [], D = /* @__PURE__ */ new Map();
|
|
1265
|
+
for (let i = 0; i < 128; i++) {
|
|
1266
|
+
const t = Math.floor(i / 12) - 1, e = i % 12, n = `${y[e]}${t}`;
|
|
1267
|
+
k[i] = n, D.set(n, i);
|
|
1268
|
+
}
|
|
1269
|
+
const Dt = (i) => {
|
|
1270
|
+
if (i >= 0 && i < 128)
|
|
1271
|
+
return k[i];
|
|
1272
|
+
const t = Math.floor(i / 12) - 1, e = i % 12;
|
|
1273
|
+
return `${y[e]}${t}`;
|
|
1274
|
+
}, E = (i) => {
|
|
1275
|
+
const t = D.get(i);
|
|
1276
|
+
if (t !== void 0)
|
|
1277
|
+
return t;
|
|
1278
|
+
const e = i.match(/^([A-G](#)?)(-?\d+)$/);
|
|
1279
|
+
if (!e) return -1;
|
|
1280
|
+
const [, o, , n] = e, s = y.findIndex((r) => r === o);
|
|
1281
|
+
return s === -1 ? -1 : (parseInt(n) + 1) * 12 + s;
|
|
1282
|
+
}, H = (i) => {
|
|
1283
|
+
const t = /* @__PURE__ */ new Set();
|
|
1284
|
+
for (const e of i)
|
|
1285
|
+
if (typeof e == "number")
|
|
1286
|
+
t.add(e);
|
|
1287
|
+
else {
|
|
1288
|
+
const o = E(e);
|
|
1289
|
+
o !== -1 && t.add(o);
|
|
1290
|
+
}
|
|
1291
|
+
return t;
|
|
1292
|
+
}, Et = (i, t) => {
|
|
1293
|
+
if (t.length === 0) return !1;
|
|
1294
|
+
const e = H(t);
|
|
1295
|
+
if (typeof i == "string") {
|
|
1296
|
+
const o = E(i);
|
|
1297
|
+
return o !== -1 && e.has(o);
|
|
1298
|
+
}
|
|
1299
|
+
return e.has(i);
|
|
1300
|
+
}, At = (i) => {
|
|
1301
|
+
const t = i % 12;
|
|
1302
|
+
return !B.has(t);
|
|
1303
|
+
}, X = (i, t) => Math.floor((t - i + 1) / 2) + i, j = (i) => i > 0 ? `+${i}` : i.toString(), Nt = (i, t, e) => {
|
|
1304
|
+
const o = X(t, e), n = i - o;
|
|
1305
|
+
return j(n);
|
|
1306
|
+
}, zt = (i) => (t) => `${t}${i}`, Tt = (i) => (t) => t.toFixed(i), Pt = (...i) => (t, e, o) => {
|
|
1307
|
+
let n = t;
|
|
1308
|
+
for (const s of i)
|
|
1309
|
+
if (typeof n == "string") {
|
|
1310
|
+
const r = parseFloat(n);
|
|
1311
|
+
isNaN(r) || (n = s(r, e, o));
|
|
1312
|
+
} else
|
|
1313
|
+
n = s(n, e, o);
|
|
1314
|
+
return n.toString();
|
|
1315
|
+
}, Lt = (i, t, e) => {
|
|
1316
|
+
const o = (i - t) / (e - t) * 100;
|
|
1317
|
+
return `${Math.round(o)}%`;
|
|
1318
|
+
}, $t = (i) => i >= 1e3 ? `${(i / 1e3).toFixed(1)}kHz` : `${Math.round(i)}Hz`;
|
|
1319
|
+
function Rt({
|
|
1320
|
+
deepCompareProps: i = ["style"],
|
|
1321
|
+
alwaysCompareProps: t = ["children"]
|
|
1322
|
+
} = {}) {
|
|
1323
|
+
const e = new Set(i), o = new Set(t);
|
|
1324
|
+
return function(s, r) {
|
|
1325
|
+
for (const a of o)
|
|
1326
|
+
if (s[a] !== r[a])
|
|
1327
|
+
return !1;
|
|
1328
|
+
for (const a of e)
|
|
1329
|
+
if (!T(s[a], r[a]))
|
|
1330
|
+
return !1;
|
|
1331
|
+
const c = Object.keys(s).filter(
|
|
1332
|
+
(a) => !e.has(a) && !o.has(a)
|
|
1333
|
+
);
|
|
1334
|
+
for (const a of c)
|
|
1335
|
+
if (s[a] !== r[a])
|
|
1336
|
+
return !1;
|
|
1337
|
+
return !0;
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
export {
|
|
1341
|
+
lt as AudioParameterConverter,
|
|
1342
|
+
ct as AudioParameterFactory,
|
|
1343
|
+
rt as BooleanInteractionController,
|
|
1344
|
+
y as CHROMATIC_NOTES,
|
|
1345
|
+
it as CIRCULAR_CURSOR,
|
|
1346
|
+
W as CLASSNAMES,
|
|
1347
|
+
P as CSS_VARS,
|
|
1348
|
+
nt as ContinuousInteractionController,
|
|
1349
|
+
L as DEFAULT_CONTINUOUS_SENSITIVITY,
|
|
1350
|
+
R as DEFAULT_KEYBOARD_STEP,
|
|
1351
|
+
tt as DEFAULT_ROUNDNESS,
|
|
1352
|
+
et as DEFAULT_THICKNESS,
|
|
1353
|
+
$ as DEFAULT_WHEEL_SENSITIVITY,
|
|
1354
|
+
kt as DIATONIC_TO_CHROMATIC,
|
|
1355
|
+
st as DiscreteInteractionController,
|
|
1356
|
+
V as ExpScale,
|
|
1357
|
+
v as LinearScale,
|
|
1358
|
+
F as LogScale,
|
|
1359
|
+
at as NoteInteractionController,
|
|
1360
|
+
ot as TARGET_PIXELS_PER_STEP,
|
|
1361
|
+
St as WHITE_KEY_NAMES,
|
|
1362
|
+
B as WHITE_KEY_POSITIONS,
|
|
1363
|
+
It as WHITE_KEY_TO_CHROMATIC,
|
|
1364
|
+
j as bipolarFormatter,
|
|
1365
|
+
ut as calculateArcAngles,
|
|
1366
|
+
dt as calculateArcPath,
|
|
1367
|
+
X as calculateCenterValue,
|
|
1368
|
+
ht as calculateLinearPosition,
|
|
1369
|
+
p as clampNormalized,
|
|
1370
|
+
Pt as combineFormatters,
|
|
1371
|
+
H as createNoteNumSet,
|
|
1372
|
+
Rt as createPropComparator,
|
|
1373
|
+
$t as frequencyFormatter,
|
|
1374
|
+
Mt as generateColorVariants,
|
|
1375
|
+
Y as generateHighlightColor,
|
|
1376
|
+
C as generateLuminosityVariant,
|
|
1377
|
+
S as generateTransparencyVariant,
|
|
1378
|
+
pt as getAdaptiveDefaultColor,
|
|
1379
|
+
ft as getSizeClassForComponent,
|
|
1380
|
+
gt as getSizeStyleForComponent,
|
|
1381
|
+
_ as horizontalSliderSizeClassMap,
|
|
1382
|
+
At as isBlackKey,
|
|
1383
|
+
mt as isDarkMode,
|
|
1384
|
+
Et as isNoteOn,
|
|
1385
|
+
U as keysSizeClassMap,
|
|
1386
|
+
Nt as midiBipolarFormatter,
|
|
1387
|
+
Dt as noteNumToNote,
|
|
1388
|
+
E as noteToNoteNum,
|
|
1389
|
+
Lt as percentageFormatter,
|
|
1390
|
+
x as polarToCartesian,
|
|
1391
|
+
G as squareSizeClassMap,
|
|
1392
|
+
J as themeColors,
|
|
1393
|
+
Q as themeColorsDirect,
|
|
1394
|
+
xt as translateButtonRoundness,
|
|
1395
|
+
Ct as translateKeysRoundness,
|
|
1396
|
+
vt as translateKnobRoundness,
|
|
1397
|
+
bt as translateKnobThickness,
|
|
1398
|
+
wt as translateSliderRoundness,
|
|
1399
|
+
yt as translateSliderThickness,
|
|
1400
|
+
O as verticalSliderSizeClassMap,
|
|
1401
|
+
Tt as withPrecision,
|
|
1402
|
+
zt as withUnit
|
|
1403
|
+
};
|
|
1404
|
+
//# sourceMappingURL=index.js.map
|