supersonic-scsynth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2411 @@
1
+ // js/lib/scsynth_osc.js
2
+ var ScsynthOSC = class {
3
+ constructor() {
4
+ this.workers = {
5
+ oscOut: null,
6
+ oscIn: null,
7
+ debug: null
8
+ };
9
+ this.callbacks = {
10
+ onOSCMessage: null,
11
+ onDebugMessage: null,
12
+ onError: null,
13
+ onInitialized: null
14
+ };
15
+ this.initialized = false;
16
+ this.sharedBuffer = null;
17
+ this.ringBufferBase = null;
18
+ this.bufferConstants = null;
19
+ }
20
+ /**
21
+ * Initialize all workers with SharedArrayBuffer
22
+ */
23
+ async init(sharedBuffer, ringBufferBase, bufferConstants) {
24
+ if (this.initialized) {
25
+ console.warn("[ScsynthOSC] Already initialized");
26
+ return;
27
+ }
28
+ this.sharedBuffer = sharedBuffer;
29
+ this.ringBufferBase = ringBufferBase;
30
+ this.bufferConstants = bufferConstants;
31
+ try {
32
+ this.workers.oscOut = new Worker("./dist/workers/osc_out_worker.js");
33
+ this.workers.oscIn = new Worker("./dist/workers/osc_in_worker.js");
34
+ this.workers.debug = new Worker("./dist/workers/debug_worker.js");
35
+ this.setupWorkerHandlers();
36
+ const initPromises = [
37
+ this.initWorker(this.workers.oscOut, "OSC OUT"),
38
+ this.initWorker(this.workers.oscIn, "OSC IN"),
39
+ this.initWorker(this.workers.debug, "DEBUG")
40
+ ];
41
+ await Promise.all(initPromises);
42
+ this.workers.oscIn.postMessage({ type: "start" });
43
+ this.workers.debug.postMessage({ type: "start" });
44
+ this.initialized = true;
45
+ if (this.callbacks.onInitialized) {
46
+ this.callbacks.onInitialized();
47
+ }
48
+ } catch (error) {
49
+ console.error("[ScsynthOSC] Initialization failed:", error);
50
+ if (this.callbacks.onError) {
51
+ this.callbacks.onError(error);
52
+ }
53
+ throw error;
54
+ }
55
+ }
56
+ /**
57
+ * Initialize a single worker
58
+ */
59
+ initWorker(worker, name) {
60
+ return new Promise((resolve, reject) => {
61
+ const timeout = setTimeout(() => {
62
+ reject(new Error(`${name} worker initialization timeout`));
63
+ }, 5e3);
64
+ const handler = (event) => {
65
+ if (event.data.type === "initialized") {
66
+ clearTimeout(timeout);
67
+ worker.removeEventListener("message", handler);
68
+ resolve();
69
+ }
70
+ };
71
+ worker.addEventListener("message", handler);
72
+ worker.postMessage({
73
+ type: "init",
74
+ sharedBuffer: this.sharedBuffer,
75
+ ringBufferBase: this.ringBufferBase,
76
+ bufferConstants: this.bufferConstants
77
+ });
78
+ });
79
+ }
80
+ /**
81
+ * Set up message handlers for all workers
82
+ */
83
+ setupWorkerHandlers() {
84
+ this.workers.oscIn.onmessage = (event) => {
85
+ const data = event.data;
86
+ switch (data.type) {
87
+ case "messages":
88
+ if (this.callbacks.onOSCMessage) {
89
+ data.messages.forEach((msg) => {
90
+ this.callbacks.onOSCMessage(msg);
91
+ });
92
+ }
93
+ break;
94
+ case "error":
95
+ console.error("[ScsynthOSC] OSC IN error:", data.error);
96
+ if (this.callbacks.onError) {
97
+ this.callbacks.onError(data.error, "oscIn");
98
+ }
99
+ break;
100
+ }
101
+ };
102
+ this.workers.debug.onmessage = (event) => {
103
+ const data = event.data;
104
+ switch (data.type) {
105
+ case "debug":
106
+ if (this.callbacks.onDebugMessage) {
107
+ data.messages.forEach((msg) => {
108
+ this.callbacks.onDebugMessage(msg);
109
+ });
110
+ }
111
+ break;
112
+ case "error":
113
+ console.error("[ScsynthOSC] DEBUG error:", data.error);
114
+ if (this.callbacks.onError) {
115
+ this.callbacks.onError(data.error, "debug");
116
+ }
117
+ break;
118
+ }
119
+ };
120
+ this.workers.oscOut.onmessage = (event) => {
121
+ const data = event.data;
122
+ switch (data.type) {
123
+ case "error":
124
+ console.error("[ScsynthOSC] OSC OUT error:", data.error);
125
+ if (this.callbacks.onError) {
126
+ this.callbacks.onError(data.error, "oscOut");
127
+ }
128
+ break;
129
+ }
130
+ };
131
+ }
132
+ /**
133
+ * Send OSC data (message or bundle)
134
+ * - OSC messages are sent immediately
135
+ * - OSC bundles are scheduled based on waitTimeMs (calculated by SuperSonic)
136
+ *
137
+ * @param {Uint8Array} oscData - Binary OSC data (message or bundle)
138
+ * @param {Object} options - Optional metadata (editorId, runTag, waitTimeMs)
139
+ */
140
+ send(oscData, options = {}) {
141
+ if (!this.initialized) {
142
+ console.error("[ScsynthOSC] Not initialized");
143
+ return;
144
+ }
145
+ const { editorId = 0, runTag = "", waitTimeMs = null } = options;
146
+ this.workers.oscOut.postMessage({
147
+ type: "send",
148
+ oscData,
149
+ editorId,
150
+ runTag,
151
+ waitTimeMs
152
+ });
153
+ }
154
+ /**
155
+ * Send OSC data immediately, ignoring any bundle timestamps
156
+ * - Extracts all messages from bundles
157
+ * - Sends all messages immediately to scsynth
158
+ * - For applications that don't expect server-side scheduling
159
+ *
160
+ * @param {Uint8Array} oscData - Binary OSC data (message or bundle)
161
+ */
162
+ sendImmediate(oscData) {
163
+ if (!this.initialized) {
164
+ console.error("[ScsynthOSC] Not initialized");
165
+ return;
166
+ }
167
+ this.workers.oscOut.postMessage({
168
+ type: "sendImmediate",
169
+ oscData
170
+ });
171
+ }
172
+ /**
173
+ * Cancel scheduled OSC bundles by editor and tag
174
+ */
175
+ cancelEditorTag(editorId, runTag) {
176
+ if (!this.initialized) return;
177
+ this.workers.oscOut.postMessage({
178
+ type: "cancelEditorTag",
179
+ editorId,
180
+ runTag
181
+ });
182
+ }
183
+ /**
184
+ * Cancel all scheduled OSC bundles from an editor
185
+ */
186
+ cancelEditor(editorId) {
187
+ if (!this.initialized) return;
188
+ this.workers.oscOut.postMessage({
189
+ type: "cancelEditor",
190
+ editorId
191
+ });
192
+ }
193
+ /**
194
+ * Cancel all scheduled OSC bundles
195
+ */
196
+ cancelAll() {
197
+ if (!this.initialized) return;
198
+ this.workers.oscOut.postMessage({
199
+ type: "cancelAll"
200
+ });
201
+ }
202
+ /**
203
+ * Clear debug buffer
204
+ */
205
+ clearDebug() {
206
+ if (!this.initialized) return;
207
+ this.workers.debug.postMessage({
208
+ type: "clear"
209
+ });
210
+ }
211
+ /**
212
+ * Get statistics from all workers
213
+ */
214
+ async getStats() {
215
+ if (!this.initialized) {
216
+ return null;
217
+ }
218
+ const statsPromises = [
219
+ this.getWorkerStats(this.workers.oscOut, "oscOut"),
220
+ this.getWorkerStats(this.workers.oscIn, "oscIn"),
221
+ this.getWorkerStats(this.workers.debug, "debug")
222
+ ];
223
+ const results = await Promise.all(statsPromises);
224
+ return {
225
+ oscOut: results[0],
226
+ oscIn: results[1],
227
+ debug: results[2]
228
+ };
229
+ }
230
+ /**
231
+ * Get stats from a single worker
232
+ */
233
+ getWorkerStats(worker, name) {
234
+ return new Promise((resolve) => {
235
+ const timeout = setTimeout(() => {
236
+ resolve({ error: "Timeout getting stats" });
237
+ }, 1e3);
238
+ const handler = (event) => {
239
+ if (event.data.type === "stats") {
240
+ clearTimeout(timeout);
241
+ worker.removeEventListener("message", handler);
242
+ resolve(event.data.stats);
243
+ }
244
+ };
245
+ worker.addEventListener("message", handler);
246
+ worker.postMessage({ type: "getStats" });
247
+ });
248
+ }
249
+ /**
250
+ * Set callback for OSC messages received from scsynth
251
+ */
252
+ onOSCMessage(callback) {
253
+ this.callbacks.onOSCMessage = callback;
254
+ }
255
+ /**
256
+ * Set callback for debug messages
257
+ */
258
+ onDebugMessage(callback) {
259
+ this.callbacks.onDebugMessage = callback;
260
+ }
261
+ /**
262
+ * Set callback for errors
263
+ */
264
+ onError(callback) {
265
+ this.callbacks.onError = callback;
266
+ }
267
+ /**
268
+ * Set callback for initialization complete
269
+ */
270
+ onInitialized(callback) {
271
+ this.callbacks.onInitialized = callback;
272
+ }
273
+ /**
274
+ * Terminate all workers and cleanup
275
+ */
276
+ terminate() {
277
+ if (this.workers.oscOut) {
278
+ this.workers.oscOut.postMessage({ type: "stop" });
279
+ this.workers.oscOut.terminate();
280
+ }
281
+ if (this.workers.oscIn) {
282
+ this.workers.oscIn.postMessage({ type: "stop" });
283
+ this.workers.oscIn.terminate();
284
+ }
285
+ if (this.workers.debug) {
286
+ this.workers.debug.postMessage({ type: "stop" });
287
+ this.workers.debug.terminate();
288
+ }
289
+ this.workers = {
290
+ oscOut: null,
291
+ oscIn: null,
292
+ debug: null
293
+ };
294
+ this.initialized = false;
295
+ console.log("[ScsynthOSC] All workers terminated");
296
+ }
297
+ };
298
+
299
+ // js/vendor/osc.js/osc.js
300
+ var osc = {};
301
+ var osc = osc || {};
302
+ (function() {
303
+ "use strict";
304
+ osc.SECS_70YRS = 2208988800;
305
+ osc.TWO_32 = 4294967296;
306
+ osc.defaults = {
307
+ metadata: false,
308
+ unpackSingleArgs: true
309
+ };
310
+ osc.isCommonJS = typeof module !== "undefined" && module.exports ? true : false;
311
+ osc.isNode = osc.isCommonJS && typeof window === "undefined";
312
+ osc.isElectron = typeof process !== "undefined" && process.versions && process.versions.electron ? true : false;
313
+ osc.isBufferEnv = osc.isNode || osc.isElectron;
314
+ osc.isArray = function(obj) {
315
+ return obj && Object.prototype.toString.call(obj) === "[object Array]";
316
+ };
317
+ osc.isTypedArrayView = function(obj) {
318
+ return obj.buffer && obj.buffer instanceof ArrayBuffer;
319
+ };
320
+ osc.isBuffer = function(obj) {
321
+ return osc.isBufferEnv && obj instanceof Buffer;
322
+ };
323
+ osc.Long = typeof Long !== "undefined" ? Long : void 0;
324
+ osc.TextDecoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-8") : typeof util !== "undefined" && typeof (util.TextDecoder !== "undefined") ? new util.TextDecoder("utf-8") : void 0;
325
+ osc.TextEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder("utf-8") : typeof util !== "undefined" && typeof (util.TextEncoder !== "undefined") ? new util.TextEncoder("utf-8") : void 0;
326
+ osc.dataView = function(obj, offset, length) {
327
+ if (obj.buffer) {
328
+ return new DataView(obj.buffer, offset, length);
329
+ }
330
+ if (obj instanceof ArrayBuffer) {
331
+ return new DataView(obj, offset, length);
332
+ }
333
+ return new DataView(new Uint8Array(obj), offset, length);
334
+ };
335
+ osc.byteArray = function(obj) {
336
+ if (obj instanceof Uint8Array) {
337
+ return obj;
338
+ }
339
+ var buf = obj.buffer ? obj.buffer : obj;
340
+ if (!(buf instanceof ArrayBuffer) && (typeof buf.length === "undefined" || typeof buf === "string")) {
341
+ throw new Error("Can't wrap a non-array-like object as Uint8Array. Object was: " + JSON.stringify(obj, null, 2));
342
+ }
343
+ return new Uint8Array(buf);
344
+ };
345
+ osc.nativeBuffer = function(obj) {
346
+ if (osc.isBufferEnv) {
347
+ return osc.isBuffer(obj) ? obj : Buffer.from(obj.buffer ? obj : new Uint8Array(obj));
348
+ }
349
+ return osc.isTypedArrayView(obj) ? obj : new Uint8Array(obj);
350
+ };
351
+ osc.copyByteArray = function(source, target, offset) {
352
+ if (osc.isTypedArrayView(source) && osc.isTypedArrayView(target)) {
353
+ target.set(source, offset);
354
+ } else {
355
+ var start = offset === void 0 ? 0 : offset, len = Math.min(target.length - offset, source.length);
356
+ for (var i = 0, j = start; i < len; i++, j++) {
357
+ target[j] = source[i];
358
+ }
359
+ }
360
+ return target;
361
+ };
362
+ osc.readString = function(dv, offsetState) {
363
+ var charCodes = [], idx = offsetState.idx;
364
+ for (; idx < dv.byteLength; idx++) {
365
+ var charCode = dv.getUint8(idx);
366
+ if (charCode !== 0) {
367
+ charCodes.push(charCode);
368
+ } else {
369
+ idx++;
370
+ break;
371
+ }
372
+ }
373
+ idx = idx + 3 & ~3;
374
+ offsetState.idx = idx;
375
+ var decoder = osc.isBufferEnv ? osc.readString.withBuffer : osc.TextDecoder ? osc.readString.withTextDecoder : osc.readString.raw;
376
+ return decoder(charCodes);
377
+ };
378
+ osc.readString.raw = function(charCodes) {
379
+ var str = "";
380
+ var sliceSize = 1e4;
381
+ for (var i = 0; i < charCodes.length; i += sliceSize) {
382
+ str += String.fromCharCode.apply(null, charCodes.slice(i, i + sliceSize));
383
+ }
384
+ return str;
385
+ };
386
+ osc.readString.withTextDecoder = function(charCodes) {
387
+ var data = new Int8Array(charCodes);
388
+ return osc.TextDecoder.decode(data);
389
+ };
390
+ osc.readString.withBuffer = function(charCodes) {
391
+ return Buffer.from(charCodes).toString("utf-8");
392
+ };
393
+ osc.writeString = function(str) {
394
+ var encoder = osc.isBufferEnv ? osc.writeString.withBuffer : osc.TextEncoder ? osc.writeString.withTextEncoder : null, terminated = str + "\0", encodedStr;
395
+ if (encoder) {
396
+ encodedStr = encoder(terminated);
397
+ }
398
+ var len = encoder ? encodedStr.length : terminated.length, paddedLen = len + 3 & ~3, arr = new Uint8Array(paddedLen);
399
+ for (var i = 0; i < len - 1; i++) {
400
+ var charCode = encoder ? encodedStr[i] : terminated.charCodeAt(i);
401
+ arr[i] = charCode;
402
+ }
403
+ return arr;
404
+ };
405
+ osc.writeString.withTextEncoder = function(str) {
406
+ return osc.TextEncoder.encode(str);
407
+ };
408
+ osc.writeString.withBuffer = function(str) {
409
+ return Buffer.from(str);
410
+ };
411
+ osc.readPrimitive = function(dv, readerName, numBytes, offsetState) {
412
+ var val = dv[readerName](offsetState.idx, false);
413
+ offsetState.idx += numBytes;
414
+ return val;
415
+ };
416
+ osc.writePrimitive = function(val, dv, writerName, numBytes, offset) {
417
+ offset = offset === void 0 ? 0 : offset;
418
+ var arr;
419
+ if (!dv) {
420
+ arr = new Uint8Array(numBytes);
421
+ dv = new DataView(arr.buffer);
422
+ } else {
423
+ arr = new Uint8Array(dv.buffer);
424
+ }
425
+ dv[writerName](offset, val, false);
426
+ return arr;
427
+ };
428
+ osc.readInt32 = function(dv, offsetState) {
429
+ return osc.readPrimitive(dv, "getInt32", 4, offsetState);
430
+ };
431
+ osc.writeInt32 = function(val, dv, offset) {
432
+ return osc.writePrimitive(val, dv, "setInt32", 4, offset);
433
+ };
434
+ osc.readInt64 = function(dv, offsetState) {
435
+ var high = osc.readPrimitive(dv, "getInt32", 4, offsetState), low = osc.readPrimitive(dv, "getInt32", 4, offsetState);
436
+ if (osc.Long) {
437
+ return new osc.Long(low, high);
438
+ } else {
439
+ return {
440
+ high,
441
+ low,
442
+ unsigned: false
443
+ };
444
+ }
445
+ };
446
+ osc.writeInt64 = function(val, dv, offset) {
447
+ var arr = new Uint8Array(8);
448
+ arr.set(osc.writePrimitive(val.high, dv, "setInt32", 4, offset), 0);
449
+ arr.set(osc.writePrimitive(val.low, dv, "setInt32", 4, offset + 4), 4);
450
+ return arr;
451
+ };
452
+ osc.readFloat32 = function(dv, offsetState) {
453
+ return osc.readPrimitive(dv, "getFloat32", 4, offsetState);
454
+ };
455
+ osc.writeFloat32 = function(val, dv, offset) {
456
+ return osc.writePrimitive(val, dv, "setFloat32", 4, offset);
457
+ };
458
+ osc.readFloat64 = function(dv, offsetState) {
459
+ return osc.readPrimitive(dv, "getFloat64", 8, offsetState);
460
+ };
461
+ osc.writeFloat64 = function(val, dv, offset) {
462
+ return osc.writePrimitive(val, dv, "setFloat64", 8, offset);
463
+ };
464
+ osc.readChar32 = function(dv, offsetState) {
465
+ var charCode = osc.readPrimitive(dv, "getUint32", 4, offsetState);
466
+ return String.fromCharCode(charCode);
467
+ };
468
+ osc.writeChar32 = function(str, dv, offset) {
469
+ var charCode = str.charCodeAt(0);
470
+ if (charCode === void 0 || charCode < -1) {
471
+ return void 0;
472
+ }
473
+ return osc.writePrimitive(charCode, dv, "setUint32", 4, offset);
474
+ };
475
+ osc.readBlob = function(dv, offsetState) {
476
+ var len = osc.readInt32(dv, offsetState), paddedLen = len + 3 & ~3, blob = new Uint8Array(dv.buffer, offsetState.idx, len);
477
+ offsetState.idx += paddedLen;
478
+ return blob;
479
+ };
480
+ osc.writeBlob = function(data) {
481
+ data = osc.byteArray(data);
482
+ var len = data.byteLength, paddedLen = len + 3 & ~3, offset = 4, blobLen = paddedLen + offset, arr = new Uint8Array(blobLen), dv = new DataView(arr.buffer);
483
+ osc.writeInt32(len, dv);
484
+ arr.set(data, offset);
485
+ return arr;
486
+ };
487
+ osc.readMIDIBytes = function(dv, offsetState) {
488
+ var midi = new Uint8Array(dv.buffer, offsetState.idx, 4);
489
+ offsetState.idx += 4;
490
+ return midi;
491
+ };
492
+ osc.writeMIDIBytes = function(bytes) {
493
+ bytes = osc.byteArray(bytes);
494
+ var arr = new Uint8Array(4);
495
+ arr.set(bytes);
496
+ return arr;
497
+ };
498
+ osc.readColor = function(dv, offsetState) {
499
+ var bytes = new Uint8Array(dv.buffer, offsetState.idx, 4), alpha = bytes[3] / 255;
500
+ offsetState.idx += 4;
501
+ return {
502
+ r: bytes[0],
503
+ g: bytes[1],
504
+ b: bytes[2],
505
+ a: alpha
506
+ };
507
+ };
508
+ osc.writeColor = function(color) {
509
+ var alpha = Math.round(color.a * 255), arr = new Uint8Array([color.r, color.g, color.b, alpha]);
510
+ return arr;
511
+ };
512
+ osc.readTrue = function() {
513
+ return true;
514
+ };
515
+ osc.readFalse = function() {
516
+ return false;
517
+ };
518
+ osc.readNull = function() {
519
+ return null;
520
+ };
521
+ osc.readImpulse = function() {
522
+ return 1;
523
+ };
524
+ osc.readTimeTag = function(dv, offsetState) {
525
+ var secs1900 = osc.readPrimitive(dv, "getUint32", 4, offsetState), frac = osc.readPrimitive(dv, "getUint32", 4, offsetState), native = secs1900 === 0 && frac === 1 ? Date.now() : osc.ntpToJSTime(secs1900, frac);
526
+ return {
527
+ raw: [secs1900, frac],
528
+ native
529
+ };
530
+ };
531
+ osc.writeTimeTag = function(timeTag) {
532
+ var raw = timeTag.raw ? timeTag.raw : osc.jsToNTPTime(timeTag.native), arr = new Uint8Array(8), dv = new DataView(arr.buffer);
533
+ osc.writeInt32(raw[0], dv, 0);
534
+ osc.writeInt32(raw[1], dv, 4);
535
+ return arr;
536
+ };
537
+ osc.timeTag = function(secs, now) {
538
+ secs = secs || 0;
539
+ now = now || Date.now();
540
+ var nowSecs = now / 1e3, nowWhole = Math.floor(nowSecs), nowFracs = nowSecs - nowWhole, secsWhole = Math.floor(secs), secsFracs = secs - secsWhole, fracs = nowFracs + secsFracs;
541
+ if (fracs > 1) {
542
+ var fracsWhole = Math.floor(fracs), fracsFracs = fracs - fracsWhole;
543
+ secsWhole += fracsWhole;
544
+ fracs = fracsFracs;
545
+ }
546
+ var ntpSecs = nowWhole + secsWhole + osc.SECS_70YRS, ntpFracs = Math.round(osc.TWO_32 * fracs);
547
+ return {
548
+ raw: [ntpSecs, ntpFracs]
549
+ };
550
+ };
551
+ osc.ntpToJSTime = function(secs1900, frac) {
552
+ var secs1970 = secs1900 - osc.SECS_70YRS, decimals = frac / osc.TWO_32, msTime = (secs1970 + decimals) * 1e3;
553
+ return msTime;
554
+ };
555
+ osc.jsToNTPTime = function(jsTime) {
556
+ var secs = jsTime / 1e3, secsWhole = Math.floor(secs), secsFrac = secs - secsWhole, ntpSecs = secsWhole + osc.SECS_70YRS, ntpFracs = Math.round(osc.TWO_32 * secsFrac);
557
+ return [ntpSecs, ntpFracs];
558
+ };
559
+ osc.readArguments = function(dv, options, offsetState) {
560
+ var typeTagString = osc.readString(dv, offsetState);
561
+ if (typeTagString.indexOf(",") !== 0) {
562
+ throw new Error("A malformed type tag string was found while reading the arguments of an OSC message. String was: " + typeTagString, " at offset: " + offsetState.idx);
563
+ }
564
+ var argTypes = typeTagString.substring(1).split(""), args = [];
565
+ osc.readArgumentsIntoArray(args, argTypes, typeTagString, dv, options, offsetState);
566
+ return args;
567
+ };
568
+ osc.readArgument = function(argType, typeTagString, dv, options, offsetState) {
569
+ var typeSpec = osc.argumentTypes[argType];
570
+ if (!typeSpec) {
571
+ throw new Error("'" + argType + "' is not a valid OSC type tag. Type tag string was: " + typeTagString);
572
+ }
573
+ var argReader = typeSpec.reader, arg = osc[argReader](dv, offsetState);
574
+ if (options.metadata) {
575
+ arg = {
576
+ type: argType,
577
+ value: arg
578
+ };
579
+ }
580
+ return arg;
581
+ };
582
+ osc.readArgumentsIntoArray = function(arr, argTypes, typeTagString, dv, options, offsetState) {
583
+ var i = 0;
584
+ while (i < argTypes.length) {
585
+ var argType = argTypes[i], arg;
586
+ if (argType === "[") {
587
+ var fromArrayOpen = argTypes.slice(i + 1), endArrayIdx = fromArrayOpen.indexOf("]");
588
+ if (endArrayIdx < 0) {
589
+ throw new Error("Invalid argument type tag: an open array type tag ('[') was found without a matching close array tag ('[]'). Type tag was: " + typeTagString);
590
+ }
591
+ var typesInArray = fromArrayOpen.slice(0, endArrayIdx);
592
+ arg = osc.readArgumentsIntoArray([], typesInArray, typeTagString, dv, options, offsetState);
593
+ i += endArrayIdx + 2;
594
+ } else {
595
+ arg = osc.readArgument(argType, typeTagString, dv, options, offsetState);
596
+ i++;
597
+ }
598
+ arr.push(arg);
599
+ }
600
+ return arr;
601
+ };
602
+ osc.writeArguments = function(args, options) {
603
+ var argCollection = osc.collectArguments(args, options);
604
+ return osc.joinParts(argCollection);
605
+ };
606
+ osc.joinParts = function(dataCollection) {
607
+ var buf = new Uint8Array(dataCollection.byteLength), parts = dataCollection.parts, offset = 0;
608
+ for (var i = 0; i < parts.length; i++) {
609
+ var part = parts[i];
610
+ osc.copyByteArray(part, buf, offset);
611
+ offset += part.length;
612
+ }
613
+ return buf;
614
+ };
615
+ osc.addDataPart = function(dataPart, dataCollection) {
616
+ dataCollection.parts.push(dataPart);
617
+ dataCollection.byteLength += dataPart.length;
618
+ };
619
+ osc.writeArrayArguments = function(args, dataCollection) {
620
+ var typeTag = "[";
621
+ for (var i = 0; i < args.length; i++) {
622
+ var arg = args[i];
623
+ typeTag += osc.writeArgument(arg, dataCollection);
624
+ }
625
+ typeTag += "]";
626
+ return typeTag;
627
+ };
628
+ osc.writeArgument = function(arg, dataCollection) {
629
+ if (osc.isArray(arg)) {
630
+ return osc.writeArrayArguments(arg, dataCollection);
631
+ }
632
+ var type = arg.type, writer = osc.argumentTypes[type].writer;
633
+ if (writer) {
634
+ var data = osc[writer](arg.value);
635
+ osc.addDataPart(data, dataCollection);
636
+ }
637
+ return arg.type;
638
+ };
639
+ osc.collectArguments = function(args, options, dataCollection) {
640
+ if (!osc.isArray(args)) {
641
+ args = typeof args === "undefined" ? [] : [args];
642
+ }
643
+ dataCollection = dataCollection || {
644
+ byteLength: 0,
645
+ parts: []
646
+ };
647
+ if (!options.metadata) {
648
+ args = osc.annotateArguments(args);
649
+ }
650
+ var typeTagString = ",", currPartIdx = dataCollection.parts.length;
651
+ for (var i = 0; i < args.length; i++) {
652
+ var arg = args[i];
653
+ typeTagString += osc.writeArgument(arg, dataCollection);
654
+ }
655
+ var typeData = osc.writeString(typeTagString);
656
+ dataCollection.byteLength += typeData.byteLength;
657
+ dataCollection.parts.splice(currPartIdx, 0, typeData);
658
+ return dataCollection;
659
+ };
660
+ osc.readMessage = function(data, options, offsetState) {
661
+ options = options || osc.defaults;
662
+ var dv = osc.dataView(data, data.byteOffset, data.byteLength);
663
+ offsetState = offsetState || {
664
+ idx: 0
665
+ };
666
+ var address = osc.readString(dv, offsetState);
667
+ return osc.readMessageContents(address, dv, options, offsetState);
668
+ };
669
+ osc.readMessageContents = function(address, dv, options, offsetState) {
670
+ if (address.indexOf("/") !== 0) {
671
+ throw new Error("A malformed OSC address was found while reading an OSC message. String was: " + address);
672
+ }
673
+ var args = osc.readArguments(dv, options, offsetState);
674
+ return {
675
+ address,
676
+ args: args.length === 1 && options.unpackSingleArgs ? args[0] : args
677
+ };
678
+ };
679
+ osc.collectMessageParts = function(msg, options, dataCollection) {
680
+ dataCollection = dataCollection || {
681
+ byteLength: 0,
682
+ parts: []
683
+ };
684
+ osc.addDataPart(osc.writeString(msg.address), dataCollection);
685
+ return osc.collectArguments(msg.args, options, dataCollection);
686
+ };
687
+ osc.writeMessage = function(msg, options) {
688
+ options = options || osc.defaults;
689
+ if (!osc.isValidMessage(msg)) {
690
+ throw new Error("An OSC message must contain a valid address. Message was: " + JSON.stringify(msg, null, 2));
691
+ }
692
+ var msgCollection = osc.collectMessageParts(msg, options);
693
+ return osc.joinParts(msgCollection);
694
+ };
695
+ osc.isValidMessage = function(msg) {
696
+ return msg.address && msg.address.indexOf("/") === 0;
697
+ };
698
+ osc.readBundle = function(dv, options, offsetState) {
699
+ return osc.readPacket(dv, options, offsetState);
700
+ };
701
+ osc.collectBundlePackets = function(bundle, options, dataCollection) {
702
+ dataCollection = dataCollection || {
703
+ byteLength: 0,
704
+ parts: []
705
+ };
706
+ osc.addDataPart(osc.writeString("#bundle"), dataCollection);
707
+ osc.addDataPart(osc.writeTimeTag(bundle.timeTag), dataCollection);
708
+ for (var i = 0; i < bundle.packets.length; i++) {
709
+ var packet = bundle.packets[i], collector = packet.address ? osc.collectMessageParts : osc.collectBundlePackets, packetCollection = collector(packet, options);
710
+ dataCollection.byteLength += packetCollection.byteLength;
711
+ osc.addDataPart(osc.writeInt32(packetCollection.byteLength), dataCollection);
712
+ dataCollection.parts = dataCollection.parts.concat(packetCollection.parts);
713
+ }
714
+ return dataCollection;
715
+ };
716
+ osc.writeBundle = function(bundle, options) {
717
+ if (!osc.isValidBundle(bundle)) {
718
+ throw new Error("An OSC bundle must contain 'timeTag' and 'packets' properties. Bundle was: " + JSON.stringify(bundle, null, 2));
719
+ }
720
+ options = options || osc.defaults;
721
+ var bundleCollection = osc.collectBundlePackets(bundle, options);
722
+ return osc.joinParts(bundleCollection);
723
+ };
724
+ osc.isValidBundle = function(bundle) {
725
+ return bundle.timeTag !== void 0 && bundle.packets !== void 0;
726
+ };
727
+ osc.readBundleContents = function(dv, options, offsetState, len) {
728
+ var timeTag = osc.readTimeTag(dv, offsetState), packets = [];
729
+ while (offsetState.idx < len) {
730
+ var packetSize = osc.readInt32(dv, offsetState), packetLen = offsetState.idx + packetSize, packet = osc.readPacket(dv, options, offsetState, packetLen);
731
+ packets.push(packet);
732
+ }
733
+ return {
734
+ timeTag,
735
+ packets
736
+ };
737
+ };
738
+ osc.readPacket = function(data, options, offsetState, len) {
739
+ var dv = osc.dataView(data, data.byteOffset, data.byteLength);
740
+ len = len === void 0 ? dv.byteLength : len;
741
+ offsetState = offsetState || {
742
+ idx: 0
743
+ };
744
+ var header = osc.readString(dv, offsetState), firstChar = header[0];
745
+ if (firstChar === "#") {
746
+ return osc.readBundleContents(dv, options, offsetState, len);
747
+ } else if (firstChar === "/") {
748
+ return osc.readMessageContents(header, dv, options, offsetState);
749
+ }
750
+ throw new Error("The header of an OSC packet didn't contain an OSC address or a #bundle string. Header was: " + header);
751
+ };
752
+ osc.writePacket = function(packet, options) {
753
+ if (osc.isValidMessage(packet)) {
754
+ return osc.writeMessage(packet, options);
755
+ } else if (osc.isValidBundle(packet)) {
756
+ return osc.writeBundle(packet, options);
757
+ } else {
758
+ throw new Error("The specified packet was not recognized as a valid OSC message or bundle. Packet was: " + JSON.stringify(packet, null, 2));
759
+ }
760
+ };
761
+ osc.argumentTypes = {
762
+ i: {
763
+ reader: "readInt32",
764
+ writer: "writeInt32"
765
+ },
766
+ h: {
767
+ reader: "readInt64",
768
+ writer: "writeInt64"
769
+ },
770
+ f: {
771
+ reader: "readFloat32",
772
+ writer: "writeFloat32"
773
+ },
774
+ s: {
775
+ reader: "readString",
776
+ writer: "writeString"
777
+ },
778
+ S: {
779
+ reader: "readString",
780
+ writer: "writeString"
781
+ },
782
+ b: {
783
+ reader: "readBlob",
784
+ writer: "writeBlob"
785
+ },
786
+ t: {
787
+ reader: "readTimeTag",
788
+ writer: "writeTimeTag"
789
+ },
790
+ T: {
791
+ reader: "readTrue"
792
+ },
793
+ F: {
794
+ reader: "readFalse"
795
+ },
796
+ N: {
797
+ reader: "readNull"
798
+ },
799
+ I: {
800
+ reader: "readImpulse"
801
+ },
802
+ d: {
803
+ reader: "readFloat64",
804
+ writer: "writeFloat64"
805
+ },
806
+ c: {
807
+ reader: "readChar32",
808
+ writer: "writeChar32"
809
+ },
810
+ r: {
811
+ reader: "readColor",
812
+ writer: "writeColor"
813
+ },
814
+ m: {
815
+ reader: "readMIDIBytes",
816
+ writer: "writeMIDIBytes"
817
+ }
818
+ // [] are special cased within read/writeArguments()
819
+ };
820
+ osc.inferTypeForArgument = function(arg) {
821
+ var type = typeof arg;
822
+ switch (type) {
823
+ case "boolean":
824
+ return arg ? "T" : "F";
825
+ case "string":
826
+ return "s";
827
+ case "number":
828
+ return "f";
829
+ case "undefined":
830
+ return "N";
831
+ case "object":
832
+ if (arg === null) {
833
+ return "N";
834
+ } else if (arg instanceof Uint8Array || arg instanceof ArrayBuffer) {
835
+ return "b";
836
+ } else if (typeof arg.high === "number" && typeof arg.low === "number") {
837
+ return "h";
838
+ }
839
+ break;
840
+ }
841
+ throw new Error("Can't infer OSC argument type for value: " + JSON.stringify(arg, null, 2));
842
+ };
843
+ osc.annotateArguments = function(args) {
844
+ var annotated = [];
845
+ for (var i = 0; i < args.length; i++) {
846
+ var arg = args[i], msgArg;
847
+ if (typeof arg === "object" && arg.type && arg.value !== void 0) {
848
+ msgArg = arg;
849
+ } else if (osc.isArray(arg)) {
850
+ msgArg = osc.annotateArguments(arg);
851
+ } else {
852
+ var oscType = osc.inferTypeForArgument(arg);
853
+ msgArg = {
854
+ type: oscType,
855
+ value: arg
856
+ };
857
+ }
858
+ annotated.push(msgArg);
859
+ }
860
+ return annotated;
861
+ };
862
+ ;
863
+ })();
864
+ var EventEmitter = function() {
865
+ };
866
+ EventEmitter.prototype.on = function() {
867
+ };
868
+ EventEmitter.prototype.emit = function() {
869
+ };
870
+ EventEmitter.prototype.removeListener = function() {
871
+ };
872
+ (function() {
873
+ "use strict";
874
+ osc.supportsSerial = false;
875
+ osc.firePacketEvents = function(port, packet, timeTag, packetInfo) {
876
+ if (packet.address) {
877
+ port.emit("message", packet, timeTag, packetInfo);
878
+ } else {
879
+ osc.fireBundleEvents(port, packet, timeTag, packetInfo);
880
+ }
881
+ };
882
+ osc.fireBundleEvents = function(port, bundle, timeTag, packetInfo) {
883
+ port.emit("bundle", bundle, timeTag, packetInfo);
884
+ for (var i = 0; i < bundle.packets.length; i++) {
885
+ var packet = bundle.packets[i];
886
+ osc.firePacketEvents(port, packet, bundle.timeTag, packetInfo);
887
+ }
888
+ };
889
+ osc.fireClosedPortSendError = function(port, msg) {
890
+ msg = msg || "Can't send packets on a closed osc.Port object. Please open (or reopen) this Port by calling open().";
891
+ port.emit("error", msg);
892
+ };
893
+ osc.Port = function(options) {
894
+ this.options = options || {};
895
+ this.on("data", this.decodeOSC.bind(this));
896
+ };
897
+ var p = osc.Port.prototype = Object.create(EventEmitter.prototype);
898
+ p.constructor = osc.Port;
899
+ p.send = function(oscPacket) {
900
+ var args = Array.prototype.slice.call(arguments), encoded = this.encodeOSC(oscPacket), buf = osc.nativeBuffer(encoded);
901
+ args[0] = buf;
902
+ this.sendRaw.apply(this, args);
903
+ };
904
+ p.encodeOSC = function(packet) {
905
+ packet = packet.buffer ? packet.buffer : packet;
906
+ var encoded;
907
+ try {
908
+ encoded = osc.writePacket(packet, this.options);
909
+ } catch (err) {
910
+ this.emit("error", err);
911
+ }
912
+ return encoded;
913
+ };
914
+ p.decodeOSC = function(data, packetInfo) {
915
+ data = osc.byteArray(data);
916
+ this.emit("raw", data, packetInfo);
917
+ try {
918
+ var packet = osc.readPacket(data, this.options);
919
+ this.emit("osc", packet, packetInfo);
920
+ osc.firePacketEvents(this, packet, void 0, packetInfo);
921
+ } catch (err) {
922
+ this.emit("error", err);
923
+ }
924
+ };
925
+ osc.SLIPPort = function(options) {
926
+ var that = this;
927
+ var o = this.options = options || {};
928
+ o.useSLIP = o.useSLIP === void 0 ? true : o.useSLIP;
929
+ this.decoder = new slip.Decoder({
930
+ onMessage: this.decodeOSC.bind(this),
931
+ onError: function(err) {
932
+ that.emit("error", err);
933
+ }
934
+ });
935
+ var decodeHandler = o.useSLIP ? this.decodeSLIPData : this.decodeOSC;
936
+ this.on("data", decodeHandler.bind(this));
937
+ };
938
+ p = osc.SLIPPort.prototype = Object.create(osc.Port.prototype);
939
+ p.constructor = osc.SLIPPort;
940
+ p.encodeOSC = function(packet) {
941
+ packet = packet.buffer ? packet.buffer : packet;
942
+ var framed;
943
+ try {
944
+ var encoded = osc.writePacket(packet, this.options);
945
+ framed = slip.encode(encoded);
946
+ } catch (err) {
947
+ this.emit("error", err);
948
+ }
949
+ return framed;
950
+ };
951
+ p.decodeSLIPData = function(data, packetInfo) {
952
+ this.decoder.decode(data, packetInfo);
953
+ };
954
+ osc.relay = function(from, to, eventName, sendFnName, transformFn, sendArgs) {
955
+ eventName = eventName || "message";
956
+ sendFnName = sendFnName || "send";
957
+ transformFn = transformFn || function() {
958
+ };
959
+ sendArgs = sendArgs ? [null].concat(sendArgs) : [];
960
+ var listener = function(data) {
961
+ sendArgs[0] = data;
962
+ data = transformFn(data);
963
+ to[sendFnName].apply(to, sendArgs);
964
+ };
965
+ from.on(eventName, listener);
966
+ return {
967
+ eventName,
968
+ listener
969
+ };
970
+ };
971
+ osc.relayPorts = function(from, to, o) {
972
+ var eventName = o.raw ? "raw" : "osc", sendFnName = o.raw ? "sendRaw" : "send";
973
+ return osc.relay(from, to, eventName, sendFnName, o.transform);
974
+ };
975
+ osc.stopRelaying = function(from, relaySpec) {
976
+ from.removeListener(relaySpec.eventName, relaySpec.listener);
977
+ };
978
+ osc.Relay = function(port1, port2, options) {
979
+ var o = this.options = options || {};
980
+ o.raw = false;
981
+ this.port1 = port1;
982
+ this.port2 = port2;
983
+ this.listen();
984
+ };
985
+ p = osc.Relay.prototype = Object.create(EventEmitter.prototype);
986
+ p.constructor = osc.Relay;
987
+ p.open = function() {
988
+ this.port1.open();
989
+ this.port2.open();
990
+ };
991
+ p.listen = function() {
992
+ if (this.port1Spec && this.port2Spec) {
993
+ this.close();
994
+ }
995
+ this.port1Spec = osc.relayPorts(this.port1, this.port2, this.options);
996
+ this.port2Spec = osc.relayPorts(this.port2, this.port1, this.options);
997
+ var closeListener = this.close.bind(this);
998
+ this.port1.on("close", closeListener);
999
+ this.port2.on("close", closeListener);
1000
+ };
1001
+ p.close = function() {
1002
+ osc.stopRelaying(this.port1, this.port1Spec);
1003
+ osc.stopRelaying(this.port2, this.port2Spec);
1004
+ this.emit("close", this.port1, this.port2);
1005
+ };
1006
+ })();
1007
+ (function() {
1008
+ "use strict";
1009
+ osc.WebSocket = typeof WebSocket !== "undefined" ? WebSocket : void 0;
1010
+ osc.WebSocketPort = function(options) {
1011
+ osc.Port.call(this, options);
1012
+ this.on("open", this.listen.bind(this));
1013
+ this.socket = options.socket;
1014
+ if (this.socket) {
1015
+ if (this.socket.readyState === 1) {
1016
+ osc.WebSocketPort.setupSocketForBinary(this.socket);
1017
+ this.emit("open", this.socket);
1018
+ } else {
1019
+ this.open();
1020
+ }
1021
+ }
1022
+ };
1023
+ var p = osc.WebSocketPort.prototype = Object.create(osc.Port.prototype);
1024
+ p.constructor = osc.WebSocketPort;
1025
+ p.open = function() {
1026
+ if (!this.socket || this.socket.readyState > 1) {
1027
+ this.socket = new osc.WebSocket(this.options.url);
1028
+ }
1029
+ osc.WebSocketPort.setupSocketForBinary(this.socket);
1030
+ var that = this;
1031
+ this.socket.onopen = function() {
1032
+ that.emit("open", that.socket);
1033
+ };
1034
+ this.socket.onerror = function(err) {
1035
+ that.emit("error", err);
1036
+ };
1037
+ };
1038
+ p.listen = function() {
1039
+ var that = this;
1040
+ this.socket.onmessage = function(e) {
1041
+ that.emit("data", e.data, e);
1042
+ };
1043
+ this.socket.onclose = function(e) {
1044
+ that.emit("close", e);
1045
+ };
1046
+ that.emit("ready");
1047
+ };
1048
+ p.sendRaw = function(encoded) {
1049
+ if (!this.socket || this.socket.readyState !== 1) {
1050
+ osc.fireClosedPortSendError(this);
1051
+ return;
1052
+ }
1053
+ this.socket.send(encoded);
1054
+ };
1055
+ p.close = function(code, reason) {
1056
+ this.socket.close(code, reason);
1057
+ };
1058
+ osc.WebSocketPort.setupSocketForBinary = function(socket) {
1059
+ socket.binaryType = osc.isNode ? "nodebuffer" : "arraybuffer";
1060
+ };
1061
+ })();
1062
+ var osc_default = osc;
1063
+ var { readPacket, writePacket, readMessage, writeMessage, readBundle, writeBundle } = osc;
1064
+
1065
+ // node_modules/@thi.ng/api/typedarray.js
1066
+ var GL2TYPE = {
1067
+ [
1068
+ 5120
1069
+ /* I8 */
1070
+ ]: "i8",
1071
+ [
1072
+ 5121
1073
+ /* U8 */
1074
+ ]: "u8",
1075
+ [
1076
+ 5122
1077
+ /* I16 */
1078
+ ]: "i16",
1079
+ [
1080
+ 5123
1081
+ /* U16 */
1082
+ ]: "u16",
1083
+ [
1084
+ 5124
1085
+ /* I32 */
1086
+ ]: "i32",
1087
+ [
1088
+ 5125
1089
+ /* U32 */
1090
+ ]: "u32",
1091
+ [
1092
+ 5126
1093
+ /* F32 */
1094
+ ]: "f32"
1095
+ };
1096
+ var SIZEOF = {
1097
+ u8: 1,
1098
+ u8c: 1,
1099
+ i8: 1,
1100
+ u16: 2,
1101
+ i16: 2,
1102
+ u32: 4,
1103
+ i32: 4,
1104
+ i64: 8,
1105
+ u64: 8,
1106
+ f32: 4,
1107
+ f64: 8
1108
+ };
1109
+ var FLOAT_ARRAY_CTORS = {
1110
+ f32: Float32Array,
1111
+ f64: Float64Array
1112
+ };
1113
+ var INT_ARRAY_CTORS = {
1114
+ i8: Int8Array,
1115
+ i16: Int16Array,
1116
+ i32: Int32Array
1117
+ };
1118
+ var UINT_ARRAY_CTORS = {
1119
+ u8: Uint8Array,
1120
+ u8c: Uint8ClampedArray,
1121
+ u16: Uint16Array,
1122
+ u32: Uint32Array
1123
+ };
1124
+ var BIGINT_ARRAY_CTORS = {
1125
+ i64: BigInt64Array,
1126
+ u64: BigUint64Array
1127
+ };
1128
+ var TYPEDARRAY_CTORS = {
1129
+ ...FLOAT_ARRAY_CTORS,
1130
+ ...INT_ARRAY_CTORS,
1131
+ ...UINT_ARRAY_CTORS
1132
+ };
1133
+ var asNativeType = (type) => {
1134
+ const t = GL2TYPE[type];
1135
+ return t !== void 0 ? t : type;
1136
+ };
1137
+ function typedArray(type, ...args) {
1138
+ const ctor = BIGINT_ARRAY_CTORS[type];
1139
+ return new (ctor || TYPEDARRAY_CTORS[asNativeType(type)])(...args);
1140
+ }
1141
+
1142
+ // node_modules/@thi.ng/binary/align.js
1143
+ var align = (addr, size) => (size--, addr + size & ~size);
1144
+
1145
+ // node_modules/@thi.ng/checks/is-number.js
1146
+ var isNumber = (x) => typeof x === "number";
1147
+
1148
+ // node_modules/@thi.ng/errors/deferror.js
1149
+ var defError = (prefix, suffix = (msg) => msg !== void 0 ? ": " + msg : "") => class extends Error {
1150
+ origMessage;
1151
+ constructor(msg) {
1152
+ super(prefix(msg) + suffix(msg));
1153
+ this.origMessage = msg !== void 0 ? String(msg) : "";
1154
+ }
1155
+ };
1156
+
1157
+ // node_modules/@thi.ng/errors/assert.js
1158
+ var AssertionError = defError(() => "Assertion failed");
1159
+ var assert = (typeof process !== "undefined" && process.env !== void 0 ? true : import.meta.env ? import.meta.env.MODE !== "production" || !!import.meta.env.UMBRELLA_ASSERTS || !!import.meta.env.VITE_UMBRELLA_ASSERTS : true) ? (test, msg) => {
1160
+ if (typeof test === "function" && !test() || !test) {
1161
+ throw new AssertionError(
1162
+ typeof msg === "function" ? msg() : msg
1163
+ );
1164
+ }
1165
+ } : () => {
1166
+ };
1167
+
1168
+ // node_modules/@thi.ng/errors/illegal-arguments.js
1169
+ var IllegalArgumentError = defError(() => "illegal argument(s)");
1170
+ var illegalArgs = (msg) => {
1171
+ throw new IllegalArgumentError(msg);
1172
+ };
1173
+
1174
+ // node_modules/@thi.ng/malloc/pool.js
1175
+ var STATE_FREE = 0;
1176
+ var STATE_USED = 1;
1177
+ var STATE_TOP = 2;
1178
+ var STATE_END = 3;
1179
+ var STATE_ALIGN = 4;
1180
+ var STATE_FLAGS = 5;
1181
+ var STATE_MIN_SPLIT = 6;
1182
+ var MASK_COMPACT = 1;
1183
+ var MASK_SPLIT = 2;
1184
+ var SIZEOF_STATE = 7 * 4;
1185
+ var MEM_BLOCK_SIZE = 0;
1186
+ var MEM_BLOCK_NEXT = 1;
1187
+ var SIZEOF_MEM_BLOCK = 2 * 4;
1188
+ var MemPool = class {
1189
+ buf;
1190
+ start;
1191
+ u8;
1192
+ u32;
1193
+ state;
1194
+ constructor(opts = {}) {
1195
+ this.buf = opts.buf ? opts.buf : new ArrayBuffer(opts.size || 4096);
1196
+ this.start = opts.start != null ? align(Math.max(opts.start, 0), 4) : 0;
1197
+ this.u8 = new Uint8Array(this.buf);
1198
+ this.u32 = new Uint32Array(this.buf);
1199
+ this.state = new Uint32Array(this.buf, this.start, SIZEOF_STATE / 4);
1200
+ if (!opts.skipInitialization) {
1201
+ const _align = opts.align || 8;
1202
+ assert(
1203
+ _align >= 8,
1204
+ `invalid alignment: ${_align}, must be a pow2 and >= 8`
1205
+ );
1206
+ const top = this.initialTop(_align);
1207
+ const resolvedEnd = opts.end != null ? Math.min(opts.end, this.buf.byteLength) : this.buf.byteLength;
1208
+ if (top >= resolvedEnd) {
1209
+ illegalArgs(
1210
+ `insufficient address range (0x${this.start.toString(
1211
+ 16
1212
+ )} - 0x${resolvedEnd.toString(16)})`
1213
+ );
1214
+ }
1215
+ this.align = _align;
1216
+ this.doCompact = opts.compact !== false;
1217
+ this.doSplit = opts.split !== false;
1218
+ this.minSplit = opts.minSplit || 16;
1219
+ this.end = resolvedEnd;
1220
+ this.top = top;
1221
+ this._free = 0;
1222
+ this._used = 0;
1223
+ }
1224
+ }
1225
+ stats() {
1226
+ const listStats = (block) => {
1227
+ let count = 0;
1228
+ let size = 0;
1229
+ while (block) {
1230
+ count++;
1231
+ size += this.blockSize(block);
1232
+ block = this.blockNext(block);
1233
+ }
1234
+ return { count, size };
1235
+ };
1236
+ const free = listStats(this._free);
1237
+ return {
1238
+ free,
1239
+ used: listStats(this._used),
1240
+ top: this.top,
1241
+ available: this.end - this.top + free.size,
1242
+ total: this.buf.byteLength
1243
+ };
1244
+ }
1245
+ callocAs(type, num, fill = 0) {
1246
+ const block = this.mallocAs(type, num);
1247
+ block?.fill(fill);
1248
+ return block;
1249
+ }
1250
+ mallocAs(type, num) {
1251
+ const addr = this.malloc(num * SIZEOF[type]);
1252
+ return addr ? typedArray(type, this.buf, addr, num) : void 0;
1253
+ }
1254
+ calloc(bytes, fill = 0) {
1255
+ const addr = this.malloc(bytes);
1256
+ addr && this.u8.fill(fill, addr, addr + bytes);
1257
+ return addr;
1258
+ }
1259
+ malloc(bytes) {
1260
+ if (bytes <= 0) {
1261
+ return 0;
1262
+ }
1263
+ const paddedSize = align(bytes + SIZEOF_MEM_BLOCK, this.align);
1264
+ const end = this.end;
1265
+ let top = this.top;
1266
+ let block = this._free;
1267
+ let prev = 0;
1268
+ while (block) {
1269
+ const blockSize = this.blockSize(block);
1270
+ const isTop = block + blockSize >= top;
1271
+ if (isTop || blockSize >= paddedSize) {
1272
+ return this.mallocTop(
1273
+ block,
1274
+ prev,
1275
+ blockSize,
1276
+ paddedSize,
1277
+ isTop
1278
+ );
1279
+ }
1280
+ prev = block;
1281
+ block = this.blockNext(block);
1282
+ }
1283
+ block = top;
1284
+ top = block + paddedSize;
1285
+ if (top <= end) {
1286
+ this.initBlock(block, paddedSize, this._used);
1287
+ this._used = block;
1288
+ this.top = top;
1289
+ return __blockDataAddress(block);
1290
+ }
1291
+ return 0;
1292
+ }
1293
+ mallocTop(block, prev, blockSize, paddedSize, isTop) {
1294
+ if (isTop && block + paddedSize > this.end) return 0;
1295
+ if (prev) {
1296
+ this.unlinkBlock(prev, block);
1297
+ } else {
1298
+ this._free = this.blockNext(block);
1299
+ }
1300
+ this.setBlockNext(block, this._used);
1301
+ this._used = block;
1302
+ if (isTop) {
1303
+ this.top = block + this.setBlockSize(block, paddedSize);
1304
+ } else if (this.doSplit) {
1305
+ const excess = blockSize - paddedSize;
1306
+ excess >= this.minSplit && this.splitBlock(block, paddedSize, excess);
1307
+ }
1308
+ return __blockDataAddress(block);
1309
+ }
1310
+ realloc(ptr, bytes) {
1311
+ if (bytes <= 0) {
1312
+ return 0;
1313
+ }
1314
+ const oldAddr = __blockSelfAddress(ptr);
1315
+ let newAddr = 0;
1316
+ let block = this._used;
1317
+ let blockEnd = 0;
1318
+ while (block) {
1319
+ if (block === oldAddr) {
1320
+ [newAddr, blockEnd] = this.reallocBlock(block, bytes);
1321
+ break;
1322
+ }
1323
+ block = this.blockNext(block);
1324
+ }
1325
+ if (newAddr && newAddr !== oldAddr) {
1326
+ this.u8.copyWithin(
1327
+ __blockDataAddress(newAddr),
1328
+ __blockDataAddress(oldAddr),
1329
+ blockEnd
1330
+ );
1331
+ }
1332
+ return __blockDataAddress(newAddr);
1333
+ }
1334
+ reallocBlock(block, bytes) {
1335
+ const blockSize = this.blockSize(block);
1336
+ const blockEnd = block + blockSize;
1337
+ const isTop = blockEnd >= this.top;
1338
+ const paddedSize = align(bytes + SIZEOF_MEM_BLOCK, this.align);
1339
+ if (paddedSize <= blockSize) {
1340
+ if (this.doSplit) {
1341
+ const excess = blockSize - paddedSize;
1342
+ if (excess >= this.minSplit) {
1343
+ this.splitBlock(block, paddedSize, excess);
1344
+ } else if (isTop) {
1345
+ this.top = block + paddedSize;
1346
+ }
1347
+ } else if (isTop) {
1348
+ this.top = block + paddedSize;
1349
+ }
1350
+ return [block, blockEnd];
1351
+ }
1352
+ if (isTop && block + paddedSize < this.end) {
1353
+ this.top = block + this.setBlockSize(block, paddedSize);
1354
+ return [block, blockEnd];
1355
+ }
1356
+ this.free(block);
1357
+ return [__blockSelfAddress(this.malloc(bytes)), blockEnd];
1358
+ }
1359
+ reallocArray(array, num) {
1360
+ if (array.buffer !== this.buf) {
1361
+ return;
1362
+ }
1363
+ const addr = this.realloc(
1364
+ array.byteOffset,
1365
+ num * array.BYTES_PER_ELEMENT
1366
+ );
1367
+ return addr ? new array.constructor(this.buf, addr, num) : void 0;
1368
+ }
1369
+ free(ptrOrArray) {
1370
+ let addr;
1371
+ if (!isNumber(ptrOrArray)) {
1372
+ if (ptrOrArray.buffer !== this.buf) {
1373
+ return false;
1374
+ }
1375
+ addr = ptrOrArray.byteOffset;
1376
+ } else {
1377
+ addr = ptrOrArray;
1378
+ }
1379
+ addr = __blockSelfAddress(addr);
1380
+ let block = this._used;
1381
+ let prev = 0;
1382
+ while (block) {
1383
+ if (block === addr) {
1384
+ if (prev) {
1385
+ this.unlinkBlock(prev, block);
1386
+ } else {
1387
+ this._used = this.blockNext(block);
1388
+ }
1389
+ this.insert(block);
1390
+ this.doCompact && this.compact();
1391
+ return true;
1392
+ }
1393
+ prev = block;
1394
+ block = this.blockNext(block);
1395
+ }
1396
+ return false;
1397
+ }
1398
+ freeAll() {
1399
+ this._free = 0;
1400
+ this._used = 0;
1401
+ this.top = this.initialTop();
1402
+ }
1403
+ release() {
1404
+ delete this.u8;
1405
+ delete this.u32;
1406
+ delete this.state;
1407
+ delete this.buf;
1408
+ return true;
1409
+ }
1410
+ get align() {
1411
+ return this.state[STATE_ALIGN];
1412
+ }
1413
+ set align(x) {
1414
+ this.state[STATE_ALIGN] = x;
1415
+ }
1416
+ get end() {
1417
+ return this.state[STATE_END];
1418
+ }
1419
+ set end(x) {
1420
+ this.state[STATE_END] = x;
1421
+ }
1422
+ get top() {
1423
+ return this.state[STATE_TOP];
1424
+ }
1425
+ set top(x) {
1426
+ this.state[STATE_TOP] = x;
1427
+ }
1428
+ get _free() {
1429
+ return this.state[STATE_FREE];
1430
+ }
1431
+ set _free(block) {
1432
+ this.state[STATE_FREE] = block;
1433
+ }
1434
+ get _used() {
1435
+ return this.state[STATE_USED];
1436
+ }
1437
+ set _used(block) {
1438
+ this.state[STATE_USED] = block;
1439
+ }
1440
+ get doCompact() {
1441
+ return !!(this.state[STATE_FLAGS] & MASK_COMPACT);
1442
+ }
1443
+ set doCompact(flag) {
1444
+ flag ? this.state[STATE_FLAGS] |= 1 << MASK_COMPACT - 1 : this.state[STATE_FLAGS] &= ~MASK_COMPACT;
1445
+ }
1446
+ get doSplit() {
1447
+ return !!(this.state[STATE_FLAGS] & MASK_SPLIT);
1448
+ }
1449
+ set doSplit(flag) {
1450
+ flag ? this.state[STATE_FLAGS] |= 1 << MASK_SPLIT - 1 : this.state[STATE_FLAGS] &= ~MASK_SPLIT;
1451
+ }
1452
+ get minSplit() {
1453
+ return this.state[STATE_MIN_SPLIT];
1454
+ }
1455
+ set minSplit(x) {
1456
+ assert(
1457
+ x > SIZEOF_MEM_BLOCK,
1458
+ `illegal min split threshold: ${x}, require at least ${SIZEOF_MEM_BLOCK + 1}`
1459
+ );
1460
+ this.state[STATE_MIN_SPLIT] = x;
1461
+ }
1462
+ blockSize(block) {
1463
+ return this.u32[(block >> 2) + MEM_BLOCK_SIZE];
1464
+ }
1465
+ /**
1466
+ * Sets & returns given block size.
1467
+ *
1468
+ * @param block -
1469
+ * @param size -
1470
+ */
1471
+ setBlockSize(block, size) {
1472
+ this.u32[(block >> 2) + MEM_BLOCK_SIZE] = size;
1473
+ return size;
1474
+ }
1475
+ blockNext(block) {
1476
+ return this.u32[(block >> 2) + MEM_BLOCK_NEXT];
1477
+ }
1478
+ /**
1479
+ * Sets block next pointer to `next`. Use zero to indicate list end.
1480
+ *
1481
+ * @param block -
1482
+ */
1483
+ setBlockNext(block, next) {
1484
+ this.u32[(block >> 2) + MEM_BLOCK_NEXT] = next;
1485
+ }
1486
+ /**
1487
+ * Initializes block header with given `size` and `next` pointer. Returns `block`.
1488
+ *
1489
+ * @param block -
1490
+ * @param size -
1491
+ * @param next -
1492
+ */
1493
+ initBlock(block, size, next) {
1494
+ const idx = block >>> 2;
1495
+ this.u32[idx + MEM_BLOCK_SIZE] = size;
1496
+ this.u32[idx + MEM_BLOCK_NEXT] = next;
1497
+ return block;
1498
+ }
1499
+ unlinkBlock(prev, block) {
1500
+ this.setBlockNext(prev, this.blockNext(block));
1501
+ }
1502
+ splitBlock(block, blockSize, excess) {
1503
+ this.insert(
1504
+ this.initBlock(
1505
+ block + this.setBlockSize(block, blockSize),
1506
+ excess,
1507
+ 0
1508
+ )
1509
+ );
1510
+ this.doCompact && this.compact();
1511
+ }
1512
+ initialTop(_align = this.align) {
1513
+ return align(this.start + SIZEOF_STATE + SIZEOF_MEM_BLOCK, _align) - SIZEOF_MEM_BLOCK;
1514
+ }
1515
+ /**
1516
+ * Traverses free list and attempts to recursively merge blocks
1517
+ * occupying consecutive memory regions. Returns true if any blocks
1518
+ * have been merged. Only called if `compact` option is enabled.
1519
+ */
1520
+ compact() {
1521
+ let block = this._free;
1522
+ let prev = 0;
1523
+ let scan = 0;
1524
+ let scanPrev;
1525
+ let res = false;
1526
+ while (block) {
1527
+ scanPrev = block;
1528
+ scan = this.blockNext(block);
1529
+ while (scan && scanPrev + this.blockSize(scanPrev) === scan) {
1530
+ scanPrev = scan;
1531
+ scan = this.blockNext(scan);
1532
+ }
1533
+ if (scanPrev !== block) {
1534
+ const newSize = scanPrev - block + this.blockSize(scanPrev);
1535
+ this.setBlockSize(block, newSize);
1536
+ const next = this.blockNext(scanPrev);
1537
+ let tmp = this.blockNext(block);
1538
+ while (tmp && tmp !== next) {
1539
+ const tn = this.blockNext(tmp);
1540
+ this.setBlockNext(tmp, 0);
1541
+ tmp = tn;
1542
+ }
1543
+ this.setBlockNext(block, next);
1544
+ res = true;
1545
+ }
1546
+ if (block + this.blockSize(block) >= this.top) {
1547
+ this.top = block;
1548
+ prev ? this.unlinkBlock(prev, block) : this._free = this.blockNext(block);
1549
+ }
1550
+ prev = block;
1551
+ block = this.blockNext(block);
1552
+ }
1553
+ return res;
1554
+ }
1555
+ /**
1556
+ * Inserts given block into list of free blocks, sorted by address.
1557
+ *
1558
+ * @param block -
1559
+ */
1560
+ insert(block) {
1561
+ let ptr = this._free;
1562
+ let prev = 0;
1563
+ while (ptr) {
1564
+ if (block <= ptr) break;
1565
+ prev = ptr;
1566
+ ptr = this.blockNext(ptr);
1567
+ }
1568
+ if (prev) {
1569
+ this.setBlockNext(prev, block);
1570
+ } else {
1571
+ this._free = block;
1572
+ }
1573
+ this.setBlockNext(block, ptr);
1574
+ }
1575
+ };
1576
+ var __blockDataAddress = (blockAddress) => blockAddress > 0 ? blockAddress + SIZEOF_MEM_BLOCK : 0;
1577
+ var __blockSelfAddress = (dataAddress) => dataAddress > 0 ? dataAddress - SIZEOF_MEM_BLOCK : 0;
1578
+
1579
+ // js/supersonic.js
1580
+ var SuperSonic = class {
1581
+ // Expose OSC utilities as static methods
1582
+ static osc = {
1583
+ encode: (message) => osc_default.writePacket(message),
1584
+ decode: (data, options = { metadata: false }) => osc_default.readPacket(data, options)
1585
+ };
1586
+ constructor(options = {}) {
1587
+ this.initialized = false;
1588
+ this.initializing = false;
1589
+ this.capabilities = {};
1590
+ this.sharedBuffer = null;
1591
+ this.ringBufferBase = null;
1592
+ this.bufferConstants = null;
1593
+ this.audioContext = null;
1594
+ this.workletNode = null;
1595
+ this.osc = null;
1596
+ this.wasmModule = null;
1597
+ this.wasmInstance = null;
1598
+ this.bufferPool = null;
1599
+ this.wasmTimeOffset = null;
1600
+ this._timeOffsetPromise = null;
1601
+ this._resolveTimeOffset = null;
1602
+ this.onMessageReceived = null;
1603
+ this.onMessageSent = null;
1604
+ this.onMetricsUpdate = null;
1605
+ this.onStatusUpdate = null;
1606
+ this.onSendError = null;
1607
+ this.onDebugMessage = null;
1608
+ this.onInitialized = null;
1609
+ this.onError = null;
1610
+ this.config = {
1611
+ wasmUrl: "./dist/wasm/scsynth-nrt.wasm",
1612
+ workletUrl: "./dist/workers/scsynth_audio_worklet.js",
1613
+ development: false,
1614
+ audioContextOptions: {
1615
+ latencyHint: "interactive",
1616
+ sampleRate: 48e3
1617
+ }
1618
+ };
1619
+ this.audioBaseURL = options.audioBaseURL || null;
1620
+ this.audioPathMap = options.audioPathMap || {};
1621
+ this.allocatedBuffers = /* @__PURE__ */ new Map();
1622
+ this.stats = {
1623
+ initStartTime: null,
1624
+ initDuration: null,
1625
+ messagesSent: 0,
1626
+ messagesReceived: 0,
1627
+ errors: 0
1628
+ };
1629
+ }
1630
+ /**
1631
+ * Check browser capabilities for required features
1632
+ */
1633
+ checkCapabilities() {
1634
+ this.capabilities = {
1635
+ audioWorklet: "AudioWorklet" in window,
1636
+ sharedArrayBuffer: typeof SharedArrayBuffer !== "undefined",
1637
+ crossOriginIsolated: window.crossOriginIsolated === true,
1638
+ wasmThreads: typeof WebAssembly !== "undefined" && typeof WebAssembly.Memory !== "undefined" && WebAssembly.Memory.prototype.hasOwnProperty("shared"),
1639
+ atomics: typeof Atomics !== "undefined",
1640
+ webWorker: typeof Worker !== "undefined"
1641
+ };
1642
+ const required = [
1643
+ "audioWorklet",
1644
+ "sharedArrayBuffer",
1645
+ "crossOriginIsolated",
1646
+ "atomics",
1647
+ "webWorker"
1648
+ ];
1649
+ const missing = required.filter((f) => !this.capabilities[f]);
1650
+ if (missing.length > 0) {
1651
+ const error = new Error(`Missing required features: ${missing.join(", ")}`);
1652
+ if (!this.capabilities.crossOriginIsolated) {
1653
+ if (this.capabilities.sharedArrayBuffer) {
1654
+ error.message += "\n\nSharedArrayBuffer is available but cross-origin isolation is not enabled. Please ensure COOP and COEP headers are set correctly:\n Cross-Origin-Opener-Policy: same-origin\n Cross-Origin-Embedder-Policy: require-corp";
1655
+ } else {
1656
+ error.message += "\n\nSharedArrayBuffer is not available. This may be due to:\n1. Missing COOP/COEP headers\n2. Browser doesn't support SharedArrayBuffer\n3. Browser security settings";
1657
+ }
1658
+ }
1659
+ throw error;
1660
+ }
1661
+ return this.capabilities;
1662
+ }
1663
+ /**
1664
+ * Initialize shared WebAssembly memory
1665
+ */
1666
+ #initializeSharedMemory() {
1667
+ const TOTAL_PAGES = 3072;
1668
+ this.wasmMemory = new WebAssembly.Memory({
1669
+ initial: TOTAL_PAGES,
1670
+ maximum: TOTAL_PAGES,
1671
+ shared: true
1672
+ });
1673
+ this.sharedBuffer = this.wasmMemory.buffer;
1674
+ const BUFFER_POOL_OFFSET = 64 * 1024 * 1024;
1675
+ const BUFFER_POOL_SIZE = 128 * 1024 * 1024;
1676
+ this.bufferPool = new MemPool({
1677
+ buf: this.sharedBuffer,
1678
+ start: BUFFER_POOL_OFFSET,
1679
+ size: BUFFER_POOL_SIZE,
1680
+ align: 8
1681
+ // 8-byte alignment (minimum required by MemPool)
1682
+ });
1683
+ console.log("[SuperSonic] Buffer pool initialized: 128MB at offset 64MB");
1684
+ }
1685
+ /**
1686
+ * Calculate time offset (AudioContext → NTP conversion)
1687
+ * Called when AudioContext is in 'running' state to ensure accurate timing
1688
+ */
1689
+ #calculateTimeOffset() {
1690
+ const SECONDS_1900_TO_1970 = 2208988800;
1691
+ const audioContextTime = this.audioContext.currentTime;
1692
+ const unixSeconds = Date.now() / 1e3;
1693
+ this.wasmTimeOffset = SECONDS_1900_TO_1970 + unixSeconds - audioContextTime;
1694
+ if (this._resolveTimeOffset) {
1695
+ this._resolveTimeOffset(this.wasmTimeOffset);
1696
+ this._resolveTimeOffset = null;
1697
+ }
1698
+ return this.wasmTimeOffset;
1699
+ }
1700
+ /**
1701
+ * Initialize AudioContext and set up time offset calculation
1702
+ */
1703
+ #initializeAudioContext() {
1704
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)(
1705
+ this.config.audioContextOptions
1706
+ );
1707
+ this._timeOffsetPromise = new Promise((resolve) => {
1708
+ this._resolveTimeOffset = resolve;
1709
+ });
1710
+ if (this.audioContext.state === "suspended") {
1711
+ const resumeContext = async () => {
1712
+ if (this.audioContext.state === "suspended") {
1713
+ await this.audioContext.resume();
1714
+ }
1715
+ };
1716
+ document.addEventListener("click", resumeContext, { once: true });
1717
+ document.addEventListener("touchstart", resumeContext, { once: true });
1718
+ }
1719
+ this.audioContext.addEventListener("statechange", () => {
1720
+ if (this.audioContext.state === "running" && this._resolveTimeOffset) {
1721
+ this.#calculateTimeOffset();
1722
+ }
1723
+ });
1724
+ if (this.audioContext.state === "running") {
1725
+ this.#calculateTimeOffset();
1726
+ }
1727
+ }
1728
+ /**
1729
+ * Load WASM manifest to get the current hashed filename
1730
+ */
1731
+ async #loadWasmManifest() {
1732
+ try {
1733
+ const manifestUrl = "./dist/wasm/manifest.json";
1734
+ const response = await fetch(manifestUrl);
1735
+ if (response.ok) {
1736
+ const manifest = await response.json();
1737
+ const wasmFile = this.config.development ? manifest.wasmFile : manifest.wasmFileStable;
1738
+ this.config.wasmUrl = `./dist/wasm/${wasmFile}`;
1739
+ console.log(`[SuperSonic] Using WASM build: ${wasmFile}`);
1740
+ console.log(`[SuperSonic] Build: ${manifest.buildId} (git: ${manifest.gitHash})`);
1741
+ }
1742
+ } catch (error) {
1743
+ console.warn("[SuperSonic] WASM manifest not found, using default filename");
1744
+ }
1745
+ }
1746
+ /**
1747
+ * Load WASM binary from network
1748
+ */
1749
+ async #loadWasm() {
1750
+ await this.#loadWasmManifest();
1751
+ const wasmResponse = await fetch(this.config.wasmUrl);
1752
+ if (!wasmResponse.ok) {
1753
+ throw new Error(`Failed to load WASM: ${wasmResponse.status} ${wasmResponse.statusText}`);
1754
+ }
1755
+ return await wasmResponse.arrayBuffer();
1756
+ }
1757
+ /**
1758
+ * Initialize AudioWorklet with WASM
1759
+ */
1760
+ async #initializeAudioWorklet(wasmBytes) {
1761
+ await this.audioContext.audioWorklet.addModule(this.config.workletUrl);
1762
+ this.workletNode = new AudioWorkletNode(this.audioContext, "scsynth-processor", {
1763
+ numberOfInputs: 0,
1764
+ numberOfOutputs: 1,
1765
+ outputChannelCount: [2]
1766
+ });
1767
+ this.workletNode.connect(this.audioContext.destination);
1768
+ this.workletNode.port.postMessage({
1769
+ type: "init",
1770
+ sharedBuffer: this.sharedBuffer
1771
+ });
1772
+ const timeOffset = await this._timeOffsetPromise;
1773
+ this.workletNode.port.postMessage({
1774
+ type: "loadWasm",
1775
+ wasmBytes,
1776
+ wasmMemory: this.wasmMemory,
1777
+ timeOffset
1778
+ });
1779
+ await this.#waitForWorkletInit();
1780
+ }
1781
+ /**
1782
+ * Initialize OSC communication layer
1783
+ */
1784
+ async #initializeOSC() {
1785
+ this.osc = new ScsynthOSC();
1786
+ this.osc.onOSCMessage((msg) => {
1787
+ if (msg.address === "/buffer/freed") {
1788
+ this._handleBufferFreed(msg.args);
1789
+ }
1790
+ if (this.onMessageReceived) {
1791
+ this.stats.messagesReceived++;
1792
+ this.onMessageReceived(msg);
1793
+ }
1794
+ });
1795
+ this.osc.onDebugMessage((msg) => {
1796
+ if (this.onDebugMessage) {
1797
+ this.onDebugMessage(msg);
1798
+ }
1799
+ });
1800
+ this.osc.onError((error, workerName) => {
1801
+ console.error(`[SuperSonic] ${workerName} error:`, error);
1802
+ this.stats.errors++;
1803
+ if (this.onError) {
1804
+ this.onError(new Error(`${workerName}: ${error}`));
1805
+ }
1806
+ });
1807
+ await this.osc.init(this.sharedBuffer, this.ringBufferBase, this.bufferConstants);
1808
+ }
1809
+ /**
1810
+ * Complete initialization and trigger callbacks
1811
+ */
1812
+ #finishInitialization() {
1813
+ this.initialized = true;
1814
+ this.initializing = false;
1815
+ this.stats.initDuration = performance.now() - this.stats.initStartTime;
1816
+ console.log(`[SuperSonic] Initialization complete in ${this.stats.initDuration.toFixed(2)}ms`);
1817
+ if (this.onInitialized) {
1818
+ this.onInitialized({
1819
+ capabilities: this.capabilities,
1820
+ stats: this.stats
1821
+ });
1822
+ }
1823
+ }
1824
+ /**
1825
+ * Initialize the audio worklet system
1826
+ * @param {Object} config - Optional configuration overrides
1827
+ * @param {boolean} config.development - Use cache-busted WASM files (default: false)
1828
+ * @param {string} config.wasmUrl - Custom WASM URL
1829
+ * @param {string} config.workletUrl - Custom worklet URL
1830
+ * @param {Object} config.audioContextOptions - AudioContext options
1831
+ */
1832
+ async init(config = {}) {
1833
+ if (this.initialized) {
1834
+ console.warn("[SuperSonic] Already initialized");
1835
+ return;
1836
+ }
1837
+ if (this.initializing) {
1838
+ console.warn("[SuperSonic] Initialization already in progress");
1839
+ return;
1840
+ }
1841
+ this.config = {
1842
+ ...this.config,
1843
+ ...config,
1844
+ audioContextOptions: {
1845
+ ...this.config.audioContextOptions,
1846
+ ...config.audioContextOptions || {}
1847
+ }
1848
+ };
1849
+ this.initializing = true;
1850
+ this.stats.initStartTime = performance.now();
1851
+ try {
1852
+ this.checkCapabilities();
1853
+ this.#initializeSharedMemory();
1854
+ this.#initializeAudioContext();
1855
+ const wasmBytes = await this.#loadWasm();
1856
+ await this.#initializeAudioWorklet(wasmBytes);
1857
+ await this.#initializeOSC();
1858
+ this.#setupMessageHandlers();
1859
+ this.#startPerformanceMonitoring();
1860
+ this.#finishInitialization();
1861
+ } catch (error) {
1862
+ this.initializing = false;
1863
+ console.error("[SuperSonic] Initialization failed:", error);
1864
+ if (this.onError) {
1865
+ this.onError(error);
1866
+ }
1867
+ throw error;
1868
+ }
1869
+ }
1870
+ /**
1871
+ * Wait for AudioWorklet to initialize
1872
+ */
1873
+ #waitForWorkletInit() {
1874
+ return new Promise((resolve, reject) => {
1875
+ const timeout = setTimeout(() => {
1876
+ reject(new Error("AudioWorklet initialization timeout"));
1877
+ }, 5e3);
1878
+ const messageHandler = (event) => {
1879
+ if (event.data.type === "debug") {
1880
+ return;
1881
+ }
1882
+ if (event.data.type === "error") {
1883
+ console.error("[AudioWorklet] Error:", event.data.error);
1884
+ clearTimeout(timeout);
1885
+ this.workletNode.port.removeEventListener("message", messageHandler);
1886
+ reject(new Error(event.data.error || "AudioWorklet error"));
1887
+ return;
1888
+ }
1889
+ if (event.data.type === "initialized") {
1890
+ clearTimeout(timeout);
1891
+ this.workletNode.port.removeEventListener("message", messageHandler);
1892
+ if (event.data.success) {
1893
+ if (event.data.ringBufferBase !== void 0) {
1894
+ this.ringBufferBase = event.data.ringBufferBase;
1895
+ } else {
1896
+ console.warn("[SuperSonic] Warning: ringBufferBase not provided by worklet");
1897
+ }
1898
+ if (event.data.bufferConstants !== void 0) {
1899
+ this.bufferConstants = event.data.bufferConstants;
1900
+ } else {
1901
+ console.warn("[SuperSonic] Warning: bufferConstants not provided by worklet");
1902
+ }
1903
+ resolve();
1904
+ } else {
1905
+ reject(new Error(event.data.error || "AudioWorklet initialization failed"));
1906
+ }
1907
+ }
1908
+ };
1909
+ this.workletNode.port.addEventListener("message", messageHandler);
1910
+ this.workletNode.port.start();
1911
+ });
1912
+ }
1913
+ /**
1914
+ * Set up message handlers for worklet
1915
+ */
1916
+ #setupMessageHandlers() {
1917
+ this.workletNode.port.onmessage = (event) => {
1918
+ const { data } = event;
1919
+ switch (data.type) {
1920
+ case "status":
1921
+ if (this.onStatusUpdate) {
1922
+ this.onStatusUpdate(data);
1923
+ }
1924
+ break;
1925
+ case "metrics":
1926
+ if (this.onMetricsUpdate) {
1927
+ this.onMetricsUpdate(data.metrics);
1928
+ }
1929
+ break;
1930
+ case "error":
1931
+ console.error("[Worklet] Error:", data.error);
1932
+ if (data.diagnostics) {
1933
+ console.error("[Worklet] Diagnostics:", data.diagnostics);
1934
+ console.table(data.diagnostics);
1935
+ }
1936
+ this.stats.errors++;
1937
+ if (this.onError) {
1938
+ this.onError(new Error(data.error));
1939
+ }
1940
+ break;
1941
+ case "process_debug":
1942
+ break;
1943
+ case "debug":
1944
+ break;
1945
+ case "console":
1946
+ if (this.onConsoleMessage) {
1947
+ this.onConsoleMessage(data.message);
1948
+ }
1949
+ break;
1950
+ case "version":
1951
+ if (this.onVersion) {
1952
+ this.onVersion(data.version);
1953
+ }
1954
+ break;
1955
+ }
1956
+ };
1957
+ }
1958
+ /**
1959
+ * Start performance monitoring
1960
+ */
1961
+ #startPerformanceMonitoring() {
1962
+ setInterval(() => {
1963
+ if (this.osc) {
1964
+ this.osc.getStats().then((stats) => {
1965
+ if (stats && this.onMetricsUpdate) {
1966
+ this.onMetricsUpdate(stats);
1967
+ }
1968
+ });
1969
+ }
1970
+ if (this.workletNode) {
1971
+ this.workletNode.port.postMessage({ type: "getMetrics" });
1972
+ }
1973
+ }, 50);
1974
+ }
1975
+ /**
1976
+ * Send OSC message with simplified syntax (auto-detects types)
1977
+ * @param {string} address - OSC address
1978
+ * @param {...*} args - Arguments (numbers, strings, Uint8Array)
1979
+ * @example
1980
+ * sonic.send('/notify', 1);
1981
+ * sonic.send('/s_new', 'sonic-pi-beep', -1, 0, 0);
1982
+ * sonic.send('/n_set', 1000, 'freq', 440.0, 'amp', 0.5);
1983
+ */
1984
+ async send(address, ...args) {
1985
+ if (!this.initialized) {
1986
+ throw new Error("SuperSonic not initialized. Call init() first.");
1987
+ }
1988
+ if (this._isBufferAllocationCommand(address)) {
1989
+ return await this._handleBufferCommand(address, args);
1990
+ }
1991
+ const oscArgs = args.map((arg) => {
1992
+ if (typeof arg === "string") {
1993
+ return { type: "s", value: arg };
1994
+ } else if (typeof arg === "number") {
1995
+ return { type: Number.isInteger(arg) ? "i" : "f", value: arg };
1996
+ } else if (arg instanceof Uint8Array || arg instanceof ArrayBuffer) {
1997
+ return { type: "b", value: arg instanceof ArrayBuffer ? new Uint8Array(arg) : arg };
1998
+ } else {
1999
+ throw new Error(`Unsupported argument type: ${typeof arg}`);
2000
+ }
2001
+ });
2002
+ const message = {
2003
+ address,
2004
+ args: oscArgs
2005
+ };
2006
+ const oscData = osc_default.writePacket(message);
2007
+ this.sendOSC(oscData);
2008
+ }
2009
+ _isBufferAllocationCommand(address) {
2010
+ return [
2011
+ "/b_allocRead",
2012
+ "/b_allocReadChannel",
2013
+ "/b_read",
2014
+ "/b_readChannel"
2015
+ // NOTE: /b_alloc and /b_free are NOT intercepted
2016
+ ].includes(address);
2017
+ }
2018
+ async _handleBufferCommand(address, args) {
2019
+ switch (address) {
2020
+ case "/b_allocRead":
2021
+ return await this._allocReadBuffer(...args);
2022
+ case "/b_allocReadChannel":
2023
+ return await this._allocReadChannelBuffer(...args);
2024
+ case "/b_read":
2025
+ return await this._readBuffer(...args);
2026
+ case "/b_readChannel":
2027
+ return await this._readChannelBuffer(...args);
2028
+ }
2029
+ }
2030
+ /**
2031
+ * /b_allocRead bufnum path [startFrame numFrames completion]
2032
+ */
2033
+ async _allocReadBuffer(bufnum, path, startFrame = 0, numFrames = 0, completionMsg = null) {
2034
+ let allocatedPtr = null;
2035
+ const GUARD_BEFORE = 3;
2036
+ const GUARD_AFTER = 1;
2037
+ try {
2038
+ const url = this._resolveAudioPath(path);
2039
+ const response = await fetch(url);
2040
+ if (!response.ok) {
2041
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2042
+ }
2043
+ const arrayBuffer = await response.arrayBuffer();
2044
+ const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
2045
+ const actualStartFrame = startFrame || 0;
2046
+ const actualNumFrames = numFrames || audioBuffer.length - actualStartFrame;
2047
+ const framesToRead = Math.min(actualNumFrames, audioBuffer.length - actualStartFrame);
2048
+ if (framesToRead <= 0) {
2049
+ throw new Error(`Invalid frame range: start=${actualStartFrame}, numFrames=${actualNumFrames}, fileLength=${audioBuffer.length}`);
2050
+ }
2051
+ const numChannels = audioBuffer.numberOfChannels;
2052
+ const guardSamples = (GUARD_BEFORE + GUARD_AFTER) * numChannels;
2053
+ const interleavedData = new Float32Array(framesToRead * numChannels + guardSamples);
2054
+ const dataOffset = GUARD_BEFORE * numChannels;
2055
+ for (let frame = 0; frame < framesToRead; frame++) {
2056
+ for (let ch = 0; ch < numChannels; ch++) {
2057
+ const channelData = audioBuffer.getChannelData(ch);
2058
+ interleavedData[dataOffset + frame * numChannels + ch] = channelData[actualStartFrame + frame];
2059
+ }
2060
+ }
2061
+ const bytesNeeded = interleavedData.length * 4;
2062
+ allocatedPtr = this.bufferPool.malloc(bytesNeeded);
2063
+ if (allocatedPtr === 0) {
2064
+ throw new Error("Buffer pool allocation failed (out of memory)");
2065
+ }
2066
+ const wasmHeap = new Float32Array(
2067
+ this.sharedBuffer,
2068
+ allocatedPtr,
2069
+ interleavedData.length
2070
+ );
2071
+ wasmHeap.set(interleavedData);
2072
+ this.allocatedBuffers.set(bufnum, {
2073
+ ptr: allocatedPtr,
2074
+ size: bytesNeeded
2075
+ });
2076
+ await this.send(
2077
+ "/b_allocPtr",
2078
+ bufnum,
2079
+ allocatedPtr,
2080
+ framesToRead,
2081
+ numChannels,
2082
+ audioBuffer.sampleRate
2083
+ );
2084
+ if (completionMsg) {
2085
+ }
2086
+ } catch (error) {
2087
+ if (allocatedPtr) {
2088
+ this.bufferPool.free(allocatedPtr);
2089
+ this.allocatedBuffers.delete(bufnum);
2090
+ }
2091
+ console.error(`[SuperSonic] Buffer ${bufnum} load failed:`, error);
2092
+ throw error;
2093
+ }
2094
+ }
2095
+ /**
2096
+ * Resolve audio file path to full URL
2097
+ */
2098
+ _resolveAudioPath(scPath) {
2099
+ if (this.audioPathMap[scPath]) {
2100
+ return this.audioPathMap[scPath];
2101
+ }
2102
+ if (!this.audioBaseURL) {
2103
+ throw new Error(
2104
+ 'audioBaseURL not configured. Please set it in SuperSonic constructor options.\nExample: new SuperSonic({ audioBaseURL: "https://unpkg.com/supersonic-scsynth-samples-bd@0.1.0/samples/" })\nOr install sample packages: npm install supersonic-scsynth-samples-bd'
2105
+ );
2106
+ }
2107
+ return this.audioBaseURL + scPath;
2108
+ }
2109
+ /**
2110
+ * Handle /buffer/freed message from WASM
2111
+ */
2112
+ _handleBufferFreed(args) {
2113
+ if (args.length < 2) {
2114
+ console.warn("[SuperSonic] Invalid /buffer/freed message:", args);
2115
+ return;
2116
+ }
2117
+ const bufnum = args[0];
2118
+ const offset = args[1];
2119
+ const bufferInfo = this.allocatedBuffers.get(bufnum);
2120
+ if (bufferInfo) {
2121
+ this.bufferPool.free(bufferInfo.ptr);
2122
+ this.allocatedBuffers.delete(bufnum);
2123
+ }
2124
+ }
2125
+ /**
2126
+ * /b_allocReadChannel bufnum path [startFrame numFrames channel1 channel2 ... completion]
2127
+ * Load specific channels from an audio file
2128
+ */
2129
+ async _allocReadChannelBuffer(bufnum, path, startFrame = 0, numFrames = 0, ...channelsAndCompletion) {
2130
+ let allocatedPtr = null;
2131
+ const GUARD_BEFORE = 3;
2132
+ const GUARD_AFTER = 1;
2133
+ try {
2134
+ const channels = [];
2135
+ let completionMsg = null;
2136
+ for (let i = 0; i < channelsAndCompletion.length; i++) {
2137
+ if (typeof channelsAndCompletion[i] === "number" && Number.isInteger(channelsAndCompletion[i])) {
2138
+ channels.push(channelsAndCompletion[i]);
2139
+ } else {
2140
+ completionMsg = channelsAndCompletion[i];
2141
+ break;
2142
+ }
2143
+ }
2144
+ const url = this._resolveAudioPath(path);
2145
+ const response = await fetch(url);
2146
+ if (!response.ok) {
2147
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2148
+ }
2149
+ const arrayBuffer = await response.arrayBuffer();
2150
+ const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
2151
+ const actualStartFrame = startFrame || 0;
2152
+ const actualNumFrames = numFrames || audioBuffer.length - actualStartFrame;
2153
+ const framesToRead = Math.min(actualNumFrames, audioBuffer.length - actualStartFrame);
2154
+ if (framesToRead <= 0) {
2155
+ throw new Error(`Invalid frame range: start=${actualStartFrame}, numFrames=${actualNumFrames}, fileLength=${audioBuffer.length}`);
2156
+ }
2157
+ const fileChannels = audioBuffer.numberOfChannels;
2158
+ const selectedChannels = channels.length > 0 ? channels : Array.from({ length: fileChannels }, (_, i) => i);
2159
+ for (const ch of selectedChannels) {
2160
+ if (ch < 0 || ch >= fileChannels) {
2161
+ throw new Error(`Invalid channel ${ch} (file has ${fileChannels} channels)`);
2162
+ }
2163
+ }
2164
+ const numChannels = selectedChannels.length;
2165
+ const guardSamples = (GUARD_BEFORE + GUARD_AFTER) * numChannels;
2166
+ const interleavedData = new Float32Array(framesToRead * numChannels + guardSamples);
2167
+ const dataOffset = GUARD_BEFORE * numChannels;
2168
+ for (let frame = 0; frame < framesToRead; frame++) {
2169
+ for (let ch = 0; ch < numChannels; ch++) {
2170
+ const fileChannel = selectedChannels[ch];
2171
+ const channelData = audioBuffer.getChannelData(fileChannel);
2172
+ interleavedData[dataOffset + frame * numChannels + ch] = channelData[actualStartFrame + frame];
2173
+ }
2174
+ }
2175
+ const bytesNeeded = interleavedData.length * 4;
2176
+ allocatedPtr = this.bufferPool.malloc(bytesNeeded);
2177
+ if (allocatedPtr === 0) {
2178
+ throw new Error("Buffer pool allocation failed (out of memory)");
2179
+ }
2180
+ const wasmHeap = new Float32Array(this.sharedBuffer, allocatedPtr, interleavedData.length);
2181
+ wasmHeap.set(interleavedData);
2182
+ this.allocatedBuffers.set(bufnum, { ptr: allocatedPtr, size: bytesNeeded });
2183
+ await this.send("/b_allocPtr", bufnum, allocatedPtr, framesToRead, numChannels, audioBuffer.sampleRate);
2184
+ if (completionMsg) {
2185
+ }
2186
+ } catch (error) {
2187
+ if (allocatedPtr) {
2188
+ this.bufferPool.free(allocatedPtr);
2189
+ this.allocatedBuffers.delete(bufnum);
2190
+ }
2191
+ console.error(`[SuperSonic] Buffer ${bufnum} load failed:`, error);
2192
+ throw error;
2193
+ }
2194
+ }
2195
+ /**
2196
+ * /b_read bufnum path [startFrame numFrames bufStartFrame leaveOpen completion]
2197
+ * Read file into existing buffer
2198
+ */
2199
+ async _readBuffer(bufnum, path, startFrame = 0, numFrames = 0, bufStartFrame = 0, leaveOpen = 0, completionMsg = null) {
2200
+ console.warn("[SuperSonic] /b_read requires pre-allocated buffer - not yet implemented");
2201
+ throw new Error("/b_read not yet implemented (requires /b_alloc first)");
2202
+ }
2203
+ /**
2204
+ * /b_readChannel bufnum path [startFrame numFrames bufStartFrame leaveOpen channel1 channel2 ... completion]
2205
+ * Read specific channels into existing buffer
2206
+ */
2207
+ async _readChannelBuffer(bufnum, path, startFrame = 0, numFrames = 0, bufStartFrame = 0, leaveOpen = 0, ...channelsAndCompletion) {
2208
+ console.warn("[SuperSonic] /b_readChannel requires pre-allocated buffer - not yet implemented");
2209
+ throw new Error("/b_readChannel not yet implemented (requires /b_alloc first)");
2210
+ }
2211
+ /**
2212
+ * Send pre-encoded OSC bytes to scsynth
2213
+ * @param {ArrayBuffer|Uint8Array} oscData - Pre-encoded OSC data
2214
+ * @param {Object} options - Send options
2215
+ */
2216
+ sendOSC(oscData, options = {}) {
2217
+ if (!this.initialized) {
2218
+ throw new Error("Not initialized. Call init() first.");
2219
+ }
2220
+ let uint8Data;
2221
+ if (oscData instanceof ArrayBuffer) {
2222
+ uint8Data = new Uint8Array(oscData);
2223
+ } else if (oscData instanceof Uint8Array) {
2224
+ uint8Data = oscData;
2225
+ } else {
2226
+ throw new Error("oscData must be ArrayBuffer or Uint8Array");
2227
+ }
2228
+ this.stats.messagesSent++;
2229
+ if (this.onMessageSent) {
2230
+ this.onMessageSent(uint8Data);
2231
+ }
2232
+ let waitTimeMs = null;
2233
+ if (uint8Data.length >= 16) {
2234
+ const header = String.fromCharCode.apply(null, uint8Data.slice(0, 8));
2235
+ if (header === "#bundle\0") {
2236
+ if (this.wasmTimeOffset === null) {
2237
+ console.warn("[SuperSonic] Time offset not yet calculated, calculating now");
2238
+ this.#calculateTimeOffset();
2239
+ }
2240
+ const view = new DataView(uint8Data.buffer, uint8Data.byteOffset);
2241
+ const ntpSeconds = view.getUint32(8, false);
2242
+ const ntpFraction = view.getUint32(12, false);
2243
+ if (!(ntpSeconds === 0 && (ntpFraction === 0 || ntpFraction === 1))) {
2244
+ const ntpTimeS = ntpSeconds + ntpFraction / 4294967296;
2245
+ const audioTimeS = ntpTimeS - this.wasmTimeOffset;
2246
+ const currentAudioTimeS = this.audioContext.currentTime;
2247
+ const latencyS = 0.05;
2248
+ waitTimeMs = (audioTimeS - currentAudioTimeS - latencyS) * 1e3;
2249
+ }
2250
+ }
2251
+ }
2252
+ this.osc.send(uint8Data, { ...options, waitTimeMs });
2253
+ }
2254
+ /**
2255
+ * Get current status
2256
+ */
2257
+ getStatus() {
2258
+ return {
2259
+ initialized: this.initialized,
2260
+ capabilities: this.capabilities,
2261
+ stats: this.stats,
2262
+ audioContextState: this.audioContext?.state
2263
+ };
2264
+ }
2265
+ /**
2266
+ * Destroy the orchestrator and clean up resources
2267
+ */
2268
+ async destroy() {
2269
+ console.log("[SuperSonic] Destroying...");
2270
+ if (this.osc) {
2271
+ this.osc.terminate();
2272
+ this.osc = null;
2273
+ }
2274
+ if (this.workletNode) {
2275
+ this.workletNode.disconnect();
2276
+ this.workletNode = null;
2277
+ }
2278
+ if (this.audioContext) {
2279
+ await this.audioContext.close();
2280
+ this.audioContext = null;
2281
+ }
2282
+ this.sharedBuffer = null;
2283
+ this.initialized = false;
2284
+ console.log("[SuperSonic] Destroyed");
2285
+ }
2286
+ /**
2287
+ * Load a binary synthdef file and send it to scsynth
2288
+ * @param {string} path - Path or URL to the .scsyndef file
2289
+ * @returns {Promise<void>}
2290
+ * @example
2291
+ * await sonic.loadSynthDef('./extra/synthdefs/sonic-pi-beep.scsyndef');
2292
+ */
2293
+ async loadSynthDef(path) {
2294
+ if (!this.initialized) {
2295
+ throw new Error("SuperSonic not initialized. Call init() first.");
2296
+ }
2297
+ try {
2298
+ const response = await fetch(path);
2299
+ if (!response.ok) {
2300
+ throw new Error(`Failed to load synthdef from ${path}: ${response.status} ${response.statusText}`);
2301
+ }
2302
+ const arrayBuffer = await response.arrayBuffer();
2303
+ const synthdefData = new Uint8Array(arrayBuffer);
2304
+ this.send("/d_recv", synthdefData);
2305
+ console.log(`[SuperSonic] Loaded synthdef from ${path} (${synthdefData.length} bytes)`);
2306
+ } catch (error) {
2307
+ console.error("[SuperSonic] Failed to load synthdef:", error);
2308
+ throw error;
2309
+ }
2310
+ }
2311
+ /**
2312
+ * Load multiple synthdefs from a directory
2313
+ * @param {string[]} names - Array of synthdef names (without .scsyndef extension)
2314
+ * @param {string} baseUrl - Base URL for synthdef files (required)
2315
+ * @returns {Promise<Object>} Map of name -> success/error
2316
+ * @example
2317
+ * const results = await sonic.loadSynthDefs(
2318
+ * ['sonic-pi-beep', 'sonic-pi-tb303'],
2319
+ * 'https://unpkg.com/supersonic-scsynth-synthdefs@0.1.0/synthdefs/'
2320
+ * );
2321
+ */
2322
+ async loadSynthDefs(names, baseUrl) {
2323
+ if (!this.initialized) {
2324
+ throw new Error("SuperSonic not initialized. Call init() first.");
2325
+ }
2326
+ if (!baseUrl) {
2327
+ throw new Error(
2328
+ 'baseUrl is required. Please specify the URL to your synthdefs.\nExample: await sonic.loadSynthDefs(["sonic-pi-beep"], "https://unpkg.com/supersonic-scsynth-synthdefs@0.1.0/synthdefs/")\nOr install: npm install supersonic-scsynth-synthdefs'
2329
+ );
2330
+ }
2331
+ const results = {};
2332
+ await Promise.all(
2333
+ names.map(async (name) => {
2334
+ try {
2335
+ const path = `${baseUrl}${name}.scsyndef`;
2336
+ await this.loadSynthDef(path);
2337
+ results[name] = { success: true };
2338
+ } catch (error) {
2339
+ console.error(`[SuperSonic] Failed to load ${name}:`, error);
2340
+ results[name] = { success: false, error: error.message };
2341
+ }
2342
+ })
2343
+ );
2344
+ const successCount = Object.values(results).filter((r) => r.success).length;
2345
+ console.log(`[SuperSonic] Loaded ${successCount}/${names.length} synthdefs`);
2346
+ return results;
2347
+ }
2348
+ /**
2349
+ * Allocate memory for an audio buffer (includes guard samples)
2350
+ * @param {number} numSamples - Number of Float32 samples to allocate
2351
+ * @returns {number} Byte offset into SharedArrayBuffer, or 0 if allocation failed
2352
+ * @example
2353
+ * const bufferAddr = sonic.allocBuffer(44100); // Allocate 1 second at 44.1kHz
2354
+ */
2355
+ allocBuffer(numSamples) {
2356
+ if (!this.initialized) {
2357
+ throw new Error("SuperSonic not initialized. Call init() first.");
2358
+ }
2359
+ const sizeBytes = numSamples * 4;
2360
+ const addr = this.bufferPool.malloc(sizeBytes);
2361
+ if (addr === 0) {
2362
+ console.error(`[SuperSonic] Buffer allocation failed: ${numSamples} samples (${sizeBytes} bytes)`);
2363
+ }
2364
+ return addr;
2365
+ }
2366
+ /**
2367
+ * Free a previously allocated buffer
2368
+ * @param {number} addr - Buffer address returned by allocBuffer()
2369
+ * @returns {boolean} true if freed successfully
2370
+ * @example
2371
+ * sonic.freeBuffer(bufferAddr);
2372
+ */
2373
+ freeBuffer(addr) {
2374
+ if (!this.initialized) {
2375
+ throw new Error("SuperSonic not initialized. Call init() first.");
2376
+ }
2377
+ return this.bufferPool.free(addr);
2378
+ }
2379
+ /**
2380
+ * Get a Float32Array view of an allocated buffer
2381
+ * @param {number} addr - Buffer address returned by allocBuffer()
2382
+ * @param {number} numSamples - Number of Float32 samples
2383
+ * @returns {Float32Array} Typed array view into the buffer
2384
+ * @example
2385
+ * const view = sonic.getBufferView(bufferAddr, 44100);
2386
+ * view[0] = 1.0; // Write to buffer
2387
+ */
2388
+ getBufferView(addr, numSamples) {
2389
+ if (!this.initialized) {
2390
+ throw new Error("SuperSonic not initialized. Call init() first.");
2391
+ }
2392
+ return new Float32Array(this.sharedBuffer, addr, numSamples);
2393
+ }
2394
+ /**
2395
+ * Get buffer pool statistics
2396
+ * @returns {Object} Stats including total, available, used, etc.
2397
+ * @example
2398
+ * const stats = sonic.getBufferPoolStats();
2399
+ * console.log(`Available: ${stats.available} bytes`);
2400
+ */
2401
+ getBufferPoolStats() {
2402
+ if (!this.initialized) {
2403
+ throw new Error("SuperSonic not initialized. Call init() first.");
2404
+ }
2405
+ return this.bufferPool.stats();
2406
+ }
2407
+ };
2408
+ export {
2409
+ SuperSonic
2410
+ };
2411
+ /*! osc.js 2.4.5, Copyright 2024 Colin Clark | github.com/colinbdclark/osc.js */