@digitalmeadow/control-panel 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -1,6 +1,12 @@
1
- # Control Panel
1
+ # @digitalmeadow/control-panel
2
2
 
3
- GUI control panel for creative coding.
3
+ ControlPanel control panel for creative coding.
4
+
5
+ ## Features
6
+
7
+ - Dependency free
8
+ - Minimal API
9
+ - Audio and MIDI input signals
4
10
 
5
11
  ## Controllers
6
12
 
@@ -16,23 +22,23 @@ GUI control panel for creative coding.
16
22
  ## Usage
17
23
 
18
24
  ```typescript
19
- import { GUI } from "./src/gui";
25
+ import { ControlPanel } from "./src/controlPanel";
20
26
 
21
27
  const state = { number: 50, color: "#3498db", enabled: true };
22
- const gui = new GUI();
28
+ const controlPanel = new ControlPanel();
23
29
 
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());
30
+ controlPanel.addNumber(state, "number", { min: 0, max: 100, step: 1 });
31
+ controlPanel.addColor(state, "color");
32
+ controlPanel.addBoolean(state, "enabled");
33
+ controlPanel.addButton("Reset", () => controlPanel.reset());
28
34
 
29
- gui.saveDefaultPreset();
35
+ controlPanel.saveDefaultPreset();
30
36
  ```
31
37
 
32
38
  ## Folders
33
39
 
34
40
  ```typescript
35
- const folder = gui.addFolder("Settings");
41
+ const folder = controlPanel.addFolder("Settings");
36
42
  folder.addNumber(state, "value", { min: 0, max: 100 });
37
43
 
38
44
  const nested = folder.addFolder("Advanced");
@@ -49,20 +55,20 @@ pnpm build
49
55
  ## API
50
56
 
51
57
  ```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()
58
+ controlPanel.addNumber(object, property, options?)
59
+ controlPanel.addSelect(object, property, options?)
60
+ controlPanel.addBoolean(object, property, options?)
61
+ controlPanel.addButton(label, fn, options?)
62
+ controlPanel.addRadio(object, property, options?)
63
+ controlPanel.addColor(object, property, options?)
64
+ controlPanel.addGradient(object, property, options?)
65
+ controlPanel.addArray(object, property, options?)
66
+ controlPanel.addFolder(title)
67
+
68
+ controlPanel.save()
69
+ controlPanel.load(state)
70
+ controlPanel.reset()
71
+ controlPanel.saveDefaultPreset()
66
72
  ```
67
73
 
68
74
  ## Options
@@ -34,8 +34,8 @@ function j(n, t, e) {
34
34
  return e[e.length - 1];
35
35
  if (i === 0 && n < t[0])
36
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;
37
+ const s = t[i], a = t[i + 1], o = e[i], h = e[i + 1];
38
+ return (n - s) / (a - s) * (h - o) + o;
39
39
  }
40
40
  class z {
41
41
  constructor() {
@@ -75,14 +75,14 @@ class z {
75
75
  }
76
76
  update() {
77
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);
78
+ const p = this.dataArray.length;
79
+ for (let c = 0; c < p; c++) {
80
+ const u = 1 + c / p * (this.spectrumBoost - 1);
81
+ this.dataArray[c] = Math.min(255, this.dataArray[c] * u);
82
82
  }
83
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);
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]), h = this.getAverage(0, i[1]);
85
+ this.processLevel("bass", s), this.processLevel("mids", a), this.processLevel("highs", o), this.processLevel("volume", h);
86
86
  }
87
87
  processLevel(t, e) {
88
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(
@@ -131,8 +131,8 @@ class $ {
131
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
132
  }
133
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}`;
134
+ const e = t.data, [i, s] = e, a = i & 240, o = t.currentTarget.name || "unknown", h = a === 144 || a === 128 ? "note" : "ctrl", p = o.replace(/[^a-zA-Z0-9]/g, "");
135
+ return `${s}_${h}_${p}`;
136
136
  }
137
137
  normalizeValue(t) {
138
138
  const [e, i, s] = t, a = e & 240;
@@ -150,7 +150,7 @@ class $ {
150
150
  return () => this.values.get(t) ?? 0;
151
151
  }
152
152
  }
