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.
- package/dist/supersonic.js +20 -3448
- package/dist/wasm/manifest.json +3 -3
- package/dist/wasm/scsynth-nrt.wasm +0 -0
- package/dist/workers/debug_worker.js +2 -281
- package/dist/workers/osc_in_worker.js +1 -279
- package/dist/workers/osc_out_prescheduler_worker.js +1 -705
- package/dist/workers/scsynth_audio_worklet.js +2 -543
- package/package.json +1 -1
package/dist/wasm/manifest.json
CHANGED
|
Binary file
|
|
@@ -1,281 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Sam Aaron
|
|
4
|
-
|
|
5
|
-
Based on SuperCollider by James McCartney and community
|
|
6
|
-
GPL v3 or later
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* DEBUG Worker - Receives debug messages from AudioWorklet
|
|
11
|
-
* Uses Atomics.wait() for instant wake when debug logs arrive
|
|
12
|
-
* Reads from DEBUG ring buffer and forwards to main thread
|
|
13
|
-
* ES5-compatible for Qt WebEngine
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
// Ring buffer configuration
|
|
17
|
-
var sharedBuffer = null;
|
|
18
|
-
var ringBufferBase = null;
|
|
19
|
-
var atomicView = null;
|
|
20
|
-
var dataView = null;
|
|
21
|
-
var uint8View = null;
|
|
22
|
-
|
|
23
|
-
// Ring buffer layout constants (loaded from WASM at initialization)
|
|
24
|
-
var bufferConstants = null;
|
|
25
|
-
|
|
26
|
-
// Control indices (calculated after init)
|
|
27
|
-
var CONTROL_INDICES = {};
|
|
28
|
-
|
|
29
|
-
// Metrics view (for writing stats to SAB)
|
|
30
|
-
var metricsView = null;
|
|
31
|
-
var METRICS_INDICES = {};
|
|
32
|
-
|
|
33
|
-
// Worker state
|
|
34
|
-
var running = false;
|
|
35
|
-
|
|
36
|
-
var DEBUG_DEBUGWORKER_LOGS = false;
|
|
37
|
-
function debugWorkerLog() {
|
|
38
|
-
if (DEBUG_DEBUGWORKER_LOGS) {
|
|
39
|
-
console.log.apply(console, arguments);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Initialize ring buffer access
|
|
45
|
-
*/
|
|
46
|
-
function initRingBuffer(buffer, base, constants) {
|
|
47
|
-
sharedBuffer = buffer;
|
|
48
|
-
ringBufferBase = base;
|
|
49
|
-
bufferConstants = constants;
|
|
50
|
-
atomicView = new Int32Array(sharedBuffer);
|
|
51
|
-
dataView = new DataView(sharedBuffer);
|
|
52
|
-
uint8View = new Uint8Array(sharedBuffer);
|
|
53
|
-
|
|
54
|
-
// Calculate control indices using constants from WASM
|
|
55
|
-
CONTROL_INDICES = {
|
|
56
|
-
DEBUG_HEAD: (ringBufferBase + bufferConstants.CONTROL_START + 16) / 4,
|
|
57
|
-
DEBUG_TAIL: (ringBufferBase + bufferConstants.CONTROL_START + 20) / 4
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Initialize metrics view (Debug metrics are at offsets 23-26 in the metrics array)
|
|
61
|
-
var metricsBase = ringBufferBase + bufferConstants.METRICS_START;
|
|
62
|
-
metricsView = new Uint32Array(sharedBuffer, metricsBase, bufferConstants.METRICS_SIZE / 4);
|
|
63
|
-
|
|
64
|
-
METRICS_INDICES = {
|
|
65
|
-
MESSAGES_RECEIVED: 23,
|
|
66
|
-
WAKEUPS: 24,
|
|
67
|
-
TIMEOUTS: 25,
|
|
68
|
-
BYTES_READ: 26
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Read debug messages from buffer
|
|
74
|
-
*/
|
|
75
|
-
function readDebugMessages() {
|
|
76
|
-
var head = Atomics.load(atomicView, CONTROL_INDICES.DEBUG_HEAD);
|
|
77
|
-
var tail = Atomics.load(atomicView, CONTROL_INDICES.DEBUG_TAIL);
|
|
78
|
-
|
|
79
|
-
if (head === tail) {
|
|
80
|
-
return null; // No messages
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
var messages = [];
|
|
84
|
-
var currentTail = tail;
|
|
85
|
-
var messagesRead = 0;
|
|
86
|
-
var maxMessages = 1000; // Process up to 1000 messages per wake
|
|
87
|
-
|
|
88
|
-
while (currentTail !== head && messagesRead < maxMessages) {
|
|
89
|
-
var bytesToEnd = bufferConstants.DEBUG_BUFFER_SIZE - currentTail;
|
|
90
|
-
if (bytesToEnd < bufferConstants.MESSAGE_HEADER_SIZE) {
|
|
91
|
-
currentTail = 0;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
var readPos = ringBufferBase + bufferConstants.DEBUG_BUFFER_START + currentTail;
|
|
96
|
-
|
|
97
|
-
// Read message header (now contiguous or wrapped)
|
|
98
|
-
var magic = dataView.getUint32(readPos, true);
|
|
99
|
-
|
|
100
|
-
// Check for padding marker - skip to beginning
|
|
101
|
-
if (magic === bufferConstants.PADDING_MAGIC) {
|
|
102
|
-
currentTail = 0;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Validate message magic
|
|
107
|
-
if (magic !== bufferConstants.MESSAGE_MAGIC) {
|
|
108
|
-
console.error('[DebugWorker] Corrupted message at position', currentTail);
|
|
109
|
-
// Skip this byte and continue
|
|
110
|
-
currentTail = (currentTail + 1) % bufferConstants.DEBUG_BUFFER_SIZE;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
var length = dataView.getUint32(readPos + 4, true);
|
|
115
|
-
var sequence = dataView.getUint32(readPos + 8, true);
|
|
116
|
-
|
|
117
|
-
// Validate message length
|
|
118
|
-
if (length < bufferConstants.MESSAGE_HEADER_SIZE || length > bufferConstants.DEBUG_BUFFER_SIZE) {
|
|
119
|
-
console.error('[DebugWorker] Invalid message length:', length);
|
|
120
|
-
currentTail = (currentTail + 1) % bufferConstants.DEBUG_BUFFER_SIZE;
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Read payload (debug text) - now contiguous due to padding
|
|
125
|
-
var payloadLength = length - bufferConstants.MESSAGE_HEADER_SIZE;
|
|
126
|
-
var payloadStart = readPos + bufferConstants.MESSAGE_HEADER_SIZE;
|
|
127
|
-
|
|
128
|
-
// Convert bytes to string using TextDecoder for proper UTF-8 handling
|
|
129
|
-
var payloadBytes = uint8View.slice(payloadStart, payloadStart + payloadLength);
|
|
130
|
-
var decoder = new TextDecoder('utf-8');
|
|
131
|
-
var messageText = decoder.decode(payloadBytes);
|
|
132
|
-
|
|
133
|
-
// Remove trailing newline if present
|
|
134
|
-
if (messageText.endsWith('\n')) {
|
|
135
|
-
messageText = messageText.slice(0, -1);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
messages.push({
|
|
139
|
-
text: messageText,
|
|
140
|
-
timestamp: performance.now(),
|
|
141
|
-
sequence: sequence
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// Move to next message
|
|
145
|
-
currentTail = (currentTail + length) % bufferConstants.DEBUG_BUFFER_SIZE;
|
|
146
|
-
messagesRead++;
|
|
147
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.MESSAGES_RECEIVED, 1);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Update tail pointer (consume messages)
|
|
151
|
-
if (messagesRead > 0) {
|
|
152
|
-
Atomics.store(atomicView, CONTROL_INDICES.DEBUG_TAIL, currentTail);
|
|
153
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.BYTES_READ, messagesRead);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return messages.length > 0 ? messages : null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Main wait loop using Atomics.wait for instant wake
|
|
161
|
-
*/
|
|
162
|
-
function waitLoop() {
|
|
163
|
-
while (running) {
|
|
164
|
-
try {
|
|
165
|
-
// Get current DEBUG_HEAD value
|
|
166
|
-
var currentHead = Atomics.load(atomicView, CONTROL_INDICES.DEBUG_HEAD);
|
|
167
|
-
var currentTail = Atomics.load(atomicView, CONTROL_INDICES.DEBUG_TAIL);
|
|
168
|
-
|
|
169
|
-
// If buffer is empty, wait for AudioWorklet to notify us
|
|
170
|
-
if (currentHead === currentTail) {
|
|
171
|
-
// Wait for up to 100ms (allows checking stop signal)
|
|
172
|
-
var result = Atomics.wait(atomicView, CONTROL_INDICES.DEBUG_HEAD, currentHead, 100);
|
|
173
|
-
|
|
174
|
-
if (result === 'ok' || result === 'not-equal') {
|
|
175
|
-
// We were notified or value changed!
|
|
176
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.WAKEUPS, 1);
|
|
177
|
-
} else if (result === 'timed-out') {
|
|
178
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.TIMEOUTS, 1);
|
|
179
|
-
continue; // Check running flag
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Read all available debug messages
|
|
184
|
-
var messages = readDebugMessages();
|
|
185
|
-
|
|
186
|
-
if (messages && messages.length > 0) {
|
|
187
|
-
// Send to main thread
|
|
188
|
-
self.postMessage({
|
|
189
|
-
type: 'debug',
|
|
190
|
-
messages: messages
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
} catch (error) {
|
|
195
|
-
console.error('[DebugWorker] Error in wait loop:', error);
|
|
196
|
-
self.postMessage({
|
|
197
|
-
type: 'error',
|
|
198
|
-
error: error.message
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Brief pause on error before retrying (use existing atomicView)
|
|
202
|
-
// Wait on a value that won't change for 10ms as a simple delay
|
|
203
|
-
Atomics.wait(atomicView, 0, atomicView[0], 10);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Start the wait loop
|
|
210
|
-
*/
|
|
211
|
-
function start() {
|
|
212
|
-
if (!sharedBuffer) {
|
|
213
|
-
console.error('[DebugWorker] Cannot start - not initialized');
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (running) {
|
|
218
|
-
console.warn('[DebugWorker] Already running');
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
running = true;
|
|
223
|
-
waitLoop();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Stop the wait loop
|
|
228
|
-
*/
|
|
229
|
-
function stop() {
|
|
230
|
-
running = false;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Clear debug buffer
|
|
235
|
-
*/
|
|
236
|
-
function clear() {
|
|
237
|
-
if (!sharedBuffer) return;
|
|
238
|
-
|
|
239
|
-
// Reset head and tail to 0
|
|
240
|
-
Atomics.store(atomicView, CONTROL_INDICES.DEBUG_HEAD, 0);
|
|
241
|
-
Atomics.store(atomicView, CONTROL_INDICES.DEBUG_TAIL, 0);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Handle messages from main thread
|
|
246
|
-
*/
|
|
247
|
-
self.addEventListener('message', function(event) {
|
|
248
|
-
var data = event.data;
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
switch (data.type) {
|
|
252
|
-
case 'init':
|
|
253
|
-
initRingBuffer(data.sharedBuffer, data.ringBufferBase, data.bufferConstants);
|
|
254
|
-
self.postMessage({ type: 'initialized' });
|
|
255
|
-
break;
|
|
256
|
-
|
|
257
|
-
case 'start':
|
|
258
|
-
start();
|
|
259
|
-
break;
|
|
260
|
-
|
|
261
|
-
case 'stop':
|
|
262
|
-
stop();
|
|
263
|
-
break;
|
|
264
|
-
|
|
265
|
-
case 'clear':
|
|
266
|
-
clear();
|
|
267
|
-
break;
|
|
268
|
-
|
|
269
|
-
default:
|
|
270
|
-
console.warn('[DebugWorker] Unknown message type:', data.type);
|
|
271
|
-
}
|
|
272
|
-
} catch (error) {
|
|
273
|
-
console.error('[DebugWorker] Error:', error);
|
|
274
|
-
self.postMessage({
|
|
275
|
-
type: 'error',
|
|
276
|
-
error: error.message
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
debugWorkerLog('[DebugWorker] Script loaded');
|
|
1
|
+
(()=>{var u=null,l=null,o=null,D=null,B=null,r=null,s={},i=null,f={},S=!1;function T(n,a,t){u=n,l=a,r=t,o=new Int32Array(u),D=new DataView(u),B=new Uint8Array(u),s={DEBUG_HEAD:(l+r.CONTROL_START+16)/4,DEBUG_TAIL:(l+r.CONTROL_START+20)/4};var e=l+r.METRICS_START;i=new Uint32Array(u,e,r.METRICS_SIZE/4),f={MESSAGES_RECEIVED:23,WAKEUPS:24,TIMEOUTS:25,BYTES_READ:26}}function p(){var n=Atomics.load(o,s.DEBUG_HEAD),a=Atomics.load(o,s.DEBUG_TAIL);if(n===a)return null;for(var t=[],e=a,E=0,U=1e3;e!==n&&E<U;){var I=r.DEBUG_BUFFER_SIZE-e;if(I<r.MESSAGE_HEADER_SIZE){e=0;continue}var A=l+r.DEBUG_BUFFER_START+e,v=D.getUint32(A,!0);if(v===r.PADDING_MAGIC){e=0;continue}if(v!==r.MESSAGE_MAGIC){console.error("[DebugWorker] Corrupted message at position",e),e=(e+1)%r.DEBUG_BUFFER_SIZE;continue}var c=D.getUint32(A+4,!0),g=D.getUint32(A+8,!0);if(c<r.MESSAGE_HEADER_SIZE||c>r.DEBUG_BUFFER_SIZE){console.error("[DebugWorker] Invalid message length:",c),e=(e+1)%r.DEBUG_BUFFER_SIZE;continue}var G=c-r.MESSAGE_HEADER_SIZE,d=A+r.MESSAGE_HEADER_SIZE,R=B.slice(d,d+G),m=new TextDecoder("utf-8"),_=m.decode(R);_.endsWith(`
|
|
2
|
+
`)&&(_=_.slice(0,-1)),t.push({text:_,timestamp:performance.now(),sequence:g}),e=(e+c)%r.DEBUG_BUFFER_SIZE,E++,i&&Atomics.add(i,f.MESSAGES_RECEIVED,1)}return E>0&&(Atomics.store(o,s.DEBUG_TAIL,e),i&&Atomics.add(i,f.BYTES_READ,E)),t.length>0?t:null}function w(){for(;S;)try{var n=Atomics.load(o,s.DEBUG_HEAD),a=Atomics.load(o,s.DEBUG_TAIL);if(n===a){var t=Atomics.wait(o,s.DEBUG_HEAD,n,100);if(t==="ok"||t==="not-equal")i&&Atomics.add(i,f.WAKEUPS,1);else if(t==="timed-out"){i&&Atomics.add(i,f.TIMEOUTS,1);continue}}var e=p();e&&e.length>0&&self.postMessage({type:"debug",messages:e})}catch(E){console.error("[DebugWorker] Error in wait loop:",E),self.postMessage({type:"error",error:E.message}),Atomics.wait(o,0,o[0],10)}}function M(){if(!u){console.error("[DebugWorker] Cannot start - not initialized");return}if(S){console.warn("[DebugWorker] Already running");return}S=!0,w()}function b(){S=!1}function C(){u&&(Atomics.store(o,s.DEBUG_HEAD,0),Atomics.store(o,s.DEBUG_TAIL,0))}self.addEventListener("message",function(n){var a=n.data;try{switch(a.type){case"init":T(a.sharedBuffer,a.ringBufferBase,a.bufferConstants),self.postMessage({type:"initialized"});break;case"start":M();break;case"stop":b();break;case"clear":C();break;default:console.warn("[DebugWorker] Unknown message type:",a.type)}}catch(t){console.error("[DebugWorker] Error:",t),self.postMessage({type:"error",error:t.message})}});})();
|
|
@@ -1,279 +1 @@
|
|
|
1
|
-
|
|
2
|
-
SuperSonic - SuperCollider AudioWorklet WebAssembly port
|
|
3
|
-
Copyright (c) 2025 Sam Aaron
|
|
4
|
-
|
|
5
|
-
Based on SuperCollider by James McCartney and community
|
|
6
|
-
GPL v3 or later
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* OSC IN Worker - Receives OSC messages from scsynth
|
|
11
|
-
* Uses Atomics.wait() for instant wake when data arrives
|
|
12
|
-
* Reads from OUT ring buffer and forwards to main thread
|
|
13
|
-
* ES5-compatible for Qt WebEngine
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
// Ring buffer configuration
|
|
17
|
-
var sharedBuffer = null;
|
|
18
|
-
var ringBufferBase = null;
|
|
19
|
-
var atomicView = null;
|
|
20
|
-
var dataView = null;
|
|
21
|
-
var uint8View = null;
|
|
22
|
-
|
|
23
|
-
// Ring buffer layout constants (loaded from WASM at initialization)
|
|
24
|
-
var bufferConstants = null;
|
|
25
|
-
|
|
26
|
-
// Control indices (calculated after init)
|
|
27
|
-
var CONTROL_INDICES = {};
|
|
28
|
-
|
|
29
|
-
// Metrics view (for writing stats to SAB)
|
|
30
|
-
var metricsView = null;
|
|
31
|
-
var METRICS_INDICES = {};
|
|
32
|
-
|
|
33
|
-
// Worker state
|
|
34
|
-
var running = false;
|
|
35
|
-
|
|
36
|
-
var DEBUG_OSCIN_LOGS = false;
|
|
37
|
-
function oscInLog() {
|
|
38
|
-
if (DEBUG_OSCIN_LOGS) {
|
|
39
|
-
console.log.apply(console, arguments);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Sequence tracking for dropped message detection
|
|
44
|
-
var lastSequenceReceived = -1;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Initialize ring buffer access
|
|
48
|
-
*/
|
|
49
|
-
function initRingBuffer(buffer, base, constants) {
|
|
50
|
-
sharedBuffer = buffer;
|
|
51
|
-
ringBufferBase = base;
|
|
52
|
-
bufferConstants = constants;
|
|
53
|
-
atomicView = new Int32Array(sharedBuffer);
|
|
54
|
-
dataView = new DataView(sharedBuffer);
|
|
55
|
-
uint8View = new Uint8Array(sharedBuffer);
|
|
56
|
-
|
|
57
|
-
// Calculate control indices using constants from WASM
|
|
58
|
-
CONTROL_INDICES = {
|
|
59
|
-
OUT_HEAD: (ringBufferBase + bufferConstants.CONTROL_START + 8) / 4,
|
|
60
|
-
OUT_TAIL: (ringBufferBase + bufferConstants.CONTROL_START + 12) / 4
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Initialize metrics view (OSC In metrics are at offsets 19-22 in the metrics array)
|
|
64
|
-
var metricsBase = ringBufferBase + bufferConstants.METRICS_START;
|
|
65
|
-
metricsView = new Uint32Array(sharedBuffer, metricsBase, bufferConstants.METRICS_SIZE / 4);
|
|
66
|
-
|
|
67
|
-
METRICS_INDICES = {
|
|
68
|
-
MESSAGES_RECEIVED: 19,
|
|
69
|
-
DROPPED_MESSAGES: 20,
|
|
70
|
-
WAKEUPS: 21,
|
|
71
|
-
TIMEOUTS: 22
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Read all available messages from OUT buffer
|
|
77
|
-
*/
|
|
78
|
-
function readMessages() {
|
|
79
|
-
var head = Atomics.load(atomicView, CONTROL_INDICES.OUT_HEAD);
|
|
80
|
-
var tail = Atomics.load(atomicView, CONTROL_INDICES.OUT_TAIL);
|
|
81
|
-
|
|
82
|
-
var messages = [];
|
|
83
|
-
|
|
84
|
-
if (head === tail) {
|
|
85
|
-
return messages; // No messages
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
var currentTail = tail;
|
|
89
|
-
var messagesRead = 0;
|
|
90
|
-
var maxMessages = 100;
|
|
91
|
-
|
|
92
|
-
while (currentTail !== head && messagesRead < maxMessages) {
|
|
93
|
-
var bytesToEnd = bufferConstants.OUT_BUFFER_SIZE - currentTail;
|
|
94
|
-
if (bytesToEnd < bufferConstants.MESSAGE_HEADER_SIZE) {
|
|
95
|
-
currentTail = 0;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
var readPos = ringBufferBase + bufferConstants.OUT_BUFFER_START + currentTail;
|
|
100
|
-
|
|
101
|
-
// Read message header (now contiguous or wrapped)
|
|
102
|
-
var magic = dataView.getUint32(readPos, true);
|
|
103
|
-
|
|
104
|
-
// Check for padding marker - skip to beginning
|
|
105
|
-
if (magic === bufferConstants.PADDING_MAGIC) {
|
|
106
|
-
currentTail = 0;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (magic !== bufferConstants.MESSAGE_MAGIC) {
|
|
111
|
-
console.error('[OSCInWorker] Corrupted message at position', currentTail);
|
|
112
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.DROPPED_MESSAGES, 1);
|
|
113
|
-
// Skip this byte and continue
|
|
114
|
-
currentTail = (currentTail + 1) % bufferConstants.OUT_BUFFER_SIZE;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
var length = dataView.getUint32(readPos + 4, true);
|
|
119
|
-
var sequence = dataView.getUint32(readPos + 8, true);
|
|
120
|
-
var padding = dataView.getUint32(readPos + 12, true); // unused padding field
|
|
121
|
-
|
|
122
|
-
// Validate message length
|
|
123
|
-
if (length < bufferConstants.MESSAGE_HEADER_SIZE || length > bufferConstants.OUT_BUFFER_SIZE) {
|
|
124
|
-
console.error('[OSCInWorker] Invalid message length:', length);
|
|
125
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.DROPPED_MESSAGES, 1);
|
|
126
|
-
currentTail = (currentTail + 1) % bufferConstants.OUT_BUFFER_SIZE;
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Check for dropped messages via sequence
|
|
131
|
-
if (lastSequenceReceived >= 0) {
|
|
132
|
-
var expectedSeq = (lastSequenceReceived + 1) & 0xFFFFFFFF;
|
|
133
|
-
if (sequence !== expectedSeq) {
|
|
134
|
-
var dropped = (sequence - expectedSeq + 0x100000000) & 0xFFFFFFFF;
|
|
135
|
-
if (dropped < 1000) { // Sanity check
|
|
136
|
-
console.warn('[OSCInWorker] Detected', dropped, 'dropped messages (expected seq', expectedSeq, 'got', sequence, ')');
|
|
137
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.DROPPED_MESSAGES, dropped);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
lastSequenceReceived = sequence;
|
|
142
|
-
|
|
143
|
-
// Read payload (OSC binary data) - now contiguous due to padding
|
|
144
|
-
var payloadLength = length - bufferConstants.MESSAGE_HEADER_SIZE;
|
|
145
|
-
var payloadStart = readPos + bufferConstants.MESSAGE_HEADER_SIZE;
|
|
146
|
-
|
|
147
|
-
// Create a proper copy (not a view into SharedArrayBuffer)
|
|
148
|
-
var payload = new Uint8Array(payloadLength);
|
|
149
|
-
for (var i = 0; i < payloadLength; i++) {
|
|
150
|
-
payload[i] = uint8View[payloadStart + i];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
messages.push({
|
|
154
|
-
oscData: payload,
|
|
155
|
-
sequence: sequence
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Move to next message
|
|
159
|
-
currentTail = (currentTail + length) % bufferConstants.OUT_BUFFER_SIZE;
|
|
160
|
-
messagesRead++;
|
|
161
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.MESSAGES_RECEIVED, 1);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Update tail pointer (consume messages)
|
|
165
|
-
if (messagesRead > 0) {
|
|
166
|
-
Atomics.store(atomicView, CONTROL_INDICES.OUT_TAIL, currentTail);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return messages;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Main wait loop using Atomics.wait for instant wake
|
|
174
|
-
*/
|
|
175
|
-
function waitLoop() {
|
|
176
|
-
while (running) {
|
|
177
|
-
try {
|
|
178
|
-
// Get current OUT_HEAD value
|
|
179
|
-
var currentHead = Atomics.load(atomicView, CONTROL_INDICES.OUT_HEAD);
|
|
180
|
-
var currentTail = Atomics.load(atomicView, CONTROL_INDICES.OUT_TAIL);
|
|
181
|
-
|
|
182
|
-
// If buffer is empty, wait for AudioWorklet to notify us
|
|
183
|
-
if (currentHead === currentTail) {
|
|
184
|
-
// Wait for up to 100ms (allows checking stop signal)
|
|
185
|
-
var result = Atomics.wait(atomicView, CONTROL_INDICES.OUT_HEAD, currentHead, 100);
|
|
186
|
-
|
|
187
|
-
if (result === 'ok' || result === 'not-equal') {
|
|
188
|
-
// We were notified or value changed!
|
|
189
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.WAKEUPS, 1);
|
|
190
|
-
} else if (result === 'timed-out') {
|
|
191
|
-
if (metricsView) Atomics.add(metricsView, METRICS_INDICES.TIMEOUTS, 1);
|
|
192
|
-
continue; // Check running flag
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Read all available messages
|
|
197
|
-
var messages = readMessages();
|
|
198
|
-
|
|
199
|
-
if (messages.length > 0) {
|
|
200
|
-
// Send to main thread
|
|
201
|
-
self.postMessage({
|
|
202
|
-
type: 'messages',
|
|
203
|
-
messages: messages
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
} catch (error) {
|
|
208
|
-
console.error('[OSCInWorker] Error in wait loop:', error);
|
|
209
|
-
self.postMessage({
|
|
210
|
-
type: 'error',
|
|
211
|
-
error: error.message
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Brief pause on error before retrying (use existing atomicView)
|
|
215
|
-
// Wait on a value that won't change for 10ms as a simple delay
|
|
216
|
-
Atomics.wait(atomicView, 0, atomicView[0], 10);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Start the wait loop
|
|
223
|
-
*/
|
|
224
|
-
function start() {
|
|
225
|
-
if (!sharedBuffer) {
|
|
226
|
-
console.error('[OSCInWorker] Cannot start - not initialized');
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (running) {
|
|
231
|
-
console.warn('[OSCInWorker] Already running');
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
running = true;
|
|
236
|
-
waitLoop();
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Stop the wait loop
|
|
241
|
-
*/
|
|
242
|
-
function stop() {
|
|
243
|
-
running = false;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Handle messages from main thread
|
|
248
|
-
*/
|
|
249
|
-
self.addEventListener('message', function(event) {
|
|
250
|
-
var data = event.data;
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
switch (data.type) {
|
|
254
|
-
case 'init':
|
|
255
|
-
initRingBuffer(data.sharedBuffer, data.ringBufferBase, data.bufferConstants);
|
|
256
|
-
self.postMessage({ type: 'initialized' });
|
|
257
|
-
break;
|
|
258
|
-
|
|
259
|
-
case 'start':
|
|
260
|
-
start();
|
|
261
|
-
break;
|
|
262
|
-
|
|
263
|
-
case 'stop':
|
|
264
|
-
stop();
|
|
265
|
-
break;
|
|
266
|
-
|
|
267
|
-
default:
|
|
268
|
-
console.warn('[OSCInWorker] Unknown message type:', data.type);
|
|
269
|
-
}
|
|
270
|
-
} catch (error) {
|
|
271
|
-
console.error('[OSCInWorker] Error:', error);
|
|
272
|
-
self.postMessage({
|
|
273
|
-
type: 'error',
|
|
274
|
-
error: error.message
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
oscInLog('[OSCInWorker] Script loaded');
|
|
1
|
+
(()=>{var f=null,l=null,o=null,u=null,F=null,e=null,E={},a=null,s={},v=!1;var U=-1;function M(i,t,n){f=i,l=t,e=n,o=new Int32Array(f),u=new DataView(f),F=new Uint8Array(f),E={OUT_HEAD:(l+e.CONTROL_START+8)/4,OUT_TAIL:(l+e.CONTROL_START+12)/4};var r=l+e.METRICS_START;a=new Uint32Array(f,r,e.METRICS_SIZE/4),s={MESSAGES_RECEIVED:19,DROPPED_MESSAGES:20,WAKEUPS:21,TIMEOUTS:22}}function w(){var i=Atomics.load(o,E.OUT_HEAD),t=Atomics.load(o,E.OUT_TAIL),n=[];if(i===t)return n;for(var r=t,S=0,g=100;r!==i&&S<g;){var D=e.OUT_BUFFER_SIZE-r;if(D<e.MESSAGE_HEADER_SIZE){r=0;continue}var A=l+e.OUT_BUFFER_START+r,d=u.getUint32(A,!0);if(d===e.PADDING_MAGIC){r=0;continue}if(d!==e.MESSAGE_MAGIC){console.error("[OSCInWorker] Corrupted message at position",r),a&&Atomics.add(a,s.DROPPED_MESSAGES,1),r=(r+1)%e.OUT_BUFFER_SIZE;continue}var c=u.getUint32(A+4,!0),_=u.getUint32(A+8,!0),G=u.getUint32(A+12,!0);if(c<e.MESSAGE_HEADER_SIZE||c>e.OUT_BUFFER_SIZE){console.error("[OSCInWorker] Invalid message length:",c),a&&Atomics.add(a,s.DROPPED_MESSAGES,1),r=(r+1)%e.OUT_BUFFER_SIZE;continue}if(U>=0){var T=U+1&4294967295;if(_!==T){var O=_-T+4294967296&4294967295;O<1e3&&(console.warn("[OSCInWorker] Detected",O,"dropped messages (expected seq",T,"got",_,")"),a&&Atomics.add(a,s.DROPPED_MESSAGES,O))}}U=_;for(var R=c-e.MESSAGE_HEADER_SIZE,C=A+e.MESSAGE_HEADER_SIZE,m=new Uint8Array(R),I=0;I<R;I++)m[I]=F[C+I];n.push({oscData:m,sequence:_}),r=(r+c)%e.OUT_BUFFER_SIZE,S++,a&&Atomics.add(a,s.MESSAGES_RECEIVED,1)}return S>0&&Atomics.store(o,E.OUT_TAIL,r),n}function p(){for(;v;)try{var i=Atomics.load(o,E.OUT_HEAD),t=Atomics.load(o,E.OUT_TAIL);if(i===t){var n=Atomics.wait(o,E.OUT_HEAD,i,100);if(n==="ok"||n==="not-equal")a&&Atomics.add(a,s.WAKEUPS,1);else if(n==="timed-out"){a&&Atomics.add(a,s.TIMEOUTS,1);continue}}var r=w();r.length>0&&self.postMessage({type:"messages",messages:r})}catch(S){console.error("[OSCInWorker] Error in wait loop:",S),self.postMessage({type:"error",error:S.message}),Atomics.wait(o,0,o[0],10)}}function k(){if(!f){console.error("[OSCInWorker] Cannot start - not initialized");return}if(v){console.warn("[OSCInWorker] Already running");return}v=!0,p()}function B(){v=!1}self.addEventListener("message",function(i){var t=i.data;try{switch(t.type){case"init":M(t.sharedBuffer,t.ringBufferBase,t.bufferConstants),self.postMessage({type:"initialized"});break;case"start":k();break;case"stop":B();break;default:console.warn("[OSCInWorker] Unknown message type:",t.type)}}catch(n){console.error("[OSCInWorker] Error:",n),self.postMessage({type:"error",error:n.message})}});})();
|