@kidlib/web-audio 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +24 -0
- package/dist/components.d.ts +94 -0
- package/dist/components.js +398 -0
- package/dist/index.d.ts +1283 -0
- package/dist/index.js +7550 -0
- package/dist/io.d.ts +239 -0
- package/dist/io.js +193 -0
- package/dist/keymap-3lZMR1Ak.js +167 -0
- package/dist/processors/processors.js +1627 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kristinn Roach Gunnarsson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @kidlib/web-audio
|
|
2
|
+
|
|
3
|
+
High-level Web Audio primitives for creating musical instruments and tools.
|
|
4
|
+
This package requires a browser with Web Audio and AudioWorklet support.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm add @kidlib/web-audio
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createSamplePlayer } from '@kidlib/web-audio';
|
|
16
|
+
import { inputController } from '@kidlib/web-audio/io';
|
|
17
|
+
import { registerKnobElement } from '@kidlib/web-audio/components';
|
|
18
|
+
|
|
19
|
+
const player = await createSamplePlayer();
|
|
20
|
+
player.play(60);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
UI elements such as `KnobElement` are available only from the optional
|
|
24
|
+
`@kidlib/web-audio/components` entry point.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export declare type AudiolibElement = (typeof ELEMENTS)[keyof typeof ELEMENTS];
|
|
2
|
+
|
|
3
|
+
export declare function defineElement(tagName: AudiolibElement, elementClass: CustomElementConstructor): void;
|
|
4
|
+
|
|
5
|
+
declare const ELEMENTS: {
|
|
6
|
+
readonly KNOB: "knob-element";
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export declare type KnobChangeEventDetail = {
|
|
10
|
+
value: number;
|
|
11
|
+
rotation: number;
|
|
12
|
+
percentage: number;
|
|
13
|
+
source: 'user' | 'programmatic';
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export declare class KnobElement extends HTMLElement {
|
|
17
|
+
private pathElement;
|
|
18
|
+
private static stylesInjected;
|
|
19
|
+
private config;
|
|
20
|
+
private currentValue;
|
|
21
|
+
private currentRotation;
|
|
22
|
+
private rotationToValue;
|
|
23
|
+
private valueToRotation;
|
|
24
|
+
private applySnapping;
|
|
25
|
+
private static mapRange;
|
|
26
|
+
private static clamp;
|
|
27
|
+
static get observedAttributes(): string[];
|
|
28
|
+
constructor();
|
|
29
|
+
connectedCallback(): void;
|
|
30
|
+
disconnectedCallback(): void;
|
|
31
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
|
|
32
|
+
private injectGlobalStyles;
|
|
33
|
+
private updateConfigFromAttributes;
|
|
34
|
+
private updateDimensions;
|
|
35
|
+
private updateColorFromAttribute;
|
|
36
|
+
private render;
|
|
37
|
+
private cleanup;
|
|
38
|
+
private createUtilityFunctions;
|
|
39
|
+
private dragHandlers?;
|
|
40
|
+
private lastClickTime;
|
|
41
|
+
private readonly DOUBLE_CLICK_THRESHOLD;
|
|
42
|
+
private createDraggable;
|
|
43
|
+
private updateBorder;
|
|
44
|
+
private dispatchChangeEvent;
|
|
45
|
+
setValue(value: number): void;
|
|
46
|
+
/**
|
|
47
|
+
* Sets the knob value using a normalized 0-1 input, automatically handling
|
|
48
|
+
* the knob's range and curve transformation.
|
|
49
|
+
* @param normalizedValue - Value between 0 and 1
|
|
50
|
+
*/
|
|
51
|
+
setValueNormalized(normalizedValue: number): void;
|
|
52
|
+
/**
|
|
53
|
+
* Gets the current knob value as a normalized 0-1 value, accounting for
|
|
54
|
+
* the knob's range and curve. Inverse of setValueNormalized().
|
|
55
|
+
* @returns Normalized value between 0 and 1
|
|
56
|
+
*/
|
|
57
|
+
getValueNormalized(): number;
|
|
58
|
+
resetToDefault(): void;
|
|
59
|
+
getValue(): number;
|
|
60
|
+
setCurve(curve: number): void;
|
|
61
|
+
getCurve(): number;
|
|
62
|
+
setDisabled(disabled: boolean): void;
|
|
63
|
+
isDisabled(): boolean;
|
|
64
|
+
getPercentage(): number;
|
|
65
|
+
get value(): number;
|
|
66
|
+
set value(val: number);
|
|
67
|
+
get disabled(): boolean;
|
|
68
|
+
set disabled(val: boolean);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export declare type KnobElementOptions = {
|
|
72
|
+
minValue: number;
|
|
73
|
+
maxValue: number;
|
|
74
|
+
defaultValue: number;
|
|
75
|
+
minRotation: number;
|
|
76
|
+
maxRotation: number;
|
|
77
|
+
snapIncrement: number;
|
|
78
|
+
allowedValues?: number[];
|
|
79
|
+
curve?: number;
|
|
80
|
+
snapThresholds?: Array<{
|
|
81
|
+
maxValue: number;
|
|
82
|
+
increment: number;
|
|
83
|
+
}>;
|
|
84
|
+
disabled?: boolean;
|
|
85
|
+
borderStyle?: 'currentState' | 'fullCircle';
|
|
86
|
+
width?: number;
|
|
87
|
+
height?: number;
|
|
88
|
+
color?: string;
|
|
89
|
+
value?: number;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export declare function registerKnobElement(): void;
|
|
93
|
+
|
|
94
|
+
export { }
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
var k = Object.defineProperty;
|
|
2
|
+
var y = (m, d, t) => d in m ? k(m, d, { enumerable: !0, configurable: !0, writable: !0, value: t }) : m[d] = t;
|
|
3
|
+
var h = (m, d, t) => y(m, typeof d != "symbol" ? d + "" : d, t);
|
|
4
|
+
const u = class u extends HTMLElement {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
h(this, "pathElement");
|
|
8
|
+
h(this, "config", {
|
|
9
|
+
minValue: 0,
|
|
10
|
+
maxValue: 100,
|
|
11
|
+
defaultValue: 0,
|
|
12
|
+
minRotation: -170,
|
|
13
|
+
maxRotation: 170,
|
|
14
|
+
snapIncrement: 1,
|
|
15
|
+
curve: 1,
|
|
16
|
+
disabled: !1,
|
|
17
|
+
borderStyle: "currentState"
|
|
18
|
+
});
|
|
19
|
+
h(this, "currentValue", 0);
|
|
20
|
+
h(this, "currentRotation", 0);
|
|
21
|
+
h(this, "rotationToValue");
|
|
22
|
+
h(this, "valueToRotation");
|
|
23
|
+
h(this, "applySnapping");
|
|
24
|
+
h(this, "dragHandlers");
|
|
25
|
+
h(this, "lastClickTime", 0);
|
|
26
|
+
h(this, "DOUBLE_CLICK_THRESHOLD", 300);
|
|
27
|
+
}
|
|
28
|
+
static mapRange(t, e, n, i, s) {
|
|
29
|
+
return e === t ? n : (s - t) * (i - n) / (e - t) + n;
|
|
30
|
+
}
|
|
31
|
+
static clamp(t, e, n) {
|
|
32
|
+
return Math.min(Math.max(t, e), n);
|
|
33
|
+
}
|
|
34
|
+
// Observed attributes
|
|
35
|
+
static get observedAttributes() {
|
|
36
|
+
return [
|
|
37
|
+
"min-value",
|
|
38
|
+
"max-value",
|
|
39
|
+
"default-value",
|
|
40
|
+
"min-rotation",
|
|
41
|
+
"max-rotation",
|
|
42
|
+
"snap-increment",
|
|
43
|
+
"allowed-values",
|
|
44
|
+
"value",
|
|
45
|
+
"disabled",
|
|
46
|
+
"width",
|
|
47
|
+
"height",
|
|
48
|
+
"border-style",
|
|
49
|
+
"curve",
|
|
50
|
+
"color"
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
connectedCallback() {
|
|
54
|
+
this.injectGlobalStyles(), this.createUtilityFunctions(), this.render(), this.updateColorFromAttribute(), this.setValue(this.config.defaultValue ?? this.config.minValue), this.createDraggable();
|
|
55
|
+
}
|
|
56
|
+
disconnectedCallback() {
|
|
57
|
+
this.cleanup();
|
|
58
|
+
}
|
|
59
|
+
attributeChangedCallback(t, e, n) {
|
|
60
|
+
if (e !== n) {
|
|
61
|
+
if (t === "max-value" || t === "min-value") {
|
|
62
|
+
const i = this.config.minValue, s = this.config.maxValue;
|
|
63
|
+
this.updateConfigFromAttributes(), this.updateBorder();
|
|
64
|
+
let o;
|
|
65
|
+
t === "max-value" ? o = u.mapRange(
|
|
66
|
+
i,
|
|
67
|
+
// old min
|
|
68
|
+
parseFloat(e),
|
|
69
|
+
// old max
|
|
70
|
+
this.config.minValue,
|
|
71
|
+
// new min
|
|
72
|
+
this.config.maxValue,
|
|
73
|
+
// new max
|
|
74
|
+
this.currentValue
|
|
75
|
+
) : o = u.mapRange(
|
|
76
|
+
parseFloat(e),
|
|
77
|
+
// old min
|
|
78
|
+
s,
|
|
79
|
+
// old max
|
|
80
|
+
this.config.minValue,
|
|
81
|
+
// new min
|
|
82
|
+
this.config.maxValue,
|
|
83
|
+
// new max
|
|
84
|
+
this.currentValue
|
|
85
|
+
), this.createUtilityFunctions(), this.setValue(o);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (this.updateConfigFromAttributes(), this.updateBorder(), t === "width" || t === "height" || t === "border-style") return;
|
|
89
|
+
if (t === "color") {
|
|
90
|
+
this.updateColorFromAttribute();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (t === "curve") {
|
|
94
|
+
this.createUtilityFunctions(), this.setValue(this.currentValue);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
injectGlobalStyles() {
|
|
100
|
+
if (u.stylesInjected) return;
|
|
101
|
+
const t = document.createElement("style");
|
|
102
|
+
t.id = "knob-element-styles", t.textContent = `
|
|
103
|
+
knob-element {
|
|
104
|
+
display: block;
|
|
105
|
+
box-sizing: border-box;
|
|
106
|
+
--knob-size: 120px;
|
|
107
|
+
--knob-stroke: rgb(234, 234, 234);
|
|
108
|
+
|
|
109
|
+
width: var(--knob-size, 120px);
|
|
110
|
+
height: var(--knob-size, 120px);
|
|
111
|
+
|
|
112
|
+
touch-action: none; /* Prevents browser touch gestures */
|
|
113
|
+
user-select: none; /* Prevents text selection during drag */
|
|
114
|
+
border-radius: 50%;
|
|
115
|
+
cursor: grab;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
knob-element[disabled] {
|
|
119
|
+
opacity: 0.5;
|
|
120
|
+
pointer-events: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
knob-element:active {
|
|
124
|
+
cursor: grabbing;
|
|
125
|
+
}
|
|
126
|
+
`, document.head.appendChild(t), u.stylesInjected = !0;
|
|
127
|
+
}
|
|
128
|
+
updateConfigFromAttributes() {
|
|
129
|
+
const t = (r, a) => {
|
|
130
|
+
const c = this.getAttribute(r);
|
|
131
|
+
return c !== null ? parseFloat(c) : a;
|
|
132
|
+
}, e = (r, a) => this.getAttribute(r) || a, n = (r) => {
|
|
133
|
+
const a = this.getAttribute(r);
|
|
134
|
+
if (a)
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(a);
|
|
137
|
+
} catch {
|
|
138
|
+
console.warn(`KnobElement: Invalid ${r} JSON:`, a);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}, i = n("allowed-values");
|
|
142
|
+
let s = t("min-value", 0), o = t("max-value", 100);
|
|
143
|
+
if (i && i.length > 0) {
|
|
144
|
+
const r = [...i].sort((f, l) => f - l), a = r[0], c = r[r.length - 1];
|
|
145
|
+
this.hasAttribute("min-value") && s !== a && console.debug(
|
|
146
|
+
`KnobElement: min-value (${s}) doesn't match first allowedValue (${a}). Using ${a}.`
|
|
147
|
+
), this.hasAttribute("max-value") && o !== c && console.debug(
|
|
148
|
+
`KnobElement: max-value (${o}) doesn't match last allowedValue (${c}). Using ${c}.`
|
|
149
|
+
), this.hasAttribute("snap-thresholds") && console.debug(
|
|
150
|
+
"KnobElement: allowedValues overrides snap-increment and snap-thresholds."
|
|
151
|
+
), s = a, o = c;
|
|
152
|
+
}
|
|
153
|
+
this.config = {
|
|
154
|
+
minValue: s,
|
|
155
|
+
maxValue: o,
|
|
156
|
+
defaultValue: t("default-value", 0),
|
|
157
|
+
minRotation: t("min-rotation", -150),
|
|
158
|
+
maxRotation: t("max-rotation", 150),
|
|
159
|
+
snapIncrement: t("snap-increment", 1),
|
|
160
|
+
curve: t("curve", 1),
|
|
161
|
+
borderStyle: e(
|
|
162
|
+
"border-style",
|
|
163
|
+
"currentState"
|
|
164
|
+
),
|
|
165
|
+
allowedValues: i ? [...i].sort((r, a) => r - a) : void 0,
|
|
166
|
+
snapThresholds: n(
|
|
167
|
+
"snap-thresholds"
|
|
168
|
+
),
|
|
169
|
+
disabled: this.hasAttribute("disabled")
|
|
170
|
+
}, this.updateDimensions();
|
|
171
|
+
}
|
|
172
|
+
updateDimensions() {
|
|
173
|
+
const t = this.getAttribute("width"), e = this.getAttribute("height");
|
|
174
|
+
if (t || e) {
|
|
175
|
+
const n = t || e || "120";
|
|
176
|
+
this.style.setProperty("--knob-size", `${n}px`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
updateColorFromAttribute() {
|
|
180
|
+
const t = this.getAttribute("color");
|
|
181
|
+
t && this.style.setProperty("--knob-stroke", t);
|
|
182
|
+
}
|
|
183
|
+
render() {
|
|
184
|
+
this.innerHTML = `
|
|
185
|
+
<svg class="ac-knob" width="100%" height="100%" viewBox="0 0 100 100">
|
|
186
|
+
<path class="knob-path"
|
|
187
|
+
fill="none"
|
|
188
|
+
stroke="var(--knob-stroke)"
|
|
189
|
+
stroke-width="5"
|
|
190
|
+
stroke-linecap="round"
|
|
191
|
+
stroke-linejoin="round"
|
|
192
|
+
d="M50,50 L50,2"
|
|
193
|
+
/>
|
|
194
|
+
</svg>
|
|
195
|
+
`, this.pathElement = this.querySelector(".knob-path");
|
|
196
|
+
}
|
|
197
|
+
cleanup() {
|
|
198
|
+
this.dragHandlers && (this.removeEventListener("mousedown", this.dragHandlers.start), this.removeEventListener("touchstart", this.dragHandlers.start), document.removeEventListener("mousemove", this.dragHandlers.move), document.removeEventListener("mouseup", this.dragHandlers.end), document.removeEventListener("touchmove", this.dragHandlers.move), document.removeEventListener("touchend", this.dragHandlers.end));
|
|
199
|
+
}
|
|
200
|
+
createUtilityFunctions() {
|
|
201
|
+
const t = this.config.curve || 1;
|
|
202
|
+
this.rotationToValue = (e) => {
|
|
203
|
+
const n = u.mapRange(
|
|
204
|
+
this.config.minRotation,
|
|
205
|
+
this.config.maxRotation,
|
|
206
|
+
0,
|
|
207
|
+
1,
|
|
208
|
+
e
|
|
209
|
+
), i = Math.pow(n, t);
|
|
210
|
+
return u.mapRange(
|
|
211
|
+
0,
|
|
212
|
+
1,
|
|
213
|
+
this.config.minValue,
|
|
214
|
+
this.config.maxValue,
|
|
215
|
+
i
|
|
216
|
+
);
|
|
217
|
+
}, this.valueToRotation = (e) => {
|
|
218
|
+
const n = u.mapRange(
|
|
219
|
+
this.config.minValue,
|
|
220
|
+
this.config.maxValue,
|
|
221
|
+
0,
|
|
222
|
+
1,
|
|
223
|
+
e
|
|
224
|
+
), i = Math.pow(n, 1 / t);
|
|
225
|
+
return u.mapRange(
|
|
226
|
+
0,
|
|
227
|
+
1,
|
|
228
|
+
this.config.minRotation,
|
|
229
|
+
this.config.maxRotation,
|
|
230
|
+
i
|
|
231
|
+
);
|
|
232
|
+
}, this.applySnapping = (e) => {
|
|
233
|
+
if (this.config.allowedValues && this.config.allowedValues.length > 0)
|
|
234
|
+
return this.config.allowedValues.reduce((i, s) => Math.abs(s - e) < Math.abs(i - e) ? s : i);
|
|
235
|
+
if (this.config.snapIncrement <= 0) return e;
|
|
236
|
+
let n = this.config.snapIncrement;
|
|
237
|
+
if (this.config.snapThresholds) {
|
|
238
|
+
for (const { maxValue: i, increment: s } of this.config.snapThresholds)
|
|
239
|
+
if (e < i) {
|
|
240
|
+
n = s;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return Math.round(e / n) * n;
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
// TODO(audiolib): Add keyboard controls and slider ARIA semantics in the planned accessibility pass.
|
|
248
|
+
createDraggable() {
|
|
249
|
+
const t = "pointerLockElement" in document && "requestPointerLock" in HTMLElement.prototype;
|
|
250
|
+
let e = !1, n = 0, i = 0, s = 0, o = !1;
|
|
251
|
+
const r = (l) => {
|
|
252
|
+
n = l.clientY, o = document.pointerLockElement === this, !(!t || document.pointerLockElement) && this.requestPointerLock().then(
|
|
253
|
+
() => {
|
|
254
|
+
o = document.pointerLockElement === this;
|
|
255
|
+
},
|
|
256
|
+
() => {
|
|
257
|
+
o = !1;
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
}, a = (l) => {
|
|
261
|
+
if (this.config.disabled) return;
|
|
262
|
+
const g = Date.now(), p = g - this.lastClickTime;
|
|
263
|
+
if (p < this.DOUBLE_CLICK_THRESHOLD && p > 0) {
|
|
264
|
+
this.resetToDefault();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
this.lastClickTime = g, e = !0, i = this.currentRotation, s = 0, "touches" in l ? (n = l.touches[0].clientY, o = !1) : r(l);
|
|
268
|
+
}, c = (l) => {
|
|
269
|
+
if (!e) return;
|
|
270
|
+
let g;
|
|
271
|
+
const p = 2;
|
|
272
|
+
if (o && document.pointerLockElement)
|
|
273
|
+
s += l.movementY, g = -s * p;
|
|
274
|
+
else {
|
|
275
|
+
const x = "touches" in l ? l.touches[0].clientY : l.clientY;
|
|
276
|
+
g = (n - x) * p;
|
|
277
|
+
}
|
|
278
|
+
const V = i + g, E = u.clamp(
|
|
279
|
+
V,
|
|
280
|
+
this.config.minRotation,
|
|
281
|
+
this.config.maxRotation
|
|
282
|
+
), R = this.rotationToValue(E), b = this.applySnapping(R);
|
|
283
|
+
this.currentValue = b, b !== R ? this.currentRotation = this.valueToRotation(b) : this.currentRotation = E, this.updateBorder(), this.dispatchChangeEvent("user"), l.preventDefault();
|
|
284
|
+
}, f = () => {
|
|
285
|
+
e = !1, o && document.pointerLockElement && document.exitPointerLock(), o = !1;
|
|
286
|
+
};
|
|
287
|
+
this.dragHandlers = {
|
|
288
|
+
start: a,
|
|
289
|
+
move: c,
|
|
290
|
+
end: f
|
|
291
|
+
}, this.addEventListener("mousedown", a), this.addEventListener("touchstart", a, { passive: !1 }), document.addEventListener("mousemove", c), document.addEventListener("mouseup", f), document.addEventListener("touchmove", c, { passive: !1 }), document.addEventListener("touchend", f);
|
|
292
|
+
}
|
|
293
|
+
updateBorder() {
|
|
294
|
+
if (!this.pathElement) return;
|
|
295
|
+
if ((this.getAttribute("border-style") || "currentState") === "currentState") {
|
|
296
|
+
const s = (this.config.minRotation - 90) * Math.PI / 180, o = (this.currentRotation - 90) * Math.PI / 180, r = 48 * Math.cos(s) + 50, a = 48 * Math.sin(s) + 50, c = 48 * Math.cos(o) + 50, f = 48 * Math.sin(o) + 50, l = this.currentRotation - this.config.minRotation, g = Math.abs(l) > 180 ? 1 : 0, p = `M50,50 L${r},${a} A48,48,0,${g},1,${c},${f} Z`;
|
|
297
|
+
this.pathElement.setAttribute("d", p);
|
|
298
|
+
} else
|
|
299
|
+
this.pathElement.setAttribute("d", "M50,2 A48,48,0,1,1,49.9,2 Z");
|
|
300
|
+
}
|
|
301
|
+
dispatchChangeEvent(t = "programmatic") {
|
|
302
|
+
const e = u.mapRange(
|
|
303
|
+
this.config.minValue,
|
|
304
|
+
this.config.maxValue,
|
|
305
|
+
0,
|
|
306
|
+
100,
|
|
307
|
+
this.currentValue
|
|
308
|
+
), n = new CustomEvent("knob-change", {
|
|
309
|
+
detail: {
|
|
310
|
+
value: this.currentValue,
|
|
311
|
+
rotation: this.currentRotation,
|
|
312
|
+
percentage: e,
|
|
313
|
+
source: t
|
|
314
|
+
},
|
|
315
|
+
bubbles: !0
|
|
316
|
+
});
|
|
317
|
+
this.dispatchEvent(n);
|
|
318
|
+
}
|
|
319
|
+
// Public API
|
|
320
|
+
setValue(t) {
|
|
321
|
+
!this.valueToRotation || !this.pathElement || (this.currentValue = u.clamp(
|
|
322
|
+
t,
|
|
323
|
+
this.config.minValue,
|
|
324
|
+
this.config.maxValue
|
|
325
|
+
), this.currentRotation = this.valueToRotation(this.currentValue), this.updateBorder(), this.dispatchChangeEvent());
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Sets the knob value using a normalized 0-1 input, automatically handling
|
|
329
|
+
* the knob's range and curve transformation.
|
|
330
|
+
* @param normalizedValue - Value between 0 and 1
|
|
331
|
+
*/
|
|
332
|
+
setValueNormalized(t) {
|
|
333
|
+
const { minRotation: e, maxRotation: n } = this.config, i = Math.max(0, Math.min(1, t)), s = e + i * (n - e), o = this.rotationToValue(s);
|
|
334
|
+
this.setValue(o);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Gets the current knob value as a normalized 0-1 value, accounting for
|
|
338
|
+
* the knob's range and curve. Inverse of setValueNormalized().
|
|
339
|
+
* @returns Normalized value between 0 and 1
|
|
340
|
+
*/
|
|
341
|
+
getValueNormalized() {
|
|
342
|
+
return (this.currentRotation - this.config.minRotation) / (this.config.maxRotation - this.config.minRotation);
|
|
343
|
+
}
|
|
344
|
+
resetToDefault() {
|
|
345
|
+
this.setValue(this.config.defaultValue);
|
|
346
|
+
}
|
|
347
|
+
getValue() {
|
|
348
|
+
return this.currentValue;
|
|
349
|
+
}
|
|
350
|
+
setCurve(t) {
|
|
351
|
+
this.config.curve = t, this.createUtilityFunctions(), this.setValue(this.currentValue);
|
|
352
|
+
}
|
|
353
|
+
getCurve() {
|
|
354
|
+
return this.config.curve || 1;
|
|
355
|
+
}
|
|
356
|
+
setDisabled(t) {
|
|
357
|
+
t ? this.setAttribute("disabled", "") : this.removeAttribute("disabled");
|
|
358
|
+
}
|
|
359
|
+
isDisabled() {
|
|
360
|
+
return this.hasAttribute("disabled");
|
|
361
|
+
}
|
|
362
|
+
getPercentage() {
|
|
363
|
+
return u.mapRange(
|
|
364
|
+
this.config.minValue,
|
|
365
|
+
this.config.maxValue,
|
|
366
|
+
0,
|
|
367
|
+
100,
|
|
368
|
+
this.currentValue
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
// Property getters/setters for easier JS usage
|
|
372
|
+
get value() {
|
|
373
|
+
return this.getValue();
|
|
374
|
+
}
|
|
375
|
+
set value(t) {
|
|
376
|
+
this.setValue(t);
|
|
377
|
+
}
|
|
378
|
+
get disabled() {
|
|
379
|
+
return this.isDisabled();
|
|
380
|
+
}
|
|
381
|
+
set disabled(t) {
|
|
382
|
+
this.setDisabled(t);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
h(u, "stylesInjected", !1);
|
|
386
|
+
let v = u;
|
|
387
|
+
const L = { KNOB: "knob-element" };
|
|
388
|
+
function w(m, d) {
|
|
389
|
+
typeof customElements > "u" || customElements.get(m) || customElements.define(m, d);
|
|
390
|
+
}
|
|
391
|
+
function C() {
|
|
392
|
+
w(L.KNOB, v);
|
|
393
|
+
}
|
|
394
|
+
export {
|
|
395
|
+
v as KnobElement,
|
|
396
|
+
w as defineElement,
|
|
397
|
+
C as registerKnobElement
|
|
398
|
+
};
|