supersonic-scsynth 0.63.0 → 0.65.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 +56 -32
- package/README.md +41 -13
- package/README.npm.md +41 -13
- package/dist/metrics_component.js +1 -1
- package/dist/osc_channel.js +1 -1
- package/dist/osc_fast.js +1 -1
- package/dist/supersonic.js +5 -5
- package/dist/workers/debug_worker.js +3 -3
- package/dist/workers/osc_in_worker.js +1 -1
- package/dist/workers/osc_out_log_sab_worker.js +1 -1
- package/dist/workers/osc_out_prescheduler_worker.js +1 -1
- package/package.json +1 -1
- package/supersonic.d.ts +102 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{function P({uint8View:o,dataView:
|
|
1
|
+
(()=>{function P({uint8View:o,dataView:E,bufferStart:t,bufferSize:r,head:L,tail:a,messageMagic:p,paddingMagic:A,headerSize:R,maxMessages:l=1/0,onMessage:_,onCorruption:c}){let e=a,x=0,O=u=>{let T=u;if(T+4<=r)return E.getUint32(t+T,!0);let C=0;for(let I=0;I<4;I++)C|=o[t+(T+I)%r]<<I*8;return C};for(;e!==L&&x<l;){let u=r-e,T;if(u>=4?T=E.getUint32(t+e,!0):T=O(e),T===A){e=0;continue}if(T!==p){c&&c(e),e=(e+1)%r;continue}let C=O((e+4)%r),I=O((e+8)%r),g=O((e+12)%r);if(C<R||C>r){c&&c(e),e=(e+1)%r;continue}let m=C-R,G=t+(e+R)%r;_(G,m,I,g),e=(e+C)%r,x++}return{newTail:e,messagesRead:x}}function B(o,E){let t=o+E;return{OUT_HEAD:(t+8)/4,OUT_TAIL:(t+12)/4}}function H(){return(performance.timeOrigin+performance.now())/1e3+2208988800}var i=null,D=null,n=null,d=null,F=null,S=null,U={},s=null,N=!1,w=(...o)=>{},f=-1,K=(o,E,t)=>{i=o,D=E,S=t,n=new Int32Array(i),d=new DataView(i),F=new Uint8Array(i),U=B(D,S.CONTROL_START);let r=D+S.METRICS_START;s=new Uint32Array(i,r,S.METRICS_SIZE/4)},Q=()=>{let o=Atomics.load(n,U.OUT_HEAD),E=Atomics.load(n,U.OUT_TAIL);if(o===E)return[];let t=[],{newTail:r,messagesRead:L}=P({uint8View:F,dataView:d,bufferStart:D+S.OUT_BUFFER_START,bufferSize:S.OUT_BUFFER_SIZE,head:o,tail:E,messageMagic:S.MESSAGE_MAGIC,paddingMagic:S.PADDING_MAGIC,headerSize:S.MESSAGE_HEADER_SIZE,maxMessages:100,onMessage:(a,p,A,R)=>{if(f>=0){let _=f+1&4294967295;if(A!==_){let c=A-_+4294967296&4294967295;c<1e3&&(console.error("[OSCInWorker] Detected",c,"dropped messages (expected seq",_,"got",A,")"),s&&Atomics.add(s,28,c))}}f=A;let l=new Uint8Array(p);for(let _=0;_<p;_++)l[_]=F[a+_];t.push({oscData:l,sequence:A,timestamp:H()}),s&&(Atomics.add(s,26,1),Atomics.add(s,27,p))},onCorruption:a=>{console.error("[OSCInWorker] Corrupted message at position",a),s&&(Atomics.add(s,28,1),Atomics.add(s,29,1))}});return L>0&&Atomics.store(n,U.OUT_TAIL,r),t},k=()=>{for(;N;)try{let o=Atomics.load(n,U.OUT_HEAD),E=Atomics.load(n,U.OUT_TAIL);o===E&&Atomics.wait(n,U.OUT_HEAD,o);let t=Q();t.length>0&&self.postMessage({type:"messages",messages:t})}catch(o){console.error("[OSCInWorker] Error in wait loop:",o),self.postMessage({type:"error",error:o.message}),Atomics.wait(n,0,n[0],10)}},h=()=>{if(!i){console.error("[OSCInWorker] Cannot start - not initialized");return}N||(N=!0,k())},Z=()=>{N=!1};self.addEventListener("message",o=>{let{data:E}=o;try{switch(E.type){case"init":K(E.sharedBuffer,E.ringBufferBase,E.bufferConstants),self.postMessage({type:"initialized"});break;case"start":h();break;case"stop":Z();break;default:}}catch(t){console.error("[OSCInWorker] Error:",t),self.postMessage({type:"error",error:t.message})}});w("[OSCInWorker] Script loaded");})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{function
|
|
1
|
+
(()=>{function G({uint8View:t,dataView:r,bufferStart:e,bufferSize:o,head:l,tail:C,messageMagic:f,paddingMagic:_,headerSize:A,maxMessages:S=1/0,onMessage:g,onCorruption:c}){let n=C,L=0,O=B=>{let E=B;if(E+4<=o)return r.getUint32(e+E,!0);let I=0;for(let p=0;p<4;p++)I|=t[e+(E+p)%o]<<p*8;return I};for(;n!==l&&L<S;){let B=o-n,E;if(B>=4?E=r.getUint32(e+n,!0):E=O(n),E===_){n=0;continue}if(E!==f){c&&c(n),n=(n+1)%o;continue}let I=O((n+4)%o),p=O((n+8)%o),F=O((n+12)%o);if(I<A||I>o){c&&c(n),n=(n+1)%o;continue}let R=I-A,H=e+(n+A)%o;g(H,R,p,F),n=(n+I)%o,L++}return{newTail:n,messagesRead:L}}function x(t,r){let e=t+r;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 D(){return(performance.timeOrigin+performance.now())/1e3+2208988800}var U=null,N=null,a=null,d=null,u=null,s=null,i={},m=!1,y=(...t)=>{},M=(t,r,e)=>{U=t,N=r,s=e,a=new Int32Array(U),d=new DataView(U),u=new Uint8Array(U),i=x(N,s.CONTROL_START);let o=Atomics.load(a,i.IN_HEAD);Atomics.store(a,i.IN_LOG_TAIL,o),y("Initialized, IN_LOG_TAIL set to",o)},P=t=>{let r=N+s.IN_BUFFER_START,e=s.IN_BUFFER_SIZE;if(t+4<=e)return d.getUint32(r+t,!0);let o=0;for(let l=0;l<4;l++)o|=u[r+(t+l)%e]<<l*8;return o},T=()=>{let t=Atomics.load(a,i.IN_HEAD),r=Atomics.load(a,i.IN_LOG_TAIL);if(t===r)return[];if(P(r)!==s.MESSAGE_MAGIC)return Atomics.store(a,i.IN_LOG_TAIL,t),[];let o=[],{newTail:l,messagesRead:C}=G({uint8View:u,dataView:d,bufferStart:N+s.IN_BUFFER_START,bufferSize:s.IN_BUFFER_SIZE,head:t,tail:r,messageMagic:s.MESSAGE_MAGIC,paddingMagic:s.PADDING_MAGIC,headerSize:s.MESSAGE_HEADER_SIZE,maxMessages:100,onMessage:(f,_,A,S)=>{let g=new Uint8Array(_);for(let c=0;c<_;c++)g[c]=u[f+c];o.push({sourceId:S,oscData:g,sequence:A,timestamp:D()})},onCorruption:f=>{if(T._corruptCount||(T._corruptCount=0),T._corruptCount++,T._corruptCount<=3){let _=N+s.IN_BUFFER_START+f,A=d.getUint32(_,!0),S=u[_],g=u[_+1],c=u[_+2],n=u[_+3],L=Atomics.load(a,i.IN_TAIL);console.error(`[OSCOutLogWorker] Corrupted message at position ${f}: head=${t} logTail=${r} inTail=${L} got=0x${(A>>>0).toString(16).padStart(8,"0")} expected=0x${(s.MESSAGE_MAGIC>>>0).toString(16).padStart(8,"0")} bytes=[${S},${g},${c},${n}] bufStart=${N+s.IN_BUFFER_START} bufSize=${s.IN_BUFFER_SIZE}`)}else T._corruptCount===4&&console.error(`[OSCOutLogWorker] Suppressing further corruption logs (${T._corruptCount}+ total)`)}});return C>0&&Atomics.store(a,i.IN_LOG_TAIL,l),o},b=()=>{for(;m;)try{let t=Atomics.load(a,i.IN_HEAD),r=Atomics.load(a,i.IN_LOG_TAIL);t===r&&Atomics.wait(a,i.IN_HEAD,t);let e=T();e.length>0&&self.postMessage({type:"oscLog",entries:e})}catch(t){console.error("[OSCOutLogWorker] Error in wait loop:",t),self.postMessage({type:"error",error:t.message}),Atomics.wait(a,0,a[0],10)}},h=()=>{if(!U){console.error("[OSCOutLogWorker] Cannot start - not initialized");return}m||(m=!0,b())},W=()=>{m=!1};self.addEventListener("message",t=>{let{data:r}=t;try{switch(r.type){case"init":M(r.sharedBuffer,r.ringBufferBase,r.bufferConstants),self.postMessage({type:"initialized"});break;case"start":h();break;case"stop":W();break;default:}}catch(e){console.error("[OSCOutLogWorker] Error:",e),self.postMessage({type:"error",error:e.message})}});y("Script loaded");})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{function
|
|
1
|
+
(()=>{function he(e,t,s){return(s-1-e+t)%s}function pe({uint8View:e,dataView:t,bufferStart:s,bufferSize:n,head:r,payload:c,sequence:l,messageMagic:u,headerSize:T,sourceId:E=0,headerScratch:w=null,headerScratchView:N=null}){let y=c.length,h=T+y+3&-4,R=n-r;if(h>R){let _=w||new Uint8Array(T),p=N||new DataView(_.buffer);p.setUint32(0,u,!0),p.setUint32(4,h,!0),p.setUint32(8,l,!0),p.setUint32(12,E,!0);let G=s+r,k=s;if(R>=T){e.set(_,G);let D=R-T;D>0&&e.set(c.subarray(0,D),G+T),e.set(c.subarray(D),k)}else{e.set(_.subarray(0,R),G),e.set(_.subarray(R),k);let D=T-R;e.set(c,k+D)}}else{let _=s+r;t.setUint32(_,u,!0),t.setUint32(_+4,h,!0),t.setUint32(_+8,l,!0),t.setUint32(_+12,E,!0),e.set(c,_+T)}return(r+h)%n}function Ie(e,t,s=0,n=!1){for(let r=0;r<=s;r++)if(Atomics.compareExchange(e,t,0,1)===0)return!0;if(n){for(let c=0;c<100;c++)if(Atomics.wait(e,t,1,100),Atomics.compareExchange(e,t,0,1)===0)return!0;return console.error("[RingBuffer] Lock acquisition timeout after 10s - possible deadlock"),!1}return!1}function De(e,t){Atomics.store(e,t,0),Atomics.notify(e,t,1)}function ee({atomicView:e,dataView:t,uint8View:s,bufferConstants:n,ringBufferBase:r,controlIndices:c,oscMessage:l,sourceId:u=0,maxSpins:T=0,useWait:E=!1}){let w=l.length,N=n.MESSAGE_HEADER_SIZE+w;if(N>n.IN_BUFFER_SIZE-n.MESSAGE_HEADER_SIZE||!Ie(e,c.IN_WRITE_LOCK,T,E))return!1;try{let y=Atomics.load(e,c.IN_HEAD),j=Atomics.load(e,c.IN_TAIL),h=N+3&-4;if(he(y,j,n.IN_BUFFER_SIZE)<h)return!1;let _=Atomics.add(e,c.IN_SEQUENCE,1),p=pe({uint8View:s,dataView:t,bufferStart:r+n.IN_BUFFER_START,bufferSize:n.IN_BUFFER_SIZE,head:y,payload:l,sequence:_,messageMagic:n.MESSAGE_MAGIC,headerSize:n.MESSAGE_HEADER_SIZE,sourceId:u});return Atomics.load(e,c.IN_HEAD),Atomics.store(e,c.IN_HEAD,p),Atomics.notify(e,c.IN_HEAD,1),!0}finally{De(e,c.IN_WRITE_LOCK)}}function te(e,t){let s=e+t;return{IN_HEAD:(s+0)/4,IN_TAIL:(s+4)/4,IN_SEQUENCE:(s+24)/4,IN_WRITE_LOCK:(s+40)/4,IN_LOG_TAIL:(s+44)/4}}function ne(e){return e.length>=8&&e[0]===35&&e[1]===98&&e[2]===117&&e[3]===110&&e[4]===100&&e[5]===108&&e[6]===101&&e[7]===0}function se(){return(performance.timeOrigin+performance.now())/1e3+2208988800}var Pe=new Uint8Array(2097152),st=new DataView(Pe.buffer);var rt=new TextDecoder,ot=new TextEncoder;var Le=4294967296,ct=new Uint8Array([35,98,117,110,100,108,101,0]);function xe(e){return!e||e.length<8?!1:e[0]===35&&e[1]===98}function re(e){if(!xe(e))return null;let t=new DataView(e.buffer,e.byteOffset,e.byteLength),s=t.getUint32(8,!1),n=t.getUint32(12,!1);return s+n/Le}var A="sab",Q=null,K=1024,Fe=65536,le=3600,Se=4294967295,g=null,M=null,S=null,P=null,Te=null,Ae=null,B={},a=null,F=null,Ee=null,W=150,C=(e,t)=>{a&&(A==="sab"?Atomics.store(a,e,t):a[e]=t)},V=e=>a?A==="sab"?Atomics.load(a,e):a[e]:0,U=(e,t)=>{a&&(A==="sab"?Atomics.add(a,e,t):a[e]+=t)},H=C,ue=V,He=()=>{if(A!=="postMessage"||Ee!==null)return;let e=()=>{F&&a&&self.postMessage({type:"preschedulerMetrics",metrics:new Uint32Array(F.slice(0))}),Ee=setTimeout(e,W)};e(),f("[PreScheduler] Started metrics sending (every "+W+"ms)")};var o=[],d=null,m=1/0,be=0,ae=!1,i=[],Y=!1,L=65536,q=.5,f=(...e)=>{},$=se,we=e=>re(e),Ge=()=>{if(!g||!S){console.error("[PreScheduler] Cannot init - missing buffer or constants");return}P=new Int32Array(g),Te=new DataView(g),Ae=new Uint8Array(g),B=te(M,S.CONTROL_START);let e=M+S.METRICS_START;a=new Uint32Array(g,e,S.METRICS_SIZE/4),f("[PreScheduler] SharedArrayBuffer initialized with direct ring buffer writing and metrics")},b=()=>{if(!a)return;C(9,o.length);let e=o.length,t=V(10);e>t&&C(10,e)},I=(e,t,s=0,n=!1)=>{if(A==="postMessage")return Q?(Q.postMessage({type:"osc",oscData:e,sourceId:s}),U(12,1),!0):(console.error("[PreScheduler] No worklet port available"),!1);if(!g||!P)return console.error("[PreScheduler] Not initialized for ring buffer writing"),!1;let r=e.length,c=S.MESSAGE_HEADER_SIZE+r;return c>S.IN_BUFFER_SIZE-S.MESSAGE_HEADER_SIZE?(console.error("[PreScheduler] Message too large:",c),!1):ee({atomicView:P,dataView:Te,uint8View:Ae,bufferConstants:S,ringBufferBase:M,controlIndices:B,oscMessage:e,sourceId:s,maxSpins:10,useWait:n})?(U(12,1),!0):!1},x=(e,t,s=0)=>{let n=o.length+i.length;if(n>=L){console.error("[PreScheduler] Backpressure: dropping retry ("+n+" pending)"),U(17,1);return}i.push({oscData:e,context:t||"unknown",queuedAt:performance.now(),sourceId:s}),C(18,i.length);let r=V(19);i.length>r&&C(19,i.length),f("[PreScheduler] Queued message for retry:",t,"queue size:",i.length),Ue()},Ue=()=>{if(Y||i.length===0||A!=="sab")return;Y=!0;let e=Atomics.load(P,B.IN_TAIL),t=Atomics.waitAsync(P,B.IN_TAIL,e),s=()=>{Y=!1,ke(),i.length>0&&Ue()};t.async?t.value.then(s):queueMicrotask(s)},ke=()=>{if(i.length===0)return;let e=0;for(let t=0;t<i.length;t++){let s=i[t];if(I(s.oscData,!0,s.sourceId,!0))e++,U(16,1);else break}e>0&&(i.splice(0,e),C(18,i.length))},fe=(e,t,s,n=0)=>{let r=o.length+i.length;if(r>=L){let E=`Prescheduler queue full (${r} >= ${L} max)`;return console.error("[PreScheduler]",E),self.postMessage({type:"error",error:E,code:"PRESCHEDULER_QUEUE_FULL"}),!1}let c=we(e);if(c===null)return f("[PreScheduler] Non-bundle message, dispatching immediately"),I(e,!1,n,!0)||x(e,"immediate message",n),!0;let l=$(),u=c-l;if(e.length>K){let E=`Bundle too large for scheduler (${e.length} > ${K} bytes)`;return console.error("[PreScheduler]",E),self.postMessage({type:"error",error:E,code:"BUNDLE_TOO_LARGE"}),!1}if(u>le){let E=`Bundle scheduled too far in future (${u.toFixed(0)}s > ${le}s max)`;return console.error("[PreScheduler]",E),self.postMessage({type:"error",error:E,code:"BUNDLE_TOO_FAR_FUTURE"}),!1}let T={ntpTime:c,seq:be++,sessionId:t||0,runTag:s||"",oscData:e,sourceId:n};return Ye(T),U(11,1),b(),f("[PreScheduler] Scheduled bundle:","NTP="+c.toFixed(3),"current="+l.toFixed(3),"wait="+(u*1e3).toFixed(1)+"ms","pending="+o.length),O(),!0},Ye=e=>{o.push(e),Xe(o.length-1)},de=()=>o.length>0?o[0]:null,ve=()=>{if(o.length===0)return null;let e=o[0],t=o.pop();return o.length>0&&(o[0]=t,Re(0)),e},Xe=e=>{for(;e>0;){let t=Math.floor((e-1)/2);if(Z(o[e],o[t])>=0)break;ge(e,t),e=t}},Re=e=>{let t=o.length;for(;;){let s=2*e+1,n=2*e+2,r=e;if(s<t&&Z(o[s],o[r])<0&&(r=s),n<t&&Z(o[n],o[r])<0&&(r=n),r===e)break;ge(e,r),e=r}},Z=(e,t)=>e.ntpTime===t.ntpTime?e.seq-t.seq:e.ntpTime-t.ntpTime,ge=(e,t)=>{let s=o[e];o[e]=o[t],o[t]=s},O=()=>{if(o.length===0){d!==null&&(clearTimeout(d),d=null,m=1/0);return}let e=de().ntpTime-q,t=$();if(e<m){d!==null&&clearTimeout(d);let s=Math.max(0,(e-t)*1e3);m=e,d=setTimeout(Ke,s)}},Qe=()=>{d===null&&(f("[PreScheduler] Starting demand-driven dispatching"),O())};var Ke=()=>{ae=!0;let e=$(),t=e+q,s=0;for(;o.length>0;){let n=de();if(n.ntpTime<=t){ve(),b();let r=n.ntpTime-e;if(U(21,1),r<0){let l=Math.round(-r*1e3);U(15,1);let u=ue(23);l>u&&H(23,l)}else{let l=Math.round(r*1e3),u=ue(14);(u===Se||l<u)&&H(14,l)}f("[PreScheduler] Dispatching bundle:","NTP="+n.ntpTime.toFixed(3),"current="+e.toFixed(3),"early="+(r*1e3).toFixed(1)+"ms","remaining="+o.length),I(n.oscData,!1,n.sourceId,!0)||x(n.oscData,"scheduled bundle NTP="+n.ntpTime.toFixed(3),n.sourceId),s++}else break}(s>0||o.length>0||i.length>0)&&f("[PreScheduler] Dispatch cycle complete:","dispatched="+s,"pending="+o.length,"retrying="+i.length),ae=!1,d=null,m=1/0,O()},J=e=>{if(o.length===0)return;let t=o.length,s=[];for(let r=0;r<o.length;r++){let c=o[r];e(c)||s.push(c)}let n=t-s.length;n>0&&(o=s,We(),U(13,n),b(),f("[PreScheduler] Cancelled "+n+" events, "+o.length+" remaining"),O())},We=()=>{for(let e=Math.floor(o.length/2)-1;e>=0;e--)Re(e)},Ze=(e,t)=>{J(s=>s.sessionId===e&&s.runTag===t)},ze=e=>{J(t=>t.sessionId===e)},Ve=e=>{J(t=>t.runTag===e)},qe=()=>{let e=o.length,t=i.length;e===0&&t===0||(U(13,e+t),o=[],i=[],C(18,0),b(),f("[PreScheduler] Cancelled all "+e+" events, "+t+" retries"),O())},$e=ne,Je=e=>{let t=[],s=new DataView(e.buffer,e.byteOffset,e.byteLength),n=16;for(;n+4<=e.length;){let r=s.getInt32(n,!1);if(n+=4,r<=0||r>Fe||n+r>e.length)break;let c=e.slice(n,n+r);for(t.push(c),n+=r;n%4!==0&&n<e.length;)n++}return t},je=(e,t=0)=>{if($e(e)){let s=Je(e);for(let n=0;n<s.length;n++)I(s[n],!1,t,!0)||x(s[n],"immediate bundle message "+n,t)}else I(e,!1,t,!0)||x(e,"immediate message",t)};self.addEventListener("message",e=>{let{data:t}=e;try{switch(t.type){case"init":if(A=t.mode||"sab",t.maxPendingMessages&&(L=t.maxPendingMessages),t.snapshotIntervalMs&&(W=t.snapshotIntervalMs),t.bypassLookaheadS!==void 0&&(q=t.bypassLookaheadS),A==="sab")g=t.sharedBuffer,M=t.ringBufferBase,S=t.bufferConstants,Ge(),S&&S.scheduler_slot_size&&(K=S.scheduler_slot_size);else{Q=t.workletPort;let n=184;F=new ArrayBuffer(n),a=new Uint32Array(F),He()}H(14,Se),H(23,0),Qe(),f("[OSCPreSchedulerWorker] Initialized with NTP-based scheduling, mode="+A+", capacity="+L),self.postMessage({type:"initialized"});break;case"addOscSource":let s=e.ports[0];s&&(s.onmessage=n=>{n.data.type==="osc"&&n.data.oscData&&fe(n.data.oscData,0,"",n.data.sourceId||0)},f("[OSCPreSchedulerWorker] Added external OSC source"));break;case"send":fe(t.oscData,t.sessionId||0,t.runTag||"",t.sourceId||0);break;case"sendImmediate":je(t.oscData,t.sourceId||0);break;case"directDispatch":I(t.oscData,!1,t.sourceId||0,!0)||x(t.oscData,"directDispatch fallback",t.sourceId||0);break;case"cancelSessionTag":t.runTag!==void 0&&t.runTag!==null&&t.runTag!==""&&Ze(t.sessionId||0,t.runTag);break;case"cancelSession":ze(t.sessionId||0);break;case"cancelTag":t.runTag!==void 0&&t.runTag!==null&&t.runTag!==""&&Ve(t.runTag);break;case"cancelAll":qe(),t.ack&&self.postMessage({type:"cancelAllAck"});break;default:}}catch(s){console.error("[OSCPreSchedulerWorker] Error:",s),self.postMessage({type:"error",error:s.message})}});f("[OSCPreSchedulerWorker] Script loaded");})();
|
package/package.json
CHANGED
package/supersonic.d.ts
CHANGED
|
@@ -262,6 +262,11 @@ export interface SuperSonicOptions {
|
|
|
262
262
|
/** Line length limits for activity events emitted to listeners. */
|
|
263
263
|
activityEvent?: ActivityLineConfig;
|
|
264
264
|
|
|
265
|
+
/** Maximum buffer pool capacity in bytes. Pool grows on demand up to this limit. Default: 256MB. */
|
|
266
|
+
maxBufferMemory?: number;
|
|
267
|
+
/** Bytes to grow the buffer pool per growth event. Default: 32MB. */
|
|
268
|
+
bufferGrowIncrement?: number;
|
|
269
|
+
|
|
265
270
|
/** Max fetch retries when loading assets. Default: 3. */
|
|
266
271
|
fetchMaxRetries?: number;
|
|
267
272
|
/** Base delay between retries in ms (exponential backoff). Default: 1000. */
|
|
@@ -402,6 +407,14 @@ export interface SuperSonicMetrics {
|
|
|
402
407
|
bufferPoolAvailableBytes: number;
|
|
403
408
|
/** Total buffer pool allocations. */
|
|
404
409
|
bufferPoolAllocations: number;
|
|
410
|
+
/** Buffer pool committed capacity in bytes (grows on demand). */
|
|
411
|
+
bufferPoolTotalCapacity: number;
|
|
412
|
+
/** Buffer pool hard ceiling in bytes. */
|
|
413
|
+
bufferPoolMaxCapacity: number;
|
|
414
|
+
/** Number of buffer pool growth events. */
|
|
415
|
+
bufferPoolGrowthCount: number;
|
|
416
|
+
/** Number of buffer pool segments (1 = no growth yet). */
|
|
417
|
+
bufferPoolPoolCount: number;
|
|
405
418
|
/** Number of loaded synthdefs. */
|
|
406
419
|
loadedSynthDefs: number;
|
|
407
420
|
/** Maximum scsynth scheduler queue size (compile-time constant). */
|
|
@@ -416,6 +429,18 @@ export interface SuperSonicMetrics {
|
|
|
416
429
|
debugBufferCapacity: number;
|
|
417
430
|
/** Transport mode as enum index: 0=sab, 1=postMessage. */
|
|
418
431
|
mode: number;
|
|
432
|
+
|
|
433
|
+
// Audio diagnostics (main thread only)
|
|
434
|
+
/** Audio underrun/glitch events (Chrome playbackStats, 0 on other browsers). */
|
|
435
|
+
glitchCount: number;
|
|
436
|
+
/** Total silence from audio underruns in ms (Chrome playbackStats, 0 on other browsers). */
|
|
437
|
+
glitchDurationMs: number;
|
|
438
|
+
/** Average audio output latency in microseconds (Chrome playbackStats, 0 on other browsers). */
|
|
439
|
+
averageLatencyUs: number;
|
|
440
|
+
/** Maximum audio output latency in microseconds (Chrome playbackStats, 0 on other browsers). */
|
|
441
|
+
maxLatencyUs: number;
|
|
442
|
+
/** Audio health: fraction of expected audio frames delivered, 0-100 (cross-browser). */
|
|
443
|
+
audioHealthPct: number;
|
|
419
444
|
}
|
|
420
445
|
|
|
421
446
|
/** Schema entry describing a single metric field. */
|
|
@@ -571,6 +596,7 @@ export interface SuperSonicInfo {
|
|
|
571
596
|
crossOriginIsolated: boolean;
|
|
572
597
|
atomics: boolean;
|
|
573
598
|
webWorker: boolean;
|
|
599
|
+
playbackStats: boolean;
|
|
574
600
|
};
|
|
575
601
|
/** scsynth WASM version string, or null if not yet initialised. */
|
|
576
602
|
version: string | null;
|
|
@@ -597,6 +623,56 @@ export interface Snapshot {
|
|
|
597
623
|
} | null;
|
|
598
624
|
}
|
|
599
625
|
|
|
626
|
+
/**
|
|
627
|
+
* System performance report returned by {@link SuperSonic.getSystemReport}.
|
|
628
|
+
*
|
|
629
|
+
* Includes hardware info, audio configuration, Chrome playbackStats (if available),
|
|
630
|
+
* a cross-browser audio health percentage, and a human-readable health assessment.
|
|
631
|
+
* Useful for diagnosing audio crackling on constrained hardware.
|
|
632
|
+
*/
|
|
633
|
+
export interface SystemReport {
|
|
634
|
+
/** ISO 8601 timestamp when the report was generated. */
|
|
635
|
+
timestamp: string;
|
|
636
|
+
/** Hardware and browser info. */
|
|
637
|
+
system: {
|
|
638
|
+
userAgent: string;
|
|
639
|
+
hardwareConcurrency: number | null;
|
|
640
|
+
deviceMemory: number | null;
|
|
641
|
+
platform: string;
|
|
642
|
+
};
|
|
643
|
+
/** AudioContext configuration and state. */
|
|
644
|
+
audio: {
|
|
645
|
+
sampleRate: number;
|
|
646
|
+
baseLatency: number | null;
|
|
647
|
+
outputLatency: number | null;
|
|
648
|
+
state: string;
|
|
649
|
+
channelCount: number;
|
|
650
|
+
};
|
|
651
|
+
/** Chrome playbackStats (null on browsers without support). */
|
|
652
|
+
playbackStats: {
|
|
653
|
+
glitchCount: number;
|
|
654
|
+
glitchDurationS: number;
|
|
655
|
+
totalDurationS: number;
|
|
656
|
+
averageLatencyS: number;
|
|
657
|
+
minimumLatencyS: number;
|
|
658
|
+
maximumLatencyS: number;
|
|
659
|
+
} | null;
|
|
660
|
+
/** Engine configuration. */
|
|
661
|
+
engine: {
|
|
662
|
+
mode: TransportMode;
|
|
663
|
+
version: string | null;
|
|
664
|
+
bootTimeMs: number | null;
|
|
665
|
+
};
|
|
666
|
+
/** Health assessment with issues and human-readable summary. */
|
|
667
|
+
health: {
|
|
668
|
+
audioHealthPct: number;
|
|
669
|
+
issues: Array<{ severity: 'warning' | 'error' | 'critical'; message: string }>;
|
|
670
|
+
summary: string;
|
|
671
|
+
};
|
|
672
|
+
/** Full metrics snapshot at time of report. */
|
|
673
|
+
metrics: SuperSonicMetrics;
|
|
674
|
+
}
|
|
675
|
+
|
|
600
676
|
/**
|
|
601
677
|
* Metadata about decoded audio content.
|
|
602
678
|
*
|
|
@@ -750,6 +826,9 @@ export interface SuperSonicEventMap {
|
|
|
750
826
|
|
|
751
827
|
/** An asset finished loading. Size is in bytes. */
|
|
752
828
|
'loading:complete': (data: { type: string; name: string; size: number }) => void;
|
|
829
|
+
|
|
830
|
+
/** Buffer pool grew on demand. Fired when the initial pool was exhausted and a new segment was added. */
|
|
831
|
+
'buffer:pool:grown': (data: { poolIndex: number; newBytes: number; totalCapacity: number }) => void;
|
|
753
832
|
}
|
|
754
833
|
|
|
755
834
|
/** Union of all event names. */
|
|
@@ -1790,6 +1869,22 @@ export class SuperSonic {
|
|
|
1790
1869
|
*/
|
|
1791
1870
|
getSnapshot(): Snapshot;
|
|
1792
1871
|
|
|
1872
|
+
/**
|
|
1873
|
+
* Get a comprehensive system performance report.
|
|
1874
|
+
*
|
|
1875
|
+
* Includes hardware info, audio configuration, Chrome playbackStats (if available),
|
|
1876
|
+
* a cross-browser audio health percentage, and a human-readable health assessment.
|
|
1877
|
+
* Useful for diagnosing audio crackling on constrained hardware.
|
|
1878
|
+
*
|
|
1879
|
+
* @example
|
|
1880
|
+
* const report = sonic.getSystemReport();
|
|
1881
|
+
* console.log(report.health.summary);
|
|
1882
|
+
* if (report.health.audioHealthPct < 95) {
|
|
1883
|
+
* console.warn('Audio thread struggling:', report.health.issues);
|
|
1884
|
+
* }
|
|
1885
|
+
*/
|
|
1886
|
+
getSystemReport(): SystemReport;
|
|
1887
|
+
|
|
1793
1888
|
// ──────────────────────────────────────────────────────────────────────────
|
|
1794
1889
|
// Node Tree
|
|
1795
1890
|
// ──────────────────────────────────────────────────────────────────────────
|
|
@@ -1842,7 +1937,13 @@ export class SuperSonic {
|
|
|
1842
1937
|
startCapture(): void;
|
|
1843
1938
|
|
|
1844
1939
|
/** Stop capturing and return the captured audio data. */
|
|
1845
|
-
stopCapture():
|
|
1940
|
+
stopCapture(): {
|
|
1941
|
+
sampleRate: number;
|
|
1942
|
+
channels: number;
|
|
1943
|
+
frames: number;
|
|
1944
|
+
left: Float32Array;
|
|
1945
|
+
right: Float32Array | null;
|
|
1946
|
+
};
|
|
1846
1947
|
|
|
1847
1948
|
/** Check if audio capture is currently enabled. */
|
|
1848
1949
|
isCaptureEnabled(): boolean;
|