@jpdutoit/squelchy 0.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/index.js ADDED
@@ -0,0 +1,4846 @@
1
+ import {
2
+ adsr_envelope_default,
3
+ bitcrusher_default,
4
+ comb_filter_default,
5
+ env_follower_default,
6
+ fdn_reverb_default,
7
+ flanger_default,
8
+ formant_table_default,
9
+ freq_divider_default,
10
+ freq_shifter_default,
11
+ glide_default,
12
+ ladder_filter_default,
13
+ map_res_default,
14
+ map_voct_to_freq_default,
15
+ map_voct_to_period_default,
16
+ ping_pong_default,
17
+ pitch_shifter_default,
18
+ spring_reverb_default,
19
+ tape_delay_default,
20
+ tape_stop_default,
21
+ vca_default,
22
+ waveshaper_default
23
+ } from "./chunk-NDUSYSHF.js";
24
+
25
+ // src/lib-runtime/core.ts
26
+ var pendingSources = {};
27
+ var registrations = /* @__PURE__ */ new WeakMap();
28
+ function preload(ctx, opts) {
29
+ const existing = registrations.get(ctx);
30
+ if (existing) return existing;
31
+ const sources = opts?.allWorklets ? import("./worklets-inline-MUVFLGPY.js").then((m) => m.WORKLET_SOURCES) : Promise.resolve(pendingSources);
32
+ const promise = sources.then(
33
+ (map) => Promise.all(Object.entries(map).map(async ([name, src]) => {
34
+ const url = URL.createObjectURL(new Blob([src], { type: "text/javascript" }));
35
+ try {
36
+ await ctx.audioWorklet.addModule(url);
37
+ } catch (err) {
38
+ throw new Error(`squelchy: failed to register worklet "${name}": ${err}`);
39
+ } finally {
40
+ URL.revokeObjectURL(url);
41
+ }
42
+ }))
43
+ ).then(() => {
44
+ });
45
+ registrations.set(ctx, promise);
46
+ return promise;
47
+ }
48
+ var instanceCounter = 0;
49
+ function defineModule(desc, opts) {
50
+ if (opts?.worklets) Object.assign(pendingSources, opts.worklets);
51
+ const meta = { type: desc.type, ports: desc.ports, params: desc.params ?? {} };
52
+ return {
53
+ meta,
54
+ create(ctx, params50 = {}) {
55
+ const module = { ...desc.createModule(`lib-${desc.type}-${instanceCounter++}`, { x: 0, y: 0 }), ...params50 };
56
+ const nodes = desc.createNodes(ctx, module);
57
+ return withPortProperties(new Instance(desc, module, nodes), desc);
58
+ }
59
+ };
60
+ }
61
+ var RESERVED_PROPS = /* @__PURE__ */ new Set(["input", "output", "set", "dispose", "params", "then"]);
62
+ function withPortProperties(inst, desc) {
63
+ const props = /* @__PURE__ */ new Map();
64
+ for (const [slug, p] of Object.entries(desc.ports)) {
65
+ if (RESERVED_PROPS.has(slug) || props.has(slug)) continue;
66
+ props.set(slug, p.type === "output" ? () => inst.output(slug) : () => inst.input(slug));
67
+ }
68
+ return new Proxy(inst, {
69
+ get(target, key, receiver) {
70
+ if (typeof key === "string") {
71
+ const hit = props.get(key);
72
+ if (hit) return hit();
73
+ }
74
+ return Reflect.get(target, key, receiver);
75
+ }
76
+ });
77
+ }
78
+ var Instance = class {
79
+ constructor(desc, module, nodes) {
80
+ this.desc = desc;
81
+ this.module = module;
82
+ this.nodes = nodes;
83
+ }
84
+ port(slug) {
85
+ const p = this.desc.ports[slug];
86
+ if (!p) throw new Error(`squelchy: unknown port "${slug}" on ${this.desc.type}`);
87
+ return p;
88
+ }
89
+ nodeFor(slug) {
90
+ const p = this.port(slug);
91
+ const node = p.node ? this.nodes[p.node] : void 0;
92
+ if (!node) throw new Error(`squelchy: port "${slug}" on ${this.desc.type} has no audio node`);
93
+ return node;
94
+ }
95
+ input(slug) {
96
+ const p = this.port(slug);
97
+ const node = this.nodeFor(slug);
98
+ if (!p.field) return node;
99
+ return this.resolveParam(p.node, p.field, `port "${slug}"`);
100
+ }
101
+ output(slug) {
102
+ return this.nodeFor(slug);
103
+ }
104
+ /** Native AudioParams behind params declaring `node` + `field` (1:1 mapped,
105
+ * no conversion) — full Web Audio automation: ramps, curves, CV. */
106
+ get params() {
107
+ const out = {};
108
+ for (const [key, spec] of Object.entries(this.desc.params ?? {})) {
109
+ if (!spec.node || !spec.field) continue;
110
+ Object.defineProperty(out, key, { enumerable: true, get: () => this.resolveParam(spec.node, spec.field, `param "${key}"`) });
111
+ }
112
+ return out;
113
+ }
114
+ resolveParam(nodeKey, field, what) {
115
+ const node = this.nodes[nodeKey];
116
+ const param = node?.[field] ?? node?.parameters?.get(field);
117
+ if (!param || typeof param.setValueAtTime !== "function") throw new Error(`squelchy: ${what} on ${this.desc.type} has no AudioParam "${nodeKey}.${field}"`);
118
+ return param;
119
+ }
120
+ set(key, value) {
121
+ this.module[key] = value;
122
+ this.desc.updateParam(this.nodes, this.module, key, value);
123
+ }
124
+ dispose() {
125
+ this.desc.dispose?.(this.nodes);
126
+ for (const node of Object.values(this.nodes)) {
127
+ if (typeof node.stop === "function") {
128
+ try {
129
+ node.stop();
130
+ } catch {
131
+ }
132
+ }
133
+ try {
134
+ node.disconnect();
135
+ } catch {
136
+ }
137
+ }
138
+ }
139
+ };
140
+
141
+ // src/lib-runtime/time/transport.ts
142
+ var EPS = 1e-9;
143
+ function isRest(v) {
144
+ return v == null || v === false || v === 0 || v === "." || v === "-";
145
+ }
146
+ function normStep(s) {
147
+ if (isRest(s)) return null;
148
+ if (typeof s === "object" && s !== null && "value" in s) {
149
+ const o = s;
150
+ return isRest(o.value) ? null : o;
151
+ }
152
+ return { value: s };
153
+ }
154
+ function callVoice(voice2, hit) {
155
+ if (typeof voice2 === "function") voice2(hit.time, hit);
156
+ else voice2.trigger(hit.time, hit);
157
+ }
158
+ function createTransport(ctx, opts = {}) {
159
+ const beatsPerBar = opts.beatsPerBar ?? 4;
160
+ const lookahead = opts.lookahead ?? 0.1;
161
+ const offline = typeof ctx.length === "number" && typeof ctx.close !== "function";
162
+ let bpm = opts.bpm ?? 120;
163
+ let aBeat = 0;
164
+ let aTime = 0;
165
+ let running = false;
166
+ let horizon = 0;
167
+ let timer = null;
168
+ const jobs = /* @__PURE__ */ new Set();
169
+ const taps = /* @__PURE__ */ new Set();
170
+ const tapTimers = /* @__PURE__ */ new Set();
171
+ const spb = () => 60 / bpm;
172
+ const beatAt = (t2) => aBeat + (t2 - aTime) / spb();
173
+ const timeAt = (b) => aTime + (b - aBeat) * spb();
174
+ const current = () => running ? Math.max(0, beatAt(ctx.currentTime + 5e-3)) : 0;
175
+ function resolve(at) {
176
+ return resolveNum(at);
177
+ }
178
+ function resolveNum(at) {
179
+ const now2 = current();
180
+ if (at === "now") return now2;
181
+ if (at === "beat") return Math.ceil(now2 - EPS);
182
+ if (at === "bar") return Math.ceil(now2 / beatsPerBar - EPS) * beatsPerBar;
183
+ if ("bar" in at) return at.bar * beatsPerBar;
184
+ return at.beat;
185
+ }
186
+ const latency = () => ctx.outputLatency || ctx.baseLatency || 0;
187
+ function emitTap(hit, local) {
188
+ if (!local && taps.size === 0) return;
189
+ const fire = () => {
190
+ local?.(hit);
191
+ for (const cb of taps) cb(hit);
192
+ };
193
+ if (offline) {
194
+ fire();
195
+ return;
196
+ }
197
+ const delay = Math.max(0, (hit.time - ctx.currentTime + latency()) * 1e3);
198
+ const id = setTimeout(() => {
199
+ tapTimers.delete(id);
200
+ fire();
201
+ }, delay);
202
+ tapTimers.add(id);
203
+ }
204
+ function horizonBeat() {
205
+ if (offline) return beatAt(ctx.length / ctx.sampleRate);
206
+ return beatAt(ctx.currentTime + lookahead);
207
+ }
208
+ function tick() {
209
+ if (!running) return;
210
+ const until = horizonBeat();
211
+ for (const job of jobs) if (!job.run(until)) jobs.delete(job);
212
+ horizon = Math.max(horizon, until);
213
+ }
214
+ const t = {
215
+ ctx,
216
+ beatsPerBar,
217
+ get bpm() {
218
+ return bpm;
219
+ },
220
+ set bpm(v) {
221
+ if (!(v > 0)) throw new RangeError("bpm must be > 0");
222
+ if (running) {
223
+ const b = Math.max(horizon, beatAt(ctx.currentTime));
224
+ aTime = timeAt(b);
225
+ aBeat = b;
226
+ }
227
+ bpm = v;
228
+ },
229
+ get running() {
230
+ return running;
231
+ },
232
+ start(when = ctx.currentTime + 0.05) {
233
+ if (running) return;
234
+ aBeat = 0;
235
+ aTime = when;
236
+ horizon = 0;
237
+ running = true;
238
+ tick();
239
+ if (!offline && opts.clock !== "manual") timer = setInterval(tick, 25);
240
+ },
241
+ stop() {
242
+ running = false;
243
+ if (timer) clearInterval(timer);
244
+ timer = null;
245
+ for (const id of tapTimers) clearTimeout(id);
246
+ tapTimers.clear();
247
+ jobs.clear();
248
+ },
249
+ beatAt,
250
+ timeAt,
251
+ resolve,
252
+ play(pattern, voice2, o = {}) {
253
+ const every = o.every ?? 0.25;
254
+ const gate = o.gate ?? 0.5;
255
+ const swing = o.swing ?? 0;
256
+ const maxCycles = o.loop === false ? 1 : o.loop === true || o.loop === void 0 ? Infinity : o.loop;
257
+ let pat = pattern;
258
+ let pending = null;
259
+ const startBeat = resolve(o.start ?? "bar");
260
+ let stopBeat = Infinity;
261
+ let k = 0;
262
+ let done = false;
263
+ const stepBeat = (i) => startBeat + i * every + (i % 2 === 1 ? swing * every * 0.5 : 0);
264
+ const job = {
265
+ nextBeat: () => stepBeat(k),
266
+ run(until) {
267
+ while (!done) {
268
+ if (pat.length === 0) {
269
+ done = true;
270
+ break;
271
+ }
272
+ const idx = k % pat.length;
273
+ if (idx === 0 && pending) {
274
+ pat = pending;
275
+ pending = null;
276
+ continue;
277
+ }
278
+ const cycle = Math.floor(k / pat.length);
279
+ const b = stepBeat(k);
280
+ if (cycle >= maxCycles || b >= stopBeat - EPS) {
281
+ done = true;
282
+ break;
283
+ }
284
+ if (b >= until) break;
285
+ const s = normStep(pat[idx]);
286
+ if (s) {
287
+ const time = timeAt(b);
288
+ const hit = {
289
+ time,
290
+ beat: b,
291
+ step: idx,
292
+ cycle,
293
+ value: s.value,
294
+ velocity: s.velocity ?? 1,
295
+ duration: (s.length ?? gate) * every * spb()
296
+ };
297
+ callVoice(voice2, hit);
298
+ emitTap(hit, o.onHit);
299
+ }
300
+ k++;
301
+ }
302
+ return !done;
303
+ }
304
+ };
305
+ jobs.add(job);
306
+ if (running) job.run(horizonBeat());
307
+ return {
308
+ set(p) {
309
+ pending = p;
310
+ },
311
+ stop(at) {
312
+ if (at) stopBeat = resolve(at);
313
+ else {
314
+ done = true;
315
+ jobs.delete(job);
316
+ }
317
+ },
318
+ get done() {
319
+ return done;
320
+ }
321
+ };
322
+ },
323
+ at(when, fn) {
324
+ const b = typeof when === "number" ? when : resolve(when);
325
+ let fired = false;
326
+ const job = {
327
+ nextBeat: () => b,
328
+ run(until) {
329
+ if (fired) return false;
330
+ if (b < until) {
331
+ fired = true;
332
+ fn(timeAt(b));
333
+ return false;
334
+ }
335
+ return true;
336
+ }
337
+ };
338
+ jobs.add(job);
339
+ if (running) tick();
340
+ return () => {
341
+ fired = true;
342
+ jobs.delete(job);
343
+ };
344
+ },
345
+ onHit(cb) {
346
+ taps.add(cb);
347
+ return () => {
348
+ taps.delete(cb);
349
+ };
350
+ },
351
+ tick,
352
+ dispose() {
353
+ t.stop();
354
+ taps.clear();
355
+ }
356
+ };
357
+ return t;
358
+ }
359
+ function steps(s) {
360
+ return [...s.replace(/[\s|]/g, "")].map((c) => c === "x" || c === "X");
361
+ }
362
+ var NOTE_RE = /^([A-Ga-g])([#b]?)(-?\d+)$/;
363
+ var PC = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
364
+ function note(name) {
365
+ if (typeof name === "number") return name;
366
+ const m = NOTE_RE.exec(name.trim());
367
+ if (!m) throw new Error(`note(): can't parse "${name}"`);
368
+ return (parseInt(m[3], 10) + 1) * 12 + PC[m[1].toLowerCase()] + (m[2] === "#" ? 1 : m[2] === "b" ? -1 : 0);
369
+ }
370
+ function notes(s) {
371
+ return s.trim().split(/\s+/).map((tok) => tok === "." || tok === "-" ? null : note(tok));
372
+ }
373
+
374
+ // src/lib/dsp/prng.ts
375
+ function mulberry32(seed) {
376
+ let a = seed >>> 0;
377
+ return () => {
378
+ a = a + 1831565813 | 0;
379
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
380
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
381
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
382
+ };
383
+ }
384
+ function noiseSource(seed) {
385
+ const rand = mulberry32(seed);
386
+ return () => rand() * 2 - 1;
387
+ }
388
+ var NOISE_SEEDS = {
389
+ noiseWhite: 2654435769,
390
+ noisePink: 2246822507,
391
+ noiseBrown: 3266489909,
392
+ loFiCrackle: 668265263,
393
+ drumSnare: 374761393,
394
+ drumHat: 3550635116,
395
+ d808Snare: 4251993797,
396
+ d808ClosedHat: 3042594569,
397
+ d808OpenHat: 724231106
398
+ };
399
+
400
+ // src/lib-runtime/time/voices.ts
401
+ function voice(output, trigger) {
402
+ return {
403
+ output,
404
+ trigger,
405
+ connect: (d) => {
406
+ output.connect(d);
407
+ return d;
408
+ },
409
+ disconnect: () => output.disconnect()
410
+ };
411
+ }
412
+ function noiseBuffer(ctx, seconds, seed) {
413
+ const n = Math.floor(ctx.sampleRate * seconds);
414
+ const buf = ctx.createBuffer(1, n, ctx.sampleRate);
415
+ const d = buf.getChannelData(0);
416
+ const rand = noiseSource(seed);
417
+ for (let i = 0; i < n; i++) d[i] = rand();
418
+ return buf;
419
+ }
420
+ var vel = (h) => Math.max(0, Math.min(1, h?.velocity ?? 1));
421
+ var Kick = {
422
+ create(ctx) {
423
+ const out = ctx.createGain();
424
+ return voice(out, (when, h) => {
425
+ const osc = ctx.createOscillator();
426
+ const g = ctx.createGain();
427
+ osc.frequency.setValueAtTime(150, when);
428
+ osc.frequency.exponentialRampToValueAtTime(40, when + 0.1);
429
+ g.gain.setValueAtTime(0.8 * vel(h) + 1e-4, when);
430
+ g.gain.exponentialRampToValueAtTime(1e-4, when + 0.3);
431
+ osc.connect(g).connect(out);
432
+ osc.start(when);
433
+ osc.stop(when + 0.3);
434
+ });
435
+ }
436
+ };
437
+ var Snare = {
438
+ create(ctx) {
439
+ const out = ctx.createGain();
440
+ const buf = noiseBuffer(ctx, 0.15, NOISE_SEEDS.drumSnare);
441
+ return voice(out, (when, h) => {
442
+ const v = vel(h);
443
+ const n = ctx.createBufferSource();
444
+ n.buffer = buf;
445
+ const ng = ctx.createGain();
446
+ ng.gain.setValueAtTime(0.4 * v + 1e-4, when);
447
+ ng.gain.exponentialRampToValueAtTime(1e-4, when + 0.15);
448
+ n.connect(ng).connect(out);
449
+ const osc = ctx.createOscillator();
450
+ osc.type = "triangle";
451
+ const og = ctx.createGain();
452
+ osc.frequency.setValueAtTime(200, when);
453
+ osc.frequency.exponentialRampToValueAtTime(80, when + 0.05);
454
+ og.gain.setValueAtTime(0.5 * v + 1e-4, when);
455
+ og.gain.exponentialRampToValueAtTime(1e-4, when + 0.08);
456
+ osc.connect(og).connect(out);
457
+ n.start(when);
458
+ osc.start(when);
459
+ osc.stop(when + 0.15);
460
+ });
461
+ }
462
+ };
463
+ var Hat = {
464
+ create(ctx) {
465
+ const out = ctx.createGain();
466
+ const buf = noiseBuffer(ctx, 0.05, NOISE_SEEDS.drumHat);
467
+ return voice(out, (when, h) => {
468
+ const n = ctx.createBufferSource();
469
+ n.buffer = buf;
470
+ const f = ctx.createBiquadFilter();
471
+ f.type = "highpass";
472
+ f.frequency.value = 8e3;
473
+ const g = ctx.createGain();
474
+ g.gain.setValueAtTime(0.2 * vel(h) + 1e-4, when);
475
+ g.gain.exponentialRampToValueAtTime(1e-4, when + 0.05);
476
+ n.connect(f).connect(g).connect(out);
477
+ n.start(when);
478
+ });
479
+ }
480
+ };
481
+ var NoteCv = {
482
+ create(ctx) {
483
+ const gate = ctx.createConstantSource();
484
+ gate.offset.value = 0;
485
+ gate.start();
486
+ const pitch = ctx.createConstantSource();
487
+ pitch.offset.value = 60;
488
+ pitch.start();
489
+ return {
490
+ gate,
491
+ pitch,
492
+ trigger(time, h) {
493
+ if (typeof h?.value === "number") pitch.offset.setValueAtTime(h.value, time);
494
+ gate.offset.setValueAtTime(0, Math.max(0, time - 1 / ctx.sampleRate));
495
+ gate.offset.setValueAtTime(vel(h), time);
496
+ gate.offset.setValueAtTime(0, time + (h?.duration ?? 0.1));
497
+ },
498
+ dispose() {
499
+ gate.stop();
500
+ pitch.stop();
501
+ gate.disconnect();
502
+ pitch.disconnect();
503
+ }
504
+ };
505
+ }
506
+ };
507
+
508
+ // src/lib-runtime/units.ts
509
+ var sec = (v) => v;
510
+ var ms = (v) => v;
511
+ var beats = (v) => v;
512
+ var hz = (v) => v;
513
+ var semitones = (v) => v;
514
+ var cents = (v) => v;
515
+ var db = (v) => v;
516
+ var noteToHz = (n) => 440 * 2 ** ((n - 69) / 12);
517
+ var hzToNote = (f) => 69 + 12 * Math.log2(f / 440);
518
+ var transpose = (n, by) => n + by;
519
+ var interval = (a, b) => b - a;
520
+ var centsToSemitones = (c) => c / 100;
521
+ var msToSeconds = (ms2) => ms2 / 1e3;
522
+ var dbToGain = (db2) => 10 ** (db2 / 20);
523
+ var gainToDb = (g) => 20 * Math.log10(g);
524
+ var now = (ctx) => ctx.currentTime;
525
+ var asPitch = (n) => n;
526
+
527
+ // src/lib/dsp/pulse.ts
528
+ var PULSE_S = 1e-3;
529
+ function pulse(target, when) {
530
+ const ctx = target.context;
531
+ const at = Math.max(when ?? 0, ctx.currentTime);
532
+ const src = ctx.createConstantSource();
533
+ src.offset.value = 1;
534
+ src.connect(target);
535
+ src.start(at);
536
+ src.stop(at + PULSE_S);
537
+ src.onended = () => src.disconnect();
538
+ }
539
+
540
+ // src/lib/samples/types.ts
541
+ var VOICE_KEYS = [
542
+ "bd",
543
+ "sd",
544
+ "rim",
545
+ "clap",
546
+ "ch",
547
+ "oh",
548
+ "lt",
549
+ "mt",
550
+ "ht",
551
+ "cym",
552
+ "perc"
553
+ ];
554
+ var VOICE_FALLBACKS = {
555
+ bd: ["lt", "mt"],
556
+ sd: ["clap", "rim"],
557
+ rim: ["perc", "ch"],
558
+ clap: ["sd", "perc"],
559
+ ch: ["perc", "rim"],
560
+ oh: ["ch", "cym"],
561
+ lt: ["mt", "bd"],
562
+ mt: ["lt", "ht"],
563
+ ht: ["mt", "lt"],
564
+ cym: ["oh", "ch"],
565
+ perc: ["rim", "ch"]
566
+ };
567
+
568
+ // src/lib-runtime/samples.ts
569
+ var DEFAULT_SAMPLE_BASE_URL = "https://squelchy.studio";
570
+ function defineSampleSets(sets) {
571
+ return sets;
572
+ }
573
+ async function loadSampleSet(ctx, set, opts = {}) {
574
+ const base = opts.baseUrl ?? DEFAULT_SAMPLE_BASE_URL;
575
+ const f = opts.fetch ?? fetch;
576
+ const buffers = {};
577
+ await Promise.all(
578
+ Object.entries(set.voices).map(async ([key, path]) => {
579
+ const res = await f(new URL(path, base).href);
580
+ if (!res.ok) throw new Error(`loadSampleSet: ${key} ${res.status}`);
581
+ buffers[key] = await ctx.decodeAudioData(await res.arrayBuffer());
582
+ })
583
+ );
584
+ return {
585
+ set,
586
+ buffers,
587
+ buffer(key) {
588
+ return buffers[key] ?? VOICE_FALLBACKS[key].map((k) => buffers[k]).find(Boolean);
589
+ }
590
+ };
591
+ }
592
+ var SampleVoice = {
593
+ create(ctx, kit, key) {
594
+ const out = ctx.createGain();
595
+ const buf = kit.buffer(key);
596
+ const trigger = (when, h) => {
597
+ if (!buf) return;
598
+ const src = ctx.createBufferSource();
599
+ const g = ctx.createGain();
600
+ src.buffer = buf;
601
+ if (typeof h?.value === "number") src.playbackRate.value = 2 ** (h.value / 12);
602
+ g.gain.value = Math.max(0, Math.min(1, h?.velocity ?? 1));
603
+ src.connect(g).connect(out);
604
+ src.start(when);
605
+ if (h?.duration) src.stop(when + h.duration);
606
+ };
607
+ return {
608
+ output: out,
609
+ trigger,
610
+ connect: (d) => {
611
+ out.connect(d);
612
+ return d;
613
+ },
614
+ disconnect: () => out.disconnect()
615
+ };
616
+ }
617
+ };
618
+
619
+ // src/lib-runtime/generated/sample-sets.ts
620
+ var SampleSets = /* @__PURE__ */ defineSampleSets({
621
+ "roland-808": {
622
+ "name": "Roland TR-808",
623
+ "blurb": "The original machine, sampled from hardware. Deep sub kick, ticking hats.",
624
+ "family": "Machines",
625
+ "credit": "Michael Fischer / Technopolis TR-808 sample set, released to the public domain",
626
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
627
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
628
+ "voices": {
629
+ "bd": "/__l5e/assets-v1/c4b2fc55-dcf1-4248-a7fb-2cd3653a659b/roland-808-bd.flac",
630
+ "ch": "/__l5e/assets-v1/c1ec3b3d-571f-46a6-9d05-12b2fe5f6fc5/roland-808-ch.flac",
631
+ "clap": "/__l5e/assets-v1/8bd15e4b-96b7-46b6-b26a-35283c816f1e/roland-808-clap.flac",
632
+ "cym": "/__l5e/assets-v1/033f0719-913a-4a7a-998e-92a7319d806f/roland-808-cym.flac",
633
+ "ht": "/__l5e/assets-v1/ca555782-7544-446a-a461-9b56b4d30072/roland-808-ht.flac",
634
+ "lt": "/__l5e/assets-v1/8e100e1c-3844-42aa-bbe7-9eb52b9d704e/roland-808-lt.flac",
635
+ "mt": "/__l5e/assets-v1/93dd309f-9347-4ea1-909f-56196b08dc88/roland-808-mt.flac",
636
+ "oh": "/__l5e/assets-v1/4881941c-b558-4159-babd-fe224b0f1543/roland-808-oh.flac",
637
+ "perc": "/__l5e/assets-v1/d13076b5-50ca-4675-9d4e-70afd6e6203d/roland-808-perc.flac",
638
+ "rim": "/__l5e/assets-v1/ce82e0e9-402d-4a5e-bf01-59dfb922d1ba/roland-808-rim.flac",
639
+ "sd": "/__l5e/assets-v1/67a799c9-e019-442b-8025-99d69bc493b9/roland-808-sd.flac"
640
+ }
641
+ },
642
+ "tr-909": {
643
+ "name": "Roland TR-909",
644
+ "blurb": "Punchy transistor kick, snappy sampled snare, sizzling metal hats. House and techno.",
645
+ "family": "Machines",
646
+ "credit": "MckAudio TR8 sample pack, dedicated to the public domain (CC0)",
647
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
648
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
649
+ "voices": {
650
+ "bd": "/__l5e/assets-v1/fd3911bb-6783-4db6-bee2-bb8c5a589f26/tr-909-bd.flac",
651
+ "ch": "/__l5e/assets-v1/6f795c20-fb89-4cd5-b696-3a505d487baa/tr-909-ch.flac",
652
+ "clap": "/__l5e/assets-v1/7b1ccf8e-0dd6-4189-82d7-03a910e895f2/tr-909-clap.flac",
653
+ "cym": "/__l5e/assets-v1/f0d5ce25-3238-4e89-bb09-f5b7118fa0a7/tr-909-cym.flac",
654
+ "ht": "/__l5e/assets-v1/5f9b4c37-25a7-492c-aaed-f7fe24fbfe2c/tr-909-ht.flac",
655
+ "lt": "/__l5e/assets-v1/8477dddd-b9ed-4957-9a48-b5501bc77056/tr-909-lt.flac",
656
+ "mt": "/__l5e/assets-v1/92d461e5-d74a-4ba2-a899-4dea1ad06d13/tr-909-mt.flac",
657
+ "oh": "/__l5e/assets-v1/20c51259-d8d1-4ee3-a1d6-e850c3227b1a/tr-909-oh.flac",
658
+ "perc": "/__l5e/assets-v1/147d346e-9ae7-4413-a50a-de4f03ef7342/tr-909-perc.flac",
659
+ "rim": "/__l5e/assets-v1/0c9f157d-1329-4804-aa12-6819651d69fb/tr-909-rim.flac",
660
+ "sd": "/__l5e/assets-v1/bd8069c2-e07c-4d5c-b05a-fb5b9a31eadb/tr-909-sd.flac"
661
+ }
662
+ },
663
+ "tr-707": {
664
+ "name": "Roland TR-707",
665
+ "blurb": "Twelve-bit sampled kit with early-digital crunch. Big eighties snare and claps.",
666
+ "family": "Machines",
667
+ "credit": "MckAudio TR8 sample pack, dedicated to the public domain (CC0)",
668
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
669
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
670
+ "voices": {
671
+ "bd": "/__l5e/assets-v1/825b2c7c-5c5f-411c-8191-306549ee26f2/tr-707-bd.flac",
672
+ "ch": "/__l5e/assets-v1/6b35b393-7e9c-462d-8d2f-dc834417062b/tr-707-ch.flac",
673
+ "clap": "/__l5e/assets-v1/efbe819a-b13e-4bf2-a6bd-9bc15a09ff3f/tr-707-clap.flac",
674
+ "cym": "/__l5e/assets-v1/4a34902b-8592-46d8-b339-05a0648cac4f/tr-707-cym.flac",
675
+ "ht": "/__l5e/assets-v1/e58beee9-45e5-4b65-af99-a05fcb96f55b/tr-707-ht.flac",
676
+ "lt": "/__l5e/assets-v1/a0d6f499-4af6-4d34-96de-5ad7c2397229/tr-707-lt.flac",
677
+ "mt": "/__l5e/assets-v1/6c4bd12b-78ff-4f70-863b-cd482f684eb6/tr-707-mt.flac",
678
+ "oh": "/__l5e/assets-v1/c07159f3-d763-4698-a786-aeae60d38ac9/tr-707-oh.flac",
679
+ "perc": "/__l5e/assets-v1/2f8cffcb-8e86-4f65-99db-a705727ed3ab/tr-707-perc.flac",
680
+ "rim": "/__l5e/assets-v1/3d6445b1-38bd-4e9f-b048-db8c079b3c03/tr-707-rim.flac",
681
+ "sd": "/__l5e/assets-v1/aaf70dd8-c740-4b05-9369-feb80f3e26be/tr-707-sd.flac"
682
+ }
683
+ },
684
+ "tr-606": {
685
+ "name": "Roland TR-606",
686
+ "blurb": "Tiny Drumatix voices \u2014 thin kick, spitty snare, ticking hats. Perfect with a 303.",
687
+ "family": "Machines",
688
+ "credit": "MckAudio TR8 sample pack, dedicated to the public domain (CC0)",
689
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
690
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
691
+ "voices": {
692
+ "bd": "/__l5e/assets-v1/f46ec897-9319-4c25-b63b-74df979de508/tr-606-bd.flac",
693
+ "ch": "/__l5e/assets-v1/f4c57254-c711-43b0-82c6-52b011e205ba/tr-606-ch.flac",
694
+ "cym": "/__l5e/assets-v1/db524ec1-36d3-43db-bfc4-fd84a1ddd2ac/tr-606-cym.flac",
695
+ "ht": "/__l5e/assets-v1/74b486cf-c1f1-45c7-bd2a-4fd0c3794e1a/tr-606-ht.flac",
696
+ "lt": "/__l5e/assets-v1/6b1a564f-6388-40c1-8028-3ea9b6824256/tr-606-lt.flac",
697
+ "mt": "/__l5e/assets-v1/d871a86a-fcc8-45a6-b5a4-83977a689593/tr-606-mt.flac",
698
+ "oh": "/__l5e/assets-v1/a4c607d2-d62c-49b5-ac99-549fd6262468/tr-606-oh.flac",
699
+ "sd": "/__l5e/assets-v1/2062f1d4-e34f-406f-911b-2eb2ec523a89/tr-606-sd.flac"
700
+ }
701
+ },
702
+ "compu-78": {
703
+ "name": "Compu-78",
704
+ "blurb": "Tiny, dry preset-box percussion. Bossa nova organ rhythm energy.",
705
+ "family": "Machines",
706
+ "credit": "Synthesised for Squelchy",
707
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
708
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
709
+ "voices": {
710
+ "bd": "/__l5e/assets-v1/150a91e2-8b92-40f2-80e0-10b30f10976b/compu-78-bd.flac",
711
+ "ch": "/__l5e/assets-v1/da9dcfc4-5f29-43b9-979f-9679edeb09ed/compu-78-ch.flac",
712
+ "clap": "/__l5e/assets-v1/f3b5d485-44bc-4b34-b2bc-d37f436dffc2/compu-78-clap.flac",
713
+ "cym": "/__l5e/assets-v1/627db2bf-08d5-4054-8de9-b8ec0ca4436a/compu-78-cym.flac",
714
+ "ht": "/__l5e/assets-v1/1323f20c-3c98-45fb-abd1-e4b1e4be158f/compu-78-ht.flac",
715
+ "lt": "/__l5e/assets-v1/ba67ce5c-3f01-4e1b-9fd8-a96bb5428cf4/compu-78-lt.flac",
716
+ "mt": "/__l5e/assets-v1/f8cd42d6-2f98-4e9d-9428-ba091ec125cf/compu-78-mt.flac",
717
+ "oh": "/__l5e/assets-v1/b4a1d5b2-0022-4f1b-ac52-1618ba03ea67/compu-78-oh.flac",
718
+ "perc": "/__l5e/assets-v1/55e15a92-ba17-48cb-aec0-47e5198f073f/compu-78-perc.flac",
719
+ "rim": "/__l5e/assets-v1/698aaf24-8e84-4b3b-8f13-dc95f2b3f157/compu-78-rim.flac",
720
+ "sd": "/__l5e/assets-v1/41357245-d767-410e-8415-08dbb9e7503e/compu-78-sd.flac"
721
+ }
722
+ },
723
+ "dr5-studio": {
724
+ "name": "Studio Kit",
725
+ "blurb": "Acoustic drums in a small dry room. Skin, air and a little ambience.",
726
+ "family": "Acoustic",
727
+ "credit": "MckAudio DR5 sample pack, dedicated to the public domain (CC0)",
728
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
729
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
730
+ "voices": {
731
+ "bd": "/__l5e/assets-v1/9bc4c40f-432c-4fd1-98f8-dc3343b5dd31/dr5-studio-bd.flac",
732
+ "ch": "/__l5e/assets-v1/862411a4-6d87-45e7-a3a2-2c4062f8e79b/dr5-studio-ch.flac",
733
+ "clap": "/__l5e/assets-v1/2feebc4f-49d2-4ea0-9b5a-890059c24a9c/dr5-studio-clap.flac",
734
+ "cym": "/__l5e/assets-v1/5abff1b5-b403-403b-b681-9ac37a394442/dr5-studio-cym.flac",
735
+ "ht": "/__l5e/assets-v1/4e6883a5-e310-4035-a6ae-ee4033c7a0a1/dr5-studio-ht.flac",
736
+ "lt": "/__l5e/assets-v1/faf35ae9-f89f-4ceb-ad5e-c562dd5d261a/dr5-studio-lt.flac",
737
+ "mt": "/__l5e/assets-v1/4b923b44-9e79-4aa0-a9ef-4178a9f0279b/dr5-studio-mt.flac",
738
+ "oh": "/__l5e/assets-v1/5a5e6d0c-4c8f-4dc5-9dea-55f90083a55a/dr5-studio-oh.flac",
739
+ "perc": "/__l5e/assets-v1/8d286dd0-4201-49f6-9738-0a39e3a3d860/dr5-studio-perc.flac",
740
+ "rim": "/__l5e/assets-v1/1d139e86-288e-48bd-baba-f7d2502271a5/dr5-studio-rim.flac",
741
+ "sd": "/__l5e/assets-v1/eb7b02d6-7063-4350-9515-40010dd583cb/dr5-studio-sd.flac"
742
+ }
743
+ },
744
+ "tape-dust": {
745
+ "name": "Tape Dust",
746
+ "blurb": "Wobbly, hissy and rolled-off. Everything through a tired cassette.",
747
+ "family": "Character",
748
+ "credit": "Synthesised for Squelchy",
749
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
750
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
751
+ "voices": {
752
+ "bd": "/__l5e/assets-v1/7e90ada0-1da8-4930-9c05-48c395e8a863/tape-dust-bd.flac",
753
+ "ch": "/__l5e/assets-v1/538fd73f-f7ee-408e-ab69-f8865506f4f5/tape-dust-ch.flac",
754
+ "clap": "/__l5e/assets-v1/21b5b5ea-6dce-495d-b740-e7162941c972/tape-dust-clap.flac",
755
+ "cym": "/__l5e/assets-v1/ac1a7f2d-9a96-4f61-b2ec-743854f15bbe/tape-dust-cym.flac",
756
+ "ht": "/__l5e/assets-v1/93dd3f9c-8267-4f4e-b292-807cf0022c9e/tape-dust-ht.flac",
757
+ "lt": "/__l5e/assets-v1/7a51ff67-62ec-408b-b6c3-4a0cae248eaf/tape-dust-lt.flac",
758
+ "mt": "/__l5e/assets-v1/ba3340c5-6ead-419a-836a-a041448f06da/tape-dust-mt.flac",
759
+ "oh": "/__l5e/assets-v1/e924553d-ac9a-427e-a69a-2939032ee618/tape-dust-oh.flac",
760
+ "perc": "/__l5e/assets-v1/abfad1e6-998b-47a8-8c98-6f511c151143/tape-dust-perc.flac",
761
+ "rim": "/__l5e/assets-v1/c86262d8-9711-41d4-b866-19a1ffcaf443/tape-dust-rim.flac",
762
+ "sd": "/__l5e/assets-v1/2d1090cf-0284-4c33-9ba2-430c5c7b4730/tape-dust-sd.flac"
763
+ }
764
+ },
765
+ "hard-cell": {
766
+ "name": "Hard Cell",
767
+ "blurb": "Overdriven and bit-crushed. Industrial techno with no headroom left.",
768
+ "family": "Character",
769
+ "credit": "Synthesised for Squelchy",
770
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
771
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
772
+ "voices": {
773
+ "bd": "/__l5e/assets-v1/d8f1c5cf-8e07-4a1e-a519-d7f69781329b/hard-cell-bd.flac",
774
+ "ch": "/__l5e/assets-v1/c3275e9a-dd79-4ac8-b4b9-84ebe25b5529/hard-cell-ch.flac",
775
+ "clap": "/__l5e/assets-v1/160a5d2c-01e0-49da-a998-149a0cf08317/hard-cell-clap.flac",
776
+ "cym": "/__l5e/assets-v1/910f3623-0209-439f-9e34-0c0f842bc78f/hard-cell-cym.flac",
777
+ "ht": "/__l5e/assets-v1/36f5b37b-9d44-49b8-a57b-bf5d2a1aa523/hard-cell-ht.flac",
778
+ "lt": "/__l5e/assets-v1/6065e512-578b-477f-88d9-49b3ccccacea/hard-cell-lt.flac",
779
+ "mt": "/__l5e/assets-v1/4e33c147-b8da-4b03-abca-4fd254d5006d/hard-cell-mt.flac",
780
+ "oh": "/__l5e/assets-v1/3e5b3bce-f21b-4ecb-8f28-375ce871427f/hard-cell-oh.flac",
781
+ "perc": "/__l5e/assets-v1/ab27e781-3d41-4e04-8f44-f7a568543586/hard-cell-perc.flac",
782
+ "rim": "/__l5e/assets-v1/ea2989c5-2354-4574-ae38-4786deeee151/hard-cell-rim.flac",
783
+ "sd": "/__l5e/assets-v1/5bd3dca9-bdb7-4618-8e0b-9ed1c0b062c7/hard-cell-sd.flac"
784
+ }
785
+ },
786
+ "rx5-fm": {
787
+ "name": "Yamaha RX5",
788
+ "blurb": "FM percussion \u2014 glassy, bell-like, unmistakably late-eighties digital.",
789
+ "family": "Digital",
790
+ "credit": "MckAudio RX5 sample pack, dedicated to the public domain (CC0)",
791
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
792
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
793
+ "voices": {
794
+ "bd": "/__l5e/assets-v1/22b27f9f-d301-48e2-a0e4-e6b6e538ec31/rx5-fm-bd.flac",
795
+ "ch": "/__l5e/assets-v1/236fb4f5-7d2a-48c0-be18-bc3777a6be91/rx5-fm-ch.flac",
796
+ "clap": "/__l5e/assets-v1/58402fc0-996c-4577-9074-bb40b585c26e/rx5-fm-clap.flac",
797
+ "cym": "/__l5e/assets-v1/d3e6ba9b-a2b8-4467-8923-97594723720e/rx5-fm-cym.flac",
798
+ "ht": "/__l5e/assets-v1/a5edef5d-7861-4b3a-99bc-10ece111ca64/rx5-fm-ht.flac",
799
+ "lt": "/__l5e/assets-v1/7151d30d-fe90-4ac5-9c9f-e91a808a1efa/rx5-fm-lt.flac",
800
+ "mt": "/__l5e/assets-v1/46119925-9ecd-431a-94ee-2135c8ce4382/rx5-fm-mt.flac",
801
+ "oh": "/__l5e/assets-v1/9fe4938f-fda7-43f8-84c6-c53325fd674c/rx5-fm-oh.flac",
802
+ "perc": "/__l5e/assets-v1/1aa471d4-4737-45e6-9070-6301892dcdfa/rx5-fm-perc.flac",
803
+ "rim": "/__l5e/assets-v1/2b0409a0-1aa6-41a4-9593-553247a2e624/rx5-fm-rim.flac",
804
+ "sd": "/__l5e/assets-v1/3d9c426c-7783-4106-940f-1e6b6405bdd5/rx5-fm-sd.flac"
805
+ }
806
+ },
807
+ "micro-click": {
808
+ "name": "Micro Click",
809
+ "blurb": "Clicks, ticks and pops. Minimal and micro-house, nothing sustains.",
810
+ "family": "Digital",
811
+ "credit": "Synthesised for Squelchy",
812
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
813
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
814
+ "voices": {
815
+ "bd": "/__l5e/assets-v1/58e3ce80-d300-47c6-8d79-52b00376550f/micro-click-bd.flac",
816
+ "ch": "/__l5e/assets-v1/1c12e400-5c5f-4aae-b006-5a3fe3c6ef2c/micro-click-ch.flac",
817
+ "clap": "/__l5e/assets-v1/b1f402f6-a85d-421b-9684-4b78719726d5/micro-click-clap.flac",
818
+ "cym": "/__l5e/assets-v1/fe501055-43d7-427f-9ab4-5efd0423292e/micro-click-cym.flac",
819
+ "ht": "/__l5e/assets-v1/3aab93cb-cd84-4600-a3e5-458fa74a2a86/micro-click-ht.flac",
820
+ "lt": "/__l5e/assets-v1/b09a0d61-c9a2-4b31-b0f2-10d3c10ef83c/micro-click-lt.flac",
821
+ "mt": "/__l5e/assets-v1/988e7a56-e914-43ad-aff1-71ec3a04b8e4/micro-click-mt.flac",
822
+ "oh": "/__l5e/assets-v1/122a3f93-4542-471b-b390-996665248653/micro-click-oh.flac",
823
+ "perc": "/__l5e/assets-v1/cae413d7-a68c-411a-a4b4-b7c20a83946d/micro-click-perc.flac",
824
+ "rim": "/__l5e/assets-v1/41922292-1c6e-4bef-ac8e-fa1ce09eefca/micro-click-rim.flac",
825
+ "sd": "/__l5e/assets-v1/6218ccdd-cba1-466e-af8b-665d7a2d4640/micro-click-sd.flac"
826
+ }
827
+ },
828
+ "deep-analogue": {
829
+ "name": "Deep Analogue",
830
+ "blurb": "Long sub kick, soft snare, warm hats. Deep house at three in the morning.",
831
+ "family": "Acoustic",
832
+ "credit": "Synthesised for Squelchy",
833
+ "license": "CC0 1.0 / public domain \u2014 free to use, no attribution required",
834
+ "licenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/",
835
+ "voices": {
836
+ "bd": "/__l5e/assets-v1/93c55e12-44f1-4cf9-8b19-82833257a68f/deep-analogue-bd.flac",
837
+ "ch": "/__l5e/assets-v1/7c881111-83d8-4c1a-b4af-4f09d2a09f4b/deep-analogue-ch.flac",
838
+ "clap": "/__l5e/assets-v1/e8b67bb8-8f7f-4b87-9ee4-d9cb7bc43efe/deep-analogue-clap.flac",
839
+ "cym": "/__l5e/assets-v1/b329f9e1-3eb0-471c-b7c7-8ad7f639323f/deep-analogue-cym.flac",
840
+ "ht": "/__l5e/assets-v1/a381a6b5-fd09-4fa6-b88e-86a29b8f25d9/deep-analogue-ht.flac",
841
+ "lt": "/__l5e/assets-v1/c9ebc90a-20aa-4f76-9e79-f440258d4bac/deep-analogue-lt.flac",
842
+ "mt": "/__l5e/assets-v1/5e1b803c-748e-4f09-bed7-c7b4511dc8b0/deep-analogue-mt.flac",
843
+ "oh": "/__l5e/assets-v1/289d67f2-6d55-4a04-8607-302442465e81/deep-analogue-oh.flac",
844
+ "perc": "/__l5e/assets-v1/8ef1f592-a3a4-45a5-9ea4-80362953aee4/deep-analogue-perc.flac",
845
+ "rim": "/__l5e/assets-v1/102a1c23-6e5f-468e-95c8-e414ce849884/deep-analogue-rim.flac",
846
+ "sd": "/__l5e/assets-v1/9be98c57-13c4-4337-bb2f-53c0b2ff7150/deep-analogue-sd.flac"
847
+ }
848
+ }
849
+ });
850
+
851
+ // src/modules/ModuleDescriptor.ts
852
+ function paramDefaults(params50) {
853
+ const out = {};
854
+ for (const k in params50) out[k] = params50[k].default;
855
+ return out;
856
+ }
857
+ function defineDescriptor(desc) {
858
+ if (desc.params && !desc.controlDescriptions) {
859
+ const cd = {};
860
+ for (const p of Object.values(desc.params)) {
861
+ if (p.kind !== "enum" && p.label) cd[p.label] = { short: p.short, long: p.long };
862
+ }
863
+ return { ...desc, controlDescriptions: cd };
864
+ }
865
+ return desc;
866
+ }
867
+
868
+ // src/lib-runtime/generated/worklet-factories.ts
869
+ function createVoctFreqNode(ctx) {
870
+ return new AudioWorkletNode(ctx, "map-voct-to-freq", {
871
+ numberOfInputs: 1,
872
+ numberOfOutputs: 1,
873
+ outputChannelCount: [1]
874
+ });
875
+ }
876
+ function createVoctPeriodNode(ctx) {
877
+ return new AudioWorkletNode(ctx, "map-voct-to-period", {
878
+ numberOfInputs: 1,
879
+ numberOfOutputs: 1,
880
+ outputChannelCount: [1]
881
+ });
882
+ }
883
+ function createAdsrNode(ctx, init) {
884
+ const node = new AudioWorkletNode(ctx, "adsr-envelope", {
885
+ numberOfInputs: 1,
886
+ numberOfOutputs: 1,
887
+ outputChannelCount: [1]
888
+ });
889
+ if (init) {
890
+ for (const [k, v] of Object.entries(init)) {
891
+ if (v === void 0) continue;
892
+ const p = node.parameters.get(k);
893
+ if (p) p.value = v;
894
+ }
895
+ }
896
+ return node;
897
+ }
898
+ function createFormantNode(ctx) {
899
+ return new AudioWorkletNode(ctx, "formant-table", {
900
+ numberOfInputs: 1,
901
+ numberOfOutputs: 1,
902
+ outputChannelCount: [5]
903
+ });
904
+ }
905
+ function createBitcrusherNode(ctx) {
906
+ return new AudioWorkletNode(ctx, "bitcrusher", {
907
+ numberOfInputs: 1,
908
+ numberOfOutputs: 1,
909
+ outputChannelCount: [1]
910
+ });
911
+ }
912
+ function createSubOctaveNode(ctx) {
913
+ return new AudioWorkletNode(ctx, "freq-divider", {
914
+ numberOfInputs: 1,
915
+ numberOfOutputs: 1,
916
+ outputChannelCount: [1]
917
+ });
918
+ }
919
+ function createEnvFollowerNode(ctx) {
920
+ return new AudioWorkletNode(ctx, "env-follower", {
921
+ numberOfInputs: 1,
922
+ numberOfOutputs: 1,
923
+ outputChannelCount: [1]
924
+ });
925
+ }
926
+ function createWaveshaperNode(ctx) {
927
+ return new AudioWorkletNode(ctx, "waveshaper", {
928
+ numberOfInputs: 1,
929
+ numberOfOutputs: 1,
930
+ outputChannelCount: [1]
931
+ });
932
+ }
933
+ function createGlideNode(ctx) {
934
+ return new AudioWorkletNode(ctx, "glide", {
935
+ numberOfInputs: 2,
936
+ numberOfOutputs: 1,
937
+ outputChannelCount: [1]
938
+ });
939
+ }
940
+ function createTapeStopNode(ctx, time = 0.5, mix = 1) {
941
+ return new AudioWorkletNode(ctx, "tape-stop", {
942
+ numberOfInputs: 2,
943
+ numberOfOutputs: 1,
944
+ outputChannelCount: [2],
945
+ channelCount: 2,
946
+ channelCountMode: "explicit",
947
+ parameterData: { time, mix }
948
+ });
949
+ }
950
+ function createSpringReverbNode(ctx, params50) {
951
+ return new AudioWorkletNode(ctx, "spring-reverb", {
952
+ numberOfInputs: 2,
953
+ numberOfOutputs: 1,
954
+ outputChannelCount: [2],
955
+ channelCount: 2,
956
+ channelCountMode: "explicit",
957
+ parameterData: { decay: params50.decay, tone: params50.tone, drip: params50.drip }
958
+ });
959
+ }
960
+ function createFdnReverbNode(ctx, opts) {
961
+ return new AudioWorkletNode(ctx, "fdn-reverb", {
962
+ numberOfInputs: 1,
963
+ numberOfOutputs: 1,
964
+ // Stereo tank: L/R in (mono upmixes to L=R → identical mid), L/R out.
965
+ outputChannelCount: [2],
966
+ channelCount: 2,
967
+ channelCountMode: "explicit",
968
+ parameterData: { rt60: opts.rt60, damping: opts.damping },
969
+ processorOptions: {
970
+ size: opts.size ?? 1,
971
+ feedbackCeiling: opts.feedbackCeiling ?? 0.97
972
+ }
973
+ });
974
+ }
975
+ function createFreqShifterNode(ctx, shift = 0) {
976
+ return new AudioWorkletNode(ctx, "freq-shifter", {
977
+ numberOfInputs: 1,
978
+ numberOfOutputs: 1,
979
+ outputChannelCount: [1],
980
+ parameterData: { shift }
981
+ });
982
+ }
983
+ function createOctaverDividerNode(ctx) {
984
+ return new AudioWorkletNode(ctx, "freq-divider", {
985
+ numberOfInputs: 1,
986
+ numberOfOutputs: 3,
987
+ outputChannelCount: [1, 2, 1]
988
+ });
989
+ }
990
+ function createPitchShifterNode(ctx, semitones2 = 12) {
991
+ return new AudioWorkletNode(ctx, "pitch-shifter", {
992
+ numberOfInputs: 1,
993
+ numberOfOutputs: 1,
994
+ outputChannelCount: [1],
995
+ channelCount: 1,
996
+ channelCountMode: "explicit",
997
+ parameterData: { semitones: semitones2 }
998
+ });
999
+ }
1000
+ function createVcaNode(ctx, gain = 0.5, cvAmount = 1) {
1001
+ return new AudioWorkletNode(ctx, "vca", {
1002
+ numberOfInputs: 1,
1003
+ numberOfOutputs: 1,
1004
+ parameterData: { gain, cvAmount }
1005
+ });
1006
+ }
1007
+ function createLadderFilterNode(ctx, init) {
1008
+ return new AudioWorkletNode(ctx, "ladder-filter", {
1009
+ numberOfInputs: 1,
1010
+ numberOfOutputs: 1,
1011
+ parameterData: {
1012
+ cutoff: 0,
1013
+ resonance: init?.resonance ?? 0.6,
1014
+ drive: init?.drive ?? 0.3
1015
+ }
1016
+ });
1017
+ }
1018
+ function createMapResNode(ctx, init) {
1019
+ const qMin = init?.qMin ?? 0.1;
1020
+ const qMax = init?.qMax ?? 20;
1021
+ return new AudioWorkletNode(ctx, "map-res", {
1022
+ numberOfInputs: 1,
1023
+ numberOfOutputs: 1,
1024
+ parameterData: {
1025
+ base: init?.base ?? 1,
1026
+ cvScale: init?.cvScale ?? qMax - qMin,
1027
+ qMin,
1028
+ qMax,
1029
+ dbOut: init?.dbOut === false ? 0 : 1
1030
+ }
1031
+ });
1032
+ }
1033
+ function createCombFilterNode(ctx, feedback = 0.7, maxPeriod = 0.1, channels = 1) {
1034
+ return new AudioWorkletNode(ctx, "comb-filter", {
1035
+ numberOfInputs: 1,
1036
+ numberOfOutputs: 1,
1037
+ outputChannelCount: [channels],
1038
+ channelCount: channels,
1039
+ channelCountMode: "explicit",
1040
+ channelInterpretation: "speakers",
1041
+ parameterData: { period: 0, feedback },
1042
+ processorOptions: { maxPeriod }
1043
+ });
1044
+ }
1045
+ function createFlangerNode(ctx, opts) {
1046
+ return new AudioWorkletNode(ctx, "flanger", {
1047
+ numberOfInputs: 1,
1048
+ numberOfOutputs: 1,
1049
+ parameterData: { rate: opts.rate, depth: opts.depth, feedback: opts.feedback }
1050
+ });
1051
+ }
1052
+ function createTapeDelayNode(ctx, opts) {
1053
+ return new AudioWorkletNode(ctx, "tape-delay", {
1054
+ numberOfInputs: 1,
1055
+ numberOfOutputs: 1,
1056
+ parameterData: { ...opts }
1057
+ });
1058
+ }
1059
+ function createPingPongNode(ctx, opts) {
1060
+ return new AudioWorkletNode(ctx, "ping-pong", {
1061
+ numberOfInputs: 1,
1062
+ numberOfOutputs: 2,
1063
+ parameterData: { ...opts }
1064
+ });
1065
+ }
1066
+
1067
+ // src/lib-runtime/generated/modules/303-filter.tsx
1068
+ var params = {
1069
+ note: { node: "_cutoffVoct", field: "transpose", unit: "note", default: 67, min: 15, max: 135 },
1070
+ // Split across two cascaded biquads' Q (0.7x / 0.4x) — not a single
1071
+ // AudioParam — so this stays set-only.
1072
+ resonance: { unit: "factor", default: 8, min: 0, max: 15, scale: "sqrt" },
1073
+ envMod: { unit: "factor", default: 0.7, min: 0, max: 1 },
1074
+ accent: { unit: "factor", default: 0.5, min: 0, max: 1 },
1075
+ decay: { node: "envSmoother", field: "decay", unit: "s", default: 0.2, min: 0.05, max: 1, scale: "log" }
1076
+ };
1077
+ var filter303Descriptor = /* @__PURE__ */ defineDescriptor({
1078
+ type: "303-filter",
1079
+ params,
1080
+ ports: {
1081
+ "in": { type: "input", signal: "audio", node: "input" },
1082
+ "out": { type: "output", signal: "audio", node: "output" },
1083
+ "trigger": { type: "input", signal: "gate", node: "trigInput" },
1084
+ "modFreq": { type: "modulation", signal: "modulation", node: "_cutoffVoct" }
1085
+ },
1086
+ createModule(id, position) {
1087
+ return { id, type: "303-filter", position, ...paramDefaults(params) };
1088
+ },
1089
+ createNodes(ctx, module) {
1090
+ const input = ctx.createGain();
1091
+ const filter1 = ctx.createBiquadFilter();
1092
+ filter1.type = "lowpass";
1093
+ filter1.frequency.value = 0;
1094
+ filter1.Q.value = module.resonance * 0.7;
1095
+ const filter2 = ctx.createBiquadFilter();
1096
+ filter2.type = "lowpass";
1097
+ filter2.frequency.value = 0;
1098
+ filter2.Q.value = module.resonance * 0.4;
1099
+ const accentVCA = ctx.createGain();
1100
+ accentVCA.gain.value = 1;
1101
+ const output = ctx.createGain();
1102
+ const _cutoffVoct = createVoctFreqNode(ctx);
1103
+ _cutoffVoct.parameters.get("transpose").value = module.note;
1104
+ const _cutoffBase = ctx.createConstantSource();
1105
+ _cutoffBase.offset.value = 0;
1106
+ _cutoffBase.start();
1107
+ _cutoffBase.connect(_cutoffVoct);
1108
+ _cutoffVoct.connect(filter1.frequency);
1109
+ _cutoffVoct.connect(filter2.frequency);
1110
+ const trigInput = ctx.createGain();
1111
+ const envSmoother = createAdsrNode(ctx, {
1112
+ attack: 3e-3,
1113
+ decay: module.decay,
1114
+ sustain: 0,
1115
+ release: 0.01
1116
+ });
1117
+ const envDepth = ctx.createGain();
1118
+ envDepth.gain.value = module.envMod * (1 + module.accent) * 36;
1119
+ const accentEnvGain = ctx.createGain();
1120
+ accentEnvGain.gain.value = module.accent * 0.5;
1121
+ input.connect(filter1);
1122
+ filter1.connect(filter2);
1123
+ filter2.connect(accentVCA);
1124
+ accentVCA.connect(output);
1125
+ trigInput.connect(envSmoother);
1126
+ envSmoother.connect(envDepth);
1127
+ envDepth.connect(_cutoffVoct);
1128
+ envSmoother.connect(accentEnvGain);
1129
+ accentEnvGain.connect(accentVCA.gain);
1130
+ return { input, output, trigInput, filter1, filter2, accentVCA, accentEnvGain, envSmoother, envDepth, _cutoffVoct, _cutoffBase };
1131
+ },
1132
+ updateParam(nodes, module, param, value) {
1133
+ switch (param) {
1134
+ case "note":
1135
+ nodes._cutoffVoct.parameters.get("transpose").value = value;
1136
+ break;
1137
+ case "resonance": {
1138
+ const q = value;
1139
+ nodes.filter1.Q.value = q * 0.7;
1140
+ nodes.filter2.Q.value = q * 0.4;
1141
+ break;
1142
+ }
1143
+ case "envMod":
1144
+ nodes.envDepth.gain.value = value * (1 + module.accent) * 36;
1145
+ break;
1146
+ case "accent": {
1147
+ const acc = value;
1148
+ nodes.envDepth.gain.value = module.envMod * (1 + acc) * 36;
1149
+ nodes.accentEnvGain.gain.value = acc * 0.5;
1150
+ break;
1151
+ }
1152
+ case "decay": {
1153
+ const p = nodes.envSmoother.parameters.get("decay");
1154
+ if (p) p.value = value;
1155
+ break;
1156
+ }
1157
+ }
1158
+ },
1159
+ dispose(nodes) {
1160
+ nodes._cutoffBase.stop();
1161
+ }
1162
+ });
1163
+
1164
+ // src/lib-runtime/generated/modules/303-filter.wrapper.ts
1165
+ var AcidFilter = /* @__PURE__ */ defineModule(filter303Descriptor, {
1166
+ worklets: {
1167
+ "adsr-envelope": adsr_envelope_default,
1168
+ "map-voct-to-freq": map_voct_to_freq_default
1169
+ }
1170
+ });
1171
+
1172
+ // src/lib-runtime/generated/modules/auto-pan.tsx
1173
+ var shapes = ["sine", "triangle", "square"];
1174
+ var params2 = {
1175
+ rate: { node: "lfo", field: "frequency", unit: "Hz", default: 2, min: 0.1, max: 10 },
1176
+ depth: { node: "lfoGain", field: "gain", unit: "factor", default: 0.8, min: 0, max: 1 },
1177
+ shape: { kind: "enum", options: shapes, default: "sine" }
1178
+ };
1179
+ var autoPanDescriptor = /* @__PURE__ */ defineDescriptor({
1180
+ type: "auto-pan",
1181
+ params: params2,
1182
+ ports: {
1183
+ "in": { type: "input", signal: "audio", node: "input" },
1184
+ "out": { type: "output", signal: "audio", node: "output" },
1185
+ "modRate": { type: "modulation", signal: "modulation", node: "lfo", field: "frequency" },
1186
+ "modDepth": { type: "modulation", signal: "modulation", node: "lfoGain", field: "gain" }
1187
+ },
1188
+ createModule(id, position) {
1189
+ return { id, type: "auto-pan", position, ...paramDefaults(params2) };
1190
+ },
1191
+ createNodes(ctx, module) {
1192
+ const input = ctx.createGain();
1193
+ const panner = ctx.createStereoPanner();
1194
+ const output = ctx.createGain();
1195
+ const lfo = ctx.createOscillator();
1196
+ const lfoGain = ctx.createGain();
1197
+ lfo.type = module.shape;
1198
+ lfo.frequency.value = module.rate;
1199
+ lfoGain.gain.value = module.depth;
1200
+ lfo.connect(lfoGain);
1201
+ lfoGain.connect(panner.pan);
1202
+ lfo.start();
1203
+ input.connect(panner);
1204
+ panner.connect(output);
1205
+ return { input, output, lfo, lfoGain };
1206
+ },
1207
+ updateParam(nodes, _m, param, value) {
1208
+ if (param === "rate") {
1209
+ nodes.lfo.frequency.value = value;
1210
+ } else if (param === "depth") {
1211
+ nodes.lfoGain.gain.value = value;
1212
+ } else if (param === "shape") {
1213
+ nodes.lfo.type = value;
1214
+ }
1215
+ },
1216
+ dispose(nodes) {
1217
+ nodes.lfo.stop();
1218
+ }
1219
+ });
1220
+
1221
+ // src/lib-runtime/generated/modules/auto-pan.wrapper.ts
1222
+ var AutoPan = /* @__PURE__ */ defineModule(autoPanDescriptor);
1223
+
1224
+ // src/lib/dsp/mix.ts
1225
+ var CURVE_POINTS = 2049;
1226
+ function lawCurve(fn) {
1227
+ const c = new Float32Array(CURVE_POINTS);
1228
+ for (let i = 0; i < CURVE_POINTS; i++) {
1229
+ const x = -1 + 2 * i / (CURVE_POINTS - 1);
1230
+ c[i] = fn(Math.min(1, Math.max(0, x)));
1231
+ }
1232
+ return c;
1233
+ }
1234
+ function createMix(ctx, dry, wet, value, law, wetScale = 1) {
1235
+ const mix = ctx.createConstantSource();
1236
+ mix.offset.value = value;
1237
+ let toWet = mix;
1238
+ if (law === "linear") {
1239
+ dry.gain.value = 1;
1240
+ const invert = ctx.createGain();
1241
+ invert.gain.value = -1;
1242
+ mix.connect(invert).connect(dry.gain);
1243
+ } else {
1244
+ dry.gain.value = 0;
1245
+ const cos = ctx.createWaveShaper();
1246
+ cos.curve = lawCurve((m) => Math.cos(m * 0.5 * Math.PI));
1247
+ const sin = ctx.createWaveShaper();
1248
+ sin.curve = lawCurve((m) => Math.sin(m * 0.5 * Math.PI));
1249
+ mix.connect(cos).connect(dry.gain);
1250
+ toWet = mix.connect(sin);
1251
+ }
1252
+ wet.gain.value = 0;
1253
+ if (wetScale !== 1) {
1254
+ const scale = ctx.createGain();
1255
+ scale.gain.value = wetScale;
1256
+ toWet = toWet.connect(scale);
1257
+ }
1258
+ toWet.connect(wet.gain);
1259
+ mix.start();
1260
+ return mix;
1261
+ }
1262
+
1263
+ // src/lib-runtime/generated/modules/bitcrusher.tsx
1264
+ var params3 = {
1265
+ bits: { node: "crusher", field: "bits", unit: "factor", default: 8, min: 1, max: 16 },
1266
+ rate: { node: "crusher", field: "rate", unit: "factor", default: 0.5, min: 0.01, max: 1, scale: "log" },
1267
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 1, min: 0, max: 1 }
1268
+ };
1269
+ var bitcrusherDescriptor = /* @__PURE__ */ defineDescriptor({
1270
+ type: "bitcrusher",
1271
+ params: params3,
1272
+ ports: {
1273
+ "in": { type: "input", signal: "audio", node: "input" },
1274
+ "out": { type: "output", signal: "audio", node: "output" }
1275
+ },
1276
+ createModule(id, position) {
1277
+ return { id, type: "bitcrusher", position, ...paramDefaults(params3) };
1278
+ },
1279
+ createNodes(ctx, module) {
1280
+ const input = ctx.createGain();
1281
+ const output = ctx.createGain();
1282
+ const dryGain = ctx.createGain();
1283
+ const wetGain = ctx.createGain();
1284
+ const crusher = createBitcrusherNode(ctx);
1285
+ const cp = crusher.parameters;
1286
+ cp.get("bits").value = module.bits;
1287
+ cp.get("rate").value = module.rate;
1288
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
1289
+ input.connect(dryGain);
1290
+ input.connect(crusher);
1291
+ crusher.connect(wetGain);
1292
+ dryGain.connect(output);
1293
+ wetGain.connect(output);
1294
+ return { input, output, crusher, dryGain, wetGain, _mix };
1295
+ },
1296
+ updateParam(nodes, _m, param, value) {
1297
+ if (param === "bits") {
1298
+ nodes.crusher.parameters.get("bits").value = value;
1299
+ } else if (param === "rate") {
1300
+ nodes.crusher.parameters.get("rate").value = value;
1301
+ } else if (param === "mix") {
1302
+ nodes._mix.offset.value = value;
1303
+ }
1304
+ }
1305
+ });
1306
+
1307
+ // src/lib-runtime/generated/modules/bitcrusher.wrapper.ts
1308
+ var Bitcrusher = /* @__PURE__ */ defineModule(bitcrusherDescriptor, {
1309
+ worklets: {
1310
+ "bitcrusher": bitcrusher_default
1311
+ }
1312
+ });
1313
+
1314
+ // src/lib-runtime/generated/modules/chorus.tsx
1315
+ var params4 = {
1316
+ rate: { node: "lfo", field: "frequency", unit: "Hz", default: 1.5, min: 0.1, max: 5 },
1317
+ depth: { unit: "factor", default: 0.5, min: 0, max: 1 },
1318
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.5, min: 0, max: 1 }
1319
+ };
1320
+ var chorusDescriptor = /* @__PURE__ */ defineDescriptor({
1321
+ type: "chorus",
1322
+ params: params4,
1323
+ ports: {
1324
+ "in": { type: "input", signal: "audio", node: "input" },
1325
+ "out": { type: "output", signal: "audio", node: "output" },
1326
+ "modRate": { type: "modulation", signal: "modulation", node: "lfo", field: "frequency" },
1327
+ "modMix": { type: "modulation", signal: "modulation", node: "_mix", field: "offset" }
1328
+ },
1329
+ createModule(id, position) {
1330
+ return { id, type: "chorus", position, ...paramDefaults(params4) };
1331
+ },
1332
+ createNodes(ctx, module) {
1333
+ const input = ctx.createGain();
1334
+ const output = ctx.createGain();
1335
+ const dryGain = ctx.createGain();
1336
+ const wetGain = ctx.createGain();
1337
+ const monoIn = ctx.createGain();
1338
+ monoIn.channelCount = 1;
1339
+ monoIn.channelCountMode = "explicit";
1340
+ monoIn.channelInterpretation = "speakers";
1341
+ const delayL = ctx.createDelay(0.1);
1342
+ const delayR = ctx.createDelay(0.1);
1343
+ const lfo = ctx.createOscillator();
1344
+ const lfoGain = ctx.createGain();
1345
+ const lfoInv = ctx.createGain();
1346
+ const merger = ctx.createChannelMerger(2);
1347
+ delayL.delayTime.value = 0.02;
1348
+ delayR.delayTime.value = 0.02;
1349
+ const _mix = ctx.createConstantSource();
1350
+ _mix.offset.value = module.mix;
1351
+ dryGain.gain.value = 1;
1352
+ const dryTap = ctx.createGain();
1353
+ dryTap.gain.value = -0.5;
1354
+ _mix.connect(dryTap).connect(dryGain.gain);
1355
+ wetGain.gain.value = 0;
1356
+ _mix.connect(wetGain.gain);
1357
+ _mix.start();
1358
+ lfo.type = "sine";
1359
+ lfo.frequency.value = module.rate;
1360
+ lfoGain.gain.value = module.depth * 0.01;
1361
+ lfoInv.gain.value = -1;
1362
+ lfo.connect(lfoGain);
1363
+ lfoGain.connect(delayL.delayTime);
1364
+ lfoGain.connect(lfoInv);
1365
+ lfoInv.connect(delayR.delayTime);
1366
+ lfo.start();
1367
+ input.connect(dryGain);
1368
+ input.connect(monoIn);
1369
+ monoIn.connect(delayL);
1370
+ monoIn.connect(delayR);
1371
+ delayL.connect(merger, 0, 0);
1372
+ delayR.connect(merger, 0, 1);
1373
+ merger.connect(wetGain);
1374
+ dryGain.connect(output);
1375
+ wetGain.connect(output);
1376
+ return { input, output, lfo, lfoGain, lfoInv, dryGain, wetGain, monoIn, delayL, delayR, merger, _mix };
1377
+ },
1378
+ updateParam(nodes, _m, param, value) {
1379
+ switch (param) {
1380
+ case "rate":
1381
+ nodes.lfo.frequency.value = value;
1382
+ break;
1383
+ case "depth":
1384
+ nodes.lfoGain.gain.value = value * 0.01;
1385
+ break;
1386
+ case "mix":
1387
+ nodes._mix.offset.value = value;
1388
+ break;
1389
+ }
1390
+ },
1391
+ dispose(nodes) {
1392
+ nodes.lfo.stop();
1393
+ nodes._mix.stop();
1394
+ }
1395
+ });
1396
+
1397
+ // src/lib-runtime/generated/modules/chorus.wrapper.ts
1398
+ var Chorus = /* @__PURE__ */ defineModule(chorusDescriptor);
1399
+
1400
+ // src/types/synth.ts
1401
+ function hzToMidi(hz2) {
1402
+ return 12 * Math.log2(hz2 / 440) + 69;
1403
+ }
1404
+
1405
+ // src/lib-runtime/generated/modules/comb-filter.tsx
1406
+ var params5 = {
1407
+ offset: { node: "_freqVoct", field: "transpose", default: 55, min: -48, max: 96, unit: "semitone" },
1408
+ feedback: { node: "comb", field: "feedback", unit: "factor", default: 0.7, min: 0, max: 0.95 },
1409
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.5, min: 0, max: 1 },
1410
+ tuning: { kind: "enum", options: ["chromatic", "free"], default: "chromatic" }
1411
+ };
1412
+ var patchCount = /* @__PURE__ */ new WeakMap();
1413
+ var combFilterDescriptor = /* @__PURE__ */ defineDescriptor({
1414
+ type: "comb-filter",
1415
+ params: params5,
1416
+ // Panel controls diverge from params (the ribbon edits two memories that
1417
+ // feed `offset`), so they are listed here, not derived from labels.
1418
+ ports: {
1419
+ "in": { type: "input", signal: "audio", node: "input" },
1420
+ "out": { type: "output", signal: "audio", node: "output" },
1421
+ "voct": { type: "input", signal: "pitch", node: "_freqVoct" },
1422
+ "modFreq": { type: "modulation", signal: "modulation", node: "_freqVoct" }
1423
+ },
1424
+ createModule(id, position) {
1425
+ const { offset: _dsp, ...rest } = paramDefaults(params5);
1426
+ return { id, type: "comb-filter", position, ...rest, frequency: 200, transpose: 0 };
1427
+ },
1428
+ createNodes(ctx, module) {
1429
+ const input = ctx.createGain();
1430
+ const output = ctx.createGain();
1431
+ const dryGain = ctx.createGain();
1432
+ const wetGain = ctx.createGain();
1433
+ const comb = createCombFilterNode(ctx, module.feedback);
1434
+ const _freqVoct = createVoctPeriodNode(ctx);
1435
+ _freqVoct.parameters.get("transpose").value = module.offset ?? hzToMidi(module.frequency);
1436
+ const periodParam = comb.parameters.get("period");
1437
+ periodParam.value = 0;
1438
+ _freqVoct.connect(periodParam);
1439
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
1440
+ input.connect(dryGain);
1441
+ input.connect(comb);
1442
+ comb.connect(wetGain);
1443
+ dryGain.connect(output);
1444
+ wetGain.connect(output);
1445
+ return { input, output, comb, dryGain, wetGain, _freqVoct, _mix };
1446
+ },
1447
+ updateParam(nodes, _m, param, value) {
1448
+ const t = nodes._freqVoct.parameters.get("transpose");
1449
+ if (param === "offset") {
1450
+ t.value = value;
1451
+ } else if (param === "frequency") {
1452
+ if (!patchCount.get(nodes)) t.value = hzToMidi(value);
1453
+ } else if (param === "transpose") {
1454
+ if (patchCount.get(nodes)) t.value = value;
1455
+ } else if (param === "feedback") {
1456
+ nodes.comb.parameters.get("feedback").value = value;
1457
+ } else if (param === "mix") {
1458
+ nodes._mix.offset.value = value;
1459
+ }
1460
+ },
1461
+ // App-only: plain summing connect; the hooks only swap which panel memory
1462
+ // drives the offset.
1463
+ onConnect(nodes, module, portId, sourceNode) {
1464
+ if (portId !== "voct") return false;
1465
+ const v = nodes._freqVoct;
1466
+ sourceNode.connect(v);
1467
+ patchCount.set(nodes, (patchCount.get(nodes) ?? 0) + 1);
1468
+ v.parameters.get("transpose").value = module.transpose ?? 0;
1469
+ return true;
1470
+ },
1471
+ onDisconnect(nodes, module, portId, sourceNode) {
1472
+ if (portId !== "voct") return false;
1473
+ const v = nodes._freqVoct;
1474
+ try {
1475
+ sourceNode.disconnect(v);
1476
+ } catch (e) {
1477
+ }
1478
+ const n = Math.max(0, (patchCount.get(nodes) ?? 0) - 1);
1479
+ patchCount.set(nodes, n);
1480
+ if (!n) v.parameters.get("transpose").value = hzToMidi(module.frequency);
1481
+ return true;
1482
+ }
1483
+ });
1484
+
1485
+ // src/lib-runtime/generated/modules/comb-filter.wrapper.ts
1486
+ var CombFilter = /* @__PURE__ */ defineModule(combFilterDescriptor, {
1487
+ worklets: {
1488
+ "map-voct-to-period": map_voct_to_period_default,
1489
+ "comb-filter": comb_filter_default
1490
+ }
1491
+ });
1492
+
1493
+ // src/lib-runtime/generated/modules/comp.tsx
1494
+ var params6 = {
1495
+ threshold: { node: "comp", field: "threshold", unit: "dB", default: -24, min: -60, max: 0 },
1496
+ ratio: { node: "comp", field: "ratio", unit: "factor", default: 4, min: 1, max: 20, scale: "log" },
1497
+ attack: { node: "comp", field: "attack", unit: "s", default: 3e-3, min: 1e-3, max: 1, scale: "log" },
1498
+ release: { node: "comp", field: "release", unit: "s", default: 0.25, min: 0.01, max: 1, scale: "log" },
1499
+ knee: { node: "comp", field: "knee", unit: "dB", default: 10, min: 0, max: 40 },
1500
+ // makeup is stored in dB but drives a linear gain node (Math.pow(10, v/20)) —
1501
+ // not a 1:1 AudioParam mapping, so no node/field.
1502
+ makeup: { unit: "dB", default: 0, min: 0, max: 30 }
1503
+ };
1504
+ var compressorDescriptor = /* @__PURE__ */ defineDescriptor({
1505
+ type: "compressor",
1506
+ params: params6,
1507
+ ports: {
1508
+ "in": { type: "input", signal: "audio", node: "comp" },
1509
+ "out": { type: "output", signal: "audio", node: "makeup" }
1510
+ },
1511
+ createModule(id, position) {
1512
+ return { id, type: "compressor", position, ...paramDefaults(params6) };
1513
+ },
1514
+ createNodes(ctx, module) {
1515
+ const comp = ctx.createDynamicsCompressor();
1516
+ comp.threshold.value = module.threshold;
1517
+ comp.ratio.value = module.ratio;
1518
+ comp.attack.value = module.attack;
1519
+ comp.release.value = module.release;
1520
+ comp.knee.value = module.knee;
1521
+ const makeup = ctx.createGain();
1522
+ makeup.gain.value = Math.pow(10, (module.makeup || 0) / 20);
1523
+ comp.connect(makeup);
1524
+ return { comp, makeup };
1525
+ },
1526
+ updateParam(nodes, _m, param, value) {
1527
+ const v = value;
1528
+ switch (param) {
1529
+ case "threshold":
1530
+ nodes.comp.threshold.value = v;
1531
+ break;
1532
+ case "ratio":
1533
+ nodes.comp.ratio.value = v;
1534
+ break;
1535
+ case "attack":
1536
+ nodes.comp.attack.value = v;
1537
+ break;
1538
+ case "release":
1539
+ nodes.comp.release.value = v;
1540
+ break;
1541
+ case "knee":
1542
+ nodes.comp.knee.value = v;
1543
+ break;
1544
+ case "makeup":
1545
+ nodes.makeup.gain.value = Math.pow(10, v / 20);
1546
+ break;
1547
+ }
1548
+ }
1549
+ });
1550
+
1551
+ // src/lib-runtime/generated/modules/comp.wrapper.ts
1552
+ var Compressor = /* @__PURE__ */ defineModule(compressorDescriptor);
1553
+
1554
+ // src/lib-runtime/generated/modules/crossfade.tsx
1555
+ var params7 = {
1556
+ blend: { node: "_mix", field: "offset", unit: "factor", default: 0.5, min: 0, max: 1 },
1557
+ curve: { kind: "enum", default: "equal-power", options: ["linear", "equal-power"] }
1558
+ };
1559
+ var CURVE_POINTS2 = 2049;
1560
+ function lawCurve2(fn) {
1561
+ const c = new Float32Array(CURVE_POINTS2);
1562
+ for (let i = 0; i < CURVE_POINTS2; i++) {
1563
+ const x = -1 + 2 * i / (CURVE_POINTS2 - 1);
1564
+ c[i] = fn(Math.min(1, Math.max(0, x)));
1565
+ }
1566
+ return c;
1567
+ }
1568
+ function curvesFor(curve) {
1569
+ if (curve === "equal-power") {
1570
+ return {
1571
+ a: lawCurve2((m) => Math.cos(m * Math.PI / 2)),
1572
+ b: lawCurve2((m) => Math.sin(m * Math.PI / 2))
1573
+ };
1574
+ }
1575
+ return {
1576
+ a: lawCurve2((m) => 1 - m),
1577
+ b: lawCurve2((m) => m)
1578
+ };
1579
+ }
1580
+ var crossfadeDescriptor = /* @__PURE__ */ defineDescriptor({
1581
+ type: "crossfade",
1582
+ params: params7,
1583
+ ports: {
1584
+ "a": { type: "input", signal: "audio", node: "inputA" },
1585
+ "b": { type: "input", signal: "audio", node: "inputB" },
1586
+ "out": { type: "output", signal: "audio", node: "output" }
1587
+ },
1588
+ createModule(id, position) {
1589
+ return { id, type: "crossfade", position, ...paramDefaults(params7) };
1590
+ },
1591
+ createNodes(ctx, module) {
1592
+ const inputA = ctx.createGain();
1593
+ const inputB = ctx.createGain();
1594
+ const output = ctx.createGain();
1595
+ inputA.gain.value = 0;
1596
+ inputB.gain.value = 0;
1597
+ const _mix = ctx.createConstantSource();
1598
+ _mix.offset.value = module.blend;
1599
+ const { a, b } = curvesFor(module.curve);
1600
+ const curveA = ctx.createWaveShaper();
1601
+ curveA.curve = a;
1602
+ const curveB = ctx.createWaveShaper();
1603
+ curveB.curve = b;
1604
+ _mix.connect(curveA).connect(inputA.gain);
1605
+ _mix.connect(curveB).connect(inputB.gain);
1606
+ _mix.start();
1607
+ inputA.connect(output);
1608
+ inputB.connect(output);
1609
+ return { inputA, inputB, output, _mix, curveA, curveB };
1610
+ },
1611
+ updateParam(nodes, module, param, value) {
1612
+ if (param === "blend") {
1613
+ nodes._mix.offset.value = value;
1614
+ } else if (param === "curve") {
1615
+ const { a, b } = curvesFor(value);
1616
+ nodes.curveA.curve = a;
1617
+ nodes.curveB.curve = b;
1618
+ }
1619
+ }
1620
+ });
1621
+
1622
+ // src/lib-runtime/generated/modules/crossfade.wrapper.ts
1623
+ var Crossfade = /* @__PURE__ */ defineModule(crossfadeDescriptor);
1624
+
1625
+ // src/lib-runtime/generated/modules/delay.tsx
1626
+ var params8 = {
1627
+ time: { node: "delay", field: "period", default: 0.3, min: 0.01, max: 1, scale: "log", unit: "s" },
1628
+ feedback: { node: "delay", field: "feedback", unit: "factor", default: 0.4, min: 0, max: 0.95 },
1629
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.5, min: 0, max: 1 }
1630
+ };
1631
+ var delayDescriptor = /* @__PURE__ */ defineDescriptor({
1632
+ type: "delay",
1633
+ params: params8,
1634
+ ports: {
1635
+ "in": { type: "input", signal: "audio", node: "input" },
1636
+ "out": { type: "output", signal: "audio", node: "output" }
1637
+ },
1638
+ createModule(id, position) {
1639
+ return { id, type: "delay", position, ...paramDefaults(params8) };
1640
+ },
1641
+ createNodes(ctx, module) {
1642
+ const delay = createCombFilterNode(ctx, module.feedback, 5, 2);
1643
+ delay.parameters.get("period").value = module.time;
1644
+ const dryGain = ctx.createGain();
1645
+ const wetGain = ctx.createGain();
1646
+ const input = ctx.createGain();
1647
+ const output = ctx.createGain();
1648
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
1649
+ input.connect(dryGain);
1650
+ input.connect(delay);
1651
+ delay.connect(wetGain);
1652
+ dryGain.connect(output);
1653
+ wetGain.connect(output);
1654
+ return { input, output, delay, dryGain, wetGain, _mix };
1655
+ },
1656
+ updateParam(nodes, _m, param, value) {
1657
+ switch (param) {
1658
+ case "time":
1659
+ nodes.delay.parameters.get("period").value = value;
1660
+ break;
1661
+ case "feedback":
1662
+ nodes.delay.parameters.get("feedback").value = value;
1663
+ break;
1664
+ case "mix":
1665
+ nodes._mix.offset.value = value;
1666
+ break;
1667
+ }
1668
+ }
1669
+ });
1670
+
1671
+ // src/lib-runtime/generated/modules/delay.wrapper.ts
1672
+ var Delay = /* @__PURE__ */ defineModule(delayDescriptor, {
1673
+ worklets: {
1674
+ "comb-filter": comb_filter_default
1675
+ }
1676
+ });
1677
+
1678
+ // src/lib-runtime/generated/modules/dimension.tsx
1679
+ var params9 = {
1680
+ rate: { node: "lfo", field: "frequency", unit: "Hz", default: 0.5, min: 0.1, max: 5 },
1681
+ depth: { unit: "factor", default: 0.3, min: 0, max: 1 }
1682
+ };
1683
+ var dimensionDescriptor = /* @__PURE__ */ defineDescriptor({
1684
+ type: "dimension",
1685
+ params: params9,
1686
+ ports: {
1687
+ "in": { type: "input", signal: "audio", node: "input" },
1688
+ "out": { type: "output", signal: "audio", node: "output" },
1689
+ "modRate": { type: "modulation", signal: "modulation", node: "lfo", field: "frequency" }
1690
+ },
1691
+ createModule(id, position) {
1692
+ return { id, type: "dimension", position, ...paramDefaults(params9) };
1693
+ },
1694
+ createNodes(ctx, module) {
1695
+ const input = ctx.createGain();
1696
+ input.channelCount = 2;
1697
+ input.channelCountMode = "explicit";
1698
+ input.channelInterpretation = "speakers";
1699
+ const output = ctx.createGain();
1700
+ const splitter = ctx.createChannelSplitter(2);
1701
+ const merger = ctx.createChannelMerger(2);
1702
+ const leftDelay = ctx.createDelay(0.05);
1703
+ const rightDelay = ctx.createDelay(0.05);
1704
+ const lfo = ctx.createOscillator();
1705
+ const lfoGainL = ctx.createGain();
1706
+ const lfoGainR = ctx.createGain();
1707
+ const llGain = ctx.createGain();
1708
+ const lrGain = ctx.createGain();
1709
+ const rlGain = ctx.createGain();
1710
+ const rrGain = ctx.createGain();
1711
+ const w = module.width;
1712
+ llGain.gain.value = (1 + w) / 2;
1713
+ rrGain.gain.value = (1 + w) / 2;
1714
+ rlGain.gain.value = (1 - w) / 2;
1715
+ lrGain.gain.value = (1 - w) / 2;
1716
+ leftDelay.delayTime.value = 0.01;
1717
+ rightDelay.delayTime.value = 0.012;
1718
+ lfo.type = "sine";
1719
+ lfo.frequency.value = module.rate;
1720
+ lfoGainL.gain.value = module.depth * 2e-3;
1721
+ lfoGainR.gain.value = -module.depth * 2e-3;
1722
+ lfo.connect(lfoGainL);
1723
+ lfo.connect(lfoGainR);
1724
+ lfoGainL.connect(leftDelay.delayTime);
1725
+ lfoGainR.connect(rightDelay.delayTime);
1726
+ lfo.start();
1727
+ input.connect(splitter);
1728
+ splitter.connect(leftDelay, 0);
1729
+ splitter.connect(rightDelay, 1);
1730
+ leftDelay.connect(llGain);
1731
+ leftDelay.connect(lrGain);
1732
+ rightDelay.connect(rrGain);
1733
+ rightDelay.connect(rlGain);
1734
+ llGain.connect(merger, 0, 0);
1735
+ rlGain.connect(merger, 0, 0);
1736
+ lrGain.connect(merger, 0, 1);
1737
+ rrGain.connect(merger, 0, 1);
1738
+ merger.connect(output);
1739
+ return { input, output, splitter, merger, leftDelay, rightDelay, lfo, lfoGainL, lfoGainR, llGain, rrGain, lrGain, rlGain };
1740
+ },
1741
+ updateParam(nodes, _m, param, value) {
1742
+ if (param === "width") {
1743
+ const w = value;
1744
+ nodes.llGain.gain.value = (1 + w) / 2;
1745
+ nodes.rrGain.gain.value = (1 + w) / 2;
1746
+ nodes.rlGain.gain.value = (1 - w) / 2;
1747
+ nodes.lrGain.gain.value = (1 - w) / 2;
1748
+ } else if (param === "rate") nodes.lfo.frequency.value = value;
1749
+ else if (param === "depth") {
1750
+ nodes.lfoGainL.gain.value = value * 2e-3;
1751
+ nodes.lfoGainR.gain.value = -value * 2e-3;
1752
+ }
1753
+ },
1754
+ dispose(nodes) {
1755
+ nodes.lfo.stop();
1756
+ }
1757
+ });
1758
+
1759
+ // src/lib-runtime/generated/modules/dimension.wrapper.ts
1760
+ var Dimension = /* @__PURE__ */ defineModule(dimensionDescriptor);
1761
+
1762
+ // src/lib-runtime/generated/modules/distortion.tsx
1763
+ var params10 = {
1764
+ distortionType: { kind: "enum", default: "soft", options: ["soft", "hard", "fuzz"] },
1765
+ drive: { unit: "factor", default: 5, min: 1, max: 20, scale: "log" },
1766
+ frequency: { node: "toneFilter", field: "frequency", unit: "Hz", default: 16e3, min: 500, max: 16e3, scale: "log" },
1767
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.8, min: 0, max: 1 }
1768
+ };
1769
+ var FUZZ_BIAS = 0.7;
1770
+ function transfer(type, k, x) {
1771
+ if (type === "soft") return Math.tanh(k * x);
1772
+ if (type === "hard") return Math.max(-1, Math.min(1, k * x));
1773
+ const f = (u) => Math.sign(u) * (1 - Math.exp(-Math.abs(u)));
1774
+ return (f(k * x + FUZZ_BIAS) - f(FUZZ_BIAS)) / (1 + f(FUZZ_BIAS));
1775
+ }
1776
+ var CURVE_LEN = 16384;
1777
+ function makeDistortionCurve(type, drive) {
1778
+ const k = Math.max(1, Math.min(20, drive));
1779
+ const curve = new Float32Array(CURVE_LEN);
1780
+ for (let i = 0; i < CURVE_LEN; i++) {
1781
+ const x = i * 2 / (CURVE_LEN - 1) - 1;
1782
+ curve[i] = transfer(type, k, x);
1783
+ }
1784
+ return curve;
1785
+ }
1786
+ function computeMakeup(type, drive) {
1787
+ const k = Math.max(1, Math.min(20, drive));
1788
+ const N = 512;
1789
+ let sum = 0;
1790
+ let sumSq = 0;
1791
+ for (let i = 0; i < N; i++) {
1792
+ const y = transfer(type, k, 0.5 * Math.sin(2 * Math.PI * i / N));
1793
+ sum += y;
1794
+ sumSq += y * y;
1795
+ }
1796
+ const mean = sum / N;
1797
+ const acRms = Math.sqrt(Math.max(0, sumSq / N - mean * mean));
1798
+ const rmsIn = 0.5 / Math.SQRT2;
1799
+ if (acRms < 1e-6) return 1;
1800
+ return Math.min(4, Math.max(0.25, rmsIn / acRms));
1801
+ }
1802
+ var distortionTypes = params10.distortionType.options;
1803
+ var distortionDescriptor = /* @__PURE__ */ defineDescriptor({
1804
+ type: "distortion",
1805
+ params: params10,
1806
+ ports: {
1807
+ "in": { type: "input", signal: "audio", node: "input" },
1808
+ "out": { type: "output", signal: "audio", node: "output" }
1809
+ },
1810
+ createModule(id, position) {
1811
+ return {
1812
+ id,
1813
+ type: "distortion",
1814
+ position,
1815
+ ...paramDefaults(params10)
1816
+ };
1817
+ },
1818
+ createNodes(ctx, module) {
1819
+ const input = ctx.createGain();
1820
+ const output = ctx.createGain();
1821
+ const dryGain = ctx.createGain();
1822
+ const wetGain = ctx.createGain();
1823
+ const waveshaper = ctx.createWaveShaper();
1824
+ const makeup = ctx.createGain();
1825
+ const dcBlock = ctx.createBiquadFilter();
1826
+ const toneFilter = ctx.createBiquadFilter();
1827
+ waveshaper.curve = makeDistortionCurve(module.distortionType, module.drive);
1828
+ waveshaper.oversample = "4x";
1829
+ makeup.gain.value = computeMakeup(module.distortionType, module.drive);
1830
+ dcBlock.type = "highpass";
1831
+ dcBlock.frequency.value = 10;
1832
+ dcBlock.Q.value = 0.707;
1833
+ toneFilter.type = "lowpass";
1834
+ toneFilter.frequency.value = module.frequency;
1835
+ toneFilter.Q.value = 0.707;
1836
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
1837
+ input.connect(dryGain);
1838
+ input.connect(waveshaper);
1839
+ waveshaper.connect(makeup);
1840
+ makeup.connect(dcBlock);
1841
+ dcBlock.connect(toneFilter);
1842
+ toneFilter.connect(wetGain);
1843
+ dryGain.connect(output);
1844
+ wetGain.connect(output);
1845
+ return { input, output, waveshaper, makeup, dcBlock, toneFilter, dryGain, wetGain, _mix };
1846
+ },
1847
+ updateParam(nodes, module, param, value) {
1848
+ if (param === "drive" || param === "distortionType") {
1849
+ nodes.waveshaper.curve = makeDistortionCurve(module.distortionType, module.drive);
1850
+ nodes.makeup.gain.value = computeMakeup(module.distortionType, module.drive);
1851
+ }
1852
+ if (param === "frequency") {
1853
+ nodes.toneFilter.frequency.value = value;
1854
+ }
1855
+ if (param === "mix") {
1856
+ nodes._mix.offset.value = value;
1857
+ }
1858
+ }
1859
+ });
1860
+
1861
+ // src/lib-runtime/generated/modules/distortion.wrapper.ts
1862
+ var Distortion = /* @__PURE__ */ defineModule(distortionDescriptor);
1863
+
1864
+ // src/lib-runtime/generated/modules/ducker.tsx
1865
+ var params11 = {
1866
+ // Feeds scInput.gain via 1/10^(threshold/20) — a conversion, not a 1:1
1867
+ // AudioParam write — so no node/field.
1868
+ threshold: { unit: "dB", default: -24, min: -60, max: 0 },
1869
+ // Feeds invertGain.gain via negation (-amount) — no node/field.
1870
+ amount: { unit: "factor", default: 0.7, min: 0, max: 1 },
1871
+ attack: { node: "envFollower", field: "attack", unit: "s", default: 0.01, min: 1e-3, max: 0.1, scale: "log" },
1872
+ release: { node: "envFollower", field: "release", unit: "s", default: 0.2, min: 1e-3, max: 1, scale: "log" }
1873
+ };
1874
+ var duckerDescriptor = /* @__PURE__ */ defineDescriptor({
1875
+ type: "ducker",
1876
+ params: params11,
1877
+ ports: {
1878
+ "in": { type: "input", signal: "audio", node: "input" },
1879
+ "sc": { type: "input", signal: "audio", node: "scInput" },
1880
+ "out": { type: "output", signal: "audio", node: "output" }
1881
+ },
1882
+ createModule(id, position) {
1883
+ return { id, type: "ducker", position, ...paramDefaults(params11) };
1884
+ },
1885
+ createNodes(ctx, module) {
1886
+ const input = ctx.createGain();
1887
+ const scInput = ctx.createGain();
1888
+ const output = ctx.createGain();
1889
+ const duckGain = ctx.createGain();
1890
+ scInput.gain.value = 1 / Math.max(1e-3, Math.pow(10, module.threshold / 20));
1891
+ const scClamp = ctx.createWaveShaper();
1892
+ scClamp.oversample = "none";
1893
+ const SAT = 1.2;
1894
+ const curve = new Float32Array(1024);
1895
+ for (let i = 0; i < curve.length; i++) {
1896
+ const x = i / (curve.length - 1) * 2 - 1;
1897
+ curve[i] = SAT * Math.tanh(x / SAT);
1898
+ }
1899
+ scClamp.curve = curve;
1900
+ const envFollower = createEnvFollowerNode(ctx);
1901
+ envFollower.parameters.get("attack").value = module.attack;
1902
+ envFollower.parameters.get("release").value = module.release;
1903
+ const envDrive = ctx.createGain();
1904
+ envDrive.gain.value = 1.2;
1905
+ const envClamp = ctx.createWaveShaper();
1906
+ envClamp.curve = new Float32Array([0, 0, 1]);
1907
+ const invertGain = ctx.createGain();
1908
+ invertGain.gain.value = -module.amount;
1909
+ const constantSource = ctx.createConstantSource();
1910
+ constantSource.offset.value = 1;
1911
+ constantSource.start();
1912
+ scInput.connect(scClamp);
1913
+ scClamp.connect(envFollower);
1914
+ envFollower.connect(envDrive);
1915
+ envDrive.connect(envClamp);
1916
+ envClamp.connect(invertGain);
1917
+ invertGain.connect(duckGain.gain);
1918
+ constantSource.connect(duckGain.gain);
1919
+ duckGain.gain.value = 0;
1920
+ input.connect(duckGain);
1921
+ duckGain.connect(output);
1922
+ return { input, scInput, scClamp, output, invertGain, envFollower, envDrive, envClamp, constantSource };
1923
+ },
1924
+ updateParam(nodes, _m, param, value) {
1925
+ if (param === "threshold") {
1926
+ nodes.scInput.gain.value = 1 / Math.max(1e-3, Math.pow(10, value / 20));
1927
+ } else if (param === "amount") {
1928
+ nodes.invertGain.gain.value = -value;
1929
+ } else if (param === "attack") {
1930
+ nodes.envFollower.parameters.get("attack").value = value;
1931
+ } else if (param === "release") {
1932
+ nodes.envFollower.parameters.get("release").value = value;
1933
+ }
1934
+ },
1935
+ dispose(nodes) {
1936
+ nodes.constantSource.stop();
1937
+ }
1938
+ });
1939
+
1940
+ // src/lib-runtime/generated/modules/ducker.wrapper.ts
1941
+ var Ducker = /* @__PURE__ */ defineModule(duckerDescriptor, {
1942
+ worklets: {
1943
+ "env-follower": env_follower_default
1944
+ }
1945
+ });
1946
+
1947
+ // src/lib-runtime/generated/modules/env-follow.tsx
1948
+ var params12 = {
1949
+ attack: { node: "envFollower", field: "attack", unit: "s", default: 0.01, min: 1e-3, max: 0.5, scale: "log" },
1950
+ release: { node: "envFollower", field: "release", unit: "s", default: 0.1, min: 1e-3, max: 2, scale: "log" },
1951
+ // sensitivityGain.gain = sensitivity * 4 — a scaled conversion, not a 1:1
1952
+ // AudioParam write — so no node/field.
1953
+ sensitivity: { unit: "factor", default: 0.5, min: 0, max: 1 },
1954
+ depth: { node: "depthGain", field: "gain", unit: "semitone", default: 48, min: 0, max: 96, scale: "sqrt" }
1955
+ };
1956
+ var envFollowDescriptor = /* @__PURE__ */ defineDescriptor({
1957
+ type: "env-follow",
1958
+ params: params12,
1959
+ ports: {
1960
+ "in": { type: "input", signal: "audio", node: "input" },
1961
+ "env": { type: "output", node: "depthGain", signal: "modulation" }
1962
+ },
1963
+ createModule(id, position) {
1964
+ return { id, type: "env-follow", position, ...paramDefaults(params12) };
1965
+ },
1966
+ createNodes(ctx, module) {
1967
+ const input = ctx.createGain();
1968
+ const depthGain = ctx.createGain();
1969
+ const sensitivityGain = ctx.createGain();
1970
+ sensitivityGain.gain.value = module.sensitivity * 4;
1971
+ depthGain.gain.value = module.depth;
1972
+ const envFollower = createEnvFollowerNode(ctx);
1973
+ envFollower.parameters.get("attack").value = module.attack;
1974
+ envFollower.parameters.get("release").value = module.release;
1975
+ input.connect(sensitivityGain);
1976
+ sensitivityGain.connect(envFollower);
1977
+ envFollower.connect(depthGain);
1978
+ return { input, depthGain, sensitivityGain, envFollower };
1979
+ },
1980
+ updateParam(nodes, _m, param, value) {
1981
+ if (param === "sensitivity") {
1982
+ nodes.sensitivityGain.gain.value = value * 4;
1983
+ } else if (param === "depth") {
1984
+ nodes.depthGain.gain.value = value;
1985
+ } else if (param === "attack") {
1986
+ nodes.envFollower.parameters.get("attack").value = value;
1987
+ } else if (param === "release") {
1988
+ nodes.envFollower.parameters.get("release").value = value;
1989
+ }
1990
+ }
1991
+ });
1992
+
1993
+ // src/lib-runtime/generated/modules/env-follow.wrapper.ts
1994
+ var EnvelopeFollower = /* @__PURE__ */ defineModule(envFollowDescriptor, {
1995
+ worklets: {
1996
+ "env-follower": env_follower_default
1997
+ }
1998
+ });
1999
+
2000
+ // src/lib-runtime/generated/modules/envelope.tsx
2001
+ var params13 = {
2002
+ attack: { node: "adsrNode", field: "attack", default: 0.01, min: 1e-3, max: 2, scale: "log", unit: "s" },
2003
+ decay: { node: "adsrNode", field: "decay", default: 0.1, min: 1e-3, max: 2, scale: "log", unit: "s" },
2004
+ sustain: { node: "adsrNode", field: "sustain", unit: "factor", default: 0.7, min: 0, max: 1 },
2005
+ release: { node: "adsrNode", field: "release", default: 0.3, min: 1e-3, max: 3, scale: "log", unit: "s" },
2006
+ depth: { node: "envOut", field: "gain", default: 48, min: 0, max: 96, scale: "sqrt", unit: "semitone" }
2007
+ };
2008
+ var envelopeDescriptor = /* @__PURE__ */ defineDescriptor({
2009
+ type: "envelope",
2010
+ params: params13,
2011
+ ports: {
2012
+ "in": { type: "input", signal: "audio", node: "mainGain" },
2013
+ "out": { type: "output", signal: "audio", node: "mainGain" },
2014
+ "env": { type: "output", signal: "modulation", node: "envOut" },
2015
+ "trigger": { type: "input", signal: "gate", node: "adsrNode" }
2016
+ },
2017
+ createModule(id, position) {
2018
+ return { id, type: "envelope", position, ...paramDefaults(params13) };
2019
+ },
2020
+ createNodes(ctx, module) {
2021
+ const mainGain = ctx.createGain();
2022
+ mainGain.gain.value = 0;
2023
+ const adsrNode = createAdsrNode(ctx);
2024
+ adsrNode.parameters.get("attack").value = module.attack;
2025
+ adsrNode.parameters.get("decay").value = module.decay;
2026
+ adsrNode.parameters.get("sustain").value = module.sustain;
2027
+ adsrNode.parameters.get("release").value = module.release;
2028
+ adsrNode.connect(mainGain.gain);
2029
+ const depth = module.depth ?? 48;
2030
+ const envOut = ctx.createGain();
2031
+ envOut.gain.value = depth;
2032
+ adsrNode.connect(envOut);
2033
+ return { mainGain, adsrNode, envOut };
2034
+ },
2035
+ updateParam(nodes, _module, param, value) {
2036
+ const v = value;
2037
+ const p = nodes.adsrNode.parameters;
2038
+ switch (param) {
2039
+ case "attack":
2040
+ p.get("attack").value = v;
2041
+ break;
2042
+ case "decay":
2043
+ p.get("decay").value = v;
2044
+ break;
2045
+ case "sustain":
2046
+ p.get("sustain").value = v;
2047
+ break;
2048
+ case "release":
2049
+ p.get("release").value = v;
2050
+ break;
2051
+ case "depth":
2052
+ nodes.envOut.gain.value = v;
2053
+ break;
2054
+ }
2055
+ }
2056
+ });
2057
+
2058
+ // src/lib-runtime/generated/modules/envelope.wrapper.ts
2059
+ var Envelope = /* @__PURE__ */ defineModule(envelopeDescriptor, {
2060
+ worklets: {
2061
+ "adsr-envelope": adsr_envelope_default
2062
+ }
2063
+ });
2064
+
2065
+ // src/lib-runtime/generated/modules/eq3.tsx
2066
+ var params14 = {
2067
+ low: { node: "lowShelf", field: "gain", unit: "dB", default: 0, min: -12, max: 12 },
2068
+ mid: { node: "midPeak", field: "gain", unit: "dB", default: 0, min: -12, max: 12 },
2069
+ high: { node: "highShelf", field: "gain", unit: "dB", default: 0, min: -12, max: 12 },
2070
+ midFreq: { node: "midPeak", field: "frequency", unit: "Hz", default: 1e3, min: 200, max: 5e3, scale: "log" }
2071
+ };
2072
+ var eq3Descriptor = /* @__PURE__ */ defineDescriptor({
2073
+ type: "eq3",
2074
+ params: params14,
2075
+ ports: {
2076
+ "in": { type: "input", signal: "audio", node: "input" },
2077
+ "out": { type: "output", signal: "audio", node: "output" }
2078
+ },
2079
+ createModule(id, position) {
2080
+ return { id, type: "eq3", position, ...paramDefaults(params14) };
2081
+ },
2082
+ createNodes(ctx, module) {
2083
+ const input = ctx.createGain();
2084
+ const output = ctx.createGain();
2085
+ const lowShelf = ctx.createBiquadFilter();
2086
+ const midPeak = ctx.createBiquadFilter();
2087
+ const highShelf = ctx.createBiquadFilter();
2088
+ lowShelf.type = "lowshelf";
2089
+ lowShelf.frequency.value = 320;
2090
+ lowShelf.gain.value = module.low;
2091
+ midPeak.type = "peaking";
2092
+ midPeak.frequency.value = module.midFreq;
2093
+ midPeak.Q.value = 1.5;
2094
+ midPeak.gain.value = module.mid;
2095
+ highShelf.type = "highshelf";
2096
+ highShelf.frequency.value = 3200;
2097
+ highShelf.gain.value = module.high;
2098
+ input.connect(lowShelf);
2099
+ lowShelf.connect(midPeak);
2100
+ midPeak.connect(highShelf);
2101
+ highShelf.connect(output);
2102
+ return { input, output, lowShelf, midPeak, highShelf };
2103
+ },
2104
+ updateParam(nodes, _m, param, value) {
2105
+ switch (param) {
2106
+ case "low":
2107
+ nodes.lowShelf.gain.value = value;
2108
+ break;
2109
+ case "mid":
2110
+ nodes.midPeak.gain.value = value;
2111
+ break;
2112
+ case "high":
2113
+ nodes.highShelf.gain.value = value;
2114
+ break;
2115
+ case "midFreq":
2116
+ nodes.midPeak.frequency.value = value;
2117
+ break;
2118
+ }
2119
+ }
2120
+ });
2121
+
2122
+ // src/lib-runtime/generated/modules/eq3.wrapper.ts
2123
+ var ThreeBandEq = /* @__PURE__ */ defineModule(eq3Descriptor);
2124
+
2125
+ // src/lib-runtime/generated/modules/exciter.tsx
2126
+ var params15 = {
2127
+ // drive rebuilds the saturation curve AND scales highshelf.gain — not a
2128
+ // 1:1 AudioParam write, so no node/field.
2129
+ drive: { unit: "factor", default: 0.5, min: 0, max: 1 },
2130
+ // frequency writes BOTH highpass.frequency and highshelf.frequency — two
2131
+ // AudioParams, so no single node/field mapping.
2132
+ frequency: { unit: "Hz", default: 4e3, min: 2e3, max: 8e3, scale: "log" },
2133
+ // Additive blend, not a dry/wet crossfade: dry stays at unity while wetGain
2134
+ // scales the excited signal — a genuine 1:1 AudioParam write.
2135
+ mix: { node: "wetGain", field: "gain", unit: "factor", default: 0.5, min: 0, max: 1 }
2136
+ };
2137
+ function makeExciterCurve(amount) {
2138
+ const samples = 44100;
2139
+ const curve = new Float32Array(samples);
2140
+ const k = 1 + amount * 8;
2141
+ const norm = 0.5 / (Math.tanh(k * 0.5) * 1.15);
2142
+ for (let i = 0; i < samples; i++) {
2143
+ const x = i * 2 / samples - 1;
2144
+ curve[i] = Math.tanh(k * x) * (1 + Math.abs(x) * 0.3) * norm;
2145
+ }
2146
+ return curve;
2147
+ }
2148
+ var exciterDescriptor = /* @__PURE__ */ defineDescriptor({
2149
+ type: "exciter",
2150
+ params: params15,
2151
+ ports: {
2152
+ "in": { type: "input", signal: "audio", node: "input" },
2153
+ "out": { type: "output", signal: "audio", node: "output" }
2154
+ },
2155
+ createModule(id, position) {
2156
+ return { id, type: "exciter", position, ...paramDefaults(params15) };
2157
+ },
2158
+ createNodes(ctx, module) {
2159
+ const input = ctx.createGain();
2160
+ const output = ctx.createGain();
2161
+ const dryGain = ctx.createGain();
2162
+ const wetGain = ctx.createGain();
2163
+ const highpass = ctx.createBiquadFilter();
2164
+ const saturation = ctx.createWaveShaper();
2165
+ const highshelf = ctx.createBiquadFilter();
2166
+ highpass.type = "highpass";
2167
+ highpass.frequency.value = module.frequency;
2168
+ highpass.Q.value = 0.7;
2169
+ saturation.curve = makeExciterCurve(module.drive);
2170
+ saturation.oversample = "2x";
2171
+ highshelf.type = "highshelf";
2172
+ highshelf.frequency.value = module.frequency;
2173
+ highshelf.gain.value = 6 + module.drive * 6;
2174
+ dryGain.gain.value = 1;
2175
+ wetGain.gain.value = module.mix;
2176
+ input.connect(dryGain);
2177
+ input.connect(highpass);
2178
+ highpass.connect(saturation);
2179
+ saturation.connect(highshelf);
2180
+ highshelf.connect(wetGain);
2181
+ dryGain.connect(output);
2182
+ wetGain.connect(output);
2183
+ return { input, output, highpass, saturation, highshelf, wetGain };
2184
+ },
2185
+ updateParam(nodes, _m, param, value) {
2186
+ if (param === "drive") {
2187
+ nodes.saturation.curve = makeExciterCurve(value);
2188
+ nodes.highshelf.gain.value = 6 + value * 6;
2189
+ } else if (param === "frequency") {
2190
+ nodes.highpass.frequency.value = value;
2191
+ nodes.highshelf.frequency.value = value;
2192
+ } else if (param === "mix") {
2193
+ nodes.wetGain.gain.value = value;
2194
+ }
2195
+ }
2196
+ });
2197
+
2198
+ // src/lib-runtime/generated/modules/exciter.wrapper.ts
2199
+ var Exciter = /* @__PURE__ */ defineModule(exciterDescriptor);
2200
+
2201
+ // src/lib-runtime/generated/modules/flanger.tsx
2202
+ var params16 = {
2203
+ rate: { node: "flanger", field: "rate", unit: "Hz", default: 0.5, min: 0.1, max: 5 },
2204
+ depth: { node: "flanger", field: "depth", unit: "factor", default: 0.7, min: 0, max: 1 },
2205
+ feedback: { node: "flanger", field: "feedback", unit: "factor", default: 0.7, min: 0, max: 0.95 },
2206
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.5, min: 0, max: 1 }
2207
+ };
2208
+ var flangerDescriptor = /* @__PURE__ */ defineDescriptor({
2209
+ type: "flanger",
2210
+ params: params16,
2211
+ ports: {
2212
+ "in": { type: "input", signal: "audio", node: "input" },
2213
+ "out": { type: "output", signal: "audio", node: "output" },
2214
+ "modRate": { type: "modulation", signal: "modulation", node: "flanger", field: "rate" },
2215
+ "modDepth": { type: "modulation", signal: "modulation", node: "flanger", field: "depth" },
2216
+ "modFeedback": { type: "modulation", signal: "modulation", node: "flanger", field: "feedback" },
2217
+ "modMix": { type: "modulation", signal: "modulation", node: "_mix", field: "offset" }
2218
+ },
2219
+ createModule(id, position) {
2220
+ return { id, type: "flanger", position, ...paramDefaults(params16) };
2221
+ },
2222
+ createNodes(ctx, module) {
2223
+ const input = ctx.createGain();
2224
+ const output = ctx.createGain();
2225
+ const dryGain = ctx.createGain();
2226
+ const wetGain = ctx.createGain();
2227
+ const flanger = createFlangerNode(ctx, {
2228
+ rate: module.rate,
2229
+ depth: module.depth,
2230
+ feedback: module.feedback
2231
+ });
2232
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
2233
+ input.connect(dryGain);
2234
+ input.connect(flanger);
2235
+ flanger.connect(wetGain);
2236
+ dryGain.connect(output);
2237
+ wetGain.connect(output);
2238
+ return { input, output, flanger, dryGain, wetGain, _mix };
2239
+ },
2240
+ updateParam(nodes, _m, param, value) {
2241
+ switch (param) {
2242
+ case "rate":
2243
+ case "depth":
2244
+ case "feedback":
2245
+ nodes.flanger.parameters.get(param).value = value;
2246
+ break;
2247
+ case "mix":
2248
+ nodes._mix.offset.value = value;
2249
+ break;
2250
+ }
2251
+ },
2252
+ dispose(nodes) {
2253
+ nodes._mix.stop();
2254
+ }
2255
+ });
2256
+
2257
+ // src/lib-runtime/generated/modules/flanger.wrapper.ts
2258
+ var Flanger = /* @__PURE__ */ defineModule(flangerDescriptor, {
2259
+ worklets: {
2260
+ "flanger": flanger_default
2261
+ }
2262
+ });
2263
+
2264
+ // src/lib-runtime/generated/modules/formant.tsx
2265
+ var params17 = {
2266
+ morph: { unit: "factor", default: 0, min: 0, max: 5 },
2267
+ resonance: { unit: "factor", default: 6, min: 1, max: 20, scale: "sqrt" },
2268
+ mix: { unit: "factor", default: 1, min: 0, max: 1 }
2269
+ };
2270
+ var formantDescriptor = /* @__PURE__ */ defineDescriptor({
2271
+ type: "formant",
2272
+ params: params17,
2273
+ ports: {
2274
+ "in": { type: "input", signal: "audio", node: "input" },
2275
+ "out": { type: "output", signal: "audio", node: "output" },
2276
+ "modVowel": { type: "modulation", signal: "modulation", node: "vowelCV" }
2277
+ },
2278
+ createModule(id, position) {
2279
+ return { id, type: "formant", position, ...paramDefaults(params17) };
2280
+ },
2281
+ createNodes(ctx, module) {
2282
+ const input = ctx.createGain();
2283
+ const output = ctx.createGain();
2284
+ const dryGain = ctx.createGain();
2285
+ const wetGain = ctx.createGain();
2286
+ const f1 = ctx.createBiquadFilter();
2287
+ const f2 = ctx.createBiquadFilter();
2288
+ const f3 = ctx.createBiquadFilter();
2289
+ const f4 = ctx.createBiquadFilter();
2290
+ const f5 = ctx.createBiquadFilter();
2291
+ const formantMix = ctx.createGain();
2292
+ const formantWorklet = createFormantNode(ctx);
2293
+ formantWorklet.parameters.get("baseMorph").value = module.morph;
2294
+ const vowelCV = ctx.createGain();
2295
+ vowelCV.gain.value = 1;
2296
+ vowelCV.connect(formantWorklet);
2297
+ const splitter = ctx.createChannelSplitter(5);
2298
+ formantWorklet.connect(splitter);
2299
+ splitter.connect(f1.frequency, 0);
2300
+ splitter.connect(f2.frequency, 1);
2301
+ splitter.connect(f3.frequency, 2);
2302
+ splitter.connect(f4.frequency, 3);
2303
+ splitter.connect(f5.frequency, 4);
2304
+ f1.frequency.value = 0;
2305
+ f2.frequency.value = 0;
2306
+ f3.frequency.value = 0;
2307
+ f4.frequency.value = 0;
2308
+ f5.frequency.value = 0;
2309
+ f1.type = "bandpass";
2310
+ f2.type = "bandpass";
2311
+ f3.type = "bandpass";
2312
+ f4.type = "bandpass";
2313
+ f5.type = "bandpass";
2314
+ f1.Q.value = module.resonance * 0.5;
2315
+ f2.Q.value = module.resonance * 0.4;
2316
+ f3.Q.value = module.resonance * 0.3;
2317
+ f4.Q.value = module.resonance * 0.25;
2318
+ f5.Q.value = module.resonance * 0.2;
2319
+ dryGain.gain.value = 1 - module.mix;
2320
+ wetGain.gain.value = module.mix;
2321
+ formantMix.gain.value = 2;
2322
+ const f4Gain = ctx.createGain();
2323
+ const f5Gain = ctx.createGain();
2324
+ f4Gain.gain.value = 0.5;
2325
+ f5Gain.gain.value = 0.35;
2326
+ input.connect(dryGain);
2327
+ input.connect(f1);
2328
+ input.connect(f2);
2329
+ input.connect(f3);
2330
+ input.connect(f4);
2331
+ input.connect(f5);
2332
+ f1.connect(formantMix);
2333
+ f2.connect(formantMix);
2334
+ f3.connect(formantMix);
2335
+ f4.connect(f4Gain);
2336
+ f5.connect(f5Gain);
2337
+ f4Gain.connect(formantMix);
2338
+ f5Gain.connect(formantMix);
2339
+ formantMix.connect(wetGain);
2340
+ dryGain.connect(output);
2341
+ wetGain.connect(output);
2342
+ return { input, output, vowelCV, f1, f2, f3, f4, f5, f4Gain, f5Gain, dryGain, wetGain, formantWorklet };
2343
+ },
2344
+ updateParam(nodes, _module, param, value) {
2345
+ if (param === "morph") {
2346
+ nodes.formantWorklet.parameters.get("baseMorph").value = value;
2347
+ } else if (param === "resonance") {
2348
+ const r = value;
2349
+ nodes.f1.Q.value = r * 0.5;
2350
+ nodes.f2.Q.value = r * 0.4;
2351
+ nodes.f3.Q.value = r * 0.3;
2352
+ nodes.f4.Q.value = r * 0.25;
2353
+ nodes.f5.Q.value = r * 0.2;
2354
+ } else if (param === "mix") {
2355
+ nodes.dryGain.gain.value = 1 - value;
2356
+ nodes.wetGain.gain.value = value;
2357
+ }
2358
+ }
2359
+ });
2360
+
2361
+ // src/lib-runtime/generated/modules/formant.wrapper.ts
2362
+ var Formant = /* @__PURE__ */ defineModule(formantDescriptor, {
2363
+ worklets: {
2364
+ "formant-table": formant_table_default
2365
+ }
2366
+ });
2367
+
2368
+ // src/lib-runtime/generated/modules/freq-shifter.tsx
2369
+ var params18 = {
2370
+ shift: { node: "shifter", field: "shift", unit: "Hz", default: 0, min: -500, max: 500 },
2371
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 1, min: 0, max: 1 }
2372
+ };
2373
+ var freqShifterDescriptor = /* @__PURE__ */ defineDescriptor({
2374
+ type: "freq-shifter",
2375
+ params: params18,
2376
+ ports: {
2377
+ "in": { type: "input", signal: "audio", node: "input" },
2378
+ "out": { type: "output", signal: "audio", node: "output" },
2379
+ "modShift": { type: "modulation", signal: "modulation", node: "shifter", field: "shift" }
2380
+ },
2381
+ createModule(id, position) {
2382
+ return { id, type: "freq-shifter", position, ...paramDefaults(params18) };
2383
+ },
2384
+ createNodes(ctx, module) {
2385
+ const input = ctx.createGain();
2386
+ const output = ctx.createGain();
2387
+ const dryGain = ctx.createGain();
2388
+ const wetGain = ctx.createGain();
2389
+ const shifter = createFreqShifterNode(ctx, module.shift);
2390
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
2391
+ input.connect(dryGain);
2392
+ input.connect(shifter);
2393
+ shifter.connect(wetGain);
2394
+ dryGain.connect(output);
2395
+ wetGain.connect(output);
2396
+ return { input, output, shifter, dryGain, wetGain, _mix };
2397
+ },
2398
+ updateParam(nodes, _m, param, value) {
2399
+ if (param === "shift") {
2400
+ const p = nodes.shifter.parameters.get("shift");
2401
+ p.setTargetAtTime(value, nodes.shifter.context.currentTime, 0.01);
2402
+ } else if (param === "mix") {
2403
+ nodes._mix.offset.value = value;
2404
+ }
2405
+ }
2406
+ });
2407
+
2408
+ // src/lib-runtime/generated/modules/freq-shifter.wrapper.ts
2409
+ var FrequencyShifter = /* @__PURE__ */ defineModule(freqShifterDescriptor, {
2410
+ worklets: {
2411
+ "freq-shifter": freq_shifter_default
2412
+ }
2413
+ });
2414
+
2415
+ // src/lib-runtime/generated/modules/gate.tsx
2416
+ var params19 = {
2417
+ // threshold/range rebuild the gateShaper curve — not a 1:1 AudioParam
2418
+ // write — so no node/field.
2419
+ threshold: { unit: "dB", default: -40, min: -60, max: 0 },
2420
+ range: { unit: "factor", default: 0.8, min: 0, max: 1 },
2421
+ attack: { node: "envFollower", field: "attack", unit: "s", default: 5e-3, min: 1e-3, max: 0.05, scale: "log" },
2422
+ release: { node: "envFollower", field: "release", unit: "s", default: 0.1, min: 0.01, max: 0.5, scale: "log" }
2423
+ };
2424
+ function makeGateCurve(threshold, range) {
2425
+ const threshLinear = Math.pow(10, threshold / 20);
2426
+ const kneeLow = Math.pow(10, (threshold - 2) / 20);
2427
+ const closed = 1 - range;
2428
+ const gsLen = 32768;
2429
+ const gsCurve = new Float32Array(gsLen);
2430
+ for (let i = 0; i < gsLen; i++) {
2431
+ const x = i / (gsLen - 1) * 2 - 1;
2432
+ const env = Math.max(0, x);
2433
+ let g;
2434
+ if (env >= threshLinear) g = 1;
2435
+ else if (env <= kneeLow) g = closed;
2436
+ else g = closed + (1 - closed) * ((env - kneeLow) / (threshLinear - kneeLow));
2437
+ gsCurve[i] = g;
2438
+ }
2439
+ return gsCurve;
2440
+ }
2441
+ var gateDescriptor = /* @__PURE__ */ defineDescriptor({
2442
+ type: "gate",
2443
+ params: params19,
2444
+ ports: {
2445
+ "in": { type: "input", signal: "audio", node: "input" },
2446
+ "out": { type: "output", signal: "audio", node: "output" }
2447
+ },
2448
+ createModule(id, position) {
2449
+ return { id, type: "gate", position, ...paramDefaults(params19) };
2450
+ },
2451
+ createNodes(ctx, module) {
2452
+ const input = ctx.createGain();
2453
+ const output = ctx.createGain();
2454
+ const gateGain = ctx.createGain();
2455
+ const envFollower = createEnvFollowerNode(ctx);
2456
+ envFollower.parameters.get("attack").value = module.attack;
2457
+ envFollower.parameters.get("release").value = module.release;
2458
+ const gateShaper = ctx.createWaveShaper();
2459
+ gateShaper.curve = makeGateCurve(module.threshold, module.range);
2460
+ gateGain.gain.value = 0;
2461
+ input.connect(envFollower);
2462
+ envFollower.connect(gateShaper);
2463
+ gateShaper.connect(gateGain.gain);
2464
+ input.connect(gateGain);
2465
+ gateGain.connect(output);
2466
+ return { input, output, envFollower, gateShaper };
2467
+ },
2468
+ updateParam(nodes, module, param, value) {
2469
+ if (param === "threshold" || param === "range") {
2470
+ const thresh = param === "threshold" ? value : module.threshold;
2471
+ const range = param === "range" ? value : module.range;
2472
+ nodes.gateShaper.curve = makeGateCurve(thresh, range);
2473
+ } else if (param === "attack") {
2474
+ nodes.envFollower.parameters.get("attack").value = value;
2475
+ } else if (param === "release") {
2476
+ nodes.envFollower.parameters.get("release").value = value;
2477
+ }
2478
+ }
2479
+ });
2480
+
2481
+ // src/lib-runtime/generated/modules/gate.wrapper.ts
2482
+ var Gate = /* @__PURE__ */ defineModule(gateDescriptor, {
2483
+ worklets: {
2484
+ "env-follower": env_follower_default
2485
+ }
2486
+ });
2487
+
2488
+ // src/lib-runtime/generated/modules/glide.tsx
2489
+ var params20 = {
2490
+ time: { node: "_glide", field: "time", unit: "s", default: 0.1, min: 0.01, max: 10, scale: "log" }
2491
+ };
2492
+ var glideDescriptor = /* @__PURE__ */ defineDescriptor({
2493
+ type: "glide",
2494
+ params: params20,
2495
+ ports: {
2496
+ "in": { type: "input", signal: "pitch", node: "_glide" },
2497
+ "gate": { type: "input", signal: "gate", node: "_gateIn" },
2498
+ "out": { type: "output", signal: "pitch", node: "output" }
2499
+ },
2500
+ createModule(id, position) {
2501
+ return { id, type: "glide", position, ...paramDefaults(params20) };
2502
+ },
2503
+ createNodes(ctx, module) {
2504
+ const _glide = createGlideNode(ctx);
2505
+ _glide.parameters.get("time").value = module.time;
2506
+ const output = ctx.createGain();
2507
+ output.gain.value = 1;
2508
+ _glide.connect(output);
2509
+ const _gateIn = ctx.createGain();
2510
+ _gateIn.gain.value = 1;
2511
+ _gateIn.connect(_glide, 0, 1);
2512
+ return { _glide, _gateIn, output };
2513
+ },
2514
+ updateParam(nodes, _m, param, value) {
2515
+ if (param === "time") {
2516
+ nodes._glide.parameters.get("time").value = value;
2517
+ }
2518
+ }
2519
+ });
2520
+
2521
+ // src/lib-runtime/generated/modules/glide.wrapper.ts
2522
+ var Glide = /* @__PURE__ */ defineModule(glideDescriptor, {
2523
+ worklets: {
2524
+ "glide": glide_default
2525
+ }
2526
+ });
2527
+
2528
+ // src/lib-runtime/generated/modules/haas.tsx
2529
+ var params21 = {
2530
+ delay: { unit: "ms", default: 15, min: 1, max: 35 },
2531
+ side: { kind: "enum", default: "right", options: ["left", "right"] },
2532
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 1, min: 0, max: 1 }
2533
+ };
2534
+ var haasDescriptor = /* @__PURE__ */ defineDescriptor({
2535
+ type: "haas",
2536
+ params: params21,
2537
+ ports: {
2538
+ "in": { type: "input", signal: "audio", node: "input" },
2539
+ "out": { type: "output", signal: "audio", node: "output" }
2540
+ },
2541
+ createModule(id, position) {
2542
+ return { id, type: "haas", position, ...paramDefaults(params21) };
2543
+ },
2544
+ createNodes(ctx, module) {
2545
+ const input = ctx.createGain();
2546
+ const output = ctx.createGain();
2547
+ const delayNode = ctx.createDelay(0.05);
2548
+ const directGain = ctx.createGain();
2549
+ const delayedGain = ctx.createGain();
2550
+ const directBleed = ctx.createGain();
2551
+ const merger = ctx.createChannelMerger(2);
2552
+ const _mix = createMix(ctx, directBleed, delayedGain, module.mix, "linear");
2553
+ delayNode.delayTime.value = module.delay / 1e3;
2554
+ directGain.gain.value = 1;
2555
+ input.connect(directGain);
2556
+ input.connect(directBleed);
2557
+ input.connect(delayNode);
2558
+ delayNode.connect(delayedGain);
2559
+ if (module.side === "right") {
2560
+ directGain.connect(merger, 0, 0);
2561
+ delayedGain.connect(merger, 0, 1);
2562
+ directBleed.connect(merger, 0, 1);
2563
+ } else {
2564
+ delayedGain.connect(merger, 0, 0);
2565
+ directBleed.connect(merger, 0, 0);
2566
+ directGain.connect(merger, 0, 1);
2567
+ }
2568
+ merger.connect(output);
2569
+ return { input, output, delayNode, directGain, delayedGain, directBleed, merger, _mix };
2570
+ },
2571
+ updateParam(nodes, _m, param, value) {
2572
+ if (param === "delay") {
2573
+ nodes.delayNode.delayTime.value = value / 1e3;
2574
+ } else if (param === "mix") {
2575
+ nodes._mix.offset.value = value;
2576
+ } else if (param === "side") {
2577
+ try {
2578
+ nodes.directGain.disconnect(nodes.merger);
2579
+ } catch (_) {
2580
+ }
2581
+ try {
2582
+ nodes.delayedGain.disconnect(nodes.merger);
2583
+ } catch (_) {
2584
+ }
2585
+ try {
2586
+ nodes.directBleed.disconnect(nodes.merger);
2587
+ } catch (_) {
2588
+ }
2589
+ if (value === "right") {
2590
+ nodes.directGain.connect(nodes.merger, 0, 0);
2591
+ nodes.delayedGain.connect(nodes.merger, 0, 1);
2592
+ nodes.directBleed.connect(nodes.merger, 0, 1);
2593
+ } else {
2594
+ nodes.delayedGain.connect(nodes.merger, 0, 0);
2595
+ nodes.directBleed.connect(nodes.merger, 0, 0);
2596
+ nodes.directGain.connect(nodes.merger, 0, 1);
2597
+ }
2598
+ }
2599
+ }
2600
+ });
2601
+
2602
+ // src/lib-runtime/generated/modules/haas.wrapper.ts
2603
+ var HaasWidener = /* @__PURE__ */ defineModule(haasDescriptor);
2604
+
2605
+ // src/lib-runtime/generated/modules/harmonic-trem.tsx
2606
+ var params22 = {
2607
+ rate: { node: "lfo", field: "frequency", unit: "Hz", default: 4, min: 1, max: 10 },
2608
+ depth: { unit: "factor", default: 0.7, min: 0, max: 1 },
2609
+ crossover: { node: "lowpass", field: "frequency", unit: "Hz", default: 800, min: 200, max: 2e3 }
2610
+ };
2611
+ var harmonicTremDescriptor = /* @__PURE__ */ defineDescriptor({
2612
+ type: "harmonic-trem",
2613
+ params: params22,
2614
+ ports: {
2615
+ "in": { type: "input", signal: "audio", node: "input" },
2616
+ "out": { type: "output", signal: "audio", node: "output" },
2617
+ "modRate": { type: "modulation", signal: "modulation", node: "lfo", field: "frequency" },
2618
+ "modCrossover": { type: "modulation", signal: "modulation", node: "lowpass", field: "frequency" }
2619
+ },
2620
+ createModule(id, position) {
2621
+ return { id, type: "harmonic-trem", position, ...paramDefaults(params22) };
2622
+ },
2623
+ createNodes(ctx, module) {
2624
+ const input = ctx.createGain();
2625
+ const output = ctx.createGain();
2626
+ const lowpass = ctx.createBiquadFilter();
2627
+ const lpInvert = ctx.createGain();
2628
+ const highBand = ctx.createGain();
2629
+ const lowGain = ctx.createGain();
2630
+ const highGain = ctx.createGain();
2631
+ const lfo = ctx.createOscillator();
2632
+ const lfoGainLow = ctx.createGain();
2633
+ const lfoGainHigh = ctx.createGain();
2634
+ const lfoInvert = ctx.createGain();
2635
+ lowpass.type = "lowpass";
2636
+ lowpass.frequency.value = module.crossover;
2637
+ lowpass.Q.value = -6.0206;
2638
+ lpInvert.gain.value = -1;
2639
+ highBand.gain.value = 1;
2640
+ lfo.type = "sine";
2641
+ lfo.frequency.value = module.rate;
2642
+ lfoGainLow.gain.value = module.depth * 0.5;
2643
+ lfoGainHigh.gain.value = module.depth * 0.5;
2644
+ lfoInvert.gain.value = -1;
2645
+ lowGain.gain.value = 1;
2646
+ highGain.gain.value = 1;
2647
+ input.connect(lowpass);
2648
+ input.connect(highBand);
2649
+ lowpass.connect(lpInvert);
2650
+ lpInvert.connect(highBand);
2651
+ lfo.connect(lfoGainLow);
2652
+ lfo.connect(lfoInvert);
2653
+ lfoInvert.connect(lfoGainHigh);
2654
+ lfoGainLow.connect(lowGain.gain);
2655
+ lfoGainHigh.connect(highGain.gain);
2656
+ lowpass.connect(lowGain);
2657
+ highBand.connect(highGain);
2658
+ lowGain.connect(output);
2659
+ highGain.connect(output);
2660
+ lfo.start();
2661
+ return { input, output, lfo, lfoGainLow, lfoGainHigh, lowpass, lpInvert, highBand };
2662
+ },
2663
+ updateParam(nodes, _m, param, value) {
2664
+ if (param === "rate") nodes.lfo.frequency.value = value;
2665
+ else if (param === "depth") {
2666
+ nodes.lfoGainLow.gain.value = value * 0.5;
2667
+ nodes.lfoGainHigh.gain.value = value * 0.5;
2668
+ } else if (param === "crossover") {
2669
+ nodes.lowpass.frequency.value = value;
2670
+ }
2671
+ },
2672
+ dispose(nodes) {
2673
+ nodes.lfo.stop();
2674
+ }
2675
+ });
2676
+
2677
+ // src/lib-runtime/generated/modules/harmonic-trem.wrapper.ts
2678
+ var HarmonicTremolo = /* @__PURE__ */ defineModule(harmonicTremDescriptor);
2679
+
2680
+ // src/lib-runtime/generated/modules/ladder-filter.tsx
2681
+ var RES_TOP = 1.1;
2682
+ var params23 = {
2683
+ note: { node: "_cutoffVoct", field: "transpose", unit: "note", default: 83, min: 15, max: 135 },
2684
+ // Stored 0–4 (UI shows % of travel) but the worklet's resonance param is
2685
+ // resonance/4·RES_TOP — a scale, not a 1:1 mapping — so this stays set-only.
2686
+ resonance: { unit: "factor", default: 2.5, min: 0, max: 4, scale: "sqrt" },
2687
+ drive: { node: "ladder", field: "drive", unit: "factor", default: 0.3, min: 0, max: 1 }
2688
+ };
2689
+ var ladderFilterDescriptor = /* @__PURE__ */ defineDescriptor({
2690
+ type: "ladder-filter",
2691
+ params: params23,
2692
+ ports: {
2693
+ "in": { type: "input", signal: "audio", node: "input" },
2694
+ "out": { type: "output", signal: "audio", node: "output" },
2695
+ "modFreq": { type: "modulation", signal: "modulation", node: "_cutoffVoct" },
2696
+ "modRes": { type: "modulation", signal: "modulation", node: "ladder", field: "resonance" }
2697
+ },
2698
+ createModule(id, position) {
2699
+ return { id, type: "ladder-filter", position, ...paramDefaults(params23) };
2700
+ },
2701
+ createNodes(ctx, module) {
2702
+ const input = ctx.createGain();
2703
+ const output = ctx.createGain();
2704
+ const ladder = createLadderFilterNode(ctx, {
2705
+ resonance: module.resonance / 4 * RES_TOP,
2706
+ drive: module.drive
2707
+ });
2708
+ const _cutoffVoct = createVoctFreqNode(ctx);
2709
+ _cutoffVoct.parameters.get("transpose").value = module.note;
2710
+ const _cutoffBase = ctx.createConstantSource();
2711
+ _cutoffBase.offset.value = 0;
2712
+ _cutoffBase.start();
2713
+ _cutoffBase.connect(_cutoffVoct);
2714
+ _cutoffVoct.connect(ladder.parameters.get("cutoff"));
2715
+ input.connect(ladder);
2716
+ ladder.connect(output);
2717
+ return { input, output, ladder, _cutoffVoct, _cutoffBase };
2718
+ },
2719
+ updateParam(nodes, _module, param, value) {
2720
+ switch (param) {
2721
+ case "note":
2722
+ nodes._cutoffVoct.parameters.get("transpose").value = value;
2723
+ break;
2724
+ case "resonance":
2725
+ nodes.ladder.parameters.get("resonance").value = value / 4 * RES_TOP;
2726
+ break;
2727
+ case "drive":
2728
+ nodes.ladder.parameters.get("drive").value = value;
2729
+ break;
2730
+ }
2731
+ },
2732
+ dispose(nodes) {
2733
+ try {
2734
+ nodes._cutoffBase.stop();
2735
+ } catch (_) {
2736
+ }
2737
+ }
2738
+ });
2739
+
2740
+ // src/lib-runtime/generated/modules/ladder-filter.wrapper.ts
2741
+ var LadderFilter = /* @__PURE__ */ defineModule(ladderFilterDescriptor, {
2742
+ worklets: {
2743
+ "ladder-filter": ladder_filter_default,
2744
+ "map-voct-to-freq": map_voct_to_freq_default
2745
+ }
2746
+ });
2747
+
2748
+ // src/lib-runtime/generated/modules/lfo.tsx
2749
+ var params24 = {
2750
+ skew: { node: "_ws", field: "skew", unit: "factor", default: 0, min: -1, max: 1 },
2751
+ shape: { node: "_ws", field: "shape", unit: "factor", default: 1, min: -1, max: 1 },
2752
+ frequency: { node: "_freqSrc", field: "offset", default: 2, min: -10, max: 20, unit: "Hz" },
2753
+ amount: { node: "gain", field: "gain", unit: "semitone", default: 12, min: 0, max: 500, scale: "sqrt" }
2754
+ };
2755
+ var lfoDescriptor = /* @__PURE__ */ defineDescriptor({
2756
+ type: "lfo",
2757
+ params: params24,
2758
+ ports: {
2759
+ "out": { type: "output", node: "gain", signal: "modulation" },
2760
+ "modRate": { type: "modulation", signal: "modulation", node: "_freqSrc", field: "offset" },
2761
+ "modAmount": { type: "modulation", signal: "modulation", node: "gain", field: "gain" },
2762
+ "modSkew": { type: "modulation", signal: "modulation", node: "_ws", field: "skew" },
2763
+ "modShape": { type: "modulation", signal: "modulation", node: "_ws", field: "shape" }
2764
+ },
2765
+ createModule(id, position) {
2766
+ return { id, type: "lfo", position, ...paramDefaults(params24) };
2767
+ },
2768
+ createNodes(ctx, module) {
2769
+ const _ws = createWaveshaperNode(ctx);
2770
+ const gain = ctx.createGain();
2771
+ const _freqSrc = ctx.createConstantSource();
2772
+ _freqSrc.offset.value = module.frequency;
2773
+ _freqSrc.start();
2774
+ _freqSrc.connect(_ws);
2775
+ _ws.parameters.get("skew").value = module.skew;
2776
+ _ws.parameters.get("shape").value = module.shape;
2777
+ gain.gain.value = module.amount;
2778
+ _ws.connect(gain);
2779
+ return { gain, _ws, _freqSrc };
2780
+ },
2781
+ updateParam(nodes, _module, param, value) {
2782
+ switch (param) {
2783
+ case "skew":
2784
+ nodes._ws.parameters.get("skew").value = value;
2785
+ break;
2786
+ case "shape":
2787
+ nodes._ws.parameters.get("shape").value = value;
2788
+ break;
2789
+ case "frequency":
2790
+ nodes._freqSrc.offset.value = value;
2791
+ break;
2792
+ case "amount":
2793
+ nodes.gain.gain.value = value;
2794
+ break;
2795
+ }
2796
+ },
2797
+ dispose(nodes) {
2798
+ nodes._freqSrc.stop();
2799
+ }
2800
+ });
2801
+
2802
+ // src/lib-runtime/generated/modules/lfo.wrapper.ts
2803
+ var Lfo = /* @__PURE__ */ defineModule(lfoDescriptor, {
2804
+ worklets: {
2805
+ "waveshaper": waveshaper_default
2806
+ }
2807
+ });
2808
+
2809
+ // src/lib-runtime/generated/modules/lo-fi.tsx
2810
+ function makeLoFiSaturationCurve(amount) {
2811
+ const samples = 44100;
2812
+ const curve = new Float32Array(samples);
2813
+ const k = 1 + amount * 5;
2814
+ for (let i = 0; i < samples; i++) {
2815
+ const x = i * 2 / samples - 1;
2816
+ curve[i] = x >= 0 ? Math.tanh(k * x * 0.8) : Math.tanh(k * x * 1.2);
2817
+ }
2818
+ return curve;
2819
+ }
2820
+ var params25 = {
2821
+ drive: { unit: "factor", default: 0.3, min: 0, max: 1 },
2822
+ cutoff: { node: "lowpass", field: "frequency", unit: "Hz", default: 4e3, min: 500, max: 8e3 },
2823
+ noise: { unit: "factor", default: 0.2, min: 0, max: 1 },
2824
+ wobble: { unit: "factor", default: 0.1, min: 0, max: 1 }
2825
+ };
2826
+ var loFiDescriptor = /* @__PURE__ */ defineDescriptor({
2827
+ type: "lo-fi",
2828
+ params: params25,
2829
+ ports: {
2830
+ "in": { type: "input", signal: "audio", node: "input" },
2831
+ "out": { type: "output", signal: "audio", node: "output" },
2832
+ "modCutoff": { type: "modulation", signal: "modulation", node: "lowpass", field: "frequency" }
2833
+ },
2834
+ createModule(id, position) {
2835
+ return { id, type: "lo-fi", position, ...paramDefaults(params25) };
2836
+ },
2837
+ createNodes(ctx, module) {
2838
+ const input = ctx.createGain();
2839
+ const output = ctx.createGain();
2840
+ const saturation = ctx.createWaveShaper();
2841
+ const lowpass = ctx.createBiquadFilter();
2842
+ const noiseGain = ctx.createGain();
2843
+ const wobbleLfo = ctx.createOscillator();
2844
+ const wobbleGain = ctx.createGain();
2845
+ saturation.curve = makeLoFiSaturationCurve(module.drive);
2846
+ saturation.oversample = "2x";
2847
+ lowpass.type = "lowpass";
2848
+ lowpass.frequency.value = module.cutoff;
2849
+ lowpass.Q.value = 0.7;
2850
+ const bufferSize = 2 * ctx.sampleRate;
2851
+ const noiseBuffer2 = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
2852
+ const data = noiseBuffer2.getChannelData(0);
2853
+ const rand = noiseSource(NOISE_SEEDS.loFiCrackle);
2854
+ let b0 = 0, b1 = 0, b2 = 0;
2855
+ for (let i = 0; i < bufferSize; i++) {
2856
+ const white = rand();
2857
+ b0 = 0.99765 * b0 + white * 0.099046;
2858
+ b1 = 0.963 * b1 + white * 0.2965164;
2859
+ b2 = 0.57 * b2 + white * 1.0526913;
2860
+ data[i] = (b0 + b1 + b2) * 0.1;
2861
+ }
2862
+ const noiseSrc = ctx.createBufferSource();
2863
+ noiseSrc.buffer = noiseBuffer2;
2864
+ noiseSrc.loop = true;
2865
+ noiseGain.gain.value = module.noise * 0.05;
2866
+ noiseSrc.connect(noiseGain);
2867
+ noiseSrc.start();
2868
+ const wobbleDelay = ctx.createDelay(0.05);
2869
+ wobbleDelay.delayTime.value = 0.01;
2870
+ wobbleLfo.type = "sine";
2871
+ wobbleLfo.frequency.value = 0.73;
2872
+ wobbleGain.gain.value = module.wobble * 2e-3;
2873
+ wobbleLfo.connect(wobbleGain);
2874
+ wobbleGain.connect(wobbleDelay.delayTime);
2875
+ wobbleLfo.start();
2876
+ input.connect(saturation);
2877
+ saturation.connect(lowpass);
2878
+ lowpass.connect(wobbleDelay);
2879
+ wobbleDelay.connect(output);
2880
+ noiseGain.connect(output);
2881
+ return { input, output, saturation, lowpass, wobbleDelay, noiseGain, wobbleGain, wobbleLfo, noiseSrc };
2882
+ },
2883
+ updateParam(nodes, _m, param, value) {
2884
+ if (param === "drive") {
2885
+ nodes.saturation.curve = makeLoFiSaturationCurve(value);
2886
+ } else if (param === "cutoff") {
2887
+ nodes.lowpass.frequency.value = value;
2888
+ } else if (param === "noise") {
2889
+ nodes.noiseGain.gain.value = value * 0.05;
2890
+ } else if (param === "wobble") {
2891
+ nodes.wobbleGain.gain.value = value * 2e-3;
2892
+ }
2893
+ },
2894
+ dispose(nodes) {
2895
+ nodes.wobbleLfo.stop();
2896
+ nodes.noiseSrc.stop();
2897
+ }
2898
+ });
2899
+
2900
+ // src/lib-runtime/generated/modules/lo-fi.wrapper.ts
2901
+ var LoFi = /* @__PURE__ */ defineModule(loFiDescriptor);
2902
+
2903
+ // src/lib-runtime/generated/modules/mid-side.tsx
2904
+ var params26 = {
2905
+ midGain: { node: "midGainNode", field: "gain", unit: "factor", default: 1, min: 0, max: 2 },
2906
+ sideGain: { node: "sideGainNode", field: "gain", unit: "factor", default: 1, min: 0, max: 2 }
2907
+ };
2908
+ var midSideDescriptor = /* @__PURE__ */ defineDescriptor({
2909
+ type: "mid-side",
2910
+ params: params26,
2911
+ ports: {
2912
+ "in": { type: "input", signal: "audio", node: "input" },
2913
+ "out": { type: "output", signal: "audio", node: "output" }
2914
+ },
2915
+ createModule(id, position) {
2916
+ return { id, type: "mid-side", position, ...paramDefaults(params26) };
2917
+ },
2918
+ createNodes(ctx, module) {
2919
+ const input = ctx.createGain();
2920
+ input.channelCount = 2;
2921
+ input.channelCountMode = "explicit";
2922
+ input.channelInterpretation = "speakers";
2923
+ const output = ctx.createGain();
2924
+ const splitter = ctx.createChannelSplitter(2);
2925
+ const merger = ctx.createChannelMerger(2);
2926
+ const midSum = ctx.createGain();
2927
+ midSum.gain.value = 0.5;
2928
+ const sideSum = ctx.createGain();
2929
+ sideSum.gain.value = 0.5;
2930
+ const rInvEnc = ctx.createGain();
2931
+ rInvEnc.gain.value = -1;
2932
+ input.connect(splitter);
2933
+ splitter.connect(midSum, 0);
2934
+ splitter.connect(midSum, 1);
2935
+ splitter.connect(sideSum, 0);
2936
+ splitter.connect(rInvEnc, 1);
2937
+ rInvEnc.connect(sideSum);
2938
+ const midGainNode = ctx.createGain();
2939
+ midGainNode.gain.value = module.midGain;
2940
+ const sideGainNode = ctx.createGain();
2941
+ sideGainNode.gain.value = module.sideGain;
2942
+ const widthNode = ctx.createGain();
2943
+ widthNode.gain.value = module.width;
2944
+ midSum.connect(midGainNode);
2945
+ sideSum.connect(sideGainNode);
2946
+ sideGainNode.connect(widthNode);
2947
+ const leftOut = ctx.createGain();
2948
+ const rightOut = ctx.createGain();
2949
+ const sideInvDec = ctx.createGain();
2950
+ sideInvDec.gain.value = -1;
2951
+ midGainNode.connect(leftOut);
2952
+ midGainNode.connect(rightOut);
2953
+ widthNode.connect(leftOut);
2954
+ widthNode.connect(sideInvDec);
2955
+ sideInvDec.connect(rightOut);
2956
+ leftOut.connect(merger, 0, 0);
2957
+ rightOut.connect(merger, 0, 1);
2958
+ merger.connect(output);
2959
+ return { input, output, midGainNode, sideGainNode, widthNode };
2960
+ },
2961
+ updateParam(nodes, _module, param, value) {
2962
+ if (param === "midGain") nodes.midGainNode.gain.value = value;
2963
+ else if (param === "sideGain") nodes.sideGainNode.gain.value = value;
2964
+ else if (param === "width") nodes.widthNode.gain.value = value;
2965
+ }
2966
+ });
2967
+
2968
+ // src/lib-runtime/generated/modules/mid-side.wrapper.ts
2969
+ var MidSide = /* @__PURE__ */ defineModule(midSideDescriptor);
2970
+
2971
+ // src/lib-runtime/generated/modules/noise.tsx
2972
+ var params27 = {
2973
+ noiseType: { kind: "enum", default: "white", options: ["white", "pink", "brown"] },
2974
+ gain: { node: "output", field: "gain", unit: "factor", default: 0.3, min: 0, max: 1 }
2975
+ };
2976
+ function generateNoiseBuffer(ctx, type) {
2977
+ const bufferSize = 2 * ctx.sampleRate;
2978
+ const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
2979
+ const data = buffer.getChannelData(0);
2980
+ if (type === "white") {
2981
+ const rand = noiseSource(NOISE_SEEDS.noiseWhite);
2982
+ for (let i = 0; i < bufferSize; i++) data[i] = rand();
2983
+ } else if (type === "pink") {
2984
+ const rand = noiseSource(NOISE_SEEDS.noisePink);
2985
+ let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;
2986
+ for (let i = 0; i < bufferSize; i++) {
2987
+ const w = rand();
2988
+ b0 = 0.99886 * b0 + w * 0.0555179;
2989
+ b1 = 0.99332 * b1 + w * 0.0750759;
2990
+ b2 = 0.969 * b2 + w * 0.153852;
2991
+ b3 = 0.8665 * b3 + w * 0.3104856;
2992
+ b4 = 0.55 * b4 + w * 0.5329522;
2993
+ b5 = -0.7616 * b5 - w * 0.016898;
2994
+ data[i] = (b0 + b1 + b2 + b3 + b4 + b5 + b6 + w * 0.5362) * 0.11;
2995
+ b6 = w * 0.115926;
2996
+ }
2997
+ } else {
2998
+ const rand = noiseSource(NOISE_SEEDS.noiseBrown);
2999
+ let lastOut = 0;
3000
+ for (let i = 0; i < bufferSize; i++) {
3001
+ const w = rand();
3002
+ data[i] = (lastOut + 0.02 * w) / 1.02;
3003
+ lastOut = data[i];
3004
+ data[i] *= 3.5;
3005
+ }
3006
+ }
3007
+ return buffer;
3008
+ }
3009
+ var noiseDescriptor = /* @__PURE__ */ defineDescriptor({
3010
+ type: "noise",
3011
+ params: params27,
3012
+ ports: {
3013
+ "out": { type: "output", signal: "audio", node: "output" },
3014
+ "modLevel": { type: "modulation", signal: "modulation", node: "output", field: "gain" }
3015
+ },
3016
+ createModule(id, position) {
3017
+ return { id, type: "noise", position, ...paramDefaults(params27) };
3018
+ },
3019
+ createNodes(ctx, module) {
3020
+ const whiteSrc = ctx.createBufferSource();
3021
+ whiteSrc.buffer = generateNoiseBuffer(ctx, "white");
3022
+ whiteSrc.loop = true;
3023
+ const pinkSrc = ctx.createBufferSource();
3024
+ pinkSrc.buffer = generateNoiseBuffer(ctx, "pink");
3025
+ pinkSrc.loop = true;
3026
+ const brownSrc = ctx.createBufferSource();
3027
+ brownSrc.buffer = generateNoiseBuffer(ctx, "brown");
3028
+ brownSrc.loop = true;
3029
+ const whiteGain = ctx.createGain();
3030
+ const pinkGain = ctx.createGain();
3031
+ const brownGain = ctx.createGain();
3032
+ whiteGain.gain.value = module.noiseType === "white" ? 1 : 0;
3033
+ pinkGain.gain.value = module.noiseType === "pink" ? 1 : 0;
3034
+ brownGain.gain.value = module.noiseType === "brown" ? 1 : 0;
3035
+ const output = ctx.createGain();
3036
+ output.gain.value = module.gain;
3037
+ whiteSrc.connect(whiteGain);
3038
+ pinkSrc.connect(pinkGain);
3039
+ brownSrc.connect(brownGain);
3040
+ whiteGain.connect(output);
3041
+ pinkGain.connect(output);
3042
+ brownGain.connect(output);
3043
+ whiteSrc.start(0);
3044
+ pinkSrc.start(0);
3045
+ brownSrc.start(0);
3046
+ return { output, whiteGain, pinkGain, brownGain, _whiteSrc: whiteSrc, _pinkSrc: pinkSrc, _brownSrc: brownSrc };
3047
+ },
3048
+ updateParam(nodes, _module, param, value) {
3049
+ const t = nodes.output.context.currentTime;
3050
+ switch (param) {
3051
+ case "noiseType":
3052
+ nodes.whiteGain.gain.setTargetAtTime(value === "white" ? 1 : 0, t, 5e-3);
3053
+ nodes.pinkGain.gain.setTargetAtTime(value === "pink" ? 1 : 0, t, 5e-3);
3054
+ nodes.brownGain.gain.setTargetAtTime(value === "brown" ? 1 : 0, t, 5e-3);
3055
+ break;
3056
+ case "gain":
3057
+ nodes.output.gain.setTargetAtTime(value, t, 5e-3);
3058
+ break;
3059
+ }
3060
+ },
3061
+ dispose(nodes) {
3062
+ try {
3063
+ nodes._whiteSrc.stop();
3064
+ } catch (_) {
3065
+ }
3066
+ try {
3067
+ nodes._pinkSrc.stop();
3068
+ } catch (_) {
3069
+ }
3070
+ try {
3071
+ nodes._brownSrc.stop();
3072
+ } catch (_) {
3073
+ }
3074
+ }
3075
+ });
3076
+
3077
+ // src/lib-runtime/generated/modules/noise.wrapper.ts
3078
+ var Noise = /* @__PURE__ */ defineModule(noiseDescriptor);
3079
+
3080
+ // src/lib-runtime/generated/modules/octaver.tsx
3081
+ var params28 = {
3082
+ octDown2: { node: "oct2Gain", field: "gain", unit: "factor", default: 0.3, min: 0, max: 1 },
3083
+ octDown1: { node: "oct1Gain", field: "gain", unit: "factor", default: 0.5, min: 0, max: 1 },
3084
+ dry: { node: "dryGain", field: "gain", unit: "factor", default: 0.7, min: 0, max: 1 },
3085
+ octUp: { node: "octUpGain", field: "gain", unit: "factor", default: 0, min: 0, max: 1 }
3086
+ };
3087
+ function makeFullWaveRectifierCurve() {
3088
+ const samples = 8192;
3089
+ const curve = new Float32Array(samples);
3090
+ for (let i = 0; i < samples; i++) {
3091
+ const x = i * 2 / samples - 1;
3092
+ curve[i] = Math.abs(x) * 2 - 1;
3093
+ }
3094
+ return curve;
3095
+ }
3096
+ var octaverDescriptor = /* @__PURE__ */ defineDescriptor({
3097
+ type: "octaver",
3098
+ params: params28,
3099
+ ports: {
3100
+ "in": { type: "input", signal: "audio", node: "input" },
3101
+ "out": { type: "output", signal: "audio", node: "output" }
3102
+ },
3103
+ createModule(id, position) {
3104
+ return { id, type: "octaver", position, ...paramDefaults(params28) };
3105
+ },
3106
+ createNodes(ctx, module) {
3107
+ const input = ctx.createGain();
3108
+ const output = ctx.createGain();
3109
+ const dryGain = ctx.createGain();
3110
+ const oct1Gain = ctx.createGain();
3111
+ const oct2Gain = ctx.createGain();
3112
+ const octUpGain = ctx.createGain();
3113
+ const divider = createOctaverDividerNode(ctx);
3114
+ divider.parameters.get("oct1Level").value = 1;
3115
+ divider.parameters.get("oct2Level").value = 1;
3116
+ const splitter = ctx.createChannelSplitter(2);
3117
+ const lowpass1 = ctx.createBiquadFilter();
3118
+ lowpass1.type = "lowpass";
3119
+ lowpass1.frequency.value = 1200;
3120
+ lowpass1.Q.value = 0.7;
3121
+ const lowpass2 = ctx.createBiquadFilter();
3122
+ lowpass2.type = "lowpass";
3123
+ lowpass2.frequency.value = 600;
3124
+ lowpass2.Q.value = 0.7;
3125
+ const waveshaperUp = ctx.createWaveShaper();
3126
+ waveshaperUp.curve = makeFullWaveRectifierCurve();
3127
+ const bpUp = ctx.createBiquadFilter();
3128
+ bpUp.type = "bandpass";
3129
+ bpUp.frequency.value = 20;
3130
+ bpUp.Q.value = 2.5;
3131
+ const freqTrack = ctx.createGain();
3132
+ freqTrack.gain.value = 2;
3133
+ dryGain.gain.value = module.dry;
3134
+ oct1Gain.gain.value = module.octDown1;
3135
+ oct2Gain.gain.value = module.octDown2;
3136
+ octUpGain.gain.value = module.octUp;
3137
+ input.connect(dryGain);
3138
+ input.connect(divider);
3139
+ divider.connect(splitter, 1, 0);
3140
+ splitter.connect(lowpass1, 0);
3141
+ splitter.connect(lowpass2, 1);
3142
+ lowpass1.connect(oct1Gain);
3143
+ lowpass2.connect(oct2Gain);
3144
+ divider.connect(freqTrack, 2, 0);
3145
+ freqTrack.connect(bpUp.frequency);
3146
+ input.connect(waveshaperUp);
3147
+ waveshaperUp.connect(bpUp);
3148
+ bpUp.connect(octUpGain);
3149
+ dryGain.connect(output);
3150
+ oct1Gain.connect(output);
3151
+ oct2Gain.connect(output);
3152
+ octUpGain.connect(output);
3153
+ return { input, output, dryGain, oct1Gain, oct2Gain, octUpGain, divider, bpUp, freqTrack };
3154
+ },
3155
+ updateParam(nodes, _m, param, value) {
3156
+ if (param === "dry") nodes.dryGain.gain.value = value;
3157
+ else if (param === "octDown1") nodes.oct1Gain.gain.value = value;
3158
+ else if (param === "octDown2") nodes.oct2Gain.gain.value = value;
3159
+ else if (param === "octUp") nodes.octUpGain.gain.value = value;
3160
+ }
3161
+ });
3162
+
3163
+ // src/lib-runtime/generated/modules/octaver.wrapper.ts
3164
+ var Octaver = /* @__PURE__ */ defineModule(octaverDescriptor, {
3165
+ worklets: {
3166
+ "freq-divider": freq_divider_default
3167
+ }
3168
+ });
3169
+
3170
+ // src/lib-runtime/generated/modules/phaser.tsx
3171
+ var params29 = {
3172
+ rate: { node: "lfo", field: "frequency", unit: "Hz", default: 0.5, min: 0.1, max: 10 },
3173
+ depth: { unit: "factor", default: 0.7, min: 0, max: 1 },
3174
+ feedback: { node: "feedback", field: "gain", unit: "factor", default: 0.6, min: 0, max: 0.9 },
3175
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.5, min: 0, max: 1 }
3176
+ };
3177
+ var phaserDescriptor = /* @__PURE__ */ defineDescriptor({
3178
+ type: "phaser",
3179
+ params: params29,
3180
+ ports: {
3181
+ "in": { type: "input", signal: "audio", node: "input" },
3182
+ "out": { type: "output", signal: "audio", node: "output" },
3183
+ "modRate": { type: "modulation", signal: "modulation", node: "lfo", field: "frequency" },
3184
+ "modFeedback": { type: "modulation", signal: "modulation", node: "feedback", field: "gain" },
3185
+ "modMix": { type: "modulation", signal: "modulation", node: "_mix", field: "offset" }
3186
+ },
3187
+ createModule(id, position) {
3188
+ return { id, type: "phaser", position, ...paramDefaults(params29) };
3189
+ },
3190
+ createNodes(ctx, module) {
3191
+ const input = ctx.createGain();
3192
+ const output = ctx.createGain();
3193
+ const dryGain = ctx.createGain();
3194
+ const wetGain = ctx.createGain();
3195
+ const lfo = ctx.createOscillator();
3196
+ const feedback = ctx.createGain();
3197
+ const stageBases = [200, 400, 600, 800];
3198
+ const allpasses = stageBases.map((base) => {
3199
+ const ap = ctx.createBiquadFilter();
3200
+ ap.type = "allpass";
3201
+ ap.frequency.value = base;
3202
+ ap.Q.value = 0.5;
3203
+ return ap;
3204
+ });
3205
+ const stageGains = stageBases.map((base) => {
3206
+ const g = ctx.createGain();
3207
+ g.gain.value = module.depth * base * 0.8;
3208
+ return g;
3209
+ });
3210
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
3211
+ feedback.gain.value = module.feedback;
3212
+ lfo.type = "sine";
3213
+ lfo.frequency.value = module.rate;
3214
+ allpasses.forEach((ap, i) => {
3215
+ lfo.connect(stageGains[i]);
3216
+ stageGains[i].connect(ap.frequency);
3217
+ });
3218
+ lfo.start();
3219
+ input.connect(dryGain);
3220
+ input.connect(allpasses[0]);
3221
+ allpasses[0].connect(allpasses[1]);
3222
+ allpasses[1].connect(allpasses[2]);
3223
+ allpasses[2].connect(allpasses[3]);
3224
+ allpasses[3].connect(feedback);
3225
+ feedback.connect(allpasses[0]);
3226
+ allpasses[3].connect(wetGain);
3227
+ dryGain.connect(output);
3228
+ wetGain.connect(output);
3229
+ return {
3230
+ input,
3231
+ output,
3232
+ lfo,
3233
+ feedback,
3234
+ dryGain,
3235
+ wetGain,
3236
+ _mix,
3237
+ stageGain0: stageGains[0],
3238
+ stageGain1: stageGains[1],
3239
+ stageGain2: stageGains[2],
3240
+ stageGain3: stageGains[3]
3241
+ };
3242
+ },
3243
+ updateParam(nodes, _m, param, value) {
3244
+ switch (param) {
3245
+ case "rate":
3246
+ nodes.lfo.frequency.value = value;
3247
+ break;
3248
+ case "depth": {
3249
+ const d = value;
3250
+ [200, 400, 600, 800].forEach((base, i) => {
3251
+ const g = nodes[`stageGain${i}`];
3252
+ g.gain.value = d * base * 0.8;
3253
+ });
3254
+ break;
3255
+ }
3256
+ case "feedback":
3257
+ nodes.feedback.gain.value = value;
3258
+ break;
3259
+ case "mix":
3260
+ nodes._mix.offset.value = value;
3261
+ break;
3262
+ }
3263
+ },
3264
+ dispose(nodes) {
3265
+ nodes.lfo.stop();
3266
+ nodes._mix.stop();
3267
+ }
3268
+ });
3269
+
3270
+ // src/lib-runtime/generated/modules/phaser.wrapper.ts
3271
+ var Phaser = /* @__PURE__ */ defineModule(phaserDescriptor);
3272
+
3273
+ // src/lib-runtime/generated/modules/ping-pong.tsx
3274
+ var params30 = {
3275
+ time: { node: "worklet", field: "time", unit: "s", default: 0.3, min: 0.1, max: 1, scale: "log" },
3276
+ feedback: { node: "worklet", field: "feedback", unit: "factor", default: 0.4, min: 0, max: 0.9 },
3277
+ spread: { unit: "factor", default: 1, min: 0, max: 1 },
3278
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.5, min: 0, max: 1 }
3279
+ };
3280
+ var pingPongDescriptor = /* @__PURE__ */ defineDescriptor({
3281
+ type: "ping-pong",
3282
+ params: params30,
3283
+ ports: {
3284
+ "in": { type: "input", signal: "audio", node: "input" },
3285
+ "out": { type: "output", signal: "audio", node: "output" }
3286
+ },
3287
+ createModule(id, position) {
3288
+ return { id, type: "ping-pong", position, ...paramDefaults(params30) };
3289
+ },
3290
+ // The delay lines and cross-feedback live in the 'ping-pong' AudioWorklet
3291
+ // (src/worklets/ping-pong.ts) as of 2026-08-08. A native two-DelayNode
3292
+ // cycle carries ONE render quantum of hidden latency at an UNSTABLE
3293
+ // position: identical code measured +3.01 dB (coherent grid) on one run
3294
+ // and −0.19 dB (right leg off by a quantum) on the next, so no static
3295
+ // delayTime compensation can hold the tap grid. The worklet's grid is
3296
+ // exact by construction. Pans, spread, and dry/wet mix stay native.
3297
+ createNodes(ctx, module) {
3298
+ const input = ctx.createGain();
3299
+ const output = ctx.createGain();
3300
+ const dryGain = ctx.createGain();
3301
+ const wetGain = ctx.createGain();
3302
+ const panL = ctx.createStereoPanner();
3303
+ const panR = ctx.createStereoPanner();
3304
+ const worklet = createPingPongNode(ctx, {
3305
+ time: module.time,
3306
+ feedback: module.feedback
3307
+ });
3308
+ panL.pan.value = -module.spread;
3309
+ panR.pan.value = module.spread;
3310
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
3311
+ input.connect(dryGain);
3312
+ input.connect(worklet);
3313
+ worklet.connect(panL, 0);
3314
+ worklet.connect(panR, 1);
3315
+ panL.connect(wetGain);
3316
+ panR.connect(wetGain);
3317
+ dryGain.connect(output);
3318
+ wetGain.connect(output);
3319
+ return { input, output, worklet, dryGain, wetGain, panL, panR, _mix };
3320
+ },
3321
+ updateParam(nodes, _m, param, value) {
3322
+ if (param === "time" || param === "feedback") {
3323
+ const p = nodes.worklet.parameters.get(param);
3324
+ if (p) p.value = value;
3325
+ } else if (param === "spread") {
3326
+ nodes.panL.pan.value = -value;
3327
+ nodes.panR.pan.value = value;
3328
+ } else if (param === "mix") {
3329
+ nodes._mix.offset.value = value;
3330
+ }
3331
+ }
3332
+ });
3333
+
3334
+ // src/lib-runtime/generated/modules/ping-pong.wrapper.ts
3335
+ var PingPong = /* @__PURE__ */ defineModule(pingPongDescriptor, {
3336
+ worklets: {
3337
+ "ping-pong": ping_pong_default
3338
+ }
3339
+ });
3340
+
3341
+ // src/lib/dsp/modal.ts
3342
+ var MODAL_Q_MAX = 1e3;
3343
+ var MODAL_Q_MIN = 2;
3344
+ var T60_PER_Q_HZ = 3 * Math.LN10 / Math.PI;
3345
+ function dampingToQ(damping) {
3346
+ const d = Math.min(1, Math.max(0, Number.isFinite(damping) ? damping : 0.5));
3347
+ return MODAL_Q_MAX * Math.pow(MODAL_Q_MIN / MODAL_Q_MAX, d);
3348
+ }
3349
+ function modalMakeup(q) {
3350
+ return Math.sqrt(Math.max(1, q));
3351
+ }
3352
+
3353
+ // src/lib-runtime/generated/modules/resonator.tsx
3354
+ var params31 = {
3355
+ // Root pitch = V/OCT input (absolute notes, silence = 0) + MOD (semitones) + offset.
3356
+ offset: { node: "_root", field: "offset", default: 57, min: -48, max: 108, unit: "semitone" },
3357
+ structure: { unit: "factor", default: 0, min: 0, max: 1 },
3358
+ damping: { unit: "factor", default: 0.5, min: 0, max: 1 },
3359
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.5, min: 0, max: 1 }
3360
+ };
3361
+ function partialOffset(partial, structure) {
3362
+ return 12 * (1 + structure) * Math.log2(partial);
3363
+ }
3364
+ var patchCount2 = /* @__PURE__ */ new WeakMap();
3365
+ var resonatorDescriptor = /* @__PURE__ */ defineDescriptor({
3366
+ type: "resonator",
3367
+ params: params31,
3368
+ ports: {
3369
+ "in": { type: "input", signal: "audio", node: "input" },
3370
+ "out": { type: "output", signal: "audio", node: "output" },
3371
+ "voct": { type: "input", signal: "pitch", node: "_pitch" },
3372
+ "modFreq": { type: "modulation", signal: "modulation", node: "_pitch" }
3373
+ },
3374
+ createModule(id, position) {
3375
+ const { offset: _dsp, ...rest } = paramDefaults(params31);
3376
+ return { id, type: "resonator", position, ...rest, note: 57, transpose: 0 };
3377
+ },
3378
+ createNodes(ctx, module) {
3379
+ const input = ctx.createGain();
3380
+ const output = ctx.createGain();
3381
+ const dryGain = ctx.createGain();
3382
+ const wetGain = ctx.createGain();
3383
+ const _root = ctx.createConstantSource();
3384
+ _root.offset.value = module.offset ?? module.note;
3385
+ _root.start();
3386
+ const _pitch = ctx.createGain();
3387
+ _root.connect(_pitch);
3388
+ const filters = [];
3389
+ const partialGains = [];
3390
+ const voct = [];
3391
+ const q = dampingToQ(module.damping);
3392
+ const makeup = modalMakeup(q);
3393
+ for (let i = 1; i <= 5; i++) {
3394
+ const v = createVoctFreqNode(ctx);
3395
+ v.parameters.get("transpose").value = partialOffset(i, module.structure);
3396
+ _pitch.connect(v);
3397
+ const filter = ctx.createBiquadFilter();
3398
+ filter.type = "bandpass";
3399
+ filter.frequency.value = 0;
3400
+ v.connect(filter.frequency);
3401
+ filter.Q.value = q;
3402
+ filters.push(filter);
3403
+ voct.push(v);
3404
+ input.connect(filter);
3405
+ const partialGain = ctx.createGain();
3406
+ partialGain.gain.value = makeup / Math.sqrt(i);
3407
+ filter.connect(partialGain);
3408
+ partialGain.connect(wetGain);
3409
+ partialGains.push(partialGain);
3410
+ }
3411
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear", 0.6);
3412
+ input.connect(dryGain);
3413
+ dryGain.connect(output);
3414
+ wetGain.connect(output);
3415
+ return {
3416
+ input,
3417
+ output,
3418
+ dryGain,
3419
+ wetGain,
3420
+ _mix,
3421
+ _root,
3422
+ _pitch,
3423
+ v1: voct[0],
3424
+ v2: voct[1],
3425
+ v3: voct[2],
3426
+ v4: voct[3],
3427
+ v5: voct[4],
3428
+ f1: filters[0],
3429
+ f2: filters[1],
3430
+ f3: filters[2],
3431
+ f4: filters[3],
3432
+ f5: filters[4],
3433
+ g1: partialGains[0],
3434
+ g2: partialGains[1],
3435
+ g3: partialGains[2],
3436
+ g4: partialGains[3],
3437
+ g5: partialGains[4]
3438
+ };
3439
+ },
3440
+ updateParam(nodes, _module, param, value) {
3441
+ const filters = [nodes.f1, nodes.f2, nodes.f3, nodes.f4, nodes.f5];
3442
+ if (param === "offset") {
3443
+ nodes._root.offset.value = value;
3444
+ } else if (param === "note") {
3445
+ if (!patchCount2.get(nodes)) nodes._root.offset.value = value;
3446
+ } else if (param === "transpose") {
3447
+ if (patchCount2.get(nodes)) nodes._root.offset.value = value;
3448
+ } else if (param === "structure") {
3449
+ const voct = [nodes.v1, nodes.v2, nodes.v3, nodes.v4, nodes.v5];
3450
+ for (let i = 1; i < 5; i++) {
3451
+ voct[i].parameters.get("transpose").value = partialOffset(i + 1, value);
3452
+ }
3453
+ } else if (param === "damping") {
3454
+ const q = dampingToQ(value);
3455
+ const makeup = modalMakeup(q);
3456
+ const gains = [nodes.g1, nodes.g2, nodes.g3, nodes.g4, nodes.g5];
3457
+ for (let i = 0; i < 5; i++) {
3458
+ filters[i].Q.value = q;
3459
+ gains[i].gain.value = makeup / Math.sqrt(i + 1);
3460
+ }
3461
+ } else if (param === "mix") {
3462
+ nodes._mix.offset.value = value;
3463
+ }
3464
+ },
3465
+ // App-only: plain summing connect; the hooks only swap which panel memory
3466
+ // drives the offset.
3467
+ onConnect(nodes, module, portId, sourceNode) {
3468
+ if (portId !== "voct") return false;
3469
+ sourceNode.connect(nodes._pitch);
3470
+ patchCount2.set(nodes, (patchCount2.get(nodes) ?? 0) + 1);
3471
+ nodes._root.offset.value = module.transpose ?? 0;
3472
+ return true;
3473
+ },
3474
+ onDisconnect(nodes, module, portId, sourceNode) {
3475
+ if (portId !== "voct") return false;
3476
+ try {
3477
+ sourceNode.disconnect(nodes._pitch);
3478
+ } catch (_) {
3479
+ }
3480
+ const n = Math.max(0, (patchCount2.get(nodes) ?? 0) - 1);
3481
+ patchCount2.set(nodes, n);
3482
+ if (!n) nodes._root.offset.value = module.note;
3483
+ return true;
3484
+ },
3485
+ dispose(nodes) {
3486
+ nodes._root.stop();
3487
+ }
3488
+ });
3489
+
3490
+ // src/lib-runtime/generated/modules/resonator.wrapper.ts
3491
+ var Resonator = /* @__PURE__ */ defineModule(resonatorDescriptor, {
3492
+ worklets: {
3493
+ "map-voct-to-freq": map_voct_to_freq_default
3494
+ }
3495
+ });
3496
+
3497
+ // src/lib/dsp/fdnTank.ts
3498
+ function createFdnTank(ctx, opts) {
3499
+ const input = ctx.createGain();
3500
+ const output = ctx.createGain();
3501
+ const node = createFdnReverbNode(ctx, opts);
3502
+ input.connect(node);
3503
+ node.connect(output);
3504
+ return { input, output, node };
3505
+ }
3506
+ function setTankDecay(tank, rt60, now2, t = 0.02) {
3507
+ tank.node.parameters.get("rt60").setTargetAtTime(rt60, now2, t);
3508
+ }
3509
+
3510
+ // src/lib-runtime/generated/modules/reverb.tsx
3511
+ var params32 = {
3512
+ decay: { node: "tankNode", field: "rt60", default: 2, min: 0.1, max: 8, scale: "log", unit: "s" },
3513
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.3, min: 0, max: 1 }
3514
+ };
3515
+ var reverbDescriptor = /* @__PURE__ */ defineDescriptor({
3516
+ type: "reverb",
3517
+ params: params32,
3518
+ ports: {
3519
+ "in": { type: "input", signal: "audio", node: "input" },
3520
+ "out": { type: "output", signal: "audio", node: "output" }
3521
+ },
3522
+ createModule(id, position) {
3523
+ return {
3524
+ id,
3525
+ type: "reverb",
3526
+ position,
3527
+ ...paramDefaults(params32)
3528
+ };
3529
+ },
3530
+ createNodes(ctx, module) {
3531
+ const input = ctx.createGain();
3532
+ const output = ctx.createGain();
3533
+ const dryGain = ctx.createGain();
3534
+ const wetGain = ctx.createGain();
3535
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "equal-power");
3536
+ const preDelay = ctx.createDelay(0.5);
3537
+ preDelay.delayTime.value = 0.02;
3538
+ const tank = createFdnTank(ctx, { rt60: module.decay, damping: 0.55 });
3539
+ input.connect(dryGain);
3540
+ input.connect(preDelay);
3541
+ preDelay.connect(tank.input);
3542
+ tank.output.connect(wetGain);
3543
+ dryGain.connect(output);
3544
+ wetGain.connect(output);
3545
+ return {
3546
+ input,
3547
+ output,
3548
+ dryGain,
3549
+ wetGain,
3550
+ _mix,
3551
+ preDelay,
3552
+ tankInput: tank.input,
3553
+ tankOutput: tank.output,
3554
+ tankNode: tank.node
3555
+ };
3556
+ },
3557
+ updateParam(nodes, _m, param, value) {
3558
+ const now2 = nodes.input.context.currentTime;
3559
+ if (param === "mix") {
3560
+ nodes._mix.offset.setTargetAtTime(value, now2, 0.02);
3561
+ } else if (param === "decay") {
3562
+ setTankDecay({ node: nodes.tankNode }, value, now2);
3563
+ }
3564
+ }
3565
+ });
3566
+
3567
+ // src/lib-runtime/generated/modules/reverb.wrapper.ts
3568
+ var Reverb = /* @__PURE__ */ defineModule(reverbDescriptor, {
3569
+ worklets: {
3570
+ "fdn-reverb": fdn_reverb_default
3571
+ }
3572
+ });
3573
+
3574
+ // src/lib-runtime/generated/modules/ring-mod.tsx
3575
+ var params33 = {
3576
+ // Carrier pitch = V/OCT input (absolute notes, silence = 0) + MOD (semitones) + offset.
3577
+ offset: { node: "_carrierVoct", field: "transpose", unit: "semitone", default: 69, min: -48, max: 108 },
3578
+ // Dry/wet is split across two gain nodes, not one AudioParam — set-only.
3579
+ mix: { unit: "factor", default: 0.5, min: 0, max: 1 }
3580
+ };
3581
+ var patchCount3 = /* @__PURE__ */ new WeakMap();
3582
+ var ringModDescriptor = /* @__PURE__ */ defineDescriptor({
3583
+ type: "ring-mod",
3584
+ params: params33,
3585
+ ports: {
3586
+ "in": { type: "input", signal: "audio", node: "input" },
3587
+ "out": { type: "output", signal: "audio", node: "output" },
3588
+ "voct": { type: "input", signal: "pitch", node: "_carrierVoct" },
3589
+ "modFreq": { type: "modulation", signal: "modulation", node: "_carrierVoct" }
3590
+ },
3591
+ createModule(id, position) {
3592
+ const { offset: _dsp, ...rest } = paramDefaults(params33);
3593
+ return { id, type: "ring-mod", position, ...rest, note: 69, transpose: 0 };
3594
+ },
3595
+ createNodes(ctx, module) {
3596
+ const input = ctx.createGain();
3597
+ const output = ctx.createGain();
3598
+ const dryGain = ctx.createGain();
3599
+ const wetGain = ctx.createGain();
3600
+ const carrier = ctx.createOscillator();
3601
+ const ringMod = ctx.createGain();
3602
+ dryGain.gain.value = 1 - module.mix;
3603
+ wetGain.gain.value = module.mix;
3604
+ carrier.type = "sine";
3605
+ ringMod.gain.value = 0;
3606
+ carrier.connect(ringMod.gain);
3607
+ carrier.start();
3608
+ carrier.frequency.value = 0;
3609
+ const _carrierVoct = createVoctFreqNode(ctx);
3610
+ _carrierVoct.parameters.get("transpose").value = module.offset ?? module.note;
3611
+ const _carrierBase = ctx.createConstantSource();
3612
+ _carrierBase.offset.value = 0;
3613
+ _carrierBase.start();
3614
+ _carrierBase.connect(_carrierVoct);
3615
+ _carrierVoct.connect(carrier.frequency);
3616
+ input.connect(dryGain);
3617
+ input.connect(ringMod);
3618
+ ringMod.connect(wetGain);
3619
+ dryGain.connect(output);
3620
+ wetGain.connect(output);
3621
+ return { input, output, carrier, ringMod, dryGain, wetGain, _carrierVoct, _carrierBase };
3622
+ },
3623
+ updateParam(nodes, _m, param, value) {
3624
+ const t = nodes._carrierVoct.parameters.get("transpose");
3625
+ if (param === "offset") t.value = value;
3626
+ else if (param === "note") {
3627
+ if (!patchCount3.get(nodes)) t.value = value;
3628
+ } else if (param === "transpose") {
3629
+ if (patchCount3.get(nodes)) t.value = value;
3630
+ } else if (param === "mix") {
3631
+ nodes.dryGain.gain.value = 1 - value;
3632
+ nodes.wetGain.gain.value = value;
3633
+ }
3634
+ },
3635
+ // App-only: plain summing connect; the hooks only swap which panel memory
3636
+ // drives the offset.
3637
+ onConnect(nodes, module, portId, sourceNode) {
3638
+ if (portId !== "voct") return false;
3639
+ sourceNode.connect(nodes._carrierVoct);
3640
+ patchCount3.set(nodes, (patchCount3.get(nodes) ?? 0) + 1);
3641
+ nodes._carrierVoct.parameters.get("transpose").value = module.transpose ?? 0;
3642
+ return true;
3643
+ },
3644
+ onDisconnect(nodes, module, portId, sourceNode) {
3645
+ if (portId !== "voct") return false;
3646
+ try {
3647
+ sourceNode.disconnect(nodes._carrierVoct);
3648
+ } catch (_) {
3649
+ }
3650
+ const n = Math.max(0, (patchCount3.get(nodes) ?? 0) - 1);
3651
+ patchCount3.set(nodes, n);
3652
+ if (!n) nodes._carrierVoct.parameters.get("transpose").value = module.note;
3653
+ return true;
3654
+ },
3655
+ dispose(nodes) {
3656
+ try {
3657
+ nodes.carrier.stop();
3658
+ } catch (_) {
3659
+ }
3660
+ try {
3661
+ nodes._carrierBase.stop();
3662
+ } catch (_) {
3663
+ }
3664
+ }
3665
+ });
3666
+
3667
+ // src/lib-runtime/generated/modules/ring-mod.wrapper.ts
3668
+ var RingModulator = /* @__PURE__ */ defineModule(ringModDescriptor, {
3669
+ worklets: {
3670
+ "map-voct-to-freq": map_voct_to_freq_default
3671
+ }
3672
+ });
3673
+
3674
+ // src/lib-runtime/generated/modules/rotary.tsx
3675
+ var CROSSOVER_HZ = 800;
3676
+ var BUTTERWORTH_Q_DB = -3.0103;
3677
+ var DRUM_RATIO = 0.85;
3678
+ var HORN = { baseDelay: 3e-3, dopplerSec: 44e-5, ampBase: 0.7, ampDepth: 0.3, phaseDeg: 0, tauUp: 0.35, tauDown: 0.55 };
3679
+ var DRUM = { baseDelay: 3e-3, dopplerSec: 2e-4, ampBase: 0.8, ampDepth: 0.2, phaseDeg: 90, tauUp: 1.5, tauDown: 1.7 };
3680
+ var params34 = {
3681
+ speed: { kind: "enum", options: ["slow", "fast"], default: "slow" },
3682
+ slowRate: { unit: "Hz", default: 0.8, min: 0.5, max: 2 },
3683
+ fastRate: { unit: "Hz", default: 6, min: 4, max: 8 },
3684
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.7, min: 0, max: 1 }
3685
+ };
3686
+ var rotaryDescriptor = /* @__PURE__ */ defineDescriptor({
3687
+ type: "rotary",
3688
+ params: params34,
3689
+ ports: {
3690
+ "in": { type: "input", signal: "audio", node: "input" },
3691
+ "out": { type: "output", signal: "audio", node: "output" },
3692
+ "modMix": { type: "modulation", signal: "modulation", node: "_mix", field: "offset" }
3693
+ },
3694
+ createModule(id, position) {
3695
+ return { id, type: "rotary", position, ...paramDefaults(params34) };
3696
+ },
3697
+ createNodes(ctx, module) {
3698
+ const input = ctx.createGain();
3699
+ const output = ctx.createGain();
3700
+ const dryGain = ctx.createGain();
3701
+ const wetGain = ctx.createGain();
3702
+ const monoIn = ctx.createGain();
3703
+ monoIn.channelCount = 1;
3704
+ monoIn.channelCountMode = "explicit";
3705
+ monoIn.channelInterpretation = "speakers";
3706
+ const merger = ctx.createChannelMerger(2);
3707
+ const MIC_DEG = 60;
3708
+ const biquad = (type) => {
3709
+ const f = ctx.createBiquadFilter();
3710
+ f.type = type;
3711
+ f.frequency.value = CROSSOVER_HZ;
3712
+ f.Q.value = BUTTERWORTH_Q_DB;
3713
+ return f;
3714
+ };
3715
+ const lp1 = biquad("lowpass"), lp2 = biquad("lowpass");
3716
+ const hp1 = biquad("highpass"), hp2 = biquad("highpass");
3717
+ monoIn.connect(lp1);
3718
+ lp1.connect(lp2);
3719
+ monoIn.connect(hp1);
3720
+ hp1.connect(hp2);
3721
+ const hornRate = module.speed === "slow" ? module.slowRate : module.fastRate;
3722
+ const sources = [];
3723
+ const rotor = (band, spec, rate) => {
3724
+ const speed = ctx.createConstantSource();
3725
+ speed.offset.value = rate;
3726
+ sources.push(speed);
3727
+ [MIC_DEG, -MIC_DEG].forEach((micDeg, ch) => {
3728
+ const phi = (micDeg + spec.phaseDeg) * Math.PI / 180;
3729
+ const wave = ctx.createPeriodicWave(
3730
+ new Float32Array([0, Math.cos(phi)]),
3731
+ new Float32Array([0, Math.sin(phi)]),
3732
+ { disableNormalization: true }
3733
+ );
3734
+ const osc = ctx.createOscillator();
3735
+ osc.setPeriodicWave(wave);
3736
+ osc.frequency.value = 0;
3737
+ speed.connect(osc.frequency);
3738
+ sources.push(osc);
3739
+ const delay = ctx.createDelay(0.02);
3740
+ delay.delayTime.value = spec.baseDelay;
3741
+ const dop = ctx.createGain();
3742
+ dop.gain.value = -spec.dopplerSec;
3743
+ osc.connect(dop);
3744
+ dop.connect(delay.delayTime);
3745
+ const amp = ctx.createGain();
3746
+ amp.gain.value = spec.ampBase;
3747
+ const am = ctx.createGain();
3748
+ am.gain.value = spec.ampDepth;
3749
+ osc.connect(am);
3750
+ am.connect(amp.gain);
3751
+ band.connect(delay);
3752
+ delay.connect(amp);
3753
+ amp.connect(merger, 0, ch);
3754
+ });
3755
+ return speed;
3756
+ };
3757
+ const hornSpeed = rotor(hp2, HORN, hornRate);
3758
+ const drumSpeed = rotor(lp2, DRUM, hornRate * DRUM_RATIO);
3759
+ sources.forEach((n) => n.start());
3760
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
3761
+ input.connect(dryGain);
3762
+ input.connect(monoIn);
3763
+ merger.connect(wetGain);
3764
+ dryGain.connect(output);
3765
+ wetGain.connect(output);
3766
+ const srcNodes = Object.fromEntries(sources.map((n, i) => [`src${i}`, n]));
3767
+ return { input, output, dryGain, wetGain, hornSpeed, drumSpeed, _mix, ...srcNodes };
3768
+ },
3769
+ updateParam(nodes, module, param, value) {
3770
+ const now2 = nodes.input.context.currentTime;
3771
+ const setRate = (horn, inertia) => {
3772
+ const hs = nodes.hornSpeed;
3773
+ const ds = nodes.drumSpeed;
3774
+ const up = horn > hs.offset.value;
3775
+ const tH = inertia ? up ? HORN.tauUp : HORN.tauDown : 0.05;
3776
+ const tD = inertia ? up ? DRUM.tauUp : DRUM.tauDown : 0.05;
3777
+ hs.offset.cancelScheduledValues(now2);
3778
+ ds.offset.cancelScheduledValues(now2);
3779
+ hs.offset.setTargetAtTime(horn, now2, tH);
3780
+ ds.offset.setTargetAtTime(horn * DRUM_RATIO, now2, tD);
3781
+ };
3782
+ if (param === "speed") {
3783
+ setRate(value === "slow" ? module.slowRate : module.fastRate, true);
3784
+ } else if (param === "slowRate") {
3785
+ if (module.speed === "slow") setRate(value, false);
3786
+ } else if (param === "fastRate") {
3787
+ if (module.speed === "fast") setRate(value, false);
3788
+ } else if (param === "mix") {
3789
+ nodes._mix.offset.value = value;
3790
+ }
3791
+ },
3792
+ dispose(nodes) {
3793
+ for (const [k, n] of Object.entries(nodes)) {
3794
+ if (k.startsWith("src")) n.stop();
3795
+ }
3796
+ nodes._mix.stop();
3797
+ }
3798
+ });
3799
+
3800
+ // src/lib-runtime/generated/modules/rotary.wrapper.ts
3801
+ var Rotary = /* @__PURE__ */ defineModule(rotaryDescriptor);
3802
+
3803
+ // src/lib-runtime/generated/modules/saturator.tsx
3804
+ var params35 = {
3805
+ // drive feeds both driveGain.gain (1 + drive*4) and the waveshaper curve —
3806
+ // a conversion, not a 1:1 AudioParam write, so no node/field.
3807
+ drive: { unit: "factor", default: 0.5, min: 0, max: 1 },
3808
+ // warmthFilter.gain = warmth * 6 — a scaled conversion, so no node/field.
3809
+ warmth: { unit: "factor", default: 0.5, min: 0, max: 1 },
3810
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 1, min: 0, max: 1 }
3811
+ };
3812
+ function makeSaturationCurve(amount) {
3813
+ const samples = 44100;
3814
+ const curve = new Float32Array(samples);
3815
+ const k = 1 + amount * 10;
3816
+ for (let i = 0; i < samples; i++) {
3817
+ const x = i * 2 / samples - 1;
3818
+ curve[i] = Math.tanh(k * x) / Math.tanh(k);
3819
+ }
3820
+ return curve;
3821
+ }
3822
+ var saturatorDescriptor = /* @__PURE__ */ defineDescriptor({
3823
+ type: "saturator",
3824
+ params: params35,
3825
+ ports: {
3826
+ "in": { type: "input", signal: "audio", node: "input" },
3827
+ "out": { type: "output", signal: "audio", node: "output" }
3828
+ },
3829
+ createModule(id, position) {
3830
+ return { id, type: "saturator", position, ...paramDefaults(params35) };
3831
+ },
3832
+ createNodes(ctx, module) {
3833
+ const input = ctx.createGain();
3834
+ const output = ctx.createGain();
3835
+ const dryGain = ctx.createGain();
3836
+ const wetGain = ctx.createGain();
3837
+ const driveGain = ctx.createGain();
3838
+ const waveshaper = ctx.createWaveShaper();
3839
+ const warmthFilter = ctx.createBiquadFilter();
3840
+ driveGain.gain.value = 1 + module.drive * 4;
3841
+ waveshaper.curve = makeSaturationCurve(module.drive);
3842
+ waveshaper.oversample = "2x";
3843
+ warmthFilter.type = "lowshelf";
3844
+ warmthFilter.frequency.value = 300;
3845
+ warmthFilter.gain.value = module.warmth * 6;
3846
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
3847
+ input.connect(dryGain);
3848
+ input.connect(driveGain);
3849
+ driveGain.connect(waveshaper);
3850
+ waveshaper.connect(warmthFilter);
3851
+ warmthFilter.connect(wetGain);
3852
+ dryGain.connect(output);
3853
+ wetGain.connect(output);
3854
+ return { input, output, driveGain, waveshaper, warmthFilter, dryGain, wetGain, _mix };
3855
+ },
3856
+ updateParam(nodes, _m, param, value) {
3857
+ if (param === "drive") {
3858
+ nodes.driveGain.gain.value = 1 + value * 4;
3859
+ nodes.waveshaper.curve = makeSaturationCurve(value);
3860
+ } else if (param === "warmth") {
3861
+ nodes.warmthFilter.gain.value = value * 6;
3862
+ } else if (param === "mix") {
3863
+ nodes._mix.offset.value = value;
3864
+ }
3865
+ }
3866
+ });
3867
+
3868
+ // src/lib-runtime/generated/modules/saturator.wrapper.ts
3869
+ var Saturator = /* @__PURE__ */ defineModule(saturatorDescriptor);
3870
+
3871
+ // src/lib-runtime/generated/modules/shimmer.tsx
3872
+ var params36 = {
3873
+ decay: { unit: "s", default: 3, min: 0.5, max: 10, scale: "log" },
3874
+ pitch: { unit: "semitone", default: 12, min: -12, max: 24 },
3875
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.5, min: 0, max: 1 },
3876
+ shimmer: { node: "regenGain", field: "gain", unit: "factor", default: 0.5, min: 0, max: 0.95 }
3877
+ };
3878
+ var shimmerDescriptor = /* @__PURE__ */ defineDescriptor({
3879
+ type: "shimmer",
3880
+ params: params36,
3881
+ ports: {
3882
+ "in": { type: "input", signal: "audio", node: "input" },
3883
+ "out": { type: "output", signal: "audio", node: "output" }
3884
+ },
3885
+ createModule(id, position) {
3886
+ return { id, type: "shimmer", position, ...paramDefaults(params36) };
3887
+ },
3888
+ createNodes(ctx, module) {
3889
+ const input = ctx.createGain();
3890
+ const output = ctx.createGain();
3891
+ const dryGain = ctx.createGain();
3892
+ const wetGain = ctx.createGain();
3893
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "equal-power");
3894
+ const tank = createFdnTank(ctx, { rt60: module.decay, damping: 0.5, feedbackCeiling: 0.93 });
3895
+ const splitter = ctx.createChannelSplitter(2);
3896
+ const merger = ctx.createChannelMerger(2);
3897
+ const mkRegenTap = () => {
3898
+ const hp = ctx.createBiquadFilter();
3899
+ hp.type = "highpass";
3900
+ hp.frequency.value = 300;
3901
+ const shifter = createPitchShifterNode(ctx, module.pitch);
3902
+ return { hp, shifter };
3903
+ };
3904
+ const tapL = mkRegenTap();
3905
+ const tapR = mkRegenTap();
3906
+ const regenGain = ctx.createGain();
3907
+ regenGain.gain.value = module.shimmer;
3908
+ const regenDelay = ctx.createDelay(0.1);
3909
+ regenDelay.delayTime.value = 0.02;
3910
+ tank.output.connect(splitter);
3911
+ splitter.connect(tapL.hp, 0);
3912
+ splitter.connect(tapR.hp, 1);
3913
+ tapL.hp.connect(tapL.shifter);
3914
+ tapR.hp.connect(tapR.shifter);
3915
+ tapL.shifter.connect(merger, 0, 0);
3916
+ tapR.shifter.connect(merger, 0, 1);
3917
+ merger.connect(regenGain);
3918
+ regenGain.connect(regenDelay);
3919
+ regenDelay.connect(tank.input);
3920
+ input.connect(dryGain);
3921
+ input.connect(tank.input);
3922
+ tank.output.connect(wetGain);
3923
+ dryGain.connect(output);
3924
+ wetGain.connect(output);
3925
+ return {
3926
+ input,
3927
+ output,
3928
+ dryGain,
3929
+ wetGain,
3930
+ _mix,
3931
+ splitter,
3932
+ merger,
3933
+ highpassL: tapL.hp,
3934
+ highpassR: tapR.hp,
3935
+ pitchShifterL: tapL.shifter,
3936
+ pitchShifterR: tapR.shifter,
3937
+ regenGain,
3938
+ regenDelay,
3939
+ tankInput: tank.input,
3940
+ tankOutput: tank.output,
3941
+ tankNode: tank.node
3942
+ };
3943
+ },
3944
+ updateParam(nodes, _m, param, value) {
3945
+ const now2 = nodes.input.context.currentTime;
3946
+ if (param === "mix") {
3947
+ nodes._mix.offset.value = value;
3948
+ } else if (param === "shimmer") {
3949
+ nodes.regenGain.gain.setTargetAtTime(value, now2, 0.02);
3950
+ } else if (param === "pitch") {
3951
+ for (const key of ["pitchShifterL", "pitchShifterR"]) {
3952
+ nodes[key].parameters.get("semitones").setTargetAtTime(value, now2, 0.02);
3953
+ }
3954
+ } else if (param === "decay") {
3955
+ setTankDecay({ node: nodes.tankNode }, value, now2);
3956
+ }
3957
+ }
3958
+ });
3959
+
3960
+ // src/lib-runtime/generated/modules/shimmer.wrapper.ts
3961
+ var Shimmer = /* @__PURE__ */ defineModule(shimmerDescriptor, {
3962
+ worklets: {
3963
+ "pitch-shifter": pitch_shifter_default,
3964
+ "fdn-reverb": fdn_reverb_default
3965
+ }
3966
+ });
3967
+
3968
+ // src/lib-runtime/generated/modules/soft-limit.tsx
3969
+ var params37 = {
3970
+ drive: { unit: "factor", default: 0.5, min: 0, max: 1 },
3971
+ knee: { unit: "factor", default: 0.5, min: 0, max: 1 },
3972
+ ceiling: { unit: "dB", default: -0.5, min: -12, max: 0 }
3973
+ };
3974
+ function makeSoftLimitCurve(knee, drive) {
3975
+ const samples = 44100;
3976
+ const curve = new Float32Array(samples);
3977
+ const k = 0.1 + knee * 0.9;
3978
+ const driveMul = 1 + drive * 3;
3979
+ for (let i = 0; i < samples; i++) {
3980
+ const x = (i * 2 / samples - 1) * driveMul;
3981
+ if (Math.abs(x) < k) {
3982
+ curve[i] = x;
3983
+ } else {
3984
+ const sign = x > 0 ? 1 : -1;
3985
+ curve[i] = sign * (k + (1 - k) * Math.tanh((Math.abs(x) - k) / (1 - k)));
3986
+ }
3987
+ }
3988
+ return curve;
3989
+ }
3990
+ var softLimitDescriptor = /* @__PURE__ */ defineDescriptor({
3991
+ type: "soft-limit",
3992
+ params: params37,
3993
+ ports: {
3994
+ "in": { type: "input", signal: "audio", node: "input" },
3995
+ "out": { type: "output", signal: "audio", node: "output" }
3996
+ },
3997
+ createModule(id, position) {
3998
+ return { id, type: "soft-limit", position, ...paramDefaults(params37) };
3999
+ },
4000
+ createNodes(ctx, module) {
4001
+ const input = ctx.createGain();
4002
+ const output = ctx.createGain();
4003
+ const waveshaper = ctx.createWaveShaper();
4004
+ const ceiling = ctx.createGain();
4005
+ waveshaper.curve = makeSoftLimitCurve(module.knee, module.drive);
4006
+ waveshaper.oversample = "2x";
4007
+ ceiling.gain.value = Math.pow(10, module.ceiling / 20);
4008
+ input.connect(waveshaper);
4009
+ waveshaper.connect(ceiling);
4010
+ ceiling.connect(output);
4011
+ return { input, output, waveshaper, ceiling };
4012
+ },
4013
+ updateParam(nodes, m, param, value) {
4014
+ if (param === "drive") nodes.waveshaper.curve = makeSoftLimitCurve(m.knee, value);
4015
+ else if (param === "knee") nodes.waveshaper.curve = makeSoftLimitCurve(value, m.drive);
4016
+ else if (param === "ceiling") nodes.ceiling.gain.value = Math.pow(10, value / 20);
4017
+ }
4018
+ });
4019
+
4020
+ // src/lib-runtime/generated/modules/soft-limit.wrapper.ts
4021
+ var SoftLimiter = /* @__PURE__ */ defineModule(softLimitDescriptor);
4022
+
4023
+ // src/lib-runtime/generated/modules/spring-reverb.tsx
4024
+ var params38 = {
4025
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.35, min: 0, max: 1 },
4026
+ decay: { node: "_worklet", field: "decay", default: 1.5, min: 0.3, max: 4, scale: "log", unit: "s" },
4027
+ tone: { node: "_worklet", field: "tone", unit: "factor", default: 0.6, min: 0, max: 1 },
4028
+ drip: { node: "_worklet", field: "drip", unit: "factor", default: 0.5, min: 0, max: 1 }
4029
+ };
4030
+ var springReverbDescriptor = /* @__PURE__ */ defineDescriptor({
4031
+ type: "spring-reverb",
4032
+ params: params38,
4033
+ ports: {
4034
+ "in": { type: "input", signal: "audio", node: "input" },
4035
+ "knock": { type: "input", signal: "gate", node: "_knockIn" },
4036
+ "out": { type: "output", signal: "audio", node: "output" }
4037
+ },
4038
+ createModule(id, position) {
4039
+ return { id, type: "spring-reverb", position, ...paramDefaults(params38) };
4040
+ },
4041
+ createNodes(ctx, module) {
4042
+ const input = ctx.createGain();
4043
+ const output = ctx.createGain();
4044
+ const dryGain = ctx.createGain();
4045
+ const wetGain = ctx.createGain();
4046
+ const _worklet = createSpringReverbNode(ctx, {
4047
+ decay: module.decay,
4048
+ tone: module.tone,
4049
+ drip: module.drip
4050
+ });
4051
+ const _knockIn = ctx.createGain();
4052
+ _knockIn.gain.value = 1;
4053
+ _knockIn.connect(_worklet, 0, 1);
4054
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "equal-power");
4055
+ input.connect(dryGain);
4056
+ input.connect(_worklet, 0, 0);
4057
+ _worklet.connect(wetGain);
4058
+ dryGain.connect(output);
4059
+ wetGain.connect(output);
4060
+ return { input, output, dryGain, wetGain, _mix, _worklet, _knockIn };
4061
+ },
4062
+ updateParam(nodes, _module, param, value, when) {
4063
+ const worklet = nodes._worklet;
4064
+ if (param === "mix") {
4065
+ nodes._mix.offset.value = value;
4066
+ } else if (param === "decay" || param === "tone" || param === "drip") {
4067
+ worklet.parameters.get(param).value = value;
4068
+ } else if (param === "knock") {
4069
+ pulse(nodes._knockIn, when);
4070
+ }
4071
+ },
4072
+ dispose(_nodes) {
4073
+ }
4074
+ });
4075
+
4076
+ // src/lib-runtime/generated/modules/spring-reverb.wrapper.ts
4077
+ var SpringReverb = /* @__PURE__ */ defineModule(springReverbDescriptor, {
4078
+ worklets: {
4079
+ "spring-reverb": spring_reverb_default
4080
+ }
4081
+ });
4082
+
4083
+ // src/lib-runtime/generated/modules/stereo-spread.tsx
4084
+ var params39 = {
4085
+ spread: { unit: "factor", default: 0.7, min: 0, max: 1 },
4086
+ center: { unit: "Hz", default: 800, min: 200, max: 2e3, scale: "log" }
4087
+ };
4088
+ var stereoSpreadDescriptor = /* @__PURE__ */ defineDescriptor({
4089
+ type: "stereo-spread",
4090
+ params: params39,
4091
+ ports: {
4092
+ "in": { type: "input", signal: "audio", node: "input" },
4093
+ "out": { type: "output", signal: "audio", node: "output" }
4094
+ },
4095
+ createModule(id, position) {
4096
+ return { id, type: "stereo-spread", position, ...paramDefaults(params39) };
4097
+ },
4098
+ createNodes(ctx, module) {
4099
+ const input = ctx.createGain();
4100
+ const output = ctx.createGain();
4101
+ const lowpass = ctx.createBiquadFilter();
4102
+ const highpass = ctx.createBiquadFilter();
4103
+ lowpass.type = "lowpass";
4104
+ lowpass.frequency.value = module.center;
4105
+ highpass.type = "highpass";
4106
+ highpass.frequency.value = module.center;
4107
+ const panLeft = ctx.createStereoPanner();
4108
+ const panRight = ctx.createStereoPanner();
4109
+ const haasDelay = ctx.createDelay(0.02);
4110
+ panLeft.pan.value = -module.spread;
4111
+ panRight.pan.value = module.spread;
4112
+ haasDelay.delayTime.value = module.spread * 5e-3;
4113
+ const wetGain = ctx.createGain();
4114
+ wetGain.gain.value = module.width;
4115
+ input.connect(lowpass);
4116
+ lowpass.connect(output);
4117
+ input.connect(highpass);
4118
+ highpass.connect(panLeft);
4119
+ highpass.connect(haasDelay);
4120
+ haasDelay.connect(panRight);
4121
+ panLeft.connect(wetGain);
4122
+ panRight.connect(wetGain);
4123
+ wetGain.connect(output);
4124
+ return { input, output, lowpass, highpass, panLeft, panRight, haasDelay, wetGain };
4125
+ },
4126
+ updateParam(nodes, _m, param, value) {
4127
+ if (param === "spread") {
4128
+ nodes.panLeft.pan.value = -value;
4129
+ nodes.panRight.pan.value = value;
4130
+ nodes.haasDelay.delayTime.value = value * 5e-3;
4131
+ } else if (param === "width") {
4132
+ nodes.wetGain.gain.value = value;
4133
+ } else if (param === "center") {
4134
+ nodes.lowpass.frequency.value = value;
4135
+ nodes.highpass.frequency.value = value;
4136
+ }
4137
+ }
4138
+ });
4139
+
4140
+ // src/lib-runtime/generated/modules/stereo-spread.wrapper.ts
4141
+ var StereoSpread = /* @__PURE__ */ defineModule(stereoSpreadDescriptor);
4142
+
4143
+ // src/lib-runtime/generated/modules/sub-harm.tsx
4144
+ var params40 = {
4145
+ direct: { node: "dryGain", field: "gain", unit: "factor", default: 1, min: 0, max: 1 },
4146
+ octave1: { node: "subOctave", field: "oct1Level", unit: "factor", default: 0.5, min: 0, max: 1 },
4147
+ octave2: { node: "subOctave", field: "oct2Level", unit: "factor", default: 0.3, min: 0, max: 1 }
4148
+ };
4149
+ var subHarmDescriptor = /* @__PURE__ */ defineDescriptor({
4150
+ type: "sub-harm",
4151
+ params: params40,
4152
+ ports: {
4153
+ "in": { type: "input", signal: "audio", node: "input" },
4154
+ "out": { type: "output", signal: "audio", node: "output" }
4155
+ },
4156
+ createModule(id, position) {
4157
+ return { id, type: "sub-harm", position, ...paramDefaults(params40) };
4158
+ },
4159
+ createNodes(ctx, module) {
4160
+ const input = ctx.createGain();
4161
+ const output = ctx.createGain();
4162
+ const dryGain = ctx.createGain();
4163
+ const wetGain = ctx.createGain();
4164
+ const subOctave = createSubOctaveNode(ctx);
4165
+ subOctave.parameters.get("oct1Level").value = module.octave1;
4166
+ subOctave.parameters.get("oct2Level").value = module.octave2;
4167
+ const subFilter = ctx.createBiquadFilter();
4168
+ subFilter.type = "lowpass";
4169
+ subFilter.frequency.value = 400;
4170
+ subFilter.Q.value = 0.7;
4171
+ dryGain.gain.value = module.direct;
4172
+ wetGain.gain.value = 1;
4173
+ input.connect(dryGain);
4174
+ input.connect(subOctave);
4175
+ subOctave.connect(subFilter);
4176
+ subFilter.connect(wetGain);
4177
+ dryGain.connect(output);
4178
+ wetGain.connect(output);
4179
+ return { input, output, subOctave, dryGain, wetGain };
4180
+ },
4181
+ updateParam(nodes, _m, param, value) {
4182
+ if (param === "octave1") {
4183
+ nodes.subOctave.parameters.get("oct1Level").value = value;
4184
+ } else if (param === "octave2") {
4185
+ nodes.subOctave.parameters.get("oct2Level").value = value;
4186
+ } else if (param === "direct") {
4187
+ nodes.dryGain.gain.value = value;
4188
+ }
4189
+ }
4190
+ });
4191
+
4192
+ // src/lib-runtime/generated/modules/sub-harm.wrapper.ts
4193
+ var Subharmonic = /* @__PURE__ */ defineModule(subHarmDescriptor, {
4194
+ worklets: {
4195
+ "freq-divider": freq_divider_default
4196
+ }
4197
+ });
4198
+
4199
+ // src/lib-runtime/generated/modules/tape-delay.tsx
4200
+ var params41 = {
4201
+ time: { node: "worklet", field: "time", unit: "s", default: 0.375, min: 0.05, max: 1.5, scale: "log" },
4202
+ feedback: { node: "worklet", field: "feedback", unit: "factor", default: 0.5, min: 0, max: 0.95 },
4203
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 0.4, min: 0, max: 1 },
4204
+ flutter: { node: "worklet", field: "flutter", unit: "factor", default: 0.3, min: 0, max: 1 },
4205
+ saturation: { node: "worklet", field: "saturation", unit: "factor", default: 0.4, min: 0, max: 1 }
4206
+ };
4207
+ var tapeDelayDescriptor = /* @__PURE__ */ defineDescriptor({
4208
+ type: "tape-delay",
4209
+ params: params41,
4210
+ ports: {
4211
+ "in": { type: "input", signal: "audio", node: "input" },
4212
+ "out": { type: "output", signal: "audio", node: "output" }
4213
+ },
4214
+ createModule(id, position) {
4215
+ return { id, type: "tape-delay", position, ...paramDefaults(params41) };
4216
+ },
4217
+ createNodes(ctx, module) {
4218
+ const input = ctx.createGain();
4219
+ const output = ctx.createGain();
4220
+ const dryGain = ctx.createGain();
4221
+ const wetGain = ctx.createGain();
4222
+ const worklet = createTapeDelayNode(ctx, {
4223
+ time: module.time,
4224
+ feedback: module.feedback,
4225
+ saturation: module.saturation,
4226
+ flutter: module.flutter
4227
+ });
4228
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
4229
+ input.connect(dryGain);
4230
+ input.connect(worklet);
4231
+ worklet.connect(wetGain);
4232
+ dryGain.connect(output);
4233
+ wetGain.connect(output);
4234
+ return { input, output, worklet, dryGain, wetGain, _mix };
4235
+ },
4236
+ updateParam(nodes, _m, param, value) {
4237
+ const worklet = nodes.worklet;
4238
+ if (param === "time" || param === "feedback" || param === "saturation" || param === "flutter") {
4239
+ const p = worklet.parameters.get(param);
4240
+ if (p) p.value = value;
4241
+ } else if (param === "mix") {
4242
+ nodes._mix.offset.value = value;
4243
+ }
4244
+ }
4245
+ });
4246
+
4247
+ // src/lib-runtime/generated/modules/tape-delay.wrapper.ts
4248
+ var TapeDelay = /* @__PURE__ */ defineModule(tapeDelayDescriptor, {
4249
+ worklets: {
4250
+ "tape-delay": tape_delay_default
4251
+ }
4252
+ });
4253
+
4254
+ // src/lib-runtime/generated/modules/tape-stop.tsx
4255
+ var params42 = {
4256
+ time: { node: "_worklet", field: "time", default: 0.5, min: 0.01, max: 4, scale: "log", unit: "s" },
4257
+ mix: { node: "_worklet", field: "mix", unit: "factor", default: 1, min: 0, max: 1 }
4258
+ };
4259
+ var tapeStopDescriptor = /* @__PURE__ */ defineDescriptor({
4260
+ type: "tape-stop",
4261
+ params: params42,
4262
+ ports: {
4263
+ "in": { type: "input", signal: "audio", node: "_worklet" },
4264
+ "trigger": { type: "input", signal: "gate", node: "_trigIn" },
4265
+ "out": { type: "output", signal: "audio", node: "output" }
4266
+ },
4267
+ createModule(id, position) {
4268
+ return { id, type: "tape-stop", position, ...paramDefaults(params42) };
4269
+ },
4270
+ createNodes(ctx, module) {
4271
+ const _worklet = createTapeStopNode(ctx, module.time, module.mix);
4272
+ const _trigIn = ctx.createGain();
4273
+ _trigIn.gain.value = 1;
4274
+ _trigIn.connect(_worklet, 0, 1);
4275
+ const output = ctx.createGain();
4276
+ _worklet.connect(output);
4277
+ return { _worklet, _trigIn, output };
4278
+ },
4279
+ updateParam(nodes, _module, param, value, when) {
4280
+ if (param === "time") {
4281
+ nodes._worklet.parameters.get("time").value = value;
4282
+ } else if (param === "mix") {
4283
+ nodes._worklet.parameters.get("mix").value = value;
4284
+ } else if (param === "trigger") {
4285
+ pulse(nodes._trigIn, when);
4286
+ }
4287
+ },
4288
+ dispose(_nodes) {
4289
+ }
4290
+ });
4291
+
4292
+ // src/lib-runtime/generated/modules/tape-stop.wrapper.ts
4293
+ var TapeStop = /* @__PURE__ */ defineModule(tapeStopDescriptor, {
4294
+ worklets: {
4295
+ "tape-stop": tape_stop_default
4296
+ }
4297
+ });
4298
+
4299
+ // src/lib-runtime/generated/modules/tilt.tsx
4300
+ var params43 = {
4301
+ tilt: { unit: "dB", default: 0, min: -12, max: 12 },
4302
+ frequency: { unit: "Hz", default: 1e3, min: 200, max: 5e3 }
4303
+ };
4304
+ var tiltEqDescriptor = /* @__PURE__ */ defineDescriptor({
4305
+ type: "tilt-eq",
4306
+ params: params43,
4307
+ ports: {
4308
+ "in": { type: "input", signal: "audio", node: "input" },
4309
+ "out": { type: "output", signal: "audio", node: "output" }
4310
+ },
4311
+ createModule(id, position) {
4312
+ return { id, type: "tilt-eq", position, ...paramDefaults(params43) };
4313
+ },
4314
+ createNodes(ctx, module) {
4315
+ const input = ctx.createGain();
4316
+ const output = ctx.createGain();
4317
+ const lowShelf = ctx.createBiquadFilter();
4318
+ const highShelf = ctx.createBiquadFilter();
4319
+ lowShelf.type = "lowshelf";
4320
+ lowShelf.frequency.value = module.frequency;
4321
+ lowShelf.gain.value = -module.tilt;
4322
+ highShelf.type = "highshelf";
4323
+ highShelf.frequency.value = module.frequency;
4324
+ highShelf.gain.value = module.tilt;
4325
+ input.connect(lowShelf);
4326
+ lowShelf.connect(highShelf);
4327
+ highShelf.connect(output);
4328
+ return { input, output, lowShelf, highShelf };
4329
+ },
4330
+ updateParam(nodes, _m, param, value) {
4331
+ if (param === "tilt") {
4332
+ nodes.lowShelf.gain.value = -value;
4333
+ nodes.highShelf.gain.value = value;
4334
+ } else if (param === "frequency") {
4335
+ nodes.lowShelf.frequency.value = value;
4336
+ nodes.highShelf.frequency.value = value;
4337
+ }
4338
+ }
4339
+ });
4340
+
4341
+ // src/lib-runtime/generated/modules/tilt.wrapper.ts
4342
+ var Tilt = /* @__PURE__ */ defineModule(tiltEqDescriptor);
4343
+
4344
+ // src/lib-runtime/generated/modules/tremolo.tsx
4345
+ var shapes2 = ["sine", "triangle", "square"];
4346
+ var params44 = {
4347
+ rate: { node: "lfo", field: "frequency", unit: "Hz", default: 5, min: 0.5, max: 20 },
4348
+ depth: { unit: "factor", default: 0.5, min: 0, max: 1 },
4349
+ shape: { kind: "enum", options: shapes2, default: "sine" }
4350
+ };
4351
+ var tremoloDescriptor = /* @__PURE__ */ defineDescriptor({
4352
+ type: "tremolo",
4353
+ params: params44,
4354
+ ports: {
4355
+ "in": { type: "input", signal: "audio", node: "input" },
4356
+ "out": { type: "output", signal: "audio", node: "output" },
4357
+ "modRate": { type: "modulation", signal: "modulation", node: "lfo", field: "frequency" }
4358
+ },
4359
+ createModule(id, position) {
4360
+ return { id, type: "tremolo", position, ...paramDefaults(params44) };
4361
+ },
4362
+ createNodes(ctx, module) {
4363
+ const input = ctx.createGain();
4364
+ const output = ctx.createGain();
4365
+ const tremGain = ctx.createGain();
4366
+ const lfo = ctx.createOscillator();
4367
+ const lfoGain = ctx.createGain();
4368
+ const dcOffset = ctx.createConstantSource();
4369
+ tremGain.gain.value = 0;
4370
+ lfo.type = module.shape;
4371
+ lfo.frequency.value = module.rate;
4372
+ lfoGain.gain.value = module.depth * 0.5;
4373
+ dcOffset.offset.value = 1 - module.depth * 0.5;
4374
+ lfo.connect(lfoGain);
4375
+ lfoGain.connect(tremGain.gain);
4376
+ dcOffset.connect(tremGain.gain);
4377
+ lfo.start();
4378
+ dcOffset.start();
4379
+ input.connect(tremGain);
4380
+ tremGain.connect(output);
4381
+ return { input, output, lfo, lfoGain, dcOffset };
4382
+ },
4383
+ updateParam(nodes, _m, param, value) {
4384
+ switch (param) {
4385
+ case "rate":
4386
+ nodes.lfo.frequency.value = value;
4387
+ break;
4388
+ case "depth":
4389
+ nodes.lfoGain.gain.value = value * 0.5;
4390
+ nodes.dcOffset.offset.value = 1 - value * 0.5;
4391
+ break;
4392
+ case "shape":
4393
+ nodes.lfo.type = value;
4394
+ break;
4395
+ }
4396
+ },
4397
+ dispose(nodes) {
4398
+ nodes.lfo.stop();
4399
+ nodes.dcOffset.stop();
4400
+ }
4401
+ });
4402
+
4403
+ // src/lib-runtime/generated/modules/tremolo.wrapper.ts
4404
+ var Tremolo = /* @__PURE__ */ defineModule(tremoloDescriptor);
4405
+
4406
+ // src/lib-runtime/generated/modules/vca.tsx
4407
+ var params45 = {
4408
+ gain: { node: "gain", field: "gain", unit: "factor", default: 0.5, min: 0, max: 2 },
4409
+ cvAmount: { node: "gain", field: "cvAmount", unit: "factor", default: 1, min: -1, max: 1 }
4410
+ };
4411
+ var gainDescriptor = /* @__PURE__ */ defineDescriptor({
4412
+ type: "gain",
4413
+ params: params45,
4414
+ ports: {
4415
+ "in": { type: "input", signal: "audio", node: "gain" },
4416
+ "out": { type: "output", signal: "audio", node: "gain" },
4417
+ "modGain": { type: "modulation", signal: "modulation", node: "gain", field: "cv" }
4418
+ },
4419
+ createModule(id, position) {
4420
+ return { id, type: "gain", position, ...paramDefaults(params45) };
4421
+ },
4422
+ createNodes(ctx, module) {
4423
+ const gain = createVcaNode(ctx, module.gain, module.cvAmount ?? 1);
4424
+ return { gain };
4425
+ },
4426
+ updateParam(nodes, _module, param, value) {
4427
+ if (param === "gain" || param === "cvAmount") {
4428
+ const p = nodes.gain.parameters.get(param);
4429
+ if (p) p.value = value;
4430
+ }
4431
+ }
4432
+ });
4433
+
4434
+ // src/lib-runtime/generated/modules/vca.wrapper.ts
4435
+ var Vca = /* @__PURE__ */ defineModule(gainDescriptor, {
4436
+ worklets: {
4437
+ "vca": vca_default
4438
+ }
4439
+ });
4440
+
4441
+ // src/lib/dsp/gainComp.ts
4442
+ function bandpassMakeup(q, cap = 4) {
4443
+ if (!Number.isFinite(q) || q <= 1) return 1;
4444
+ return Math.min(Math.sqrt(q), cap);
4445
+ }
4446
+
4447
+ // src/lib-runtime/generated/modules/vcf.tsx
4448
+ var makeupFor = (type, q) => type === "bandpass" ? bandpassMakeup(q) : 1;
4449
+ var qIsDb = (type) => type === "lowpass" || type === "highpass";
4450
+ var params46 = {
4451
+ filterType: { kind: "enum", options: ["lowpass", "highpass", "bandpass", "notch"], default: "lowpass" },
4452
+ note: { node: "_cutoffVoct", field: "transpose", unit: "note", default: 83, min: 15, max: 135 },
4453
+ // Resonance is split across the map-res worklet AND a compensation gain,
4454
+ // so it cannot honestly declare a single AudioParam — see lib/dsp/mix.ts
4455
+ // convention: rebuild or leave set-only. It stays set-only here.
4456
+ resonance: { unit: "factor", default: 1, min: 0.1, max: 20, scale: "sqrt" }
4457
+ };
4458
+ var filterDescriptor = /* @__PURE__ */ defineDescriptor({
4459
+ type: "filter",
4460
+ params: params46,
4461
+ ports: {
4462
+ "in": { type: "input", signal: "audio", node: "filter" },
4463
+ "out": { type: "output", signal: "audio", node: "output" },
4464
+ "modFreq": { type: "modulation", signal: "modulation", node: "_cutoffVoct" },
4465
+ "modQ": { type: "modulation", signal: "modulation", node: "_resMap" }
4466
+ },
4467
+ createModule(id, position) {
4468
+ return { id, type: "filter", position, resUnit: "q", ...paramDefaults(params46) };
4469
+ },
4470
+ createNodes(ctx, module) {
4471
+ const filter = ctx.createBiquadFilter();
4472
+ filter.type = module.filterType;
4473
+ filter.frequency.value = 0;
4474
+ filter.Q.value = 0;
4475
+ const output = ctx.createGain();
4476
+ output.gain.value = makeupFor(module.filterType, module.resonance);
4477
+ filter.connect(output);
4478
+ const _cutoffVoct = createVoctFreqNode(ctx);
4479
+ _cutoffVoct.parameters.get("transpose").value = module.note;
4480
+ const _cutoffBase = ctx.createConstantSource();
4481
+ _cutoffBase.offset.value = 0;
4482
+ _cutoffBase.start();
4483
+ _cutoffBase.connect(_cutoffVoct);
4484
+ _cutoffVoct.connect(filter.frequency);
4485
+ const _resMap = createMapResNode(ctx, {
4486
+ base: module.resonance,
4487
+ dbOut: qIsDb(module.filterType)
4488
+ });
4489
+ _resMap.connect(filter.Q);
4490
+ return { filter, output, _cutoffVoct, _cutoffBase, _resMap };
4491
+ },
4492
+ dispose(nodes) {
4493
+ try {
4494
+ nodes._cutoffBase.stop();
4495
+ } catch (_) {
4496
+ }
4497
+ },
4498
+ updateParam(nodes, module, param, value) {
4499
+ switch (param) {
4500
+ case "filterType":
4501
+ nodes.filter.type = value;
4502
+ nodes._resMap.parameters.get("dbOut").value = qIsDb(value) ? 1 : 0;
4503
+ nodes.output.gain.value = makeupFor(value, module.resonance);
4504
+ break;
4505
+ case "note":
4506
+ nodes._cutoffVoct.parameters.get("transpose").value = value;
4507
+ break;
4508
+ case "resonance":
4509
+ nodes._resMap.parameters.get("base").value = value;
4510
+ nodes.output.gain.value = makeupFor(module.filterType, value);
4511
+ break;
4512
+ }
4513
+ }
4514
+ });
4515
+
4516
+ // src/lib-runtime/generated/modules/vcf.wrapper.ts
4517
+ var Vcf = /* @__PURE__ */ defineModule(filterDescriptor, {
4518
+ worklets: {
4519
+ "map-res": map_res_default,
4520
+ "map-voct-to-freq": map_voct_to_freq_default
4521
+ }
4522
+ });
4523
+
4524
+ // src/lib-runtime/generated/modules/vco.tsx
4525
+ var params47 = {
4526
+ skew: { node: "_ws", field: "skew", unit: "factor", default: 1, min: -1, max: 1 },
4527
+ shape: { node: "_ws", field: "shape", unit: "factor", default: 0, min: -1, max: 1 },
4528
+ offset: { node: "_voct", field: "transpose", default: 69, min: -48, max: 108, unit: "semitone" },
4529
+ detune: { node: "_detune", field: "offset", default: 0, min: -1, max: 1, unit: "semitone" },
4530
+ gain: { node: "gain", field: "gain", unit: "factor", default: 0.5, min: 0, max: 1 }
4531
+ };
4532
+ var patchCount4 = /* @__PURE__ */ new WeakMap();
4533
+ var oscillatorDescriptor = /* @__PURE__ */ defineDescriptor({
4534
+ type: "oscillator",
4535
+ ports: {
4536
+ "voct": { type: "input", node: "_voct", signal: "pitch" },
4537
+ "out": { type: "output", signal: "audio", node: "gain" },
4538
+ "modSkew": { type: "modulation", signal: "modulation", node: "_ws", field: "skew" },
4539
+ "modShape": { type: "modulation", signal: "modulation", node: "_ws", field: "shape" },
4540
+ "modDetune": { type: "modulation", signal: "modulation", node: "_detune", field: "offset" }
4541
+ },
4542
+ params: params47,
4543
+ // ─── Factory ─────────────────────────────────────────────────────
4544
+ createModule(id, position) {
4545
+ const { offset: _dsp, ...rest } = paramDefaults(params47);
4546
+ return { id, type: "oscillator", position, ...rest, note: 69, transpose: 0 };
4547
+ },
4548
+ // ─── Audio Engine ────────────────────────────────────────────────
4549
+ createNodes(ctx, module) {
4550
+ const _ws = createWaveshaperNode(ctx);
4551
+ const gain = ctx.createGain();
4552
+ const _detune = ctx.createConstantSource();
4553
+ _detune.offset.value = module.detune;
4554
+ _detune.start();
4555
+ const _voct = createVoctFreqNode(ctx);
4556
+ _detune.connect(_voct);
4557
+ _voct.connect(_ws);
4558
+ _voct.parameters.get("transpose").value = module.offset ?? module.note;
4559
+ _ws.parameters.get("skew").value = module.skew;
4560
+ _ws.parameters.get("shape").value = module.shape;
4561
+ _ws.parameters.get("normalize").value = 1;
4562
+ gain.gain.value = module.gain;
4563
+ _ws.connect(gain);
4564
+ return { gain, _ws, _voct, _detune };
4565
+ },
4566
+ updateParam(nodes, _module, param, value) {
4567
+ switch (param) {
4568
+ case "skew":
4569
+ nodes._ws.parameters.get("skew").value = value;
4570
+ break;
4571
+ case "shape":
4572
+ nodes._ws.parameters.get("shape").value = value;
4573
+ break;
4574
+ case "offset":
4575
+ nodes._voct.parameters.get("transpose").value = value;
4576
+ break;
4577
+ // App panel memories: only the one matching the patch state is live.
4578
+ case "note":
4579
+ if (!patchCount4.get(nodes)) nodes._voct.parameters.get("transpose").value = value;
4580
+ break;
4581
+ case "transpose":
4582
+ if (patchCount4.get(nodes)) nodes._voct.parameters.get("transpose").value = value;
4583
+ break;
4584
+ case "detune":
4585
+ nodes._detune.offset.value = value;
4586
+ break;
4587
+ case "gain":
4588
+ nodes.gain.gain.value = value;
4589
+ break;
4590
+ }
4591
+ },
4592
+ // App-only: the connection is a plain summing connect; these hooks only
4593
+ // swap which panel memory drives the offset.
4594
+ onConnect(nodes, module, portId, sourceNode) {
4595
+ if (portId !== "voct") return false;
4596
+ sourceNode.connect(nodes._voct);
4597
+ patchCount4.set(nodes, (patchCount4.get(nodes) ?? 0) + 1);
4598
+ nodes._voct.parameters.get("transpose").value = module.transpose ?? 0;
4599
+ return true;
4600
+ },
4601
+ onDisconnect(nodes, module, portId, sourceNode) {
4602
+ if (portId !== "voct") return false;
4603
+ try {
4604
+ sourceNode.disconnect(nodes._voct);
4605
+ } catch (e) {
4606
+ }
4607
+ const n = Math.max(0, (patchCount4.get(nodes) ?? 0) - 1);
4608
+ patchCount4.set(nodes, n);
4609
+ if (!n) nodes._voct.parameters.get("transpose").value = module.note;
4610
+ return true;
4611
+ },
4612
+ dispose(nodes) {
4613
+ nodes._detune.stop();
4614
+ }
4615
+ });
4616
+
4617
+ // src/lib-runtime/generated/modules/vco.wrapper.ts
4618
+ var Vco = /* @__PURE__ */ defineModule(oscillatorDescriptor, {
4619
+ worklets: {
4620
+ "map-voct-to-freq": map_voct_to_freq_default,
4621
+ "waveshaper": waveshaper_default
4622
+ }
4623
+ });
4624
+
4625
+ // src/lib-runtime/generated/modules/wah.tsx
4626
+ var params48 = {
4627
+ note: { node: "_cutoffVoct", field: "transpose", unit: "note", default: 79, min: 55, max: 95 },
4628
+ // Resonance drives both the filter Q and a separate makeup gain — a split, so it stays set-only.
4629
+ resonance: { unit: "factor", default: 8, min: 1, max: 20, scale: "log" },
4630
+ rate: { node: "lfo", field: "frequency", unit: "Hz", default: 1, min: 0.1, max: 10, scale: "log" },
4631
+ // Depth is stored 0-1 but scaled by 36 semitones onto lfoGain.gain — a conversion, so set-only.
4632
+ depth: { unit: "factor", default: 0.6, min: 0, max: 1 }
4633
+ };
4634
+ var wahDescriptor = /* @__PURE__ */ defineDescriptor({
4635
+ type: "wah",
4636
+ params: params48,
4637
+ ports: {
4638
+ "in": { type: "input", signal: "audio", node: "input" },
4639
+ "out": { type: "output", signal: "audio", node: "output" },
4640
+ "modFreq": { type: "modulation", signal: "modulation", node: "_cutoffVoct" }
4641
+ },
4642
+ createModule(id, position) {
4643
+ return { id, type: "wah", position, ...paramDefaults(params48) };
4644
+ },
4645
+ createNodes(ctx, module) {
4646
+ const input = ctx.createGain();
4647
+ const output = ctx.createGain();
4648
+ const filter = ctx.createBiquadFilter();
4649
+ const lfo = ctx.createOscillator();
4650
+ const lfoGain = ctx.createGain();
4651
+ filter.type = "bandpass";
4652
+ filter.frequency.value = 0;
4653
+ filter.Q.value = module.resonance;
4654
+ const makeup = ctx.createGain();
4655
+ makeup.gain.value = bandpassMakeup(module.resonance);
4656
+ const _cutoffVoct = createVoctFreqNode(ctx);
4657
+ _cutoffVoct.parameters.get("transpose").value = module.note;
4658
+ const _cutoffBase = ctx.createConstantSource();
4659
+ _cutoffBase.offset.value = 0;
4660
+ _cutoffBase.start();
4661
+ _cutoffBase.connect(_cutoffVoct);
4662
+ _cutoffVoct.connect(filter.frequency);
4663
+ lfo.type = "sine";
4664
+ lfo.frequency.value = module.rate;
4665
+ lfoGain.gain.value = module.depth * 36;
4666
+ lfo.connect(lfoGain);
4667
+ lfoGain.connect(_cutoffVoct);
4668
+ lfo.start();
4669
+ input.connect(filter);
4670
+ filter.connect(makeup);
4671
+ makeup.connect(output);
4672
+ return { input, output, filter, makeup, lfo, lfoGain, _cutoffVoct, _cutoffBase };
4673
+ },
4674
+ updateParam(nodes, _module, param, value) {
4675
+ if (param === "note") {
4676
+ nodes._cutoffVoct.parameters.get("transpose").value = value;
4677
+ } else if (param === "resonance") {
4678
+ nodes.filter.Q.value = value;
4679
+ nodes.makeup.gain.value = bandpassMakeup(value);
4680
+ } else if (param === "rate") {
4681
+ nodes.lfo.frequency.value = value;
4682
+ } else if (param === "depth") {
4683
+ nodes.lfoGain.gain.value = value * 36;
4684
+ }
4685
+ },
4686
+ dispose(nodes) {
4687
+ nodes.lfo.stop();
4688
+ nodes._cutoffBase.stop();
4689
+ }
4690
+ });
4691
+
4692
+ // src/lib-runtime/generated/modules/wah.wrapper.ts
4693
+ var Wah = /* @__PURE__ */ defineModule(wahDescriptor, {
4694
+ worklets: {
4695
+ "map-voct-to-freq": map_voct_to_freq_default
4696
+ }
4697
+ });
4698
+
4699
+ // src/lib-runtime/generated/modules/wavefolder.tsx
4700
+ var params49 = {
4701
+ folds: { unit: "factor", default: 2, min: 1, max: 10 },
4702
+ symmetry: { unit: "factor", default: 0, min: -1, max: 1 },
4703
+ mix: { node: "_mix", field: "offset", unit: "factor", default: 1, min: 0, max: 1 }
4704
+ };
4705
+ function makeWavefoldCurve(folds, symmetry) {
4706
+ const samples = 65536;
4707
+ const curve = new Float32Array(samples);
4708
+ const bias = symmetry * 0.2;
4709
+ const w = Math.PI / 2 * folds;
4710
+ const dc = Math.sin(w * bias);
4711
+ let peak = 0;
4712
+ for (let i = 0; i < samples; i++) {
4713
+ const x = i * 2 / samples - 1;
4714
+ const y = Math.sin(w * (x + bias)) - dc;
4715
+ curve[i] = y;
4716
+ const a = Math.abs(y);
4717
+ if (a > peak) peak = a;
4718
+ }
4719
+ if (peak > 1) {
4720
+ const inv = 1 / peak;
4721
+ for (let i = 0; i < samples; i++) curve[i] *= inv;
4722
+ }
4723
+ return curve;
4724
+ }
4725
+ var wavefolderDescriptor = /* @__PURE__ */ defineDescriptor({
4726
+ type: "wavefolder",
4727
+ params: params49,
4728
+ ports: {
4729
+ "in": { type: "input", signal: "audio", node: "input" },
4730
+ "out": { type: "output", signal: "audio", node: "output" }
4731
+ },
4732
+ createModule(id, position) {
4733
+ return { id, type: "wavefolder", position, ...paramDefaults(params49) };
4734
+ },
4735
+ createNodes(ctx, module) {
4736
+ const input = ctx.createGain();
4737
+ const output = ctx.createGain();
4738
+ const dryGain = ctx.createGain();
4739
+ const wetGain = ctx.createGain();
4740
+ const waveshaper = ctx.createWaveShaper();
4741
+ waveshaper.curve = makeWavefoldCurve(module.folds, module.symmetry);
4742
+ waveshaper.oversample = "4x";
4743
+ const _mix = createMix(ctx, dryGain, wetGain, module.mix, "linear");
4744
+ input.connect(dryGain);
4745
+ input.connect(waveshaper);
4746
+ waveshaper.connect(wetGain);
4747
+ dryGain.connect(output);
4748
+ wetGain.connect(output);
4749
+ return { input, output, waveshaper, dryGain, wetGain, _mix };
4750
+ },
4751
+ updateParam(nodes, module, param, value) {
4752
+ if (param === "folds" || param === "symmetry") {
4753
+ nodes.waveshaper.curve = makeWavefoldCurve(module.folds, module.symmetry);
4754
+ } else if (param === "mix") {
4755
+ nodes._mix.offset.value = value;
4756
+ }
4757
+ }
4758
+ });
4759
+
4760
+ // src/lib-runtime/generated/modules/wavefolder.wrapper.ts
4761
+ var Wavefolder = /* @__PURE__ */ defineModule(wavefolderDescriptor);
4762
+ export {
4763
+ AcidFilter,
4764
+ AutoPan,
4765
+ Bitcrusher,
4766
+ Chorus,
4767
+ CombFilter,
4768
+ Compressor,
4769
+ Crossfade,
4770
+ DEFAULT_SAMPLE_BASE_URL,
4771
+ Delay,
4772
+ Dimension,
4773
+ Distortion,
4774
+ Ducker,
4775
+ Envelope,
4776
+ EnvelopeFollower,
4777
+ Exciter,
4778
+ Flanger,
4779
+ Formant,
4780
+ FrequencyShifter,
4781
+ Gate,
4782
+ Glide,
4783
+ HaasWidener,
4784
+ HarmonicTremolo,
4785
+ Hat,
4786
+ Kick,
4787
+ LadderFilter,
4788
+ Lfo,
4789
+ LoFi,
4790
+ MidSide,
4791
+ Noise,
4792
+ NoteCv,
4793
+ Octaver,
4794
+ Phaser,
4795
+ PingPong,
4796
+ Resonator,
4797
+ Reverb,
4798
+ RingModulator,
4799
+ Rotary,
4800
+ SampleSets,
4801
+ SampleVoice,
4802
+ Saturator,
4803
+ Shimmer,
4804
+ Snare,
4805
+ SoftLimiter,
4806
+ SpringReverb,
4807
+ StereoSpread,
4808
+ Subharmonic,
4809
+ TapeDelay,
4810
+ TapeStop,
4811
+ ThreeBandEq,
4812
+ Tilt,
4813
+ Tremolo,
4814
+ VOICE_KEYS,
4815
+ Vca,
4816
+ Vcf,
4817
+ Vco,
4818
+ Wah,
4819
+ Wavefolder,
4820
+ asPitch,
4821
+ beats,
4822
+ cents,
4823
+ centsToSemitones,
4824
+ createTransport,
4825
+ db,
4826
+ dbToGain,
4827
+ gainToDb,
4828
+ hz,
4829
+ hzToNote,
4830
+ interval,
4831
+ isRest,
4832
+ loadSampleSet,
4833
+ ms,
4834
+ msToSeconds,
4835
+ note,
4836
+ noteToHz,
4837
+ notes,
4838
+ now,
4839
+ preload,
4840
+ pulse,
4841
+ sec,
4842
+ semitones,
4843
+ steps,
4844
+ transpose
4845
+ };
4846
+ //# sourceMappingURL=index.js.map