supersonic-scsynth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +27 -0
- package/README.md +320 -0
- package/dist/README.md +21 -0
- package/dist/supersonic.js +2411 -0
- package/dist/wasm/manifest.json +8 -0
- package/dist/wasm/scsynth-nrt.wasm +0 -0
- package/dist/workers/debug_worker.js +274 -0
- package/dist/workers/osc_in_worker.js +274 -0
- package/dist/workers/osc_out_worker.js +519 -0
- package/dist/workers/scsynth_audio_worklet.js +531 -0
- package/package.json +51 -0
|
Binary file
|
|
@@ -0,0 +1,274 @@
|
|
|
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
|
+
* 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
|
+
// Worker state
|
|
30
|
+
var running = false;
|
|
31
|
+
|
|
32
|
+
// Statistics
|
|
33
|
+
var stats = {
|
|
34
|
+
messagesReceived: 0,
|
|
35
|
+
wakeups: 0,
|
|
36
|
+
timeouts: 0,
|
|
37
|
+
bytesRead: 0
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize ring buffer access
|
|
42
|
+
*/
|
|
43
|
+
function initRingBuffer(buffer, base, constants) {
|
|
44
|
+
sharedBuffer = buffer;
|
|
45
|
+
ringBufferBase = base;
|
|
46
|
+
bufferConstants = constants;
|
|
47
|
+
atomicView = new Int32Array(sharedBuffer);
|
|
48
|
+
dataView = new DataView(sharedBuffer);
|
|
49
|
+
uint8View = new Uint8Array(sharedBuffer);
|
|
50
|
+
|
|
51
|
+
// Calculate control indices using constants from WASM
|
|
52
|
+
CONTROL_INDICES = {
|
|
53
|
+
DEBUG_HEAD: (ringBufferBase + bufferConstants.CONTROL_START + 16) / 4,
|
|
54
|
+
DEBUG_TAIL: (ringBufferBase + bufferConstants.CONTROL_START + 20) / 4
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read debug messages from buffer
|
|
60
|
+
*/
|
|
61
|
+
function readDebugMessages() {
|
|
62
|
+
var head = Atomics.load(atomicView, CONTROL_INDICES.DEBUG_HEAD);
|
|
63
|
+
var tail = Atomics.load(atomicView, CONTROL_INDICES.DEBUG_TAIL);
|
|
64
|
+
|
|
65
|
+
if (head === tail) {
|
|
66
|
+
return null; // No messages
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
var messages = [];
|
|
70
|
+
var currentTail = tail;
|
|
71
|
+
var messagesRead = 0;
|
|
72
|
+
var maxMessages = 10; // Process up to 10 messages per wake
|
|
73
|
+
|
|
74
|
+
while (currentTail !== head && messagesRead < maxMessages) {
|
|
75
|
+
var readPos = ringBufferBase + bufferConstants.DEBUG_BUFFER_START + currentTail;
|
|
76
|
+
|
|
77
|
+
// Read message header (now always contiguous due to padding)
|
|
78
|
+
var magic = dataView.getUint32(readPos, true);
|
|
79
|
+
|
|
80
|
+
// Check for padding marker - skip to beginning
|
|
81
|
+
if (magic === bufferConstants.PADDING_MAGIC) {
|
|
82
|
+
currentTail = 0;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Validate message magic
|
|
87
|
+
if (magic !== bufferConstants.MESSAGE_MAGIC) {
|
|
88
|
+
console.error('[DebugWorker] Corrupted message at position', currentTail);
|
|
89
|
+
// Skip this byte and continue
|
|
90
|
+
currentTail = (currentTail + 1) % bufferConstants.DEBUG_BUFFER_SIZE;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var length = dataView.getUint32(readPos + 4, true);
|
|
95
|
+
var sequence = dataView.getUint32(readPos + 8, true);
|
|
96
|
+
|
|
97
|
+
// Validate message length
|
|
98
|
+
if (length < bufferConstants.MESSAGE_HEADER_SIZE || length > bufferConstants.DEBUG_BUFFER_SIZE) {
|
|
99
|
+
console.error('[DebugWorker] Invalid message length:', length);
|
|
100
|
+
currentTail = (currentTail + 1) % bufferConstants.DEBUG_BUFFER_SIZE;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Read payload (debug text) - now contiguous due to padding
|
|
105
|
+
var payloadLength = length - bufferConstants.MESSAGE_HEADER_SIZE;
|
|
106
|
+
var payloadStart = readPos + bufferConstants.MESSAGE_HEADER_SIZE;
|
|
107
|
+
|
|
108
|
+
// Convert bytes to string using TextDecoder for proper UTF-8 handling
|
|
109
|
+
var payloadBytes = uint8View.slice(payloadStart, payloadStart + payloadLength);
|
|
110
|
+
var decoder = new TextDecoder('utf-8');
|
|
111
|
+
var messageText = decoder.decode(payloadBytes);
|
|
112
|
+
|
|
113
|
+
// Remove trailing newline if present
|
|
114
|
+
if (messageText.endsWith('\n')) {
|
|
115
|
+
messageText = messageText.slice(0, -1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
messages.push({
|
|
119
|
+
text: messageText,
|
|
120
|
+
timestamp: performance.now(),
|
|
121
|
+
sequence: sequence
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Move to next message
|
|
125
|
+
currentTail = (currentTail + length) % bufferConstants.DEBUG_BUFFER_SIZE;
|
|
126
|
+
messagesRead++;
|
|
127
|
+
stats.messagesReceived++;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Update tail pointer (consume messages)
|
|
131
|
+
if (messagesRead > 0) {
|
|
132
|
+
Atomics.store(atomicView, CONTROL_INDICES.DEBUG_TAIL, currentTail);
|
|
133
|
+
stats.bytesRead += messagesRead;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return messages.length > 0 ? messages : null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Main wait loop using Atomics.wait for instant wake
|
|
141
|
+
*/
|
|
142
|
+
function waitLoop() {
|
|
143
|
+
while (running) {
|
|
144
|
+
try {
|
|
145
|
+
// Get current DEBUG_HEAD value
|
|
146
|
+
var currentHead = Atomics.load(atomicView, CONTROL_INDICES.DEBUG_HEAD);
|
|
147
|
+
var currentTail = Atomics.load(atomicView, CONTROL_INDICES.DEBUG_TAIL);
|
|
148
|
+
|
|
149
|
+
// If buffer is empty, wait for AudioWorklet to notify us
|
|
150
|
+
if (currentHead === currentTail) {
|
|
151
|
+
// Wait for up to 100ms (allows checking stop signal)
|
|
152
|
+
var result = Atomics.wait(atomicView, CONTROL_INDICES.DEBUG_HEAD, currentHead, 100);
|
|
153
|
+
|
|
154
|
+
if (result === 'ok' || result === 'not-equal') {
|
|
155
|
+
// We were notified or value changed!
|
|
156
|
+
stats.wakeups++;
|
|
157
|
+
} else if (result === 'timed-out') {
|
|
158
|
+
stats.timeouts++;
|
|
159
|
+
continue; // Check running flag
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Read all available debug messages
|
|
164
|
+
var messages = readDebugMessages();
|
|
165
|
+
|
|
166
|
+
if (messages && messages.length > 0) {
|
|
167
|
+
// Send to main thread
|
|
168
|
+
self.postMessage({
|
|
169
|
+
type: 'debug',
|
|
170
|
+
messages: messages,
|
|
171
|
+
stats: {
|
|
172
|
+
wakeups: stats.wakeups,
|
|
173
|
+
timeouts: stats.timeouts,
|
|
174
|
+
messagesReceived: stats.messagesReceived,
|
|
175
|
+
bytesRead: stats.bytesRead
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error('[DebugWorker] Error in wait loop:', error);
|
|
182
|
+
self.postMessage({
|
|
183
|
+
type: 'error',
|
|
184
|
+
error: error.message
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Brief pause on error before retrying (use existing atomicView)
|
|
188
|
+
// Wait on a value that won't change for 10ms as a simple delay
|
|
189
|
+
Atomics.wait(atomicView, 0, atomicView[0], 10);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Start the wait loop
|
|
196
|
+
*/
|
|
197
|
+
function start() {
|
|
198
|
+
if (!sharedBuffer) {
|
|
199
|
+
console.error('[DebugWorker] Cannot start - not initialized');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (running) {
|
|
204
|
+
console.warn('[DebugWorker] Already running');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
running = true;
|
|
209
|
+
waitLoop();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Stop the wait loop
|
|
214
|
+
*/
|
|
215
|
+
function stop() {
|
|
216
|
+
running = false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Clear debug buffer
|
|
221
|
+
*/
|
|
222
|
+
function clear() {
|
|
223
|
+
if (!sharedBuffer) return;
|
|
224
|
+
|
|
225
|
+
// Reset head and tail to 0
|
|
226
|
+
Atomics.store(atomicView, CONTROL_INDICES.DEBUG_HEAD, 0);
|
|
227
|
+
Atomics.store(atomicView, CONTROL_INDICES.DEBUG_TAIL, 0);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Handle messages from main thread
|
|
232
|
+
*/
|
|
233
|
+
self.onmessage = function(event) {
|
|
234
|
+
var data = event.data;
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
switch (data.type) {
|
|
238
|
+
case 'init':
|
|
239
|
+
initRingBuffer(data.sharedBuffer, data.ringBufferBase, data.bufferConstants);
|
|
240
|
+
self.postMessage({ type: 'initialized' });
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case 'start':
|
|
244
|
+
start();
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
case 'stop':
|
|
248
|
+
stop();
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
case 'clear':
|
|
252
|
+
clear();
|
|
253
|
+
break;
|
|
254
|
+
|
|
255
|
+
case 'getStats':
|
|
256
|
+
self.postMessage({
|
|
257
|
+
type: 'stats',
|
|
258
|
+
stats: stats
|
|
259
|
+
});
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
default:
|
|
263
|
+
console.warn('[DebugWorker] Unknown message type:', data.type);
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error('[DebugWorker] Error:', error);
|
|
267
|
+
self.postMessage({
|
|
268
|
+
type: 'error',
|
|
269
|
+
error: error.message
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
console.log('[DebugWorker] Script loaded');
|
|
@@ -0,0 +1,274 @@
|
|
|
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
|
+
// Worker state
|
|
30
|
+
var running = false;
|
|
31
|
+
|
|
32
|
+
// Statistics
|
|
33
|
+
var stats = {
|
|
34
|
+
messagesReceived: 0,
|
|
35
|
+
lastSequenceReceived: -1,
|
|
36
|
+
droppedMessages: 0,
|
|
37
|
+
wakeups: 0,
|
|
38
|
+
timeouts: 0
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Initialize ring buffer access
|
|
43
|
+
*/
|
|
44
|
+
function initRingBuffer(buffer, base, constants) {
|
|
45
|
+
sharedBuffer = buffer;
|
|
46
|
+
ringBufferBase = base;
|
|
47
|
+
bufferConstants = constants;
|
|
48
|
+
atomicView = new Int32Array(sharedBuffer);
|
|
49
|
+
dataView = new DataView(sharedBuffer);
|
|
50
|
+
uint8View = new Uint8Array(sharedBuffer);
|
|
51
|
+
|
|
52
|
+
// Calculate control indices using constants from WASM
|
|
53
|
+
CONTROL_INDICES = {
|
|
54
|
+
OUT_HEAD: (ringBufferBase + bufferConstants.CONTROL_START + 8) / 4,
|
|
55
|
+
OUT_TAIL: (ringBufferBase + bufferConstants.CONTROL_START + 12) / 4
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read all available messages from OUT buffer
|
|
61
|
+
*/
|
|
62
|
+
function readMessages() {
|
|
63
|
+
var head = Atomics.load(atomicView, CONTROL_INDICES.OUT_HEAD);
|
|
64
|
+
var tail = Atomics.load(atomicView, CONTROL_INDICES.OUT_TAIL);
|
|
65
|
+
|
|
66
|
+
var messages = [];
|
|
67
|
+
|
|
68
|
+
if (head === tail) {
|
|
69
|
+
return messages; // No messages
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
var currentTail = tail;
|
|
73
|
+
var messagesRead = 0;
|
|
74
|
+
// Process up to 10 messages per wake to balance:
|
|
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;
|
|
79
|
+
|
|
80
|
+
while (currentTail !== head && messagesRead < maxMessages) {
|
|
81
|
+
var readPos = ringBufferBase + bufferConstants.OUT_BUFFER_START + currentTail;
|
|
82
|
+
|
|
83
|
+
// Read message header (now always contiguous due to padding)
|
|
84
|
+
var magic = dataView.getUint32(readPos, true);
|
|
85
|
+
|
|
86
|
+
// Check for padding marker - skip to beginning
|
|
87
|
+
if (magic === bufferConstants.PADDING_MAGIC) {
|
|
88
|
+
currentTail = 0;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (magic !== bufferConstants.MESSAGE_MAGIC) {
|
|
93
|
+
console.error('[OSCInWorker] Corrupted message at position', currentTail);
|
|
94
|
+
stats.droppedMessages++;
|
|
95
|
+
// Skip this byte and continue
|
|
96
|
+
currentTail = (currentTail + 1) % bufferConstants.OUT_BUFFER_SIZE;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
var length = dataView.getUint32(readPos + 4, true);
|
|
101
|
+
var sequence = dataView.getUint32(readPos + 8, true);
|
|
102
|
+
var padding = dataView.getUint32(readPos + 12, true); // unused padding field
|
|
103
|
+
|
|
104
|
+
// Validate message length
|
|
105
|
+
if (length < bufferConstants.MESSAGE_HEADER_SIZE || length > bufferConstants.OUT_BUFFER_SIZE) {
|
|
106
|
+
console.error('[OSCInWorker] Invalid message length:', length);
|
|
107
|
+
stats.droppedMessages++;
|
|
108
|
+
currentTail = (currentTail + 1) % bufferConstants.OUT_BUFFER_SIZE;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check for dropped messages via sequence
|
|
113
|
+
if (stats.lastSequenceReceived >= 0) {
|
|
114
|
+
var expectedSeq = (stats.lastSequenceReceived + 1) & 0xFFFFFFFF;
|
|
115
|
+
if (sequence !== expectedSeq) {
|
|
116
|
+
var dropped = (sequence - expectedSeq + 0x100000000) & 0xFFFFFFFF;
|
|
117
|
+
if (dropped < 1000) { // Sanity check
|
|
118
|
+
console.warn('[OSCInWorker] Detected', dropped, 'dropped messages');
|
|
119
|
+
stats.droppedMessages += dropped;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
stats.lastSequenceReceived = sequence;
|
|
124
|
+
|
|
125
|
+
// Read payload (OSC binary data) - now contiguous due to padding
|
|
126
|
+
var payloadLength = length - bufferConstants.MESSAGE_HEADER_SIZE;
|
|
127
|
+
var payloadStart = readPos + bufferConstants.MESSAGE_HEADER_SIZE;
|
|
128
|
+
|
|
129
|
+
// Create a proper copy (not a view into SharedArrayBuffer)
|
|
130
|
+
var payload = new Uint8Array(payloadLength);
|
|
131
|
+
for (var i = 0; i < payloadLength; i++) {
|
|
132
|
+
payload[i] = uint8View[payloadStart + i];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
messages.push({
|
|
136
|
+
oscData: payload,
|
|
137
|
+
sequence: sequence
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Move to next message
|
|
141
|
+
currentTail = (currentTail + length) % bufferConstants.OUT_BUFFER_SIZE;
|
|
142
|
+
messagesRead++;
|
|
143
|
+
stats.messagesReceived++;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Update tail pointer (consume messages)
|
|
147
|
+
if (messagesRead > 0) {
|
|
148
|
+
Atomics.store(atomicView, CONTROL_INDICES.OUT_TAIL, currentTail);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return messages;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Main wait loop using Atomics.wait for instant wake
|
|
156
|
+
*/
|
|
157
|
+
function waitLoop() {
|
|
158
|
+
while (running) {
|
|
159
|
+
try {
|
|
160
|
+
// Get current OUT_HEAD value
|
|
161
|
+
var currentHead = Atomics.load(atomicView, CONTROL_INDICES.OUT_HEAD);
|
|
162
|
+
var currentTail = Atomics.load(atomicView, CONTROL_INDICES.OUT_TAIL);
|
|
163
|
+
|
|
164
|
+
// If buffer is empty, wait for AudioWorklet to notify us
|
|
165
|
+
if (currentHead === currentTail) {
|
|
166
|
+
// Wait for up to 100ms (allows checking stop signal)
|
|
167
|
+
var result = Atomics.wait(atomicView, CONTROL_INDICES.OUT_HEAD, currentHead, 100);
|
|
168
|
+
|
|
169
|
+
if (result === 'ok' || result === 'not-equal') {
|
|
170
|
+
// We were notified or value changed!
|
|
171
|
+
stats.wakeups++;
|
|
172
|
+
} else if (result === 'timed-out') {
|
|
173
|
+
stats.timeouts++;
|
|
174
|
+
continue; // Check running flag
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Read all available messages
|
|
179
|
+
var messages = readMessages();
|
|
180
|
+
|
|
181
|
+
if (messages.length > 0) {
|
|
182
|
+
// Send to main thread
|
|
183
|
+
self.postMessage({
|
|
184
|
+
type: 'messages',
|
|
185
|
+
messages: messages,
|
|
186
|
+
stats: {
|
|
187
|
+
wakeups: stats.wakeups,
|
|
188
|
+
timeouts: stats.timeouts,
|
|
189
|
+
messagesReceived: stats.messagesReceived,
|
|
190
|
+
droppedMessages: stats.droppedMessages
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('[OSCInWorker] Error in wait loop:', error);
|
|
197
|
+
self.postMessage({
|
|
198
|
+
type: 'error',
|
|
199
|
+
error: error.message
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Brief pause on error before retrying (use existing atomicView)
|
|
203
|
+
// Wait on a value that won't change for 10ms as a simple delay
|
|
204
|
+
Atomics.wait(atomicView, 0, atomicView[0], 10);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Start the wait loop
|
|
211
|
+
*/
|
|
212
|
+
function start() {
|
|
213
|
+
if (!sharedBuffer) {
|
|
214
|
+
console.error('[OSCInWorker] Cannot start - not initialized');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (running) {
|
|
219
|
+
console.warn('[OSCInWorker] Already running');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
running = true;
|
|
224
|
+
waitLoop();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Stop the wait loop
|
|
229
|
+
*/
|
|
230
|
+
function stop() {
|
|
231
|
+
running = false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Handle messages from main thread
|
|
236
|
+
*/
|
|
237
|
+
self.onmessage = function(event) {
|
|
238
|
+
var data = event.data;
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
switch (data.type) {
|
|
242
|
+
case 'init':
|
|
243
|
+
initRingBuffer(data.sharedBuffer, data.ringBufferBase, data.bufferConstants);
|
|
244
|
+
self.postMessage({ type: 'initialized' });
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
case 'start':
|
|
248
|
+
start();
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
case 'stop':
|
|
252
|
+
stop();
|
|
253
|
+
break;
|
|
254
|
+
|
|
255
|
+
case 'getStats':
|
|
256
|
+
self.postMessage({
|
|
257
|
+
type: 'stats',
|
|
258
|
+
stats: stats
|
|
259
|
+
});
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
default:
|
|
263
|
+
console.warn('[OSCInWorker] Unknown message type:', data.type);
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error('[OSCInWorker] Error:', error);
|
|
267
|
+
self.postMessage({
|
|
268
|
+
type: 'error',
|
|
269
|
+
error: error.message
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
console.log('[OSCInWorker] Script loaded');
|