@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.
Files changed (48) hide show
  1. package/LICENSE.md +690 -0
  2. package/dist/constants/cssVars.d.ts +42 -0
  3. package/dist/constants/cssVars.d.ts.map +1 -0
  4. package/dist/constants/cursors.d.ts +2 -0
  5. package/dist/constants/cursors.d.ts.map +1 -0
  6. package/dist/constants/interaction.d.ts +24 -0
  7. package/dist/constants/interaction.d.ts.map +1 -0
  8. package/dist/constants/styles.d.ts +17 -0
  9. package/dist/constants/styles.d.ts.map +1 -0
  10. package/dist/constants/theme.d.ts +66 -0
  11. package/dist/constants/theme.d.ts.map +1 -0
  12. package/dist/constants/themeDefaults.d.ts +16 -0
  13. package/dist/constants/themeDefaults.d.ts.map +1 -0
  14. package/dist/controller/BooleanInteractionController.d.ts +141 -0
  15. package/dist/controller/BooleanInteractionController.d.ts.map +1 -0
  16. package/dist/controller/ContinuousInteractionController.d.ts +114 -0
  17. package/dist/controller/ContinuousInteractionController.d.ts.map +1 -0
  18. package/dist/controller/DiscreteInteractionController.d.ts +51 -0
  19. package/dist/controller/DiscreteInteractionController.d.ts.map +1 -0
  20. package/dist/controller/NoteInteractionController.d.ts +88 -0
  21. package/dist/controller/NoteInteractionController.d.ts.map +1 -0
  22. package/dist/index.d.ts +21 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +1404 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/model/AudioParameter.d.ts +437 -0
  27. package/dist/model/AudioParameter.d.ts.map +1 -0
  28. package/dist/styles/styles.css +256 -0
  29. package/dist/styles/themes.css +193 -0
  30. package/dist/types.d.ts +18 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/utils/colorUtils.d.ts +102 -0
  33. package/dist/utils/colorUtils.d.ts.map +1 -0
  34. package/dist/utils/math.d.ts +108 -0
  35. package/dist/utils/math.d.ts.map +1 -0
  36. package/dist/utils/normalizedProps.d.ts +22 -0
  37. package/dist/utils/normalizedProps.d.ts.map +1 -0
  38. package/dist/utils/noteUtils.d.ts +71 -0
  39. package/dist/utils/noteUtils.d.ts.map +1 -0
  40. package/dist/utils/propCompare.d.ts +22 -0
  41. package/dist/utils/propCompare.d.ts.map +1 -0
  42. package/dist/utils/sizing.d.ts +39 -0
  43. package/dist/utils/sizing.d.ts.map +1 -0
  44. package/dist/utils/svg.d.ts +27 -0
  45. package/dist/utils/svg.d.ts.map +1 -0
  46. package/dist/utils/valueFormatters.d.ts +167 -0
  47. package/dist/utils/valueFormatters.d.ts.map +1 -0
  48. 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