supersonic-scsynth 0.69.0 → 0.70.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 +19 -0
- package/README.md +82 -456
- package/README.npm.md +82 -456
- package/dist/metrics_component.js +1 -1
- package/dist/osc_channel.js +1 -1
- package/dist/supersonic.js +5 -5
- package/dist/workers/osc_in_worker.js +1 -1
- package/dist/workers/osc_out_log_sab_worker.js +1 -1
- package/package.json +2 -1
- package/supersonic.d.ts +36 -204
- package/dist/workers/debug_worker.js +0 -3
- package/dist/workers/osc_out_prescheduler_worker.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{function u(
|
|
1
|
+
(()=>{function u({uint8View:c,dataView:r,bufferStart:s,bufferSize:I,head:U,tail:A,messageMagic:T,paddingMagic:R,headerSize:C,maxMessages:f=1/0,onMessage:t,onCorruption:a}){let o=A,O=0,S=()=>{a&&a(o),o=U};for(;o!==U&&O<f;){let i=I-o,_=(U-o+I)%I;if(i<4||_<4){S();break}let e=r.getUint32(s+o,!0);if(e===R){if(o===0){S();break}o=0;continue}if(e!==T){S();break}if(i<C){S();break}let n=r.getUint32(s+o+4,!0),p=r.getUint32(s+o+8,!0),N=r.getUint32(s+o+12,!0),E=n+3&-4;if(n<C||E>I||E>i||E>_){S();break}let l=n-C,d=s+o+C;t(d,l,p,N),o=(o+E)%I,O++}return{newTail:o,messagesRead:O}}function L(c,r){let s=c+r;return{OUT_HEAD:(s+8)/4,OUT_TAIL:(s+12)/4}}function D(){return(performance.timeOrigin+performance.now())/1e3+2208988800}function F(c){let{name:r,calculateControlIndices:s,headIndex:I,tailIndex:U,readMessages:A,postResults:T,initMetrics:R=!0,onInit:C,extraHandlers:f}=c,t={sharedBuffer:null,ringBufferBase:null,bufferConstants:null,atomicView:null,dataView:null,uint8View:null,metricsView:null,CONTROL_INDICES:{}},a=!1;function o(_,e,n){if(t.sharedBuffer=_,t.ringBufferBase=e,t.bufferConstants=n,t.atomicView=new Int32Array(_),t.dataView=new DataView(_),t.uint8View=new Uint8Array(_),t.CONTROL_INDICES=s(e,n.CONTROL_START),R){let p=e+n.METRICS_START;t.metricsView=new Uint32Array(_,p,n.METRICS_SIZE/4)}C?.(t)}function O(){let _=I(t.CONTROL_INDICES),e=U(t.CONTROL_INDICES);for(;a;)try{let n=Atomics.load(t.atomicView,_),p=Atomics.load(t.atomicView,e);n===p&&Atomics.wait(t.atomicView,_,n);let N=A(t);N&&N.length>0&&T(N)}catch(n){console.error(`[${r}] Error in wait loop:`,n),self.postMessage({type:"error",error:n.message}),Atomics.wait(t.atomicView,0,t.atomicView[0],10)}}function S(){if(!t.sharedBuffer){console.error(`[${r}] Cannot start - not initialized`);return}a||(a=!0,O())}function i(){a=!1}self.addEventListener("message",_=>{let{data:e}=_;try{if(f?.[e.type]){f[e.type](e,t);return}switch(e.type){case"init":e.sharedBuffer&&o(e.sharedBuffer,e.ringBufferBase,e.bufferConstants),self.postMessage({type:"initialized"});break;case"start":t.sharedBuffer&&S();break;case"stop":i();break;default:}}catch(n){console.error(`[${r}] Error:`,n),self.postMessage({type:"error",error:n.message})}})}var x=-1;function H(c){let{atomicView:r,uint8View:s,dataView:I,ringBufferBase:U,bufferConstants:A,metricsView:T,CONTROL_INDICES:R}=c,C=Atomics.load(r,R.OUT_HEAD),f=Atomics.load(r,R.OUT_TAIL);if(C===f)return[];let t=[],{newTail:a,messagesRead:o}=u({uint8View:s,dataView:I,bufferStart:U+A.OUT_BUFFER_START,bufferSize:A.OUT_BUFFER_SIZE,head:C,tail:f,messageMagic:A.MESSAGE_MAGIC,paddingMagic:A.PADDING_MAGIC,headerSize:A.MESSAGE_HEADER_SIZE,maxMessages:100,onMessage:(O,S,i,_)=>{if(x>=0){let E=x+1&4294967295;if(i!==E){let l=i-E+4294967296&4294967295;l<1e3&&(console.error("[OSCInWorker] Detected",l,"dropped messages (expected seq",E,"got",i,")"),T&&Atomics.add(T,13,l))}}x=i;let e=4,n=O+e,p=S-e;if(p<=0)return;let N=new Uint8Array(p);for(let E=0;E<p;E++)N[E]=s[n+E];t.push({oscData:N,sequence:i,timestamp:D()}),T&&(Atomics.add(T,11,1),Atomics.add(T,12,S))},onCorruption:O=>{console.error("[OSCInWorker] Corrupted message at position",O),T&&(Atomics.add(T,13,1),Atomics.add(T,14,1))}});return o>0&&Atomics.store(r,R.OUT_TAIL,a),t}F({name:"OSCInWorker",calculateControlIndices:L,headIndex:c=>c.OUT_HEAD,tailIndex:c=>c.OUT_TAIL,readMessages:H,postResults:c=>self.postMessage({type:"messages",messages:c})});})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{function
|
|
1
|
+
(()=>{function C({uint8View:a,dataView:o,bufferStart:e,bufferSize:I,head:T,tail:_,messageMagic:E,paddingMagic:f,headerSize:c,maxMessages:A=1/0,onMessage:t,onCorruption:u}){let i=_,g=0,p=()=>{u&&u(i),i=T};for(;i!==T&&g<A;){let l=I-i,n=(T-i+I)%I;if(l<4||n<4){p();break}let r=o.getUint32(e+i,!0);if(r===f){if(i===0){p();break}i=0;continue}if(r!==E){p();break}if(l<c){p();break}let s=o.getUint32(e+i+4,!0),N=o.getUint32(e+i+8,!0),S=o.getUint32(e+i+12,!0),d=s+3&-4;if(s<c||d>I||d>l||d>n){p();break}let O=s-c,M=e+i+c;t(M,O,N,S),i=(i+d)%I,g++}return{newTail:i,messagesRead:g}}function L(a,o,e,I,T,_,E=0){let f=I-o;for(let c=0;c<T;c++)_[E+c]=a[o+(f+c)%e]}function U(a,o){let e=a+o;return{IN_HEAD:(e+0)/4,IN_TAIL:(e+4)/4,IN_SEQUENCE:(e+24)/4,IN_WRITE_LOCK:(e+40)/4,IN_LOG_TAIL:(e+44)/4}}function R(){return(performance.timeOrigin+performance.now())/1e3+2208988800}function x(a){let{name:o,calculateControlIndices:e,headIndex:I,tailIndex:T,readMessages:_,postResults:E,initMetrics:f=!0,onInit:c,extraHandlers:A}=a,t={sharedBuffer:null,ringBufferBase:null,bufferConstants:null,atomicView:null,dataView:null,uint8View:null,metricsView:null,CONTROL_INDICES:{}},u=!1;function i(n,r,s){if(t.sharedBuffer=n,t.ringBufferBase=r,t.bufferConstants=s,t.atomicView=new Int32Array(n),t.dataView=new DataView(n),t.uint8View=new Uint8Array(n),t.CONTROL_INDICES=e(r,s.CONTROL_START),f){let N=r+s.METRICS_START;t.metricsView=new Uint32Array(n,N,s.METRICS_SIZE/4)}c?.(t)}function g(){let n=I(t.CONTROL_INDICES),r=T(t.CONTROL_INDICES);for(;u;)try{let s=Atomics.load(t.atomicView,n),N=Atomics.load(t.atomicView,r);s===N&&Atomics.wait(t.atomicView,n,s);let S=_(t);S&&S.length>0&&E(S)}catch(s){console.error(`[${o}] Error in wait loop:`,s),self.postMessage({type:"error",error:s.message}),Atomics.wait(t.atomicView,0,t.atomicView[0],10)}}function p(){if(!t.sharedBuffer){console.error(`[${o}] Cannot start - not initialized`);return}u||(u=!0,g())}function l(){u=!1}self.addEventListener("message",n=>{let{data:r}=n;try{if(A?.[r.type]){A[r.type](r,t);return}switch(r.type){case"init":r.sharedBuffer&&i(r.sharedBuffer,r.ringBufferBase,r.bufferConstants),self.postMessage({type:"initialized"});break;case"start":t.sharedBuffer&&p();break;case"stop":l();break;default:}}catch(s){console.error(`[${o}] Error:`,s),self.postMessage({type:"error",error:s.message})}})}var m=0;function w(a){let{atomicView:o,uint8View:e,dataView:I,ringBufferBase:T,bufferConstants:_,CONTROL_INDICES:E}=a,f=Atomics.load(o,E.IN_HEAD),c=Atomics.load(o,E.IN_LOG_TAIL);if(f===c)return[];let A=T+_.IN_BUFFER_START,t=_.IN_BUFFER_SIZE,u;if(c+4<=t)u=I.getUint32(A+c,!0);else{u=0;for(let l=0;l<4;l++)u|=e[A+(c+l)%t]<<l*8}if(u!==_.MESSAGE_MAGIC&&u!==_.PADDING_MAGIC)return Atomics.store(o,E.IN_LOG_TAIL,f),[];let i=[],{newTail:g,messagesRead:p}=C({uint8View:e,dataView:I,bufferStart:A,bufferSize:t,head:f,tail:c,messageMagic:_.MESSAGE_MAGIC,paddingMagic:_.PADDING_MAGIC,headerSize:_.MESSAGE_HEADER_SIZE,maxMessages:100,onMessage:(l,n,r,s)=>{let N=new Uint8Array(n);L(e,A,t,l,n,N,0),i.push({sourceId:s,oscData:N,sequence:r,timestamp:R()})},onCorruption:l=>{if(m++,m<=3){let n=T+_.IN_BUFFER_START+l,r=I.getUint32(n,!0),s=e[n],N=e[n+1],S=e[n+2],d=e[n+3],O=Atomics.load(o,E.IN_TAIL);console.error(`[OSCOutLogWorker] Corrupted message at position ${l}: head=${f} logTail=${c} inTail=${O} got=0x${(r>>>0).toString(16).padStart(8,"0")} expected=0x${(_.MESSAGE_MAGIC>>>0).toString(16).padStart(8,"0")} bytes=[${s},${N},${S},${d}] bufStart=${A} bufSize=${t}`)}else m===4&&console.error(`[OSCOutLogWorker] Suppressing further corruption logs (${m}+ total)`)}});return p>0&&Atomics.store(o,E.IN_LOG_TAIL,g),i}x({name:"OSCOutLogWorker",calculateControlIndices:U,headIndex:a=>a.IN_HEAD,tailIndex:a=>a.IN_LOG_TAIL,readMessages:w,postResults:a=>self.postMessage({type:"oscLog",entries:a}),initMetrics:!1,onInit:a=>{let o=Atomics.load(a.atomicView,a.CONTROL_INDICES.IN_HEAD);Atomics.store(a.atomicView,a.CONTROL_INDICES.IN_LOG_TAIL,o)}});})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supersonic-scsynth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.0",
|
|
4
4
|
"description": "SuperCollider scsynth WebAssembly port for AudioWorklet - Run SuperCollider synthesis in the browser",
|
|
5
5
|
"main": "dist/supersonic.js",
|
|
6
6
|
"types": "supersonic.d.ts",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"pretest": "esbuild node_modules/fast-check/lib/fast-check.js --bundle --format=esm --outfile=test/assets/fast-check.bundle.js",
|
|
16
16
|
"test": "playwright test --reporter=list --max-failures=1",
|
|
17
17
|
"test:all": "playwright test --reporter=list",
|
|
18
|
+
"test:unit": "node --test \"test/unit/*.test.mjs\"",
|
|
18
19
|
"prepublishOnly": "./scripts/build-web.sh --release",
|
|
19
20
|
"test:types": "tsd",
|
|
20
21
|
"docs:api": "typedoc && mv docs/api-generated/README.md docs/API.md && rm -rf docs/api-generated",
|
package/supersonic.d.ts
CHANGED
|
@@ -240,10 +240,6 @@ export interface SuperSonicOptions {
|
|
|
240
240
|
|
|
241
241
|
/** How often to snapshot metrics/tree in postMessage mode (ms). */
|
|
242
242
|
snapshotIntervalMs?: number;
|
|
243
|
-
/** Max pending events in the JS prescheduler. Default: 65536. */
|
|
244
|
-
preschedulerCapacity?: number;
|
|
245
|
-
/** Bundles scheduled within this many ms of now are dispatched immediately for lowest latency. Bundles further in the future are held and dispatched closer to their scheduled time. Default: 500. */
|
|
246
|
-
bypassLookaheadMs?: number;
|
|
247
243
|
|
|
248
244
|
/** Enable all debug console logging. Default: false. */
|
|
249
245
|
debug?: boolean;
|
|
@@ -300,38 +296,6 @@ export interface SuperSonicMetrics {
|
|
|
300
296
|
/** Bundles executed after their scheduled time. */
|
|
301
297
|
scsynthSchedulerLates: number;
|
|
302
298
|
|
|
303
|
-
// Prescheduler metrics
|
|
304
|
-
/** Events waiting in JS prescheduler queue. */
|
|
305
|
-
preschedulerPending: number;
|
|
306
|
-
/** Peak pending events. */
|
|
307
|
-
preschedulerPendingPeak: number;
|
|
308
|
-
/** Bundles added to prescheduler. */
|
|
309
|
-
preschedulerBundlesScheduled: number;
|
|
310
|
-
/** Events sent from prescheduler to worklet. */
|
|
311
|
-
preschedulerDispatched: number;
|
|
312
|
-
/** Bundles cancelled before dispatch. */
|
|
313
|
-
preschedulerEventsCancelled: number;
|
|
314
|
-
/** Smallest time gap between dispatch and execution (ms). 0xFFFFFFFF = no data yet. */
|
|
315
|
-
preschedulerMinHeadroomMs: number;
|
|
316
|
-
/** Bundles dispatched after their scheduled time. */
|
|
317
|
-
preschedulerLates: number;
|
|
318
|
-
/** Ring buffer write retries that succeeded. */
|
|
319
|
-
preschedulerRetriesSucceeded: number;
|
|
320
|
-
/** Ring buffer write retries that failed. */
|
|
321
|
-
preschedulerRetriesFailed: number;
|
|
322
|
-
/** Current retry queue size. */
|
|
323
|
-
preschedulerRetryQueueSize: number;
|
|
324
|
-
/** Peak retry queue size. */
|
|
325
|
-
preschedulerRetryQueuePeak: number;
|
|
326
|
-
/** Total messages that needed retry. */
|
|
327
|
-
preschedulerMessagesRetried: number;
|
|
328
|
-
/** Total dispatch cycles. */
|
|
329
|
-
preschedulerTotalDispatches: number;
|
|
330
|
-
/** Messages sent directly, bypassing prescheduler (aggregate). */
|
|
331
|
-
preschedulerBypassed: number;
|
|
332
|
-
/** Maximum lateness at prescheduler (ms). */
|
|
333
|
-
preschedulerMaxLateMs: number;
|
|
334
|
-
|
|
335
299
|
// OSC Out metrics
|
|
336
300
|
/** OSC messages sent from JS to scsynth. */
|
|
337
301
|
oscOutMessagesSent: number;
|
|
@@ -368,16 +332,6 @@ export interface SuperSonicMetrics {
|
|
|
368
332
|
/** Peak bytes used in DEBUG ring buffer. */
|
|
369
333
|
debugBufferPeakBytes: number;
|
|
370
334
|
|
|
371
|
-
// Bypass categories
|
|
372
|
-
/** Plain OSC messages (not bundles) that bypassed prescheduler. */
|
|
373
|
-
bypassNonBundle: number;
|
|
374
|
-
/** Bundles with timetag 0 or 1 that bypassed prescheduler. */
|
|
375
|
-
bypassImmediate: number;
|
|
376
|
-
/** Bundles within lookahead threshold that bypassed prescheduler. */
|
|
377
|
-
bypassNearFuture: number;
|
|
378
|
-
/** Late bundles that bypassed prescheduler. */
|
|
379
|
-
bypassLate: number;
|
|
380
|
-
|
|
381
335
|
// scsynth late timing diagnostics
|
|
382
336
|
/** Maximum lateness observed in scsynth scheduler (ms). */
|
|
383
337
|
scsynthSchedulerMaxLateMs: number;
|
|
@@ -386,9 +340,35 @@ export interface SuperSonicMetrics {
|
|
|
386
340
|
/** Process count when last scsynth late occurred. */
|
|
387
341
|
scsynthSchedulerLastLateTick: number;
|
|
388
342
|
|
|
389
|
-
/** SAB mode only:
|
|
343
|
+
/** SAB mode only: direct IN-ring writes dropped on lock contention / full ring. */
|
|
390
344
|
ringBufferDirectWriteFails: number;
|
|
391
345
|
|
|
346
|
+
// System info (cross-platform; written by shared C++ at init)
|
|
347
|
+
/** SuperSonic major version. */
|
|
348
|
+
supersonicVersionMajor: number;
|
|
349
|
+
/** SuperSonic minor version. */
|
|
350
|
+
supersonicVersionMinor: number;
|
|
351
|
+
/** SuperSonic patch version. */
|
|
352
|
+
supersonicVersionPatch: number;
|
|
353
|
+
/** Output sample rate in Hz. */
|
|
354
|
+
audioSampleRate: number;
|
|
355
|
+
/** Audio block size in frames (128 on web). */
|
|
356
|
+
audioBlockSize: number;
|
|
357
|
+
/** Number of output bus channels. */
|
|
358
|
+
audioOutputChannels: number;
|
|
359
|
+
/** Number of input bus channels. */
|
|
360
|
+
audioInputChannels: number;
|
|
361
|
+
|
|
362
|
+
// SuperClock readouts (cross-platform; written per audio block)
|
|
363
|
+
/** Tempo in milli-BPM (bpm * 1000). Divide by 1000 for BPM. */
|
|
364
|
+
clockTempoMbpm: number;
|
|
365
|
+
/** Beat position * 100. Divide by 100 for the beat. */
|
|
366
|
+
clockBeatCenti: number;
|
|
367
|
+
/** Phase within the quantum * 100. Divide by 100 for the phase. */
|
|
368
|
+
clockPhaseCenti: number;
|
|
369
|
+
/** Transport playing (0 = stopped, 1 = playing). */
|
|
370
|
+
clockPlaying: number;
|
|
371
|
+
|
|
392
372
|
// Context metrics (main thread only)
|
|
393
373
|
/** Clock drift between AudioContext and wall clock (ms, signed). */
|
|
394
374
|
driftOffsetMs: number;
|
|
@@ -414,8 +394,6 @@ export interface SuperSonicMetrics {
|
|
|
414
394
|
loadedSynthDefs: number;
|
|
415
395
|
/** Maximum scsynth scheduler queue size (compile-time constant). */
|
|
416
396
|
scsynthSchedulerCapacity: number;
|
|
417
|
-
/** Maximum pending events in JS prescheduler. */
|
|
418
|
-
preschedulerCapacity: number;
|
|
419
397
|
/** IN ring buffer capacity (bytes). */
|
|
420
398
|
inBufferCapacity: number;
|
|
421
399
|
/** OUT ring buffer capacity (bytes). */
|
|
@@ -485,11 +463,6 @@ export interface MetricsSchema {
|
|
|
485
463
|
}>;
|
|
486
464
|
}>;
|
|
487
465
|
};
|
|
488
|
-
/** Magic values used in the metrics array. */
|
|
489
|
-
sentinels: {
|
|
490
|
-
/** Value of preschedulerMinHeadroomMs before any data arrives. */
|
|
491
|
-
HEADROOM_UNSET: number;
|
|
492
|
-
};
|
|
493
466
|
}
|
|
494
467
|
|
|
495
468
|
// ============================================================================
|
|
@@ -912,26 +885,10 @@ export type SuperSonicEvent = keyof SuperSonicEventMap;
|
|
|
912
885
|
// OscChannel
|
|
913
886
|
// ============================================================================
|
|
914
887
|
|
|
915
|
-
/**
|
|
916
|
-
* Classification category for OSC message routing.
|
|
917
|
-
*
|
|
918
|
-
* - `'nonBundle'` — plain message (not a bundle), sent directly
|
|
919
|
-
* - `'immediate'` — bundle with timetag 0 or 1, sent directly
|
|
920
|
-
* - `'nearFuture'` — bundle within the bypass lookahead threshold, sent directly
|
|
921
|
-
* - `'late'` — bundle past its scheduled time, sent directly
|
|
922
|
-
* - `'farFuture'` — bundle beyond the lookahead threshold, routed to the prescheduler
|
|
923
|
-
*/
|
|
924
|
-
export type OscCategory = 'nonBundle' | 'immediate' | 'nearFuture' | 'late' | 'farFuture';
|
|
925
|
-
|
|
926
888
|
/** OscChannel metrics counters. */
|
|
927
889
|
export interface OscChannelMetrics {
|
|
928
890
|
messagesSent: number;
|
|
929
891
|
bytesSent: number;
|
|
930
|
-
nonBundle: number;
|
|
931
|
-
immediate: number;
|
|
932
|
-
nearFuture: number;
|
|
933
|
-
late: number;
|
|
934
|
-
bypassed: number;
|
|
935
892
|
}
|
|
936
893
|
|
|
937
894
|
/** Transferable config for SAB mode OscChannel. */
|
|
@@ -941,8 +898,6 @@ export interface OscChannelSABTransferable {
|
|
|
941
898
|
ringBufferBase: number;
|
|
942
899
|
bufferConstants: Record<string, number>;
|
|
943
900
|
controlIndices: Record<string, number>;
|
|
944
|
-
preschedulerPort: MessagePort | null;
|
|
945
|
-
bypassLookaheadS: number;
|
|
946
901
|
sourceId: number;
|
|
947
902
|
blocking: boolean;
|
|
948
903
|
}
|
|
@@ -951,8 +906,6 @@ export interface OscChannelSABTransferable {
|
|
|
951
906
|
export interface OscChannelPMTransferable {
|
|
952
907
|
mode: 'postMessage';
|
|
953
908
|
port: MessagePort;
|
|
954
|
-
preschedulerPort: MessagePort | null;
|
|
955
|
-
bypassLookaheadS: number;
|
|
956
909
|
sourceId: number;
|
|
957
910
|
blocking: boolean;
|
|
958
911
|
}
|
|
@@ -981,17 +934,9 @@ export type OscChannelTransferable = OscChannelSABTransferable | OscChannelPMTra
|
|
|
981
934
|
*/
|
|
982
935
|
export class OscChannel {
|
|
983
936
|
/**
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
|
|
987
|
-
classify(oscData: Uint8Array): OscCategory;
|
|
988
|
-
|
|
989
|
-
/**
|
|
990
|
-
* Send an OSC message with automatic routing.
|
|
991
|
-
*
|
|
992
|
-
* Classifies the message and routes it:
|
|
993
|
-
* - bypass categories → sent directly to the AudioWorklet
|
|
994
|
-
* - far-future bundles → routed to the prescheduler for timed dispatch
|
|
937
|
+
* Send an OSC message: frames it onto the IN ring (SAB) or postMessages it to
|
|
938
|
+
* the worklet (PM). Classification and scheduling happen on the audio thread
|
|
939
|
+
* (the engine's OscIngress + BundleScheduler) — the producer never classifies.
|
|
995
940
|
*
|
|
996
941
|
* @param oscData - Encoded OSC bytes
|
|
997
942
|
* @returns true if sent successfully
|
|
@@ -999,19 +944,12 @@ export class OscChannel {
|
|
|
999
944
|
send(oscData: Uint8Array): boolean;
|
|
1000
945
|
|
|
1001
946
|
/**
|
|
1002
|
-
*
|
|
947
|
+
* Alias of {@link send} — kept for callers that used the explicit direct path.
|
|
1003
948
|
* @param oscData - Encoded OSC bytes
|
|
1004
949
|
* @returns true if sent successfully
|
|
1005
950
|
*/
|
|
1006
951
|
sendDirect(oscData: Uint8Array): boolean;
|
|
1007
952
|
|
|
1008
|
-
/**
|
|
1009
|
-
* Send to prescheduler without classification.
|
|
1010
|
-
* @param oscData - Encoded OSC bytes
|
|
1011
|
-
* @returns true if sent successfully
|
|
1012
|
-
*/
|
|
1013
|
-
sendToPrescheduler(oscData: Uint8Array): boolean;
|
|
1014
|
-
|
|
1015
953
|
/** Get current metrics snapshot. */
|
|
1016
954
|
getMetrics(): OscChannelMetrics;
|
|
1017
955
|
|
|
@@ -1029,73 +967,6 @@ export class OscChannel {
|
|
|
1029
967
|
*/
|
|
1030
968
|
nextNodeId(): number;
|
|
1031
969
|
|
|
1032
|
-
/**
|
|
1033
|
-
* Register a handler for OSC replies from scsynth. Idempotent — replaces
|
|
1034
|
-
* any previously-registered handler. In AudioWorklet contexts the worklet
|
|
1035
|
-
* must call {@link pollReplies} from `process()` to drain; in all other
|
|
1036
|
-
* contexts delivery is automatic. All registered channels receive all
|
|
1037
|
-
* replies (broadcast); filter locally if you only need specific addresses.
|
|
1038
|
-
*
|
|
1039
|
-
* SAB mode: handler receives `(view, offset, length, sequence)` — zero-copy
|
|
1040
|
-
* into the shared buffer. Read bytes from `view[offset..offset+length]`.
|
|
1041
|
-
* Data is valid only for the duration of the handler call.
|
|
1042
|
-
*
|
|
1043
|
-
* PM mode: handler receives `(oscData, sequence)` where `oscData` is a copy.
|
|
1044
|
-
*
|
|
1045
|
-
* @example
|
|
1046
|
-
* channel.setReplyHandler((view, offset, length, sequence) => {
|
|
1047
|
-
* // SAB: read raw OSC bytes from view[offset..offset+length]
|
|
1048
|
-
* });
|
|
1049
|
-
*/
|
|
1050
|
-
setReplyHandler(
|
|
1051
|
-
handler:
|
|
1052
|
-
| ((view: Uint8Array, offset: number, length: number, sequence: number) => void)
|
|
1053
|
-
| ((oscData: Uint8Array, sequence: number) => void),
|
|
1054
|
-
): void;
|
|
1055
|
-
|
|
1056
|
-
/**
|
|
1057
|
-
* Clear the reply handler and release the reply channel. Idempotent.
|
|
1058
|
-
*/
|
|
1059
|
-
clearReplyHandler(): void;
|
|
1060
|
-
|
|
1061
|
-
/**
|
|
1062
|
-
* Activate the reply slot without registering a handler. Usually called
|
|
1063
|
-
* for you by {@link setReplyHandler}; only call directly if you want to
|
|
1064
|
-
* claim the slot before installing a handler (or to use the optional
|
|
1065
|
-
* one-shot handler argument of {@link pollReplies}).
|
|
1066
|
-
*/
|
|
1067
|
-
activateReplies(): void;
|
|
1068
|
-
|
|
1069
|
-
/**
|
|
1070
|
-
* Release the reply slot. Usually called for you by {@link clearReplyHandler}.
|
|
1071
|
-
*/
|
|
1072
|
-
deactivateReplies(): void;
|
|
1073
|
-
|
|
1074
|
-
/**
|
|
1075
|
-
* Drain pending replies, calling the registered handler (or `handler`
|
|
1076
|
-
* argument, if given) once per message. Returns the number of messages
|
|
1077
|
-
* processed. Zero-allocation on the hot path.
|
|
1078
|
-
*
|
|
1079
|
-
* Call from an AudioWorklet's `process()` method to receive replies on
|
|
1080
|
-
* the audio thread. In other contexts automatic delivery already calls
|
|
1081
|
-
* this for you.
|
|
1082
|
-
*
|
|
1083
|
-
* @param handler - Optional override for this call only
|
|
1084
|
-
* @returns Number of messages drained
|
|
1085
|
-
*/
|
|
1086
|
-
pollReplies(
|
|
1087
|
-
handler?:
|
|
1088
|
-
| ((view: Uint8Array, offset: number, length: number, sequence: number) => void)
|
|
1089
|
-
| ((oscData: Uint8Array, sequence: number) => void),
|
|
1090
|
-
): number;
|
|
1091
|
-
|
|
1092
|
-
/**
|
|
1093
|
-
* Number of reply messages dropped because the reply buffer was full.
|
|
1094
|
-
* SAB mode only — undefined in PM mode or before {@link activateReplies}.
|
|
1095
|
-
* Counter resets each time the slot is (re)claimed.
|
|
1096
|
-
*/
|
|
1097
|
-
get replyDrops(): number | undefined;
|
|
1098
|
-
|
|
1099
970
|
/** Close the channel and release its ports. */
|
|
1100
971
|
close(): void;
|
|
1101
972
|
|
|
@@ -1246,14 +1117,6 @@ export type BlockedCommand =
|
|
|
1246
1117
|
// SuperSonic
|
|
1247
1118
|
// ============================================================================
|
|
1248
1119
|
|
|
1249
|
-
/** Options for {@link SuperSonic.sendOSC}. */
|
|
1250
|
-
export interface SendOSCOptions {
|
|
1251
|
-
/** Session ID for cancellation via {@link SuperSonic.cancelSession}. */
|
|
1252
|
-
sessionId?: string;
|
|
1253
|
-
/** Run tag for cancellation via {@link SuperSonic.cancelTag}. */
|
|
1254
|
-
runTag?: string;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
1120
|
/**
|
|
1258
1121
|
* SuperSonic — WebAssembly SuperCollider synthesis engine for the browser.
|
|
1259
1122
|
*
|
|
@@ -1631,7 +1494,7 @@ export class SuperSonic {
|
|
|
1631
1494
|
send(address: '/b_write', ...args: OscArg[]): never;
|
|
1632
1495
|
/** @deprecated File writing is not available in the browser. */
|
|
1633
1496
|
send(address: '/b_close', ...args: OscArg[]): never;
|
|
1634
|
-
/** @deprecated Use purge() to clear
|
|
1497
|
+
/** @deprecated Use purge() to clear the WASM BundleScheduler + IN ring. */
|
|
1635
1498
|
send(address: '/clearSched', ...args: OscArg[]): never;
|
|
1636
1499
|
/** @deprecated SuperSonic always enables error notifications so you never miss a /fail reply. */
|
|
1637
1500
|
send(address: '/error', ...args: OscArg[]): never;
|
|
@@ -1784,48 +1647,17 @@ export class SuperSonic {
|
|
|
1784
1647
|
* Use {@link send} for buffer commands so they are handled correctly.
|
|
1785
1648
|
*
|
|
1786
1649
|
* @param oscData - Encoded OSC message or bundle bytes
|
|
1787
|
-
* @
|
|
1788
|
-
* @throws If the bundle exceeds the maximum schedulable size
|
|
1650
|
+
* @throws If the message exceeds the IN ring size
|
|
1789
1651
|
*
|
|
1790
1652
|
* @example
|
|
1791
1653
|
* const msg = SuperSonic.osc.encodeMessage('/n_set', [1001, 'freq', 880]);
|
|
1792
1654
|
* sonic.sendOSC(msg);
|
|
1793
|
-
*
|
|
1794
|
-
* // With cancellation tags:
|
|
1795
|
-
* const bundle = SuperSonic.osc.encodeBundle(futureTime, packets);
|
|
1796
|
-
* sonic.sendOSC(bundle, { sessionId: 'song1', runTag: 'verse' });
|
|
1797
|
-
*/
|
|
1798
|
-
sendOSC(oscData: Uint8Array | ArrayBuffer, options?: SendOSCOptions): void;
|
|
1799
|
-
|
|
1800
|
-
/**
|
|
1801
|
-
* Cancel all scheduled messages with the given run tag.
|
|
1802
|
-
* Only affects messages in the JS prescheduler (not yet dispatched to WASM).
|
|
1803
|
-
* @param runTag - Tag to cancel
|
|
1804
|
-
*/
|
|
1805
|
-
cancelTag(runTag: string): void;
|
|
1806
|
-
|
|
1807
|
-
/**
|
|
1808
|
-
* Cancel all scheduled messages for a session.
|
|
1809
|
-
* @param sessionId - Session to cancel
|
|
1810
1655
|
*/
|
|
1811
|
-
|
|
1656
|
+
sendOSC(oscData: Uint8Array | ArrayBuffer): void;
|
|
1812
1657
|
|
|
1813
1658
|
/**
|
|
1814
|
-
*
|
|
1815
|
-
*
|
|
1816
|
-
* @param runTag - Tag to match within that session
|
|
1817
|
-
*/
|
|
1818
|
-
cancelSessionTag(sessionId: string, runTag: string): void;
|
|
1819
|
-
|
|
1820
|
-
/** Cancel all scheduled messages in the JS prescheduler. */
|
|
1821
|
-
cancelAll(): void;
|
|
1822
|
-
|
|
1823
|
-
/**
|
|
1824
|
-
* Cancel all pending scheduled messages everywhere in the pipeline.
|
|
1825
|
-
*
|
|
1826
|
-
* Unlike {@link cancelAll} which only clears messages still waiting in JS,
|
|
1827
|
-
* `purge()` guarantees that nothing already in-flight will fire either.
|
|
1828
|
-
* Resolves when the flush is confirmed complete.
|
|
1659
|
+
* Flush all pending scheduled OSC: clears the WASM BundleScheduler and the IN
|
|
1660
|
+
* ring so nothing already in-flight will fire. Resolves when confirmed.
|
|
1829
1661
|
*/
|
|
1830
1662
|
purge(): Promise<void>;
|
|
1831
1663
|
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
(()=>{function L({uint8View:s,dataView:o,bufferStart:c,bufferSize:t,head:A,tail:T,messageMagic:C,paddingMagic:R,headerSize:i,maxMessages:U=1/0,onMessage:e,onCorruption:S}){let E=T,I=0,a=D=>{let r=D;if(r+4<=t)return o.getUint32(c+r,!0);let _=0;for(let n=0;n<4;n++)_|=s[c+(r+n)%t]<<n*8;return _};for(;E!==A&&I<U;){let D=t-E,r;if(D>=4?r=o.getUint32(c+E,!0):r=a(E),r===R){E=0;continue}if(r!==C){S&&S(E),E=(E+1)%t;continue}let _=a((E+4)%t),n=a((E+8)%t),p=a((E+12)%t);if(_<i||_>t){S&&S(E),E=(E+1)%t;continue}let l=_-i,u=c+(E+i)%t;e(u,l,n,p),E=(E+_)%t,I++}return{newTail:E,messagesRead:I}}function N(s,o){let c=s+o;return{DEBUG_HEAD:(c+16)/4,DEBUG_TAIL:(c+20)/4}}function x(s){let{name:o,calculateControlIndices:c,headIndex:t,tailIndex:A,readMessages:T,postResults:C,initMetrics:R=!0,onInit:i,extraHandlers:U}=s,e={sharedBuffer:null,ringBufferBase:null,bufferConstants:null,atomicView:null,dataView:null,uint8View:null,metricsView:null,CONTROL_INDICES:{}},S=!1;function E(r,_,n){if(e.sharedBuffer=r,e.ringBufferBase=_,e.bufferConstants=n,e.atomicView=new Int32Array(r),e.dataView=new DataView(r),e.uint8View=new Uint8Array(r),e.CONTROL_INDICES=c(_,n.CONTROL_START),R){let p=_+n.METRICS_START;e.metricsView=new Uint32Array(r,p,n.METRICS_SIZE/4)}i?.(e)}function I(){let r=t(e.CONTROL_INDICES),_=A(e.CONTROL_INDICES);for(;S;)try{let n=Atomics.load(e.atomicView,r),p=Atomics.load(e.atomicView,_);n===p&&Atomics.wait(e.atomicView,r,n);let l=T(e);l&&l.length>0&&C(l)}catch(n){console.error(`[${o}] Error in wait loop:`,n),self.postMessage({type:"error",error:n.message}),Atomics.wait(e.atomicView,0,e.atomicView[0],10)}}function a(){if(!e.sharedBuffer){console.error(`[${o}] Cannot start - not initialized`);return}S||(S=!0,I())}function D(){S=!1}self.addEventListener("message",r=>{let{data:_}=r;try{if(U?.[_.type]){U[_.type](_,e);return}switch(_.type){case"init":_.sharedBuffer&&E(_.sharedBuffer,_.ringBufferBase,_.bufferConstants),self.postMessage({type:"initialized"});break;case"start":e.sharedBuffer&&a();break;case"stop":D();break;default:}}catch(n){console.error(`[${o}] Error:`,n),self.postMessage({type:"error",error:n.message})}})}var O=new TextDecoder("utf-8");function H(s,o,c){let t=new Uint8Array(c);for(let T=0;T<c;T++)t[T]=s[o+T];let A=O.decode(t);return A.endsWith(`
|
|
2
|
-
`)&&(A=A.slice(0,-1)),A}function d(s){let{atomicView:o,uint8View:c,dataView:t,ringBufferBase:A,bufferConstants:T,metricsView:C,CONTROL_INDICES:R}=s,i=Atomics.load(o,R.DEBUG_HEAD),U=Atomics.load(o,R.DEBUG_TAIL);if(i===U)return[];let e=[],{newTail:S,messagesRead:E}=L({uint8View:c,dataView:t,bufferStart:A+T.DEBUG_BUFFER_START,bufferSize:T.DEBUG_BUFFER_SIZE,head:i,tail:U,messageMagic:T.MESSAGE_MAGIC,paddingMagic:T.PADDING_MAGIC,headerSize:T.MESSAGE_HEADER_SIZE,maxMessages:1e3,onMessage:(I,a,D)=>{e.push({text:H(c,I,a),timestamp:performance.now(),sequence:D}),C&&(Atomics.add(C,30,1),Atomics.add(C,31,a))},onCorruption:I=>{console.error("[DebugWorker] Corrupted message at position",I)}});return E>0&&Atomics.store(o,R.DEBUG_TAIL,S),e}function M(s){let o=[];for(let c of s)try{let t=O.decode(new Uint8Array(c.bytes));t.endsWith(`
|
|
3
|
-
`)&&(t=t.slice(0,-1)),o.push({text:t,timestamp:performance.now(),sequence:c.sequence})}catch(t){console.error("[DebugWorker] Failed to decode message:",t)}o.length>0&&self.postMessage({type:"debug",messages:o})}x({name:"DebugWorker",calculateControlIndices:N,headIndex:s=>s.DEBUG_HEAD,tailIndex:s=>s.DEBUG_TAIL,readMessages:d,postResults:s=>self.postMessage({type:"debug",messages:s}),extraHandlers:{clear:(s,o)=>{o.sharedBuffer&&(Atomics.store(o.atomicView,o.CONTROL_INDICES.DEBUG_HEAD,0),Atomics.store(o.atomicView,o.CONTROL_INDICES.DEBUG_TAIL,0))},debugRaw:s=>{s.messages&&M(s.messages)}}});})();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(()=>{function Ce(t,e,n){return(n-1-t+e)%n}function Ie({uint8View:t,dataView:e,bufferStart:n,bufferSize:s,head:r,payload:o,sequence:_,messageMagic:d,headerSize:i,sourceId:T=0,headerScratch:X=null,headerScratchView:K=null}){let M=o.length,R=i+M+3&-4,A=s-r;if(R>A){let S=X||new Uint8Array(i),L=K||new DataView(S.buffer);L.setUint32(0,d,!0),L.setUint32(4,R,!0),L.setUint32(8,_,!0),L.setUint32(12,T,!0);let P=n+r,O=n;if(A>=i){t.set(S,P);let N=A-i;for(let l=0;l<N;l++)t[P+i+l]=o[l];for(let l=N;l<M;l++)t[O+l-N]=o[l]}else{for(let l=0;l<A;l++)t[P+l]=S[l];for(let l=A;l<i;l++)t[O+l-A]=S[l];let N=i-A;t.set(o,O+N)}}else{let S=n+r;e.setUint32(S,d,!0),e.setUint32(S+4,R,!0),e.setUint32(S+8,_,!0),e.setUint32(S+12,T,!0),t.set(o,S+i)}return(r+R)%s}function De(t,e,n=0,s=!1){for(let r=0;r<=n;r++)if(Atomics.compareExchange(t,e,0,1)===0)return!0;if(s){for(let o=0;o<100;o++)if(Atomics.wait(t,e,1,100),Atomics.compareExchange(t,e,0,1)===0)return!0;return console.error("[RingBuffer] Lock acquisition timeout after 10s - possible deadlock"),!1}return!1}function xe(t,e){Atomics.store(t,e,0),Atomics.notify(t,e,1)}function ne({atomicView:t,dataView:e,uint8View:n,bufferConstants:s,ringBufferBase:r,controlIndices:o,oscMessage:_,sourceId:d=0,maxSpins:i=0,useWait:T=!1,headerScratch:X=null,headerScratchView:K=null}){let M=_.length,z=s.MESSAGE_HEADER_SIZE+M;if(z>s.IN_BUFFER_SIZE-s.MESSAGE_HEADER_SIZE||!De(t,o.IN_WRITE_LOCK,i,T))return!1;try{let R=Atomics.load(t,o.IN_HEAD),A=Atomics.load(t,o.IN_TAIL),S=z+3&-4;if(Ce(R,A,s.IN_BUFFER_SIZE)<S)return!1;let P=Atomics.add(t,o.IN_SEQUENCE,1),O=Ie({uint8View:n,dataView:e,bufferStart:r+s.IN_BUFFER_START,bufferSize:s.IN_BUFFER_SIZE,head:R,payload:_,sequence:P,messageMagic:s.MESSAGE_MAGIC,headerSize:s.MESSAGE_HEADER_SIZE,sourceId:d,headerScratch:X,headerScratchView:K});return Atomics.load(t,o.IN_HEAD),Atomics.store(t,o.IN_HEAD,O),Atomics.notify(t,o.IN_HEAD,1),!0}finally{xe(t,o.IN_WRITE_LOCK)}}function se(t,e){let n=t+e;return{IN_HEAD:(n+0)/4,IN_TAIL:(n+4)/4,IN_SEQUENCE:(n+24)/4,IN_WRITE_LOCK:(n+40)/4,IN_LOG_TAIL:(n+44)/4}}function re(t){return t.length>=8&&t[0]===35&&t[1]===98&&t[2]===117&&t[3]===110&&t[4]===100&&t[5]===108&&t[6]===101&&t[7]===0}function oe(){return(performance.timeOrigin+performance.now())/1e3+2208988800}var Le=new Uint8Array(2097152),Je=new DataView(Le.buffer);var je=new TextDecoder,et=new TextEncoder;var Pe=4294967296,tt=new Uint8Array([35,98,117,110,100,108,101,0]);function Oe(t){return!t||t.length<8?!1:t[0]===35&&t[1]===98}function ce(t){if(!Oe(t))return null;let e=new DataView(t.buffer,t.byteOffset,t.byteLength),n=e.getUint32(8,!1),s=e.getUint32(12,!1);return n+s/Pe}var B=class{constructor({lookaheadS:e=.5,maxPending:n=1e3,maxFutureS:s=3600,poolBytes:r=524288}={}){this.lookaheadS=e,this.maxPending=n,this.maxFutureS=s,this.poolBytes=r,this._heap=[],this._seq=0,this.retryCount=0}size(){return this._heap.length}schedule(e,n){if(this._heap.length+this.retryCount>=this.maxPending)return{ok:!1,reason:"queue_full"};if(e.ntpTime==null)return{ok:!0,immediate:e};if((e.bytes||0)>this.poolBytes)return{ok:!1,reason:"too_large"};if(e.ntpTime-n>this.maxFutureS)return{ok:!1,reason:"too_far_future"};let r={ntpTime:e.ntpTime,seq:this._seq++,sessionId:e.sessionId||0,runTag:e.runTag||"",payload:e.payload};return this._push(r),{ok:!0,scheduled:r}}nextDueTime(){return this._heap.length?this._heap[0].ntpTime-this.lookaheadS:null}dispatchDue(e,n){let s=e+this.lookaheadS,r=0;for(;this._heap.length&&this._heap[0].ntpTime<=s;)n(this._pop()),r++;return r}cancelBy(e){if(this._heap.length===0)return 0;let n=this._heap.length;this._heap=this._heap.filter(r=>!e(r));let s=n-this._heap.length;return s>0&&this._heapify(),s}cancelTag(e){return this.cancelBy(n=>n.runTag===e)}cancelSession(e){return this.cancelBy(n=>n.sessionId===e)}cancelSessionTag(e,n){return this.cancelBy(s=>s.sessionId===e&&s.runTag===n)}cancelAll(){let e=this._heap.length;return this._heap=[],e}_cmp(e,n){return e.ntpTime===n.ntpTime?e.seq-n.seq:e.ntpTime-n.ntpTime}_swap(e,n){let s=this._heap[e];this._heap[e]=this._heap[n],this._heap[n]=s}_push(e){this._heap.push(e),this._siftUp(this._heap.length-1)}_pop(){let e=this._heap[0],n=this._heap.pop();return this._heap.length&&(this._heap[0]=n,this._siftDown(0)),e}_siftUp(e){for(;e>0;){let n=e-1>>1;if(this._cmp(this._heap[e],this._heap[n])>=0)break;this._swap(e,n),e=n}}_siftDown(e){let n=this._heap.length;for(;;){let s=2*e+1,r=2*e+2,o=e;if(s<n&&this._cmp(this._heap[s],this._heap[o])<0&&(o=s),r<n&&this._cmp(this._heap[r],this._heap[o])<0&&(o=r),o===e)break;this._swap(e,o),e=o}}_heapify(){for(let e=(this._heap.length>>1)-1;e>=0;e--)this._siftDown(e)}};var h="sab",W=null,F=524288,He=65536,Te=3600,Ae=4294967295,C=null,w=null,u=null,y=null,he=null,pe=null,q=null,de=null,b={},a=null,k=null,_e=null,V=150,I=(t,e)=>{a&&(h==="sab"?Atomics.store(a,t,e):a[t]=e)},j=t=>a?h==="sab"?Atomics.load(a,t):a[t]:0,p=(t,e)=>{a&&(h==="sab"?Atomics.add(a,t,e):a[t]+=e)},G=I,ue=j,Fe=()=>{if(h!=="postMessage"||_e!==null)return;let t=()=>{k&&a&&self.postMessage({type:"preschedulerMetrics",metrics:new Uint32Array(k.slice(0))}),_e=setTimeout(t,V)};t(),f("[PreScheduler] Started metrics sending (every "+V+"ms)")};var U=null,H=1/0,ae=!1,E=[],v=!1,D=65536,$=.5,c=new B({lookaheadS:$,maxPending:D,maxFutureS:Te,poolBytes:F}),f=(...t)=>{},ee=oe,we=t=>ce(t),be=()=>{if(!C||!u){console.error("[PreScheduler] Cannot init - missing buffer or constants");return}y=new Int32Array(C),he=new DataView(C),pe=new Uint8Array(C);let t=u.MESSAGE_HEADER_SIZE||16;q=new Uint8Array(t),de=new DataView(q.buffer),b=se(w,u.CONTROL_START);let e=w+u.METRICS_START;a=new Uint32Array(C,e,u.METRICS_SIZE/4),f("[PreScheduler] SharedArrayBuffer initialized with direct ring buffer writing and metrics")},Y=()=>{if(!a)return;I(9,c.size());let t=c.size(),e=j(10);t>e&&I(10,t)},x=(t,e,n=0,s=!1)=>{if(h==="postMessage")return W?(W.postMessage({type:"osc",oscData:t,sourceId:n}),p(12,1),!0):(console.error("[PreScheduler] No worklet port available"),!1);if(!C||!y)return console.error("[PreScheduler] Not initialized for ring buffer writing"),!1;let r=t.length,o=u.MESSAGE_HEADER_SIZE+r;return o>u.IN_BUFFER_SIZE-u.MESSAGE_HEADER_SIZE?(console.error("[PreScheduler] Message too large:",o),!1):ne({atomicView:y,dataView:he,uint8View:pe,bufferConstants:u,ringBufferBase:w,controlIndices:b,oscMessage:t,sourceId:n,maxSpins:10,useWait:s,headerScratch:q,headerScratchView:de})?(p(12,1),!0):!1},g=(t,e,n=0)=>{let s=c.size()+E.length;if(s>=D){console.error("[PreScheduler] Backpressure: dropping retry ("+s+" pending)"),p(17,1);return}E.push({oscData:t,context:e||"unknown",queuedAt:performance.now(),sourceId:n}),I(18,E.length);let r=j(19);E.length>r&&I(19,E.length),f("[PreScheduler] Queued message for retry:",e,"queue size:",E.length),Ue()},Ue=()=>{if(v||E.length===0||h!=="sab")return;v=!0;let t=Atomics.load(y,b.IN_TAIL),e=Atomics.waitAsync(y,b.IN_TAIL,t),n=()=>{v=!1,ke(),E.length>0&&Ue()};e.async?e.value.then(n):queueMicrotask(n)},ke=()=>{if(E.length===0)return;let t=0;for(let e=0;e<E.length;e++){let n=E[e];if(x(n.oscData,!0,n.sourceId,!0))t++,p(16,1);else break}t>0&&(E.splice(0,t),I(18,E.length))},fe=(t,e,n,s=0)=>{let r=we(t),o=ee();c.retryCount=E.length;let _=c.schedule({ntpTime:r,bytes:t.length,sessionId:e,runTag:n,payload:{oscData:t,sourceId:s}},o);if(!_.ok){let[d,i]={queue_full:["PRESCHEDULER_QUEUE_FULL",`Prescheduler queue full (${c.size()+E.length} >= ${D} max)`],too_large:["BUNDLE_TOO_LARGE",`Bundle too large for scheduler pool (${t.length} > ${F} bytes)`],too_far_future:["BUNDLE_TOO_FAR_FUTURE",`Bundle scheduled too far in future (${(r-o).toFixed(0)}s > ${Te}s max)`]}[_.reason];return console.error("[PreScheduler]",i),self.postMessage({type:"error",error:i,code:d}),!1}return _.immediate?(f("[PreScheduler] Non-bundle message, dispatching immediately"),x(t,!1,s,!0)||g(t,"immediate message",s),!0):(p(11,1),Y(),f("[PreScheduler] Scheduled bundle:","NTP="+r.toFixed(3),"current="+o.toFixed(3),"wait="+((r-o)*1e3).toFixed(1)+"ms","pending="+c.size()),m(),!0)},m=()=>{let t=c.nextDueTime();if(t===null){U!==null&&(clearTimeout(U),U=null,H=1/0);return}if(t<H){U!==null&&clearTimeout(U);let e=Math.max(0,(t-ee())*1e3);H=t,U=setTimeout(Ye,e)}},Ge=()=>{U===null&&(f("[PreScheduler] Starting demand-driven dispatching"),m())};var Ye=()=>{ae=!0;let t=ee(),e=0,n=s=>{Y();let{oscData:r,sourceId:o}=s.payload,_=s.ntpTime-t;if(p(21,1),_<0){let i=Math.round(-_*1e3);p(15,1);let T=ue(23);i>T&&G(23,i)}else{let i=Math.round(_*1e3),T=ue(14);(T===Ae||i<T)&&G(14,i)}f("[PreScheduler] Dispatching bundle:","NTP="+s.ntpTime.toFixed(3),"current="+t.toFixed(3),"early="+(_*1e3).toFixed(1)+"ms","remaining="+c.size()),x(r,!1,o,!0)||g(r,"scheduled bundle NTP="+s.ntpTime.toFixed(3),o),e++};c.dispatchDue(t,n),(e>0||c.size()>0||E.length>0)&&f("[PreScheduler] Dispatch cycle complete:","dispatched="+e,"pending="+c.size(),"retrying="+E.length),ae=!1,U=null,H=1/0,m()},te=t=>{t>0&&(p(13,t),Y(),f("[PreScheduler] Cancelled "+t+" events, "+c.size()+" remaining"),m())},Xe=(t,e)=>te(c.cancelSessionTag(t,e)),Ke=t=>te(c.cancelSession(t)),ze=t=>te(c.cancelTag(t)),ve=()=>{let t=c.cancelAll(),e=E.length;t===0&&e===0||(p(13,t+e),E=[],I(18,0),Y(),f("[PreScheduler] Cancelled all "+t+" events, "+e+" retries"),m())},Qe=re,Ze=t=>{let e=[],n=new DataView(t.buffer,t.byteOffset,t.byteLength),s=16;for(;s+4<=t.length;){let r=n.getInt32(s,!1);if(s+=4,r<=0||r>He||s+r>t.length)break;let o=t.slice(s,s+r);for(e.push(o),s+=r;s%4!==0&&s<t.length;)s++}return e},We=(t,e=0)=>{if(Qe(t)){let n=Ze(t);for(let s=0;s<n.length;s++)x(n[s],!1,e,!0)||g(n[s],"immediate bundle message "+s,e)}else x(t,!1,e,!0)||g(t,"immediate message",e)};self.addEventListener("message",t=>{let{data:e}=t;try{switch(e.type){case"init":if(h=e.mode||"sab",e.maxPendingMessages&&(D=e.maxPendingMessages),e.snapshotIntervalMs&&(V=e.snapshotIntervalMs),e.bypassLookaheadS!==void 0&&($=e.bypassLookaheadS),h==="sab")C=e.sharedBuffer,w=e.ringBufferBase,u=e.bufferConstants,be(),u&&u.scheduler_data_pool_size&&(F=u.scheduler_data_pool_size);else{W=e.workletPort;let s=184;k=new ArrayBuffer(s),a=new Uint32Array(k),Fe()}G(14,Ae),G(23,0),c.maxPending=D,c.lookaheadS=$,c.poolBytes=F,Ge(),f("[OSCPreSchedulerWorker] Initialized with NTP-based scheduling, mode="+h+", capacity="+D),self.postMessage({type:"initialized"});break;case"addOscSource":let n=t.ports[0];n&&(n.onmessage=s=>{s.data.type==="osc"&&s.data.oscData&&fe(s.data.oscData,0,"",s.data.sourceId||0)},f("[OSCPreSchedulerWorker] Added external OSC source"));break;case"send":fe(e.oscData,e.sessionId||0,e.runTag||"",e.sourceId||0);break;case"sendImmediate":We(e.oscData,e.sourceId||0);break;case"directDispatch":x(e.oscData,!1,e.sourceId||0,!0)||g(e.oscData,"directDispatch fallback",e.sourceId||0);break;case"cancelSessionTag":e.runTag!==void 0&&e.runTag!==null&&e.runTag!==""&&Xe(e.sessionId||0,e.runTag);break;case"cancelSession":Ke(e.sessionId||0);break;case"cancelTag":e.runTag!==void 0&&e.runTag!==null&&e.runTag!==""&&ze(e.runTag);break;case"cancelAll":ve(),e.ack&&self.postMessage({type:"cancelAllAck"});break;default:}}catch(n){console.error("[OSCPreSchedulerWorker] Error:",n),self.postMessage({type:"error",error:n.message})}});f("[OSCPreSchedulerWorker] Script loaded");})();
|