supersonic-scsynth 0.6.2 → 0.6.3

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.
@@ -1,3449 +1,21 @@
1
- // js/vendor/osc.js/osc.js
2
- var osc = {};
3
- var osc = osc || {};
4
- (function() {
5
- "use strict";
6
- osc.SECS_70YRS = 2208988800;
7
- osc.TWO_32 = 4294967296;
8
- osc.defaults = {
9
- metadata: false,
10
- unpackSingleArgs: true
11
- };
12
- osc.isCommonJS = typeof module !== "undefined" && module.exports ? true : false;
13
- osc.isNode = osc.isCommonJS && typeof window === "undefined";
14
- osc.isElectron = typeof process !== "undefined" && process.versions && process.versions.electron ? true : false;
15
- osc.isBufferEnv = osc.isNode || osc.isElectron;
16
- osc.isArray = function(obj) {
17
- return obj && Object.prototype.toString.call(obj) === "[object Array]";
18
- };
19
- osc.isTypedArrayView = function(obj) {
20
- return obj.buffer && obj.buffer instanceof ArrayBuffer;
21
- };
22
- osc.isBuffer = function(obj) {
23
- return osc.isBufferEnv && obj instanceof Buffer;
24
- };
25
- osc.Long = typeof Long !== "undefined" ? Long : void 0;
26
- osc.TextDecoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-8") : typeof util !== "undefined" && typeof (util.TextDecoder !== "undefined") ? new util.TextDecoder("utf-8") : void 0;
27
- osc.TextEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder("utf-8") : typeof util !== "undefined" && typeof (util.TextEncoder !== "undefined") ? new util.TextEncoder("utf-8") : void 0;
28
- osc.dataView = function(obj, offset, length) {
29
- if (obj.buffer) {
30
- return new DataView(obj.buffer, offset, length);
31
- }
32
- if (obj instanceof ArrayBuffer) {
33
- return new DataView(obj, offset, length);
34
- }
35
- return new DataView(new Uint8Array(obj), offset, length);
36
- };
37
- osc.byteArray = function(obj) {
38
- if (obj instanceof Uint8Array) {
39
- return obj;
40
- }
41
- var buf = obj.buffer ? obj.buffer : obj;
42
- if (!(buf instanceof ArrayBuffer) && (typeof buf.length === "undefined" || typeof buf === "string")) {
43
- throw new Error("Can't wrap a non-array-like object as Uint8Array. Object was: " + JSON.stringify(obj, null, 2));
44
- }
45
- return new Uint8Array(buf);
46
- };
47
- osc.nativeBuffer = function(obj) {
48
- if (osc.isBufferEnv) {
49
- return osc.isBuffer(obj) ? obj : Buffer.from(obj.buffer ? obj : new Uint8Array(obj));
50
- }
51
- return osc.isTypedArrayView(obj) ? obj : new Uint8Array(obj);
52
- };
53
- osc.copyByteArray = function(source, target, offset) {
54
- if (osc.isTypedArrayView(source) && osc.isTypedArrayView(target)) {
55
- target.set(source, offset);
56
- } else {
57
- var start = offset === void 0 ? 0 : offset, len = Math.min(target.length - offset, source.length);
58
- for (var i = 0, j = start; i < len; i++, j++) {
59
- target[j] = source[i];
60
- }
61
- }
62
- return target;
63
- };
64
- osc.readString = function(dv, offsetState) {
65
- var charCodes = [], idx = offsetState.idx;
66
- for (; idx < dv.byteLength; idx++) {
67
- var charCode = dv.getUint8(idx);
68
- if (charCode !== 0) {
69
- charCodes.push(charCode);
70
- } else {
71
- idx++;
72
- break;
73
- }
74
- }
75
- idx = idx + 3 & ~3;
76
- offsetState.idx = idx;
77
- var decoder = osc.isBufferEnv ? osc.readString.withBuffer : osc.TextDecoder ? osc.readString.withTextDecoder : osc.readString.raw;
78
- return decoder(charCodes);
79
- };
80
- osc.readString.raw = function(charCodes) {
81
- var str = "";
82
- var sliceSize = 1e4;
83
- for (var i = 0; i < charCodes.length; i += sliceSize) {
84
- str += String.fromCharCode.apply(null, charCodes.slice(i, i + sliceSize));
85
- }
86
- return str;
87
- };
88
- osc.readString.withTextDecoder = function(charCodes) {
89
- var data = new Int8Array(charCodes);
90
- return osc.TextDecoder.decode(data);
91
- };
92
- osc.readString.withBuffer = function(charCodes) {
93
- return Buffer.from(charCodes).toString("utf-8");
94
- };
95
- osc.writeString = function(str) {
96
- var encoder = osc.isBufferEnv ? osc.writeString.withBuffer : osc.TextEncoder ? osc.writeString.withTextEncoder : null, terminated = str + "\0", encodedStr;
97
- if (encoder) {
98
- encodedStr = encoder(terminated);
99
- }
100
- var len = encoder ? encodedStr.length : terminated.length, paddedLen = len + 3 & ~3, arr = new Uint8Array(paddedLen);
101
- for (var i = 0; i < len - 1; i++) {
102
- var charCode = encoder ? encodedStr[i] : terminated.charCodeAt(i);
103
- arr[i] = charCode;
104
- }
105
- return arr;
106
- };
107
- osc.writeString.withTextEncoder = function(str) {
108
- return osc.TextEncoder.encode(str);
109
- };
110
- osc.writeString.withBuffer = function(str) {
111
- return Buffer.from(str);
112
- };
113
- osc.readPrimitive = function(dv, readerName, numBytes, offsetState) {
114
- var val = dv[readerName](offsetState.idx, false);
115
- offsetState.idx += numBytes;
116
- return val;
117
- };
118
- osc.writePrimitive = function(val, dv, writerName, numBytes, offset) {
119
- offset = offset === void 0 ? 0 : offset;
120
- var arr;
121
- if (!dv) {
122
- arr = new Uint8Array(numBytes);
123
- dv = new DataView(arr.buffer);
124
- } else {
125
- arr = new Uint8Array(dv.buffer);
126
- }
127
- dv[writerName](offset, val, false);
128
- return arr;
129
- };
130
- osc.readInt32 = function(dv, offsetState) {
131
- return osc.readPrimitive(dv, "getInt32", 4, offsetState);
132
- };
133
- osc.writeInt32 = function(val, dv, offset) {
134
- return osc.writePrimitive(val, dv, "setInt32", 4, offset);
135
- };
136
- osc.readInt64 = function(dv, offsetState) {
137
- var high = osc.readPrimitive(dv, "getInt32", 4, offsetState), low = osc.readPrimitive(dv, "getInt32", 4, offsetState);
138
- if (osc.Long) {
139
- return new osc.Long(low, high);
140
- } else {
141
- return {
142
- high,
143
- low,
144
- unsigned: false
145
- };
146
- }
147
- };
148
- osc.writeInt64 = function(val, dv, offset) {
149
- var arr = new Uint8Array(8);
150
- arr.set(osc.writePrimitive(val.high, dv, "setInt32", 4, offset), 0);
151
- arr.set(osc.writePrimitive(val.low, dv, "setInt32", 4, offset + 4), 4);
152
- return arr;
153
- };
154
- osc.readFloat32 = function(dv, offsetState) {
155
- return osc.readPrimitive(dv, "getFloat32", 4, offsetState);
156
- };
157
- osc.writeFloat32 = function(val, dv, offset) {
158
- return osc.writePrimitive(val, dv, "setFloat32", 4, offset);
159
- };
160
- osc.readFloat64 = function(dv, offsetState) {
161
- return osc.readPrimitive(dv, "getFloat64", 8, offsetState);
162
- };
163
- osc.writeFloat64 = function(val, dv, offset) {
164
- return osc.writePrimitive(val, dv, "setFloat64", 8, offset);
165
- };
166
- osc.readChar32 = function(dv, offsetState) {
167
- var charCode = osc.readPrimitive(dv, "getUint32", 4, offsetState);
168
- return String.fromCharCode(charCode);
169
- };
170
- osc.writeChar32 = function(str, dv, offset) {
171
- var charCode = str.charCodeAt(0);
172
- if (charCode === void 0 || charCode < -1) {
173
- return void 0;
174
- }
175
- return osc.writePrimitive(charCode, dv, "setUint32", 4, offset);
176
- };
177
- osc.readBlob = function(dv, offsetState) {
178
- var len = osc.readInt32(dv, offsetState), paddedLen = len + 3 & ~3, blob = new Uint8Array(dv.buffer, offsetState.idx, len);
179
- offsetState.idx += paddedLen;
180
- return blob;
181
- };
182
- osc.writeBlob = function(data) {
183
- data = osc.byteArray(data);
184
- var len = data.byteLength, paddedLen = len + 3 & ~3, offset = 4, blobLen = paddedLen + offset, arr = new Uint8Array(blobLen), dv = new DataView(arr.buffer);
185
- osc.writeInt32(len, dv);
186
- arr.set(data, offset);
187
- return arr;
188
- };
189
- osc.readMIDIBytes = function(dv, offsetState) {
190
- var midi = new Uint8Array(dv.buffer, offsetState.idx, 4);
191
- offsetState.idx += 4;
192
- return midi;
193
- };
194
- osc.writeMIDIBytes = function(bytes) {
195
- bytes = osc.byteArray(bytes);
196
- var arr = new Uint8Array(4);
197
- arr.set(bytes);
198
- return arr;
199
- };
200
- osc.readColor = function(dv, offsetState) {
201
- var bytes = new Uint8Array(dv.buffer, offsetState.idx, 4), alpha = bytes[3] / 255;
202
- offsetState.idx += 4;
203
- return {
204
- r: bytes[0],
205
- g: bytes[1],
206
- b: bytes[2],
207
- a: alpha
208
- };
209
- };
210
- osc.writeColor = function(color) {
211
- var alpha = Math.round(color.a * 255), arr = new Uint8Array([color.r, color.g, color.b, alpha]);
212
- return arr;
213
- };
214
- osc.readTrue = function() {
215
- return true;
216
- };
217
- osc.readFalse = function() {
218
- return false;
219
- };
220
- osc.readNull = function() {
221
- return null;
222
- };
223
- osc.readImpulse = function() {
224
- return 1;
225
- };
226
- osc.readTimeTag = function(dv, offsetState) {
227
- 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);
228
- return {
229
- raw: [secs1900, frac],
230
- native
231
- };
232
- };
233
- osc.writeTimeTag = function(timeTag) {
234
- var raw = timeTag.raw ? timeTag.raw : osc.jsToNTPTime(timeTag.native), arr = new Uint8Array(8), dv = new DataView(arr.buffer);
235
- osc.writeInt32(raw[0], dv, 0);
236
- osc.writeInt32(raw[1], dv, 4);
237
- return arr;
238
- };
239
- osc.timeTag = function(secs, now) {
240
- secs = secs || 0;
241
- now = now || Date.now();
242
- var nowSecs = now / 1e3, nowWhole = Math.floor(nowSecs), nowFracs = nowSecs - nowWhole, secsWhole = Math.floor(secs), secsFracs = secs - secsWhole, fracs = nowFracs + secsFracs;
243
- if (fracs > 1) {
244
- var fracsWhole = Math.floor(fracs), fracsFracs = fracs - fracsWhole;
245
- secsWhole += fracsWhole;
246
- fracs = fracsFracs;
247
- }
248
- var ntpSecs = nowWhole + secsWhole + osc.SECS_70YRS, ntpFracs = Math.round(osc.TWO_32 * fracs);
249
- return {
250
- raw: [ntpSecs, ntpFracs]
251
- };
252
- };
253
- osc.ntpToJSTime = function(secs1900, frac) {
254
- var secs1970 = secs1900 - osc.SECS_70YRS, decimals = frac / osc.TWO_32, msTime = (secs1970 + decimals) * 1e3;
255
- return msTime;
256
- };
257
- osc.jsToNTPTime = function(jsTime) {
258
- var secs = jsTime / 1e3, secsWhole = Math.floor(secs), secsFrac = secs - secsWhole, ntpSecs = secsWhole + osc.SECS_70YRS, ntpFracs = Math.round(osc.TWO_32 * secsFrac);
259
- return [ntpSecs, ntpFracs];
260
- };
261
- osc.readArguments = function(dv, options, offsetState) {
262
- var typeTagString = osc.readString(dv, offsetState);
263
- if (typeTagString.indexOf(",") !== 0) {
264
- 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);
265
- }
266
- var argTypes = typeTagString.substring(1).split(""), args = [];
267
- osc.readArgumentsIntoArray(args, argTypes, typeTagString, dv, options, offsetState);
268
- return args;
269
- };
270
- osc.readArgument = function(argType, typeTagString, dv, options, offsetState) {
271
- var typeSpec = osc.argumentTypes[argType];
272
- if (!typeSpec) {
273
- throw new Error("'" + argType + "' is not a valid OSC type tag. Type tag string was: " + typeTagString);
274
- }
275
- var argReader = typeSpec.reader, arg = osc[argReader](dv, offsetState);
276
- if (options.metadata) {
277
- arg = {
278
- type: argType,
279
- value: arg
280
- };
281
- }
282
- return arg;
283
- };
284
- osc.readArgumentsIntoArray = function(arr, argTypes, typeTagString, dv, options, offsetState) {
285
- var i = 0;
286
- while (i < argTypes.length) {
287
- var argType = argTypes[i], arg;
288
- if (argType === "[") {
289
- var fromArrayOpen = argTypes.slice(i + 1), endArrayIdx = fromArrayOpen.indexOf("]");
290
- if (endArrayIdx < 0) {
291
- throw new Error("Invalid argument type tag: an open array type tag ('[') was found without a matching close array tag ('[]'). Type tag was: " + typeTagString);
292
- }
293
- var typesInArray = fromArrayOpen.slice(0, endArrayIdx);
294
- arg = osc.readArgumentsIntoArray([], typesInArray, typeTagString, dv, options, offsetState);
295
- i += endArrayIdx + 2;
296
- } else {
297
- arg = osc.readArgument(argType, typeTagString, dv, options, offsetState);
298
- i++;
299
- }
300
- arr.push(arg);
301
- }
302
- return arr;
303
- };
304
- osc.writeArguments = function(args, options) {
305
- var argCollection = osc.collectArguments(args, options);
306
- return osc.joinParts(argCollection);
307
- };
308
- osc.joinParts = function(dataCollection) {
309
- var buf = new Uint8Array(dataCollection.byteLength), parts = dataCollection.parts, offset = 0;
310
- for (var i = 0; i < parts.length; i++) {
311
- var part = parts[i];
312
- osc.copyByteArray(part, buf, offset);
313
- offset += part.length;
314
- }
315
- return buf;
316
- };
317
- osc.addDataPart = function(dataPart, dataCollection) {
318
- dataCollection.parts.push(dataPart);
319
- dataCollection.byteLength += dataPart.length;
320
- };
321
- osc.writeArrayArguments = function(args, dataCollection) {
322
- var typeTag = "[";
323
- for (var i = 0; i < args.length; i++) {
324
- var arg = args[i];
325
- typeTag += osc.writeArgument(arg, dataCollection);
326
- }
327
- typeTag += "]";
328
- return typeTag;
329
- };
330
- osc.writeArgument = function(arg, dataCollection) {
331
- if (osc.isArray(arg)) {
332
- return osc.writeArrayArguments(arg, dataCollection);
333
- }
334
- var type = arg.type, writer = osc.argumentTypes[type].writer;
335
- if (writer) {
336
- var data = osc[writer](arg.value);
337
- osc.addDataPart(data, dataCollection);
338
- }
339
- return arg.type;
340
- };
341
- osc.collectArguments = function(args, options, dataCollection) {
342
- if (!osc.isArray(args)) {
343
- args = typeof args === "undefined" ? [] : [args];
344
- }
345
- dataCollection = dataCollection || {
346
- byteLength: 0,
347
- parts: []
348
- };
349
- if (!options.metadata) {
350
- args = osc.annotateArguments(args);
351
- }
352
- var typeTagString = ",", currPartIdx = dataCollection.parts.length;
353
- for (var i = 0; i < args.length; i++) {
354
- var arg = args[i];
355
- typeTagString += osc.writeArgument(arg, dataCollection);
356
- }
357
- var typeData = osc.writeString(typeTagString);
358
- dataCollection.byteLength += typeData.byteLength;
359
- dataCollection.parts.splice(currPartIdx, 0, typeData);
360
- return dataCollection;
361
- };
362
- osc.readMessage = function(data, options, offsetState) {
363
- options = options || osc.defaults;
364
- var dv = osc.dataView(data, data.byteOffset, data.byteLength);
365
- offsetState = offsetState || {
366
- idx: 0
367
- };
368
- var address = osc.readString(dv, offsetState);
369
- return osc.readMessageContents(address, dv, options, offsetState);
370
- };
371
- osc.readMessageContents = function(address, dv, options, offsetState) {
372
- if (address.indexOf("/") !== 0) {
373
- throw new Error("A malformed OSC address was found while reading an OSC message. String was: " + address);
374
- }
375
- var args = osc.readArguments(dv, options, offsetState);
376
- return {
377
- address,
378
- args: args.length === 1 && options.unpackSingleArgs ? args[0] : args
379
- };
380
- };
381
- osc.collectMessageParts = function(msg, options, dataCollection) {
382
- dataCollection = dataCollection || {
383
- byteLength: 0,
384
- parts: []
385
- };
386
- osc.addDataPart(osc.writeString(msg.address), dataCollection);
387
- return osc.collectArguments(msg.args, options, dataCollection);
388
- };
389
- osc.writeMessage = function(msg, options) {
390
- options = options || osc.defaults;
391
- if (!osc.isValidMessage(msg)) {
392
- throw new Error("An OSC message must contain a valid address. Message was: " + JSON.stringify(msg, null, 2));
393
- }
394
- var msgCollection = osc.collectMessageParts(msg, options);
395
- return osc.joinParts(msgCollection);
396
- };
397
- osc.isValidMessage = function(msg) {
398
- return msg.address && msg.address.indexOf("/") === 0;
399
- };
400
- osc.readBundle = function(dv, options, offsetState) {
401
- return osc.readPacket(dv, options, offsetState);
402
- };
403
- osc.collectBundlePackets = function(bundle, options, dataCollection) {
404
- dataCollection = dataCollection || {
405
- byteLength: 0,
406
- parts: []
407
- };
408
- osc.addDataPart(osc.writeString("#bundle"), dataCollection);
409
- osc.addDataPart(osc.writeTimeTag(bundle.timeTag), dataCollection);
410
- for (var i = 0; i < bundle.packets.length; i++) {
411
- var packet = bundle.packets[i], collector = packet.address ? osc.collectMessageParts : osc.collectBundlePackets, packetCollection = collector(packet, options);
412
- dataCollection.byteLength += packetCollection.byteLength;
413
- osc.addDataPart(osc.writeInt32(packetCollection.byteLength), dataCollection);
414
- dataCollection.parts = dataCollection.parts.concat(packetCollection.parts);
415
- }
416
- return dataCollection;
417
- };
418
- osc.writeBundle = function(bundle, options) {
419
- if (!osc.isValidBundle(bundle)) {
420
- throw new Error("An OSC bundle must contain 'timeTag' and 'packets' properties. Bundle was: " + JSON.stringify(bundle, null, 2));
421
- }
422
- options = options || osc.defaults;
423
- var bundleCollection = osc.collectBundlePackets(bundle, options);
424
- return osc.joinParts(bundleCollection);
425
- };
426
- osc.isValidBundle = function(bundle) {
427
- return bundle.timeTag !== void 0 && bundle.packets !== void 0;
428
- };
429
- osc.readBundleContents = function(dv, options, offsetState, len) {
430
- var timeTag = osc.readTimeTag(dv, offsetState), packets = [];
431
- while (offsetState.idx < len) {
432
- var packetSize = osc.readInt32(dv, offsetState), packetLen = offsetState.idx + packetSize, packet = osc.readPacket(dv, options, offsetState, packetLen);
433
- packets.push(packet);
434
- }
435
- return {
436
- timeTag,
437
- packets
438
- };
439
- };
440
- osc.readPacket = function(data, options, offsetState, len) {
441
- var dv = osc.dataView(data, data.byteOffset, data.byteLength);
442
- len = len === void 0 ? dv.byteLength : len;
443
- offsetState = offsetState || {
444
- idx: 0
445
- };
446
- var header = osc.readString(dv, offsetState), firstChar = header[0];
447
- if (firstChar === "#") {
448
- return osc.readBundleContents(dv, options, offsetState, len);
449
- } else if (firstChar === "/") {
450
- return osc.readMessageContents(header, dv, options, offsetState);
451
- }
452
- throw new Error("The header of an OSC packet didn't contain an OSC address or a #bundle string. Header was: " + header);
453
- };
454
- osc.writePacket = function(packet, options) {
455
- if (osc.isValidMessage(packet)) {
456
- return osc.writeMessage(packet, options);
457
- } else if (osc.isValidBundle(packet)) {
458
- return osc.writeBundle(packet, options);
459
- } else {
460
- throw new Error("The specified packet was not recognized as a valid OSC message or bundle. Packet was: " + JSON.stringify(packet, null, 2));
461
- }
462
- };
463
- osc.argumentTypes = {
464
- i: {
465
- reader: "readInt32",
466
- writer: "writeInt32"
467
- },
468
- h: {
469
- reader: "readInt64",
470
- writer: "writeInt64"
471
- },
472
- f: {
473
- reader: "readFloat32",
474
- writer: "writeFloat32"
475
- },
476
- s: {
477
- reader: "readString",
478
- writer: "writeString"
479
- },
480
- S: {
481
- reader: "readString",
482
- writer: "writeString"
483
- },
484
- b: {
485
- reader: "readBlob",
486
- writer: "writeBlob"
487
- },
488
- t: {
489
- reader: "readTimeTag",
490
- writer: "writeTimeTag"
491
- },
492
- T: {
493
- reader: "readTrue"
494
- },
495
- F: {
496
- reader: "readFalse"
497
- },
498
- N: {
499
- reader: "readNull"
500
- },
501
- I: {
502
- reader: "readImpulse"
503
- },
504
- d: {
505
- reader: "readFloat64",
506
- writer: "writeFloat64"
507
- },
508
- c: {
509
- reader: "readChar32",
510
- writer: "writeChar32"
511
- },
512
- r: {
513
- reader: "readColor",
514
- writer: "writeColor"
515
- },
516
- m: {
517
- reader: "readMIDIBytes",
518
- writer: "writeMIDIBytes"
519
- }
520
- // [] are special cased within read/writeArguments()
521
- };
522
- osc.inferTypeForArgument = function(arg) {
523
- var type = typeof arg;
524
- switch (type) {
525
- case "boolean":
526
- return arg ? "T" : "F";
527
- case "string":
528
- return "s";
529
- case "number":
530
- return "f";
531
- case "undefined":
532
- return "N";
533
- case "object":
534
- if (arg === null) {
535
- return "N";
536
- } else if (arg instanceof Uint8Array || arg instanceof ArrayBuffer) {
537
- return "b";
538
- } else if (typeof arg.high === "number" && typeof arg.low === "number") {
539
- return "h";
540
- }
541
- break;
542
- }
543
- throw new Error("Can't infer OSC argument type for value: " + JSON.stringify(arg, null, 2));
544
- };
545
- osc.annotateArguments = function(args) {
546
- var annotated = [];
547
- for (var i = 0; i < args.length; i++) {
548
- var arg = args[i], msgArg;
549
- if (typeof arg === "object" && arg.type && arg.value !== void 0) {
550
- msgArg = arg;
551
- } else if (osc.isArray(arg)) {
552
- msgArg = osc.annotateArguments(arg);
553
- } else {
554
- var oscType = osc.inferTypeForArgument(arg);
555
- msgArg = {
556
- type: oscType,
557
- value: arg
558
- };
559
- }
560
- annotated.push(msgArg);
561
- }
562
- return annotated;
563
- };
564
- ;
565
- })();
566
- var EventEmitter = function() {
567
- };
568
- EventEmitter.prototype.on = function() {
569
- };
570
- EventEmitter.prototype.emit = function() {
571
- };
572
- EventEmitter.prototype.removeListener = function() {
573
- };
574
- (function() {
575
- "use strict";
576
- osc.supportsSerial = false;
577
- osc.firePacketEvents = function(port, packet, timeTag, packetInfo) {
578
- if (packet.address) {
579
- port.emit("message", packet, timeTag, packetInfo);
580
- } else {
581
- osc.fireBundleEvents(port, packet, timeTag, packetInfo);
582
- }
583
- };
584
- osc.fireBundleEvents = function(port, bundle, timeTag, packetInfo) {
585
- port.emit("bundle", bundle, timeTag, packetInfo);
586
- for (var i = 0; i < bundle.packets.length; i++) {
587
- var packet = bundle.packets[i];
588
- osc.firePacketEvents(port, packet, bundle.timeTag, packetInfo);
589
- }
590
- };
591
- osc.fireClosedPortSendError = function(port, msg) {
592
- msg = msg || "Can't send packets on a closed osc.Port object. Please open (or reopen) this Port by calling open().";
593
- port.emit("error", msg);
594
- };
595
- osc.Port = function(options) {
596
- this.options = options || {};
597
- this.on("data", this.decodeOSC.bind(this));
598
- };
599
- var p = osc.Port.prototype = Object.create(EventEmitter.prototype);
600
- p.constructor = osc.Port;
601
- p.send = function(oscPacket) {
602
- var args = Array.prototype.slice.call(arguments), encoded = this.encodeOSC(oscPacket), buf = osc.nativeBuffer(encoded);
603
- args[0] = buf;
604
- this.sendRaw.apply(this, args);
605
- };
606
- p.encodeOSC = function(packet) {
607
- packet = packet.buffer ? packet.buffer : packet;
608
- var encoded;
609
- try {
610
- encoded = osc.writePacket(packet, this.options);
611
- } catch (err) {
612
- this.emit("error", err);
613
- }
614
- return encoded;
615
- };
616
- p.decodeOSC = function(data, packetInfo) {
617
- data = osc.byteArray(data);
618
- this.emit("raw", data, packetInfo);
619
- try {
620
- var packet = osc.readPacket(data, this.options);
621
- this.emit("osc", packet, packetInfo);
622
- osc.firePacketEvents(this, packet, void 0, packetInfo);
623
- } catch (err) {
624
- this.emit("error", err);
625
- }
626
- };
627
- osc.SLIPPort = function(options) {
628
- var that = this;
629
- var o = this.options = options || {};
630
- o.useSLIP = o.useSLIP === void 0 ? true : o.useSLIP;
631
- this.decoder = new slip.Decoder({
632
- onMessage: this.decodeOSC.bind(this),
633
- onError: function(err) {
634
- that.emit("error", err);
635
- }
636
- });
637
- var decodeHandler = o.useSLIP ? this.decodeSLIPData : this.decodeOSC;
638
- this.on("data", decodeHandler.bind(this));
639
- };
640
- p = osc.SLIPPort.prototype = Object.create(osc.Port.prototype);
641
- p.constructor = osc.SLIPPort;
642
- p.encodeOSC = function(packet) {
643
- packet = packet.buffer ? packet.buffer : packet;
644
- var framed;
645
- try {
646
- var encoded = osc.writePacket(packet, this.options);
647
- framed = slip.encode(encoded);
648
- } catch (err) {
649
- this.emit("error", err);
650
- }
651
- return framed;
652
- };
653
- p.decodeSLIPData = function(data, packetInfo) {
654
- this.decoder.decode(data, packetInfo);
655
- };
656
- osc.relay = function(from, to, eventName, sendFnName, transformFn, sendArgs) {
657
- eventName = eventName || "message";
658
- sendFnName = sendFnName || "send";
659
- transformFn = transformFn || function() {
660
- };
661
- sendArgs = sendArgs ? [null].concat(sendArgs) : [];
662
- var listener = function(data) {
663
- sendArgs[0] = data;
664
- data = transformFn(data);
665
- to[sendFnName].apply(to, sendArgs);
666
- };
667
- from.on(eventName, listener);
668
- return {
669
- eventName,
670
- listener
671
- };
672
- };
673
- osc.relayPorts = function(from, to, o) {
674
- var eventName = o.raw ? "raw" : "osc", sendFnName = o.raw ? "sendRaw" : "send";
675
- return osc.relay(from, to, eventName, sendFnName, o.transform);
676
- };
677
- osc.stopRelaying = function(from, relaySpec) {
678
- from.removeListener(relaySpec.eventName, relaySpec.listener);
679
- };
680
- osc.Relay = function(port1, port2, options) {
681
- var o = this.options = options || {};
682
- o.raw = false;
683
- this.port1 = port1;
684
- this.port2 = port2;
685
- this.listen();
686
- };
687
- p = osc.Relay.prototype = Object.create(EventEmitter.prototype);
688
- p.constructor = osc.Relay;
689
- p.open = function() {
690
- this.port1.open();
691
- this.port2.open();
692
- };
693
- p.listen = function() {
694
- if (this.port1Spec && this.port2Spec) {
695
- this.close();
696
- }
697
- this.port1Spec = osc.relayPorts(this.port1, this.port2, this.options);
698
- this.port2Spec = osc.relayPorts(this.port2, this.port1, this.options);
699
- var closeListener = this.close.bind(this);
700
- this.port1.on("close", closeListener);
701
- this.port2.on("close", closeListener);
702
- };
703
- p.close = function() {
704
- osc.stopRelaying(this.port1, this.port1Spec);
705
- osc.stopRelaying(this.port2, this.port2Spec);
706
- this.emit("close", this.port1, this.port2);
707
- };
708
- })();
709
- (function() {
710
- "use strict";
711
- osc.WebSocket = typeof WebSocket !== "undefined" ? WebSocket : void 0;
712
- osc.WebSocketPort = function(options) {
713
- osc.Port.call(this, options);
714
- this.on("open", this.listen.bind(this));
715
- this.socket = options.socket;
716
- if (this.socket) {
717
- if (this.socket.readyState === 1) {
718
- osc.WebSocketPort.setupSocketForBinary(this.socket);
719
- this.emit("open", this.socket);
720
- } else {
721
- this.open();
722
- }
723
- }
724
- };
725
- var p = osc.WebSocketPort.prototype = Object.create(osc.Port.prototype);
726
- p.constructor = osc.WebSocketPort;
727
- p.open = function() {
728
- if (!this.socket || this.socket.readyState > 1) {
729
- this.socket = new osc.WebSocket(this.options.url);
730
- }
731
- osc.WebSocketPort.setupSocketForBinary(this.socket);
732
- var that = this;
733
- this.socket.onopen = function() {
734
- that.emit("open", that.socket);
735
- };
736
- this.socket.onerror = function(err) {
737
- that.emit("error", err);
738
- };
739
- };
740
- p.listen = function() {
741
- var that = this;
742
- this.socket.onmessage = function(e) {
743
- that.emit("data", e.data, e);
744
- };
745
- this.socket.onclose = function(e) {
746
- that.emit("close", e);
747
- };
748
- that.emit("ready");
749
- };
750
- p.sendRaw = function(encoded) {
751
- if (!this.socket || this.socket.readyState !== 1) {
752
- osc.fireClosedPortSendError(this);
753
- return;
754
- }
755
- this.socket.send(encoded);
756
- };
757
- p.close = function(code, reason) {
758
- this.socket.close(code, reason);
759
- };
760
- osc.WebSocketPort.setupSocketForBinary = function(socket) {
761
- socket.binaryType = osc.isNode ? "nodebuffer" : "arraybuffer";
762
- };
763
- })();
764
- var osc_default = osc;
765
- var { readPacket, writePacket, readMessage, writeMessage, readBundle, writeBundle } = osc;
766
-
767
- // js/lib/scsynth_osc.js
768
- var ScsynthOSC = class {
769
- constructor(workerBaseURL = null) {
770
- this.workerBaseURL = workerBaseURL;
771
- this.workers = {
772
- oscOut: null,
773
- // Scheduler worker (now also writes directly to ring buffer)
774
- oscIn: null,
775
- debug: null
776
- };
777
- this.callbacks = {
778
- onRawOSC: null,
779
- // Raw binary OSC callback
780
- onParsedOSC: null,
781
- // Parsed OSC callback
782
- onDebugMessage: null,
783
- onError: null,
784
- onInitialized: null
785
- };
786
- this.initialized = false;
787
- this.sharedBuffer = null;
788
- this.ringBufferBase = null;
789
- this.bufferConstants = null;
790
- }
791
- /**
792
- * Initialize all workers with SharedArrayBuffer
793
- */
794
- async init(sharedBuffer, ringBufferBase, bufferConstants) {
795
- if (this.initialized) {
796
- console.warn("[ScsynthOSC] Already initialized");
797
- return;
798
- }
799
- this.sharedBuffer = sharedBuffer;
800
- this.ringBufferBase = ringBufferBase;
801
- this.bufferConstants = bufferConstants;
802
- try {
803
- this.workers.oscOut = new Worker(this.workerBaseURL + "osc_out_prescheduler_worker.js", { type: "module" });
804
- this.workers.oscIn = new Worker(this.workerBaseURL + "osc_in_worker.js", { type: "module" });
805
- this.workers.debug = new Worker(this.workerBaseURL + "debug_worker.js", { type: "module" });
806
- this.setupWorkerHandlers();
807
- const initPromises = [
808
- this.initWorker(this.workers.oscOut, "OSC SCHEDULER+WRITER"),
809
- this.initWorker(this.workers.oscIn, "OSC IN"),
810
- this.initWorker(this.workers.debug, "DEBUG")
811
- ];
812
- await Promise.all(initPromises);
813
- this.workers.oscIn.postMessage({ type: "start" });
814
- this.workers.debug.postMessage({ type: "start" });
815
- this.initialized = true;
816
- if (this.callbacks.onInitialized) {
817
- this.callbacks.onInitialized();
818
- }
819
- } catch (error) {
820
- console.error("[ScsynthOSC] Initialization failed:", error);
821
- if (this.callbacks.onError) {
822
- this.callbacks.onError(error);
823
- }
824
- throw error;
825
- }
826
- }
827
- /**
828
- * Initialize a single worker
829
- */
830
- initWorker(worker, name) {
831
- return new Promise((resolve, reject) => {
832
- const timeout = setTimeout(() => {
833
- reject(new Error(`${name} worker initialization timeout`));
834
- }, 5e3);
835
- const handler = (event) => {
836
- if (event.data.type === "initialized") {
837
- clearTimeout(timeout);
838
- worker.removeEventListener("message", handler);
839
- resolve();
840
- }
841
- };
842
- worker.addEventListener("message", handler);
843
- worker.postMessage({
844
- type: "init",
845
- sharedBuffer: this.sharedBuffer,
846
- ringBufferBase: this.ringBufferBase,
847
- bufferConstants: this.bufferConstants
848
- });
849
- });
850
- }
851
- /**
852
- * Set up message handlers for all workers
853
- */
854
- setupWorkerHandlers() {
855
- this.workers.oscIn.onmessage = (event) => {
856
- const data = event.data;
857
- switch (data.type) {
858
- case "messages":
859
- data.messages.forEach((msg) => {
860
- if (!msg.oscData) return;
861
- if (this.callbacks.onRawOSC) {
862
- this.callbacks.onRawOSC({
863
- oscData: msg.oscData,
864
- sequence: msg.sequence
865
- });
866
- }
867
- if (this.callbacks.onParsedOSC) {
868
- try {
869
- const options = { metadata: false, unpackSingleArgs: false };
870
- const decoded = osc_default.readPacket(msg.oscData, options);
871
- this.callbacks.onParsedOSC(decoded);
872
- } catch (e) {
873
- console.error("[ScsynthOSC] Failed to decode OSC message:", e, msg);
874
- }
875
- }
876
- });
877
- break;
878
- case "error":
879
- console.error("[ScsynthOSC] OSC IN error:", data.error);
880
- if (this.callbacks.onError) {
881
- this.callbacks.onError(data.error, "oscIn");
882
- }
883
- break;
884
- }
885
- };
886
- this.workers.debug.onmessage = (event) => {
887
- const data = event.data;
888
- switch (data.type) {
889
- case "debug":
890
- if (this.callbacks.onDebugMessage) {
891
- data.messages.forEach((msg) => {
892
- this.callbacks.onDebugMessage(msg);
893
- });
894
- }
895
- break;
896
- case "error":
897
- console.error("[ScsynthOSC] DEBUG error:", data.error);
898
- if (this.callbacks.onError) {
899
- this.callbacks.onError(data.error, "debug");
900
- }
901
- break;
902
- }
903
- };
904
- this.workers.oscOut.onmessage = (event) => {
905
- const data = event.data;
906
- switch (data.type) {
907
- case "error":
908
- console.error("[ScsynthOSC] OSC OUT error:", data.error);
909
- if (this.callbacks.onError) {
910
- this.callbacks.onError(data.error, "oscOut");
911
- }
912
- break;
913
- }
914
- };
915
- }
916
- /**
917
- * Send OSC data (message or bundle)
918
- * - OSC messages are sent immediately
919
- * - OSC bundles are scheduled based on audioTimeS (target audio time)
920
- *
921
- * @param {Uint8Array} oscData - Binary OSC data (message or bundle)
922
- * @param {Object} options - Optional metadata (editorId, runTag, audioTimeS, currentTimeS)
923
- */
924
- send(oscData, options = {}) {
925
- if (!this.initialized) {
926
- console.error("[ScsynthOSC] Not initialized");
927
- return;
928
- }
929
- const { editorId = 0, runTag = "", audioTimeS = null, currentTimeS = null } = options;
930
- this.workers.oscOut.postMessage({
931
- type: "send",
932
- oscData,
933
- editorId,
934
- runTag,
935
- audioTimeS,
936
- currentTimeS
937
- });
938
- }
939
- /**
940
- * Send OSC data immediately, ignoring any bundle timestamps
941
- * - Extracts all messages from bundles
942
- * - Sends all messages immediately to scsynth
943
- * - For applications that don't expect server-side scheduling
944
- *
945
- * @param {Uint8Array} oscData - Binary OSC data (message or bundle)
946
- */
947
- sendImmediate(oscData) {
948
- if (!this.initialized) {
949
- console.error("[ScsynthOSC] Not initialized");
950
- return;
951
- }
952
- this.workers.oscOut.postMessage({
953
- type: "sendImmediate",
954
- oscData
955
- });
956
- }
957
- /**
958
- * Cancel scheduled OSC bundles by editor and tag
959
- */
960
- cancelEditorTag(editorId, runTag) {
961
- if (!this.initialized) return;
962
- this.workers.oscOut.postMessage({
963
- type: "cancelEditorTag",
964
- editorId,
965
- runTag
966
- });
967
- }
968
- /**
969
- * Cancel all scheduled OSC bundles from an editor
970
- */
971
- cancelEditor(editorId) {
972
- if (!this.initialized) return;
973
- this.workers.oscOut.postMessage({
974
- type: "cancelEditor",
975
- editorId
976
- });
977
- }
978
- /**
979
- * Cancel all scheduled OSC bundles
980
- */
981
- cancelAll() {
982
- if (!this.initialized) return;
983
- this.workers.oscOut.postMessage({
984
- type: "cancelAll"
985
- });
986
- }
987
- /**
988
- * Clear debug buffer
989
- */
990
- clearDebug() {
991
- if (!this.initialized) return;
992
- this.workers.debug.postMessage({
993
- type: "clear"
994
- });
995
- }
996
- /**
997
- * Set callback for raw binary OSC messages received from scsynth
998
- */
999
- onRawOSC(callback) {
1000
- this.callbacks.onRawOSC = callback;
1001
- }
1002
- /**
1003
- * Set callback for parsed OSC messages received from scsynth
1004
- */
1005
- onParsedOSC(callback) {
1006
- this.callbacks.onParsedOSC = callback;
1007
- }
1008
- /**
1009
- * Set callback for debug messages
1010
- */
1011
- onDebugMessage(callback) {
1012
- this.callbacks.onDebugMessage = callback;
1013
- }
1014
- /**
1015
- * Set callback for errors
1016
- */
1017
- onError(callback) {
1018
- this.callbacks.onError = callback;
1019
- }
1020
- /**
1021
- * Set callback for initialization complete
1022
- */
1023
- onInitialized(callback) {
1024
- this.callbacks.onInitialized = callback;
1025
- }
1026
- /**
1027
- * Terminate all workers and cleanup
1028
- */
1029
- terminate() {
1030
- if (this.workers.oscOut) {
1031
- this.workers.oscOut.postMessage({ type: "stop" });
1032
- this.workers.oscOut.terminate();
1033
- }
1034
- if (this.workers.oscIn) {
1035
- this.workers.oscIn.postMessage({ type: "stop" });
1036
- this.workers.oscIn.terminate();
1037
- }
1038
- if (this.workers.debug) {
1039
- this.workers.debug.postMessage({ type: "stop" });
1040
- this.workers.debug.terminate();
1041
- }
1042
- this.workers = {
1043
- oscOut: null,
1044
- oscIn: null,
1045
- debug: null
1046
- };
1047
- this.initialized = false;
1048
- console.log("[ScsynthOSC] All workers terminated");
1049
- }
1050
- };
1051
-
1052
- // node_modules/@thi.ng/api/typedarray.js
1053
- var GL2TYPE = {
1054
- [
1055
- 5120
1056
- /* I8 */
1057
- ]: "i8",
1058
- [
1059
- 5121
1060
- /* U8 */
1061
- ]: "u8",
1062
- [
1063
- 5122
1064
- /* I16 */
1065
- ]: "i16",
1066
- [
1067
- 5123
1068
- /* U16 */
1069
- ]: "u16",
1070
- [
1071
- 5124
1072
- /* I32 */
1073
- ]: "i32",
1074
- [
1075
- 5125
1076
- /* U32 */
1077
- ]: "u32",
1078
- [
1079
- 5126
1080
- /* F32 */
1081
- ]: "f32"
1082
- };
1083
- var SIZEOF = {
1084
- u8: 1,
1085
- u8c: 1,
1086
- i8: 1,
1087
- u16: 2,
1088
- i16: 2,
1089
- u32: 4,
1090
- i32: 4,
1091
- i64: 8,
1092
- u64: 8,
1093
- f32: 4,
1094
- f64: 8
1095
- };
1096
- var FLOAT_ARRAY_CTORS = {
1097
- f32: Float32Array,
1098
- f64: Float64Array
1099
- };
1100
- var INT_ARRAY_CTORS = {
1101
- i8: Int8Array,
1102
- i16: Int16Array,
1103
- i32: Int32Array
1104
- };
1105
- var UINT_ARRAY_CTORS = {
1106
- u8: Uint8Array,
1107
- u8c: Uint8ClampedArray,
1108
- u16: Uint16Array,
1109
- u32: Uint32Array
1110
- };
1111
- var BIGINT_ARRAY_CTORS = {
1112
- i64: BigInt64Array,
1113
- u64: BigUint64Array
1114
- };
1115
- var TYPEDARRAY_CTORS = {
1116
- ...FLOAT_ARRAY_CTORS,
1117
- ...INT_ARRAY_CTORS,
1118
- ...UINT_ARRAY_CTORS
1119
- };
1120
- var asNativeType = (type) => {
1121
- const t = GL2TYPE[type];
1122
- return t !== void 0 ? t : type;
1123
- };
1124
- function typedArray(type, ...args) {
1125
- const ctor = BIGINT_ARRAY_CTORS[type];
1126
- return new (ctor || TYPEDARRAY_CTORS[asNativeType(type)])(...args);
1127
- }
1128
-
1129
- // node_modules/@thi.ng/binary/align.js
1130
- var align = (addr, size) => (size--, addr + size & ~size);
1131
-
1132
- // node_modules/@thi.ng/checks/is-number.js
1133
- var isNumber = (x) => typeof x === "number";
1134
-
1135
- // node_modules/@thi.ng/errors/deferror.js
1136
- var defError = (prefix, suffix = (msg) => msg !== void 0 ? ": " + msg : "") => class extends Error {
1137
- origMessage;
1138
- constructor(msg) {
1139
- super(prefix(msg) + suffix(msg));
1140
- this.origMessage = msg !== void 0 ? String(msg) : "";
1141
- }
1142
- };
1143
-
1144
- // node_modules/@thi.ng/errors/assert.js
1145
- var AssertionError = defError(() => "Assertion failed");
1146
- 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) => {
1147
- if (typeof test === "function" && !test() || !test) {
1148
- throw new AssertionError(
1149
- typeof msg === "function" ? msg() : msg
1150
- );
1151
- }
1152
- } : () => {
1153
- };
1154
-
1155
- // node_modules/@thi.ng/errors/illegal-arguments.js
1156
- var IllegalArgumentError = defError(() => "illegal argument(s)");
1157
- var illegalArgs = (msg) => {
1158
- throw new IllegalArgumentError(msg);
1159
- };
1160
-
1161
- // node_modules/@thi.ng/malloc/pool.js
1162
- var STATE_FREE = 0;
1163
- var STATE_USED = 1;
1164
- var STATE_TOP = 2;
1165
- var STATE_END = 3;
1166
- var STATE_ALIGN = 4;
1167
- var STATE_FLAGS = 5;
1168
- var STATE_MIN_SPLIT = 6;
1169
- var MASK_COMPACT = 1;
1170
- var MASK_SPLIT = 2;
1171
- var SIZEOF_STATE = 7 * 4;
1172
- var MEM_BLOCK_SIZE = 0;
1173
- var MEM_BLOCK_NEXT = 1;
1174
- var SIZEOF_MEM_BLOCK = 2 * 4;
1175
- var MemPool = class {
1176
- buf;
1177
- start;
1178
- u8;
1179
- u32;
1180
- state;
1181
- constructor(opts = {}) {
1182
- this.buf = opts.buf ? opts.buf : new ArrayBuffer(opts.size || 4096);
1183
- this.start = opts.start != null ? align(Math.max(opts.start, 0), 4) : 0;
1184
- this.u8 = new Uint8Array(this.buf);
1185
- this.u32 = new Uint32Array(this.buf);
1186
- this.state = new Uint32Array(this.buf, this.start, SIZEOF_STATE / 4);
1187
- if (!opts.skipInitialization) {
1188
- const _align = opts.align || 8;
1189
- assert(
1190
- _align >= 8,
1191
- `invalid alignment: ${_align}, must be a pow2 and >= 8`
1192
- );
1193
- const top = this.initialTop(_align);
1194
- const resolvedEnd = opts.end != null ? Math.min(opts.end, this.buf.byteLength) : this.buf.byteLength;
1195
- if (top >= resolvedEnd) {
1196
- illegalArgs(
1197
- `insufficient address range (0x${this.start.toString(
1198
- 16
1199
- )} - 0x${resolvedEnd.toString(16)})`
1200
- );
1201
- }
1202
- this.align = _align;
1203
- this.doCompact = opts.compact !== false;
1204
- this.doSplit = opts.split !== false;
1205
- this.minSplit = opts.minSplit || 16;
1206
- this.end = resolvedEnd;
1207
- this.top = top;
1208
- this._free = 0;
1209
- this._used = 0;
1210
- }
1211
- }
1212
- stats() {
1213
- const listStats = (block) => {
1214
- let count = 0;
1215
- let size = 0;
1216
- while (block) {
1217
- count++;
1218
- size += this.blockSize(block);
1219
- block = this.blockNext(block);
1220
- }
1221
- return { count, size };
1222
- };
1223
- const free = listStats(this._free);
1224
- return {
1225
- free,
1226
- used: listStats(this._used),
1227
- top: this.top,
1228
- available: this.end - this.top + free.size,
1229
- total: this.buf.byteLength
1230
- };
1231
- }
1232
- callocAs(type, num, fill = 0) {
1233
- const block = this.mallocAs(type, num);
1234
- block?.fill(fill);
1235
- return block;
1236
- }
1237
- mallocAs(type, num) {
1238
- const addr = this.malloc(num * SIZEOF[type]);
1239
- return addr ? typedArray(type, this.buf, addr, num) : void 0;
1240
- }
1241
- calloc(bytes, fill = 0) {
1242
- const addr = this.malloc(bytes);
1243
- addr && this.u8.fill(fill, addr, addr + bytes);
1244
- return addr;
1245
- }
1246
- malloc(bytes) {
1247
- if (bytes <= 0) {
1248
- return 0;
1249
- }
1250
- const paddedSize = align(bytes + SIZEOF_MEM_BLOCK, this.align);
1251
- const end = this.end;
1252
- let top = this.top;
1253
- let block = this._free;
1254
- let prev = 0;
1255
- while (block) {
1256
- const blockSize = this.blockSize(block);
1257
- const isTop = block + blockSize >= top;
1258
- if (isTop || blockSize >= paddedSize) {
1259
- return this.mallocTop(
1260
- block,
1261
- prev,
1262
- blockSize,
1263
- paddedSize,
1264
- isTop
1265
- );
1266
- }
1267
- prev = block;
1268
- block = this.blockNext(block);
1269
- }
1270
- block = top;
1271
- top = block + paddedSize;
1272
- if (top <= end) {
1273
- this.initBlock(block, paddedSize, this._used);
1274
- this._used = block;
1275
- this.top = top;
1276
- return __blockDataAddress(block);
1277
- }
1278
- return 0;
1279
- }
1280
- mallocTop(block, prev, blockSize, paddedSize, isTop) {
1281
- if (isTop && block + paddedSize > this.end) return 0;
1282
- if (prev) {
1283
- this.unlinkBlock(prev, block);
1284
- } else {
1285
- this._free = this.blockNext(block);
1286
- }
1287
- this.setBlockNext(block, this._used);
1288
- this._used = block;
1289
- if (isTop) {
1290
- this.top = block + this.setBlockSize(block, paddedSize);
1291
- } else if (this.doSplit) {
1292
- const excess = blockSize - paddedSize;
1293
- excess >= this.minSplit && this.splitBlock(block, paddedSize, excess);
1294
- }
1295
- return __blockDataAddress(block);
1296
- }
1297
- realloc(ptr, bytes) {
1298
- if (bytes <= 0) {
1299
- return 0;
1300
- }
1301
- const oldAddr = __blockSelfAddress(ptr);
1302
- let newAddr = 0;
1303
- let block = this._used;
1304
- let blockEnd = 0;
1305
- while (block) {
1306
- if (block === oldAddr) {
1307
- [newAddr, blockEnd] = this.reallocBlock(block, bytes);
1308
- break;
1309
- }
1310
- block = this.blockNext(block);
1311
- }
1312
- if (newAddr && newAddr !== oldAddr) {
1313
- this.u8.copyWithin(
1314
- __blockDataAddress(newAddr),
1315
- __blockDataAddress(oldAddr),
1316
- blockEnd
1317
- );
1318
- }
1319
- return __blockDataAddress(newAddr);
1320
- }
1321
- reallocBlock(block, bytes) {
1322
- const blockSize = this.blockSize(block);
1323
- const blockEnd = block + blockSize;
1324
- const isTop = blockEnd >= this.top;
1325
- const paddedSize = align(bytes + SIZEOF_MEM_BLOCK, this.align);
1326
- if (paddedSize <= blockSize) {
1327
- if (this.doSplit) {
1328
- const excess = blockSize - paddedSize;
1329
- if (excess >= this.minSplit) {
1330
- this.splitBlock(block, paddedSize, excess);
1331
- } else if (isTop) {
1332
- this.top = block + paddedSize;
1333
- }
1334
- } else if (isTop) {
1335
- this.top = block + paddedSize;
1336
- }
1337
- return [block, blockEnd];
1338
- }
1339
- if (isTop && block + paddedSize < this.end) {
1340
- this.top = block + this.setBlockSize(block, paddedSize);
1341
- return [block, blockEnd];
1342
- }
1343
- this.free(block);
1344
- return [__blockSelfAddress(this.malloc(bytes)), blockEnd];
1345
- }
1346
- reallocArray(array, num) {
1347
- if (array.buffer !== this.buf) {
1348
- return;
1349
- }
1350
- const addr = this.realloc(
1351
- array.byteOffset,
1352
- num * array.BYTES_PER_ELEMENT
1353
- );
1354
- return addr ? new array.constructor(this.buf, addr, num) : void 0;
1355
- }
1356
- free(ptrOrArray) {
1357
- let addr;
1358
- if (!isNumber(ptrOrArray)) {
1359
- if (ptrOrArray.buffer !== this.buf) {
1360
- return false;
1361
- }
1362
- addr = ptrOrArray.byteOffset;
1363
- } else {
1364
- addr = ptrOrArray;
1365
- }
1366
- addr = __blockSelfAddress(addr);
1367
- let block = this._used;
1368
- let prev = 0;
1369
- while (block) {
1370
- if (block === addr) {
1371
- if (prev) {
1372
- this.unlinkBlock(prev, block);
1373
- } else {
1374
- this._used = this.blockNext(block);
1375
- }
1376
- this.insert(block);
1377
- this.doCompact && this.compact();
1378
- return true;
1379
- }
1380
- prev = block;
1381
- block = this.blockNext(block);
1382
- }
1383
- return false;
1384
- }
1385
- freeAll() {
1386
- this._free = 0;
1387
- this._used = 0;
1388
- this.top = this.initialTop();
1389
- }
1390
- release() {
1391
- delete this.u8;
1392
- delete this.u32;
1393
- delete this.state;
1394
- delete this.buf;
1395
- return true;
1396
- }
1397
- get align() {
1398
- return this.state[STATE_ALIGN];
1399
- }
1400
- set align(x) {
1401
- this.state[STATE_ALIGN] = x;
1402
- }
1403
- get end() {
1404
- return this.state[STATE_END];
1405
- }
1406
- set end(x) {
1407
- this.state[STATE_END] = x;
1408
- }
1409
- get top() {
1410
- return this.state[STATE_TOP];
1411
- }
1412
- set top(x) {
1413
- this.state[STATE_TOP] = x;
1414
- }
1415
- get _free() {
1416
- return this.state[STATE_FREE];
1417
- }
1418
- set _free(block) {
1419
- this.state[STATE_FREE] = block;
1420
- }
1421
- get _used() {
1422
- return this.state[STATE_USED];
1423
- }
1424
- set _used(block) {
1425
- this.state[STATE_USED] = block;
1426
- }
1427
- get doCompact() {
1428
- return !!(this.state[STATE_FLAGS] & MASK_COMPACT);
1429
- }
1430
- set doCompact(flag) {
1431
- flag ? this.state[STATE_FLAGS] |= 1 << MASK_COMPACT - 1 : this.state[STATE_FLAGS] &= ~MASK_COMPACT;
1432
- }
1433
- get doSplit() {
1434
- return !!(this.state[STATE_FLAGS] & MASK_SPLIT);
1435
- }
1436
- set doSplit(flag) {
1437
- flag ? this.state[STATE_FLAGS] |= 1 << MASK_SPLIT - 1 : this.state[STATE_FLAGS] &= ~MASK_SPLIT;
1438
- }
1439
- get minSplit() {
1440
- return this.state[STATE_MIN_SPLIT];
1441
- }
1442
- set minSplit(x) {
1443
- assert(
1444
- x > SIZEOF_MEM_BLOCK,
1445
- `illegal min split threshold: ${x}, require at least ${SIZEOF_MEM_BLOCK + 1}`
1446
- );
1447
- this.state[STATE_MIN_SPLIT] = x;
1448
- }
1449
- blockSize(block) {
1450
- return this.u32[(block >> 2) + MEM_BLOCK_SIZE];
1451
- }
1452
- /**
1453
- * Sets & returns given block size.
1454
- *
1455
- * @param block -
1456
- * @param size -
1457
- */
1458
- setBlockSize(block, size) {
1459
- this.u32[(block >> 2) + MEM_BLOCK_SIZE] = size;
1460
- return size;
1461
- }
1462
- blockNext(block) {
1463
- return this.u32[(block >> 2) + MEM_BLOCK_NEXT];
1464
- }
1465
- /**
1466
- * Sets block next pointer to `next`. Use zero to indicate list end.
1467
- *
1468
- * @param block -
1469
- */
1470
- setBlockNext(block, next) {
1471
- this.u32[(block >> 2) + MEM_BLOCK_NEXT] = next;
1472
- }
1473
- /**
1474
- * Initializes block header with given `size` and `next` pointer. Returns `block`.
1475
- *
1476
- * @param block -
1477
- * @param size -
1478
- * @param next -
1479
- */
1480
- initBlock(block, size, next) {
1481
- const idx = block >>> 2;
1482
- this.u32[idx + MEM_BLOCK_SIZE] = size;
1483
- this.u32[idx + MEM_BLOCK_NEXT] = next;
1484
- return block;
1485
- }
1486
- unlinkBlock(prev, block) {
1487
- this.setBlockNext(prev, this.blockNext(block));
1488
- }
1489
- splitBlock(block, blockSize, excess) {
1490
- this.insert(
1491
- this.initBlock(
1492
- block + this.setBlockSize(block, blockSize),
1493
- excess,
1494
- 0
1495
- )
1496
- );
1497
- this.doCompact && this.compact();
1498
- }
1499
- initialTop(_align = this.align) {
1500
- return align(this.start + SIZEOF_STATE + SIZEOF_MEM_BLOCK, _align) - SIZEOF_MEM_BLOCK;
1501
- }
1502
- /**
1503
- * Traverses free list and attempts to recursively merge blocks
1504
- * occupying consecutive memory regions. Returns true if any blocks
1505
- * have been merged. Only called if `compact` option is enabled.
1506
- */
1507
- compact() {
1508
- let block = this._free;
1509
- let prev = 0;
1510
- let scan = 0;
1511
- let scanPrev;
1512
- let res = false;
1513
- while (block) {
1514
- scanPrev = block;
1515
- scan = this.blockNext(block);
1516
- while (scan && scanPrev + this.blockSize(scanPrev) === scan) {
1517
- scanPrev = scan;
1518
- scan = this.blockNext(scan);
1519
- }
1520
- if (scanPrev !== block) {
1521
- const newSize = scanPrev - block + this.blockSize(scanPrev);
1522
- this.setBlockSize(block, newSize);
1523
- const next = this.blockNext(scanPrev);
1524
- let tmp = this.blockNext(block);
1525
- while (tmp && tmp !== next) {
1526
- const tn = this.blockNext(tmp);
1527
- this.setBlockNext(tmp, 0);
1528
- tmp = tn;
1529
- }
1530
- this.setBlockNext(block, next);
1531
- res = true;
1532
- }
1533
- if (block + this.blockSize(block) >= this.top) {
1534
- this.top = block;
1535
- prev ? this.unlinkBlock(prev, block) : this._free = this.blockNext(block);
1536
- }
1537
- prev = block;
1538
- block = this.blockNext(block);
1539
- }
1540
- return res;
1541
- }
1542
- /**
1543
- * Inserts given block into list of free blocks, sorted by address.
1544
- *
1545
- * @param block -
1546
- */
1547
- insert(block) {
1548
- let ptr = this._free;
1549
- let prev = 0;
1550
- while (ptr) {
1551
- if (block <= ptr) break;
1552
- prev = ptr;
1553
- ptr = this.blockNext(ptr);
1554
- }
1555
- if (prev) {
1556
- this.setBlockNext(prev, block);
1557
- } else {
1558
- this._free = block;
1559
- }
1560
- this.setBlockNext(block, ptr);
1561
- }
1562
- };
1563
- var __blockDataAddress = (blockAddress) => blockAddress > 0 ? blockAddress + SIZEOF_MEM_BLOCK : 0;
1564
- var __blockSelfAddress = (dataAddress) => dataAddress > 0 ? dataAddress - SIZEOF_MEM_BLOCK : 0;
1565
-
1566
- // js/lib/buffer_manager.js
1567
- var BUFFER_POOL_ALIGNMENT = 8;
1568
- var BufferManager = class {
1569
- // Private configuration
1570
- #sampleBaseURL;
1571
- #audioPathMap;
1572
- // Private implementation
1573
- #audioContext;
1574
- #sharedBuffer;
1575
- #bufferPool;
1576
- #allocatedBuffers;
1577
- #pendingBufferOps;
1578
- #bufferLocks;
1579
- constructor(options) {
1580
- const {
1581
- audioContext,
1582
- sharedBuffer,
1583
- bufferPoolConfig,
1584
- sampleBaseURL,
1585
- audioPathMap = {},
1586
- maxBuffers = 1024
1587
- } = options;
1588
- if (!audioContext) {
1589
- throw new Error("BufferManager requires audioContext");
1590
- }
1591
- if (!sharedBuffer || !(sharedBuffer instanceof SharedArrayBuffer)) {
1592
- throw new Error("BufferManager requires sharedBuffer (SharedArrayBuffer)");
1593
- }
1594
- if (!bufferPoolConfig || typeof bufferPoolConfig !== "object") {
1595
- throw new Error("BufferManager requires bufferPoolConfig (object with start, size, align)");
1596
- }
1597
- if (!Number.isFinite(bufferPoolConfig.start) || bufferPoolConfig.start < 0) {
1598
- throw new Error("bufferPoolConfig.start must be a non-negative number");
1599
- }
1600
- if (!Number.isFinite(bufferPoolConfig.size) || bufferPoolConfig.size <= 0) {
1601
- throw new Error("bufferPoolConfig.size must be a positive number");
1602
- }
1603
- if (audioPathMap && typeof audioPathMap !== "object") {
1604
- throw new Error("audioPathMap must be an object");
1605
- }
1606
- if (!Number.isInteger(maxBuffers) || maxBuffers <= 0) {
1607
- throw new Error("maxBuffers must be a positive integer");
1608
- }
1609
- this.#audioContext = audioContext;
1610
- this.#sharedBuffer = sharedBuffer;
1611
- this.#sampleBaseURL = sampleBaseURL;
1612
- this.#audioPathMap = audioPathMap;
1613
- this.#bufferPool = new MemPool({
1614
- buf: sharedBuffer,
1615
- start: bufferPoolConfig.start,
1616
- size: bufferPoolConfig.size,
1617
- align: BUFFER_POOL_ALIGNMENT
1618
- });
1619
- this.#allocatedBuffers = /* @__PURE__ */ new Map();
1620
- this.#pendingBufferOps = /* @__PURE__ */ new Map();
1621
- this.#bufferLocks = /* @__PURE__ */ new Map();
1622
- this.GUARD_BEFORE = 3;
1623
- this.GUARD_AFTER = 1;
1624
- this.MAX_BUFFERS = maxBuffers;
1625
- const poolSizeMB = (bufferPoolConfig.size / (1024 * 1024)).toFixed(0);
1626
- const poolOffsetMB = (bufferPoolConfig.start / (1024 * 1024)).toFixed(0);
1627
- console.log(`[BufferManager] Initialized: ${poolSizeMB}MB pool at offset ${poolOffsetMB}MB`);
1628
- }
1629
- #resolveAudioPath(scPath) {
1630
- if (typeof scPath !== "string" || scPath.length === 0) {
1631
- throw new Error(`Invalid audio path: must be a non-empty string`);
1632
- }
1633
- if (scPath.includes("..")) {
1634
- throw new Error(`Invalid audio path: path cannot contain '..' (got: ${scPath})`);
1635
- }
1636
- if (scPath.startsWith("/") || /^[a-zA-Z]:/.test(scPath)) {
1637
- throw new Error(`Invalid audio path: path must be relative (got: ${scPath})`);
1638
- }
1639
- if (scPath.includes("%2e") || scPath.includes("%2E")) {
1640
- throw new Error(`Invalid audio path: path cannot contain URL-encoded characters (got: ${scPath})`);
1641
- }
1642
- if (scPath.includes("\\")) {
1643
- throw new Error(`Invalid audio path: use forward slashes only (got: ${scPath})`);
1644
- }
1645
- if (this.#audioPathMap[scPath]) {
1646
- return this.#audioPathMap[scPath];
1647
- }
1648
- if (!this.#sampleBaseURL) {
1649
- throw new Error(
1650
- 'sampleBaseURL not configured. Please set it in SuperSonic constructor options.\nExample: new SuperSonic({ sampleBaseURL: "./dist/samples/" })\nOr use CDN: new SuperSonic({ sampleBaseURL: "https://unpkg.com/supersonic-scsynth-samples@latest/samples/" })\nOr install: npm install supersonic-scsynth-samples'
1651
- );
1652
- }
1653
- return this.#sampleBaseURL + scPath;
1654
- }
1655
- #validateBufferNumber(bufnum) {
1656
- if (!Number.isInteger(bufnum) || bufnum < 0 || bufnum >= this.MAX_BUFFERS) {
1657
- throw new Error(`Invalid buffer number ${bufnum} (must be 0-${this.MAX_BUFFERS - 1})`);
1658
- }
1659
- }
1660
- /**
1661
- * Execute a buffer operation with proper locking, registration, and cleanup
1662
- * @private
1663
- * @param {number} bufnum - Buffer number
1664
- * @param {number} timeoutMs - Operation timeout
1665
- * @param {Function} operation - Async function that performs the actual buffer work
1666
- * Should return {ptr, sizeBytes, ...extraProps}
1667
- * @returns {Promise<Object>} Result object with ptr, uuid, allocationComplete, and extra props
1668
- */
1669
- async #executeBufferOperation(bufnum, timeoutMs, operation) {
1670
- let allocatedPtr = null;
1671
- let pendingToken = null;
1672
- let allocationRegistered = false;
1673
- const releaseLock = await this.#acquireBufferLock(bufnum);
1674
- let lockReleased = false;
1675
- try {
1676
- await this.#awaitPendingReplacement(bufnum);
1677
- const { ptr, sizeBytes, ...extraProps } = await operation();
1678
- allocatedPtr = ptr;
1679
- const { uuid, allocationComplete } = this.#registerPending(bufnum, timeoutMs);
1680
- pendingToken = uuid;
1681
- this.#recordAllocation(bufnum, allocatedPtr, sizeBytes, uuid, allocationComplete);
1682
- allocationRegistered = true;
1683
- const managedCompletion = this.#attachFinalizer(bufnum, uuid, allocationComplete);
1684
- releaseLock();
1685
- lockReleased = true;
1686
- return {
1687
- ptr: allocatedPtr,
1688
- uuid,
1689
- allocationComplete: managedCompletion,
1690
- ...extraProps
1691
- };
1692
- } catch (error) {
1693
- if (allocationRegistered && pendingToken) {
1694
- this.#finalizeReplacement(bufnum, pendingToken, false);
1695
- } else if (allocatedPtr) {
1696
- this.#bufferPool.free(allocatedPtr);
1697
- }
1698
- throw error;
1699
- } finally {
1700
- if (!lockReleased) {
1701
- releaseLock();
1702
- }
1703
- }
1704
- }
1705
- async prepareFromFile(params) {
1706
- const {
1707
- bufnum,
1708
- path,
1709
- startFrame = 0,
1710
- numFrames = 0,
1711
- channels = null
1712
- } = params;
1713
- this.#validateBufferNumber(bufnum);
1714
- return this.#executeBufferOperation(bufnum, 6e4, async () => {
1715
- const resolvedPath = this.#resolveAudioPath(path);
1716
- const response = await fetch(resolvedPath);
1717
- if (!response.ok) {
1718
- throw new Error(`Failed to fetch ${resolvedPath}: ${response.status} ${response.statusText}`);
1719
- }
1720
- const arrayBuffer = await response.arrayBuffer();
1721
- const audioBuffer = await this.#audioContext.decodeAudioData(arrayBuffer);
1722
- const start = Math.max(0, Math.floor(startFrame || 0));
1723
- const availableFrames = audioBuffer.length - start;
1724
- const framesRequested = numFrames && numFrames > 0 ? Math.min(Math.floor(numFrames), availableFrames) : availableFrames;
1725
- if (framesRequested <= 0) {
1726
- throw new Error(`No audio frames available for buffer ${bufnum} from ${path}`);
1727
- }
1728
- const selectedChannels = this.#normalizeChannels(channels, audioBuffer.numberOfChannels);
1729
- const numChannels = selectedChannels.length;
1730
- const totalSamples = framesRequested * numChannels + (this.GUARD_BEFORE + this.GUARD_AFTER) * numChannels;
1731
- const ptr = this.#malloc(totalSamples);
1732
- const interleaved = new Float32Array(totalSamples);
1733
- const dataOffset = this.GUARD_BEFORE * numChannels;
1734
- for (let frame = 0; frame < framesRequested; frame++) {
1735
- for (let ch = 0; ch < numChannels; ch++) {
1736
- const sourceChannel = selectedChannels[ch];
1737
- const channelData = audioBuffer.getChannelData(sourceChannel);
1738
- interleaved[dataOffset + frame * numChannels + ch] = channelData[start + frame];
1739
- }
1740
- }
1741
- this.#writeToSharedBuffer(ptr, interleaved);
1742
- const sizeBytes = interleaved.length * 4;
1743
- return {
1744
- ptr,
1745
- sizeBytes,
1746
- numFrames: framesRequested,
1747
- numChannels,
1748
- sampleRate: audioBuffer.sampleRate
1749
- };
1750
- });
1751
- }
1752
- async prepareEmpty(params) {
1753
- const {
1754
- bufnum,
1755
- numFrames,
1756
- numChannels = 1,
1757
- sampleRate = null
1758
- } = params;
1759
- this.#validateBufferNumber(bufnum);
1760
- if (!Number.isFinite(numFrames) || numFrames <= 0) {
1761
- throw new Error(`/b_alloc requires a positive number of frames (got ${numFrames})`);
1762
- }
1763
- if (!Number.isFinite(numChannels) || numChannels <= 0) {
1764
- throw new Error(`/b_alloc requires a positive channel count (got ${numChannels})`);
1765
- }
1766
- const roundedFrames = Math.floor(numFrames);
1767
- const roundedChannels = Math.floor(numChannels);
1768
- return this.#executeBufferOperation(bufnum, 5e3, async () => {
1769
- const totalSamples = roundedFrames * roundedChannels + (this.GUARD_BEFORE + this.GUARD_AFTER) * roundedChannels;
1770
- const ptr = this.#malloc(totalSamples);
1771
- const interleaved = new Float32Array(totalSamples);
1772
- this.#writeToSharedBuffer(ptr, interleaved);
1773
- const sizeBytes = interleaved.length * 4;
1774
- return {
1775
- ptr,
1776
- sizeBytes,
1777
- numFrames: roundedFrames,
1778
- numChannels: roundedChannels,
1779
- sampleRate: sampleRate || this.#audioContext.sampleRate
1780
- };
1781
- });
1782
- }
1783
- #normalizeChannels(requestedChannels, fileChannels) {
1784
- if (!requestedChannels || requestedChannels.length === 0) {
1785
- return Array.from({ length: fileChannels }, (_, i) => i);
1786
- }
1787
- requestedChannels.forEach((channel) => {
1788
- if (!Number.isInteger(channel) || channel < 0 || channel >= fileChannels) {
1789
- throw new Error(`Channel ${channel} is out of range (file has ${fileChannels} channels)`);
1790
- }
1791
- });
1792
- return requestedChannels;
1793
- }
1794
- #malloc(totalSamples) {
1795
- const bytesNeeded = totalSamples * 4;
1796
- const ptr = this.#bufferPool.malloc(bytesNeeded);
1797
- if (ptr === 0) {
1798
- const stats = this.#bufferPool.stats();
1799
- const availableMB = ((stats.available || 0) / (1024 * 1024)).toFixed(2);
1800
- const totalMB = ((stats.total || 0) / (1024 * 1024)).toFixed(2);
1801
- const requestedMB = (bytesNeeded / (1024 * 1024)).toFixed(2);
1802
- throw new Error(
1803
- `Buffer pool allocation failed: requested ${requestedMB}MB, available ${availableMB}MB of ${totalMB}MB total`
1804
- );
1805
- }
1806
- return ptr;
1807
- }
1808
- #writeToSharedBuffer(ptr, data) {
1809
- const heap = new Float32Array(this.#sharedBuffer, ptr, data.length);
1810
- heap.set(data);
1811
- }
1812
- #createPendingOperation(uuid, bufnum, timeoutMs) {
1813
- return new Promise((resolve, reject) => {
1814
- const timeout = setTimeout(() => {
1815
- this.#pendingBufferOps.delete(uuid);
1816
- reject(new Error(`Buffer ${bufnum} allocation timeout (${timeoutMs}ms)`));
1817
- }, timeoutMs);
1818
- this.#pendingBufferOps.set(uuid, { resolve, reject, timeout });
1819
- });
1820
- }
1821
- #registerPending(bufnum, timeoutMs) {
1822
- const uuid = crypto.randomUUID();
1823
- const allocationComplete = this.#createPendingOperation(uuid, bufnum, timeoutMs);
1824
- return { uuid, allocationComplete };
1825
- }
1826
- async #acquireBufferLock(bufnum) {
1827
- const prev = this.#bufferLocks.get(bufnum) || Promise.resolve();
1828
- let releaseLock;
1829
- const current = new Promise((resolve) => {
1830
- releaseLock = resolve;
1831
- });
1832
- this.#bufferLocks.set(bufnum, prev.then(() => current));
1833
- await prev;
1834
- return () => {
1835
- if (releaseLock) {
1836
- releaseLock();
1837
- releaseLock = null;
1838
- }
1839
- if (this.#bufferLocks.get(bufnum) === current) {
1840
- this.#bufferLocks.delete(bufnum);
1841
- }
1842
- };
1843
- }
1844
- #recordAllocation(bufnum, ptr, sizeBytes, pendingToken, pendingPromise) {
1845
- const previousEntry = this.#allocatedBuffers.get(bufnum);
1846
- const entry = {
1847
- ptr,
1848
- size: sizeBytes,
1849
- pendingToken,
1850
- pendingPromise,
1851
- previousAllocation: previousEntry ? { ptr: previousEntry.ptr, size: previousEntry.size } : null
1852
- };
1853
- this.#allocatedBuffers.set(bufnum, entry);
1854
- return entry;
1855
- }
1856
- async #awaitPendingReplacement(bufnum) {
1857
- const existing = this.#allocatedBuffers.get(bufnum);
1858
- if (existing && existing.pendingToken && existing.pendingPromise) {
1859
- try {
1860
- await existing.pendingPromise;
1861
- } catch {
1862
- }
1863
- }
1864
- }
1865
- #attachFinalizer(bufnum, pendingToken, promise) {
1866
- if (!promise || typeof promise.then !== "function") {
1867
- this.#finalizeReplacement(bufnum, pendingToken, true);
1868
- return Promise.resolve();
1869
- }
1870
- return promise.then((value) => {
1871
- this.#finalizeReplacement(bufnum, pendingToken, true);
1872
- return value;
1873
- }).catch((error) => {
1874
- this.#finalizeReplacement(bufnum, pendingToken, false);
1875
- throw error;
1876
- });
1877
- }
1878
- #finalizeReplacement(bufnum, pendingToken, success) {
1879
- const entry = this.#allocatedBuffers.get(bufnum);
1880
- if (!entry || entry.pendingToken !== pendingToken) {
1881
- return;
1882
- }
1883
- const previous = entry.previousAllocation;
1884
- if (success) {
1885
- entry.pendingToken = null;
1886
- entry.pendingPromise = null;
1887
- entry.previousAllocation = null;
1888
- if (previous?.ptr) {
1889
- this.#bufferPool.free(previous.ptr);
1890
- }
1891
- return;
1892
- }
1893
- if (entry.ptr) {
1894
- this.#bufferPool.free(entry.ptr);
1895
- }
1896
- entry.pendingPromise = null;
1897
- if (previous?.ptr) {
1898
- this.#allocatedBuffers.set(bufnum, {
1899
- ptr: previous.ptr,
1900
- size: previous.size,
1901
- pendingToken: null,
1902
- previousAllocation: null
1903
- });
1904
- } else {
1905
- this.#allocatedBuffers.delete(bufnum);
1906
- }
1907
- }
1908
- /**
1909
- * Handle /buffer/freed notification from scsynth
1910
- * Called by SuperSonic when /buffer/freed OSC message is received
1911
- * @param {Array} args - [bufnum, freedPtr]
1912
- */
1913
- handleBufferFreed(args) {
1914
- const bufnum = args[0];
1915
- const freedPtr = args[1];
1916
- const bufferInfo = this.#allocatedBuffers.get(bufnum);
1917
- if (!bufferInfo) {
1918
- if (typeof freedPtr === "number" && freedPtr !== 0) {
1919
- this.#bufferPool.free(freedPtr);
1920
- }
1921
- return;
1922
- }
1923
- if (typeof freedPtr === "number" && freedPtr === bufferInfo.ptr) {
1924
- this.#bufferPool.free(bufferInfo.ptr);
1925
- this.#allocatedBuffers.delete(bufnum);
1926
- return;
1927
- }
1928
- if (typeof freedPtr === "number" && bufferInfo.previousAllocation && bufferInfo.previousAllocation.ptr === freedPtr) {
1929
- this.#bufferPool.free(freedPtr);
1930
- bufferInfo.previousAllocation = null;
1931
- return;
1932
- }
1933
- this.#bufferPool.free(bufferInfo.ptr);
1934
- this.#allocatedBuffers.delete(bufnum);
1935
- }
1936
- /**
1937
- * Handle /buffer/allocated notification from scsynth
1938
- * Called by SuperSonic when /buffer/allocated OSC message is received
1939
- * @param {Array} args - [uuid, bufnum]
1940
- */
1941
- handleBufferAllocated(args) {
1942
- const uuid = args[0];
1943
- const bufnum = args[1];
1944
- const pending = this.#pendingBufferOps.get(uuid);
1945
- if (pending) {
1946
- clearTimeout(pending.timeout);
1947
- pending.resolve({ bufnum });
1948
- this.#pendingBufferOps.delete(uuid);
1949
- }
1950
- }
1951
- /**
1952
- * Allocate raw buffer memory
1953
- * @param {number} numSamples - Number of Float32 samples
1954
- * @returns {number} Byte offset, or 0 if failed
1955
- */
1956
- allocate(numSamples) {
1957
- const sizeBytes = numSamples * 4;
1958
- const addr = this.#bufferPool.malloc(sizeBytes);
1959
- if (addr === 0) {
1960
- const stats = this.#bufferPool.stats();
1961
- const availableMB = ((stats.available || 0) / (1024 * 1024)).toFixed(2);
1962
- const totalMB = ((stats.total || 0) / (1024 * 1024)).toFixed(2);
1963
- const requestedMB = (sizeBytes / (1024 * 1024)).toFixed(2);
1964
- console.error(
1965
- `[BufferManager] Allocation failed: requested ${requestedMB}MB, available ${availableMB}MB of ${totalMB}MB total`
1966
- );
1967
- }
1968
- return addr;
1969
- }
1970
- /**
1971
- * Free previously allocated buffer
1972
- * @param {number} addr - Buffer address
1973
- * @returns {boolean} true if freed successfully
1974
- */
1975
- free(addr) {
1976
- return this.#bufferPool.free(addr);
1977
- }
1978
- /**
1979
- * Get Float32Array view of buffer
1980
- * @param {number} addr - Buffer address
1981
- * @param {number} numSamples - Number of samples
1982
- * @returns {Float32Array} Typed array view
1983
- */
1984
- getView(addr, numSamples) {
1985
- return new Float32Array(this.#sharedBuffer, addr, numSamples);
1986
- }
1987
- /**
1988
- * Get buffer pool statistics
1989
- * @returns {Object} Stats including total, available, used
1990
- */
1991
- getStats() {
1992
- return this.#bufferPool.stats();
1993
- }
1994
- /**
1995
- * Get buffer diagnostics
1996
- * @returns {Object} Buffer state and pool statistics
1997
- */
1998
- getDiagnostics() {
1999
- const poolStats = this.#bufferPool.stats();
2000
- let bytesActive = 0;
2001
- let pendingCount = 0;
2002
- for (const entry of this.#allocatedBuffers.values()) {
2003
- if (!entry) continue;
2004
- bytesActive += entry.size || 0;
2005
- if (entry.pendingToken) {
2006
- pendingCount++;
2007
- }
2008
- }
2009
- return {
2010
- active: this.#allocatedBuffers.size,
2011
- pending: pendingCount,
2012
- bytesActive,
2013
- pool: {
2014
- total: poolStats.total || 0,
2015
- available: poolStats.available || 0,
2016
- freeBytes: poolStats.free?.size || 0,
2017
- freeBlocks: poolStats.free?.count || 0,
2018
- usedBytes: poolStats.used?.size || 0,
2019
- usedBlocks: poolStats.used?.count || 0
2020
- }
2021
- };
2022
- }
2023
- /**
2024
- * Clean up resources
2025
- */
2026
- destroy() {
2027
- for (const [uuid, pending] of this.#pendingBufferOps.entries()) {
2028
- clearTimeout(pending.timeout);
2029
- pending.reject(new Error("BufferManager destroyed"));
2030
- }
2031
- this.#pendingBufferOps.clear();
2032
- for (const [bufnum, entry] of this.#allocatedBuffers.entries()) {
2033
- if (entry.ptr) {
2034
- this.#bufferPool.free(entry.ptr);
2035
- }
2036
- }
2037
- this.#allocatedBuffers.clear();
2038
- this.#bufferLocks.clear();
2039
- console.log("[BufferManager] Destroyed");
2040
- }
2041
- };
2042
-
2043
- // js/timing_constants.js
2044
- var NTP_EPOCH_OFFSET = 2208988800;
2045
- var DRIFT_UPDATE_INTERVAL_MS = 15e3;
2046
-
2047
- // js/memory_layout.js
2048
- var MemoryLayout = {
2049
- /**
2050
- * Total WebAssembly memory in pages (1 page = 64KB)
2051
- * Current: 1280 pages = 80MB
2052
- *
2053
- * This value is used by build.sh to set -sINITIAL_MEMORY
2054
- * Must match: totalPages * 65536 = bufferPoolOffset + bufferPoolSize
2055
- */
2056
- totalPages: 1280,
2057
- /**
2058
- * WASM heap size (implicit, first section of memory)
2059
- * Not directly configurable here - defined by bufferPoolOffset - ringBufferReserved
2060
- * Current: 0-16MB (16 * 1024 * 1024 = 16777216 bytes)
2061
- */
2062
- // wasmHeapSize is implicitly: bufferPoolOffset - ringBufferReserved
2063
- /**
2064
- * Ring buffer reserved space (between WASM heap and buffer pool)
2065
- * Actual ring buffer usage: IN: 768KB, OUT: 128KB, DEBUG: 64KB = 960KB
2066
- * Plus control structures: CONTROL_SIZE (40B) + METRICS_SIZE (48B) + NTP_START_TIME_SIZE (8B) ≈ 96B
2067
- * Total actual usage: ~960KB
2068
- * Reserved: 1MB (provides ~64KB headroom for alignment and future expansion)
2069
- * Current: 1MB reserved (starts where WASM heap ends at 16MB)
2070
- */
2071
- ringBufferReserved: 1 * 1024 * 1024,
2072
- // 1MB reserved
2073
- /**
2074
- * Buffer pool byte offset from start of SharedArrayBuffer
2075
- * Audio samples are allocated from this pool using @thi.ng/malloc
2076
- * Must be after WASM heap + ring buffer area
2077
- * Current: 17MB offset = after 16MB heap + 1MB ring buffers
2078
- */
2079
- bufferPoolOffset: 17 * 1024 * 1024,
2080
- // 17825792 bytes
2081
- /**
2082
- * Buffer pool size in bytes
2083
- * Used for audio sample storage (loaded files + allocated buffers)
2084
- * Current: 63MB (enough for ~3.5 minutes of stereo at 48kHz uncompressed)
2085
- */
2086
- bufferPoolSize: 63 * 1024 * 1024,
2087
- // 66060288 bytes
2088
- /**
2089
- * Total memory calculation (should equal totalPages * 65536)
2090
- * wasmHeap (16MB) + ringReserve (1MB) + bufferPool (63MB) = 80MB
2091
- */
2092
- get totalMemory() {
2093
- return this.bufferPoolOffset + this.bufferPoolSize;
2094
- },
2095
- /**
2096
- * Effective WASM heap size (derived)
2097
- * This is the space available for scsynth C++ allocations
2098
- */
2099
- get wasmHeapSize() {
2100
- return this.bufferPoolOffset - this.ringBufferReserved;
2101
- }
2102
- };
2103
-
2104
- // js/scsynth_options.js
2105
- var defaultWorldOptions = {
2106
- /**
2107
- * Maximum number of audio buffers (SndBuf slots)
2108
- * Each buffer slot: 104 bytes overhead (2x SndBuf + SndBufUpdates structs)
2109
- * Actual audio data is stored in buffer pool (separate from heap)
2110
- * Default: 1024 (matching SuperCollider default)
2111
- * Range: 1-65535 (limited by practical memory constraints)
2112
- */
2113
- numBuffers: 1024,
2114
- /**
2115
- * Maximum number of synthesis nodes (synths + groups)
2116
- * Each node: ~200-500 bytes depending on synth complexity
2117
- * Default: 1024 (matching SuperCollider default)
2118
- */
2119
- maxNodes: 1024,
2120
- /**
2121
- * Maximum number of synth definitions (SynthDef count)
2122
- * Each definition: variable size (typically 1-10KB)
2123
- * Default: 1024 (matching SuperCollider default)
2124
- */
2125
- maxGraphDefs: 1024,
2126
- /**
2127
- * Maximum wire buffers for internal audio routing
2128
- * Wire buffers: temporary buffers for UGen connections
2129
- * Each: bufLength * 8 bytes (128 samples * 8 = 1024 bytes)
2130
- * Default: 64 (matching SuperCollider default)
2131
- */
2132
- maxWireBufs: 64,
2133
- /**
2134
- * Number of audio bus channels
2135
- * Audio buses: real-time audio routing between synths
2136
- * Memory: bufLength * numChannels * 4 bytes (128 * 128 * 4 = 64KB)
2137
- * Default: 128 (SuperSonic default, SC uses 1024)
2138
- */
2139
- numAudioBusChannels: 128,
2140
- /**
2141
- * Number of input bus channels (hardware audio input)
2142
- * AudioWorklet can support input, but SuperSonic doesn't currently route it
2143
- * Default: 0 (audio input not implemented)
2144
- */
2145
- numInputBusChannels: 0,
2146
- /**
2147
- * Number of output bus channels (hardware audio output)
2148
- * WebAudio/AudioWorklet output
2149
- * Default: 2 (stereo)
2150
- */
2151
- numOutputBusChannels: 2,
2152
- /**
2153
- * Number of control bus channels
2154
- * Control buses: control-rate data sharing between synths
2155
- * Memory: numChannels * 4 bytes (4096 * 4 = 16KB)
2156
- * Default: 4096 (SuperSonic default, SC uses 16384)
2157
- */
2158
- numControlBusChannels: 4096,
2159
- /**
2160
- * Audio buffer length in samples (AudioWorklet quantum)
2161
- *
2162
- * FIXED at 128 (WebAudio API spec - cannot be changed)
2163
- * Unlike SuperCollider (configurable 32/64/128), AudioWorklet has a fixed quantum.
2164
- * Overriding this value will cause initialization to fail.
2165
- *
2166
- * Default: 128
2167
- */
2168
- bufLength: 128,
2169
- /**
2170
- * Real-time memory pool size in kilobytes
2171
- * AllocPool for synthesis-time allocations (UGen memory, etc.)
2172
- * This is the largest single allocation from WASM heap
2173
- * Memory: realTimeMemorySize * 1024 bytes (8192 * 1024 = 8MB)
2174
- * Default: 8192 KB (8MB, matching Sonic Pi and SuperCollider defaults)
2175
- */
2176
- realTimeMemorySize: 8192,
2177
- /**
2178
- * Number of random number generators
2179
- * Each synth can have its own RNG for reproducible randomness
2180
- * Default: 64 (matching SuperCollider default)
2181
- */
2182
- numRGens: 64,
2183
- /**
2184
- * Clock source mode
2185
- * false = Externally clocked (driven by AudioWorklet process() callback)
2186
- * true = Internally clocked (not applicable in WebAudio context)
2187
- * Note: In SC terminology, this is "NRT mode" but we're still doing real-time audio
2188
- * Default: false (SuperSonic is always externally clocked by AudioWorklet)
2189
- */
2190
- realTime: false,
2191
- /**
2192
- * Memory locking (mlock)
2193
- * Not applicable in WebAssembly/browser environment
2194
- * Default: false
2195
- */
2196
- memoryLocking: false,
2197
- /**
2198
- * Auto-load SynthDefs from disk
2199
- * 0 = don't auto-load (synths sent via /d_recv)
2200
- * 1 = auto-load from plugin path
2201
- * Default: 0 (SuperSonic loads synthdefs via network)
2202
- */
2203
- loadGraphDefs: 0,
2204
- /**
2205
- * Preferred sample rate (if not specified, uses AudioContext.sampleRate)
2206
- * Common values: 44100, 48000, 96000
2207
- * Default: 0 (use AudioContext default, typically 48000)
2208
- */
2209
- preferredSampleRate: 0,
2210
- /**
2211
- * Debug verbosity level
2212
- * 0 = quiet, 1 = errors, 2 = warnings, 3 = info, 4 = debug
2213
- * Default: 0
2214
- */
2215
- verbosity: 0
2216
- };
2217
-
2218
- // js/supersonic.js
2219
- var SuperSonic = class _SuperSonic {
2220
- // Expose OSC utilities as static methods
2221
- static osc = {
2222
- encode: (message) => osc_default.writePacket(message),
2223
- decode: (data, options = { metadata: false }) => osc_default.readPacket(data, options)
2224
- };
2225
- // Private implementation
2226
- #audioContext;
2227
- #workletNode;
2228
- #osc;
2229
- #wasmMemory;
2230
- #sharedBuffer;
2231
- #ringBufferBase;
2232
- #bufferConstants;
2233
- #bufferManager;
2234
- #driftOffsetTimer;
2235
- #syncListeners;
2236
- #initialNTPStartTime;
2237
- #sampleBaseURL;
2238
- #synthdefBaseURL;
2239
- #audioPathMap;
2240
- #initialized;
2241
- #initializing;
2242
- #capabilities;
2243
- // Runtime metrics (private counters)
2244
- #metrics_messagesSent = 0;
2245
- #metrics_messagesReceived = 0;
2246
- #metrics_errors = 0;
2247
- #metricsIntervalId = null;
2248
- #metricsGatherInProgress = false;
2249
- constructor(options = {}) {
2250
- this.#initialized = false;
2251
- this.#initializing = false;
2252
- this.#capabilities = {};
2253
- this.#sharedBuffer = null;
2254
- this.#ringBufferBase = null;
2255
- this.#bufferConstants = null;
2256
- this.#audioContext = null;
2257
- this.#workletNode = null;
2258
- this.#osc = null;
2259
- this.#bufferManager = null;
2260
- this.loadedSynthDefs = /* @__PURE__ */ new Set();
2261
- this.onOSC = null;
2262
- this.onMessage = null;
2263
- this.onMessageSent = null;
2264
- this.onMetricsUpdate = null;
2265
- this.onDebugMessage = null;
2266
- this.onInitialized = null;
2267
- this.onError = null;
2268
- if (!options.workerBaseURL || !options.wasmBaseURL) {
2269
- throw new Error('SuperSonic requires workerBaseURL and wasmBaseURL options. Example:\nnew SuperSonic({\n workerBaseURL: "/supersonic/workers/",\n wasmBaseURL: "/supersonic/wasm/"\n})');
2270
- }
2271
- const workerBaseURL = options.workerBaseURL;
2272
- const wasmBaseURL = options.wasmBaseURL;
2273
- const worldOptions = { ...defaultWorldOptions, ...options.scsynthOptions };
2274
- this.config = {
2275
- wasmUrl: options.wasmUrl || wasmBaseURL + "scsynth-nrt.wasm",
2276
- wasmBaseURL,
2277
- workletUrl: options.workletUrl || workerBaseURL + "scsynth_audio_worklet.js",
2278
- workerBaseURL,
2279
- development: false,
2280
- audioContextOptions: {
2281
- latencyHint: "interactive",
2282
- // hint to push for lowest latency possible
2283
- sampleRate: 48e3
2284
- // only requested rate - actual rate is determined by hardware
2285
- },
2286
- // Build-time memory layout (constant)
2287
- memory: MemoryLayout,
2288
- // Runtime world options (merged defaults + user overrides)
2289
- worldOptions
2290
- };
2291
- this.#sampleBaseURL = options.sampleBaseURL || null;
2292
- this.#synthdefBaseURL = options.synthdefBaseURL || null;
2293
- this.#audioPathMap = options.audioPathMap || {};
2294
- this.bootStats = {
2295
- initStartTime: null,
2296
- initDuration: null
2297
- };
2298
- }
2299
- /**
2300
- * Get initialization status (read-only)
2301
- */
2302
- get initialized() {
2303
- return this.#initialized;
2304
- }
2305
- /**
2306
- * Get initialization in-progress status (read-only)
2307
- */
2308
- get initializing() {
2309
- return this.#initializing;
2310
- }
2311
- /**
2312
- * Get browser capabilities (read-only)
2313
- */
2314
- get capabilities() {
2315
- return this.#capabilities;
2316
- }
2317
- /**
2318
- * Set and validate browser capabilities for required features
2319
- */
2320
- setAndValidateCapabilities() {
2321
- this.#capabilities = {
2322
- audioWorklet: typeof AudioWorklet !== "undefined",
2323
- sharedArrayBuffer: typeof SharedArrayBuffer !== "undefined",
2324
- crossOriginIsolated: window.crossOriginIsolated === true,
2325
- atomics: typeof Atomics !== "undefined",
2326
- webWorker: typeof Worker !== "undefined"
2327
- };
2328
- const required = [
2329
- "audioWorklet",
2330
- "sharedArrayBuffer",
2331
- "crossOriginIsolated",
2332
- "atomics",
2333
- "webWorker"
2334
- ];
2335
- const missing = required.filter((f) => !this.#capabilities[f]);
2336
- if (missing.length > 0) {
2337
- const error = new Error(`Missing required features: ${missing.join(", ")}`);
2338
- if (!this.#capabilities.crossOriginIsolated) {
2339
- if (this.#capabilities.sharedArrayBuffer) {
2340
- 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";
2341
- } else {
2342
- 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";
2343
- }
2344
- }
2345
- throw error;
2346
- }
2347
- return this.#capabilities;
2348
- }
2349
- /**
2350
- * Merge user-provided world options with defaults
2351
- * @private
2352
- */
2353
- /**
2354
- * Initialize shared WebAssembly memory
2355
- */
2356
- #initializeSharedMemory() {
2357
- const memConfig = this.config.memory;
2358
- this.#wasmMemory = new WebAssembly.Memory({
2359
- initial: memConfig.totalPages,
2360
- maximum: memConfig.totalPages,
2361
- shared: true
2362
- });
2363
- this.#sharedBuffer = this.#wasmMemory.buffer;
2364
- }
2365
- #initializeAudioContext() {
2366
- this.#audioContext = new AudioContext(this.config.audioContextOptions);
2367
- return this.#audioContext;
2368
- }
2369
- #initializeBufferManager() {
2370
- this.#bufferManager = new BufferManager({
2371
- audioContext: this.#audioContext,
2372
- sharedBuffer: this.#sharedBuffer,
2373
- bufferPoolConfig: {
2374
- start: this.config.memory.bufferPoolOffset,
2375
- size: this.config.memory.bufferPoolSize
2376
- },
2377
- sampleBaseURL: this.#sampleBaseURL,
2378
- audioPathMap: this.#audioPathMap,
2379
- maxBuffers: this.config.worldOptions.numBuffers
2380
- });
2381
- }
2382
- async #loadWasmManifest() {
2383
- const manifestUrl = this.config.wasmBaseURL + "manifest.json";
2384
- try {
2385
- const response = await fetch(manifestUrl);
2386
- if (!response.ok) {
2387
- return;
2388
- }
2389
- const manifest = await response.json();
2390
- this.config.wasmUrl = this.config.wasmBaseURL + manifest.wasmFile;
2391
- console.log(`[SuperSonic] WASM: ${manifest.wasmFile} (${manifest.buildId}, git: ${manifest.gitHash})`);
2392
- } catch (error) {
2393
- }
2394
- }
2395
- /**
2396
- * Load WASM binary from network
2397
- */
2398
- async #loadWasm() {
2399
- if (this.config.development) {
2400
- await this.#loadWasmManifest();
2401
- }
2402
- const wasmResponse = await fetch(this.config.wasmUrl);
2403
- if (!wasmResponse.ok) {
2404
- throw new Error(`Failed to load WASM: ${wasmResponse.status} ${wasmResponse.statusText}`);
2405
- }
2406
- return await wasmResponse.arrayBuffer();
2407
- }
2408
- /**
2409
- * Initialize AudioWorklet with WASM
2410
- */
2411
- async #initializeAudioWorklet(wasmBytes) {
2412
- await this.#audioContext.audioWorklet.addModule(this.config.workletUrl);
2413
- this.#workletNode = new AudioWorkletNode(this.#audioContext, "scsynth-processor", {
2414
- numberOfInputs: 0,
2415
- numberOfOutputs: 1,
2416
- outputChannelCount: [2]
2417
- });
2418
- this.#workletNode.connect(this.#audioContext.destination);
2419
- this.#workletNode.port.postMessage({
2420
- type: "init",
2421
- sharedBuffer: this.#sharedBuffer
2422
- });
2423
- this.#workletNode.port.postMessage({
2424
- type: "loadWasm",
2425
- wasmBytes,
2426
- wasmMemory: this.#wasmMemory,
2427
- worldOptions: this.config.worldOptions,
2428
- sampleRate: this.#audioContext.sampleRate
2429
- // Pass actual AudioContext sample rate
2430
- });
2431
- await this.#waitForWorkletInit();
2432
- }
2433
- /**
2434
- * Initialize OSC communication layer
2435
- */
2436
- async #initializeOSC() {
2437
- this.#osc = new ScsynthOSC(this.config.workerBaseURL);
2438
- this.#osc.onRawOSC((msg) => {
2439
- if (this.onOSC) {
2440
- this.onOSC(msg);
2441
- }
2442
- });
2443
- this.#osc.onParsedOSC((msg) => {
2444
- if (msg.address === "/buffer/freed") {
2445
- this.#bufferManager?.handleBufferFreed(msg.args);
2446
- } else if (msg.address === "/buffer/allocated") {
2447
- this.#bufferManager?.handleBufferAllocated(msg.args);
2448
- } else if (msg.address === "/synced" && msg.args.length > 0) {
2449
- const syncId = msg.args[0];
2450
- if (this.#syncListeners && this.#syncListeners.has(syncId)) {
2451
- const listener = this.#syncListeners.get(syncId);
2452
- listener(msg);
2453
- }
2454
- }
2455
- if (this.onMessage) {
2456
- this.#metrics_messagesReceived++;
2457
- this.onMessage(msg);
2458
- }
2459
- });
2460
- this.#osc.onDebugMessage((msg) => {
2461
- if (this.onDebugMessage) {
2462
- this.onDebugMessage(msg);
2463
- }
2464
- });
2465
- this.#osc.onError((error, workerName) => {
2466
- console.error(`[SuperSonic] ${workerName} error:`, error);
2467
- this.#metrics_errors++;
2468
- if (this.onError) {
2469
- this.onError(new Error(`${workerName}: ${error}`));
2470
- }
2471
- });
2472
- await this.#osc.init(this.#sharedBuffer, this.#ringBufferBase, this.#bufferConstants);
2473
- }
2474
- /**
2475
- * Complete initialization and trigger callbacks
2476
- */
2477
- #finishInitialization() {
2478
- this.#initialized = true;
2479
- this.#initializing = false;
2480
- this.bootStats.initDuration = performance.now() - this.bootStats.initStartTime;
2481
- console.log(`[SuperSonic] Initialization complete in ${this.bootStats.initDuration.toFixed(2)}ms`);
2482
- if (this.onInitialized) {
2483
- this.onInitialized({
2484
- capabilities: this.#capabilities,
2485
- bootStats: this.bootStats
2486
- });
2487
- }
2488
- }
2489
- /**
2490
- * Initialize the audio worklet system
2491
- * @param {Object} config - Optional configuration overrides
2492
- * @param {boolean} config.development - Use cache-busted WASM files (default: false)
2493
- * @param {string} config.wasmUrl - Custom WASM URL
2494
- * @param {string} config.workletUrl - Custom worklet URL
2495
- * @param {Object} config.audioContextOptions - AudioContext options
2496
- */
2497
- async init(config = {}) {
2498
- if (this.#initialized) {
2499
- console.warn("[SuperSonic] Already initialized");
2500
- return;
2501
- }
2502
- if (this.#initializing) {
2503
- console.warn("[SuperSonic] Initialization already in progress");
2504
- return;
2505
- }
2506
- this.config = {
2507
- ...this.config,
2508
- ...config,
2509
- audioContextOptions: {
2510
- ...this.config.audioContextOptions,
2511
- ...config.audioContextOptions || {}
2512
- }
2513
- };
2514
- this.#initializing = true;
2515
- this.bootStats.initStartTime = performance.now();
2516
- try {
2517
- this.setAndValidateCapabilities();
2518
- this.#initializeSharedMemory();
2519
- this.#initializeAudioContext();
2520
- this.#initializeBufferManager();
2521
- const wasmBytes = await this.#loadWasm();
2522
- await this.#initializeAudioWorklet(wasmBytes);
2523
- await this.#initializeOSC();
2524
- this.#setupMessageHandlers();
2525
- this.#startPerformanceMonitoring();
2526
- this.#finishInitialization();
2527
- } catch (error) {
2528
- this.#initializing = false;
2529
- console.error("[SuperSonic] Initialization failed:", error);
2530
- if (this.onError) {
2531
- this.onError(error);
2532
- }
2533
- throw error;
2534
- }
2535
- }
2536
- /**
2537
- * Wait for AudioWorklet to initialize
2538
- */
2539
- #waitForWorkletInit() {
2540
- return new Promise((resolve, reject) => {
2541
- const timeout = setTimeout(() => {
2542
- reject(new Error("AudioWorklet initialization timeout"));
2543
- }, 5e3);
2544
- const messageHandler = async (event) => {
2545
- if (event.data.type === "debug") {
2546
- return;
2547
- }
2548
- if (event.data.type === "error") {
2549
- console.error("[AudioWorklet] Error:", event.data.error);
2550
- clearTimeout(timeout);
2551
- this.#workletNode.port.removeEventListener("message", messageHandler);
2552
- reject(new Error(event.data.error || "AudioWorklet error"));
2553
- return;
2554
- }
2555
- if (event.data.type === "initialized") {
2556
- clearTimeout(timeout);
2557
- this.#workletNode.port.removeEventListener("message", messageHandler);
2558
- if (event.data.success) {
2559
- if (event.data.ringBufferBase !== void 0) {
2560
- this.#ringBufferBase = event.data.ringBufferBase;
2561
- } else {
2562
- console.warn("[SuperSonic] Warning: ringBufferBase not provided by worklet");
2563
- }
2564
- if (event.data.bufferConstants !== void 0) {
2565
- console.log("[SuperSonic] Received bufferConstants from worklet");
2566
- this.#bufferConstants = event.data.bufferConstants;
2567
- console.log("[SuperSonic] Initializing NTP timing (waiting for audio to flow)...");
2568
- await this.initializeNTPTiming();
2569
- this.#startDriftOffsetTimer();
2570
- } else {
2571
- console.warn("[SuperSonic] Warning: bufferConstants not provided by worklet");
2572
- }
2573
- console.log("[SuperSonic] Calling resolve() for worklet initialization");
2574
- resolve();
2575
- } else {
2576
- reject(new Error(event.data.error || "AudioWorklet initialization failed"));
2577
- }
2578
- }
2579
- };
2580
- this.#workletNode.port.addEventListener("message", messageHandler);
2581
- this.#workletNode.port.start();
2582
- });
2583
- }
2584
- /**
2585
- * Set up message handlers for worklet
2586
- */
2587
- #setupMessageHandlers() {
2588
- this.#workletNode.port.onmessage = (event) => {
2589
- const { data } = event;
2590
- switch (data.type) {
2591
- case "error":
2592
- console.error("[Worklet] Error:", data.error);
2593
- if (data.diagnostics) {
2594
- console.error("[Worklet] Diagnostics:", data.diagnostics);
2595
- console.table(data.diagnostics);
2596
- }
2597
- this.#metrics_errors++;
2598
- if (this.onError) {
2599
- this.onError(new Error(data.error));
2600
- }
2601
- break;
2602
- case "process_debug":
2603
- break;
2604
- case "debug":
2605
- break;
2606
- case "console":
2607
- if (this.onConsoleMessage) {
2608
- this.onConsoleMessage(data.message);
2609
- }
2610
- break;
2611
- case "version":
2612
- if (this.onVersion) {
2613
- this.onVersion(data.version);
2614
- }
2615
- break;
2616
- }
2617
- };
2618
- }
2619
- /**
2620
- * Get metrics from SharedArrayBuffer (worklet metrics written by WASM)
2621
- * @returns {Object|null}
2622
- * @private
2623
- */
2624
- #getWorkletMetrics() {
2625
- if (!this.#sharedBuffer || !this.#bufferConstants || !this.#ringBufferBase) {
2626
- return null;
2627
- }
2628
- const metricsBase = this.#ringBufferBase + this.#bufferConstants.METRICS_START;
2629
- const metricsCount = this.#bufferConstants.METRICS_SIZE / 4;
2630
- const metricsView = new Uint32Array(this.#sharedBuffer, metricsBase, metricsCount);
2631
- return {
2632
- processCount: Atomics.load(metricsView, 0),
2633
- // PROCESS_COUNT offset / 4
2634
- bufferOverruns: Atomics.load(metricsView, 1),
2635
- // BUFFER_OVERRUNS offset / 4
2636
- messagesProcessed: Atomics.load(metricsView, 2),
2637
- // MESSAGES_PROCESSED offset / 4
2638
- messagesDropped: Atomics.load(metricsView, 3),
2639
- // MESSAGES_DROPPED offset / 4
2640
- schedulerQueueDepth: Atomics.load(metricsView, 4),
2641
- // SCHEDULER_QUEUE_DEPTH offset / 4
2642
- schedulerQueueMax: Atomics.load(metricsView, 5),
2643
- // SCHEDULER_QUEUE_MAX offset / 4
2644
- schedulerQueueDropped: Atomics.load(metricsView, 6)
2645
- // SCHEDULER_QUEUE_DROPPED offset / 4
2646
- };
2647
- }
2648
- /**
2649
- * Get buffer usage statistics from SAB head/tail pointers
2650
- * @returns {Object|null}
2651
- * @private
2652
- */
2653
- #getBufferUsage() {
2654
- if (!this.#sharedBuffer || !this.#bufferConstants || !this.#ringBufferBase) {
2655
- return null;
2656
- }
2657
- const atomicView = new Int32Array(this.#sharedBuffer);
2658
- const controlBase = this.#ringBufferBase + this.#bufferConstants.CONTROL_START;
2659
- const inHead = Atomics.load(atomicView, (controlBase + 0) / 4);
2660
- const inTail = Atomics.load(atomicView, (controlBase + 4) / 4);
2661
- const outHead = Atomics.load(atomicView, (controlBase + 8) / 4);
2662
- const outTail = Atomics.load(atomicView, (controlBase + 12) / 4);
2663
- const debugHead = Atomics.load(atomicView, (controlBase + 16) / 4);
2664
- const debugTail = Atomics.load(atomicView, (controlBase + 20) / 4);
2665
- const inUsed = (inHead - inTail + this.#bufferConstants.IN_BUFFER_SIZE) % this.#bufferConstants.IN_BUFFER_SIZE;
2666
- const outUsed = (outHead - outTail + this.#bufferConstants.OUT_BUFFER_SIZE) % this.#bufferConstants.OUT_BUFFER_SIZE;
2667
- const debugUsed = (debugHead - debugTail + this.#bufferConstants.DEBUG_BUFFER_SIZE) % this.#bufferConstants.DEBUG_BUFFER_SIZE;
2668
- return {
2669
- inBufferUsed: {
2670
- bytes: inUsed,
2671
- percentage: Math.round(inUsed / this.#bufferConstants.IN_BUFFER_SIZE * 100)
2672
- },
2673
- outBufferUsed: {
2674
- bytes: outUsed,
2675
- percentage: Math.round(outUsed / this.#bufferConstants.OUT_BUFFER_SIZE * 100)
2676
- },
2677
- debugBufferUsed: {
2678
- bytes: debugUsed,
2679
- percentage: Math.round(debugUsed / this.#bufferConstants.DEBUG_BUFFER_SIZE * 100)
2680
- }
2681
- };
2682
- }
2683
- /**
2684
- * Get OSC worker metrics from SharedArrayBuffer (written by OSC workers)
2685
- * @returns {Object|null}
2686
- * @private
2687
- */
2688
- #getOSCMetrics() {
2689
- if (!this.#sharedBuffer || !this.#bufferConstants || !this.#ringBufferBase) {
2690
- return null;
2691
- }
2692
- const metricsBase = this.#ringBufferBase + this.#bufferConstants.METRICS_START;
2693
- const metricsCount = this.#bufferConstants.METRICS_SIZE / 4;
2694
- const metricsView = new Uint32Array(this.#sharedBuffer, metricsBase, metricsCount);
2695
- return {
2696
- // OSC Out (prescheduler) - offsets 7-18
2697
- preschedulerPending: metricsView[7],
2698
- preschedulerPeak: metricsView[8],
2699
- preschedulerSent: metricsView[9],
2700
- bundlesDropped: metricsView[10],
2701
- retriesSucceeded: metricsView[11],
2702
- retriesFailed: metricsView[12],
2703
- bundlesScheduled: metricsView[13],
2704
- eventsCancelled: metricsView[14],
2705
- totalDispatches: metricsView[15],
2706
- messagesRetried: metricsView[16],
2707
- retryQueueSize: metricsView[17],
2708
- retryQueueMax: metricsView[18],
2709
- // OSC In - offsets 19-22
2710
- oscInMessagesReceived: metricsView[19],
2711
- oscInDroppedMessages: metricsView[20],
2712
- oscInWakeups: metricsView[21],
2713
- oscInTimeouts: metricsView[22],
2714
- // Debug - offsets 23-26
2715
- debugMessagesReceived: metricsView[23],
2716
- debugWakeups: metricsView[24],
2717
- debugTimeouts: metricsView[25],
2718
- debugBytesRead: metricsView[26]
2719
- };
2720
- }
2721
- /**
2722
- * Gather metrics from all sources (worklet, OSC, internal counters)
2723
- * All metrics are read synchronously from SAB
2724
- * @returns {SuperSonicMetrics}
2725
- * @private
2726
- */
2727
- #gatherMetrics() {
2728
- const startTime = performance.now();
2729
- const metrics = {
2730
- // SuperSonic counters (in-memory, fast)
2731
- messagesSent: this.#metrics_messagesSent,
2732
- messagesReceived: this.#metrics_messagesReceived,
2733
- errors: this.#metrics_errors
2734
- };
2735
- const workletMetrics = this.#getWorkletMetrics();
2736
- if (workletMetrics) {
2737
- Object.assign(metrics, workletMetrics);
2738
- }
2739
- const bufferUsage = this.#getBufferUsage();
2740
- if (bufferUsage) {
2741
- Object.assign(metrics, bufferUsage);
2742
- }
2743
- const oscMetrics = this.#getOSCMetrics();
2744
- if (oscMetrics) {
2745
- Object.assign(metrics, oscMetrics);
2746
- }
2747
- const totalDuration = performance.now() - startTime;
2748
- if (totalDuration > 1) {
2749
- console.warn(`[SuperSonic] Slow metrics gathering: ${totalDuration.toFixed(2)}ms`);
2750
- }
2751
- return metrics;
2752
- }
2753
- /**
2754
- * Start performance monitoring - gathers metrics from all sources
2755
- * and calls onMetricsUpdate with consolidated snapshot
2756
- */
2757
- #startPerformanceMonitoring() {
2758
- if (this.#metricsIntervalId) {
2759
- clearInterval(this.#metricsIntervalId);
2760
- }
2761
- this.#metricsIntervalId = setInterval(() => {
2762
- if (!this.onMetricsUpdate) return;
2763
- if (this.#metricsGatherInProgress) {
2764
- console.warn("[SuperSonic] Metrics gathering took >100ms, skipping this interval");
2765
- return;
2766
- }
2767
- this.#metricsGatherInProgress = true;
2768
- try {
2769
- const metrics = this.#gatherMetrics();
2770
- this.onMetricsUpdate(metrics);
2771
- } catch (error) {
2772
- console.error("[SuperSonic] Metrics gathering failed:", error);
2773
- } finally {
2774
- this.#metricsGatherInProgress = false;
2775
- }
2776
- }, 100);
2777
- }
2778
- /**
2779
- * Stop performance monitoring
2780
- * @private
2781
- */
2782
- #stopPerformanceMonitoring() {
2783
- if (this.#metricsIntervalId) {
2784
- clearInterval(this.#metricsIntervalId);
2785
- this.#metricsIntervalId = null;
2786
- }
2787
- }
2788
- /**
2789
- * Send OSC message with simplified syntax (auto-detects types)
2790
- * @param {string} address - OSC address
2791
- * @param {...*} args - Arguments (numbers, strings, Uint8Array)
2792
- * @example
2793
- * sonic.send('/notify', 1);
2794
- * sonic.send('/s_new', 'sonic-pi-beep', -1, 0, 0);
2795
- * sonic.send('/n_set', 1000, 'freq', 440.0, 'amp', 0.5);
2796
- */
2797
- async send(address, ...args) {
2798
- this.#ensureInitialized("send OSC messages");
2799
- const oscArgs = args.map((arg) => {
2800
- if (typeof arg === "string") {
2801
- return { type: "s", value: arg };
2802
- } else if (typeof arg === "number") {
2803
- return { type: Number.isInteger(arg) ? "i" : "f", value: arg };
2804
- } else if (arg instanceof Uint8Array || arg instanceof ArrayBuffer) {
2805
- return { type: "b", value: arg instanceof ArrayBuffer ? new Uint8Array(arg) : arg };
2806
- } else {
2807
- throw new Error(`Unsupported argument type: ${typeof arg}`);
2808
- }
2809
- });
2810
- const message = { address, args: oscArgs };
2811
- const oscData = _SuperSonic.osc.encode(message);
2812
- return this.sendOSC(oscData);
2813
- }
2814
- #ensureInitialized(actionDescription = "perform this operation") {
2815
- if (!this.#initialized) {
2816
- throw new Error(`SuperSonic not initialized. Call init() before attempting to ${actionDescription}.`);
2817
- }
2818
- }
2819
- /**
2820
- * Send pre-encoded OSC bytes to scsynth
2821
- * @param {ArrayBuffer|Uint8Array} oscData - Pre-encoded OSC data
2822
- * @param {Object} options - Send options
2823
- */
2824
- async sendOSC(oscData, options = {}) {
2825
- this.#ensureInitialized("send OSC data");
2826
- const uint8Data = this.#toUint8Array(oscData);
2827
- const preparedData = await this.#prepareOutboundPacket(uint8Data);
2828
- this.#metrics_messagesSent++;
2829
- if (this.onMessageSent) {
2830
- this.onMessageSent(preparedData);
2831
- }
2832
- const timing = this.#calculateBundleWait(preparedData);
2833
- const sendOptions = { ...options };
2834
- if (timing) {
2835
- sendOptions.audioTimeS = timing.audioTimeS;
2836
- sendOptions.currentTimeS = timing.currentTimeS;
2837
- }
2838
- this.#osc.send(preparedData, sendOptions);
2839
- }
2840
- /**
2841
- * Get AudioContext instance (read-only)
2842
- * @returns {AudioContext} The AudioContext instance
2843
- */
2844
- get audioContext() {
2845
- return this.#audioContext;
2846
- }
2847
- /**
2848
- * Get AudioWorkletNode instance (read-only)
2849
- * @returns {AudioWorkletNode} The AudioWorkletNode instance
2850
- */
2851
- get workletNode() {
2852
- return this.#workletNode;
2853
- }
2854
- /**
2855
- * Get ScsynthOSC instance (read-only)
2856
- * @returns {ScsynthOSC} The OSC communication layer instance
2857
- */
2858
- get osc() {
2859
- return this.#osc;
2860
- }
2861
- /**
2862
- * Get current status
2863
- */
2864
- getStatus() {
2865
- return {
2866
- initialized: this.#initialized,
2867
- capabilities: this.#capabilities,
2868
- bootStats: this.bootStats,
2869
- audioContextState: this.#audioContext?.state
2870
- };
2871
- }
2872
- /**
2873
- * Get current configuration (merged defaults + user overrides)
2874
- * Useful for debugging and displaying in UI
2875
- * @returns {Object} Current scsynth configuration
2876
- * @example
2877
- * const config = sonic.getConfig();
2878
- * console.log('Buffer limit:', config.worldOptions.numBuffers);
2879
- * console.log('Memory layout:', config.memory);
2880
- */
2881
- getConfig() {
2882
- if (!this.config) {
2883
- return null;
2884
- }
2885
- return {
2886
- memory: { ...this.config.memory },
2887
- worldOptions: { ...this.config.worldOptions }
2888
- };
2889
- }
2890
- /**
2891
- * Destroy the orchestrator and clean up resources
2892
- */
2893
- async destroy() {
2894
- console.log("[SuperSonic] Destroying...");
2895
- this.#stopDriftOffsetTimer();
2896
- this.#stopPerformanceMonitoring();
2897
- if (this.#osc) {
2898
- this.#osc.terminate();
2899
- this.#osc = null;
2900
- }
2901
- if (this.#workletNode) {
2902
- this.#workletNode.disconnect();
2903
- this.#workletNode = null;
2904
- }
2905
- if (this.#audioContext) {
2906
- await this.#audioContext.close();
2907
- this.#audioContext = null;
2908
- }
2909
- if (this.#bufferManager) {
2910
- this.#bufferManager.destroy();
2911
- this.#bufferManager = null;
2912
- }
2913
- this.#sharedBuffer = null;
2914
- this.#initialized = false;
2915
- this.loadedSynthDefs.clear();
2916
- console.log("[SuperSonic] Destroyed");
2917
- }
2918
- /**
2919
- * Get NTP start time for bundle creation.
2920
- * This is the NTP timestamp when AudioContext.currentTime was 0.
2921
- * Bundles should have timestamp = audioContextTime + ntpStartTime
2922
- */
2923
- waitForTimeSync() {
2924
- this.#ensureInitialized("wait for time sync");
2925
- const ntpStartView = new Float64Array(this.#sharedBuffer, this.#ringBufferBase + this.#bufferConstants.NTP_START_TIME_START, 1);
2926
- return ntpStartView[0];
2927
- }
2928
- /**
2929
- * Load a sample into a buffer and wait for confirmation
2930
- * @param {number} bufnum - Buffer number
2931
- * @param {string} path - Audio file path
2932
- * @returns {Promise} Resolves when buffer is ready
2933
- */
2934
- async loadSample(bufnum, path, startFrame = 0, numFrames = 0) {
2935
- this.#ensureInitialized("load samples");
2936
- const bufferInfo = await this.#requireBufferManager().prepareFromFile({
2937
- bufnum,
2938
- path,
2939
- startFrame,
2940
- numFrames
2941
- });
2942
- await this.send(
2943
- "/b_allocPtr",
2944
- bufnum,
2945
- bufferInfo.ptr,
2946
- bufferInfo.numFrames,
2947
- bufferInfo.numChannels,
2948
- bufferInfo.sampleRate,
2949
- bufferInfo.uuid
2950
- );
2951
- return bufferInfo.allocationComplete;
2952
- }
2953
- /**
2954
- * Load a binary synthdef file and send it to scsynth
2955
- * @param {string} path - Path or URL to the .scsyndef file
2956
- * @returns {Promise<void>}
2957
- * @example
2958
- * await sonic.loadSynthDef('./extra/synthdefs/sonic-pi-beep.scsyndef');
2959
- */
2960
- async loadSynthDef(path) {
2961
- if (!this.#initialized) {
2962
- throw new Error("SuperSonic not initialized. Call init() first.");
2963
- }
2964
- try {
2965
- const response = await fetch(path);
2966
- if (!response.ok) {
2967
- throw new Error(`Failed to load synthdef from ${path}: ${response.status} ${response.statusText}`);
2968
- }
2969
- const arrayBuffer = await response.arrayBuffer();
2970
- const synthdefData = new Uint8Array(arrayBuffer);
2971
- await this.send("/d_recv", synthdefData);
2972
- const synthName = this.#extractSynthDefName(path);
2973
- if (synthName) {
2974
- this.loadedSynthDefs.add(synthName);
2975
- }
2976
- console.log(`[SuperSonic] Sent synthdef from ${path} (${synthdefData.length} bytes)`);
2977
- } catch (error) {
2978
- console.error("[SuperSonic] Failed to load synthdef:", error);
2979
- throw error;
2980
- }
2981
- }
2982
- /**
2983
- * Load multiple synthdefs from a directory
2984
- * @param {string[]} names - Array of synthdef names (without .scsyndef extension)
2985
- * @returns {Promise<Object>} Map of name -> success/error
2986
- * @example
2987
- * const results = await sonic.loadSynthDefs(['sonic-pi-beep', 'sonic-pi-tb303']);
2988
- */
2989
- async loadSynthDefs(names) {
2990
- if (!this.#initialized) {
2991
- throw new Error("SuperSonic not initialized. Call init() first.");
2992
- }
2993
- if (!this.#synthdefBaseURL) {
2994
- throw new Error(
2995
- 'synthdefBaseURL not configured. Please set it in SuperSonic constructor options.\nExample: new SuperSonic({ synthdefBaseURL: "./dist/synthdefs/" })\nOr use CDN: new SuperSonic({ synthdefBaseURL: "https://unpkg.com/supersonic-scsynth-synthdefs@latest/synthdefs/" })\nOr install: npm install supersonic-scsynth-synthdefs'
2996
- );
2997
- }
2998
- const results = {};
2999
- await Promise.all(
3000
- names.map(async (name) => {
3001
- try {
3002
- const path = `${this.#synthdefBaseURL}${name}.scsyndef`;
3003
- await this.loadSynthDef(path);
3004
- results[name] = { success: true };
3005
- } catch (error) {
3006
- console.error(`[SuperSonic] Failed to load ${name}:`, error);
3007
- results[name] = { success: false, error: error.message };
3008
- }
3009
- })
3010
- );
3011
- const successCount = Object.values(results).filter((r) => r.success).length;
3012
- console.log(`[SuperSonic] Sent ${successCount}/${names.length} synthdef loads`);
3013
- return results;
3014
- }
3015
- /**
3016
- * Send /sync command and wait for /synced response
3017
- * Use this to ensure all previous asynchronous commands have completed
3018
- * @param {number} syncId - Unique integer identifier for this sync operation
3019
- * @returns {Promise<void>}
3020
- * @example
3021
- * await sonic.loadSynthDefs(['synth1', 'synth2']);
3022
- * await sonic.sync(12345); // Wait for all synthdefs to be processed
3023
- */
3024
- async sync(syncId) {
3025
- if (!this.#initialized) {
3026
- throw new Error("SuperSonic not initialized. Call init() first.");
3027
- }
3028
- if (!Number.isInteger(syncId)) {
3029
- throw new Error("sync() requires an integer syncId parameter");
3030
- }
3031
- const syncPromise = new Promise((resolve, reject) => {
3032
- const timeout = setTimeout(() => {
3033
- if (this.#syncListeners) {
3034
- this.#syncListeners.delete(syncId);
3035
- }
3036
- reject(new Error("Timeout waiting for /synced response"));
3037
- }, 1e4);
3038
- const messageHandler = (msg) => {
3039
- clearTimeout(timeout);
3040
- this.#syncListeners.delete(syncId);
3041
- resolve();
3042
- };
3043
- if (!this.#syncListeners) {
3044
- this.#syncListeners = /* @__PURE__ */ new Map();
3045
- }
3046
- this.#syncListeners.set(syncId, messageHandler);
3047
- });
3048
- await this.send("/sync", syncId);
3049
- await syncPromise;
3050
- }
3051
- /**
3052
- * Allocate memory for an audio buffer (includes guard samples)
3053
- * @param {number} numSamples - Number of Float32 samples to allocate
3054
- * @returns {number} Byte offset into SharedArrayBuffer, or 0 if allocation failed
3055
- * @example
3056
- * const bufferAddr = sonic.allocBuffer(44100); // Allocate 1 second at 44.1kHz
3057
- */
3058
- allocBuffer(numSamples) {
3059
- this.#ensureInitialized("allocate buffers");
3060
- return this.#bufferManager.allocate(numSamples);
3061
- }
3062
- /**
3063
- * Free a previously allocated buffer
3064
- * @param {number} addr - Buffer address returned by allocBuffer()
3065
- * @returns {boolean} true if freed successfully
3066
- * @example
3067
- * sonic.freeBuffer(bufferAddr);
3068
- */
3069
- freeBuffer(addr) {
3070
- this.#ensureInitialized("free buffers");
3071
- return this.#bufferManager.free(addr);
3072
- }
3073
- /**
3074
- * Get a Float32Array view of an allocated buffer
3075
- * @param {number} addr - Buffer address returned by allocBuffer()
3076
- * @param {number} numSamples - Number of Float32 samples
3077
- * @returns {Float32Array} Typed array view into the buffer
3078
- * @example
3079
- * const view = sonic.getBufferView(bufferAddr, 44100);
3080
- * view[0] = 1.0; // Write to buffer
3081
- */
3082
- getBufferView(addr, numSamples) {
3083
- this.#ensureInitialized("get buffer views");
3084
- return this.#bufferManager.getView(addr, numSamples);
3085
- }
3086
- /**
3087
- * Get buffer pool statistics
3088
- * @returns {Object} Stats including total, available, used, etc.
3089
- * @example
3090
- * const stats = sonic.getBufferPoolStats();
3091
- * console.log(`Available: ${stats.available} bytes`);
3092
- */
3093
- getBufferPoolStats() {
3094
- this.#ensureInitialized("get buffer pool stats");
3095
- return this.#bufferManager.getStats();
3096
- }
3097
- getDiagnostics() {
3098
- this.#ensureInitialized("get diagnostics");
3099
- return {
3100
- buffers: this.#bufferManager.getDiagnostics(),
3101
- synthdefs: {
3102
- count: this.loadedSynthDefs.size
3103
- }
3104
- };
3105
- }
3106
- /**
3107
- * Initialize NTP timing (write-once)
3108
- * Sets the NTP start time when AudioContext started
3109
- * Blocks until audio is actually flowing (contextTime > 0)
3110
- * @private
3111
- */
3112
- async initializeNTPTiming() {
3113
- if (!this.#bufferConstants || !this.#audioContext) {
3114
- return;
3115
- }
3116
- let timestamp;
3117
- while (true) {
3118
- timestamp = this.#audioContext.getOutputTimestamp();
3119
- if (timestamp.contextTime > 0) {
3120
- break;
3121
- }
3122
- await new Promise((resolve) => setTimeout(resolve, 50));
3123
- }
3124
- const perfTimeMs = performance.timeOrigin + timestamp.performanceTime;
3125
- const currentNTP = perfTimeMs / 1e3 + NTP_EPOCH_OFFSET;
3126
- const ntpStartTime = currentNTP - timestamp.contextTime;
3127
- const ntpStartView = new Float64Array(
3128
- this.#sharedBuffer,
3129
- this.#ringBufferBase + this.#bufferConstants.NTP_START_TIME_START,
3130
- 1
3131
- );
3132
- ntpStartView[0] = ntpStartTime;
3133
- this.#initialNTPStartTime = ntpStartTime;
3134
- console.log(`[SuperSonic] NTP timing initialized: start=${ntpStartTime.toFixed(6)}s (NTP=${currentNTP.toFixed(3)}s, contextTime=${timestamp.contextTime.toFixed(3)}s)`);
3135
- }
3136
- /**
3137
- * Update drift offset (AudioContext → NTP drift correction)
3138
- * CRITICAL: This REPLACES the drift value, does not accumulate
3139
- * @private
3140
- */
3141
- updateDriftOffset() {
3142
- if (!this.#bufferConstants || !this.#audioContext || this.#initialNTPStartTime === void 0) {
3143
- return;
3144
- }
3145
- const timestamp = this.#audioContext.getOutputTimestamp();
3146
- const perfTimeMs = performance.timeOrigin + timestamp.performanceTime;
3147
- const currentNTP = perfTimeMs / 1e3 + NTP_EPOCH_OFFSET;
3148
- const expectedContextTime = currentNTP - this.#initialNTPStartTime;
3149
- const driftSeconds = expectedContextTime - timestamp.contextTime;
3150
- const driftMs = Math.round(driftSeconds * 1e3);
3151
- const driftView = new Int32Array(
3152
- this.#sharedBuffer,
3153
- this.#ringBufferBase + this.#bufferConstants.DRIFT_OFFSET_START,
3154
- 1
3155
- );
3156
- Atomics.store(driftView, 0, driftMs);
3157
- console.log(`[SuperSonic] Drift offset: ${driftMs}ms (expected=${expectedContextTime.toFixed(3)}s, actual=${timestamp.contextTime.toFixed(3)}s, NTP=${currentNTP.toFixed(3)}s)`);
3158
- }
3159
- /**
3160
- * Get current drift offset in milliseconds
3161
- * @returns {number} Current drift in milliseconds
3162
- */
3163
- getDriftOffset() {
3164
- if (!this.#bufferConstants) {
3165
- return 0;
3166
- }
3167
- const driftView = new Int32Array(
3168
- this.#sharedBuffer,
3169
- this.#ringBufferBase + this.#bufferConstants.DRIFT_OFFSET_START,
3170
- 1
3171
- );
3172
- return Atomics.load(driftView, 0);
3173
- }
3174
- /**
3175
- * Start periodic drift offset updates
3176
- * @private
3177
- */
3178
- #startDriftOffsetTimer() {
3179
- this.#stopDriftOffsetTimer();
3180
- this.#driftOffsetTimer = setInterval(() => {
3181
- this.updateDriftOffset();
3182
- }, DRIFT_UPDATE_INTERVAL_MS);
3183
- console.log(`[SuperSonic] Started drift offset correction (every ${DRIFT_UPDATE_INTERVAL_MS}ms)`);
3184
- }
3185
- /**
3186
- * Stop periodic drift offset updates
3187
- * @private
3188
- */
3189
- #stopDriftOffsetTimer() {
3190
- if (this.#driftOffsetTimer) {
3191
- clearInterval(this.#driftOffsetTimer);
3192
- this.#driftOffsetTimer = null;
3193
- }
3194
- }
3195
- #extractSynthDefName(path) {
3196
- if (!path || typeof path !== "string") {
3197
- return null;
3198
- }
3199
- const lastSegment = path.split("/").filter(Boolean).pop() || path;
3200
- return lastSegment.replace(/\.scsyndef$/i, "");
3201
- }
3202
- #toUint8Array(data) {
3203
- if (data instanceof Uint8Array) {
3204
- return data;
3205
- }
3206
- if (data instanceof ArrayBuffer) {
3207
- return new Uint8Array(data);
3208
- }
3209
- throw new Error("oscData must be ArrayBuffer or Uint8Array");
3210
- }
3211
- async #prepareOutboundPacket(uint8Data) {
3212
- const decodeOptions = { metadata: true, unpackSingleArgs: false };
3213
- try {
3214
- const decodedPacket = _SuperSonic.osc.decode(uint8Data, decodeOptions);
3215
- const { packet, changed } = await this.#rewritePacket(decodedPacket);
3216
- if (!changed) {
3217
- return uint8Data;
3218
- }
3219
- return _SuperSonic.osc.encode(packet);
3220
- } catch (error) {
3221
- console.error("[SuperSonic] Failed to prepare OSC packet:", error);
3222
- throw error;
3223
- }
3224
- }
3225
- async #rewritePacket(packet) {
3226
- if (packet && packet.address) {
3227
- const { message, changed } = await this.#rewriteMessage(packet);
3228
- return { packet: message, changed };
3229
- }
3230
- if (this.#isBundle(packet)) {
3231
- const subResults = await Promise.all(
3232
- packet.packets.map((subPacket) => this.#rewritePacket(subPacket))
3233
- );
3234
- const changed = subResults.some((result) => result.changed);
3235
- if (!changed) {
3236
- return { packet, changed: false };
3237
- }
3238
- const rewrittenPackets = subResults.map((result) => result.packet);
3239
- return {
3240
- packet: {
3241
- timeTag: packet.timeTag,
3242
- packets: rewrittenPackets
3243
- },
3244
- changed: true
3245
- };
3246
- }
3247
- return { packet, changed: false };
3248
- }
3249
- async #rewriteMessage(message) {
3250
- switch (message.address) {
3251
- case "/b_alloc":
3252
- return {
3253
- message: await this.#rewriteAlloc(message),
3254
- changed: true
3255
- };
3256
- case "/b_allocRead":
3257
- return {
3258
- message: await this.#rewriteAllocRead(message),
3259
- changed: true
3260
- };
3261
- case "/b_allocReadChannel":
3262
- return {
3263
- message: await this.#rewriteAllocReadChannel(message),
3264
- changed: true
3265
- };
3266
- default:
3267
- return { message, changed: false };
3268
- }
3269
- }
3270
- async #rewriteAllocRead(message) {
3271
- const bufferManager = this.#requireBufferManager();
3272
- const bufnum = this.#requireIntArg(message.args, 0, "/b_allocRead requires a buffer number");
3273
- const path = this.#requireStringArg(message.args, 1, "/b_allocRead requires a file path");
3274
- const startFrame = this.#optionalIntArg(message.args, 2, 0);
3275
- const numFrames = this.#optionalIntArg(message.args, 3, 0);
3276
- const bufferInfo = await bufferManager.prepareFromFile({
3277
- bufnum,
3278
- path,
3279
- startFrame,
3280
- numFrames
3281
- });
3282
- this.#detachAllocationPromise(bufferInfo.allocationComplete, `/b_allocRead ${bufnum}`);
3283
- return this.#buildAllocPtrMessage(bufnum, bufferInfo);
3284
- }
3285
- async #rewriteAllocReadChannel(message) {
3286
- const bufferManager = this.#requireBufferManager();
3287
- const bufnum = this.#requireIntArg(message.args, 0, "/b_allocReadChannel requires a buffer number");
3288
- const path = this.#requireStringArg(message.args, 1, "/b_allocReadChannel requires a file path");
3289
- const startFrame = this.#optionalIntArg(message.args, 2, 0);
3290
- const numFrames = this.#optionalIntArg(message.args, 3, 0);
3291
- const channels = [];
3292
- for (let i = 4; i < (message.args?.length || 0); i++) {
3293
- if (!this.#isNumericArg(message.args[i])) {
3294
- break;
3295
- }
3296
- channels.push(Math.floor(this.#getArgValue(message.args[i])));
3297
- }
3298
- const bufferInfo = await bufferManager.prepareFromFile({
3299
- bufnum,
3300
- path,
3301
- startFrame,
3302
- numFrames,
3303
- channels: channels.length > 0 ? channels : null
3304
- });
3305
- this.#detachAllocationPromise(bufferInfo.allocationComplete, `/b_allocReadChannel ${bufnum}`);
3306
- return this.#buildAllocPtrMessage(bufnum, bufferInfo);
3307
- }
3308
- async #rewriteAlloc(message) {
3309
- const bufferManager = this.#requireBufferManager();
3310
- const bufnum = this.#requireIntArg(message.args, 0, "/b_alloc requires a buffer number");
3311
- const numFrames = this.#requireIntArg(message.args, 1, "/b_alloc requires a frame count");
3312
- let argIndex = 2;
3313
- let numChannels = 1;
3314
- let sampleRate = this.#audioContext?.sampleRate || 44100;
3315
- if (this.#isNumericArg(this.#argAt(message.args, argIndex))) {
3316
- numChannels = Math.max(1, this.#optionalIntArg(message.args, argIndex, 1));
3317
- argIndex++;
3318
- }
3319
- if (this.#argAt(message.args, argIndex)?.type === "b") {
3320
- argIndex++;
3321
- }
3322
- if (this.#isNumericArg(this.#argAt(message.args, argIndex))) {
3323
- sampleRate = this.#getArgValue(this.#argAt(message.args, argIndex));
3324
- }
3325
- const bufferInfo = await bufferManager.prepareEmpty({
3326
- bufnum,
3327
- numFrames,
3328
- numChannels,
3329
- sampleRate
3330
- });
3331
- this.#detachAllocationPromise(bufferInfo.allocationComplete, `/b_alloc ${bufnum}`);
3332
- return this.#buildAllocPtrMessage(bufnum, bufferInfo);
3333
- }
3334
- #buildAllocPtrMessage(bufnum, bufferInfo) {
3335
- return {
3336
- address: "/b_allocPtr",
3337
- args: [
3338
- this.#intArg(bufnum),
3339
- this.#intArg(bufferInfo.ptr),
3340
- this.#intArg(bufferInfo.numFrames),
3341
- this.#intArg(bufferInfo.numChannels),
3342
- this.#floatArg(bufferInfo.sampleRate),
3343
- this.#stringArg(bufferInfo.uuid)
3344
- ]
3345
- };
3346
- }
3347
- #intArg(value) {
3348
- return { type: "i", value: Math.floor(value) };
3349
- }
3350
- #floatArg(value) {
3351
- return { type: "f", value };
3352
- }
3353
- #stringArg(value) {
3354
- return { type: "s", value: String(value) };
3355
- }
3356
- #argAt(args, index) {
3357
- if (!Array.isArray(args)) {
3358
- return void 0;
3359
- }
3360
- return args[index];
3361
- }
3362
- #getArgValue(arg) {
3363
- if (arg === void 0 || arg === null) {
3364
- return void 0;
3365
- }
3366
- return typeof arg === "object" && Object.prototype.hasOwnProperty.call(arg, "value") ? arg.value : arg;
3367
- }
3368
- #requireIntArg(args, index, errorMessage) {
3369
- const value = this.#getArgValue(this.#argAt(args, index));
3370
- if (!Number.isFinite(value)) {
3371
- throw new Error(errorMessage);
3372
- }
3373
- return Math.floor(value);
3374
- }
3375
- #optionalIntArg(args, index, defaultValue = 0) {
3376
- const value = this.#getArgValue(this.#argAt(args, index));
3377
- if (!Number.isFinite(value)) {
3378
- return defaultValue;
3379
- }
3380
- return Math.floor(value);
3381
- }
3382
- #requireStringArg(args, index, errorMessage) {
3383
- const value = this.#getArgValue(this.#argAt(args, index));
3384
- if (typeof value !== "string") {
3385
- throw new Error(errorMessage);
3386
- }
3387
- return value;
3388
- }
3389
- #isNumericArg(arg) {
3390
- if (!arg) {
3391
- return false;
3392
- }
3393
- const value = this.#getArgValue(arg);
3394
- return Number.isFinite(value);
3395
- }
3396
- #detachAllocationPromise(promise, context) {
3397
- if (!promise || typeof promise.catch !== "function") {
3398
- return;
3399
- }
3400
- promise.catch((error) => {
3401
- console.error(`[SuperSonic] ${context} allocation failed:`, error);
3402
- });
3403
- }
3404
- #requireBufferManager() {
3405
- if (!this.#bufferManager) {
3406
- throw new Error("Buffer manager not ready. Call init() before issuing buffer commands.");
3407
- }
3408
- return this.#bufferManager;
3409
- }
3410
- #isBundle(packet) {
3411
- return packet && packet.timeTag !== void 0 && Array.isArray(packet.packets);
3412
- }
3413
- #calculateBundleWait(uint8Data) {
3414
- if (uint8Data.length < 16) {
3415
- return null;
3416
- }
3417
- const header = String.fromCharCode.apply(null, uint8Data.slice(0, 8));
3418
- if (header !== "#bundle\0") {
3419
- return null;
3420
- }
3421
- const ntpStartView = new Float64Array(this.#sharedBuffer, this.#ringBufferBase + this.#bufferConstants.NTP_START_TIME_START, 1);
3422
- const ntpStartTime = ntpStartView[0];
3423
- if (ntpStartTime === 0) {
3424
- console.warn("[SuperSonic] NTP start time not yet initialized");
3425
- return null;
3426
- }
3427
- const driftView = new Int32Array(this.#sharedBuffer, this.#ringBufferBase + this.#bufferConstants.DRIFT_OFFSET_START, 1);
3428
- const driftMs = Atomics.load(driftView, 0);
3429
- const driftSeconds = driftMs / 1e3;
3430
- const globalView = new Int32Array(this.#sharedBuffer, this.#ringBufferBase + this.#bufferConstants.GLOBAL_OFFSET_START, 1);
3431
- const globalMs = Atomics.load(globalView, 0);
3432
- const globalSeconds = globalMs / 1e3;
3433
- const totalOffset = ntpStartTime + driftSeconds + globalSeconds;
3434
- const view = new DataView(uint8Data.buffer, uint8Data.byteOffset);
3435
- const ntpSeconds = view.getUint32(8, false);
3436
- const ntpFraction = view.getUint32(12, false);
3437
- if (ntpSeconds === 0 && (ntpFraction === 0 || ntpFraction === 1)) {
3438
- return null;
3439
- }
3440
- const ntpTimeS = ntpSeconds + ntpFraction / 4294967296;
3441
- const audioTimeS = ntpTimeS - totalOffset;
3442
- const currentTimeS = this.#audioContext.currentTime;
3443
- return { audioTimeS, currentTimeS };
3444
- }
3445
- };
3446
- export {
3447
- SuperSonic
3448
- };
1
+ var n={},n=n||{};(function(){"use strict";n.SECS_70YRS=2208988800,n.TWO_32=4294967296,n.defaults={metadata:!1,unpackSingleArgs:!0},n.isCommonJS=!!(typeof module<"u"&&module.exports),n.isNode=n.isCommonJS&&typeof window>"u",n.isElectron=!!(typeof process<"u"&&process.versions&&process.versions.electron),n.isBufferEnv=n.isNode||n.isElectron,n.isArray=function(s){return s&&Object.prototype.toString.call(s)==="[object Array]"},n.isTypedArrayView=function(s){return s.buffer&&s.buffer instanceof ArrayBuffer},n.isBuffer=function(s){return n.isBufferEnv&&s instanceof Buffer},n.Long=typeof Long<"u"?Long:void 0,n.TextDecoder=typeof TextDecoder<"u"?new TextDecoder("utf-8"):typeof util<"u"&&typeof(util.TextDecoder!=="undefined")?new util.TextDecoder("utf-8"):void 0,n.TextEncoder=typeof TextEncoder<"u"?new TextEncoder("utf-8"):typeof util<"u"&&typeof(util.TextEncoder!=="undefined")?new util.TextEncoder("utf-8"):void 0,n.dataView=function(s,e,t){return s.buffer?new DataView(s.buffer,e,t):s instanceof ArrayBuffer?new DataView(s,e,t):new DataView(new Uint8Array(s),e,t)},n.byteArray=function(s){if(s instanceof Uint8Array)return s;var e=s.buffer?s.buffer:s;if(!(e instanceof ArrayBuffer)&&(typeof e.length>"u"||typeof e=="string"))throw new Error("Can't wrap a non-array-like object as Uint8Array. Object was: "+JSON.stringify(s,null,2));return new Uint8Array(e)},n.nativeBuffer=function(s){return n.isBufferEnv?n.isBuffer(s)?s:Buffer.from(s.buffer?s:new Uint8Array(s)):n.isTypedArrayView(s)?s:new Uint8Array(s)},n.copyByteArray=function(s,e,t){if(n.isTypedArrayView(s)&&n.isTypedArrayView(e))e.set(s,t);else for(var r=t===void 0?0:t,i=Math.min(e.length-t,s.length),o=0,a=r;o<i;o++,a++)e[a]=s[o];return e},n.readString=function(s,e){for(var t=[],r=e.idx;r<s.byteLength;r++){var i=s.getUint8(r);if(i!==0)t.push(i);else{r++;break}}r=r+3&-4,e.idx=r;var o=n.isBufferEnv?n.readString.withBuffer:n.TextDecoder?n.readString.withTextDecoder:n.readString.raw;return o(t)},n.readString.raw=function(s){for(var e="",t=1e4,r=0;r<s.length;r+=t)e+=String.fromCharCode.apply(null,s.slice(r,r+t));return e},n.readString.withTextDecoder=function(s){var e=new Int8Array(s);return n.TextDecoder.decode(e)},n.readString.withBuffer=function(s){return Buffer.from(s).toString("utf-8")},n.writeString=function(s){var e=n.isBufferEnv?n.writeString.withBuffer:n.TextEncoder?n.writeString.withTextEncoder:null,t=s+"\0",r;e&&(r=e(t));for(var i=e?r.length:t.length,o=i+3&-4,a=new Uint8Array(o),l=0;l<i-1;l++){var u=e?r[l]:t.charCodeAt(l);a[l]=u}return a},n.writeString.withTextEncoder=function(s){return n.TextEncoder.encode(s)},n.writeString.withBuffer=function(s){return Buffer.from(s)},n.readPrimitive=function(s,e,t,r){var i=s[e](r.idx,!1);return r.idx+=t,i},n.writePrimitive=function(s,e,t,r,i){i=i===void 0?0:i;var o;return e?o=new Uint8Array(e.buffer):(o=new Uint8Array(r),e=new DataView(o.buffer)),e[t](i,s,!1),o},n.readInt32=function(s,e){return n.readPrimitive(s,"getInt32",4,e)},n.writeInt32=function(s,e,t){return n.writePrimitive(s,e,"setInt32",4,t)},n.readInt64=function(s,e){var t=n.readPrimitive(s,"getInt32",4,e),r=n.readPrimitive(s,"getInt32",4,e);return n.Long?new n.Long(r,t):{high:t,low:r,unsigned:!1}},n.writeInt64=function(s,e,t){var r=new Uint8Array(8);return r.set(n.writePrimitive(s.high,e,"setInt32",4,t),0),r.set(n.writePrimitive(s.low,e,"setInt32",4,t+4),4),r},n.readFloat32=function(s,e){return n.readPrimitive(s,"getFloat32",4,e)},n.writeFloat32=function(s,e,t){return n.writePrimitive(s,e,"setFloat32",4,t)},n.readFloat64=function(s,e){return n.readPrimitive(s,"getFloat64",8,e)},n.writeFloat64=function(s,e,t){return n.writePrimitive(s,e,"setFloat64",8,t)},n.readChar32=function(s,e){var t=n.readPrimitive(s,"getUint32",4,e);return String.fromCharCode(t)},n.writeChar32=function(s,e,t){var r=s.charCodeAt(0);if(!(r===void 0||r<-1))return n.writePrimitive(r,e,"setUint32",4,t)},n.readBlob=function(s,e){var t=n.readInt32(s,e),r=t+3&-4,i=new Uint8Array(s.buffer,e.idx,t);return e.idx+=r,i},n.writeBlob=function(s){s=n.byteArray(s);var e=s.byteLength,t=e+3&-4,r=4,i=t+r,o=new Uint8Array(i),a=new DataView(o.buffer);return n.writeInt32(e,a),o.set(s,r),o},n.readMIDIBytes=function(s,e){var t=new Uint8Array(s.buffer,e.idx,4);return e.idx+=4,t},n.writeMIDIBytes=function(s){s=n.byteArray(s);var e=new Uint8Array(4);return e.set(s),e},n.readColor=function(s,e){var t=new Uint8Array(s.buffer,e.idx,4),r=t[3]/255;return e.idx+=4,{r:t[0],g:t[1],b:t[2],a:r}},n.writeColor=function(s){var e=Math.round(s.a*255),t=new Uint8Array([s.r,s.g,s.b,e]);return t},n.readTrue=function(){return!0},n.readFalse=function(){return!1},n.readNull=function(){return null},n.readImpulse=function(){return 1},n.readTimeTag=function(s,e){var t=n.readPrimitive(s,"getUint32",4,e),r=n.readPrimitive(s,"getUint32",4,e),i=t===0&&r===1?Date.now():n.ntpToJSTime(t,r);return{raw:[t,r],native:i}},n.writeTimeTag=function(s){var e=s.raw?s.raw:n.jsToNTPTime(s.native),t=new Uint8Array(8),r=new DataView(t.buffer);return n.writeInt32(e[0],r,0),n.writeInt32(e[1],r,4),t},n.timeTag=function(s,e){s=s||0,e=e||Date.now();var t=e/1e3,r=Math.floor(t),i=t-r,o=Math.floor(s),a=s-o,l=i+a;if(l>1){var u=Math.floor(l),c=l-u;o+=u,l=c}var f=r+o+n.SECS_70YRS,h=Math.round(n.TWO_32*l);return{raw:[f,h]}},n.ntpToJSTime=function(s,e){var t=s-n.SECS_70YRS,r=e/n.TWO_32,i=(t+r)*1e3;return i},n.jsToNTPTime=function(s){var e=s/1e3,t=Math.floor(e),r=e-t,i=t+n.SECS_70YRS,o=Math.round(n.TWO_32*r);return[i,o]},n.readArguments=function(s,e,t){var r=n.readString(s,t);if(r.indexOf(",")!==0)throw new Error("A malformed type tag string was found while reading the arguments of an OSC message. String was: "+r," at offset: "+t.idx);var i=r.substring(1).split(""),o=[];return n.readArgumentsIntoArray(o,i,r,s,e,t),o},n.readArgument=function(s,e,t,r,i){var o=n.argumentTypes[s];if(!o)throw new Error("'"+s+"' is not a valid OSC type tag. Type tag string was: "+e);var a=o.reader,l=n[a](t,i);return r.metadata&&(l={type:s,value:l}),l},n.readArgumentsIntoArray=function(s,e,t,r,i,o){for(var a=0;a<e.length;){var l=e[a],u;if(l==="["){var c=e.slice(a+1),f=c.indexOf("]");if(f<0)throw new Error("Invalid argument type tag: an open array type tag ('[') was found without a matching close array tag ('[]'). Type tag was: "+t);var h=c.slice(0,f);u=n.readArgumentsIntoArray([],h,t,r,i,o),a+=f+2}else u=n.readArgument(l,t,r,i,o),a++;s.push(u)}return s},n.writeArguments=function(s,e){var t=n.collectArguments(s,e);return n.joinParts(t)},n.joinParts=function(s){for(var e=new Uint8Array(s.byteLength),t=s.parts,r=0,i=0;i<t.length;i++){var o=t[i];n.copyByteArray(o,e,r),r+=o.length}return e},n.addDataPart=function(s,e){e.parts.push(s),e.byteLength+=s.length},n.writeArrayArguments=function(s,e){for(var t="[",r=0;r<s.length;r++){var i=s[r];t+=n.writeArgument(i,e)}return t+="]",t},n.writeArgument=function(s,e){if(n.isArray(s))return n.writeArrayArguments(s,e);var t=s.type,r=n.argumentTypes[t].writer;if(r){var i=n[r](s.value);n.addDataPart(i,e)}return s.type},n.collectArguments=function(s,e,t){n.isArray(s)||(s=typeof s>"u"?[]:[s]),t=t||{byteLength:0,parts:[]},e.metadata||(s=n.annotateArguments(s));for(var r=",",i=t.parts.length,o=0;o<s.length;o++){var a=s[o];r+=n.writeArgument(a,t)}var l=n.writeString(r);return t.byteLength+=l.byteLength,t.parts.splice(i,0,l),t},n.readMessage=function(s,e,t){e=e||n.defaults;var r=n.dataView(s,s.byteOffset,s.byteLength);t=t||{idx:0};var i=n.readString(r,t);return n.readMessageContents(i,r,e,t)},n.readMessageContents=function(s,e,t,r){if(s.indexOf("/")!==0)throw new Error("A malformed OSC address was found while reading an OSC message. String was: "+s);var i=n.readArguments(e,t,r);return{address:s,args:i.length===1&&t.unpackSingleArgs?i[0]:i}},n.collectMessageParts=function(s,e,t){return t=t||{byteLength:0,parts:[]},n.addDataPart(n.writeString(s.address),t),n.collectArguments(s.args,e,t)},n.writeMessage=function(s,e){if(e=e||n.defaults,!n.isValidMessage(s))throw new Error("An OSC message must contain a valid address. Message was: "+JSON.stringify(s,null,2));var t=n.collectMessageParts(s,e);return n.joinParts(t)},n.isValidMessage=function(s){return s.address&&s.address.indexOf("/")===0},n.readBundle=function(s,e,t){return n.readPacket(s,e,t)},n.collectBundlePackets=function(s,e,t){t=t||{byteLength:0,parts:[]},n.addDataPart(n.writeString("#bundle"),t),n.addDataPart(n.writeTimeTag(s.timeTag),t);for(var r=0;r<s.packets.length;r++){var i=s.packets[r],o=i.address?n.collectMessageParts:n.collectBundlePackets,a=o(i,e);t.byteLength+=a.byteLength,n.addDataPart(n.writeInt32(a.byteLength),t),t.parts=t.parts.concat(a.parts)}return t},n.writeBundle=function(s,e){if(!n.isValidBundle(s))throw new Error("An OSC bundle must contain 'timeTag' and 'packets' properties. Bundle was: "+JSON.stringify(s,null,2));e=e||n.defaults;var t=n.collectBundlePackets(s,e);return n.joinParts(t)},n.isValidBundle=function(s){return s.timeTag!==void 0&&s.packets!==void 0},n.readBundleContents=function(s,e,t,r){for(var i=n.readTimeTag(s,t),o=[];t.idx<r;){var a=n.readInt32(s,t),l=t.idx+a,u=n.readPacket(s,e,t,l);o.push(u)}return{timeTag:i,packets:o}},n.readPacket=function(s,e,t,r){var i=n.dataView(s,s.byteOffset,s.byteLength);r=r===void 0?i.byteLength:r,t=t||{idx:0};var o=n.readString(i,t),a=o[0];if(a==="#")return n.readBundleContents(i,e,t,r);if(a==="/")return n.readMessageContents(o,i,e,t);throw new Error("The header of an OSC packet didn't contain an OSC address or a #bundle string. Header was: "+o)},n.writePacket=function(s,e){if(n.isValidMessage(s))return n.writeMessage(s,e);if(n.isValidBundle(s))return n.writeBundle(s,e);throw new Error("The specified packet was not recognized as a valid OSC message or bundle. Packet was: "+JSON.stringify(s,null,2))},n.argumentTypes={i:{reader:"readInt32",writer:"writeInt32"},h:{reader:"readInt64",writer:"writeInt64"},f:{reader:"readFloat32",writer:"writeFloat32"},s:{reader:"readString",writer:"writeString"},S:{reader:"readString",writer:"writeString"},b:{reader:"readBlob",writer:"writeBlob"},t:{reader:"readTimeTag",writer:"writeTimeTag"},T:{reader:"readTrue"},F:{reader:"readFalse"},N:{reader:"readNull"},I:{reader:"readImpulse"},d:{reader:"readFloat64",writer:"writeFloat64"},c:{reader:"readChar32",writer:"writeChar32"},r:{reader:"readColor",writer:"writeColor"},m:{reader:"readMIDIBytes",writer:"writeMIDIBytes"}},n.inferTypeForArgument=function(s){var e=typeof s;switch(e){case"boolean":return s?"T":"F";case"string":return"s";case"number":return"f";case"undefined":return"N";case"object":if(s===null)return"N";if(s instanceof Uint8Array||s instanceof ArrayBuffer)return"b";if(typeof s.high=="number"&&typeof s.low=="number")return"h";break}throw new Error("Can't infer OSC argument type for value: "+JSON.stringify(s,null,2))},n.annotateArguments=function(s){for(var e=[],t=0;t<s.length;t++){var r=s[t],i;if(typeof r=="object"&&r.type&&r.value!==void 0)i=r;else if(n.isArray(r))i=n.annotateArguments(r);else{var o=n.inferTypeForArgument(r);i={type:o,value:r}}e.push(i)}return e}})();var S=function(){};S.prototype.on=function(){};S.prototype.emit=function(){};S.prototype.removeListener=function(){};(function(){"use strict";n.supportsSerial=!1,n.firePacketEvents=function(e,t,r,i){t.address?e.emit("message",t,r,i):n.fireBundleEvents(e,t,r,i)},n.fireBundleEvents=function(e,t,r,i){e.emit("bundle",t,r,i);for(var o=0;o<t.packets.length;o++){var a=t.packets[o];n.firePacketEvents(e,a,t.timeTag,i)}},n.fireClosedPortSendError=function(e,t){t=t||"Can't send packets on a closed osc.Port object. Please open (or reopen) this Port by calling open().",e.emit("error",t)},n.Port=function(e){this.options=e||{},this.on("data",this.decodeOSC.bind(this))};var s=n.Port.prototype=Object.create(S.prototype);s.constructor=n.Port,s.send=function(e){var t=Array.prototype.slice.call(arguments),r=this.encodeOSC(e),i=n.nativeBuffer(r);t[0]=i,this.sendRaw.apply(this,t)},s.encodeOSC=function(e){e=e.buffer?e.buffer:e;var t;try{t=n.writePacket(e,this.options)}catch(r){this.emit("error",r)}return t},s.decodeOSC=function(e,t){e=n.byteArray(e),this.emit("raw",e,t);try{var r=n.readPacket(e,this.options);this.emit("osc",r,t),n.firePacketEvents(this,r,void 0,t)}catch(i){this.emit("error",i)}},n.SLIPPort=function(e){var t=this,r=this.options=e||{};r.useSLIP=r.useSLIP===void 0?!0:r.useSLIP,this.decoder=new slip.Decoder({onMessage:this.decodeOSC.bind(this),onError:function(o){t.emit("error",o)}});var i=r.useSLIP?this.decodeSLIPData:this.decodeOSC;this.on("data",i.bind(this))},s=n.SLIPPort.prototype=Object.create(n.Port.prototype),s.constructor=n.SLIPPort,s.encodeOSC=function(e){e=e.buffer?e.buffer:e;var t;try{var r=n.writePacket(e,this.options);t=slip.encode(r)}catch(i){this.emit("error",i)}return t},s.decodeSLIPData=function(e,t){this.decoder.decode(e,t)},n.relay=function(e,t,r,i,o,a){r=r||"message",i=i||"send",o=o||function(){},a=a?[null].concat(a):[];var l=function(u){a[0]=u,u=o(u),t[i].apply(t,a)};return e.on(r,l),{eventName:r,listener:l}},n.relayPorts=function(e,t,r){var i=r.raw?"raw":"osc",o=r.raw?"sendRaw":"send";return n.relay(e,t,i,o,r.transform)},n.stopRelaying=function(e,t){e.removeListener(t.eventName,t.listener)},n.Relay=function(e,t,r){var i=this.options=r||{};i.raw=!1,this.port1=e,this.port2=t,this.listen()},s=n.Relay.prototype=Object.create(S.prototype),s.constructor=n.Relay,s.open=function(){this.port1.open(),this.port2.open()},s.listen=function(){this.port1Spec&&this.port2Spec&&this.close(),this.port1Spec=n.relayPorts(this.port1,this.port2,this.options),this.port2Spec=n.relayPorts(this.port2,this.port1,this.options);var e=this.close.bind(this);this.port1.on("close",e),this.port2.on("close",e)},s.close=function(){n.stopRelaying(this.port1,this.port1Spec),n.stopRelaying(this.port2,this.port2Spec),this.emit("close",this.port1,this.port2)}})();(function(){"use strict";n.WebSocket=typeof WebSocket<"u"?WebSocket:void 0,n.WebSocketPort=function(e){n.Port.call(this,e),this.on("open",this.listen.bind(this)),this.socket=e.socket,this.socket&&(this.socket.readyState===1?(n.WebSocketPort.setupSocketForBinary(this.socket),this.emit("open",this.socket)):this.open())};var s=n.WebSocketPort.prototype=Object.create(n.Port.prototype);s.constructor=n.WebSocketPort,s.open=function(){(!this.socket||this.socket.readyState>1)&&(this.socket=new n.WebSocket(this.options.url)),n.WebSocketPort.setupSocketForBinary(this.socket);var e=this;this.socket.onopen=function(){e.emit("open",e.socket)},this.socket.onerror=function(t){e.emit("error",t)}},s.listen=function(){var e=this;this.socket.onmessage=function(t){e.emit("data",t.data,t)},this.socket.onclose=function(t){e.emit("close",t)},e.emit("ready")},s.sendRaw=function(e){if(!this.socket||this.socket.readyState!==1){n.fireClosedPortSendError(this);return}this.socket.send(e)},s.close=function(e,t){this.socket.close(e,t)},n.WebSocketPort.setupSocketForBinary=function(e){e.binaryType=n.isNode?"nodebuffer":"arraybuffer"}})();var b=n,{readPacket:pe,writePacket:me,readMessage:we,writeMessage:ge,readBundle:ye,writeBundle:Se}=n;var A=class{constructor(e=null){this.workerBaseURL=e,this.workers={oscOut:null,oscIn:null,debug:null},this.callbacks={onRawOSC:null,onParsedOSC:null,onDebugMessage:null,onError:null,onInitialized:null},this.initialized=!1,this.sharedBuffer=null,this.ringBufferBase=null,this.bufferConstants=null}async init(e,t,r){if(this.initialized){console.warn("[ScsynthOSC] Already initialized");return}this.sharedBuffer=e,this.ringBufferBase=t,this.bufferConstants=r;try{this.workers.oscOut=new Worker(this.workerBaseURL+"osc_out_prescheduler_worker.js",{type:"module"}),this.workers.oscIn=new Worker(this.workerBaseURL+"osc_in_worker.js",{type:"module"}),this.workers.debug=new Worker(this.workerBaseURL+"debug_worker.js",{type:"module"}),this.setupWorkerHandlers();let i=[this.initWorker(this.workers.oscOut,"OSC SCHEDULER+WRITER"),this.initWorker(this.workers.oscIn,"OSC IN"),this.initWorker(this.workers.debug,"DEBUG")];await Promise.all(i),this.workers.oscIn.postMessage({type:"start"}),this.workers.debug.postMessage({type:"start"}),this.initialized=!0,this.callbacks.onInitialized&&this.callbacks.onInitialized()}catch(i){throw console.error("[ScsynthOSC] Initialization failed:",i),this.callbacks.onError&&this.callbacks.onError(i),i}}initWorker(e,t){return new Promise((r,i)=>{let o=setTimeout(()=>{i(new Error(`${t} worker initialization timeout`))},5e3),a=l=>{l.data.type==="initialized"&&(clearTimeout(o),e.removeEventListener("message",a),r())};e.addEventListener("message",a),e.postMessage({type:"init",sharedBuffer:this.sharedBuffer,ringBufferBase:this.ringBufferBase,bufferConstants:this.bufferConstants})})}setupWorkerHandlers(){this.workers.oscIn.onmessage=e=>{let t=e.data;switch(t.type){case"messages":t.messages.forEach(r=>{if(r.oscData&&(this.callbacks.onRawOSC&&this.callbacks.onRawOSC({oscData:r.oscData,sequence:r.sequence}),this.callbacks.onParsedOSC))try{let i={metadata:!1,unpackSingleArgs:!1},o=b.readPacket(r.oscData,i);this.callbacks.onParsedOSC(o)}catch(i){console.error("[ScsynthOSC] Failed to decode OSC message:",i,r)}});break;case"error":console.error("[ScsynthOSC] OSC IN error:",t.error),this.callbacks.onError&&this.callbacks.onError(t.error,"oscIn");break}},this.workers.debug.onmessage=e=>{let t=e.data;switch(t.type){case"debug":this.callbacks.onDebugMessage&&t.messages.forEach(r=>{this.callbacks.onDebugMessage(r)});break;case"error":console.error("[ScsynthOSC] DEBUG error:",t.error),this.callbacks.onError&&this.callbacks.onError(t.error,"debug");break}},this.workers.oscOut.onmessage=e=>{let t=e.data;switch(t.type){case"error":console.error("[ScsynthOSC] OSC OUT error:",t.error),this.callbacks.onError&&this.callbacks.onError(t.error,"oscOut");break}}}send(e,t={}){if(!this.initialized){console.error("[ScsynthOSC] Not initialized");return}let{editorId:r=0,runTag:i="",audioTimeS:o=null,currentTimeS:a=null}=t;this.workers.oscOut.postMessage({type:"send",oscData:e,editorId:r,runTag:i,audioTimeS:o,currentTimeS:a})}sendImmediate(e){if(!this.initialized){console.error("[ScsynthOSC] Not initialized");return}this.workers.oscOut.postMessage({type:"sendImmediate",oscData:e})}cancelEditorTag(e,t){this.initialized&&this.workers.oscOut.postMessage({type:"cancelEditorTag",editorId:e,runTag:t})}cancelEditor(e){this.initialized&&this.workers.oscOut.postMessage({type:"cancelEditor",editorId:e})}cancelAll(){this.initialized&&this.workers.oscOut.postMessage({type:"cancelAll"})}clearDebug(){this.initialized&&this.workers.debug.postMessage({type:"clear"})}onRawOSC(e){this.callbacks.onRawOSC=e}onParsedOSC(e){this.callbacks.onParsedOSC=e}onDebugMessage(e){this.callbacks.onDebugMessage=e}onError(e){this.callbacks.onError=e}onInitialized(e){this.callbacks.onInitialized=e}terminate(){this.workers.oscOut&&(this.workers.oscOut.postMessage({type:"stop"}),this.workers.oscOut.terminate()),this.workers.oscIn&&(this.workers.oscIn.postMessage({type:"stop"}),this.workers.oscIn.terminate()),this.workers.debug&&(this.workers.debug.postMessage({type:"stop"}),this.workers.debug.terminate()),this.workers={oscOut:null,oscIn:null,debug:null},this.initialized=!1}};var ie={5120:"i8",5121:"u8",5122:"i16",5123:"u16",5124:"i32",5125:"u32",5126:"f32"};var z={u8:1,u8c:1,i8:1,u16:2,i16:2,u32:4,i32:4,i64:8,u64:8,f32:4,f64:8};var se={f32:Float32Array,f64:Float64Array},ne={i8:Int8Array,i16:Int16Array,i32:Int32Array},oe={u8:Uint8Array,u8c:Uint8ClampedArray,u16:Uint16Array,u32:Uint32Array},ae={i64:BigInt64Array,u64:BigUint64Array},le={...se,...ne,...oe},ue=s=>{let e=ie[s];return e!==void 0?e:s};function N(s,...e){let t=ae[s];return new(t||le[ue(s)])(...e)}var B=(s,e)=>(e--,s+e&~e);var L=s=>typeof s=="number";var O=(s,e=t=>t!==void 0?": "+t:"")=>class extends Error{origMessage;constructor(t){super(s(t)+e(t)),this.origMessage=t!==void 0?String(t):""}};var ce=O(()=>"Assertion failed"),I=(typeof process<"u"&&process.env!==void 0?process.env.UMBRELLA_ASSERTS:!import.meta.env||import.meta.env.MODE!=="production"||import.meta.env.UMBRELLA_ASSERTS||import.meta.env.VITE_UMBRELLA_ASSERTS)?(s,e)=>{if(typeof s=="function"&&!s()||!s)throw new ce(typeof e=="function"?e():e)}:()=>{};var fe=O(()=>"illegal argument(s)"),$=s=>{throw new fe(s)};var V=0,W=1,q=2,H=3,Z=4,y=5,j=6,R=1,F=2,Y=7*4,x=0,U=1,w=2*4,M=class{buf;start;u8;u32;state;constructor(e={}){if(this.buf=e.buf?e.buf:new ArrayBuffer(e.size||4096),this.start=e.start!=null?B(Math.max(e.start,0),4):0,this.u8=new Uint8Array(this.buf),this.u32=new Uint32Array(this.buf),this.state=new Uint32Array(this.buf,this.start,Y/4),!e.skipInitialization){let t=e.align||8;I(t>=8,`invalid alignment: ${t}, must be a pow2 and >= 8`);let r=this.initialTop(t),i=e.end!=null?Math.min(e.end,this.buf.byteLength):this.buf.byteLength;r>=i&&$(`insufficient address range (0x${this.start.toString(16)} - 0x${i.toString(16)})`),this.align=t,this.doCompact=e.compact!==!1,this.doSplit=e.split!==!1,this.minSplit=e.minSplit||16,this.end=i,this.top=r,this._free=0,this._used=0}}stats(){let e=r=>{let i=0,o=0;for(;r;)i++,o+=this.blockSize(r),r=this.blockNext(r);return{count:i,size:o}},t=e(this._free);return{free:t,used:e(this._used),top:this.top,available:this.end-this.top+t.size,total:this.buf.byteLength}}callocAs(e,t,r=0){let i=this.mallocAs(e,t);return i?.fill(r),i}mallocAs(e,t){let r=this.malloc(t*z[e]);return r?N(e,this.buf,r,t):void 0}calloc(e,t=0){let r=this.malloc(e);return r&&this.u8.fill(t,r,r+e),r}malloc(e){if(e<=0)return 0;let t=B(e+w,this.align),r=this.end,i=this.top,o=this._free,a=0;for(;o;){let l=this.blockSize(o),u=o+l>=i;if(u||l>=t)return this.mallocTop(o,a,l,t,u);a=o,o=this.blockNext(o)}return o=i,i=o+t,i<=r?(this.initBlock(o,t,this._used),this._used=o,this.top=i,T(o)):0}mallocTop(e,t,r,i,o){if(o&&e+i>this.end)return 0;if(t?this.unlinkBlock(t,e):this._free=this.blockNext(e),this.setBlockNext(e,this._used),this._used=e,o)this.top=e+this.setBlockSize(e,i);else if(this.doSplit){let a=r-i;a>=this.minSplit&&this.splitBlock(e,i,a)}return T(e)}realloc(e,t){if(t<=0)return 0;let r=D(e),i=0,o=this._used,a=0;for(;o;){if(o===r){[i,a]=this.reallocBlock(o,t);break}o=this.blockNext(o)}return i&&i!==r&&this.u8.copyWithin(T(i),T(r),a),T(i)}reallocBlock(e,t){let r=this.blockSize(e),i=e+r,o=i>=this.top,a=B(t+w,this.align);if(a<=r){if(this.doSplit){let l=r-a;l>=this.minSplit?this.splitBlock(e,a,l):o&&(this.top=e+a)}else o&&(this.top=e+a);return[e,i]}return o&&e+a<this.end?(this.top=e+this.setBlockSize(e,a),[e,i]):(this.free(e),[D(this.malloc(t)),i])}reallocArray(e,t){if(e.buffer!==this.buf)return;let r=this.realloc(e.byteOffset,t*e.BYTES_PER_ELEMENT);return r?new e.constructor(this.buf,r,t):void 0}free(e){let t;if(L(e))t=e;else{if(e.buffer!==this.buf)return!1;t=e.byteOffset}t=D(t);let r=this._used,i=0;for(;r;){if(r===t)return i?this.unlinkBlock(i,r):this._used=this.blockNext(r),this.insert(r),this.doCompact&&this.compact(),!0;i=r,r=this.blockNext(r)}return!1}freeAll(){this._free=0,this._used=0,this.top=this.initialTop()}release(){return delete this.u8,delete this.u32,delete this.state,delete this.buf,!0}get align(){return this.state[Z]}set align(e){this.state[Z]=e}get end(){return this.state[H]}set end(e){this.state[H]=e}get top(){return this.state[q]}set top(e){this.state[q]=e}get _free(){return this.state[V]}set _free(e){this.state[V]=e}get _used(){return this.state[W]}set _used(e){this.state[W]=e}get doCompact(){return!!(this.state[y]&R)}set doCompact(e){e?this.state[y]|=1<<R-1:this.state[y]&=~R}get doSplit(){return!!(this.state[y]&F)}set doSplit(e){e?this.state[y]|=1<<F-1:this.state[y]&=~F}get minSplit(){return this.state[j]}set minSplit(e){I(e>w,`illegal min split threshold: ${e}, require at least ${w+1}`),this.state[j]=e}blockSize(e){return this.u32[(e>>2)+x]}setBlockSize(e,t){return this.u32[(e>>2)+x]=t,t}blockNext(e){return this.u32[(e>>2)+U]}setBlockNext(e,t){this.u32[(e>>2)+U]=t}initBlock(e,t,r){let i=e>>>2;return this.u32[i+x]=t,this.u32[i+U]=r,e}unlinkBlock(e,t){this.setBlockNext(e,this.blockNext(t))}splitBlock(e,t,r){this.insert(this.initBlock(e+this.setBlockSize(e,t),r,0)),this.doCompact&&this.compact()}initialTop(e=this.align){return B(this.start+Y+w,e)-w}compact(){let e=this._free,t=0,r=0,i,o=!1;for(;e;){for(i=e,r=this.blockNext(e);r&&i+this.blockSize(i)===r;)i=r,r=this.blockNext(r);if(i!==e){let a=i-e+this.blockSize(i);this.setBlockSize(e,a);let l=this.blockNext(i),u=this.blockNext(e);for(;u&&u!==l;){let c=this.blockNext(u);this.setBlockNext(u,0),u=c}this.setBlockNext(e,l),o=!0}e+this.blockSize(e)>=this.top&&(this.top=e,t?this.unlinkBlock(t,e):this._free=this.blockNext(e)),t=e,e=this.blockNext(e)}return o}insert(e){let t=this._free,r=0;for(;t&&!(e<=t);)r=t,t=this.blockNext(t);r?this.setBlockNext(r,e):this._free=e,this.setBlockNext(e,t)}},T=s=>s>0?s+w:0,D=s=>s>0?s-w:0;var he=8,P=class{#i;#n;#o;#d;#t;#r;#e;#s;constructor(e){let{audioContext:t,sharedBuffer:r,bufferPoolConfig:i,sampleBaseURL:o,audioPathMap:a={},maxBuffers:l=1024}=e;if(!t)throw new Error("BufferManager requires audioContext");if(!r||!(r instanceof SharedArrayBuffer))throw new Error("BufferManager requires sharedBuffer (SharedArrayBuffer)");if(!i||typeof i!="object")throw new Error("BufferManager requires bufferPoolConfig (object with start, size, align)");if(!Number.isFinite(i.start)||i.start<0)throw new Error("bufferPoolConfig.start must be a non-negative number");if(!Number.isFinite(i.size)||i.size<=0)throw new Error("bufferPoolConfig.size must be a positive number");if(a&&typeof a!="object")throw new Error("audioPathMap must be an object");if(!Number.isInteger(l)||l<=0)throw new Error("maxBuffers must be a positive integer");this.#o=t,this.#d=r,this.#i=o,this.#n=a,this.#t=new M({buf:r,start:i.start,size:i.size,align:he}),this.#r=new Map,this.#e=new Map,this.#s=new Map,this.GUARD_BEFORE=3,this.GUARD_AFTER=1,this.MAX_BUFFERS=l;let u=(i.size/(1024*1024)).toFixed(0),c=(i.start/(1024*1024)).toFixed(0)}#m(e){if(typeof e!="string"||e.length===0)throw new Error("Invalid audio path: must be a non-empty string");if(e.includes(".."))throw new Error(`Invalid audio path: path cannot contain '..' (got: ${e})`);if(e.startsWith("/")||/^[a-zA-Z]:/.test(e))throw new Error(`Invalid audio path: path must be relative (got: ${e})`);if(e.includes("%2e")||e.includes("%2E"))throw new Error(`Invalid audio path: path cannot contain URL-encoded characters (got: ${e})`);if(e.includes("\\"))throw new Error(`Invalid audio path: use forward slashes only (got: ${e})`);if(this.#n[e])return this.#n[e];if(!this.#i)throw new Error(`sampleBaseURL not configured. Please set it in SuperSonic constructor options.
2
+ Example: new SuperSonic({ sampleBaseURL: "./dist/samples/" })
3
+ Or use CDN: new SuperSonic({ sampleBaseURL: "https://unpkg.com/supersonic-scsynth-samples@latest/samples/" })
4
+ Or install: npm install supersonic-scsynth-samples`);return this.#i+e}#a(e){if(!Number.isInteger(e)||e<0||e>=this.MAX_BUFFERS)throw new Error(`Invalid buffer number ${e} (must be 0-${this.MAX_BUFFERS-1})`)}async#w(e,t,r){let i=null,o=null,a=!1,l=await this.#u(e),u=!1;try{await this.#E(e);let{ptr:c,sizeBytes:f,...h}=await r();i=c;let{uuid:d,allocationComplete:p}=this.#h(e,t);o=d,this.#T(e,i,f,d,p),a=!0;let m=this.#b(e,d,p);return l(),u=!0,{ptr:i,uuid:d,allocationComplete:m,...h}}catch(c){throw a&&o?this.#c(e,o,!1):i&&this.#t.free(i),c}finally{u||l()}}async prepareFromFile(e){let{bufnum:t,path:r,startFrame:i=0,numFrames:o=0,channels:a=null}=e;return this.#a(t),this.#w(t,6e4,async()=>{let l=this.#m(r),u=await fetch(l);if(!u.ok)throw new Error(`Failed to fetch ${l}: ${u.status} ${u.statusText}`);let c=await u.arrayBuffer(),f=await this.#o.decodeAudioData(c),h=Math.max(0,Math.floor(i||0)),d=f.length-h,p=o&&o>0?Math.min(Math.floor(o),d):d;if(p<=0)throw new Error(`No audio frames available for buffer ${t} from ${r}`);let m=this.#B(a,f.numberOfChannels),g=m.length,E=p*g+(this.GUARD_BEFORE+this.GUARD_AFTER)*g,v=this.#g(E),C=new Float32Array(E),X=this.GUARD_BEFORE*g;for(let k=0;k<p;k++)for(let _=0;_<g;_++){let te=m[_],re=f.getChannelData(te);C[X+k*g+_]=re[h+k]}this.#S(v,C);let ee=C.length*4;return{ptr:v,sizeBytes:ee,numFrames:p,numChannels:g,sampleRate:f.sampleRate}})}async prepareEmpty(e){let{bufnum:t,numFrames:r,numChannels:i=1,sampleRate:o=null}=e;if(this.#a(t),!Number.isFinite(r)||r<=0)throw new Error(`/b_alloc requires a positive number of frames (got ${r})`);if(!Number.isFinite(i)||i<=0)throw new Error(`/b_alloc requires a positive channel count (got ${i})`);let a=Math.floor(r),l=Math.floor(i);return this.#w(t,5e3,async()=>{let u=a*l+(this.GUARD_BEFORE+this.GUARD_AFTER)*l,c=this.#g(u),f=new Float32Array(u);this.#S(c,f);let h=f.length*4;return{ptr:c,sizeBytes:h,numFrames:a,numChannels:l,sampleRate:o||this.#o.sampleRate}})}#B(e,t){return!e||e.length===0?Array.from({length:t},(r,i)=>i):(e.forEach(r=>{if(!Number.isInteger(r)||r<0||r>=t)throw new Error(`Channel ${r} is out of range (file has ${t} channels)`)}),e)}#g(e){let t=e*4,r=this.#t.malloc(t);if(r===0){let i=this.#t.stats(),o=((i.available||0)/(1024*1024)).toFixed(2),a=((i.total||0)/(1024*1024)).toFixed(2),l=(t/(1024*1024)).toFixed(2);throw new Error(`Buffer pool allocation failed: requested ${l}MB, available ${o}MB of ${a}MB total`)}return r}#S(e,t){new Float32Array(this.#d,e,t.length).set(t)}#l(e,t,r){return new Promise((i,o)=>{let a=setTimeout(()=>{this.#e.delete(e),o(new Error(`Buffer ${t} allocation timeout (${r}ms)`))},r);this.#e.set(e,{resolve:i,reject:o,timeout:a})})}#h(e,t){let r=crypto.randomUUID(),i=this.#l(r,e,t);return{uuid:r,allocationComplete:i}}async#u(e){let t=this.#s.get(e)||Promise.resolve(),r,i=new Promise(o=>{r=o});return this.#s.set(e,t.then(()=>i)),await t,()=>{r&&(r(),r=null),this.#s.get(e)===i&&this.#s.delete(e)}}#T(e,t,r,i,o){let a=this.#r.get(e),l={ptr:t,size:r,pendingToken:i,pendingPromise:o,previousAllocation:a?{ptr:a.ptr,size:a.size}:null};return this.#r.set(e,l),l}async#E(e){let t=this.#r.get(e);if(t&&t.pendingToken&&t.pendingPromise)try{await t.pendingPromise}catch{}}#b(e,t,r){return!r||typeof r.then!="function"?(this.#c(e,t,!0),Promise.resolve()):r.then(i=>(this.#c(e,t,!0),i)).catch(i=>{throw this.#c(e,t,!1),i})}#c(e,t,r){let i=this.#r.get(e);if(!i||i.pendingToken!==t)return;let o=i.previousAllocation;if(r){i.pendingToken=null,i.pendingPromise=null,i.previousAllocation=null,o?.ptr&&this.#t.free(o.ptr);return}i.ptr&&this.#t.free(i.ptr),i.pendingPromise=null,o?.ptr?this.#r.set(e,{ptr:o.ptr,size:o.size,pendingToken:null,previousAllocation:null}):this.#r.delete(e)}handleBufferFreed(e){let t=e[0],r=e[1],i=this.#r.get(t);if(!i){typeof r=="number"&&r!==0&&this.#t.free(r);return}if(typeof r=="number"&&r===i.ptr){this.#t.free(i.ptr),this.#r.delete(t);return}if(typeof r=="number"&&i.previousAllocation&&i.previousAllocation.ptr===r){this.#t.free(r),i.previousAllocation=null;return}this.#t.free(i.ptr),this.#r.delete(t)}handleBufferAllocated(e){let t=e[0],r=e[1],i=this.#e.get(t);i&&(clearTimeout(i.timeout),i.resolve({bufnum:r}),this.#e.delete(t))}allocate(e){let t=e*4,r=this.#t.malloc(t);if(r===0){let i=this.#t.stats(),o=((i.available||0)/(1024*1024)).toFixed(2),a=((i.total||0)/(1024*1024)).toFixed(2),l=(t/(1024*1024)).toFixed(2);console.error(`[BufferManager] Allocation failed: requested ${l}MB, available ${o}MB of ${a}MB total`)}return r}free(e){return this.#t.free(e)}getView(e,t){return new Float32Array(this.#d,e,t)}getStats(){return this.#t.stats()}getDiagnostics(){let e=this.#t.stats(),t=0,r=0;for(let i of this.#r.values())i&&(t+=i.size||0,i.pendingToken&&r++);return{active:this.#r.size,pending:r,bytesActive:t,pool:{total:e.total||0,available:e.available||0,freeBytes:e.free?.size||0,freeBlocks:e.free?.count||0,usedBytes:e.used?.size||0,usedBlocks:e.used?.count||0}}}destroy(){for(let[e,t]of this.#e.entries())clearTimeout(t.timeout),t.reject(new Error("BufferManager destroyed"));this.#e.clear();for(let[e,t]of this.#r.entries())t.ptr&&this.#t.free(t.ptr);this.#r.clear(),this.#s.clear()}};var G={totalPages:1280,ringBufferReserved:1048576,bufferPoolOffset:17825792,bufferPoolSize:66060288,get totalMemory(){return this.bufferPoolOffset+this.bufferPoolSize},get wasmHeapSize(){return this.bufferPoolOffset-this.ringBufferReserved}};var J={numBuffers:1024,maxNodes:1024,maxGraphDefs:1024,maxWireBufs:64,numAudioBusChannels:128,numInputBusChannels:0,numOutputBusChannels:2,numControlBusChannels:4096,bufLength:128,realTimeMemorySize:8192,numRGens:64,realTime:!1,memoryLocking:!1,loadGraphDefs:0,preferredSampleRate:0,verbosity:0};var Q=class s{static osc={encode:e=>b.writePacket(e),decode:(e,t={metadata:!1})=>b.readPacket(e,t)};#i;#n;#o;#d;#t;#r;#e;#s;#m;#a;#w;#B;#g;#S;#l;#h;#u;#T=0;#E=0;#b=0;#c=null;#O=!1;constructor(e={}){if(this.#l=!1,this.#h=!1,this.#u={},this.#t=null,this.#r=null,this.#e=null,this.#i=null,this.#n=null,this.#o=null,this.#s=null,this.loadedSynthDefs=new Set,this.onOSC=null,this.onMessage=null,this.onMessageSent=null,this.onMetricsUpdate=null,this.onDebugMessage=null,this.onInitialized=null,this.onError=null,!e.workerBaseURL||!e.wasmBaseURL)throw new Error(`SuperSonic requires workerBaseURL and wasmBaseURL options. Example:
5
+ new SuperSonic({
6
+ workerBaseURL: "/supersonic/workers/",
7
+ wasmBaseURL: "/supersonic/wasm/"
8
+ })`);let t=e.workerBaseURL,r=e.wasmBaseURL,i={...J,...e.scsynthOptions};this.config={wasmUrl:e.wasmUrl||r+"scsynth-nrt.wasm",wasmBaseURL:r,workletUrl:e.workletUrl||t+"scsynth_audio_worklet.js",workerBaseURL:t,development:!1,audioContextOptions:{latencyHint:"interactive",sampleRate:48e3},memory:G,worldOptions:i},this.#B=e.sampleBaseURL||null,this.#g=e.synthdefBaseURL||null,this.#S=e.audioPathMap||{},this.bootStats={initStartTime:null,initDuration:null}}get initialized(){return this.#l}get initializing(){return this.#h}get capabilities(){return this.#u}setAndValidateCapabilities(){this.#u={audioWorklet:typeof AudioWorklet<"u",sharedArrayBuffer:typeof SharedArrayBuffer<"u",crossOriginIsolated:window.crossOriginIsolated===!0,atomics:typeof Atomics<"u",webWorker:typeof Worker<"u"};let t=["audioWorklet","sharedArrayBuffer","crossOriginIsolated","atomics","webWorker"].filter(r=>!this.#u[r]);if(t.length>0){let r=new Error(`Missing required features: ${t.join(", ")}`);throw this.#u.crossOriginIsolated||(this.#u.sharedArrayBuffer?r.message+=`
9
+
10
+ SharedArrayBuffer is available but cross-origin isolation is not enabled. Please ensure COOP and COEP headers are set correctly:
11
+ Cross-Origin-Opener-Policy: same-origin
12
+ Cross-Origin-Embedder-Policy: require-corp`:r.message+=`
13
+
14
+ SharedArrayBuffer is not available. This may be due to:
15
+ 1. Missing COOP/COEP headers
16
+ 2. Browser doesn't support SharedArrayBuffer
17
+ 3. Browser security settings`),r}return this.#u}#x(){let e=this.config.memory;this.#d=new WebAssembly.Memory({initial:e.totalPages,maximum:e.totalPages,shared:!0}),this.#t=this.#d.buffer}#U(){return this.#i=new AudioContext(this.config.audioContextOptions),this.#i}#D(){this.#s=new P({audioContext:this.#i,sharedBuffer:this.#t,bufferPoolConfig:{start:this.config.memory.bufferPoolOffset,size:this.config.memory.bufferPoolSize},sampleBaseURL:this.#B,audioPathMap:this.#S,maxBuffers:this.config.worldOptions.numBuffers})}async#z(){let e=this.config.wasmBaseURL+"manifest.json";try{let t=await fetch(e);if(!t.ok)return;let r=await t.json();this.config.wasmUrl=this.config.wasmBaseURL+r.wasmFile}catch{}}async#N(){this.config.development&&await this.#z();let e=await fetch(this.config.wasmUrl);if(!e.ok)throw new Error(`Failed to load WASM: ${e.status} ${e.statusText}`);return await e.arrayBuffer()}async#L(e){await this.#i.audioWorklet.addModule(this.config.workletUrl),this.#n=new AudioWorkletNode(this.#i,"scsynth-processor",{numberOfInputs:0,numberOfOutputs:1,outputChannelCount:[2]}),this.#n.connect(this.#i.destination),this.#n.port.postMessage({type:"init",sharedBuffer:this.#t}),this.#n.port.postMessage({type:"loadWasm",wasmBytes:e,wasmMemory:this.#d,worldOptions:this.config.worldOptions,sampleRate:this.#i.sampleRate}),await this.#W()}async#$(){this.#o=new A(this.config.workerBaseURL),this.#o.onRawOSC(e=>{this.onOSC&&this.onOSC(e)}),this.#o.onParsedOSC(e=>{if(e.address==="/buffer/freed")this.#s?.handleBufferFreed(e.args);else if(e.address==="/buffer/allocated")this.#s?.handleBufferAllocated(e.args);else if(e.address==="/synced"&&e.args.length>0){let t=e.args[0];this.#a&&this.#a.has(t)&&this.#a.get(t)(e)}this.onMessage&&(this.#E++,this.onMessage(e))}),this.#o.onDebugMessage(e=>{this.onDebugMessage&&this.onDebugMessage(e)}),this.#o.onError((e,t)=>{console.error(`[SuperSonic] ${t} error:`,e),this.#b++,this.onError&&this.onError(new Error(`${t}: ${e}`))}),await this.#o.init(this.#t,this.#r,this.#e)}#V(){this.#l=!0,this.#h=!1,this.bootStats.initDuration=performance.now()-this.bootStats.initStartTime,this.onInitialized&&this.onInitialized({capabilities:this.#u,bootStats:this.bootStats})}async init(e={}){if(this.#l){console.warn("[SuperSonic] Already initialized");return}if(this.#h){console.warn("[SuperSonic] Initialization already in progress");return}this.config={...this.config,...e,audioContextOptions:{...this.config.audioContextOptions,...e.audioContextOptions||{}}},this.#h=!0,this.bootStats.initStartTime=performance.now();try{this.setAndValidateCapabilities(),this.#x(),this.#U(),this.#D();let t=await this.#N();await this.#L(t),await this.#$(),this.#q(),this.#G(),this.#V()}catch(t){throw this.#h=!1,console.error("[SuperSonic] Initialization failed:",t),this.onError&&this.onError(t),t}}#W(){return new Promise((e,t)=>{let r=setTimeout(()=>{t(new Error("AudioWorklet initialization timeout"))},5e3),i=async o=>{if(o.data.type!=="debug"){if(o.data.type==="error"){console.error("[AudioWorklet] Error:",o.data.error),clearTimeout(r),this.#n.port.removeEventListener("message",i),t(new Error(o.data.error||"AudioWorklet error"));return}o.data.type==="initialized"&&(clearTimeout(r),this.#n.port.removeEventListener("message",i),o.data.success?(o.data.ringBufferBase!==void 0?this.#r=o.data.ringBufferBase:console.warn("[SuperSonic] Warning: ringBufferBase not provided by worklet"),o.data.bufferConstants!==void 0?(this.#e=o.data.bufferConstants,await this.initializeNTPTiming(),this.#K()):console.warn("[SuperSonic] Warning: bufferConstants not provided by worklet"),e()):t(new Error(o.data.error||"AudioWorklet initialization failed")))}};this.#n.port.addEventListener("message",i),this.#n.port.start()})}#q(){this.#n.port.onmessage=e=>{let{data:t}=e;switch(t.type){case"error":console.error("[Worklet] Error:",t.error),t.diagnostics&&(console.error("[Worklet] Diagnostics:",t.diagnostics),console.table(t.diagnostics)),this.#b++,this.onError&&this.onError(new Error(t.error));break;case"process_debug":break;case"debug":break;case"console":this.onConsoleMessage&&this.onConsoleMessage(t.message);break;case"version":this.onVersion&&this.onVersion(t.version);break}}}#H(){if(!this.#t||!this.#e||!this.#r)return null;let e=this.#r+this.#e.METRICS_START,t=this.#e.METRICS_SIZE/4,r=new Uint32Array(this.#t,e,t);return{processCount:Atomics.load(r,0),bufferOverruns:Atomics.load(r,1),messagesProcessed:Atomics.load(r,2),messagesDropped:Atomics.load(r,3),schedulerQueueDepth:Atomics.load(r,4),schedulerQueueMax:Atomics.load(r,5),schedulerQueueDropped:Atomics.load(r,6)}}#Z(){if(!this.#t||!this.#e||!this.#r)return null;let e=new Int32Array(this.#t),t=this.#r+this.#e.CONTROL_START,r=Atomics.load(e,(t+0)/4),i=Atomics.load(e,(t+4)/4),o=Atomics.load(e,(t+8)/4),a=Atomics.load(e,(t+12)/4),l=Atomics.load(e,(t+16)/4),u=Atomics.load(e,(t+20)/4),c=(r-i+this.#e.IN_BUFFER_SIZE)%this.#e.IN_BUFFER_SIZE,f=(o-a+this.#e.OUT_BUFFER_SIZE)%this.#e.OUT_BUFFER_SIZE,h=(l-u+this.#e.DEBUG_BUFFER_SIZE)%this.#e.DEBUG_BUFFER_SIZE;return{inBufferUsed:{bytes:c,percentage:Math.round(c/this.#e.IN_BUFFER_SIZE*100)},outBufferUsed:{bytes:f,percentage:Math.round(f/this.#e.OUT_BUFFER_SIZE*100)},debugBufferUsed:{bytes:h,percentage:Math.round(h/this.#e.DEBUG_BUFFER_SIZE*100)}}}#j(){if(!this.#t||!this.#e||!this.#r)return null;let e=this.#r+this.#e.METRICS_START,t=this.#e.METRICS_SIZE/4,r=new Uint32Array(this.#t,e,t);return{preschedulerPending:r[7],preschedulerPeak:r[8],preschedulerSent:r[9],bundlesDropped:r[10],retriesSucceeded:r[11],retriesFailed:r[12],bundlesScheduled:r[13],eventsCancelled:r[14],totalDispatches:r[15],messagesRetried:r[16],retryQueueSize:r[17],retryQueueMax:r[18],oscInMessagesReceived:r[19],oscInDroppedMessages:r[20],oscInWakeups:r[21],oscInTimeouts:r[22],debugMessagesReceived:r[23],debugWakeups:r[24],debugTimeouts:r[25],debugBytesRead:r[26]}}#Y(){let e=performance.now(),t={messagesSent:this.#T,messagesReceived:this.#E,errors:this.#b},r=this.#H();r&&Object.assign(t,r);let i=this.#Z();i&&Object.assign(t,i);let o=this.#j();o&&Object.assign(t,o);let a=performance.now()-e;return a>1&&console.warn(`[SuperSonic] Slow metrics gathering: ${a.toFixed(2)}ms`),t}#G(){this.#c&&clearInterval(this.#c),this.#c=setInterval(()=>{if(this.onMetricsUpdate){if(this.#O){console.warn("[SuperSonic] Metrics gathering took >100ms, skipping this interval");return}this.#O=!0;try{let e=this.#Y();this.onMetricsUpdate(e)}catch(e){console.error("[SuperSonic] Metrics gathering failed:",e)}finally{this.#O=!1}}},100)}#J(){this.#c&&(clearInterval(this.#c),this.#c=null)}async send(e,...t){this.#f("send OSC messages");let r=t.map(a=>{if(typeof a=="string")return{type:"s",value:a};if(typeof a=="number")return{type:Number.isInteger(a)?"i":"f",value:a};if(a instanceof Uint8Array||a instanceof ArrayBuffer)return{type:"b",value:a instanceof ArrayBuffer?new Uint8Array(a):a};throw new Error(`Unsupported argument type: ${typeof a}`)}),i={address:e,args:r},o=s.osc.encode(i);return this.sendOSC(o)}#f(e="perform this operation"){if(!this.#l)throw new Error(`SuperSonic not initialized. Call init() before attempting to ${e}.`)}async sendOSC(e,t={}){this.#f("send OSC data");let r=this.#X(e),i=await this.#ee(r);this.#T++,this.onMessageSent&&this.onMessageSent(i);let o=this.#le(i),a={...t};o&&(a.audioTimeS=o.audioTimeS,a.currentTimeS=o.currentTimeS),this.#o.send(i,a)}get audioContext(){return this.#i}get workletNode(){return this.#n}get osc(){return this.#o}getStatus(){return{initialized:this.#l,capabilities:this.#u,bootStats:this.bootStats,audioContextState:this.#i?.state}}getConfig(){return this.config?{memory:{...this.config.memory},worldOptions:{...this.config.worldOptions}}:null}async destroy(){this.#I(),this.#J(),this.#o&&(this.#o.terminate(),this.#o=null),this.#n&&(this.#n.disconnect(),this.#n=null),this.#i&&(await this.#i.close(),this.#i=null),this.#s&&(this.#s.destroy(),this.#s=null),this.#t=null,this.#l=!1,this.loadedSynthDefs.clear()}waitForTimeSync(){return this.#f("wait for time sync"),new Float64Array(this.#t,this.#r+this.#e.NTP_START_TIME_START,1)[0]}async loadSample(e,t,r=0,i=0){this.#f("load samples");let o=await this.#_().prepareFromFile({bufnum:e,path:t,startFrame:r,numFrames:i});return await this.send("/b_allocPtr",e,o.ptr,o.numFrames,o.numChannels,o.sampleRate,o.uuid),o.allocationComplete}async loadSynthDef(e){if(!this.#l)throw new Error("SuperSonic not initialized. Call init() first.");try{let t=await fetch(e);if(!t.ok)throw new Error(`Failed to load synthdef from ${e}: ${t.status} ${t.statusText}`);let r=await t.arrayBuffer(),i=new Uint8Array(r);await this.send("/d_recv",i);let o=this.#Q(e);o&&this.loadedSynthDefs.add(o)}catch(t){throw console.error("[SuperSonic] Failed to load synthdef:",t),t}}async loadSynthDefs(e){if(!this.#l)throw new Error("SuperSonic not initialized. Call init() first.");if(!this.#g)throw new Error(`synthdefBaseURL not configured. Please set it in SuperSonic constructor options.
18
+ Example: new SuperSonic({ synthdefBaseURL: "./dist/synthdefs/" })
19
+ Or use CDN: new SuperSonic({ synthdefBaseURL: "https://unpkg.com/supersonic-scsynth-synthdefs@latest/synthdefs/" })
20
+ Or install: npm install supersonic-scsynth-synthdefs`);let t={};await Promise.all(e.map(async i=>{try{let o=`${this.#g}${i}.scsyndef`;await this.loadSynthDef(o),t[i]={success:!0}}catch(o){console.error(`[SuperSonic] Failed to load ${i}:`,o),t[i]={success:!1,error:o.message}}}));let r=Object.values(t).filter(i=>i.success).length;return t}async sync(e){if(!this.#l)throw new Error("SuperSonic not initialized. Call init() first.");if(!Number.isInteger(e))throw new Error("sync() requires an integer syncId parameter");let t=new Promise((r,i)=>{let o=setTimeout(()=>{this.#a&&this.#a.delete(e),i(new Error("Timeout waiting for /synced response"))},1e4),a=l=>{clearTimeout(o),this.#a.delete(e),r()};this.#a||(this.#a=new Map),this.#a.set(e,a)});await this.send("/sync",e),await t}allocBuffer(e){return this.#f("allocate buffers"),this.#s.allocate(e)}freeBuffer(e){return this.#f("free buffers"),this.#s.free(e)}getBufferView(e,t){return this.#f("get buffer views"),this.#s.getView(e,t)}getBufferPoolStats(){return this.#f("get buffer pool stats"),this.#s.getStats()}getDiagnostics(){return this.#f("get diagnostics"),{buffers:this.#s.getDiagnostics(),synthdefs:{count:this.loadedSynthDefs.size}}}async initializeNTPTiming(){if(!this.#e||!this.#i)return;let e;for(;e=this.#i.getOutputTimestamp(),!(e.contextTime>0);)await new Promise(a=>setTimeout(a,50));let i=(performance.timeOrigin+e.performanceTime)/1e3+2208988800-e.contextTime,o=new Float64Array(this.#t,this.#r+this.#e.NTP_START_TIME_START,1);o[0]=i,this.#w=i}updateDriftOffset(){if(!this.#e||!this.#i||this.#w===void 0)return;let e=this.#i.getOutputTimestamp(),o=(performance.timeOrigin+e.performanceTime)/1e3+2208988800-this.#w-e.contextTime,a=Math.round(o*1e3),l=new Int32Array(this.#t,this.#r+this.#e.DRIFT_OFFSET_START,1);Atomics.store(l,0,a)}getDriftOffset(){if(!this.#e)return 0;let e=new Int32Array(this.#t,this.#r+this.#e.DRIFT_OFFSET_START,1);return Atomics.load(e,0)}#K(){this.#I(),this.#m=setInterval(()=>{this.updateDriftOffset()},15e3)}#I(){this.#m&&(clearInterval(this.#m),this.#m=null)}#Q(e){return!e||typeof e!="string"?null:(e.split("/").filter(Boolean).pop()||e).replace(/\.scsyndef$/i,"")}#X(e){if(e instanceof Uint8Array)return e;if(e instanceof ArrayBuffer)return new Uint8Array(e);throw new Error("oscData must be ArrayBuffer or Uint8Array")}async#ee(e){let t={metadata:!0,unpackSingleArgs:!1};try{let r=s.osc.decode(e,t),{packet:i,changed:o}=await this.#R(r);return o?s.osc.encode(i):e}catch(r){throw console.error("[SuperSonic] Failed to prepare OSC packet:",r),r}}async#R(e){if(e&&e.address){let{message:t,changed:r}=await this.#te(e);return{packet:t,changed:r}}if(this.#ae(e)){let t=await Promise.all(e.packets.map(o=>this.#R(o)));if(!t.some(o=>o.changed))return{packet:e,changed:!1};let i=t.map(o=>o.packet);return{packet:{timeTag:e.timeTag,packets:i},changed:!0}}return{packet:e,changed:!1}}async#te(e){switch(e.address){case"/b_alloc":return{message:await this.#se(e),changed:!0};case"/b_allocRead":return{message:await this.#re(e),changed:!0};case"/b_allocReadChannel":return{message:await this.#ie(e),changed:!0};default:return{message:e,changed:!1}}}async#re(e){let t=this.#_(),r=this.#k(e.args,0,"/b_allocRead requires a buffer number"),i=this.#F(e.args,1,"/b_allocRead requires a file path"),o=this.#A(e.args,2,0),a=this.#A(e.args,3,0),l=await t.prepareFromFile({bufnum:r,path:i,startFrame:o,numFrames:a});return this.#C(l.allocationComplete,`/b_allocRead ${r}`),this.#M(r,l)}async#ie(e){let t=this.#_(),r=this.#k(e.args,0,"/b_allocReadChannel requires a buffer number"),i=this.#F(e.args,1,"/b_allocReadChannel requires a file path"),o=this.#A(e.args,2,0),a=this.#A(e.args,3,0),l=[];for(let c=4;c<(e.args?.length||0)&&this.#P(e.args[c]);c++)l.push(Math.floor(this.#y(e.args[c])));let u=await t.prepareFromFile({bufnum:r,path:i,startFrame:o,numFrames:a,channels:l.length>0?l:null});return this.#C(u.allocationComplete,`/b_allocReadChannel ${r}`),this.#M(r,u)}async#se(e){let t=this.#_(),r=this.#k(e.args,0,"/b_alloc requires a buffer number"),i=this.#k(e.args,1,"/b_alloc requires a frame count"),o=2,a=1,l=this.#i?.sampleRate||44100;this.#P(this.#p(e.args,o))&&(a=Math.max(1,this.#A(e.args,o,1)),o++),this.#p(e.args,o)?.type==="b"&&o++,this.#P(this.#p(e.args,o))&&(l=this.#y(this.#p(e.args,o)));let u=await t.prepareEmpty({bufnum:r,numFrames:i,numChannels:a,sampleRate:l});return this.#C(u.allocationComplete,`/b_alloc ${r}`),this.#M(r,u)}#M(e,t){return{address:"/b_allocPtr",args:[this.#v(e),this.#v(t.ptr),this.#v(t.numFrames),this.#v(t.numChannels),this.#ne(t.sampleRate),this.#oe(t.uuid)]}}#v(e){return{type:"i",value:Math.floor(e)}}#ne(e){return{type:"f",value:e}}#oe(e){return{type:"s",value:String(e)}}#p(e,t){if(Array.isArray(e))return e[t]}#y(e){if(e!=null)return typeof e=="object"&&Object.prototype.hasOwnProperty.call(e,"value")?e.value:e}#k(e,t,r){let i=this.#y(this.#p(e,t));if(!Number.isFinite(i))throw new Error(r);return Math.floor(i)}#A(e,t,r=0){let i=this.#y(this.#p(e,t));return Number.isFinite(i)?Math.floor(i):r}#F(e,t,r){let i=this.#y(this.#p(e,t));if(typeof i!="string")throw new Error(r);return i}#P(e){if(!e)return!1;let t=this.#y(e);return Number.isFinite(t)}#C(e,t){!e||typeof e.catch!="function"||e.catch(r=>{console.error(`[SuperSonic] ${t} allocation failed:`,r)})}#_(){if(!this.#s)throw new Error("Buffer manager not ready. Call init() before issuing buffer commands.");return this.#s}#ae(e){return e&&e.timeTag!==void 0&&Array.isArray(e.packets)}#le(e){if(e.length<16||String.fromCharCode.apply(null,e.slice(0,8))!=="#bundle\0")return null;let i=new Float64Array(this.#t,this.#r+this.#e.NTP_START_TIME_START,1)[0];if(i===0)return console.warn("[SuperSonic] NTP start time not yet initialized"),null;let o=new Int32Array(this.#t,this.#r+this.#e.DRIFT_OFFSET_START,1),l=Atomics.load(o,0)/1e3,u=new Int32Array(this.#t,this.#r+this.#e.GLOBAL_OFFSET_START,1),f=Atomics.load(u,0)/1e3,h=i+l+f,d=new DataView(e.buffer,e.byteOffset),p=d.getUint32(8,!1),m=d.getUint32(12,!1);if(p===0&&(m===0||m===1))return null;let E=p+m/4294967296-h,v=this.#i.currentTime;return{audioTimeS:E,currentTimeS:v}}};export{Q as SuperSonic};
3449
21
  /*! osc.js 2.4.5, Copyright 2024 Colin Clark | github.com/colinbdclark/osc.js */