supersonic-scsynth 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/supersonic.js +510 -468
- package/dist/wasm/manifest.json +3 -3
- package/dist/wasm/scsynth-nrt.wasm +0 -0
- package/dist/workers/osc_in_worker.js +2 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,11 +21,11 @@ A WebAssembly port of SuperCollider's scsynth audio synthesis engine for the bro
|
|
|
21
21
|
await sonic.loadSynthDefs(['sonic-pi-beep']);
|
|
22
22
|
|
|
23
23
|
// Trigger the synth
|
|
24
|
-
sonic.send('/s_new', 'sonic-pi-beep', -1, 0,
|
|
24
|
+
sonic.send('/s_new', 'sonic-pi-beep', -1, 0, 0, 'note', 60);
|
|
25
25
|
|
|
26
26
|
// Load and play a sample
|
|
27
27
|
sonic.send('/b_allocRead', 0, 'bd_haus.flac');
|
|
28
|
-
sonic.send('/s_new', 'sonic-pi-basic_mono_player', -1, 0,
|
|
28
|
+
sonic.send('/s_new', 'sonic-pi-basic_mono_player', -1, 0, 0, 'buf', 0);
|
|
29
29
|
</script>
|
|
30
30
|
```
|
|
31
31
|
|
|
@@ -90,7 +90,7 @@ const sonic = new SuperSonic({
|
|
|
90
90
|
**Common OSC commands:**
|
|
91
91
|
```javascript
|
|
92
92
|
sonic.send('/notify', 1); // Enable notifications
|
|
93
|
-
sonic.send('/s_new', 'synth-name', -1, 0,
|
|
93
|
+
sonic.send('/s_new', 'synth-name', -1, 0, 0); // Create synth
|
|
94
94
|
sonic.send('/n_set', 1000, 'freq', 440.0, 'amp', 0.5); // Set parameters
|
|
95
95
|
sonic.send('/n_free', 1000); // Free node
|
|
96
96
|
sonic.send('/b_allocRead', 0, 'sample.flac'); // Load audio buffer
|
package/dist/supersonic.js
CHANGED
|
@@ -1,301 +1,3 @@
|
|
|
1
|
-
// js/lib/scsynth_osc.js
|
|
2
|
-
var ScsynthOSC = class {
|
|
3
|
-
constructor() {
|
|
4
|
-
this.workers = {
|
|
5
|
-
oscOut: null,
|
|
6
|
-
oscIn: null,
|
|
7
|
-
debug: null
|
|
8
|
-
};
|
|
9
|
-
this.callbacks = {
|
|
10
|
-
onOSCMessage: null,
|
|
11
|
-
onDebugMessage: null,
|
|
12
|
-
onError: null,
|
|
13
|
-
onInitialized: null
|
|
14
|
-
};
|
|
15
|
-
this.initialized = false;
|
|
16
|
-
this.sharedBuffer = null;
|
|
17
|
-
this.ringBufferBase = null;
|
|
18
|
-
this.bufferConstants = null;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Initialize all workers with SharedArrayBuffer
|
|
22
|
-
*/
|
|
23
|
-
async init(sharedBuffer, ringBufferBase, bufferConstants) {
|
|
24
|
-
if (this.initialized) {
|
|
25
|
-
console.warn("[ScsynthOSC] Already initialized");
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
this.sharedBuffer = sharedBuffer;
|
|
29
|
-
this.ringBufferBase = ringBufferBase;
|
|
30
|
-
this.bufferConstants = bufferConstants;
|
|
31
|
-
try {
|
|
32
|
-
this.workers.oscOut = new Worker("./dist/workers/osc_out_worker.js");
|
|
33
|
-
this.workers.oscIn = new Worker("./dist/workers/osc_in_worker.js");
|
|
34
|
-
this.workers.debug = new Worker("./dist/workers/debug_worker.js");
|
|
35
|
-
this.setupWorkerHandlers();
|
|
36
|
-
const initPromises = [
|
|
37
|
-
this.initWorker(this.workers.oscOut, "OSC OUT"),
|
|
38
|
-
this.initWorker(this.workers.oscIn, "OSC IN"),
|
|
39
|
-
this.initWorker(this.workers.debug, "DEBUG")
|
|
40
|
-
];
|
|
41
|
-
await Promise.all(initPromises);
|
|
42
|
-
this.workers.oscIn.postMessage({ type: "start" });
|
|
43
|
-
this.workers.debug.postMessage({ type: "start" });
|
|
44
|
-
this.initialized = true;
|
|
45
|
-
if (this.callbacks.onInitialized) {
|
|
46
|
-
this.callbacks.onInitialized();
|
|
47
|
-
}
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error("[ScsynthOSC] Initialization failed:", error);
|
|
50
|
-
if (this.callbacks.onError) {
|
|
51
|
-
this.callbacks.onError(error);
|
|
52
|
-
}
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Initialize a single worker
|
|
58
|
-
*/
|
|
59
|
-
initWorker(worker, name) {
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
const timeout = setTimeout(() => {
|
|
62
|
-
reject(new Error(`${name} worker initialization timeout`));
|
|
63
|
-
}, 5e3);
|
|
64
|
-
const handler = (event) => {
|
|
65
|
-
if (event.data.type === "initialized") {
|
|
66
|
-
clearTimeout(timeout);
|
|
67
|
-
worker.removeEventListener("message", handler);
|
|
68
|
-
resolve();
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
worker.addEventListener("message", handler);
|
|
72
|
-
worker.postMessage({
|
|
73
|
-
type: "init",
|
|
74
|
-
sharedBuffer: this.sharedBuffer,
|
|
75
|
-
ringBufferBase: this.ringBufferBase,
|
|
76
|
-
bufferConstants: this.bufferConstants
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Set up message handlers for all workers
|
|
82
|
-
*/
|
|
83
|
-
setupWorkerHandlers() {
|
|
84
|
-
this.workers.oscIn.onmessage = (event) => {
|
|
85
|
-
const data = event.data;
|
|
86
|
-
switch (data.type) {
|
|
87
|
-
case "messages":
|
|
88
|
-
if (this.callbacks.onOSCMessage) {
|
|
89
|
-
data.messages.forEach((msg) => {
|
|
90
|
-
this.callbacks.onOSCMessage(msg);
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
break;
|
|
94
|
-
case "error":
|
|
95
|
-
console.error("[ScsynthOSC] OSC IN error:", data.error);
|
|
96
|
-
if (this.callbacks.onError) {
|
|
97
|
-
this.callbacks.onError(data.error, "oscIn");
|
|
98
|
-
}
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
this.workers.debug.onmessage = (event) => {
|
|
103
|
-
const data = event.data;
|
|
104
|
-
switch (data.type) {
|
|
105
|
-
case "debug":
|
|
106
|
-
if (this.callbacks.onDebugMessage) {
|
|
107
|
-
data.messages.forEach((msg) => {
|
|
108
|
-
this.callbacks.onDebugMessage(msg);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
break;
|
|
112
|
-
case "error":
|
|
113
|
-
console.error("[ScsynthOSC] DEBUG error:", data.error);
|
|
114
|
-
if (this.callbacks.onError) {
|
|
115
|
-
this.callbacks.onError(data.error, "debug");
|
|
116
|
-
}
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
this.workers.oscOut.onmessage = (event) => {
|
|
121
|
-
const data = event.data;
|
|
122
|
-
switch (data.type) {
|
|
123
|
-
case "error":
|
|
124
|
-
console.error("[ScsynthOSC] OSC OUT error:", data.error);
|
|
125
|
-
if (this.callbacks.onError) {
|
|
126
|
-
this.callbacks.onError(data.error, "oscOut");
|
|
127
|
-
}
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Send OSC data (message or bundle)
|
|
134
|
-
* - OSC messages are sent immediately
|
|
135
|
-
* - OSC bundles are scheduled based on waitTimeMs (calculated by SuperSonic)
|
|
136
|
-
*
|
|
137
|
-
* @param {Uint8Array} oscData - Binary OSC data (message or bundle)
|
|
138
|
-
* @param {Object} options - Optional metadata (editorId, runTag, waitTimeMs)
|
|
139
|
-
*/
|
|
140
|
-
send(oscData, options = {}) {
|
|
141
|
-
if (!this.initialized) {
|
|
142
|
-
console.error("[ScsynthOSC] Not initialized");
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const { editorId = 0, runTag = "", waitTimeMs = null } = options;
|
|
146
|
-
this.workers.oscOut.postMessage({
|
|
147
|
-
type: "send",
|
|
148
|
-
oscData,
|
|
149
|
-
editorId,
|
|
150
|
-
runTag,
|
|
151
|
-
waitTimeMs
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Send OSC data immediately, ignoring any bundle timestamps
|
|
156
|
-
* - Extracts all messages from bundles
|
|
157
|
-
* - Sends all messages immediately to scsynth
|
|
158
|
-
* - For applications that don't expect server-side scheduling
|
|
159
|
-
*
|
|
160
|
-
* @param {Uint8Array} oscData - Binary OSC data (message or bundle)
|
|
161
|
-
*/
|
|
162
|
-
sendImmediate(oscData) {
|
|
163
|
-
if (!this.initialized) {
|
|
164
|
-
console.error("[ScsynthOSC] Not initialized");
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
this.workers.oscOut.postMessage({
|
|
168
|
-
type: "sendImmediate",
|
|
169
|
-
oscData
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Cancel scheduled OSC bundles by editor and tag
|
|
174
|
-
*/
|
|
175
|
-
cancelEditorTag(editorId, runTag) {
|
|
176
|
-
if (!this.initialized) return;
|
|
177
|
-
this.workers.oscOut.postMessage({
|
|
178
|
-
type: "cancelEditorTag",
|
|
179
|
-
editorId,
|
|
180
|
-
runTag
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Cancel all scheduled OSC bundles from an editor
|
|
185
|
-
*/
|
|
186
|
-
cancelEditor(editorId) {
|
|
187
|
-
if (!this.initialized) return;
|
|
188
|
-
this.workers.oscOut.postMessage({
|
|
189
|
-
type: "cancelEditor",
|
|
190
|
-
editorId
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Cancel all scheduled OSC bundles
|
|
195
|
-
*/
|
|
196
|
-
cancelAll() {
|
|
197
|
-
if (!this.initialized) return;
|
|
198
|
-
this.workers.oscOut.postMessage({
|
|
199
|
-
type: "cancelAll"
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Clear debug buffer
|
|
204
|
-
*/
|
|
205
|
-
clearDebug() {
|
|
206
|
-
if (!this.initialized) return;
|
|
207
|
-
this.workers.debug.postMessage({
|
|
208
|
-
type: "clear"
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Get statistics from all workers
|
|
213
|
-
*/
|
|
214
|
-
async getStats() {
|
|
215
|
-
if (!this.initialized) {
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
const statsPromises = [
|
|
219
|
-
this.getWorkerStats(this.workers.oscOut, "oscOut"),
|
|
220
|
-
this.getWorkerStats(this.workers.oscIn, "oscIn"),
|
|
221
|
-
this.getWorkerStats(this.workers.debug, "debug")
|
|
222
|
-
];
|
|
223
|
-
const results = await Promise.all(statsPromises);
|
|
224
|
-
return {
|
|
225
|
-
oscOut: results[0],
|
|
226
|
-
oscIn: results[1],
|
|
227
|
-
debug: results[2]
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Get stats from a single worker
|
|
232
|
-
*/
|
|
233
|
-
getWorkerStats(worker, name) {
|
|
234
|
-
return new Promise((resolve) => {
|
|
235
|
-
const timeout = setTimeout(() => {
|
|
236
|
-
resolve({ error: "Timeout getting stats" });
|
|
237
|
-
}, 1e3);
|
|
238
|
-
const handler = (event) => {
|
|
239
|
-
if (event.data.type === "stats") {
|
|
240
|
-
clearTimeout(timeout);
|
|
241
|
-
worker.removeEventListener("message", handler);
|
|
242
|
-
resolve(event.data.stats);
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
worker.addEventListener("message", handler);
|
|
246
|
-
worker.postMessage({ type: "getStats" });
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Set callback for OSC messages received from scsynth
|
|
251
|
-
*/
|
|
252
|
-
onOSCMessage(callback) {
|
|
253
|
-
this.callbacks.onOSCMessage = callback;
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Set callback for debug messages
|
|
257
|
-
*/
|
|
258
|
-
onDebugMessage(callback) {
|
|
259
|
-
this.callbacks.onDebugMessage = callback;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Set callback for errors
|
|
263
|
-
*/
|
|
264
|
-
onError(callback) {
|
|
265
|
-
this.callbacks.onError = callback;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Set callback for initialization complete
|
|
269
|
-
*/
|
|
270
|
-
onInitialized(callback) {
|
|
271
|
-
this.callbacks.onInitialized = callback;
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Terminate all workers and cleanup
|
|
275
|
-
*/
|
|
276
|
-
terminate() {
|
|
277
|
-
if (this.workers.oscOut) {
|
|
278
|
-
this.workers.oscOut.postMessage({ type: "stop" });
|
|
279
|
-
this.workers.oscOut.terminate();
|
|
280
|
-
}
|
|
281
|
-
if (this.workers.oscIn) {
|
|
282
|
-
this.workers.oscIn.postMessage({ type: "stop" });
|
|
283
|
-
this.workers.oscIn.terminate();
|
|
284
|
-
}
|
|
285
|
-
if (this.workers.debug) {
|
|
286
|
-
this.workers.debug.postMessage({ type: "stop" });
|
|
287
|
-
this.workers.debug.terminate();
|
|
288
|
-
}
|
|
289
|
-
this.workers = {
|
|
290
|
-
oscOut: null,
|
|
291
|
-
oscIn: null,
|
|
292
|
-
debug: null
|
|
293
|
-
};
|
|
294
|
-
this.initialized = false;
|
|
295
|
-
console.log("[ScsynthOSC] All workers terminated");
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
1
|
// js/vendor/osc.js/osc.js
|
|
300
2
|
var osc = {};
|
|
301
3
|
var osc = osc || {};
|
|
@@ -879,188 +581,494 @@ EventEmitter.prototype.removeListener = function() {
|
|
|
879
581
|
osc.fireBundleEvents(port, packet, timeTag, packetInfo);
|
|
880
582
|
}
|
|
881
583
|
};
|
|
882
|
-
osc.fireBundleEvents = function(port, bundle, timeTag, packetInfo) {
|
|
883
|
-
port.emit("bundle", bundle, timeTag, packetInfo);
|
|
884
|
-
for (var i = 0; i < bundle.packets.length; i++) {
|
|
885
|
-
var packet = bundle.packets[i];
|
|
886
|
-
osc.firePacketEvents(port, packet, bundle.timeTag, packetInfo);
|
|
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);
|
|
887
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
|
+
};
|
|
888
739
|
};
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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");
|
|
892
749
|
};
|
|
893
|
-
|
|
894
|
-
this.
|
|
895
|
-
|
|
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);
|
|
896
756
|
};
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
p.send = function(oscPacket) {
|
|
900
|
-
var args = Array.prototype.slice.call(arguments), encoded = this.encodeOSC(oscPacket), buf = osc.nativeBuffer(encoded);
|
|
901
|
-
args[0] = buf;
|
|
902
|
-
this.sendRaw.apply(this, args);
|
|
757
|
+
p.close = function(code, reason) {
|
|
758
|
+
this.socket.close(code, reason);
|
|
903
759
|
};
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
var encoded;
|
|
907
|
-
try {
|
|
908
|
-
encoded = osc.writePacket(packet, this.options);
|
|
909
|
-
} catch (err) {
|
|
910
|
-
this.emit("error", err);
|
|
911
|
-
}
|
|
912
|
-
return encoded;
|
|
760
|
+
osc.WebSocketPort.setupSocketForBinary = function(socket) {
|
|
761
|
+
socket.binaryType = osc.isNode ? "nodebuffer" : "arraybuffer";
|
|
913
762
|
};
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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() {
|
|
770
|
+
this.workers = {
|
|
771
|
+
oscOut: null,
|
|
772
|
+
oscIn: null,
|
|
773
|
+
debug: null
|
|
774
|
+
};
|
|
775
|
+
this.callbacks = {
|
|
776
|
+
onOSCMessage: null,
|
|
777
|
+
onDebugMessage: null,
|
|
778
|
+
onError: null,
|
|
779
|
+
onInitialized: null
|
|
780
|
+
};
|
|
781
|
+
this.initialized = false;
|
|
782
|
+
this.sharedBuffer = null;
|
|
783
|
+
this.ringBufferBase = null;
|
|
784
|
+
this.bufferConstants = null;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Initialize all workers with SharedArrayBuffer
|
|
788
|
+
*/
|
|
789
|
+
async init(sharedBuffer, ringBufferBase, bufferConstants) {
|
|
790
|
+
if (this.initialized) {
|
|
791
|
+
console.warn("[ScsynthOSC] Already initialized");
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
this.sharedBuffer = sharedBuffer;
|
|
795
|
+
this.ringBufferBase = ringBufferBase;
|
|
796
|
+
this.bufferConstants = bufferConstants;
|
|
917
797
|
try {
|
|
918
|
-
|
|
919
|
-
this.
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
798
|
+
this.workers.oscOut = new Worker("./dist/workers/osc_out_worker.js");
|
|
799
|
+
this.workers.oscIn = new Worker("./dist/workers/osc_in_worker.js");
|
|
800
|
+
this.workers.debug = new Worker("./dist/workers/debug_worker.js");
|
|
801
|
+
this.setupWorkerHandlers();
|
|
802
|
+
const initPromises = [
|
|
803
|
+
this.initWorker(this.workers.oscOut, "OSC OUT"),
|
|
804
|
+
this.initWorker(this.workers.oscIn, "OSC IN"),
|
|
805
|
+
this.initWorker(this.workers.debug, "DEBUG")
|
|
806
|
+
];
|
|
807
|
+
await Promise.all(initPromises);
|
|
808
|
+
this.workers.oscIn.postMessage({ type: "start" });
|
|
809
|
+
this.workers.debug.postMessage({ type: "start" });
|
|
810
|
+
this.initialized = true;
|
|
811
|
+
if (this.callbacks.onInitialized) {
|
|
812
|
+
this.callbacks.onInitialized();
|
|
813
|
+
}
|
|
814
|
+
} catch (error) {
|
|
815
|
+
console.error("[ScsynthOSC] Initialization failed:", error);
|
|
816
|
+
if (this.callbacks.onError) {
|
|
817
|
+
this.callbacks.onError(error);
|
|
818
|
+
}
|
|
819
|
+
throw error;
|
|
923
820
|
}
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Initialize a single worker
|
|
824
|
+
*/
|
|
825
|
+
initWorker(worker, name) {
|
|
826
|
+
return new Promise((resolve, reject) => {
|
|
827
|
+
const timeout = setTimeout(() => {
|
|
828
|
+
reject(new Error(`${name} worker initialization timeout`));
|
|
829
|
+
}, 5e3);
|
|
830
|
+
const handler = (event) => {
|
|
831
|
+
if (event.data.type === "initialized") {
|
|
832
|
+
clearTimeout(timeout);
|
|
833
|
+
worker.removeEventListener("message", handler);
|
|
834
|
+
resolve();
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
worker.addEventListener("message", handler);
|
|
838
|
+
worker.postMessage({
|
|
839
|
+
type: "init",
|
|
840
|
+
sharedBuffer: this.sharedBuffer,
|
|
841
|
+
ringBufferBase: this.ringBufferBase,
|
|
842
|
+
bufferConstants: this.bufferConstants
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Set up message handlers for all workers
|
|
848
|
+
*/
|
|
849
|
+
setupWorkerHandlers() {
|
|
850
|
+
this.workers.oscIn.onmessage = (event) => {
|
|
851
|
+
const data = event.data;
|
|
852
|
+
switch (data.type) {
|
|
853
|
+
case "messages":
|
|
854
|
+
if (this.callbacks.onOSCMessage) {
|
|
855
|
+
data.messages.forEach((msg) => {
|
|
856
|
+
if (msg.oscData) {
|
|
857
|
+
try {
|
|
858
|
+
const options = { metadata: false, unpackSingleArgs: false };
|
|
859
|
+
const decoded = osc_default.readPacket(msg.oscData, options);
|
|
860
|
+
this.callbacks.onOSCMessage(decoded);
|
|
861
|
+
} catch (e) {
|
|
862
|
+
console.error("[ScsynthOSC] Failed to decode OSC message:", e, msg);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
break;
|
|
868
|
+
case "error":
|
|
869
|
+
console.error("[ScsynthOSC] OSC IN error:", data.error);
|
|
870
|
+
if (this.callbacks.onError) {
|
|
871
|
+
this.callbacks.onError(data.error, "oscIn");
|
|
872
|
+
}
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
this.workers.debug.onmessage = (event) => {
|
|
877
|
+
const data = event.data;
|
|
878
|
+
switch (data.type) {
|
|
879
|
+
case "debug":
|
|
880
|
+
if (this.callbacks.onDebugMessage) {
|
|
881
|
+
data.messages.forEach((msg) => {
|
|
882
|
+
this.callbacks.onDebugMessage(msg);
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
break;
|
|
886
|
+
case "error":
|
|
887
|
+
console.error("[ScsynthOSC] DEBUG error:", data.error);
|
|
888
|
+
if (this.callbacks.onError) {
|
|
889
|
+
this.callbacks.onError(data.error, "debug");
|
|
890
|
+
}
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
this.workers.oscOut.onmessage = (event) => {
|
|
895
|
+
const data = event.data;
|
|
896
|
+
switch (data.type) {
|
|
897
|
+
case "error":
|
|
898
|
+
console.error("[ScsynthOSC] OSC OUT error:", data.error);
|
|
899
|
+
if (this.callbacks.onError) {
|
|
900
|
+
this.callbacks.onError(data.error, "oscOut");
|
|
901
|
+
}
|
|
902
|
+
break;
|
|
933
903
|
}
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Send OSC data (message or bundle)
|
|
908
|
+
* - OSC messages are sent immediately
|
|
909
|
+
* - OSC bundles are scheduled based on waitTimeMs (calculated by SuperSonic)
|
|
910
|
+
*
|
|
911
|
+
* @param {Uint8Array} oscData - Binary OSC data (message or bundle)
|
|
912
|
+
* @param {Object} options - Optional metadata (editorId, runTag, waitTimeMs)
|
|
913
|
+
*/
|
|
914
|
+
send(oscData, options = {}) {
|
|
915
|
+
if (!this.initialized) {
|
|
916
|
+
console.error("[ScsynthOSC] Not initialized");
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const { editorId = 0, runTag = "", waitTimeMs = null } = options;
|
|
920
|
+
this.workers.oscOut.postMessage({
|
|
921
|
+
type: "send",
|
|
922
|
+
oscData,
|
|
923
|
+
editorId,
|
|
924
|
+
runTag,
|
|
925
|
+
waitTimeMs
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Send OSC data immediately, ignoring any bundle timestamps
|
|
930
|
+
* - Extracts all messages from bundles
|
|
931
|
+
* - Sends all messages immediately to scsynth
|
|
932
|
+
* - For applications that don't expect server-side scheduling
|
|
933
|
+
*
|
|
934
|
+
* @param {Uint8Array} oscData - Binary OSC data (message or bundle)
|
|
935
|
+
*/
|
|
936
|
+
sendImmediate(oscData) {
|
|
937
|
+
if (!this.initialized) {
|
|
938
|
+
console.error("[ScsynthOSC] Not initialized");
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
this.workers.oscOut.postMessage({
|
|
942
|
+
type: "sendImmediate",
|
|
943
|
+
oscData
|
|
934
944
|
});
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Cancel scheduled OSC bundles by editor and tag
|
|
948
|
+
*/
|
|
949
|
+
cancelEditorTag(editorId, runTag) {
|
|
950
|
+
if (!this.initialized) return;
|
|
951
|
+
this.workers.oscOut.postMessage({
|
|
952
|
+
type: "cancelEditorTag",
|
|
953
|
+
editorId,
|
|
954
|
+
runTag
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Cancel all scheduled OSC bundles from an editor
|
|
959
|
+
*/
|
|
960
|
+
cancelEditor(editorId) {
|
|
961
|
+
if (!this.initialized) return;
|
|
962
|
+
this.workers.oscOut.postMessage({
|
|
963
|
+
type: "cancelEditor",
|
|
964
|
+
editorId
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Cancel all scheduled OSC bundles
|
|
969
|
+
*/
|
|
970
|
+
cancelAll() {
|
|
971
|
+
if (!this.initialized) return;
|
|
972
|
+
this.workers.oscOut.postMessage({
|
|
973
|
+
type: "cancelAll"
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Clear debug buffer
|
|
978
|
+
*/
|
|
979
|
+
clearDebug() {
|
|
980
|
+
if (!this.initialized) return;
|
|
981
|
+
this.workers.debug.postMessage({
|
|
982
|
+
type: "clear"
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Get statistics from all workers
|
|
987
|
+
*/
|
|
988
|
+
async getStats() {
|
|
989
|
+
if (!this.initialized) {
|
|
990
|
+
return null;
|
|
948
991
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
eventName = eventName || "message";
|
|
956
|
-
sendFnName = sendFnName || "send";
|
|
957
|
-
transformFn = transformFn || function() {
|
|
958
|
-
};
|
|
959
|
-
sendArgs = sendArgs ? [null].concat(sendArgs) : [];
|
|
960
|
-
var listener = function(data) {
|
|
961
|
-
sendArgs[0] = data;
|
|
962
|
-
data = transformFn(data);
|
|
963
|
-
to[sendFnName].apply(to, sendArgs);
|
|
964
|
-
};
|
|
965
|
-
from.on(eventName, listener);
|
|
992
|
+
const statsPromises = [
|
|
993
|
+
this.getWorkerStats(this.workers.oscOut, "oscOut"),
|
|
994
|
+
this.getWorkerStats(this.workers.oscIn, "oscIn"),
|
|
995
|
+
this.getWorkerStats(this.workers.debug, "debug")
|
|
996
|
+
];
|
|
997
|
+
const results = await Promise.all(statsPromises);
|
|
966
998
|
return {
|
|
967
|
-
|
|
968
|
-
|
|
999
|
+
oscOut: results[0],
|
|
1000
|
+
oscIn: results[1],
|
|
1001
|
+
debug: results[2]
|
|
969
1002
|
};
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Get stats from a single worker
|
|
1006
|
+
*/
|
|
1007
|
+
getWorkerStats(worker, name) {
|
|
1008
|
+
return new Promise((resolve) => {
|
|
1009
|
+
const timeout = setTimeout(() => {
|
|
1010
|
+
resolve({ error: "Timeout getting stats" });
|
|
1011
|
+
}, 1e3);
|
|
1012
|
+
const handler = (event) => {
|
|
1013
|
+
if (event.data.type === "stats") {
|
|
1014
|
+
clearTimeout(timeout);
|
|
1015
|
+
worker.removeEventListener("message", handler);
|
|
1016
|
+
resolve(event.data.stats);
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
worker.addEventListener("message", handler);
|
|
1020
|
+
worker.postMessage({ type: "getStats" });
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Set callback for OSC messages received from scsynth
|
|
1025
|
+
*/
|
|
1026
|
+
onOSCMessage(callback) {
|
|
1027
|
+
this.callbacks.onOSCMessage = callback;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Set callback for debug messages
|
|
1031
|
+
*/
|
|
1032
|
+
onDebugMessage(callback) {
|
|
1033
|
+
this.callbacks.onDebugMessage = callback;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Set callback for errors
|
|
1037
|
+
*/
|
|
1038
|
+
onError(callback) {
|
|
1039
|
+
this.callbacks.onError = callback;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Set callback for initialization complete
|
|
1043
|
+
*/
|
|
1044
|
+
onInitialized(callback) {
|
|
1045
|
+
this.callbacks.onInitialized = callback;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Terminate all workers and cleanup
|
|
1049
|
+
*/
|
|
1050
|
+
terminate() {
|
|
1051
|
+
if (this.workers.oscOut) {
|
|
1052
|
+
this.workers.oscOut.postMessage({ type: "stop" });
|
|
1053
|
+
this.workers.oscOut.terminate();
|
|
994
1054
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
this.port1.on("close", closeListener);
|
|
999
|
-
this.port2.on("close", closeListener);
|
|
1000
|
-
};
|
|
1001
|
-
p.close = function() {
|
|
1002
|
-
osc.stopRelaying(this.port1, this.port1Spec);
|
|
1003
|
-
osc.stopRelaying(this.port2, this.port2Spec);
|
|
1004
|
-
this.emit("close", this.port1, this.port2);
|
|
1005
|
-
};
|
|
1006
|
-
})();
|
|
1007
|
-
(function() {
|
|
1008
|
-
"use strict";
|
|
1009
|
-
osc.WebSocket = typeof WebSocket !== "undefined" ? WebSocket : void 0;
|
|
1010
|
-
osc.WebSocketPort = function(options) {
|
|
1011
|
-
osc.Port.call(this, options);
|
|
1012
|
-
this.on("open", this.listen.bind(this));
|
|
1013
|
-
this.socket = options.socket;
|
|
1014
|
-
if (this.socket) {
|
|
1015
|
-
if (this.socket.readyState === 1) {
|
|
1016
|
-
osc.WebSocketPort.setupSocketForBinary(this.socket);
|
|
1017
|
-
this.emit("open", this.socket);
|
|
1018
|
-
} else {
|
|
1019
|
-
this.open();
|
|
1020
|
-
}
|
|
1055
|
+
if (this.workers.oscIn) {
|
|
1056
|
+
this.workers.oscIn.postMessage({ type: "stop" });
|
|
1057
|
+
this.workers.oscIn.terminate();
|
|
1021
1058
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
p.open = function() {
|
|
1026
|
-
if (!this.socket || this.socket.readyState > 1) {
|
|
1027
|
-
this.socket = new osc.WebSocket(this.options.url);
|
|
1059
|
+
if (this.workers.debug) {
|
|
1060
|
+
this.workers.debug.postMessage({ type: "stop" });
|
|
1061
|
+
this.workers.debug.terminate();
|
|
1028
1062
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
};
|
|
1034
|
-
this.socket.onerror = function(err) {
|
|
1035
|
-
that.emit("error", err);
|
|
1036
|
-
};
|
|
1037
|
-
};
|
|
1038
|
-
p.listen = function() {
|
|
1039
|
-
var that = this;
|
|
1040
|
-
this.socket.onmessage = function(e) {
|
|
1041
|
-
that.emit("data", e.data, e);
|
|
1042
|
-
};
|
|
1043
|
-
this.socket.onclose = function(e) {
|
|
1044
|
-
that.emit("close", e);
|
|
1063
|
+
this.workers = {
|
|
1064
|
+
oscOut: null,
|
|
1065
|
+
oscIn: null,
|
|
1066
|
+
debug: null
|
|
1045
1067
|
};
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
osc.fireClosedPortSendError(this);
|
|
1051
|
-
return;
|
|
1052
|
-
}
|
|
1053
|
-
this.socket.send(encoded);
|
|
1054
|
-
};
|
|
1055
|
-
p.close = function(code, reason) {
|
|
1056
|
-
this.socket.close(code, reason);
|
|
1057
|
-
};
|
|
1058
|
-
osc.WebSocketPort.setupSocketForBinary = function(socket) {
|
|
1059
|
-
socket.binaryType = osc.isNode ? "nodebuffer" : "arraybuffer";
|
|
1060
|
-
};
|
|
1061
|
-
})();
|
|
1062
|
-
var osc_default = osc;
|
|
1063
|
-
var { readPacket, writePacket, readMessage, writeMessage, readBundle, writeBundle } = osc;
|
|
1068
|
+
this.initialized = false;
|
|
1069
|
+
console.log("[ScsynthOSC] All workers terminated");
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1064
1072
|
|
|
1065
1073
|
// node_modules/@thi.ng/api/typedarray.js
|
|
1066
1074
|
var GL2TYPE = {
|
|
@@ -1596,6 +1604,7 @@ var SuperSonic = class {
|
|
|
1596
1604
|
this.wasmModule = null;
|
|
1597
1605
|
this.wasmInstance = null;
|
|
1598
1606
|
this.bufferPool = null;
|
|
1607
|
+
this.pendingBufferOps = /* @__PURE__ */ new Map();
|
|
1599
1608
|
this.wasmTimeOffset = null;
|
|
1600
1609
|
this._timeOffsetPromise = null;
|
|
1601
1610
|
this._resolveTimeOffset = null;
|
|
@@ -1790,6 +1799,8 @@ var SuperSonic = class {
|
|
|
1790
1799
|
this.osc.onOSCMessage((msg) => {
|
|
1791
1800
|
if (msg.address === "/buffer/freed") {
|
|
1792
1801
|
this._handleBufferFreed(msg.args);
|
|
1802
|
+
} else if (msg.address === "/buffer/allocated") {
|
|
1803
|
+
this._handleBufferAllocated(msg.args);
|
|
1793
1804
|
}
|
|
1794
1805
|
if (this.onMessageReceived) {
|
|
1795
1806
|
this.stats.messagesReceived++;
|
|
@@ -2077,14 +2088,24 @@ var SuperSonic = class {
|
|
|
2077
2088
|
ptr: allocatedPtr,
|
|
2078
2089
|
size: bytesNeeded
|
|
2079
2090
|
});
|
|
2091
|
+
const uuid = crypto.randomUUID();
|
|
2092
|
+
const allocationComplete = new Promise((resolve, reject) => {
|
|
2093
|
+
const timeout = setTimeout(() => {
|
|
2094
|
+
this.pendingBufferOps.delete(uuid);
|
|
2095
|
+
reject(new Error(`Timeout waiting for buffer ${bufnum} allocation`));
|
|
2096
|
+
}, 5e3);
|
|
2097
|
+
this.pendingBufferOps.set(uuid, { resolve, reject, timeout });
|
|
2098
|
+
});
|
|
2080
2099
|
await this.send(
|
|
2081
2100
|
"/b_allocPtr",
|
|
2082
2101
|
bufnum,
|
|
2083
2102
|
allocatedPtr,
|
|
2084
2103
|
framesToRead,
|
|
2085
2104
|
numChannels,
|
|
2086
|
-
audioBuffer.sampleRate
|
|
2105
|
+
audioBuffer.sampleRate,
|
|
2106
|
+
uuid
|
|
2087
2107
|
);
|
|
2108
|
+
await allocationComplete;
|
|
2088
2109
|
if (completionMsg) {
|
|
2089
2110
|
}
|
|
2090
2111
|
} catch (error) {
|
|
@@ -2114,10 +2135,6 @@ var SuperSonic = class {
|
|
|
2114
2135
|
* Handle /buffer/freed message from WASM
|
|
2115
2136
|
*/
|
|
2116
2137
|
_handleBufferFreed(args) {
|
|
2117
|
-
if (args.length < 2) {
|
|
2118
|
-
console.warn("[SuperSonic] Invalid /buffer/freed message:", args);
|
|
2119
|
-
return;
|
|
2120
|
-
}
|
|
2121
2138
|
const bufnum = args[0];
|
|
2122
2139
|
const offset = args[1];
|
|
2123
2140
|
const bufferInfo = this.allocatedBuffers.get(bufnum);
|
|
@@ -2126,6 +2143,19 @@ var SuperSonic = class {
|
|
|
2126
2143
|
this.allocatedBuffers.delete(bufnum);
|
|
2127
2144
|
}
|
|
2128
2145
|
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Handle /buffer/allocated message with UUID correlation
|
|
2148
|
+
*/
|
|
2149
|
+
_handleBufferAllocated(args) {
|
|
2150
|
+
const uuid = args[0];
|
|
2151
|
+
const bufnum = args[1];
|
|
2152
|
+
const pending = this.pendingBufferOps.get(uuid);
|
|
2153
|
+
if (pending) {
|
|
2154
|
+
clearTimeout(pending.timeout);
|
|
2155
|
+
pending.resolve({ bufnum });
|
|
2156
|
+
this.pendingBufferOps.delete(uuid);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2129
2159
|
/**
|
|
2130
2160
|
* /b_allocReadChannel bufnum path [startFrame numFrames channel1 channel2 ... completion]
|
|
2131
2161
|
* Load specific channels from an audio file
|
|
@@ -2287,6 +2317,18 @@ var SuperSonic = class {
|
|
|
2287
2317
|
this.initialized = false;
|
|
2288
2318
|
console.log("[SuperSonic] Destroyed");
|
|
2289
2319
|
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Load a sample into a buffer and wait for confirmation
|
|
2322
|
+
* @param {number} bufnum - Buffer number
|
|
2323
|
+
* @param {string} path - Audio file path
|
|
2324
|
+
* @returns {Promise} Resolves when buffer is ready
|
|
2325
|
+
*/
|
|
2326
|
+
async loadSample(bufnum, path, startFrame = 0, numFrames = 0) {
|
|
2327
|
+
if (!this.initialized) {
|
|
2328
|
+
throw new Error("SuperSonic not initialized. Call init() first.");
|
|
2329
|
+
}
|
|
2330
|
+
await this._allocReadBuffer(bufnum, path, startFrame, numFrames);
|
|
2331
|
+
}
|
|
2290
2332
|
/**
|
|
2291
2333
|
* Load a binary synthdef file and send it to scsynth
|
|
2292
2334
|
* @param {string} path - Path or URL to the .scsyndef file
|
package/dist/wasm/manifest.json
CHANGED
|
Binary file
|
|
@@ -71,11 +71,7 @@ function readMessages() {
|
|
|
71
71
|
|
|
72
72
|
var currentTail = tail;
|
|
73
73
|
var messagesRead = 0;
|
|
74
|
-
|
|
75
|
-
// - Low latency (don't block too long processing)
|
|
76
|
-
// - Efficiency (batch processing when busy)
|
|
77
|
-
// - Prevents starvation of other tasks
|
|
78
|
-
var maxMessages = 10;
|
|
74
|
+
var maxMessages = 100;
|
|
79
75
|
|
|
80
76
|
while (currentTail !== head && messagesRead < maxMessages) {
|
|
81
77
|
var readPos = ringBufferBase + bufferConstants.OUT_BUFFER_START + currentTail;
|
|
@@ -115,7 +111,7 @@ function readMessages() {
|
|
|
115
111
|
if (sequence !== expectedSeq) {
|
|
116
112
|
var dropped = (sequence - expectedSeq + 0x100000000) & 0xFFFFFFFF;
|
|
117
113
|
if (dropped < 1000) { // Sanity check
|
|
118
|
-
console.warn('[OSCInWorker] Detected', dropped, 'dropped messages');
|
|
114
|
+
console.warn('[OSCInWorker] Detected', dropped, 'dropped messages (expected seq', expectedSeq, 'got', sequence, ')');
|
|
119
115
|
stats.droppedMessages += dropped;
|
|
120
116
|
}
|
|
121
117
|
}
|
package/package.json
CHANGED