supersonic-scsynth 0.1.9 → 0.2.1

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.
@@ -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 (14 uint32_t fields + 1 uint8_t + 3 padding bytes)
76
- const uint32View = new Uint32Array(memory.buffer, layoutPtr, 15);
77
- const uint8View = new Uint8Array(memory.buffer, layoutPtr, 60);
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
- TOTAL_BUFFER_SIZE: uint32View[10],
92
- MAX_MESSAGE_SIZE: uint32View[11],
93
- MESSAGE_MAGIC: uint32View[12],
94
- PADDING_MAGIC: uint32View[13],
95
- DEBUG_PADDING_MARKER: uint8View[56],
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
- const unixSeconds = Date.now() / 1000; // Current Unix time in seconds
430
- const keepAlive = this.wasmInstance.exports.process_audio(audioContextTime, unixSeconds);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supersonic-scsynth",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "SuperCollider scsynth WebAssembly port for AudioWorklet - Run SuperCollider synthesis in the browser",
5
5
  "main": "dist/supersonic.js",
6
6
  "unpkg": "dist/supersonic.js",