supersonic-scsynth 0.1.9 → 0.2.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/README.md +1 -1
- package/dist/supersonic.js +808 -288
- package/dist/wasm/manifest.json +3 -3
- package/dist/wasm/scsynth-nrt.wasm +0 -0
- package/dist/workers/debug_worker.js +16 -3
- package/dist/workers/osc_in_worker.js +15 -2
- package/dist/workers/osc_out_prescheduler_worker.js +575 -0
- package/dist/workers/osc_out_worker.js +46 -183
- package/dist/workers/ring_buffer_worker_base.js +305 -0
- package/dist/workers/scsynth_audio_worklet.js +44 -23
- package/dist/workers/system_worker.js +64 -0
- package/package.json +1 -1
|
@@ -33,6 +33,7 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
33
33
|
this.atomicView = null;
|
|
34
34
|
this.uint8View = null;
|
|
35
35
|
this.dataView = null;
|
|
36
|
+
this.localClockOffsetView = null; // Float64Array for reading local clock offset
|
|
36
37
|
|
|
37
38
|
// Buffer constants (loaded from WASM at initialization)
|
|
38
39
|
this.bufferConstants = null;
|
|
@@ -72,9 +73,9 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
72
73
|
throw new Error('WASM memory not available');
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
// Read the struct (
|
|
76
|
-
const uint32View = new Uint32Array(memory.buffer, layoutPtr,
|
|
77
|
-
const uint8View = new Uint8Array(memory.buffer, layoutPtr,
|
|
76
|
+
// Read the struct (20 uint32_t fields + 1 uint8_t + 3 padding bytes)
|
|
77
|
+
const uint32View = new Uint32Array(memory.buffer, layoutPtr, 21);
|
|
78
|
+
const uint8View = new Uint8Array(memory.buffer, layoutPtr, 84);
|
|
78
79
|
|
|
79
80
|
// Extract constants (order matches BufferLayout struct in shared_memory.h)
|
|
80
81
|
this.bufferConstants = {
|
|
@@ -88,11 +89,17 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
88
89
|
CONTROL_SIZE: uint32View[7],
|
|
89
90
|
METRICS_START: uint32View[8],
|
|
90
91
|
METRICS_SIZE: uint32View[9],
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
NTP_START_TIME_START: uint32View[10],
|
|
93
|
+
NTP_START_TIME_SIZE: uint32View[11],
|
|
94
|
+
DRIFT_OFFSET_START: uint32View[12],
|
|
95
|
+
DRIFT_OFFSET_SIZE: uint32View[13],
|
|
96
|
+
GLOBAL_OFFSET_START: uint32View[14],
|
|
97
|
+
GLOBAL_OFFSET_SIZE: uint32View[15],
|
|
98
|
+
TOTAL_BUFFER_SIZE: uint32View[16],
|
|
99
|
+
MAX_MESSAGE_SIZE: uint32View[17],
|
|
100
|
+
MESSAGE_MAGIC: uint32View[18],
|
|
101
|
+
PADDING_MAGIC: uint32View[19],
|
|
102
|
+
DEBUG_PADDING_MARKER: uint8View[80],
|
|
96
103
|
MESSAGE_HEADER_SIZE: 16 // sizeof(Message) - 4 x uint32_t (magic, length, sequence, padding)
|
|
97
104
|
};
|
|
98
105
|
|
|
@@ -128,8 +135,12 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
128
135
|
PROCESS_COUNT: (ringBufferBase + METRICS_START + 0) / 4,
|
|
129
136
|
BUFFER_OVERRUNS: (ringBufferBase + METRICS_START + 4) / 4,
|
|
130
137
|
MESSAGES_PROCESSED: (ringBufferBase + METRICS_START + 8) / 4,
|
|
131
|
-
MESSAGES_DROPPED: (ringBufferBase + METRICS_START + 12) / 4
|
|
138
|
+
MESSAGES_DROPPED: (ringBufferBase + METRICS_START + 12) / 4,
|
|
139
|
+
SCHEDULER_QUEUE_DEPTH: (ringBufferBase + METRICS_START + 16) / 4,
|
|
140
|
+
SCHEDULER_QUEUE_MAX: (ringBufferBase + METRICS_START + 20) / 4,
|
|
141
|
+
SCHEDULER_QUEUE_DROPPED: (ringBufferBase + METRICS_START + 24) / 4
|
|
132
142
|
};
|
|
143
|
+
|
|
133
144
|
}
|
|
134
145
|
|
|
135
146
|
// Write debug message to SharedArrayBuffer DEBUG ring buffer
|
|
@@ -264,11 +275,6 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
264
275
|
if (this.wasmInstance.exports.init_memory) {
|
|
265
276
|
this.wasmInstance.exports.init_memory(48000.0);
|
|
266
277
|
|
|
267
|
-
// Set time offset from JavaScript
|
|
268
|
-
if (this.wasmInstance.exports.set_time_offset && data.timeOffset) {
|
|
269
|
-
this.wasmInstance.exports.set_time_offset(data.timeOffset);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
278
|
this.isInitialized = true;
|
|
273
279
|
|
|
274
280
|
this.port.postMessage({
|
|
@@ -297,11 +303,6 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
297
303
|
if (this.wasmInstance.exports.init_memory) {
|
|
298
304
|
this.wasmInstance.exports.init_memory(48000.0);
|
|
299
305
|
|
|
300
|
-
// Set time offset from JavaScript
|
|
301
|
-
if (this.wasmInstance.exports.set_time_offset && data.timeOffset) {
|
|
302
|
-
this.wasmInstance.exports.set_time_offset(data.timeOffset);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
306
|
this.isInitialized = true;
|
|
306
307
|
|
|
307
308
|
this.port.postMessage({
|
|
@@ -344,6 +345,9 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
344
345
|
bufferOverruns: Atomics.load(this.atomicView, this.METRICS_INDICES.BUFFER_OVERRUNS),
|
|
345
346
|
messagesProcessed: Atomics.load(this.atomicView, this.METRICS_INDICES.MESSAGES_PROCESSED),
|
|
346
347
|
messagesDropped: Atomics.load(this.atomicView, this.METRICS_INDICES.MESSAGES_DROPPED),
|
|
348
|
+
schedulerQueueDepth: Atomics.load(this.atomicView, this.METRICS_INDICES.SCHEDULER_QUEUE_DEPTH),
|
|
349
|
+
schedulerQueueMax: Atomics.load(this.atomicView, this.METRICS_INDICES.SCHEDULER_QUEUE_MAX),
|
|
350
|
+
schedulerQueueDropped: Atomics.load(this.atomicView, this.METRICS_INDICES.SCHEDULER_QUEUE_DROPPED),
|
|
347
351
|
statusFlags: Atomics.load(this.atomicView, this.CONTROL_INDICES.STATUS_FLAGS),
|
|
348
352
|
inBufferUsed: {
|
|
349
353
|
bytes: inUsed,
|
|
@@ -416,6 +420,12 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
416
420
|
}
|
|
417
421
|
|
|
418
422
|
process(inputs, outputs, parameters) {
|
|
423
|
+
// DEBUG: Log first call
|
|
424
|
+
if (!this._everCalled) {
|
|
425
|
+
this._everCalled = true;
|
|
426
|
+
console.log('[AudioWorklet] process() called for first time');
|
|
427
|
+
}
|
|
428
|
+
|
|
419
429
|
if (!this.isInitialized) {
|
|
420
430
|
return true;
|
|
421
431
|
}
|
|
@@ -426,8 +436,14 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
426
436
|
// In AudioWorkletGlobalScope, currentTime is a bare global variable (not on globalThis)
|
|
427
437
|
// We use a different variable name to avoid shadowing
|
|
428
438
|
const audioContextTime = currentTime; // Access the global currentTime directly
|
|
429
|
-
|
|
430
|
-
|
|
439
|
+
|
|
440
|
+
// C++ process_audio() now calculates NTP time internally from:
|
|
441
|
+
// - NTP_START_TIME (write-once, set during initialization)
|
|
442
|
+
// - DRIFT_OFFSET (updated every 15s by main thread)
|
|
443
|
+
// - GLOBAL_OFFSET (for future multi-system sync)
|
|
444
|
+
// DEPRECATED: Legacy timing offset views kept in memory for compatibility but unused
|
|
445
|
+
|
|
446
|
+
const keepAlive = this.wasmInstance.exports.process_audio(audioContextTime);
|
|
431
447
|
|
|
432
448
|
// Copy scsynth audio output to AudioWorklet outputs
|
|
433
449
|
if (this.wasmInstance.exports.get_audio_output_bus && outputs[0] && outputs[0].length >= 2) {
|
|
@@ -484,6 +500,8 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
484
500
|
return keepAlive !== 0;
|
|
485
501
|
}
|
|
486
502
|
} catch (error) {
|
|
503
|
+
console.error('[AudioWorklet] process() error:', error);
|
|
504
|
+
console.error('[AudioWorklet] Stack:', error.stack);
|
|
487
505
|
if (this.atomicView) {
|
|
488
506
|
Atomics.or(this.atomicView, this.CONTROL_INDICES.STATUS_FLAGS, this.STATUS_FLAGS.WASM_ERROR);
|
|
489
507
|
}
|
|
@@ -510,7 +528,10 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
510
528
|
processCount: Atomics.load(this.atomicView, this.METRICS_INDICES.PROCESS_COUNT),
|
|
511
529
|
messagesProcessed: Atomics.load(this.atomicView, this.METRICS_INDICES.MESSAGES_PROCESSED),
|
|
512
530
|
messagesDropped: Atomics.load(this.atomicView, this.METRICS_INDICES.MESSAGES_DROPPED),
|
|
513
|
-
bufferOverruns: Atomics.load(this.atomicView, this.METRICS_INDICES.BUFFER_OVERRUNS)
|
|
531
|
+
bufferOverruns: Atomics.load(this.atomicView, this.METRICS_INDICES.BUFFER_OVERRUNS),
|
|
532
|
+
schedulerQueueDepth: Atomics.load(this.atomicView, this.METRICS_INDICES.SCHEDULER_QUEUE_DEPTH),
|
|
533
|
+
schedulerQueueMax: Atomics.load(this.atomicView, this.METRICS_INDICES.SCHEDULER_QUEUE_MAX),
|
|
534
|
+
schedulerQueueDropped: Atomics.load(this.atomicView, this.METRICS_INDICES.SCHEDULER_QUEUE_DROPPED)
|
|
514
535
|
};
|
|
515
536
|
|
|
516
537
|
this.port.postMessage({
|
|
@@ -528,4 +549,4 @@ class ScsynthProcessor extends AudioWorkletProcessor {
|
|
|
528
549
|
}
|
|
529
550
|
|
|
530
551
|
// Register the processor
|
|
531
|
-
registerProcessor('scsynth-processor', ScsynthProcessor);
|
|
552
|
+
registerProcessor('scsynth-processor', ScsynthProcessor);
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
* System Message Worker - Handles internal SuperSonic messages
|
|
11
|
+
* Uses the shared ring buffer base class
|
|
12
|
+
* ES5-compatible for Qt WebEngine
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Import the base class
|
|
16
|
+
importScripts('ring_buffer_worker_base.js');
|
|
17
|
+
|
|
18
|
+
// Create worker instance for SYSTEM buffer
|
|
19
|
+
var worker = new RingBufferWorkerBase('SYSTEM');
|
|
20
|
+
|
|
21
|
+
// Override postMessage to maintain compatibility
|
|
22
|
+
worker.postMessage = self.postMessage.bind(self);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Handle messages from main thread
|
|
26
|
+
*/
|
|
27
|
+
self.onmessage = function(event) {
|
|
28
|
+
var data = event.data;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
switch (data.type) {
|
|
32
|
+
case 'init':
|
|
33
|
+
worker.initRingBuffer(data.sharedBuffer, data.ringBufferBase, data.bufferConstants);
|
|
34
|
+
self.postMessage({ type: 'initialized' });
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
case 'start':
|
|
38
|
+
worker.start();
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
case 'stop':
|
|
42
|
+
worker.stop();
|
|
43
|
+
break;
|
|
44
|
+
|
|
45
|
+
case 'getStats':
|
|
46
|
+
self.postMessage({
|
|
47
|
+
type: 'stats',
|
|
48
|
+
stats: worker.stats
|
|
49
|
+
});
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
default:
|
|
53
|
+
console.warn('[SystemWorker] Unknown message type:', data.type);
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('[SystemWorker] Error:', error);
|
|
57
|
+
self.postMessage({
|
|
58
|
+
type: 'error',
|
|
59
|
+
error: error.message
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
console.log('[SystemWorker] Script loaded');
|
package/package.json
CHANGED