153
- const U = new $(), M = class M {
153
+ const q = new $(), M = class M {
154
154
  constructor(t, e, i = {}) {
155
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
156
  const s = i.label ?? R(e), a = r("label", { className: "cp-label" }, [
@@ -184,7 +184,7 @@ const U = new $(), M = class M {
184
184
  this.domElement.appendChild(t);
185
185
  }
186
186
  };
187
- M.audio = x, M.midi = U;
187
+ M.audio = x, M.midi = q;
188
188
  let m = M;
189
189
  const O = {
190
190
  linear: (n) => n,
@@ -201,7 +201,7 @@ const O = {
201
201
  sineOut: (n) => Math.sin(n * Math.PI / 2),
202
202
  sineInOut: (n) => -(Math.cos(Math.PI * n) - 1) / 2
203
203
  };
204
- class k {
204
+ class P {
205
205
  constructor(t) {
206
206
  this.rafId = null, this.currentSignalType = null, this.currentMidiId = null, this.currentEase = "linear", this.currentBehaviour = "forward", this.loop = () => {
207
207
  if (this.currentSignalType) {
@@ -259,9 +259,9 @@ class k {
259
259
  ]), o = r("select", {
260
260
  className: "cp-select cp-input-small"
261
261
  });
262
- return e.forEach((l) => {
263
- const d = r("option", { value: l }, [l]);
264
- o.appendChild(d);
262
+ return e.forEach((h) => {
263
+ const p = r("option", { value: h }, [h]);
264
+ o.appendChild(p);
265
265
  }), o.addEventListener("change", () => i(o.value)), s.appendChild(a), s.appendChild(o), { row: s, select: o };
266
266
  }
267
267
  setSignalType(t) {
@@ -303,7 +303,7 @@ class k {
303
303
  this.setSignalType("none"), this.setEase("linear"), this.setBehaviour("forward"), this.setMidiId(null);
304
304
  }
305
305
  }
306
- class q extends m {
306
+ class H extends m {
307
307
  constructor(t, e, i = {}) {
308
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
309
  const s = r("details", {
@@ -322,36 +322,38 @@ class q extends m {
322
322
  },
323
323
  [String(this.value.toFixed(1))]
324
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();
325
+ const l = parseFloat(this.input.value);
326
+ isNaN(l) || (this.setValue(l), this.display.textContent = String(l.toFixed(1)));
327
+ }), this.input.addEventListener("click", (l) => {
328
+ l.stopPropagation();
329
329
  });
330
330
  const o = r("div", {
331
331
  className: "cp-controller-summary-content"
332
332
  });
333
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(
334
+ const h = r("div", { className: "cp-number-settings" }), p = this.createSetting(
335
335
  "min",
336
336
  i.min,
337
- (p) => this.setMin(p)
337
+ (l) => this.setMin(l)
338
338
  );
339
- this.minInput = d.input, l.appendChild(d.row);
340
- const h = this.createSetting(
339
+ this.minInput = p.input, h.appendChild(p.row);
340
+ const c = this.createSetting(
341
341
  "max",
342
342
  i.max,
343
- (p) => this.setMax(p)
343
+ (l) => this.setMax(l)
344
344
  );
345
- this.maxInput = h.input, l.appendChild(h.row);
345
+ this.maxInput = c.input, h.appendChild(c.row);
346
346
  const y = this.createSetting(
347
347
  "step",
348
348
  i.step,
349
- (p) => this.setStep(p)
349
+ (l) => this.setStep(l)
350
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);
351
+ this.stepInput = y.input, h.appendChild(y.row);
352
+ const u = r("hr", { className: "cp-separator" });
353
+ h.appendChild(u), this.signalHandler = new P({
354
+ container: h,
355
+ onChange: (l, d) => this.applySignal(l, d)
356
+ }), s.appendChild(h), this.appendWidget(s);
355
357
  }
356
358
  // Setters
357
359
  setMin(t) {
@@ -432,7 +434,7 @@ class q extends m {
432
434
  ), this.setStep(this.initialOptions.step), this.signalHandler?.reset();
433
435
  }
434
436
  }
435
- class H extends m {
437
+ class U extends m {
436
438
  constructor(t, e, i) {
437
439
  super(t, e, i), this.optionValues = [], this.select = r("select", { className: "cp-select" }), this.optionValues = i.options || [], this.optionValues.forEach((s, a) => {
438
440
  const o = r("option", { value: String(a) }, [
@@ -485,7 +487,7 @@ class W extends m {
485
487
  this.input.checked = this.value;
486
488
  }
487
489
  }
488
- class G extends m {
490
+ class J extends m {
489
491
  constructor(t, e, i) {
490
492
  super(t, e, i), this.buttons = [], this.optionValues = [], this.container = r("div", { className: "cp-radios" }), this.optionValues = i.options || [], this.optionValues.forEach((s) => {
491
493
  const a = r("button", { className: "cp-button cp-radio" }, [
@@ -503,7 +505,7 @@ class G extends m {
503
505
  });
504
506
  }
505
507
  }
506
- class J extends m {
508
+ class G extends m {
507
509
  constructor(t, e, i = {}) {
508
510
  super(t, e, i), this.input = r("input", {
509
511
  type: "color",
@@ -539,7 +541,7 @@ function A(n) {
539
541
  return n <= 31308e-7 ? n * 12.92 * 255 : (1.055 * Math.pow(n, 1 / 2.4) - 0.055) * 255;
540
542
  }
541
543
  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);
544
+ const [i, s, a] = F(n), [o, h, p] = F(t), c = w(i), y = w(s), u = w(a), l = w(o), d = w(h), f = w(p), v = c + e * (l - c), I = y + e * (d - y), S = u + e * (f - u), C = A(v), g = A(I), b = A(S);
543
545
  return Z(C, g, b);
544
546
  }
545
547
  class Q extends m {
@@ -563,25 +565,25 @@ class Q extends m {
563
565
  { className: "cp-value-display" },
564
566
  [String(this.value)]
565
567
  ), o.appendChild(this.displayColor), o.appendChild(this.displayText), a.appendChild(o), s.appendChild(a);
566
- const l = r("div", { className: "cp-number-settings" });
568
+ const h = r("div", { className: "cp-number-settings" });
567
569
  this.stopsContainer = r("div", {
568
570
  className: "cp-stops-container"
569
- }), this.renderStops(), l.appendChild(this.stopsContainer);
570
- const d = r(
571
+ }), this.renderStops(), h.appendChild(this.stopsContainer);
572
+ const p = r(
571
573
  "button",
572
574
  {
573
575
  className: "cp-button"
574
576
  },
575
577
  ["+ Add Stop"]
576
578
  );
577
- d.addEventListener("click", () => {
579
+ p.addEventListener("click", () => {
578
580
  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);
581
+ }), h.appendChild(p);
582
+ const c = r("hr", { className: "cp-separator" });
583
+ h.appendChild(c), this.signalHandler = new P({
584
+ container: h,
585
+ onChange: (y, u) => this.applySignal(y, u)
586
+ }), s.appendChild(h), this.appendWidget(s), this.updateOutput(0);
585
587
  }
586
588
  sortStops() {
587
589
  this.stops.sort((t, e) => t.position - e.position);
@@ -593,8 +595,8 @@ class Q extends m {
593
595
  className: "cp-input-color",
594
596
  value: t.color
595
597
  });
596
- s.addEventListener("input", (l) => {
597
- t.color = l.target.value, this.updateOutput();
598
+ s.addEventListener("input", (h) => {
599
+ t.color = h.target.value, this.updateOutput();
598
600
  });
599
601
  const a = r("input", {
600
602
  type: "number",
@@ -604,9 +606,9 @@ class Q extends m {
604
606
  step: "0.01",
605
607
  value: String(t.position)
606
608
  });
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();
609
+ a.addEventListener("change", (h) => {
610
+ let p = parseFloat(h.target.value);
611
+ isNaN(p) && (p = 0), t.position = Math.max(0, Math.min(1, p)), this.sortStops(), this.renderStops(), this.updateOutput();
610
612
  });
611
613
  const o = B(() => {
612
614
  this.stops.splice(e, 1), this.renderStops(), this.updateOutput();
@@ -630,8 +632,8 @@ class Q extends m {
630
632
  for (let i = 0; i < this.stops.length - 1; i++) {
631
633
  const s = this.stops[i], a = this.stops[i + 1];
632
634
  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
+ const o = a.position - s.position, h = o === 0 ? 0 : (t - s.position) / o;
636
+ e = K(s.color, a.color, h);
635
637
  break;
636
638
  }
637
639
  }
@@ -679,15 +681,15 @@ class X extends m {
679
681
  className: "cp-controller-summary"
680
682
  }), o = r("div", {
681
683
  className: "cp-controller-summary-content"
682
- }), l = r("span", { className: "cp-value-display" }, [
684
+ }), h = r("span", { className: "cp-value-display" }, [
683
685
  `${this.items.length} items`
684
686
  ]);
685
- o.appendChild(l), a.appendChild(o), s.appendChild(a);
686
- const d = r("div", { className: "cp-number-settings" });
687
+ o.appendChild(h), a.appendChild(o), s.appendChild(a);
688
+ const p = r("div", { className: "cp-number-settings" });
687
689
  this.itemsContainer = r("div", {
688
690
  className: "cp-stops-container"
689
- }), this.renderItems(), d.appendChild(this.itemsContainer);
690
- const h = r(
691
+ }), this.renderItems(), p.appendChild(this.itemsContainer);
692
+ const c = r(
691
693
  "button",
692
694
  {
693
695
  className: "cp-button cp-input-small",
@@ -695,9 +697,9 @@ class X extends m {
695
697
  },
696
698
  ["+ Add Item"]
697
699
  );
698
- h.addEventListener("click", () => {
700
+ c.addEventListener("click", () => {
699
701
  this.addItem();
700
- }), d.appendChild(h), s.appendChild(d), this.appendWidget(s);
702
+ }), p.appendChild(c), s.appendChild(p), this.appendWidget(s);
701
703
  }
702
704
  parseValue(t) {
703
705
  return !t || t.trim() === "" ? [] : t.split(",").map((e) => e.trim());
@@ -784,16 +786,16 @@ class Y {
784
786
  cancelAnimationFrame(this.rafId);
785
787
  }
786
788
  }
787
- class P {
789
+ class k {
788
790
  constructor() {
789
791
  this.controllers = [], this.folders = [];
790
792
  }
791
793
  addNumber(t, e, i = {}) {
792
- const s = new q(t, e, i);
794
+ const s = new H(t, e, i);
793
795
  return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
794
796
  }
795
797
  addSelect(t, e, i = {}) {
796
- const s = new H(t, e, i);
798
+ const s = new U(t, e, i);
797
799
  return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
798
800
  }
799
801
  addBoolean(t, e, i = {}) {
@@ -805,11 +807,11 @@ class P {
805
807
  return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
806
808
  }
807
809
  addRadio(t, e, i = {}) {
808
- const s = new G(t, e, i);
810
+ const s = new J(t, e, i);
809
811
  return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
810
812
  }
811
813
  addColor(t, e, i = {}) {
812
- const s = new J(t, e, i);
814
+ const s = new G(t, e, i);
813
815
  return this.contentElement.appendChild(s.domElement), this.controllers.push(s), s;
814
816
  }
815
817
  addGradient(t, e, i = {}) {
@@ -859,7 +861,7 @@ class P {
859
861
  t.reset();
860
862
  }
861
863
  }
862
- class tt extends P {
864
+ class tt extends k {
863
865
  constructor(t) {
864
866
  super(), this.title = t, this.domElement = r("details", {
865
867
  className: "cp-folder",
@@ -877,7 +879,7 @@ class tt extends P {
877
879
  );
878
880
  }
879
881
  }
880
- class et extends P {
882
+ class et extends k {
881
883
  constructor(t, e = {}) {
882
884
  super(), this.domElement = r("details", {
883
885
  className: "cp-root",
@@ -885,7 +887,9 @@ class et extends P {
885
887
  }), this.summaryElement = r("summary", {
886
888
  className: "cp-summary cp-summary-root"
887
889
  }), this.domElement.appendChild(this.summaryElement);
888
- const i = r("span", {}, [e.title || "GUI"]);
890
+ const i = r("span", {}, [
891
+ e.title || "ControlPanel"
892
+ ]);
889
893
  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
894
  const s = this.addFolder("_Signals"), a = {
891
895
  audioInput: null,
@@ -894,72 +898,72 @@ class et extends P {
894
898
  s.addRadio(a, "audioInput", {
895
899
  label: "Audio Signal",
896
900
  options: ["microphone", "browser"]
897
- }).onChange((c) => {
898
- x.setInput(c);
901
+ }).onChange((l) => {
902
+ x.setInput(l);
899
903
  }), s.addSelect(a, "fftSize", {
900
904
  label: "FFT Size",
901
905
  options: [256, 512, 1024, 2048]
902
- }).onChange((c) => {
903
- x.setFFTSize(c);
906
+ }).onChange((l) => {
907
+ x.setFFTSize(l);
904
908
  }), s.addNumber(x, "smoothingTimeConstant", {
905
909
  min: 0,
906
910
  max: 0.99,
907
911
  step: 0.01,
908
912
  label: "Smoothing"
909
- }).onChange((c) => {
910
- x.analyser.smoothingTimeConstant = c;
913
+ }).onChange((l) => {
914
+ x.analyser.smoothingTimeConstant = l;
911
915
  }), s.addNumber(x, "spectrumBoost", {
912
916
  min: 1,
913
917
  max: 5,
914
918
  step: 0.1,
915
919
  label: "Compression"
916
920
  }), t ? t.appendChild(this.domElement) : document.body.appendChild(this.domElement);
917
- const o = e.title || "GUI";
921
+ const o = e.title || "ControlPanel";
918
922
  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);
923
+ const h = this.addFolder("_User Presets"), p = () => {
924
+ const l = ["Default"];
925
+ if (typeof localStorage > "u") return l;
926
+ for (let d = 0; d < localStorage.length; d++) {
927
+ const f = localStorage.key(d);
924
928
  if (f && f.startsWith(this.presetStoragePrefix)) {
925
929
  const v = f.substring(this.presetStoragePrefix.length);
926
- v !== "Default" && !c.includes(v) && c.push(v);
930
+ v !== "Default" && !l.includes(v) && l.push(v);
927
931
  }
928
932
  }
929
- return c.sort();
930
- }, h = {
933
+ return l.sort();
934
+ }, c = {
931
935
  selected: "Default",
932
936
  save: () => {
933
- const c = prompt("Preset Name:", h.selected);
934
- if (c) {
935
- if (c === "Default") {
937
+ const l = prompt("Preset Name:", c.selected);
938
+ if (l) {
939
+ if (l === "Default") {
936
940
  alert("Cannot overwrite Default preset");
937
941
  return;
938
942
  }
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
+ const d = this.presetStoragePrefix + l;
944
+ this.saveToLocalStorage(d);
945
+ const f = p();
946
+ u.setOptions(f), c.selected = l, u.setValue(l);
943
947
  }
944
948
  },
945
949
  load: () => {
946
- const c = h.selected, u = this.presetStoragePrefix + c;
947
- this.loadFromLocalStorage(u), h.selected = c, p.setValue(c);
950
+ const l = c.selected, d = this.presetStoragePrefix + l;
951
+ this.loadFromLocalStorage(d), c.selected = l, u.setValue(l);
948
952
  },
949
953
  delete: () => {
950
- if (h.selected === "Default") {
954
+ if (c.selected === "Default") {
951
955
  alert("Cannot delete Default preset");
952
956
  return;
953
957
  }
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();
958
+ if (confirm(`Delete preset "${c.selected}"?`)) {
959
+ const l = this.presetStoragePrefix + c.selected;
960
+ localStorage.removeItem(l);
961
+ const d = p();
962
+ u.setOptions(d), c.selected = "Default", u.setValue("Default"), this.reset();
959
963
  }
960
964
  },
961
965
  export: () => {
962
- const c = this.save(), u = (T) => {
966
+ const l = this.save(), d = (T) => {
963
967
  const N = {
964
968
  controllers: {},
965
969
  folders: {}
@@ -969,22 +973,22 @@ class et extends P {
969
973
  for (const [E, L] of Object.entries(
970
974
  T.folders
971
975
  ))
972
- E.startsWith("_") || (N.folders[E] = u(L));
976
+ E.startsWith("_") || (N.folders[E] = d(L));
973
977
  return N;
974
- }, f = u(c), v = {
975
- _presetName: h.selected || "CustomPreset",
978
+ }, f = d(l), v = {
979
+ _presetName: c.selected || "CustomPreset",
976
980
  _exportDate: (/* @__PURE__ */ new Date()).toISOString(),
977
981
  _instructions: "To add as factory preset: Copy 'controllers' and 'folders' fields into the presets.json file",
978
982
  ...f
979
983
  }, I = JSON.stringify(v, null, 2), S = new Blob([I], { type: "application/json" }), C = URL.createObjectURL(S), g = document.createElement("a");
980
984
  g.href = C;
981
- const b = (/* @__PURE__ */ new Date()).toISOString().split("T")[0], V = h.selected.replace(/[^a-z0-9]/gi, "-").toLowerCase();
985
+ const b = (/* @__PURE__ */ new Date()).toISOString().split("T")[0], V = c.selected.replace(/[^a-z0-9]/gi, "-").toLowerCase();
982
986
  g.download = `${o.toLowerCase()}-preset-${V}-${b}.json`, document.body.appendChild(g), g.click(), document.body.removeChild(g), URL.revokeObjectURL(C);
983
987
  },
984
988
  import: () => {
985
- const c = document.createElement("input");
986
- c.type = "file", c.accept = ".json", c.onchange = (u) => {
987
- const f = u.target.files?.[0];
989
+ const l = document.createElement("input");
990
+ l.type = "file", l.accept = ".json", l.onchange = (d) => {
991
+ const f = d.target.files?.[0];
988
992
  if (!f) return;
989
993
  const v = new FileReader();
990
994
  v.onload = (I) => {
@@ -1006,8 +1010,8 @@ class et extends P {
1006
1010
  )) {
1007
1011
  const T = this.presetStoragePrefix + b;
1008
1012
  this.saveToLocalStorage(T);
1009
- const N = d();
1010
- p.setOptions(N), h.selected = b, p.setValue(b);
1013
+ const N = p();
1014
+ u.setOptions(N), c.selected = b, u.setValue(b);
1011
1015
  }
1012
1016
  } catch (S) {
1013
1017
  alert(
@@ -1015,20 +1019,20 @@ class et extends P {
1015
1019
  ), console.error("Import error:", S);
1016
1020
  }
1017
1021
  }, v.readAsText(f);
1018
- }, c.click();
1022
+ }, l.click();
1019
1023
  }
1020
- }, y = d(), p = l.addSelect(h, "selected", {
1024
+ }, y = p(), u = h.addSelect(c, "selected", {
1021
1025
  label: "Preset",
1022
1026
  options: y
1023
1027
  });
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());
1028
+ h.addButton("Load", () => c.load()), h.addButton("Save / New", () => c.save()), h.addButton("Delete", () => c.delete()), h.addButton("Export JSON", () => c.export()), h.addButton("Import JSON", () => c.import());
1025
1029
  }
1026
1030
  saveToLocalStorage(t) {
1027
1031
  const e = this.save();
1028
1032
  try {
1029
1033
  localStorage.setItem(t, JSON.stringify(e));
1030
1034
  } catch (i) {
1031
- console.warn("GUI: Failed to save to localStorage", i);
1035
+ console.warn("ControlPanel: Failed to save to localStorage", i);
1032
1036
  }
1033
1037
  }
1034
1038
  loadFromLocalStorage(t) {
@@ -1039,7 +1043,7 @@ class et extends P {
1039
1043
  this.load(i);
1040
1044
  }
1041
1045
  } catch (e) {
1042
- console.warn("GUI: Failed to load from localStorage", e);
1046
+ console.warn("ControlPanel: Failed to load from localStorage", e);
1043
1047
  }
1044
1048
  }
1045
1049
  saveDefaultPreset() {
@@ -1055,14 +1059,14 @@ export {
1055
1059
  z as AudioSignals,
1056
1060
  W as BooleanController,
1057
1061
  _ as ButtonController,
1058
- J as ColorController,
1062
+ G as ColorController,
1063
+ et as ControlPanel,
1059
1064
  m as Controller,
1060
- et as GUI,
1061
1065
  Q as GradientController,
1062
1066
  $ as MidiSignals,
1063
- q as NumberController,
1064
- G as RadioController,
1065
- H as SelectController,
1067
+ H as NumberController,
1068
+ J as RadioController,
1069
+ U as SelectController,
1066
1070
  x as audioSignals,
1067
- U as midiSignals
1071
+ q as midiSignals
1068
1072
  };
@@ -1 +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"})}));
1
+ (function(d,o){typeof exports=="object"&&typeof module<"u"?o(exports):typeof define=="function"&&define.amd?define(["exports"],o):(d=typeof globalThis<"u"?globalThis:d||self,o(d.ControlPanel={}))})(this,(function(d){"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],h=e[i+1];return(n-s)/(a-s)*(h-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 p=this.dataArray.length;for(let c=0;c<p;c++){const f=1+c/p*(this.spectrumBoost-1);this.dataArray[c]=Math.min(255,this.dataArray[c]*f)}}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]),h=this.getAverage(0,i[1]);this.processLevel("bass",s),this.processLevel("mids",a),this.processLevel("highs",r),this.processLevel("volume",h)}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",h=a===144||a===128?"note":"ctrl",p=r.replace(/[^a-zA-Z0-9]/g,"");return`${s}_${h}_${p}`}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 P=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=P;let m=M;const k={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=k[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(k),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(h=>{const p=o("option",{value:h},[h]);r.appendChild(p)}),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 l=parseFloat(this.input.value);isNaN(l)||(this.setValue(l),this.display.textContent=String(l.toFixed(1)))}),this.input.addEventListener("click",l=>{l.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 h=o("div",{className:"cp-number-settings"}),p=this.createSetting("min",i.min,l=>this.setMin(l));this.minInput=p.input,h.appendChild(p.row);const c=this.createSetting("max",i.max,l=>this.setMax(l));this.maxInput=c.input,h.appendChild(c.row);const v=this.createSetting("step",i.step,l=>this.setStep(l));this.stepInput=v.input,h.appendChild(v.row);const f=o("hr",{className:"cp-separator"});h.appendChild(f),this.signalHandler=new R({container:h,onChange:(l,u)=>this.applySignal(l,u)}),s.appendChild(h),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 $ 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 q 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 H 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 U 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,h,p]=_(t),c=I(i),v=I(s),f=I(a),l=I(r),u=I(h),g=I(p),S=c+e*(l-c),N=v+e*(u-v),C=f+e*(g-f),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 h=o("div",{className:"cp-number-settings"});this.stopsContainer=o("div",{className:"cp-stops-container"}),this.renderStops(),h.appendChild(this.stopsContainer);const p=o("button",{className:"cp-button"},["+ Add Stop"]);p.addEventListener("click",()=>{this.stops.push({color:"#ffffff",position:.5}),this.sortStops(),this.renderStops(),this.updateOutput()}),h.appendChild(p);const c=o("hr",{className:"cp-separator"});h.appendChild(c),this.signalHandler=new R({container:h,onChange:(v,f)=>this.applySignal(v,f)}),s.appendChild(h),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",h=>{t.color=h.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",h=>{let p=parseFloat(h.target.value);isNaN(p)&&(p=0),t.position=Math.max(0,Math.min(1,p)),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,h=r===0?0:(t-s.position)/r;e=Y(s.color,a.color,h);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 J 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"}),h=o("span",{className:"cp-value-display"},[`${this.items.length} items`]);r.appendChild(h),a.appendChild(r),s.appendChild(a);const p=o("div",{className:"cp-number-settings"});this.itemsContainer=o("div",{className:"cp-stops-container"}),this.renderItems(),p.appendChild(this.itemsContainer);const c=o("button",{className:"cp-button cp-input-small",style:"margin-top: 8px; width: 100%;"},["+ Add Item"]);c.addEventListener("click",()=>{this.addItem()}),p.appendChild(c),s.appendChild(p),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 G{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 q(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addButton(t,e,i={}){const s=new $(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addRadio(t,e,i={}){const s=new H(t,e,i);return this.contentElement.appendChild(s.domElement),this.controllers.push(s),s}addColor(t,e,i={}){const s=new U(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 J(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 G{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 G{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||"ControlPanel"]);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(l=>{x.setInput(l)}),s.addSelect(a,"fftSize",{label:"FFT Size",options:[256,512,1024,2048]}).onChange(l=>{x.setFFTSize(l)}),s.addNumber(x,"smoothingTimeConstant",{min:0,max:.99,step:.01,label:"Smoothing"}).onChange(l=>{x.analyser.smoothingTimeConstant=l}),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||"ControlPanel";this.presetStoragePrefix=`cp-presets-${r}-`;const h=this.addFolder("_User Presets"),p=()=>{const l=["Default"];if(typeof localStorage>"u")return l;for(let u=0;u<localStorage.length;u++){const g=localStorage.key(u);if(g&&g.startsWith(this.presetStoragePrefix)){const S=g.substring(this.presetStoragePrefix.length);S!=="Default"&&!l.includes(S)&&l.push(S)}}return l.sort()},c={selected:"Default",save:()=>{const l=prompt("Preset Name:",c.selected);if(l){if(l==="Default"){alert("Cannot overwrite Default preset");return}const u=this.presetStoragePrefix+l;this.saveToLocalStorage(u);const g=p();f.setOptions(g),c.selected=l,f.setValue(l)}},load:()=>{const l=c.selected,u=this.presetStoragePrefix+l;this.loadFromLocalStorage(u),c.selected=l,f.setValue(l)},delete:()=>{if(c.selected==="Default"){alert("Cannot delete Default preset");return}if(confirm(`Delete preset "${c.selected}"?`)){const l=this.presetStoragePrefix+c.selected;localStorage.removeItem(l);const u=p();f.setOptions(u),c.selected="Default",f.setValue("Default"),this.reset()}},export:()=>{const l=this.save(),u=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]=u(V));return E},g=u(l),S={_presetName:c.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=c.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 l=document.createElement("input");l.type="file",l.accept=".json",l.onchange=u=>{const g=u.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=p();f.setOptions(E),c.selected=w,f.setValue(w)}}catch(C){alert(`Failed to import preset: ${C instanceof Error?C.message:"Invalid JSON"}`),console.error("Import error:",C)}},S.readAsText(g)},l.click()}},v=p(),f=h.addSelect(c,"selected",{label:"Preset",options:v});h.addButton("Load",()=>c.load()),h.addButton("Save / New",()=>c.save()),h.addButton("Delete",()=>c.delete()),h.addButton("Export JSON",()=>c.export()),h.addButton("Import JSON",()=>c.import())}saveToLocalStorage(t){const e=this.save();try{localStorage.setItem(t,JSON.stringify(e))}catch(i){console.warn("ControlPanel: 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("ControlPanel: 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=[]}}d.ArrayController=J,d.AudioSignals=B,d.BooleanController=q,d.ButtonController=$,d.ColorController=U,d.ControlPanel=st,d.Controller=m,d.GradientController=W,d.MidiSignals=F,d.NumberController=j,d.RadioController=H,d.SelectController=z,d.audioSignals=x,d.midiSignals=P,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitalmeadow/control-panel",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "description": "A minimalist, framework-agnostic control panel GUI",
6
6
  "author": "Digital Meadow <inbox@digitalmeadow.studio> (https://digitalmeadow.studio)",
@@ -18,8 +18,10 @@
18
18
  ],
19
19
  "main": "./dist/control-panel.umd.cjs",
20
20
  "module": "./dist/control-panel.js",
21
+ "types": "./dist/index.d.ts",
21
22
  "exports": {
22
23
  ".": {
24
+ "types": "./dist/index.d.ts",
23
25
  "import": "./dist/control-panel.js",
24
26
  "require": "./dist/control-panel.umd.cjs"
25
27
  }