@digitalmeadow/control-panel 1.0.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 +87 -0
- package/dist/control-panel.css +1 -0
- package/dist/control-panel.js +1068 -0
- package/dist/control-panel.umd.cjs +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Digital Meadow
|
|
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,87 @@
|
|
|
1
|
+
# Control Panel
|
|
2
|
+
|
|
3
|
+
GUI control panel for creative coding.
|
|
4
|
+
|
|
5
|
+
## Controllers
|
|
6
|
+
|
|
7
|
+
- Number
|
|
8
|
+
- Select
|
|
9
|
+
- Boolean
|
|
10
|
+
- Button
|
|
11
|
+
- Radio
|
|
12
|
+
- Color
|
|
13
|
+
- Gradient
|
|
14
|
+
- Array
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { GUI } from "./src/gui";
|
|
20
|
+
|
|
21
|
+
const state = { number: 50, color: "#3498db", enabled: true };
|
|
22
|
+
const gui = new GUI();
|
|
23
|
+
|
|
24
|
+
gui.addNumber(state, "number", { min: 0, max: 100, step: 1 });
|
|
25
|
+
gui.addColor(state, "color");
|
|
26
|
+
gui.addBoolean(state, "enabled");
|
|
27
|
+
gui.addButton("Reset", () => gui.reset());
|
|
28
|
+
|
|
29
|
+
gui.saveDefaultPreset();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Folders
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const folder = gui.addFolder("Settings");
|
|
36
|
+
folder.addNumber(state, "value", { min: 0, max: 100 });
|
|
37
|
+
|
|
38
|
+
const nested = folder.addFolder("Advanced");
|
|
39
|
+
nested.addNumber(state, "other", { min: 0, max: 100 });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pnpm dev
|
|
46
|
+
pnpm build
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
gui.addNumber(object, property, options?)
|
|
53
|
+
gui.addSelect(object, property, options?)
|
|
54
|
+
gui.addBoolean(object, property, options?)
|
|
55
|
+
gui.addButton(label, fn, options?)
|
|
56
|
+
gui.addRadio(object, property, options?)
|
|
57
|
+
gui.addColor(object, property, options?)
|
|
58
|
+
gui.addGradient(object, property, options?)
|
|
59
|
+
gui.addArray(object, property, options?)
|
|
60
|
+
gui.addFolder(title)
|
|
61
|
+
|
|
62
|
+
gui.save()
|
|
63
|
+
gui.load(state)
|
|
64
|
+
gui.reset()
|
|
65
|
+
gui.saveDefaultPreset()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Options
|
|
69
|
+
|
|
70
|
+
All controllers:
|
|
71
|
+
- `label?: string`
|
|
72
|
+
- `disabled?: boolean`
|
|
73
|
+
- `id?: string`
|
|
74
|
+
|
|
75
|
+
Number:
|
|
76
|
+
- `min?: number`
|
|
77
|
+
- `max?: number`
|
|
78
|
+
- `step?: number`
|
|
79
|
+
|
|
80
|
+
Select/Radio:
|
|
81
|
+
- `options?: T[]`
|
|
82
|
+
|
|
83
|
+
Gradient:
|
|
84
|
+
- `stops?: ColorStop[]`
|
|
85
|
+
|
|
86
|
+
Array:
|
|
87
|
+
- `itemType?: "color" | "number" | "string"`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.cp-root{position:fixed;top:10px;right:10px;width:280px;max-height:90vh;overflow:auto;background:transparent;color:#fff;mix-blend-mode:exclusion;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:10px;line-height:1.2;padding:8px;z-index:100}.cp-root::-webkit-scrollbar{width:1px}.cp-root::-webkit-scrollbar-track{background:transparent}.cp-root::-webkit-scrollbar-thumb{background:#ffffff80}.cp-summary{cursor:pointer;-webkit-user-select:none;user-select:none;font-weight:700;outline:none}.cp-summary-root{position:sticky;top:0}.cp-stats{float:right;font-weight:400;opacity:.6;font-size:.9em;font-variant-numeric:tabular-nums}.cp-content{margin-top:4px;display:flex;flex-direction:column;gap:6px}.cp-folder{width:100%}.cp-folder-content{margin:0 0 6px;padding:4px 0 0 9px}.cp-controller{display:flex}.cp-label{margin:auto 0;width:50%;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;-webkit-user-select:none;user-select:none;padding-right:8px;opacity:.8}.cp-input-number{width:50%;background:transparent;border:1px solid #fff;color:inherit;padding:2px 4px;border-radius:2px;font-family:inherit;font-size:inherit}.cp-input-number:focus{outline:none;border-color:#fff;background:transparent}.cp-select{width:50%;background:#ffffff4d;border:none;padding:2px 4px;border-radius:2px;font-family:inherit;font-size:inherit}.cp-checkbox{margin:auto 0}.cp-button{width:100%;background:#ffffff4d;border:none;padding:4px 2px;border-radius:2px;cursor:pointer;text-align:center;font-family:inherit;font-size:inherit;transition:background .1s}.cp-button:hover{background:#fff6}.cp-button:active{background:#fff3;transform:translateY(1px)}.cp-controller[data-disabled=true]{opacity:.5;pointer-events:none;cursor:not-allowed}.cp-controller-details{width:50%}.cp-controller-summary{cursor:pointer;outline:none}.cp-controller-summary-content{display:inline-flex;align-items:center;gap:6px;width:calc(100% - 16px);vertical-align:middle}.cp-input-range{-webkit-appearance:none;flex:1;min-width:0;height:2px;background:#ffffff4d;margin:0;vertical-align:middle;cursor:grab}.cp-input-range::-webkit-slider-thumb{-webkit-appearance:none;width:4px;height:8px;border-radius:1px;background:#fff;cursor:grab}.cp-input-range::-moz-range-thumb{width:4px;height:16px;background:#fff;cursor:grab}.cp-input-range:active{cursor:grabbing}.cp-value-display{min-width:24px;text-align:right;font-variant-numeric:tabular-nums;font-size:.9em;opacity:.8;-webkit-user-select:none;user-select:none}.cp-number-settings{margin-top:4px;background:transparent;border:1px solid rgba(255,255,255,.2);padding:4px;border-radius:4px;display:flex;flex-direction:column;gap:4px}.cp-separator{border:none;border-top:1px solid rgba(255,255,255,.3);margin:4px 0;width:100%}.cp-setting-row{display:flex;align-items:center;gap:4px}.cp-setting-label{width:50%;font-size:.9em;opacity:.7}.cp-input-small{width:50%}.cp-radios{width:50%;display:flex;gap:2px}.cp-radio{flex:1;font-size:.9em;padding:4px 2px;height:100%}.cp-radio[data-active=true]{background:#fff3;border-color:#fff;font-weight:700}.cp-button-delete{width:24px;padding:0;display:flex;align-items:center;justify-content:center;line-height:1}.cp-input-color{padding:0;margin:0;border:none;background:none;outline:none;isolation:isolate;mix-blend-mode:normal;cursor:pointer}.cp-color-swatch{width:10px;height:10px;margin-right:8px}.cp-stops-container{display:flex;flex-direction:column;gap:4px}
|
|
@@ -0,0 +1,1068 @@
|
|
|
1
|
+
function r(n, t = {}, e = []) {
|
|
2
|
+
const i = document.createElement(n);
|
|
3
|
+
for (const [s, a] of Object.entries(t))
|
|
4
|
+
s === "className" ? i.className = String(a) : s === "style" && typeof a == "object" ? Object.assign(i.style, a) : s === "open" && typeof a == "boolean" ? a ? i.setAttribute("open", "") : i.removeAttribute("open") : typeof a != "object" && i.setAttribute(s, String(a));
|
|
5
|
+
for (const s of e)
|
|
6
|
+
typeof s == "string" ? i.appendChild(document.createTextNode(s)) : i.appendChild(s);
|
|
7
|
+
return i;
|
|
8
|
+
}
|
|
9
|
+
function B(n) {
|
|
10
|
+
const t = r(
|
|
11
|
+
"button",
|
|
12
|
+
{
|
|
13
|
+
className: "cp-button cp-button-delete"
|
|
14
|
+
},
|
|
15
|
+
["×"]
|
|
16
|
+
);
|
|
17
|
+
return t.addEventListener("click", n), t;
|
|
18
|
+
}
|
|
19
|
+
function R(n) {
|
|
20
|
+
return n.replace(/([A-Z])/g, " $1").replace(/^./, (t) => t.toUpperCase()).trim();
|
|
21
|
+
}
|
|
22
|
+
function D(n, t, e) {
|
|
23
|
+
return Math.min(Math.max(n, t), e);
|
|
24
|
+
}
|
|
25
|
+
function j(n, t, e) {
|
|
26
|
+
if (t.length !== e.length)
|
|
27
|
+
throw new Error("Input and output ranges must have the same length");
|
|
28
|
+
if (t.length < 2)
|
|
29
|
+
throw new Error("Input and output ranges must have at least two values");
|
|
30
|
+
let i = 0;
|
|
31
|
+
for (; i < t.length - 1 && n > t[i + 1]; )
|
|
32
|
+
i++;
|
|
33
|
+
if (i === t.length - 1)
|
|
34
|
+
return e[e.length - 1];
|
|
35
|
+
if (i === 0 && n < t[0])
|
|
36
|
+
return e[0];
|
|
37
|
+
const s = t[i], a = t[i + 1], o = e[i], l = e[i + 1];
|
|
38
|
+
return (n - s) / (a - s) * (l - o) + o;
|
|
39
|
+
}
|
|
40
|
+
class z {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.source = null, this.stream = null, this.fftSize = 2048, this.smoothingTimeConstant = 0.92, this.spectrumBoost = 2, this.levels = {
|
|
43
|
+
bass: 0,
|
|
44
|
+
mids: 0,
|
|
45
|
+
highs: 0,
|
|
46
|
+
volume: 0
|
|
47
|
+
}, this.peaks = {
|
|
48
|
+
bass: 0,
|
|
49
|
+
mids: 0,
|
|
50
|
+
highs: 0,
|
|
51
|
+
volume: 0
|
|
52
|
+
}, this._isAnalyzing = !1, this.loop = () => {
|
|
53
|
+
this._isAnalyzing && (requestAnimationFrame(this.loop), this.update());
|
|
54
|
+
};
|
|
55
|
+
const t = window.AudioContext || window.webkitAudioContext;
|
|
56
|
+
this.ctx = new t(), this.analyser = this.ctx.createAnalyser(), this.analyser.fftSize = this.fftSize, this.analyser.smoothingTimeConstant = this.smoothingTimeConstant, this.dataArray = new Uint8Array(this.analyser.frequencyBinCount), this.waveformArray = new Uint8Array(this.analyser.frequencyBinCount);
|
|
57
|
+
}
|
|
58
|
+
setFFTSize(t) {
|
|
59
|
+
this.fftSize = t, this.analyser.fftSize = t, this.dataArray = new Uint8Array(this.analyser.frequencyBinCount), this.waveformArray = new Uint8Array(this.analyser.frequencyBinCount);
|
|
60
|
+
}
|
|
61
|
+
async setInput(t) {
|
|
62
|
+
try {
|
|
63
|
+
let e;
|
|
64
|
+
t === "browser" ? e = navigator.mediaDevices.getDisplayMedia({
|
|
65
|
+
audio: !0,
|
|
66
|
+
video: !0
|
|
67
|
+
}) : e = navigator.mediaDevices.getUserMedia({
|
|
68
|
+
audio: !0
|
|
69
|
+
});
|
|
70
|
+
const i = await e;
|
|
71
|
+
this.ctx.state === "suspended" && this.ctx.resume(), this.source && this.source.disconnect(), this.stream && this.stream.getTracks().forEach((s) => s.stop()), this.stream = i, this.source = this.ctx.createMediaStreamSource(this.stream), this.source.connect(this.analyser), this._isAnalyzing = !0, this.loop();
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error("Error accessing audio input:", e), this._isAnalyzing = !1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
update() {
|
|
77
|
+
if (this.analyser.getByteFrequencyData(this.dataArray), this.analyser.getByteTimeDomainData(this.waveformArray), this.spectrumBoost !== 1) {
|
|
78
|
+
const d = this.dataArray.length;
|
|
79
|
+
for (let h = 0; h < d; h++) {
|
|
80
|
+
const p = 1 + h / d * (this.spectrumBoost - 1);
|
|
81
|
+
this.dataArray[h] = Math.min(255, this.dataArray[h] * p);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const t = [2, 10], e = [10, 150], i = [150, 600], s = this.getAverage(t[0], t[1]), a = this.getAverage(e[0], e[1]), o = this.getAverage(i[0], i[1]), l = this.getAverage(0, i[1]);
|
|
85
|
+
this.processLevel("bass", s), this.processLevel("mids", a), this.processLevel("highs", o), this.processLevel("volume", l);
|
|
86
|
+
}
|
|
87
|
+
processLevel(t, e) {
|
|
88
|
+
this.peaks[t] -= 5e-4, this.peaks[t] = D(this.peaks[t], 0.1, 1), e > this.peaks[t] && (this.peaks[t] = e), this.levels[t] = D(
|
|
89
|
+
j(e, [0, this.peaks[t]], [0, 1]),
|
|
90
|
+
0,
|
|
91
|
+
1
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
getAverage(t, e) {
|
|
95
|
+
let i = 0;
|
|
96
|
+
const s = e - t;
|
|
97
|
+
if (s <= 0) return 0;
|
|
98
|
+
for (let a = t; a < e; a++)
|
|
99
|
+
i += this.dataArray[a];
|
|
100
|
+
return i / s / 255;
|
|
101
|
+
}
|
|
102
|
+
getSignal(t) {
|
|
103
|
+
return () => this.levels[t];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const x = new z();
|
|
107
|
+
class $ {
|
|
108
|
+
constructor() {
|
|
109
|
+
this.midiAccess = null, this.values = /* @__PURE__ */ new Map(), this.isListening = !1, this.resolveListen = null, this.listeningCallback = null, this.init();
|
|
110
|
+
}
|
|
111
|
+
async init() {
|
|
112
|
+
if (typeof navigator < "u" && navigator.requestMIDIAccess)
|
|
113
|
+
try {
|
|
114
|
+
this.midiAccess = await navigator.requestMIDIAccess(), this.setupInputs(), this.midiAccess.onstatechange = (t) => {
|
|
115
|
+
t.port.type === "input" && t.port.state === "connected" && this.setupInputs();
|
|
116
|
+
};
|
|
117
|
+
} catch (t) {
|
|
118
|
+
console.warn("MIDI Access failed", t);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
setupInputs() {
|
|
122
|
+
if (!this.midiAccess) return;
|
|
123
|
+
const t = this.midiAccess.inputs.values();
|
|
124
|
+
for (const e of t)
|
|
125
|
+
e.onmidimessage = this.handleMessage.bind(this);
|
|
126
|
+
}
|
|
127
|
+
handleMessage(t) {
|
|
128
|
+
const e = t.data, [i] = e;
|
|
129
|
+
if ((i & 240) >= 240) return;
|
|
130
|
+
const a = this.getIdFromMessage(t), o = this.normalizeValue(e);
|
|
131
|
+
this.values.set(a, o), this.isListening && this.resolveListen && o > 0 && (this.resolveListen(a), this.isListening = !1, this.resolveListen = null, this.listeningCallback && this.listeningCallback());
|
|
132
|
+
}
|
|
133
|
+
getIdFromMessage(t) {
|
|
134
|
+
const e = t.data, [i, s] = e, a = i & 240, o = t.currentTarget.name || "unknown", l = a === 144 || a === 128 ? "note" : "ctrl", d = o.replace(/[^a-zA-Z0-9]/g, "");
|
|
135
|
+
return `${s}_${l}_${d}`;
|
|
136
|
+
}
|
|
137
|
+
normalizeValue(t) {
|
|
138
|
+
const [e, i, s] = t, a = e & 240;
|
|
139
|
+
return a === 144 ? s > 0 ? 1 : 0 : a === 128 ? 0 : a === 176 ? s / 127 : 0;
|
|
140
|
+
}
|
|
141
|
+
listen() {
|
|
142
|
+
return this.isListening = !0, new Promise((t) => {
|
|
143
|
+
this.resolveListen = t;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
cancelListen() {
|
|
147
|
+
this.isListening = !1, this.resolveListen = null;
|
|
148
|
+
}
|
|
149
|
+
getSignal(t) {
|
|
150
|
+
return () => this.values.get(t) ?? 0;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const U = new $(), M = class M {
|
|
154
|
+
constructor(t, e, i = {}) {
|
|
155
|
+
this.changeFns = /* @__PURE__ */ new Set(), this.object = t, this.property = e, this.key = i.id ?? e, this.initialValue = this.object[this.property], this.domElement = r("div", { className: "cp-controller" });
|
|
156
|
+
const s = i.label ?? R(e), a = r("label", { className: "cp-label" }, [
|
|
157
|
+
String(s)
|
|
158
|
+
]);
|
|
159
|
+
a.setAttribute("title", String(s)), this.domElement.appendChild(a), i.disabled && this.domElement.setAttribute("data-disabled", "true");
|
|
160
|
+
}
|
|
161
|
+
get value() {
|
|
162
|
+
return this.object[this.property];
|
|
163
|
+
}
|
|
164
|
+
setValue(t, e = !0) {
|
|
165
|
+
this.object[this.property] = t, e && this.emitChange(t), this.updateDisplay();
|
|
166
|
+
}
|
|
167
|
+
save() {
|
|
168
|
+
return this.value;
|
|
169
|
+
}
|
|
170
|
+
load(t) {
|
|
171
|
+
this.setValue(t);
|
|
172
|
+
}
|
|
173
|
+
reset() {
|
|
174
|
+
this.setValue(this.initialValue);
|
|
175
|
+
}
|
|
176
|
+
onChange(t) {
|
|
177
|
+
return this.changeFns.add(t), this;
|
|
178
|
+
}
|
|
179
|
+
emitChange(t) {
|
|
180
|
+
for (const e of this.changeFns)
|
|
181
|
+
e(t);
|
|
182
|
+
}
|
|
183
|
+
appendWidget(t) {
|
|
184
|
+
this.domElement.appendChild(t);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
M.audio = x, M.midi = U;
|
|
188
|
+
let m = M;
|
|
189
|
+
const O = {
|
|
190
|
+
linear: (n) => n,
|
|
191
|
+
quadIn: (n) => n * n,
|
|
192
|
+
quadOut: (n) => n * (2 - n),
|
|
193
|
+
quadInOut: (n) => n < 0.5 ? 2 * n * n : -1 + (4 - 2 * n) * n,
|
|
194
|
+
cubicIn: (n) => n * n * n,
|
|
195
|
+
cubicOut: (n) => --n * n * n + 1,
|
|
196
|
+
cubicInOut: (n) => n < 0.5 ? 4 * n * n * n : (n - 1) * (2 * n - 2) * (2 * n - 2) + 1,
|
|
197
|
+
expoIn: (n) => n === 0 ? 0 : Math.pow(2, 10 * (n - 1)),
|
|
198
|
+
expoOut: (n) => n === 1 ? 1 : -Math.pow(2, -10 * n) + 1,
|
|
199
|
+
expoInOut: (n) => n === 0 || n === 1 ? n : (n *= 2) < 1 ? 0.5 * Math.pow(2, 10 * (n - 1)) : 0.5 * (-Math.pow(2, -10 * --n) + 2),
|
|
200
|
+
sineIn: (n) => 1 - Math.cos(n * Math.PI / 2),
|
|
201
|
+
sineOut: (n) => Math.sin(n * Math.PI / 2),
|
|
202
|
+
sineInOut: (n) => -(Math.cos(Math.PI * n) - 1) / 2
|
|
203
|
+
};
|
|
204
|
+
class k {
|
|
205
|
+
constructor(t) {
|
|
206
|
+
this.rafId = null, this.currentSignalType = null, this.currentMidiId = null, this.currentEase = "linear", this.currentBehaviour = "forward", this.loop = () => {
|
|
207
|
+
if (this.currentSignalType) {
|
|
208
|
+
let e = 0;
|
|
209
|
+
this.currentSignalType === "midi" ? this.currentMidiId && (e = m.midi.getSignal(this.currentMidiId)()) : e = m.audio.getSignal(this.currentSignalType)();
|
|
210
|
+
const i = O[this.currentEase](e);
|
|
211
|
+
this.onChange(i, this.currentBehaviour), this.rafId = requestAnimationFrame(this.loop);
|
|
212
|
+
}
|
|
213
|
+
}, this.onChange = t.onChange, this.setupControllers(t.container);
|
|
214
|
+
}
|
|
215
|
+
setupControllers(t) {
|
|
216
|
+
const e = this.createSettingSelect(
|
|
217
|
+
"signal",
|
|
218
|
+
["none", "bass", "mids", "highs", "volume", "midi"],
|
|
219
|
+
(o) => this.setSignalType(o)
|
|
220
|
+
);
|
|
221
|
+
this.signalSelect = e.select, t.appendChild(e.row), this.midiRow = r("div", {
|
|
222
|
+
className: "cp-setting-row",
|
|
223
|
+
style: "display: none;"
|
|
224
|
+
});
|
|
225
|
+
const i = r(
|
|
226
|
+
"label",
|
|
227
|
+
{ className: "cp-setting-label" },
|
|
228
|
+
["Midi"]
|
|
229
|
+
);
|
|
230
|
+
this.midiBtn = r(
|
|
231
|
+
"button",
|
|
232
|
+
{ className: "cp-button cp-input-small" },
|
|
233
|
+
["Learn"]
|
|
234
|
+
), this.midiBtn.addEventListener("click", async () => {
|
|
235
|
+
if (this.midiBtn.textContent === "Listening...") {
|
|
236
|
+
m.midi.cancelListen(), this.setMidiId(null);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
this.midiBtn.textContent = "Listening...";
|
|
240
|
+
const o = await m.midi.listen();
|
|
241
|
+
this.setMidiId(o);
|
|
242
|
+
}), this.midiRow.appendChild(i), this.midiRow.appendChild(this.midiBtn), t.appendChild(this.midiRow);
|
|
243
|
+
const s = this.createSettingSelect(
|
|
244
|
+
"behaviour",
|
|
245
|
+
["forward", "backward", "loopForward", "loopBackward", "pingpong"],
|
|
246
|
+
(o) => this.setBehaviour(o)
|
|
247
|
+
);
|
|
248
|
+
this.behaviourRow = s.row, this.behaviourSelect = s.select, this.behaviourRow.style.display = "none", this.behaviourSelect.value = this.currentBehaviour, t.appendChild(this.behaviourRow);
|
|
249
|
+
const a = this.createSettingSelect(
|
|
250
|
+
"ease",
|
|
251
|
+
Object.keys(O),
|
|
252
|
+
(o) => this.setEase(o)
|
|
253
|
+
);
|
|
254
|
+
this.easeRow = a.row, this.easeSelect = a.select, this.easeRow.style.display = "none", this.easeSelect.value = this.currentEase, t.appendChild(this.easeRow);
|
|
255
|
+
}
|
|
256
|
+
createSettingSelect(t, e, i) {
|
|
257
|
+
const s = r("div", { className: "cp-setting-row" }), a = r("label", { className: "cp-setting-label" }, [
|
|
258
|
+
t
|
|
259
|
+
]), o = r("select", {
|
|
260
|
+
className: "cp-select cp-input-small"
|
|
261
|
+
});
|
|
262
|
+
return e.forEach((l) => {
|
|
263
|
+
const d = r("option", { value: l }, [l]);
|
|
264
|
+
o.appendChild(d);
|
|
265
|
+
}), o.addEventListener("change", () => i(o.value)), s.appendChild(a), s.appendChild(o), { row: s, select: o };
|
|
266
|
+
}
|
|
267
|
+
setSignalType(t) {
|
|
268
|
+
if (!t || t === "none")
|
|
269
|
+
this.currentSignalType = null, this.currentMidiId = null, this.midiRow.style.display = "none", this.easeRow.style.display = "none", this.behaviourRow.style.display = "none", this.stop(), this.signalSelect.value !== "none" && (this.signalSelect.value = "none");
|
|
270
|
+
else {
|
|
271
|
+
this.currentSignalType = t;
|
|
272
|
+
const e = t === "midi";
|
|
273
|
+
this.midiRow.style.display = e ? "flex" : "none", this.easeRow.style.display = "flex", this.behaviourRow.style.display = "flex", e || (this.currentMidiId = null, m.audio.ctx.state === "suspended" && m.audio.setInput("microphone")), this.start(), this.signalSelect.value !== t && (this.signalSelect.value = t);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
setMidiId(t) {
|
|
277
|
+
this.currentMidiId = t, this.midiBtn.textContent = t ?? "Learn";
|
|
278
|
+
}
|
|
279
|
+
setEase(t) {
|
|
280
|
+
this.currentEase = t, this.easeSelect.value !== t && (this.easeSelect.value = t);
|
|
281
|
+
}
|
|
282
|
+
setBehaviour(t) {
|
|
283
|
+
this.currentBehaviour = t, this.behaviourSelect.value !== t && (this.behaviourSelect.value = t);
|
|
284
|
+
}
|
|
285
|
+
start() {
|
|
286
|
+
!this.rafId && this.currentSignalType && this.loop();
|
|
287
|
+
}
|
|
288
|
+
stop() {
|
|
289
|
+
this.rafId && (cancelAnimationFrame(this.rafId), this.rafId = null);
|
|
290
|
+
}
|
|
291
|
+
save() {
|
|
292
|
+
return {
|
|
293
|
+
type: this.currentSignalType,
|
|
294
|
+
midiId: this.currentMidiId,
|
|
295
|
+
ease: this.currentEase,
|
|
296
|
+
behaviour: this.currentBehaviour
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
load(t) {
|
|
300
|
+
t && (this.setSignalType(t.type), this.setMidiId(t.midiId || null), this.setEase(t.ease || "linear"), this.setBehaviour(t.behaviour || "forward"));
|
|
301
|
+
}
|
|
302
|
+
reset() {
|
|
303
|
+
this.setSignalType("none"), this.setEase("linear"), this.setBehaviour("forward"), this.setMidiId(null);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
class q extends m {
|
|
307
|
+
constructor(t, e, i = {}) {
|
|
308
|
+
super(t, e, i), this.pingPongDirection = 1, this.min = 0, this.max = 100, this.initialOptions = i, this.min = i.min ?? 0, this.max = i.max ?? 100;
|
|
309
|
+
const s = r("details", {
|
|
310
|
+
className: "cp-controller-details"
|
|
311
|
+
}), a = r("summary", {
|
|
312
|
+
className: "cp-controller-summary"
|
|
313
|
+
});
|
|
314
|
+
this.input = r("input", {
|
|
315
|
+
type: "range",
|
|
316
|
+
className: "cp-input-range",
|
|
317
|
+
step: i.step ?? "any"
|
|
318
|
+
}), i.min !== void 0 && (this.input.min = String(i.min)), i.max !== void 0 && (this.input.max = String(i.max)), this.input.value = String(this.value), this.display = r(
|
|
319
|
+
"span",
|
|
320
|
+
{
|
|
321
|
+
className: "cp-value-display"
|
|
322
|
+
},
|
|
323
|
+
[String(this.value.toFixed(1))]
|
|
324
|
+
), this.input.addEventListener("input", () => {
|
|
325
|
+
const p = parseFloat(this.input.value);
|
|
326
|
+
isNaN(p) || (this.setValue(p), this.display.textContent = String(p.toFixed(1)));
|
|
327
|
+
}), this.input.addEventListener("click", (p) => {
|
|
328
|
+
p.stopPropagation();
|
|
329
|
+
});
|
|
330
|
+
const o = r("div", {
|
|
331
|
+
className: "cp-controller-summary-content"
|
|
332
|
+
});
|
|
333
|
+
o.appendChild(this.input), o.appendChild(this.display), a.appendChild(o), s.appendChild(a);
|
|
334
|
+
const l = r("div", { className: "cp-number-settings" }), d = this.createSetting(
|
|
335
|
+
"min",
|
|
336
|
+
i.min,
|
|
337
|
+
(p) => this.setMin(p)
|
|
338
|
+
);
|
|
339
|
+
this.minInput = d.input, l.appendChild(d.row);
|
|
340
|
+
const h = this.createSetting(
|
|
341
|
+
"max",
|
|
342
|
+
i.max,
|
|
343
|
+
(p) => this.setMax(p)
|
|
344
|
+
);
|
|
345
|
+
this.maxInput = h.input, l.appendChild(h.row);
|
|
346
|
+
const y = this.createSetting(
|
|
347
|
+
"step",
|
|
348
|
+
i.step,
|
|
349
|
+
(p) => this.setStep(p)
|
|
350
|
+
);
|
|
351
|
+
this.stepInput = y.input, l.appendChild(y.row), this.signalHandler = new k({
|
|
352
|
+
container: l,
|
|
353
|
+
onChange: (p, c) => this.applySignal(p, c)
|
|
354
|
+
}), s.appendChild(l), this.appendWidget(s);
|
|
355
|
+
}
|
|
356
|
+
// Setters
|
|
357
|
+
setMin(t) {
|
|
358
|
+
typeof t == "number" && (t = String(t)), t === "" || isNaN(parseFloat(t)) ? this.input.removeAttribute("min") : (this.input.min = t, this.min = parseFloat(t)), this.minInput && this.minInput.value !== t && (this.minInput.value = t);
|
|
359
|
+
}
|
|
360
|
+
setMax(t) {
|
|
361
|
+
typeof t == "number" && (t = String(t)), t === "" || isNaN(parseFloat(t)) ? this.input.removeAttribute("max") : (this.input.max = t, this.max = parseFloat(t)), this.maxInput && this.maxInput.value !== t && (this.maxInput.value = t);
|
|
362
|
+
}
|
|
363
|
+
setStep(t) {
|
|
364
|
+
t === void 0 && (t = ""), typeof t == "number" && (t = String(t)), t === "" || t === "any" || isNaN(parseFloat(t)) ? this.input.step = "any" : this.input.step = t, this.stepInput && (t === "any" || t === "" ? this.stepInput.value = "" : this.stepInput.value !== t && (this.stepInput.value = t));
|
|
365
|
+
}
|
|
366
|
+
applySignal(t, e) {
|
|
367
|
+
const i = this.max - this.min;
|
|
368
|
+
let s;
|
|
369
|
+
if (e === "forward")
|
|
370
|
+
s = this.min + t * i;
|
|
371
|
+
else if (e === "backward")
|
|
372
|
+
s = this.max - t * i;
|
|
373
|
+
else {
|
|
374
|
+
const a = t * (i * 0.01);
|
|
375
|
+
s = this.value, e === "loopForward" ? (s += a, s > this.max && (s = this.min + (s - this.min) % i)) : e === "loopBackward" ? (s -= a, s < this.min && (s = this.max - (this.max - s) % i)) : e === "pingpong" && (s += a * this.pingPongDirection, s >= this.max ? (s = this.max, this.pingPongDirection = -1) : s <= this.min && (s = this.min, this.pingPongDirection = 1));
|
|
376
|
+
}
|
|
377
|
+
s = this.roundToStep(s), this.setValue(s), this.input.value = String(s), this.display.textContent = String(s.toFixed(1));
|
|
378
|
+
}
|
|
379
|
+
roundToStep(t) {
|
|
380
|
+
const e = this.input.step;
|
|
381
|
+
if (e === "any" || e === "" || isNaN(parseFloat(e)))
|
|
382
|
+
return t;
|
|
383
|
+
const i = parseFloat(e), s = this.min;
|
|
384
|
+
return s + Math.round((t - s) / i) * i;
|
|
385
|
+
}
|
|
386
|
+
createSetting(t, e, i) {
|
|
387
|
+
const s = r("div", { className: "cp-setting-row" }), a = r("label", { className: "cp-setting-label" }, [
|
|
388
|
+
t
|
|
389
|
+
]), o = r("input", {
|
|
390
|
+
type: "number",
|
|
391
|
+
className: "cp-input-number cp-input-small",
|
|
392
|
+
step: "any"
|
|
393
|
+
});
|
|
394
|
+
return e !== void 0 && (o.value = String(e)), o.addEventListener("input", () => i(o.value)), s.appendChild(a), s.appendChild(o), { row: s, input: o };
|
|
395
|
+
}
|
|
396
|
+
updateDisplay() {
|
|
397
|
+
this.input.value = String(this.value), this.display.textContent = String(this.value.toFixed(1));
|
|
398
|
+
}
|
|
399
|
+
save() {
|
|
400
|
+
return {
|
|
401
|
+
value: this.value,
|
|
402
|
+
settings: {
|
|
403
|
+
min: this.min,
|
|
404
|
+
max: this.max,
|
|
405
|
+
step: this.input.step,
|
|
406
|
+
signal: this.signalHandler.save()
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
load(t) {
|
|
411
|
+
if (typeof t == "number")
|
|
412
|
+
this.setValue(t), this.resetSettings();
|
|
413
|
+
else if (typeof t == "object" && t !== null && "value" in t) {
|
|
414
|
+
const e = t.settings || {};
|
|
415
|
+
e.min !== void 0 ? this.setMin(e.min) : this.setMin(
|
|
416
|
+
this.initialOptions.min !== void 0 ? this.initialOptions.min : ""
|
|
417
|
+
), e.max !== void 0 ? this.setMax(e.max) : this.setMax(
|
|
418
|
+
this.initialOptions.max !== void 0 ? this.initialOptions.max : ""
|
|
419
|
+
), e.step !== void 0 ? this.setStep(e.step) : this.setStep(this.initialOptions.step);
|
|
420
|
+
let i = t.value;
|
|
421
|
+
!isNaN(this.min) && i < this.min && (i = this.min), !isNaN(this.max) && i > this.max && (i = this.max), this.setValue(i), this.signalHandler?.load(e.signal);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
reset() {
|
|
425
|
+
this.setValue(this.initialValue), this.resetSettings();
|
|
426
|
+
}
|
|
427
|
+
resetSettings() {
|
|
428
|
+
this.setMin(
|
|
429
|
+
this.initialOptions.min !== void 0 ? this.initialOptions.min : ""
|
|
430
|
+
), this.setMax(
|
|
431
|
+
this.initialOptions.max !== void 0 ? this.initialOptions.max : ""
|
|
432
|
+
), this.setStep(this.initialOptions.step), this.signalHandler?.reset();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
class H extends m {
|
|
436
|
+
constructor(t, e, i) {
|
|
437
|
+
super(t, e, i), this.optionValues = [], this.select = r("select", { className: "cp-select" }), this.optionValues = i.options || [], this.optionValues.forEach((s, a) => {
|
|
438
|
+
const o = r("option", { value: String(a) }, [
|
|
439
|
+
String(s)
|
|
440
|
+
]);
|
|
441
|
+
this.select.appendChild(o);
|
|
442
|
+
}), this.updateDisplay(), this.select.addEventListener("change", () => {
|
|
443
|
+
const s = parseInt(this.select.value), a = this.optionValues[s];
|
|
444
|
+
this.setValue(a);
|
|
445
|
+
}), this.appendWidget(this.select);
|
|
446
|
+
}
|
|
447
|
+
setOptions(t) {
|
|
448
|
+
this.select.innerHTML = "", this.optionValues = t, this.optionValues.forEach((e, i) => {
|
|
449
|
+
const s = r("option", { value: String(i) }, [
|
|
450
|
+
String(e)
|
|
451
|
+
]);
|
|
452
|
+
this.select.appendChild(s);
|
|
453
|
+
}), this.updateDisplay(), this.select.value === "" && this.optionValues.length > 0 && this.setValue(this.optionValues[0]);
|
|
454
|
+
}
|
|
455
|
+
updateDisplay() {
|
|
456
|
+
const t = this.optionValues.indexOf(this.value);
|
|
457
|
+
t !== -1 && (this.select.value = String(t));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
class _ extends m {
|
|
461
|
+
constructor(t, e, i = {}) {
|
|
462
|
+
const s = { action: e };
|
|
463
|
+
super(s, "action", i);
|
|
464
|
+
const a = i.label ?? t;
|
|
465
|
+
this.button = r("button", { className: "cp-button" }, [
|
|
466
|
+
String(a)
|
|
467
|
+
]), this.button.addEventListener("click", () => {
|
|
468
|
+
const o = this.value;
|
|
469
|
+
typeof o == "function" && o(), this.emitChange(o);
|
|
470
|
+
}), this.appendWidget(this.button);
|
|
471
|
+
}
|
|
472
|
+
updateDisplay() {
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
class W extends m {
|
|
476
|
+
constructor(t, e, i = {}) {
|
|
477
|
+
super(t, e, i), this.input = r("input", {
|
|
478
|
+
type: "checkbox",
|
|
479
|
+
className: "cp-checkbox"
|
|
480
|
+
}), this.input.checked = this.value, this.input.addEventListener("change", () => {
|
|
481
|
+
this.setValue(this.input.checked);
|
|
482
|
+
}), this.appendWidget(this.input);
|
|
483
|
+
}
|
|
484
|
+
updateDisplay() {
|
|
485
|
+
this.input.checked = this.value;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
class G extends m {
|
|
489
|
+
constructor(t, e, i) {
|
|
490
|
+
super(t, e, i), this.buttons = [], this.optionValues = [], this.container = r("div", { className: "cp-radios" }), this.optionValues = i.options || [], this.optionValues.forEach((s) => {
|
|
491
|
+
const a = r("button", { className: "cp-button cp-radio" }, [
|
|
492
|
+
String(s)
|
|
493
|
+
]);
|
|
494
|
+
a.addEventListener("click", () => {
|
|
495
|
+
this.setValue(s);
|
|
496
|
+
}), this.container.appendChild(a), this.buttons.push(a);
|
|
497
|
+
}), this.updateDisplay(), this.appendWidget(this.container);
|
|
498
|
+
}
|
|
499
|
+
updateDisplay() {
|
|
500
|
+
const t = this.value;
|
|
501
|
+
this.buttons.forEach((e, i) => {
|
|
502
|
+
this.optionValues[i] === t ? e.setAttribute("data-active", "true") : e.removeAttribute("data-active");
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
class J extends m {
|
|
507
|
+
constructor(t, e, i = {}) {
|
|
508
|
+
super(t, e, i), this.input = r("input", {
|
|
509
|
+
type: "color",
|
|
510
|
+
className: "cp-input-color",
|
|
511
|
+
value: this.value || "#000000"
|
|
512
|
+
}), this.appendWidget(this.input), this.input.addEventListener("input", (s) => {
|
|
513
|
+
const a = s.target;
|
|
514
|
+
this.setValue(a.value);
|
|
515
|
+
}), this.updateDisplay();
|
|
516
|
+
}
|
|
517
|
+
updateDisplay() {
|
|
518
|
+
this.input.value = this.value;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function F(n) {
|
|
522
|
+
const t = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
523
|
+
n = n.replace(t, (i, s, a, o) => s + s + a + a + o + o);
|
|
524
|
+
const e = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(n);
|
|
525
|
+
return e ? [
|
|
526
|
+
parseInt(e[1], 16),
|
|
527
|
+
parseInt(e[2], 16),
|
|
528
|
+
parseInt(e[3], 16)
|
|
529
|
+
] : [0, 0, 0];
|
|
530
|
+
}
|
|
531
|
+
function Z(n, t, e) {
|
|
532
|
+
return "#" + ((1 << 24) + (Math.round(n) << 16) + (Math.round(t) << 8) + Math.round(e)).toString(16).slice(1);
|
|
533
|
+
}
|
|
534
|
+
function w(n) {
|
|
535
|
+
const t = n / 255;
|
|
536
|
+
return t <= 0.04045 ? t / 12.92 : Math.pow((t + 0.055) / 1.055, 2.4);
|
|
537
|
+
}
|
|
538
|
+
function A(n) {
|
|
539
|
+
return n <= 31308e-7 ? n * 12.92 * 255 : (1.055 * Math.pow(n, 1 / 2.4) - 0.055) * 255;
|
|
540
|
+
}
|
|
541
|
+
function K(n, t, e) {
|
|
542
|
+
const [i, s, a] = F(n), [o, l, d] = F(t), h = w(i), y = w(s), p = w(a), c = w(o), u = w(l), f = w(d), v = h + e * (c - h), I = y + e * (u - y), S = p + e * (f - p), C = A(v), g = A(I), b = A(S);
|
|
543
|
+
return Z(C, g, b);
|
|
544
|
+
}
|
|
545
|
+
class Q extends m {
|
|
546
|
+
constructor(t, e, i = {}) {
|
|
547
|
+
super(t, e, i), this.stops = [], this.pingPongDirection = 1, this.animationT = 0, this.manualPosition = 0, this.initialOptions = i, this.stops = i.stops || [
|
|
548
|
+
{ color: "#000000", position: 0 },
|
|
549
|
+
{ color: "#ffffff", position: 1 }
|
|
550
|
+
], this.sortStops();
|
|
551
|
+
const s = r("details", {
|
|
552
|
+
className: "cp-controller-details"
|
|
553
|
+
}), a = r("summary", {
|
|
554
|
+
className: "cp-controller-summary"
|
|
555
|
+
}), o = r("div", {
|
|
556
|
+
className: "cp-controller-summary-content"
|
|
557
|
+
});
|
|
558
|
+
this.displayColor = r("div", {
|
|
559
|
+
className: "cp-color-swatch",
|
|
560
|
+
style: `background-color: ${this.value}`
|
|
561
|
+
}), this.displayText = r(
|
|
562
|
+
"span",
|
|
563
|
+
{ className: "cp-value-display" },
|
|
564
|
+
[String(this.value)]
|
|
565
|
+
), o.appendChild(this.displayColor), o.appendChild(this.displayText), a.appendChild(o), s.appendChild(a);
|
|
566
|
+
const l = r("div", { className: "cp-number-settings" });
|
|
567
|
+
this.stopsContainer = r("div", {
|
|
568
|
+
className: "cp-stops-container"
|
|
569
|
+
}), this.renderStops(), l.appendChild(this.stopsContainer);
|
|
570
|
+
const d = r(
|
|
571
|
+
"button",
|
|
572
|
+
{
|
|
573
|
+
className: "cp-button"
|
|
574
|
+
},
|
|
575
|
+
["+ Add Stop"]
|
|
576
|
+
);
|
|
577
|
+
d.addEventListener("click", () => {
|
|
578
|
+
this.stops.push({ color: "#ffffff", position: 0.5 }), this.sortStops(), this.renderStops(), this.updateOutput();
|
|
579
|
+
}), l.appendChild(d);
|
|
580
|
+
const h = r("hr", { className: "cp-separator" });
|
|
581
|
+
l.appendChild(h), this.signalHandler = new k({
|
|
582
|
+
container: l,
|
|
583
|
+
onChange: (y, p) => this.applySignal(y, p)
|
|
584
|
+
}), s.appendChild(l), this.appendWidget(s), this.updateOutput(0);
|
|
585
|
+
}
|
|
586
|
+
sortStops() {
|
|
587
|
+
this.stops.sort((t, e) => t.position - e.position);
|
|
588
|
+
}
|
|
589
|
+
renderStops() {
|
|
590
|
+
this.stopsContainer.innerHTML = "", this.stops.forEach((t, e) => {
|
|
591
|
+
const i = r("div", { className: "cp-setting-row" }), s = r("input", {
|
|
592
|
+
type: "color",
|
|
593
|
+
className: "cp-input-color",
|
|
594
|
+
value: t.color
|
|
595
|
+
});
|
|
596
|
+
s.addEventListener("input", (l) => {
|
|
597
|
+
t.color = l.target.value, this.updateOutput();
|
|
598
|
+
});
|
|
599
|
+
const a = r("input", {
|
|
600
|
+
type: "number",
|
|
601
|
+
className: "cp-input-number cp-input-small",
|
|
602
|
+
min: "0",
|
|
603
|
+
max: "1",
|
|
604
|
+
step: "0.01",
|
|
605
|
+
value: String(t.position)
|
|
606
|
+
});
|
|
607
|
+
a.addEventListener("change", (l) => {
|
|
608
|
+
let d = parseFloat(l.target.value);
|
|
609
|
+
isNaN(d) && (d = 0), t.position = Math.max(0, Math.min(1, d)), this.sortStops(), this.renderStops(), this.updateOutput();
|
|
610
|
+
});
|
|
611
|
+
const o = B(() => {
|
|
612
|
+
this.stops.splice(e, 1), this.renderStops(), this.updateOutput();
|
|
613
|
+
});
|
|
614
|
+
i.appendChild(s), i.appendChild(a), i.appendChild(o), this.stopsContainer.appendChild(i);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
// Calculate color at t (0-1) and update value
|
|
618
|
+
updateOutput(t = this.manualPosition) {
|
|
619
|
+
if (this.stops.length === 0) return;
|
|
620
|
+
if (this.stops.length === 1) {
|
|
621
|
+
this.setValue(this.stops[0].color), this.updateDisplay();
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
let e = "#000000";
|
|
625
|
+
if (t = Math.max(0, Math.min(1, t)), t <= this.stops[0].position)
|
|
626
|
+
e = this.stops[0].color;
|
|
627
|
+
else if (t >= this.stops[this.stops.length - 1].position)
|
|
628
|
+
e = this.stops[this.stops.length - 1].color;
|
|
629
|
+
else
|
|
630
|
+
for (let i = 0; i < this.stops.length - 1; i++) {
|
|
631
|
+
const s = this.stops[i], a = this.stops[i + 1];
|
|
632
|
+
if (t >= s.position && t <= a.position) {
|
|
633
|
+
const o = a.position - s.position, l = o === 0 ? 0 : (t - s.position) / o;
|
|
634
|
+
e = K(s.color, a.color, l);
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
this.setValue(e), this.updateDisplay();
|
|
639
|
+
}
|
|
640
|
+
updateDisplay() {
|
|
641
|
+
this.displayColor && (this.displayColor.style.backgroundColor = this.value), this.displayText && (this.displayText.textContent = this.value);
|
|
642
|
+
}
|
|
643
|
+
applySignal(t, e) {
|
|
644
|
+
let i = t;
|
|
645
|
+
if (e === "forward")
|
|
646
|
+
i = t;
|
|
647
|
+
else if (e === "backward")
|
|
648
|
+
i = 1 - t;
|
|
649
|
+
else {
|
|
650
|
+
const s = t * 0.05;
|
|
651
|
+
e === "loopForward" ? (this.animationT = (this.animationT + s) % 1, i = this.animationT) : e === "loopBackward" ? (this.animationT = (this.animationT - s + 1) % 1, i = this.animationT) : e === "pingpong" && (this.animationT += s * this.pingPongDirection, this.animationT >= 1 ? (this.animationT = 1, this.pingPongDirection = -1) : this.animationT <= 0 && (this.animationT = 0, this.pingPongDirection = 1), i = this.animationT);
|
|
652
|
+
}
|
|
653
|
+
this.updateOutput(i), this.manualPosition = i;
|
|
654
|
+
}
|
|
655
|
+
save() {
|
|
656
|
+
return {
|
|
657
|
+
stops: this.stops,
|
|
658
|
+
settings: {
|
|
659
|
+
signal: this.signalHandler.save()
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
load(t) {
|
|
664
|
+
t && t.stops && (this.stops = t.stops, this.sortStops(), this.renderStops()), t && t.settings && this.signalHandler?.load(t.settings.signal);
|
|
665
|
+
}
|
|
666
|
+
reset() {
|
|
667
|
+
this.stops = this.initialOptions.stops || [
|
|
668
|
+
{ color: "#000000", position: 0 },
|
|
669
|
+
{ color: "#ffffff", position: 1 }
|
|
670
|
+
], this.sortStops(), this.renderStops(), this.signalHandler?.reset(), this.updateOutput(0);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
class X extends m {
|
|
674
|
+
constructor(t, e, i = {}) {
|
|
675
|
+
super(t, e, i), this.items = [], this.initialOptions = i, this.itemType = i.itemType || "string", this.items = this.parseValue(this.value);
|
|
676
|
+
const s = r("details", {
|
|
677
|
+
className: "cp-controller-details"
|
|
678
|
+
}), a = r("summary", {
|
|
679
|
+
className: "cp-controller-summary"
|
|
680
|
+
}), o = r("div", {
|
|
681
|
+
className: "cp-controller-summary-content"
|
|
682
|
+
}), l = r("span", { className: "cp-value-display" }, [
|
|
683
|
+
`${this.items.length} items`
|
|
684
|
+
]);
|
|
685
|
+
o.appendChild(l), a.appendChild(o), s.appendChild(a);
|
|
686
|
+
const d = r("div", { className: "cp-number-settings" });
|
|
687
|
+
this.itemsContainer = r("div", {
|
|
688
|
+
className: "cp-stops-container"
|
|
689
|
+
}), this.renderItems(), d.appendChild(this.itemsContainer);
|
|
690
|
+
const h = r(
|
|
691
|
+
"button",
|
|
692
|
+
{
|
|
693
|
+
className: "cp-button cp-input-small",
|
|
694
|
+
style: "margin-top: 8px; width: 100%;"
|
|
695
|
+
},
|
|
696
|
+
["+ Add Item"]
|
|
697
|
+
);
|
|
698
|
+
h.addEventListener("click", () => {
|
|
699
|
+
this.addItem();
|
|
700
|
+
}), d.appendChild(h), s.appendChild(d), this.appendWidget(s);
|
|
701
|
+
}
|
|
702
|
+
parseValue(t) {
|
|
703
|
+
return !t || t.trim() === "" ? [] : t.split(",").map((e) => e.trim());
|
|
704
|
+
}
|
|
705
|
+
serializeValue() {
|
|
706
|
+
return this.items.join(",");
|
|
707
|
+
}
|
|
708
|
+
getDefaultItemValue() {
|
|
709
|
+
switch (this.itemType) {
|
|
710
|
+
case "color":
|
|
711
|
+
return "#ffffff";
|
|
712
|
+
case "number":
|
|
713
|
+
return "0";
|
|
714
|
+
default:
|
|
715
|
+
return "";
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
addItem(t) {
|
|
719
|
+
const e = t !== void 0 ? t : this.getDefaultItemValue();
|
|
720
|
+
this.items.push(e), this.renderItems(), this.updateValue();
|
|
721
|
+
}
|
|
722
|
+
updateValue() {
|
|
723
|
+
const t = this.serializeValue();
|
|
724
|
+
this.setValue(t), this.updateSummary();
|
|
725
|
+
}
|
|
726
|
+
updateSummary() {
|
|
727
|
+
const t = this.domElement.querySelector(".cp-value-display");
|
|
728
|
+
t && (t.textContent = `${this.items.length} items`);
|
|
729
|
+
}
|
|
730
|
+
renderItems() {
|
|
731
|
+
this.itemsContainer.innerHTML = "", this.items.forEach((t, e) => {
|
|
732
|
+
const i = r("div", { className: "cp-setting-row" });
|
|
733
|
+
let s;
|
|
734
|
+
this.itemType === "color" ? s = r("input", {
|
|
735
|
+
type: "color",
|
|
736
|
+
className: "cp-input-color",
|
|
737
|
+
value: t
|
|
738
|
+
}) : this.itemType === "number" ? s = r("input", {
|
|
739
|
+
type: "number",
|
|
740
|
+
className: "cp-input-number cp-input-small",
|
|
741
|
+
step: "any",
|
|
742
|
+
value: t
|
|
743
|
+
}) : s = r("input", {
|
|
744
|
+
type: "text",
|
|
745
|
+
className: "cp-input-number cp-input-small",
|
|
746
|
+
value: t
|
|
747
|
+
}), s.addEventListener("input", (o) => {
|
|
748
|
+
this.items[e] = o.target.value, this.updateValue();
|
|
749
|
+
});
|
|
750
|
+
const a = B(() => {
|
|
751
|
+
this.items.splice(e, 1), this.renderItems(), this.updateValue();
|
|
752
|
+
});
|
|
753
|
+
i.appendChild(s), i.appendChild(a), this.itemsContainer.appendChild(i);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
updateDisplay() {
|
|
757
|
+
}
|
|
758
|
+
save() {
|
|
759
|
+
return [...this.items];
|
|
760
|
+
}
|
|
761
|
+
load(t) {
|
|
762
|
+
Array.isArray(t) ? this.items = [...t] : typeof t == "string" && (this.items = this.parseValue(t)), this.renderItems(), this.updateValue();
|
|
763
|
+
}
|
|
764
|
+
reset() {
|
|
765
|
+
const t = this.initialValue || "";
|
|
766
|
+
this.items = this.parseValue(t), this.renderItems(), this.updateValue();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
class Y {
|
|
770
|
+
constructor() {
|
|
771
|
+
this.frames = 0, this.pollingInterval = 1e3, this.prevTime = performance.now(), this.render = () => {
|
|
772
|
+
this.frames++;
|
|
773
|
+
const t = performance.now();
|
|
774
|
+
if (t >= this.prevTime + this.pollingInterval) {
|
|
775
|
+
const e = Math.round(this.frames * 1e3 / (t - this.prevTime));
|
|
776
|
+
let i = "";
|
|
777
|
+
const s = performance.memory;
|
|
778
|
+
s && (i = ` / ${Math.round(s.usedJSHeapSize / 1048576)}MB`), this.domElement.textContent = `${e} FPS${i}`, this.prevTime = t, this.frames = 0;
|
|
779
|
+
}
|
|
780
|
+
this.rafId = requestAnimationFrame(this.render);
|
|
781
|
+
}, this.domElement = r("span", { className: "cp-stats" }), this.rafId = requestAnimationFrame(this.render);
|
|
782
|
+
}
|
|
783
|
+
destroy() {
|
|
784
|
+
cancelAnimationFrame(this.rafId);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
class P {
|
|
788
|
+
constructor() {
|
|
789
|
+
this.controllers = [], this.folders = [];
|
|
790
|
+
}
|
|
791
|
+
addNumber(t, e, i = {}) {
|
|
792
|
+
const s = new q(t, e, i);
|
|
793
|
+
return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
|
|
794
|
+
}
|
|
795
|
+
addSelect(t, e, i = {}) {
|
|
796
|
+
const s = new H(t, e, i);
|
|
797
|
+
return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
|
|
798
|
+
}
|
|
799
|
+
addBoolean(t, e, i = {}) {
|
|
800
|
+
const s = new W(t, e, i);
|
|
801
|
+
return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
|
|
802
|
+
}
|
|
803
|
+
addButton(t, e, i = {}) {
|
|
804
|
+
const s = new _(t, e, i);
|
|
805
|
+
return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
|
|
806
|
+
}
|
|
807
|
+
addRadio(t, e, i = {}) {
|
|
808
|
+
const s = new G(t, e, i);
|
|
809
|
+
return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
|
|
810
|
+
}
|
|
811
|
+
addColor(t, e, i = {}) {
|
|
812
|
+
const s = new J(t, e, i);
|
|
813
|
+
return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
|
|
814
|
+
}
|
|
815
|
+
addGradient(t, e, i = {}) {
|
|
816
|
+
const s = new Q(t, e, i);
|
|
817
|
+
return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
|
|
818
|
+
}
|
|
819
|
+
addArray(t, e, i = {}) {
|
|
820
|
+
const s = new X(t, e, i);
|
|
821
|
+
return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
|
|
822
|
+
}
|
|
823
|
+
addFolder(t) {
|
|
824
|
+
const e = new tt(t);
|
|
825
|
+
return this.contentElement.appendChild(e.domElement), this.folders.push(e), e;
|
|
826
|
+
}
|
|
827
|
+
save() {
|
|
828
|
+
const t = {
|
|
829
|
+
controllers: {},
|
|
830
|
+
folders: {}
|
|
831
|
+
};
|
|
832
|
+
for (const e of this.controllers)
|
|
833
|
+
typeof e.value != "function" && (t.controllers[e.key] = e.save());
|
|
834
|
+
for (const e of this.folders)
|
|
835
|
+
t.folders[e.title] = e.save();
|
|
836
|
+
return t;
|
|
837
|
+
}
|
|
838
|
+
load(t) {
|
|
839
|
+
if (!t) {
|
|
840
|
+
this.reset();
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
for (const e of this.controllers)
|
|
844
|
+
if (typeof e.value != "function")
|
|
845
|
+
if (t.controllers && e.key in t.controllers) {
|
|
846
|
+
const i = t.controllers[e.key];
|
|
847
|
+
i !== void 0 && e.load(i);
|
|
848
|
+
} else
|
|
849
|
+
e.reset();
|
|
850
|
+
for (const e of this.folders) {
|
|
851
|
+
const i = t.folders ? t.folders[e.title] : void 0;
|
|
852
|
+
e.load(i);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
reset() {
|
|
856
|
+
for (const t of this.controllers)
|
|
857
|
+
typeof t.value != "function" && t.reset();
|
|
858
|
+
for (const t of this.folders)
|
|
859
|
+
t.reset();
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
class tt extends P {
|
|
863
|
+
constructor(t) {
|
|
864
|
+
super(), this.title = t, this.domElement = r("details", {
|
|
865
|
+
className: "cp-folder",
|
|
866
|
+
open: !0
|
|
867
|
+
}), this.summaryElement = r(
|
|
868
|
+
"summary",
|
|
869
|
+
{
|
|
870
|
+
className: "cp-summary"
|
|
871
|
+
},
|
|
872
|
+
[t]
|
|
873
|
+
), this.domElement.appendChild(this.summaryElement), this.contentElement = r("div", {
|
|
874
|
+
className: "cp-content cp-folder-content"
|
|
875
|
+
}), this.domElement.appendChild(this.contentElement), this.domElement.appendChild(
|
|
876
|
+
r("hr", { className: "cp-separator" })
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
class et extends P {
|
|
881
|
+
constructor(t, e = {}) {
|
|
882
|
+
super(), this.domElement = r("details", {
|
|
883
|
+
className: "cp-root",
|
|
884
|
+
open: !0
|
|
885
|
+
}), this.summaryElement = r("summary", {
|
|
886
|
+
className: "cp-summary cp-summary-root"
|
|
887
|
+
}), this.domElement.appendChild(this.summaryElement);
|
|
888
|
+
const i = r("span", {}, [e.title || "GUI"]);
|
|
889
|
+
this.summaryElement.appendChild(i), this.stats = new Y(), this.summaryElement.appendChild(this.stats.domElement), this.contentElement = r("div", { className: "cp-content" }), this.domElement.appendChild(this.contentElement);
|
|
890
|
+
const s = this.addFolder("_Signals"), a = {
|
|
891
|
+
audioInput: null,
|
|
892
|
+
fftSize: 2048
|
|
893
|
+
};
|
|
894
|
+
s.addRadio(a, "audioInput", {
|
|
895
|
+
label: "Audio Signal",
|
|
896
|
+
options: ["microphone", "browser"]
|
|
897
|
+
}).onChange((c) => {
|
|
898
|
+
x.setInput(c);
|
|
899
|
+
}), s.addSelect(a, "fftSize", {
|
|
900
|
+
label: "FFT Size",
|
|
901
|
+
options: [256, 512, 1024, 2048]
|
|
902
|
+
}).onChange((c) => {
|
|
903
|
+
x.setFFTSize(c);
|
|
904
|
+
}), s.addNumber(x, "smoothingTimeConstant", {
|
|
905
|
+
min: 0,
|
|
906
|
+
max: 0.99,
|
|
907
|
+
step: 0.01,
|
|
908
|
+
label: "Smoothing"
|
|
909
|
+
}).onChange((c) => {
|
|
910
|
+
x.analyser.smoothingTimeConstant = c;
|
|
911
|
+
}), s.addNumber(x, "spectrumBoost", {
|
|
912
|
+
min: 1,
|
|
913
|
+
max: 5,
|
|
914
|
+
step: 0.1,
|
|
915
|
+
label: "Compression"
|
|
916
|
+
}), t ? t.appendChild(this.domElement) : document.body.appendChild(this.domElement);
|
|
917
|
+
const o = e.title || "GUI";
|
|
918
|
+
this.presetStoragePrefix = `cp-presets-${o}-`;
|
|
919
|
+
const l = this.addFolder("_User Presets"), d = () => {
|
|
920
|
+
const c = ["Default"];
|
|
921
|
+
if (typeof localStorage > "u") return c;
|
|
922
|
+
for (let u = 0; u < localStorage.length; u++) {
|
|
923
|
+
const f = localStorage.key(u);
|
|
924
|
+
if (f && f.startsWith(this.presetStoragePrefix)) {
|
|
925
|
+
const v = f.substring(this.presetStoragePrefix.length);
|
|
926
|
+
v !== "Default" && !c.includes(v) && c.push(v);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return c.sort();
|
|
930
|
+
}, h = {
|
|
931
|
+
selected: "Default",
|
|
932
|
+
save: () => {
|
|
933
|
+
const c = prompt("Preset Name:", h.selected);
|
|
934
|
+
if (c) {
|
|
935
|
+
if (c === "Default") {
|
|
936
|
+
alert("Cannot overwrite Default preset");
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const u = this.presetStoragePrefix + c;
|
|
940
|
+
this.saveToLocalStorage(u);
|
|
941
|
+
const f = d();
|
|
942
|
+
p.setOptions(f), h.selected = c, p.setValue(c);
|
|
943
|
+
}
|
|
944
|
+
},
|
|
945
|
+
load: () => {
|
|
946
|
+
const c = h.selected, u = this.presetStoragePrefix + c;
|
|
947
|
+
this.loadFromLocalStorage(u), h.selected = c, p.setValue(c);
|
|
948
|
+
},
|
|
949
|
+
delete: () => {
|
|
950
|
+
if (h.selected === "Default") {
|
|
951
|
+
alert("Cannot delete Default preset");
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (confirm(`Delete preset "${h.selected}"?`)) {
|
|
955
|
+
const c = this.presetStoragePrefix + h.selected;
|
|
956
|
+
localStorage.removeItem(c);
|
|
957
|
+
const u = d();
|
|
958
|
+
p.setOptions(u), h.selected = "Default", p.setValue("Default"), this.reset();
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
export: () => {
|
|
962
|
+
const c = this.save(), u = (T) => {
|
|
963
|
+
const N = {
|
|
964
|
+
controllers: {},
|
|
965
|
+
folders: {}
|
|
966
|
+
};
|
|
967
|
+
for (const [E, L] of Object.entries(T.controllers))
|
|
968
|
+
E.startsWith("_") || (N.controllers[E] = L);
|
|
969
|
+
for (const [E, L] of Object.entries(
|
|
970
|
+
T.folders
|
|
971
|
+
))
|
|
972
|
+
E.startsWith("_") || (N.folders[E] = u(L));
|
|
973
|
+
return N;
|
|
974
|
+
}, f = u(c), v = {
|
|
975
|
+
_presetName: h.selected || "CustomPreset",
|
|
976
|
+
_exportDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
977
|
+
_instructions: "To add as factory preset: Copy 'controllers' and 'folders' fields into the presets.json file",
|
|
978
|
+
...f
|
|
979
|
+
}, I = JSON.stringify(v, null, 2), S = new Blob([I], { type: "application/json" }), C = URL.createObjectURL(S), g = document.createElement("a");
|
|
980
|
+
g.href = C;
|
|
981
|
+
const b = (/* @__PURE__ */ new Date()).toISOString().split("T")[0], V = h.selected.replace(/[^a-z0-9]/gi, "-").toLowerCase();
|
|
982
|
+
g.download = `${o.toLowerCase()}-preset-${V}-${b}.json`, document.body.appendChild(g), g.click(), document.body.removeChild(g), URL.revokeObjectURL(C);
|
|
983
|
+
},
|
|
984
|
+
import: () => {
|
|
985
|
+
const c = document.createElement("input");
|
|
986
|
+
c.type = "file", c.accept = ".json", c.onchange = (u) => {
|
|
987
|
+
const f = u.target.files?.[0];
|
|
988
|
+
if (!f) return;
|
|
989
|
+
const v = new FileReader();
|
|
990
|
+
v.onload = (I) => {
|
|
991
|
+
try {
|
|
992
|
+
const S = I.target?.result, C = JSON.parse(S), g = {
|
|
993
|
+
controllers: C.controllers || {},
|
|
994
|
+
folders: C.folders || {}
|
|
995
|
+
};
|
|
996
|
+
if (!g.controllers || !g.folders) {
|
|
997
|
+
alert(
|
|
998
|
+
"Invalid preset file: missing 'controllers' or 'folders'"
|
|
999
|
+
);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
this.load(g);
|
|
1003
|
+
const b = C._presetName || "ImportedPreset";
|
|
1004
|
+
if (confirm(
|
|
1005
|
+
`Preset loaded! Save as "${b}" to User Presets?`
|
|
1006
|
+
)) {
|
|
1007
|
+
const T = this.presetStoragePrefix + b;
|
|
1008
|
+
this.saveToLocalStorage(T);
|
|
1009
|
+
const N = d();
|
|
1010
|
+
p.setOptions(N), h.selected = b, p.setValue(b);
|
|
1011
|
+
}
|
|
1012
|
+
} catch (S) {
|
|
1013
|
+
alert(
|
|
1014
|
+
`Failed to import preset: ${S instanceof Error ? S.message : "Invalid JSON"}`
|
|
1015
|
+
), console.error("Import error:", S);
|
|
1016
|
+
}
|
|
1017
|
+
}, v.readAsText(f);
|
|
1018
|
+
}, c.click();
|
|
1019
|
+
}
|
|
1020
|
+
}, y = d(), p = l.addSelect(h, "selected", {
|
|
1021
|
+
label: "Preset",
|
|
1022
|
+
options: y
|
|
1023
|
+
});
|
|
1024
|
+
l.addButton("Load", () => h.load()), l.addButton("Save / New", () => h.save()), l.addButton("Delete", () => h.delete()), l.addButton("Export JSON", () => h.export()), l.addButton("Import JSON", () => h.import());
|
|
1025
|
+
}
|
|
1026
|
+
saveToLocalStorage(t) {
|
|
1027
|
+
const e = this.save();
|
|
1028
|
+
try {
|
|
1029
|
+
localStorage.setItem(t, JSON.stringify(e));
|
|
1030
|
+
} catch (i) {
|
|
1031
|
+
console.warn("GUI: Failed to save to localStorage", i);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
loadFromLocalStorage(t) {
|
|
1035
|
+
try {
|
|
1036
|
+
const e = localStorage.getItem(t);
|
|
1037
|
+
if (e) {
|
|
1038
|
+
const i = JSON.parse(e);
|
|
1039
|
+
this.load(i);
|
|
1040
|
+
}
|
|
1041
|
+
} catch (e) {
|
|
1042
|
+
console.warn("GUI: Failed to load from localStorage", e);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
saveDefaultPreset() {
|
|
1046
|
+
const t = this.presetStoragePrefix + "Default";
|
|
1047
|
+
this.save(), this.saveToLocalStorage(t);
|
|
1048
|
+
}
|
|
1049
|
+
destroy() {
|
|
1050
|
+
this.stats.destroy(), this.domElement.remove(), this.controllers = [], this.folders = [];
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
export {
|
|
1054
|
+
X as ArrayController,
|
|
1055
|
+
z as AudioSignals,
|
|
1056
|
+
W as BooleanController,
|
|
1057
|
+
_ as ButtonController,
|
|
1058
|
+
J as ColorController,
|
|
1059
|
+
m as Controller,
|
|
1060
|
+
et as GUI,
|
|
1061
|
+
Q as GradientController,
|
|
1062
|
+
$ as MidiSignals,
|
|
1063
|
+
q as NumberController,
|
|
1064
|
+
G as RadioController,
|
|
1065
|
+
H as SelectController,
|
|
1066
|
+
x as audioSignals,
|
|
1067
|
+
U as midiSignals
|
|
1068
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(u,o){typeof exports=="object"&&typeof module<"u"?o(exports):typeof define=="function"&&define.amd?define(["exports"],o):(u=typeof globalThis<"u"?globalThis:u||self,o(u.ControlPanel={}))})(this,(function(u){"use strict";function o(n,t={},e=[]){const i=document.createElement(n);for(const[s,a]of Object.entries(t))s==="className"?i.className=String(a):s==="style"&&typeof a=="object"?Object.assign(i.style,a):s==="open"&&typeof a=="boolean"?a?i.setAttribute("open",""):i.removeAttribute("open"):typeof a!="object"&&i.setAttribute(s,String(a));for(const s of e)typeof s=="string"?i.appendChild(document.createTextNode(s)):i.appendChild(s);return i}function D(n){const t=o("button",{className:"cp-button cp-button-delete"},["×"]);return t.addEventListener("click",n),t}function K(n){return n.replace(/([A-Z])/g," $1").replace(/^./,t=>t.toUpperCase()).trim()}function O(n,t,e){return Math.min(Math.max(n,t),e)}function Q(n,t,e){if(t.length!==e.length)throw new Error("Input and output ranges must have the same length");if(t.length<2)throw new Error("Input and output ranges must have at least two values");let i=0;for(;i<t.length-1&&n>t[i+1];)i++;if(i===t.length-1)return e[e.length-1];if(i===0&&n<t[0])return e[0];const s=t[i],a=t[i+1],r=e[i],l=e[i+1];return(n-s)/(a-s)*(l-r)+r}class B{constructor(){this.source=null,this.stream=null,this.fftSize=2048,this.smoothingTimeConstant=.92,this.spectrumBoost=2,this.levels={bass:0,mids:0,highs:0,volume:0},this.peaks={bass:0,mids:0,highs:0,volume:0},this._isAnalyzing=!1,this.loop=()=>{this._isAnalyzing&&(requestAnimationFrame(this.loop),this.update())};const t=window.AudioContext||window.webkitAudioContext;this.ctx=new t,this.analyser=this.ctx.createAnalyser(),this.analyser.fftSize=this.fftSize,this.analyser.smoothingTimeConstant=this.smoothingTimeConstant,this.dataArray=new Uint8Array(this.analyser.frequencyBinCount),this.waveformArray=new Uint8Array(this.analyser.frequencyBinCount)}setFFTSize(t){this.fftSize=t,this.analyser.fftSize=t,this.dataArray=new Uint8Array(this.analyser.frequencyBinCount),this.waveformArray=new Uint8Array(this.analyser.frequencyBinCount)}async setInput(t){try{let e;t==="browser"?e=navigator.mediaDevices.getDisplayMedia({audio:!0,video:!0}):e=navigator.mediaDevices.getUserMedia({audio:!0});const i=await e;this.ctx.state==="suspended"&&this.ctx.resume(),this.source&&this.source.disconnect(),this.stream&&this.stream.getTracks().forEach(s=>s.stop()),this.stream=i,this.source=this.ctx.createMediaStreamSource(this.stream),this.source.connect(this.analyser),this._isAnalyzing=!0,this.loop()}catch(e){console.error("Error accessing audio input:",e),this._isAnalyzing=!1}}update(){if(this.analyser.getByteFrequencyData(this.dataArray),this.analyser.getByteTimeDomainData(this.waveformArray),this.spectrumBoost!==1){const d=this.dataArray.length;for(let h=0;h<d;h++){const p=1+h/d*(this.spectrumBoost-1);this.dataArray[h]=Math.min(255,this.dataArray[h]*p)}}const t=[2,10],e=[10,150],i=[150,600],s=this.getAverage(t[0],t[1]),a=this.getAverage(e[0],e[1]),r=this.getAverage(i[0],i[1]),l=this.getAverage(0,i[1]);this.processLevel("bass",s),this.processLevel("mids",a),this.processLevel("highs",r),this.processLevel("volume",l)}processLevel(t,e){this.peaks[t]-=5e-4,this.peaks[t]=O(this.peaks[t],.1,1),e>this.peaks[t]&&(this.peaks[t]=e),this.levels[t]=O(Q(e,[0,this.peaks[t]],[0,1]),0,1)}getAverage(t,e){let i=0;const s=e-t;if(s<=0)return 0;for(let a=t;a<e;a++)i+=this.dataArray[a];return i/s/255}getSignal(t){return()=>this.levels[t]}}const x=new B;class F{constructor(){this.midiAccess=null,this.values=new Map,this.isListening=!1,this.resolveListen=null,this.listeningCallback=null,this.init()}async init(){if(typeof navigator<"u"&&navigator.requestMIDIAccess)try{this.midiAccess=await navigator.requestMIDIAccess(),this.setupInputs(),this.midiAccess.onstatechange=t=>{t.port.type==="input"&&t.port.state==="connected"&&this.setupInputs()}}catch(t){console.warn("MIDI Access failed",t)}}setupInputs(){if(!this.midiAccess)return;const t=this.midiAccess.inputs.values();for(const e of t)e.onmidimessage=this.handleMessage.bind(this)}handleMessage(t){const e=t.data,[i]=e;if((i&240)>=240)return;const a=this.getIdFromMessage(t),r=this.normalizeValue(e);this.values.set(a,r),this.isListening&&this.resolveListen&&r>0&&(this.resolveListen(a),this.isListening=!1,this.resolveListen=null,this.listeningCallback&&this.listeningCallback())}getIdFromMessage(t){const e=t.data,[i,s]=e,a=i&240,r=t.currentTarget.name||"unknown",l=a===144||a===128?"note":"ctrl",d=r.replace(/[^a-zA-Z0-9]/g,"");return`${s}_${l}_${d}`}normalizeValue(t){const[e,i,s]=t,a=e&240;return a===144?s>0?1:0:a===128?0:a===176?s/127:0}listen(){return this.isListening=!0,new Promise(t=>{this.resolveListen=t})}cancelListen(){this.isListening=!1,this.resolveListen=null}getSignal(t){return()=>this.values.get(t)??0}}const k=new F,M=class M{constructor(t,e,i={}){this.changeFns=new Set,this.object=t,this.property=e,this.key=i.id??e,this.initialValue=this.object[this.property],this.domElement=o("div",{className:"cp-controller"});const s=i.label??K(e),a=o("label",{className:"cp-label"},[String(s)]);a.setAttribute("title",String(s)),this.domElement.appendChild(a),i.disabled&&this.domElement.setAttribute("data-disabled","true")}get value(){return this.object[this.property]}setValue(t,e=!0){this.object[this.property]=t,e&&this.emitChange(t),this.updateDisplay()}save(){return this.value}load(t){this.setValue(t)}reset(){this.setValue(this.initialValue)}onChange(t){return this.changeFns.add(t),this}emitChange(t){for(const e of this.changeFns)e(t)}appendWidget(t){this.domElement.appendChild(t)}};M.audio=x,M.midi=k;let m=M;const P={linear:n=>n,quadIn:n=>n*n,quadOut:n=>n*(2-n),quadInOut:n=>n<.5?2*n*n:-1+(4-2*n)*n,cubicIn:n=>n*n*n,cubicOut:n=>--n*n*n+1,cubicInOut:n=>n<.5?4*n*n*n:(n-1)*(2*n-2)*(2*n-2)+1,expoIn:n=>n===0?0:Math.pow(2,10*(n-1)),expoOut:n=>n===1?1:-Math.pow(2,-10*n)+1,expoInOut:n=>n===0||n===1?n:(n*=2)<1?.5*Math.pow(2,10*(n-1)):.5*(-Math.pow(2,-10*--n)+2),sineIn:n=>1-Math.cos(n*Math.PI/2),sineOut:n=>Math.sin(n*Math.PI/2),sineInOut:n=>-(Math.cos(Math.PI*n)-1)/2};class R{constructor(t){this.rafId=null,this.currentSignalType=null,this.currentMidiId=null,this.currentEase="linear",this.currentBehaviour="forward",this.loop=()=>{if(this.currentSignalType){let e=0;this.currentSignalType==="midi"?this.currentMidiId&&(e=m.midi.getSignal(this.currentMidiId)()):e=m.audio.getSignal(this.currentSignalType)();const i=P[this.currentEase](e);this.onChange(i,this.currentBehaviour),this.rafId=requestAnimationFrame(this.loop)}},this.onChange=t.onChange,this.setupControllers(t.container)}setupControllers(t){const e=this.createSettingSelect("signal",["none","bass","mids","highs","volume","midi"],r=>this.setSignalType(r));this.signalSelect=e.select,t.appendChild(e.row),this.midiRow=o("div",{className:"cp-setting-row",style:"display: none;"});const i=o("label",{className:"cp-setting-label"},["Midi"]);this.midiBtn=o("button",{className:"cp-button cp-input-small"},["Learn"]),this.midiBtn.addEventListener("click",async()=>{if(this.midiBtn.textContent==="Listening..."){m.midi.cancelListen(),this.setMidiId(null);return}this.midiBtn.textContent="Listening...";const r=await m.midi.listen();this.setMidiId(r)}),this.midiRow.appendChild(i),this.midiRow.appendChild(this.midiBtn),t.appendChild(this.midiRow);const s=this.createSettingSelect("behaviour",["forward","backward","loopForward","loopBackward","pingpong"],r=>this.setBehaviour(r));this.behaviourRow=s.row,this.behaviourSelect=s.select,this.behaviourRow.style.display="none",this.behaviourSelect.value=this.currentBehaviour,t.appendChild(this.behaviourRow);const a=this.createSettingSelect("ease",Object.keys(P),r=>this.setEase(r));this.easeRow=a.row,this.easeSelect=a.select,this.easeRow.style.display="none",this.easeSelect.value=this.currentEase,t.appendChild(this.easeRow)}createSettingSelect(t,e,i){const s=o("div",{className:"cp-setting-row"}),a=o("label",{className:"cp-setting-label"},[t]),r=o("select",{className:"cp-select cp-input-small"});return e.forEach(l=>{const d=o("option",{value:l},[l]);r.appendChild(d)}),r.addEventListener("change",()=>i(r.value)),s.appendChild(a),s.appendChild(r),{row:s,select:r}}setSignalType(t){if(!t||t==="none")this.currentSignalType=null,this.currentMidiId=null,this.midiRow.style.display="none",this.easeRow.style.display="none",this.behaviourRow.style.display="none",this.stop(),this.signalSelect.value!=="none"&&(this.signalSelect.value="none");else{this.currentSignalType=t;const e=t==="midi";this.midiRow.style.display=e?"flex":"none",this.easeRow.style.display="flex",this.behaviourRow.style.display="flex",e||(this.currentMidiId=null,m.audio.ctx.state==="suspended"&&m.audio.setInput("microphone")),this.start(),this.signalSelect.value!==t&&(this.signalSelect.value=t)}}setMidiId(t){this.currentMidiId=t,this.midiBtn.textContent=t??"Learn"}setEase(t){this.currentEase=t,this.easeSelect.value!==t&&(this.easeSelect.value=t)}setBehaviour(t){this.currentBehaviour=t,this.behaviourSelect.value!==t&&(this.behaviourSelect.value=t)}start(){!this.rafId&&this.currentSignalType&&this.loop()}stop(){this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null)}save(){return{type:this.currentSignalType,midiId:this.currentMidiId,ease:this.currentEase,behaviour:this.currentBehaviour}}load(t){t&&(this.setSignalType(t.type),this.setMidiId(t.midiId||null),this.setEase(t.ease||"linear"),this.setBehaviour(t.behaviour||"forward"))}reset(){this.setSignalType("none"),this.setEase("linear"),this.setBehaviour("forward"),this.setMidiId(null)}}class j extends m{constructor(t,e,i={}){super(t,e,i),this.pingPongDirection=1,this.min=0,this.max=100,this.initialOptions=i,this.min=i.min??0,this.max=i.max??100;const s=o("details",{className:"cp-controller-details"}),a=o("summary",{className:"cp-controller-summary"});this.input=o("input",{type:"range",className:"cp-input-range",step:i.step??"any"}),i.min!==void 0&&(this.input.min=String(i.min)),i.max!==void 0&&(this.input.max=String(i.max)),this.input.value=String(this.value),this.display=o("span",{className:"cp-value-display"},[String(this.value.toFixed(1))]),this.input.addEventListener("input",()=>{const p=parseFloat(this.input.value);isNaN(p)||(this.setValue(p),this.display.textContent=String(p.toFixed(1)))}),this.input.addEventListener("click",p=>{p.stopPropagation()});const r=o("div",{className:"cp-controller-summary-content"});r.appendChild(this.input),r.appendChild(this.display),a.appendChild(r),s.appendChild(a);const l=o("div",{className:"cp-number-settings"}),d=this.createSetting("min",i.min,p=>this.setMin(p));this.minInput=d.input,l.appendChild(d.row);const h=this.createSetting("max",i.max,p=>this.setMax(p));this.maxInput=h.input,l.appendChild(h.row);const v=this.createSetting("step",i.step,p=>this.setStep(p));this.stepInput=v.input,l.appendChild(v.row),this.signalHandler=new R({container:l,onChange:(p,c)=>this.applySignal(p,c)}),s.appendChild(l),this.appendWidget(s)}setMin(t){typeof t=="number"&&(t=String(t)),t===""||isNaN(parseFloat(t))?this.input.removeAttribute("min"):(this.input.min=t,this.min=parseFloat(t)),this.minInput&&this.minInput.value!==t&&(this.minInput.value=t)}setMax(t){typeof t=="number"&&(t=String(t)),t===""||isNaN(parseFloat(t))?this.input.removeAttribute("max"):(this.input.max=t,this.max=parseFloat(t)),this.maxInput&&this.maxInput.value!==t&&(this.maxInput.value=t)}setStep(t){t===void 0&&(t=""),typeof t=="number"&&(t=String(t)),t===""||t==="any"||isNaN(parseFloat(t))?this.input.step="any":this.input.step=t,this.stepInput&&(t==="any"||t===""?this.stepInput.value="":this.stepInput.value!==t&&(this.stepInput.value=t))}applySignal(t,e){const i=this.max-this.min;let s;if(e==="forward")s=this.min+t*i;else if(e==="backward")s=this.max-t*i;else{const a=t*(i*.01);s=this.value,e==="loopForward"?(s+=a,s>this.max&&(s=this.min+(s-this.min)%i)):e==="loopBackward"?(s-=a,s<this.min&&(s=this.max-(this.max-s)%i)):e==="pingpong"&&(s+=a*this.pingPongDirection,s>=this.max?(s=this.max,this.pingPongDirection=-1):s<=this.min&&(s=this.min,this.pingPongDirection=1))}s=this.roundToStep(s),this.setValue(s),this.input.value=String(s),this.display.textContent=String(s.toFixed(1))}roundToStep(t){const e=this.input.step;if(e==="any"||e===""||isNaN(parseFloat(e)))return t;const i=parseFloat(e),s=this.min;return s+Math.round((t-s)/i)*i}createSetting(t,e,i){const s=o("div",{className:"cp-setting-row"}),a=o("label",{className:"cp-setting-label"},[t]),r=o("input",{type:"number",className:"cp-input-number cp-input-small",step:"any"});return e!==void 0&&(r.value=String(e)),r.addEventListener("input",()=>i(r.value)),s.appendChild(a),s.appendChild(r),{row:s,input:r}}updateDisplay(){this.input.value=String(this.value),this.display.textContent=String(this.value.toFixed(1))}save(){return{value:this.value,settings:{min:this.min,max:this.max,step:this.input.step,signal:this.signalHandler.save()}}}load(t){if(typeof t=="number")this.setValue(t),this.resetSettings();else if(typeof t=="object"&&t!==null&&"value"in t){const e=t.settings||{};e.min!==void 0?this.setMin(e.min):this.setMin(this.initialOptions.min!==void 0?this.initialOptions.min:""),e.max!==void 0?this.setMax(e.max):this.setMax(this.initialOptions.max!==void 0?this.initialOptions.max:""),e.step!==void 0?this.setStep(e.step):this.setStep(this.initialOptions.step);let i=t.value;!isNaN(this.min)&&i<this.min&&(i=this.min),!isNaN(this.max)&&i>this.max&&(i=this.max),this.setValue(i),this.signalHandler?.load(e.signal)}}reset(){this.setValue(this.initialValue),this.resetSettings()}resetSettings(){this.setMin(this.initialOptions.min!==void 0?this.initialOptions.min:""),this.setMax(this.initialOptions.max!==void 0?this.initialOptions.max:""),this.setStep(this.initialOptions.step),this.signalHandler?.reset()}}class z extends m{constructor(t,e,i){super(t,e,i),this.optionValues=[],this.select=o("select",{className:"cp-select"}),this.optionValues=i.options||[],this.optionValues.forEach((s,a)=>{const r=o("option",{value:String(a)},[String(s)]);this.select.appendChild(r)}),this.updateDisplay(),this.select.addEventListener("change",()=>{const s=parseInt(this.select.value),a=this.optionValues[s];this.setValue(a)}),this.appendWidget(this.select)}setOptions(t){this.select.innerHTML="",this.optionValues=t,this.optionValues.forEach((e,i)=>{const s=o("option",{value:String(i)},[String(e)]);this.select.appendChild(s)}),this.updateDisplay(),this.select.value===""&&this.optionValues.length>0&&this.setValue(this.optionValues[0])}updateDisplay(){const t=this.optionValues.indexOf(this.value);t!==-1&&(this.select.value=String(t))}}class U extends m{constructor(t,e,i={}){const s={action:e};super(s,"action",i);const a=i.label??t;this.button=o("button",{className:"cp-button"},[String(a)]),this.button.addEventListener("click",()=>{const r=this.value;typeof r=="function"&&r(),this.emitChange(r)}),this.appendWidget(this.button)}updateDisplay(){}}class $ extends m{constructor(t,e,i={}){super(t,e,i),this.input=o("input",{type:"checkbox",className:"cp-checkbox"}),this.input.checked=this.value,this.input.addEventListener("change",()=>{this.setValue(this.input.checked)}),this.appendWidget(this.input)}updateDisplay(){this.input.checked=this.value}}class q extends m{constructor(t,e,i){super(t,e,i),this.buttons=[],this.optionValues=[],this.container=o("div",{className:"cp-radios"}),this.optionValues=i.options||[],this.optionValues.forEach(s=>{const a=o("button",{className:"cp-button cp-radio"},[String(s)]);a.addEventListener("click",()=>{this.setValue(s)}),this.container.appendChild(a),this.buttons.push(a)}),this.updateDisplay(),this.appendWidget(this.container)}updateDisplay(){const t=this.value;this.buttons.forEach((e,i)=>{this.optionValues[i]===t?e.setAttribute("data-active","true"):e.removeAttribute("data-active")})}}class H extends m{constructor(t,e,i={}){super(t,e,i),this.input=o("input",{type:"color",className:"cp-input-color",value:this.value||"#000000"}),this.appendWidget(this.input),this.input.addEventListener("input",s=>{const a=s.target;this.setValue(a.value)}),this.updateDisplay()}updateDisplay(){this.input.value=this.value}}function _(n){const t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;n=n.replace(t,(i,s,a,r)=>s+s+a+a+r+r);const e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(n);return e?[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]:[0,0,0]}function X(n,t,e){return"#"+((1<<24)+(Math.round(n)<<16)+(Math.round(t)<<8)+Math.round(e)).toString(16).slice(1)}function I(n){const t=n/255;return t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function A(n){return n<=.0031308?n*12.92*255:(1.055*Math.pow(n,1/2.4)-.055)*255}function Y(n,t,e){const[i,s,a]=_(n),[r,l,d]=_(t),h=I(i),v=I(s),p=I(a),c=I(r),f=I(l),g=I(d),S=h+e*(c-h),N=v+e*(f-v),C=p+e*(g-p),b=A(S),y=A(N),w=A(C);return X(b,y,w)}class W extends m{constructor(t,e,i={}){super(t,e,i),this.stops=[],this.pingPongDirection=1,this.animationT=0,this.manualPosition=0,this.initialOptions=i,this.stops=i.stops||[{color:"#000000",position:0},{color:"#ffffff",position:1}],this.sortStops();const s=o("details",{className:"cp-controller-details"}),a=o("summary",{className:"cp-controller-summary"}),r=o("div",{className:"cp-controller-summary-content"});this.displayColor=o("div",{className:"cp-color-swatch",style:`background-color: ${this.value}`}),this.displayText=o("span",{className:"cp-value-display"},[String(this.value)]),r.appendChild(this.displayColor),r.appendChild(this.displayText),a.appendChild(r),s.appendChild(a);const l=o("div",{className:"cp-number-settings"});this.stopsContainer=o("div",{className:"cp-stops-container"}),this.renderStops(),l.appendChild(this.stopsContainer);const d=o("button",{className:"cp-button"},["+ Add Stop"]);d.addEventListener("click",()=>{this.stops.push({color:"#ffffff",position:.5}),this.sortStops(),this.renderStops(),this.updateOutput()}),l.appendChild(d);const h=o("hr",{className:"cp-separator"});l.appendChild(h),this.signalHandler=new R({container:l,onChange:(v,p)=>this.applySignal(v,p)}),s.appendChild(l),this.appendWidget(s),this.updateOutput(0)}sortStops(){this.stops.sort((t,e)=>t.position-e.position)}renderStops(){this.stopsContainer.innerHTML="",this.stops.forEach((t,e)=>{const i=o("div",{className:"cp-setting-row"}),s=o("input",{type:"color",className:"cp-input-color",value:t.color});s.addEventListener("input",l=>{t.color=l.target.value,this.updateOutput()});const a=o("input",{type:"number",className:"cp-input-number cp-input-small",min:"0",max:"1",step:"0.01",value:String(t.position)});a.addEventListener("change",l=>{let d=parseFloat(l.target.value);isNaN(d)&&(d=0),t.position=Math.max(0,Math.min(1,d)),this.sortStops(),this.renderStops(),this.updateOutput()});const r=D(()=>{this.stops.splice(e,1),this.renderStops(),this.updateOutput()});i.appendChild(s),i.appendChild(a),i.appendChild(r),this.stopsContainer.appendChild(i)})}updateOutput(t=this.manualPosition){if(this.stops.length===0)return;if(this.stops.length===1){this.setValue(this.stops[0].color),this.updateDisplay();return}let e="#000000";if(t=Math.max(0,Math.min(1,t)),t<=this.stops[0].position)e=this.stops[0].color;else if(t>=this.stops[this.stops.length-1].position)e=this.stops[this.stops.length-1].color;else for(let i=0;i<this.stops.length-1;i++){const s=this.stops[i],a=this.stops[i+1];if(t>=s.position&&t<=a.position){const r=a.position-s.position,l=r===0?0:(t-s.position)/r;e=Y(s.color,a.color,l);break}}this.setValue(e),this.updateDisplay()}updateDisplay(){this.displayColor&&(this.displayColor.style.backgroundColor=this.value),this.displayText&&(this.displayText.textContent=this.value)}applySignal(t,e){let i=t;if(e==="forward")i=t;else if(e==="backward")i=1-t;else{const s=t*.05;e==="loopForward"?(this.animationT=(this.animationT+s)%1,i=this.animationT):e==="loopBackward"?(this.animationT=(this.animationT-s+1)%1,i=this.animationT):e==="pingpong"&&(this.animationT+=s*this.pingPongDirection,this.animationT>=1?(this.animationT=1,this.pingPongDirection=-1):this.animationT<=0&&(this.animationT=0,this.pingPongDirection=1),i=this.animationT)}this.updateOutput(i),this.manualPosition=i}save(){return{stops:this.stops,settings:{signal:this.signalHandler.save()}}}load(t){t&&t.stops&&(this.stops=t.stops,this.sortStops(),this.renderStops()),t&&t.settings&&this.signalHandler?.load(t.settings.signal)}reset(){this.stops=this.initialOptions.stops||[{color:"#000000",position:0},{color:"#ffffff",position:1}],this.sortStops(),this.renderStops(),this.signalHandler?.reset(),this.updateOutput(0)}}class G extends m{constructor(t,e,i={}){super(t,e,i),this.items=[],this.initialOptions=i,this.itemType=i.itemType||"string",this.items=this.parseValue(this.value);const s=o("details",{className:"cp-controller-details"}),a=o("summary",{className:"cp-controller-summary"}),r=o("div",{className:"cp-controller-summary-content"}),l=o("span",{className:"cp-value-display"},[`${this.items.length} items`]);r.appendChild(l),a.appendChild(r),s.appendChild(a);const d=o("div",{className:"cp-number-settings"});this.itemsContainer=o("div",{className:"cp-stops-container"}),this.renderItems(),d.appendChild(this.itemsContainer);const h=o("button",{className:"cp-button cp-input-small",style:"margin-top: 8px; width: 100%;"},["+ Add Item"]);h.addEventListener("click",()=>{this.addItem()}),d.appendChild(h),s.appendChild(d),this.appendWidget(s)}parseValue(t){return!t||t.trim()===""?[]:t.split(",").map(e=>e.trim())}serializeValue(){return this.items.join(",")}getDefaultItemValue(){switch(this.itemType){case"color":return"#ffffff";case"number":return"0";default:return""}}addItem(t){const e=t!==void 0?t:this.getDefaultItemValue();this.items.push(e),this.renderItems(),this.updateValue()}updateValue(){const t=this.serializeValue();this.setValue(t),this.updateSummary()}updateSummary(){const t=this.domElement.querySelector(".cp-value-display");t&&(t.textContent=`${this.items.length} items`)}renderItems(){this.itemsContainer.innerHTML="",this.items.forEach((t,e)=>{const i=o("div",{className:"cp-setting-row"});let s;this.itemType==="color"?s=o("input",{type:"color",className:"cp-input-color",value:t}):this.itemType==="number"?s=o("input",{type:"number",className:"cp-input-number cp-input-small",step:"any",value:t}):s=o("input",{type:"text",className:"cp-input-number cp-input-small",value:t}),s.addEventListener("input",r=>{this.items[e]=r.target.value,this.updateValue()});const a=D(()=>{this.items.splice(e,1),this.renderItems(),this.updateValue()});i.appendChild(s),i.appendChild(a),this.itemsContainer.appendChild(i)})}updateDisplay(){}save(){return[...this.items]}load(t){Array.isArray(t)?this.items=[...t]:typeof t=="string"&&(this.items=this.parseValue(t)),this.renderItems(),this.updateValue()}reset(){const t=this.initialValue||"";this.items=this.parseValue(t),this.renderItems(),this.updateValue()}}class tt{constructor(){this.frames=0,this.pollingInterval=1e3,this.prevTime=performance.now(),this.render=()=>{this.frames++;const t=performance.now();if(t>=this.prevTime+this.pollingInterval){const e=Math.round(this.frames*1e3/(t-this.prevTime));let i="";const s=performance.memory;s&&(i=` / ${Math.round(s.usedJSHeapSize/1048576)}MB`),this.domElement.textContent=`${e} FPS${i}`,this.prevTime=t,this.frames=0}this.rafId=requestAnimationFrame(this.render)},this.domElement=o("span",{className:"cp-stats"}),this.rafId=requestAnimationFrame(this.render)}destroy(){cancelAnimationFrame(this.rafId)}}class J{constructor(){this.controllers=[],this.folders=[]}addNumber(t,e,i={}){const s=new j(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addSelect(t,e,i={}){const s=new z(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addBoolean(t,e,i={}){const s=new $(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addButton(t,e,i={}){const s=new U(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addRadio(t,e,i={}){const s=new q(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addColor(t,e,i={}){const s=new H(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addGradient(t,e,i={}){const s=new W(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addArray(t,e,i={}){const s=new G(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addFolder(t){const e=new et(t);return this.contentElement.appendChild(e.domElement),this.folders.push(e),e}save(){const t={controllers:{},folders:{}};for(const e of this.controllers)typeof e.value!="function"&&(t.controllers[e.key]=e.save());for(const e of this.folders)t.folders[e.title]=e.save();return t}load(t){if(!t){this.reset();return}for(const e of this.controllers)if(typeof e.value!="function")if(t.controllers&&e.key in t.controllers){const i=t.controllers[e.key];i!==void 0&&e.load(i)}else e.reset();for(const e of this.folders){const i=t.folders?t.folders[e.title]:void 0;e.load(i)}}reset(){for(const t of this.controllers)typeof t.value!="function"&&t.reset();for(const t of this.folders)t.reset()}}class et extends J{constructor(t){super(),this.title=t,this.domElement=o("details",{className:"cp-folder",open:!0}),this.summaryElement=o("summary",{className:"cp-summary"},[t]),this.domElement.appendChild(this.summaryElement),this.contentElement=o("div",{className:"cp-content cp-folder-content"}),this.domElement.appendChild(this.contentElement),this.domElement.appendChild(o("hr",{className:"cp-separator"}))}}class st extends J{constructor(t,e={}){super(),this.domElement=o("details",{className:"cp-root",open:!0}),this.summaryElement=o("summary",{className:"cp-summary cp-summary-root"}),this.domElement.appendChild(this.summaryElement);const i=o("span",{},[e.title||"GUI"]);this.summaryElement.appendChild(i),this.stats=new tt,this.summaryElement.appendChild(this.stats.domElement),this.contentElement=o("div",{className:"cp-content"}),this.domElement.appendChild(this.contentElement);const s=this.addFolder("_Signals"),a={audioInput:null,fftSize:2048};s.addRadio(a,"audioInput",{label:"Audio Signal",options:["microphone","browser"]}).onChange(c=>{x.setInput(c)}),s.addSelect(a,"fftSize",{label:"FFT Size",options:[256,512,1024,2048]}).onChange(c=>{x.setFFTSize(c)}),s.addNumber(x,"smoothingTimeConstant",{min:0,max:.99,step:.01,label:"Smoothing"}).onChange(c=>{x.analyser.smoothingTimeConstant=c}),s.addNumber(x,"spectrumBoost",{min:1,max:5,step:.1,label:"Compression"}),t?t.appendChild(this.domElement):document.body.appendChild(this.domElement);const r=e.title||"GUI";this.presetStoragePrefix=`cp-presets-${r}-`;const l=this.addFolder("_User Presets"),d=()=>{const c=["Default"];if(typeof localStorage>"u")return c;for(let f=0;f<localStorage.length;f++){const g=localStorage.key(f);if(g&&g.startsWith(this.presetStoragePrefix)){const S=g.substring(this.presetStoragePrefix.length);S!=="Default"&&!c.includes(S)&&c.push(S)}}return c.sort()},h={selected:"Default",save:()=>{const c=prompt("Preset Name:",h.selected);if(c){if(c==="Default"){alert("Cannot overwrite Default preset");return}const f=this.presetStoragePrefix+c;this.saveToLocalStorage(f);const g=d();p.setOptions(g),h.selected=c,p.setValue(c)}},load:()=>{const c=h.selected,f=this.presetStoragePrefix+c;this.loadFromLocalStorage(f),h.selected=c,p.setValue(c)},delete:()=>{if(h.selected==="Default"){alert("Cannot delete Default preset");return}if(confirm(`Delete preset "${h.selected}"?`)){const c=this.presetStoragePrefix+h.selected;localStorage.removeItem(c);const f=d();p.setOptions(f),h.selected="Default",p.setValue("Default"),this.reset()}},export:()=>{const c=this.save(),f=L=>{const E={controllers:{},folders:{}};for(const[T,V]of Object.entries(L.controllers))T.startsWith("_")||(E.controllers[T]=V);for(const[T,V]of Object.entries(L.folders))T.startsWith("_")||(E.folders[T]=f(V));return E},g=f(c),S={_presetName:h.selected||"CustomPreset",_exportDate:new Date().toISOString(),_instructions:"To add as factory preset: Copy 'controllers' and 'folders' fields into the presets.json file",...g},N=JSON.stringify(S,null,2),C=new Blob([N],{type:"application/json"}),b=URL.createObjectURL(C),y=document.createElement("a");y.href=b;const w=new Date().toISOString().split("T")[0],Z=h.selected.replace(/[^a-z0-9]/gi,"-").toLowerCase();y.download=`${r.toLowerCase()}-preset-${Z}-${w}.json`,document.body.appendChild(y),y.click(),document.body.removeChild(y),URL.revokeObjectURL(b)},import:()=>{const c=document.createElement("input");c.type="file",c.accept=".json",c.onchange=f=>{const g=f.target.files?.[0];if(!g)return;const S=new FileReader;S.onload=N=>{try{const C=N.target?.result,b=JSON.parse(C),y={controllers:b.controllers||{},folders:b.folders||{}};if(!y.controllers||!y.folders){alert("Invalid preset file: missing 'controllers' or 'folders'");return}this.load(y);const w=b._presetName||"ImportedPreset";if(confirm(`Preset loaded! Save as "${w}" to User Presets?`)){const L=this.presetStoragePrefix+w;this.saveToLocalStorage(L);const E=d();p.setOptions(E),h.selected=w,p.setValue(w)}}catch(C){alert(`Failed to import preset: ${C instanceof Error?C.message:"Invalid JSON"}`),console.error("Import error:",C)}},S.readAsText(g)},c.click()}},v=d(),p=l.addSelect(h,"selected",{label:"Preset",options:v});l.addButton("Load",()=>h.load()),l.addButton("Save / New",()=>h.save()),l.addButton("Delete",()=>h.delete()),l.addButton("Export JSON",()=>h.export()),l.addButton("Import JSON",()=>h.import())}saveToLocalStorage(t){const e=this.save();try{localStorage.setItem(t,JSON.stringify(e))}catch(i){console.warn("GUI: Failed to save to localStorage",i)}}loadFromLocalStorage(t){try{const e=localStorage.getItem(t);if(e){const i=JSON.parse(e);this.load(i)}}catch(e){console.warn("GUI: Failed to load from localStorage",e)}}saveDefaultPreset(){const t=this.presetStoragePrefix+"Default";this.save(),this.saveToLocalStorage(t)}destroy(){this.stats.destroy(),this.domElement.remove(),this.controllers=[],this.folders=[]}}u.ArrayController=G,u.AudioSignals=B,u.BooleanController=$,u.ButtonController=U,u.ColorController=H,u.Controller=m,u.GUI=st,u.GradientController=W,u.MidiSignals=F,u.NumberController=j,u.RadioController=q,u.SelectController=z,u.audioSignals=x,u.midiSignals=k,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@digitalmeadow/control-panel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A minimalist, framework-agnostic control panel GUI",
|
|
6
|
+
"author": "Digital Meadow <inbox@digitalmeadow.studio> (https://digitalmeadow.studio)",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"control panel",
|
|
9
|
+
"gui"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/digitalmeadow/control-panel.git"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"main": "./dist/control-panel.umd.cjs",
|
|
20
|
+
"module": "./dist/control-panel.js",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./dist/control-panel.js",
|
|
24
|
+
"require": "./dist/control-panel.umd.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "vite",
|
|
29
|
+
"build": "vite build",
|
|
30
|
+
"preview": "vite preview"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^25.2.3",
|
|
34
|
+
"typescript": "5.9.3",
|
|
35
|
+
"vite": "7.3.1"
|
|
36
|
+
}
|
|
37
|
+
}
|