@audiofab-io/easy-spin-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,764 @@
1
+ import { useCallback as e, useEffect as t, useRef as n, useState as r } from "react";
2
+ import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
3
+ import { PROGRAM_SLOT_COUNT as s, PROGRAM_SLOT_COUNT as c } from "@audiofab-io/fv1-core/pedal";
4
+ import { FV1Assembler as l } from "@audiofab-io/fv1-core";
5
+ //#region src/components/Knob.tsx
6
+ var u = 300, d = -u / 2;
7
+ function f({ value: t, label: r, onChange: s, size: c = 56, overlay: l = !1 }) {
8
+ let f = n(null), p = n(!1), m = n(0), h = n(0), g = e((e) => {
9
+ let t = f.current;
10
+ if (!t) return 0;
11
+ let n = t.getBoundingClientRect(), r = n.left + n.width / 2, i = n.top + n.height / 2;
12
+ return Math.atan2(e.clientY - i, e.clientX - r) * (180 / Math.PI);
13
+ }, []), _ = e((e) => {
14
+ p.current = !0, m.current = g(e.nativeEvent), h.current = t, e.target.setPointerCapture(e.pointerId);
15
+ }, [g, t]), v = e((e) => {
16
+ if (!p.current) return;
17
+ let t = ((g(e.nativeEvent) - m.current + 540) % 360 - 180) / u;
18
+ s(Math.max(0, Math.min(1, h.current + t)));
19
+ }, [g, s]), y = e(() => {
20
+ p.current = !1;
21
+ }, []), b = d + t * u, x = /* @__PURE__ */ o("svg", {
22
+ ref: f,
23
+ width: l ? "100%" : c,
24
+ height: l ? "100%" : c,
25
+ viewBox: "0 0 56 56",
26
+ className: "cursor-grab active:cursor-grabbing select-none touch-none",
27
+ role: "slider",
28
+ "aria-valuenow": Math.round(t * 100),
29
+ "aria-valuemin": 0,
30
+ "aria-valuemax": 100,
31
+ "aria-label": r,
32
+ onPointerDown: _,
33
+ onPointerMove: v,
34
+ onPointerUp: y,
35
+ children: [
36
+ /* @__PURE__ */ a("circle", {
37
+ cx: "28",
38
+ cy: "28",
39
+ r: "28",
40
+ fill: "transparent"
41
+ }),
42
+ l ? /* @__PURE__ */ a("circle", {
43
+ cx: "28",
44
+ cy: "28",
45
+ r: "24",
46
+ fill: "none",
47
+ stroke: "#111",
48
+ strokeWidth: "1.5"
49
+ }) : /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ a("circle", {
50
+ cx: "28",
51
+ cy: "28",
52
+ r: "26",
53
+ fill: "none",
54
+ stroke: "#888",
55
+ strokeWidth: "1.5"
56
+ }), /* @__PURE__ */ a("circle", {
57
+ cx: "28",
58
+ cy: "28",
59
+ r: "22",
60
+ fill: "#f0f0f0",
61
+ stroke: "#aaa",
62
+ strokeWidth: "1"
63
+ })] }),
64
+ /* @__PURE__ */ a("g", {
65
+ transform: `rotate(${b} 28 28)`,
66
+ children: /* @__PURE__ */ a("line", {
67
+ x1: "28",
68
+ y1: "28",
69
+ x2: "28",
70
+ y2: l ? 10 : 8,
71
+ stroke: l ? "#111" : "#B8942C",
72
+ strokeWidth: l ? 2.5 : 2,
73
+ strokeLinecap: "round"
74
+ })
75
+ })
76
+ ]
77
+ });
78
+ return l ? x : /* @__PURE__ */ o("div", {
79
+ className: "flex flex-col items-center gap-1",
80
+ children: [x, /* @__PURE__ */ a("span", {
81
+ className: "text-[10px] text-text-secondary text-center leading-tight max-w-[80px] truncate",
82
+ children: r
83
+ })]
84
+ });
85
+ }
86
+ //#endregion
87
+ //#region src/components/ProgramSelector.tsx
88
+ var p = c, m = 180, h = (390 - m) / (p - 1);
89
+ function g(e) {
90
+ return m + e * h;
91
+ }
92
+ function _(e) {
93
+ let t = (e % 360 + 360) % 360, n = 0, r = Infinity;
94
+ for (let e = 0; e < p; e++) {
95
+ let i = (g(e) % 360 + 360) % 360, a = Math.abs(t - i);
96
+ a > 180 && (a = 360 - a), a < r && (r = a, n = e);
97
+ }
98
+ return n;
99
+ }
100
+ function v({ selectedSlot: t, onSelectSlot: r, slotLabels: i, overlay: s = !1 }) {
101
+ let c = n(null), l = n(!1), u = e((e) => {
102
+ let t = c.current;
103
+ if (!t) return 0;
104
+ let n = t.getBoundingClientRect(), r = n.left + n.width / 2, i = n.top + n.height / 2;
105
+ return (Math.atan2(e.clientY - i, e.clientX - r) * (180 / Math.PI) % 360 + 360) % 360;
106
+ }, []), d = e((e) => {
107
+ l.current = !0, e.target.setPointerCapture(e.pointerId);
108
+ let n = _(u(e.nativeEvent));
109
+ n !== t && r(n);
110
+ }, [
111
+ u,
112
+ t,
113
+ r
114
+ ]), f = e((e) => {
115
+ if (!l.current) return;
116
+ let n = _(u(e.nativeEvent));
117
+ n !== t && r(n);
118
+ }, [
119
+ u,
120
+ t,
121
+ r
122
+ ]), m = e(() => {
123
+ l.current = !1;
124
+ }, []);
125
+ if (!s) return /* @__PURE__ */ a("div", {
126
+ className: "grid grid-cols-4 gap-1",
127
+ children: Array.from({ length: p }, (e, n) => /* @__PURE__ */ a("button", {
128
+ onClick: () => r(n),
129
+ title: i?.[n] ?? `Slot ${n + 1}`,
130
+ className: `w-7 h-7 rounded text-xs font-bold ${t === n ? "bg-gold text-surface" : "bg-surface-hover text-text-secondary hover:bg-surface-card border border-border"}`,
131
+ children: n + 1
132
+ }, n))
133
+ });
134
+ let h = g(t) - 270;
135
+ return /* @__PURE__ */ o("svg", {
136
+ ref: c,
137
+ width: "100%",
138
+ height: "100%",
139
+ viewBox: "0 0 56 56",
140
+ className: "cursor-grab active:cursor-grabbing select-none touch-none",
141
+ role: "listbox",
142
+ "aria-label": "Program selector",
143
+ "aria-activedescendant": `prog-slot-${t}`,
144
+ onPointerDown: d,
145
+ onPointerMove: f,
146
+ onPointerUp: m,
147
+ children: [
148
+ /* @__PURE__ */ a("circle", {
149
+ cx: "28",
150
+ cy: "28",
151
+ r: "28",
152
+ fill: "transparent"
153
+ }),
154
+ /* @__PURE__ */ a("circle", {
155
+ cx: "28",
156
+ cy: "28",
157
+ r: "24",
158
+ fill: "none",
159
+ stroke: "#111",
160
+ strokeWidth: "1.5"
161
+ }),
162
+ /* @__PURE__ */ a("g", {
163
+ transform: `rotate(${h} 28 28)`,
164
+ children: /* @__PURE__ */ a("line", {
165
+ x1: "28",
166
+ y1: "28",
167
+ x2: "28",
168
+ y2: "10",
169
+ stroke: "#111",
170
+ strokeWidth: "2.5",
171
+ strokeLinecap: "round"
172
+ })
173
+ })
174
+ ]
175
+ });
176
+ }
177
+ //#endregion
178
+ //#region src/components/ClipSelector.tsx
179
+ function y({ clips: e, selectedClipId: t, onSelect: n, disabled: r = !1 }) {
180
+ return /* @__PURE__ */ o("select", {
181
+ value: t ?? "",
182
+ onChange: (e) => n(e.target.value),
183
+ disabled: r,
184
+ className: "px-2 py-1 rounded bg-surface-hover border border-border text-xs text-text-primary focus:outline-none focus:border-gold-dim disabled:opacity-50",
185
+ children: [/* @__PURE__ */ a("option", {
186
+ value: "",
187
+ disabled: !0,
188
+ children: r ? "Loading…" : "Select clip"
189
+ }), e.map((e) => /* @__PURE__ */ a("option", {
190
+ value: e.id,
191
+ children: e.name
192
+ }, e.id))]
193
+ });
194
+ }
195
+ //#endregion
196
+ //#region src/components/PedalFace.tsx
197
+ var b = {
198
+ pot0: {
199
+ left: "22.85%",
200
+ top: "14%"
201
+ },
202
+ pot1: {
203
+ left: "77.15%",
204
+ top: "14%"
205
+ },
206
+ pot2: {
207
+ left: "22.85%",
208
+ top: "33.75%"
209
+ },
210
+ programSelector: {
211
+ left: "76%",
212
+ top: "33.75%"
213
+ },
214
+ led: {
215
+ left: "50%",
216
+ top: "42%"
217
+ },
218
+ footswitch: {
219
+ left: "50%",
220
+ top: "68.5%"
221
+ },
222
+ pot0Label: {
223
+ left: "22.85%",
224
+ top: "2.5%"
225
+ },
226
+ pot1Label: {
227
+ left: "77.15%",
228
+ top: "2.5%"
229
+ },
230
+ pot2Label: {
231
+ left: "22.85%",
232
+ top: "44%"
233
+ },
234
+ pedalIO: {
235
+ left: "105%",
236
+ top: "70%"
237
+ }
238
+ }, x = 20, S = 14, C = 20, w = 5;
239
+ function T({ pedalImageUrl: e, pots: t, onPotChange: n, potLabels: s = [
240
+ "Pot 0",
241
+ "Pot 1",
242
+ "Pot 2"
243
+ ], selectedSlot: l, onSelectSlot: u, slotLabels: d, onAssignSlot: f, onUnassignSlot: p, onUnassignAllSlots: m, onProgramSlot: h, programmingSlot: g = null, clips: _, selectedClipId: T, onSelectClip: N, playing: P, onPlay: F, onPause: I, bypassed: L, onToggleBypass: R, channelMode: z, onChannelModeChange: B, pedalConnected: V = !1, onConnectPedal: H, onReadPedal: U, onWritePedal: W, pedalReading: G = !1, pedalWriting: K = !1 }) {
244
+ let q = G || K, [J, Y] = r(null), X = !L, Z = d?.some((e) => e) ?? !1, Q = V && !q;
245
+ return /* @__PURE__ */ o("div", {
246
+ className: "flex flex-col items-center gap-5 w-full max-w-[460px] mx-auto",
247
+ children: [
248
+ /* @__PURE__ */ o("div", {
249
+ className: "relative w-full max-w-[360px] pedal-outline",
250
+ style: {
251
+ aspectRatio: "1573 / 2627",
252
+ backgroundImage: `url(${e})`,
253
+ backgroundSize: "contain",
254
+ backgroundRepeat: "no-repeat",
255
+ backgroundPosition: "center top"
256
+ },
257
+ children: [
258
+ /* @__PURE__ */ a(E, {
259
+ posStyle: b.pot0,
260
+ sizePct: x,
261
+ value: t[0],
262
+ label: s[0],
263
+ onChange: (e) => n(0, e)
264
+ }),
265
+ /* @__PURE__ */ a(E, {
266
+ posStyle: b.pot1,
267
+ sizePct: x,
268
+ value: t[1],
269
+ label: s[1],
270
+ onChange: (e) => n(1, e)
271
+ }),
272
+ /* @__PURE__ */ a(E, {
273
+ posStyle: b.pot2,
274
+ sizePct: x,
275
+ value: t[2],
276
+ label: s[2],
277
+ onChange: (e) => n(2, e)
278
+ }),
279
+ /* @__PURE__ */ a(D, {
280
+ posStyle: b.pot0Label,
281
+ text: s[0]
282
+ }),
283
+ /* @__PURE__ */ a(D, {
284
+ posStyle: b.pot1Label,
285
+ text: s[1]
286
+ }),
287
+ /* @__PURE__ */ a(D, {
288
+ posStyle: b.pot2Label,
289
+ text: s[2]
290
+ }),
291
+ /* @__PURE__ */ a("div", {
292
+ className: "absolute",
293
+ style: {
294
+ left: b.programSelector.left,
295
+ top: b.programSelector.top,
296
+ width: `${x}%`,
297
+ aspectRatio: "1 / 1",
298
+ transform: "translate(-50%, -50%)"
299
+ },
300
+ children: /* @__PURE__ */ a(v, {
301
+ overlay: !0,
302
+ selectedSlot: l,
303
+ onSelectSlot: u,
304
+ slotLabels: d
305
+ })
306
+ }),
307
+ /* @__PURE__ */ a("div", {
308
+ className: "absolute rounded-full transition-all",
309
+ style: {
310
+ left: b.led.left,
311
+ top: b.led.top,
312
+ width: `${w}%`,
313
+ aspectRatio: "1 / 1",
314
+ transform: "translate(-50%, -50%)",
315
+ background: X ? "radial-gradient(circle at 35% 30%, #ff8a8a 0%, #e11 55%, #800 100%)" : "radial-gradient(circle at 35% 30%, #f2f2f2 0%, #bbb 60%, #777 100%)",
316
+ boxShadow: X ? "0 0 10px rgba(255, 60, 60, 0.75)" : "inset 0 1px 2px rgba(0,0,0,0.25)",
317
+ border: "1px solid #333"
318
+ },
319
+ "aria-label": "Status LED"
320
+ }),
321
+ /* @__PURE__ */ o("svg", {
322
+ viewBox: "0 0 100 100",
323
+ className: "absolute pointer-events-none",
324
+ style: {
325
+ left: b.footswitch.left,
326
+ top: b.footswitch.top,
327
+ width: `${C}%`,
328
+ aspectRatio: "1 / 1",
329
+ transform: "translate(-50%, -50%)",
330
+ filter: "drop-shadow(0 2px 2px rgba(0,0,0,0.45))"
331
+ },
332
+ children: [/* @__PURE__ */ a("defs", { children: /* @__PURE__ */ o("radialGradient", {
333
+ id: "silverHexNut",
334
+ cx: "40%",
335
+ cy: "35%",
336
+ r: "65%",
337
+ children: [
338
+ /* @__PURE__ */ a("stop", {
339
+ offset: "0%",
340
+ stopColor: "#f8f8f8"
341
+ }),
342
+ /* @__PURE__ */ a("stop", {
343
+ offset: "45%",
344
+ stopColor: "#c8c8c8"
345
+ }),
346
+ /* @__PURE__ */ a("stop", {
347
+ offset: "100%",
348
+ stopColor: "#707070"
349
+ })
350
+ ]
351
+ }) }), /* @__PURE__ */ a("polygon", {
352
+ points: "5,50 27.5,11 72.5,11 95,50 72.5,89 27.5,89",
353
+ fill: "url(#silverHexNut)",
354
+ stroke: "#333",
355
+ strokeWidth: "2",
356
+ strokeLinejoin: "round"
357
+ })]
358
+ }),
359
+ (H || U || W) && /* @__PURE__ */ a("div", {
360
+ className: "absolute flex items-center gap-1",
361
+ style: {
362
+ left: b.pedalIO.left,
363
+ top: b.pedalIO.top,
364
+ transform: "translate(-50%, -50%)"
365
+ },
366
+ children: V ? /* @__PURE__ */ o(i, { children: [U && /* @__PURE__ */ a(O, {
367
+ onClick: U,
368
+ disabled: q,
369
+ busy: G,
370
+ label: "Read programs from pedal",
371
+ children: /* @__PURE__ */ a(A, {})
372
+ }), W && /* @__PURE__ */ a(O, {
373
+ onClick: W,
374
+ disabled: q,
375
+ busy: K,
376
+ label: "Write programs to pedal",
377
+ children: /* @__PURE__ */ a(j, {})
378
+ })] }) : H && /* @__PURE__ */ a("button", {
379
+ type: "button",
380
+ onClick: H,
381
+ className: "px-2 py-1 rounded border border-border bg-surface-card text-[10px] font-semibold text-text-primary hover:border-gold-dim transition-colors whitespace-nowrap",
382
+ children: "Connect"
383
+ })
384
+ }),
385
+ /* @__PURE__ */ a("button", {
386
+ onClick: R,
387
+ "aria-label": L ? "Bypassed — click to engage" : "Engaged — click to bypass",
388
+ className: "absolute rounded-full cursor-pointer",
389
+ style: {
390
+ left: b.footswitch.left,
391
+ top: b.footswitch.top,
392
+ width: `${S}%`,
393
+ aspectRatio: "1 / 1",
394
+ transform: "translate(-50%, -50%)",
395
+ background: "radial-gradient(circle at 35% 30%, #fafafa 0%, #cfcfcf 45%, #888 90%, #555 100%)",
396
+ border: "1.5px solid #222",
397
+ boxShadow: "0 2px 4px rgba(0,0,0,0.4), inset 0 1px 1px rgba(255,255,255,0.6), inset 0 -2px 3px rgba(0,0,0,0.25)"
398
+ },
399
+ children: /* @__PURE__ */ a("span", {
400
+ className: "absolute rounded-full pointer-events-none",
401
+ style: {
402
+ left: "50%",
403
+ top: "50%",
404
+ width: "80%",
405
+ aspectRatio: "1 / 1",
406
+ transform: "translate(-50%, -50%)",
407
+ background: "radial-gradient(circle at 40% 35%, #f0f0f0 0%, #b0b0b0 60%, #707070 100%)",
408
+ border: "1px solid #444",
409
+ boxShadow: "inset 0 1px 1px rgba(255,255,255,0.5), inset 0 -1px 2px rgba(0,0,0,0.35)"
410
+ }
411
+ })
412
+ })
413
+ ]
414
+ }),
415
+ /* @__PURE__ */ o("div", {
416
+ className: "flex items-center gap-3 flex-wrap justify-center",
417
+ children: [
418
+ /* @__PURE__ */ a("button", {
419
+ onClick: P ? I : F,
420
+ className: "px-4 py-1.5 rounded border border-border bg-surface-card text-sm font-semibold text-text-primary hover:border-gold-dim transition-colors",
421
+ children: P ? "Pause" : "Play"
422
+ }),
423
+ _ && _.length > 0 && /* @__PURE__ */ a(y, {
424
+ clips: _,
425
+ selectedClipId: T,
426
+ onSelect: N
427
+ }),
428
+ /* @__PURE__ */ a("div", {
429
+ role: "radiogroup",
430
+ "aria-label": "Output channel mode",
431
+ title: "Mono matches the current Easy Spin hardware (DACL to both jacks). Stereo plays the FV-1's native left/right outputs.",
432
+ className: "flex items-center rounded border border-border bg-surface-card text-sm overflow-hidden",
433
+ children: ["mono", "stereo"].map((e) => /* @__PURE__ */ a("button", {
434
+ type: "button",
435
+ role: "radio",
436
+ "aria-checked": z === e,
437
+ onClick: () => B(e),
438
+ className: `px-3 py-1.5 font-semibold transition-colors ${z === e ? "bg-gold text-surface" : "text-text-secondary hover:text-text-primary"}`,
439
+ children: e === "mono" ? "Mono" : "Stereo"
440
+ }, e))
441
+ })
442
+ ]
443
+ }),
444
+ /* @__PURE__ */ o("div", {
445
+ className: "w-full space-y-2",
446
+ children: [/* @__PURE__ */ o("div", {
447
+ className: "flex items-center justify-between",
448
+ children: [/* @__PURE__ */ a("h3", {
449
+ className: "text-xs font-semibold uppercase tracking-wider text-text-muted",
450
+ children: "Programs on Pedal"
451
+ }), Z && m && /* @__PURE__ */ a("button", {
452
+ type: "button",
453
+ onClick: m,
454
+ className: "text-[11px] text-text-muted hover:text-red-400 transition-colors",
455
+ title: "Clear all local slot assignments",
456
+ children: "Unassign all"
457
+ })]
458
+ }), /* @__PURE__ */ a("div", {
459
+ className: "grid grid-cols-2 gap-2",
460
+ children: Array.from({ length: c }, (e, t) => {
461
+ let n = l === t, r = J === t, i = d?.[t], s = !i;
462
+ return /* @__PURE__ */ o("div", {
463
+ role: "button",
464
+ tabIndex: 0,
465
+ onClick: () => u(t),
466
+ onKeyDown: (e) => {
467
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), u(t));
468
+ },
469
+ onDragOver: (e) => {
470
+ f && (e.preventDefault(), e.dataTransfer.dropEffect = "copy", J !== t && Y(t));
471
+ },
472
+ onDragLeave: () => Y(null),
473
+ onDrop: (e) => {
474
+ if (!f) return;
475
+ e.preventDefault(), Y(null);
476
+ let n = e.dataTransfer.getData("text/plain");
477
+ n && f(t, n);
478
+ },
479
+ className: [
480
+ "flex items-center gap-2 px-2 py-2 rounded border text-left transition-colors cursor-pointer",
481
+ "bg-surface-card text-sm outline-none focus-visible:ring-1 focus-visible:ring-gold-dim",
482
+ n ? "border-gold-dim ring-1 ring-gold-dim/60 text-text-primary" : "border-border text-text-primary hover:border-gold-dim",
483
+ r ? "border-dashed border-gold-dim bg-gold-dim/10" : ""
484
+ ].join(" "),
485
+ children: [
486
+ /* @__PURE__ */ a("span", {
487
+ className: ["flex-none w-6 h-6 inline-flex items-center justify-center rounded-full text-xs font-bold", n ? "bg-gold-dim text-black" : "bg-black/30 text-text-primary"].join(" "),
488
+ children: t + 1
489
+ }),
490
+ /* @__PURE__ */ a("span", {
491
+ className: ["flex-1 min-w-0 truncate", s ? "italic text-text-muted" : "font-medium"].join(" "),
492
+ children: s ? "Empty" : i
493
+ }),
494
+ !s && h && Q && /* @__PURE__ */ a("button", {
495
+ type: "button",
496
+ onClick: (e) => {
497
+ e.stopPropagation(), h(t);
498
+ },
499
+ disabled: g !== null,
500
+ "aria-label": `Write slot ${t + 1} to pedal`,
501
+ title: "Write this slot to pedal",
502
+ className: "flex-none p-1 rounded text-text-muted hover:text-gold-dim hover:bg-black/20 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
503
+ children: a(g === t ? k : j, { small: !0 })
504
+ }),
505
+ !s && p && /* @__PURE__ */ a("button", {
506
+ type: "button",
507
+ onClick: (e) => {
508
+ e.stopPropagation(), p(t);
509
+ },
510
+ "aria-label": `Unassign slot ${t + 1}`,
511
+ title: "Unassign",
512
+ className: "flex-none p-1 rounded text-text-muted hover:text-red-400 hover:bg-black/20 transition-colors",
513
+ children: /* @__PURE__ */ a(M, {})
514
+ })
515
+ ]
516
+ }, t);
517
+ })
518
+ })]
519
+ })
520
+ ]
521
+ });
522
+ }
523
+ function E({ posStyle: e, sizePct: t, value: n, label: r, onChange: i }) {
524
+ return /* @__PURE__ */ a("div", {
525
+ className: "absolute",
526
+ style: {
527
+ left: e.left,
528
+ top: e.top,
529
+ width: `${t}%`,
530
+ aspectRatio: "1 / 1",
531
+ transform: "translate(-50%, -50%)"
532
+ },
533
+ children: /* @__PURE__ */ a(f, {
534
+ overlay: !0,
535
+ value: n,
536
+ label: r,
537
+ onChange: i
538
+ })
539
+ });
540
+ }
541
+ function D({ posStyle: e, text: t }) {
542
+ return /* @__PURE__ */ a("div", {
543
+ className: "absolute text-center text-[11px] sm:text-xs font-semibold text-black whitespace-nowrap pointer-events-none",
544
+ style: {
545
+ left: e.left,
546
+ top: e.top,
547
+ transform: "translate(-50%, -50%)"
548
+ },
549
+ children: t
550
+ });
551
+ }
552
+ function O({ onClick: e, disabled: t, busy: n, label: r, children: i }) {
553
+ return /* @__PURE__ */ a("button", {
554
+ type: "button",
555
+ onClick: e,
556
+ disabled: t,
557
+ "aria-label": r,
558
+ title: r,
559
+ className: "p-1 rounded border border-border bg-surface-card text-text-primary hover:border-gold-dim hover:text-gold-dim disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
560
+ children: n ? /* @__PURE__ */ a(k, {}) : i
561
+ });
562
+ }
563
+ function k({ small: e = !1 }) {
564
+ return /* @__PURE__ */ a("svg", {
565
+ viewBox: "0 0 24 24",
566
+ fill: "none",
567
+ stroke: "currentColor",
568
+ strokeWidth: "2",
569
+ strokeLinecap: "round",
570
+ strokeLinejoin: "round",
571
+ className: e ? "w-3.5 h-3.5 animate-spin" : "w-4 h-4 animate-spin",
572
+ children: /* @__PURE__ */ a("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
573
+ });
574
+ }
575
+ function A() {
576
+ return /* @__PURE__ */ o("svg", {
577
+ viewBox: "0 0 24 24",
578
+ fill: "none",
579
+ stroke: "currentColor",
580
+ strokeWidth: "2",
581
+ strokeLinecap: "round",
582
+ strokeLinejoin: "round",
583
+ className: "w-4 h-4",
584
+ children: [
585
+ /* @__PURE__ */ a("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
586
+ /* @__PURE__ */ a("polyline", { points: "17 8 12 3 7 8" }),
587
+ /* @__PURE__ */ a("line", {
588
+ x1: "12",
589
+ y1: "3",
590
+ x2: "12",
591
+ y2: "15"
592
+ })
593
+ ]
594
+ });
595
+ }
596
+ function j({ small: e = !1 }) {
597
+ return /* @__PURE__ */ o("svg", {
598
+ viewBox: "0 0 24 24",
599
+ fill: "none",
600
+ stroke: "currentColor",
601
+ strokeWidth: "2",
602
+ strokeLinecap: "round",
603
+ strokeLinejoin: "round",
604
+ className: e ? "w-3.5 h-3.5" : "w-4 h-4",
605
+ children: [
606
+ /* @__PURE__ */ a("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
607
+ /* @__PURE__ */ a("polyline", { points: "7 10 12 15 17 10" }),
608
+ /* @__PURE__ */ a("line", {
609
+ x1: "12",
610
+ y1: "15",
611
+ x2: "12",
612
+ y2: "3"
613
+ })
614
+ ]
615
+ });
616
+ }
617
+ function M() {
618
+ return /* @__PURE__ */ o("svg", {
619
+ viewBox: "0 0 24 24",
620
+ fill: "none",
621
+ stroke: "currentColor",
622
+ strokeWidth: "2",
623
+ strokeLinecap: "round",
624
+ strokeLinejoin: "round",
625
+ className: "w-3.5 h-3.5",
626
+ children: [
627
+ /* @__PURE__ */ a("path", { d: "M3 6h18" }),
628
+ /* @__PURE__ */ a("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
629
+ /* @__PURE__ */ a("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
630
+ /* @__PURE__ */ a("path", { d: "M10 11v6M14 11v6" })
631
+ ]
632
+ });
633
+ }
634
+ //#endregion
635
+ //#region src/audio/binary.ts
636
+ function N(e) {
637
+ let t = [], n = new DataView(e.buffer, e.byteOffset, e.byteLength);
638
+ for (let r = 0; r < e.length; r += 4) t.push(n.getUint32(r, !1));
639
+ return t;
640
+ }
641
+ //#endregion
642
+ //#region src/simulator/useSimulator.ts
643
+ function P({ workletUrl: i, sampleRate: a = 32768 }) {
644
+ let o = n(null), s = n(null), c = n(null), u = n(null), d = n(null), [f, p] = r({
645
+ playing: !1,
646
+ bypassed: !1,
647
+ channelMode: "mono",
648
+ sampleRate: null
649
+ }), m = e(async () => {
650
+ if (!o.current) return d.current ||= (async () => {
651
+ let e = new AudioContext({ sampleRate: a });
652
+ await e.audioWorklet.addModule(i);
653
+ let t = new AudioWorkletNode(e, "fv1-processor", { outputChannelCount: [2] });
654
+ t.connect(e.destination), o.current = e, s.current = t, p((t) => ({
655
+ ...t,
656
+ sampleRate: e.sampleRate
657
+ }));
658
+ })(), d.current;
659
+ }, [i, a]), h = e(async (e) => {
660
+ await m();
661
+ let t = N(e);
662
+ s.current.port.postMessage({
663
+ type: "loadProgram",
664
+ machineCode: t
665
+ });
666
+ }, [m]), g = e(async (e) => {
667
+ await m();
668
+ let t = new l().assemble(e), n = t.problems.filter((e) => e.isfatal);
669
+ if (n.length > 0) return {
670
+ success: !1,
671
+ errors: n
672
+ };
673
+ let r = N(l.toUint8Array(t.machineCode));
674
+ return s.current.port.postMessage({
675
+ type: "loadProgram",
676
+ machineCode: r
677
+ }), { success: !0 };
678
+ }, [m]), _ = e(async (e) => {
679
+ await m();
680
+ let t = o.current, n = c.current !== null;
681
+ if (c.current) {
682
+ try {
683
+ c.current.stop();
684
+ } catch {}
685
+ c.current.disconnect(), c.current = null;
686
+ }
687
+ let r = e instanceof ArrayBuffer ? e : e.buffer.slice(e.byteOffset, e.byteOffset + e.byteLength), i = await t.decodeAudioData(r);
688
+ if (u.current = i, n) {
689
+ let e = t.createBufferSource();
690
+ e.buffer = i, e.loop = !0, e.connect(s.current), e.start(), c.current = e, await t.resume();
691
+ }
692
+ }, [m]), v = e(async () => {
693
+ await m();
694
+ let e = o.current, t = u.current;
695
+ if (!t) return;
696
+ if (c.current) {
697
+ try {
698
+ c.current.stop();
699
+ } catch {}
700
+ c.current.disconnect();
701
+ }
702
+ let n = e.createBufferSource();
703
+ n.buffer = t, n.loop = !0, n.connect(s.current), n.start(), c.current = n, await e.resume(), p((e) => ({
704
+ ...e,
705
+ playing: !0
706
+ }));
707
+ }, [m]), y = e(() => {
708
+ if (c.current) {
709
+ try {
710
+ c.current.stop();
711
+ } catch {}
712
+ c.current.disconnect(), c.current = null;
713
+ }
714
+ o.current?.suspend(), p((e) => ({
715
+ ...e,
716
+ playing: !1
717
+ }));
718
+ }, []), b = e((e, t) => {
719
+ s.current?.port.postMessage({
720
+ type: "setPot",
721
+ index: e,
722
+ value: t
723
+ });
724
+ }, []), x = e((e) => {
725
+ s.current?.port.postMessage({
726
+ type: "bypass",
727
+ active: e
728
+ }), p((t) => ({
729
+ ...t,
730
+ bypassed: e
731
+ }));
732
+ }, []), S = e((e) => {
733
+ s.current?.port.postMessage({
734
+ type: "setChannelMode",
735
+ mode: e
736
+ }), p((t) => ({
737
+ ...t,
738
+ channelMode: e
739
+ }));
740
+ }, []);
741
+ return t(() => () => {
742
+ if (c.current) {
743
+ try {
744
+ c.current.stop();
745
+ } catch {}
746
+ c.current.disconnect();
747
+ }
748
+ o.current?.close().catch(() => {});
749
+ }, []), {
750
+ ...f,
751
+ loadProgram: h,
752
+ loadProgramFromSource: g,
753
+ loadClipBuffer: _,
754
+ play: v,
755
+ pause: y,
756
+ setPot: b,
757
+ setBypass: x,
758
+ setChannelMode: S
759
+ };
760
+ }
761
+ //#endregion
762
+ export { y as ClipSelector, f as Knob, s as PROGRAM_SLOT_COUNT, T as PedalFace, v as ProgramSelector, N as binaryToMachineCode, P as useSimulator };
763
+
764
+ //# sourceMappingURL=index.js.map