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 CHANGED
@@ -1,31 +1,46 @@
1
- SuperSonic - WebAssembly Audio Synthesis Engine
1
+ SuperSonic - Audio Synthesis Engine
2
2
  Copyright (c) 2025 Sam Aaron
3
3
 
4
4
  This project is dual-licensed depending on the component:
5
5
 
6
6
  ================================================================================
7
- JavaScript Client Library (MIT OR GPL-3.0-or-later)
7
+ Client Libraries (MIT OR GPL-3.0-or-later)
8
8
  ================================================================================
9
9
 
10
- The following JavaScript files are dual-licensed under your choice of MIT or
11
- GPL-3.0-or-later:
10
+ The following client API files are dual-licensed under your choice of MIT or
11
+ GPL-3.0-or-later. These communicate with the engine exclusively via serialised
12
+ Open Sound Control (OSC) messages and contain no GPL-derived code.
12
13
 
13
- js/supersonic.js
14
- js/lib/
15
- js/workers/debug_worker.js
16
- js/workers/osc_in_worker.js
17
- js/workers/osc_out_log_sab_worker.js
18
- js/workers/osc_out_prescheduler_worker.js
14
+ JavaScript client:
15
+ js/supersonic.js
16
+ js/lib/
17
+ js/workers/debug_worker.js
18
+ js/workers/osc_in_worker.js
19
+ js/workers/osc_out_log_sab_worker.js
20
+ js/workers/osc_out_prescheduler_worker.js
21
+
22
+ Erlang/BEAM client:
23
+ src/nif/supersonic.erl
19
24
 
20
25
  ================================================================================
21
- Audio Engine and AudioWorklet (GPL-3.0-or-later)
26
+ Audio Engine (GPL-3.0-or-later)
22
27
  ================================================================================
23
28
 
24
29
  The following components are licensed under GPL-3.0-or-later only:
25
30
 
26
- src/ (C++/WASM audio engine)
31
+ src/ (C++ audio engine — shared across all
32
+ build targets: WASM, native, NIF)
33
+ src/native/ (Native JUCE host wrapper)
27
34
  js/workers/scsynth_audio_worklet.js (AudioWorklet that executes WASM)
28
35
 
36
+ The engine compiles to three targets:
37
+ - WebAssembly (AudioWorklet) — for browsers
38
+ - Native executable (JUCE) — for desktop use and Sonic Pi
39
+ - NIF shared library (BEAM) — for Erlang/Elixir applications
40
+
41
+ All three targets link the same GPL-licensed engine code derived from
42
+ SuperCollider's scsynth.
43
+
29
44
  Based on SuperCollider's scsynth
30
45
  Copyright (c) 2002-2023 James McCartney and SuperCollider contributors
31
46
 
@@ -76,33 +91,42 @@ It is provided for clarification and guidance only and does not modify,
76
91
  supersede, or reinterpret the terms of the GPL.
77
92
 
78
93
  SuperSonic is deliberately designed with a strict execution boundary between
79
- the GPL-licensed audio engine and application-level code:
80
-
81
- - The GPL-licensed engine runs inside an AudioWorklet, providing strong
82
- execution isolation within the browser. Code outside the AudioWorklet
83
- cannot directly access engine internals, shared closures, object
84
- references, or function calls.
85
-
86
- - The MIT-licensed JavaScript client library and supporting workers
87
- communicate with the engine exclusively using serialized Open Sound
88
- Control (OSC) messages. These messages are transported using explicit
89
- inter-context communication mechanisms such as postMessage or
90
- shared-memory transport used only for serialized message exchange.
91
-
92
- OSC is a language-neutral, protocol-level messaging system comparable to
93
- network or IPC-based communication between independent software components.
94
+ the GPL-licensed audio engine and application-level code. All communication
95
+ crosses this boundary via serialised OSC messages — a language-neutral,
96
+ protocol-level messaging system comparable to network or IPC-based
97
+ communication between independent software components.
98
+
99
+ The boundary is enforced by architecture on each platform:
100
+
101
+ - Web (WASM): The engine runs inside an AudioWorklet, providing strong
102
+ execution isolation. Code outside the AudioWorklet cannot directly access
103
+ engine internals, shared closures, object references, or function calls.
104
+ The MIT-licensed JavaScript client communicates exclusively via
105
+ postMessage or shared-memory transport used only for serialised OSC
106
+ message exchange.
107
+
108
+ - Native (JUCE): The engine runs as a standalone process or within a host
109
+ application. External code communicates via UDP OSC messages over a
110
+ network socket — a clear process boundary.
111
+
112
+ - NIF (BEAM): The engine is loaded as a NIF shared library. The
113
+ MIT-licensed Erlang module (src/nif/supersonic.erl) exposes five
114
+ functions: start, stop, send_osc, set_notification_pid, and
115
+ clear_notification_pid. All data crossing the NIF boundary consists of
116
+ raw OSC binary blobs and Erlang messages — no engine types, data
117
+ structures, or callbacks leak into the Erlang API. This is functionally
118
+ equivalent to communication over a network socket.
94
119
 
95
120
  It is the project author's good-faith interpretation that application code
96
- which uses the MIT-licensed client library solely to send and receive OSC
121
+ which uses the MIT-licensed client libraries solely to send and receive OSC
97
122
  messages with the engine would generally not constitute a derivative work of
98
123
  the GPL-licensed audio engine, provided that:
99
124
 
100
- 1. The GPL-licensed components (src/ and
101
- js/workers/scsynth_audio_worklet.js) are not modified. Any
102
- modifications to these components remain subject to the GPL.
125
+ 1. The GPL-licensed components are not modified. Any modifications to these
126
+ components remain subject to the GPL.
103
127
 
104
128
  2. Application code interacts with the engine exclusively through the
105
- documented OSC message interface via the MIT-licensed client library.
129
+ documented OSC message interface via the MIT-licensed client libraries.
106
130
 
107
131
  3. Distribution of the GPL-licensed engine components remains compliant
108
132
  with the GPL, including providing access to corresponding source code
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # SuperSonic 0.63.0
1
+ # SuperSonic 0.65.0
2
2
 
3
3
  > SuperCollider's scsynth in the browser as an AudioWorklet.
4
4
  >
@@ -247,6 +247,7 @@ scsynth with low latency inside a web page.
247
247
  | [`ringBufferBase`](#ringbufferbase) | Ring buffer base offset in SharedArrayBuffer. |
248
248
  | [`sharedBuffer`](#sharedbuffer) | The SharedArrayBuffer (SAB mode) or null (postMessage mode). |
249
249
  | [`getLoadedBuffers()`](#getloadedbuffers) | Get info about all loaded audio buffers. |
250
+ | [`getSystemReport()`](#getsystemreport) | Get a comprehensive system performance report. |
250
251
  | [`nextNodeId()`](#nextnodeid) | Get the next unique node ID. |
251
252
  | [`getRawTreeSchema()`](#getrawtreeschema) | Get schema describing the raw flat node tree structure. |
252
253
  | [`getTreeSchema()`](#gettreeschema) | Get schema describing the hierarchical node tree structure. |
@@ -859,6 +860,7 @@ if (loaded.some(b => b.hash === info.hash)) {
859
860
  | <a id="audiocontextoptions"></a> `audioContextOptions?` | `AudioContextOptions` | Options passed to `new AudioContext()`. Ignored if `audioContext` is provided. | |
860
861
  | <a id="autoconnect"></a> `autoConnect?` | `boolean` | Auto-connect the AudioWorkletNode to the AudioContext destination. Default: true. | |
861
862
  | <a id="baseurl"></a> `baseURL?` | `string` | Convenience shorthand when all assets (WASM, workers, synthdefs, samples) are co-located. | Yes\* |
863
+ | <a id="buffergrowincrement"></a> `bufferGrowIncrement?` | `number` | Bytes to grow the buffer pool per growth event. Default: 32MB. | |
862
864
  | <a id="bypasslookaheadms"></a> `bypassLookaheadMs?` | `number` | 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. | |
863
865
  | <a id="corebaseurl"></a> `coreBaseURL?` | `string` | Base URL for GPL assets: WASM and AudioWorklet (supersonic-scsynth-core package). Defaults to `baseURL`. | |
864
866
  | <a id="debug-1"></a> `debug?` | `boolean` | Enable all debug console logging. Default: false. | |
@@ -867,6 +869,7 @@ if (loaded.some(b => b.hash === info.hash)) {
867
869
  | <a id="debugscsynth"></a> `debugScsynth?` | `boolean` | Log scsynth debug output to console. Default: false. | |
868
870
  | <a id="fetchmaxretries"></a> `fetchMaxRetries?` | `number` | Max fetch retries when loading assets. Default: 3. | |
869
871
  | <a id="fetchretrydelay"></a> `fetchRetryDelay?` | `number` | Base delay between retries in ms (exponential backoff). Default: 1000. | |
872
+ | <a id="maxbuffermemory"></a> `maxBufferMemory?` | `number` | Maximum buffer pool capacity in bytes. Pool grows on demand up to this limit. Default: 256MB. | |
870
873
  | <a id="mode-5"></a> `mode?` | [`TransportMode`](#transportmode) | Transport mode. - `'postMessage'` (default) — works everywhere, no special headers needed - `'sab'` — lowest latency, requires Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers See docs/MODES.md for a full comparison of communication modes. | |
871
874
  | <a id="preschedulercapacity-1"></a> `preschedulerCapacity?` | `number` | Max pending events in the JS prescheduler. Default: 65536. | |
872
875
  | <a id="samplebaseurl"></a> `sampleBaseURL?` | `string` | Base URL for audio sample files (used by [SuperSonic.loadSample](#loadsample)). | |
@@ -1329,6 +1332,30 @@ Useful for capturing state for bug reports or debugging timing issues.
1329
1332
 
1330
1333
  [`Snapshot`](#snapshot)
1331
1334
 
1335
+ ##### getSystemReport()
1336
+
1337
+ > **getSystemReport**(): [`SystemReport`](#systemreport)
1338
+
1339
+ Get a comprehensive system performance report.
1340
+
1341
+ Includes hardware info, audio configuration, Chrome playbackStats (if available),
1342
+ a cross-browser audio health percentage, and a human-readable health assessment.
1343
+ Useful for diagnosing audio crackling on constrained hardware.
1344
+
1345
+ ###### Returns
1346
+
1347
+ [`SystemReport`](#systemreport)
1348
+
1349
+ ###### Example
1350
+
1351
+ ```ts
1352
+ const report = sonic.getSystemReport();
1353
+ console.log(report.health.summary);
1354
+ if (report.health.audioHealthPct < 95) {
1355
+ console.warn('Audio thread struggling:', report.health.issues);
1356
+ }
1357
+ ```
1358
+
1332
1359
  ##### getTree()
1333
1360
 
1334
1361
  > **getTree**(): [`Tree`](#tree)
@@ -1573,11 +1600,7 @@ Subscribe to an event.
1573
1600
 
1574
1601
  Unsubscribe function — call it to remove the listener
1575
1602
 
1576
- > (): `void`
1577
-
1578
- ###### Returns
1579
-
1580
- `void`
1603
+ () => `void`
1581
1604
 
1582
1605
  ###### Example
1583
1606
 
@@ -1614,11 +1637,7 @@ Returns an unsubscribe function (matching [on](#on)).
1614
1637
 
1615
1638
  Unsubscribe function — call it to remove the listener before it fires
1616
1639
 
1617
- > (): `void`
1618
-
1619
- ###### Returns
1620
-
1621
- `void`
1640
+ () => `void`
1622
1641
 
1623
1642
  ##### purge()
1624
1643
 
@@ -1699,6 +1718,7 @@ Remove all listeners for an event, or all listeners entirely.
1699
1718
  | <a id="audiocontextresumed"></a> `audiocontext:resumed` | AudioContext resumed to 'running' state. |
1700
1719
  | <a id="audiocontextstatechange"></a> `audiocontext:statechange` | AudioContext state changed. State is one of: `'running'`, `'suspended'`, `'closed'`, or `'interrupted'`. |
1701
1720
  | <a id="audiocontextsuspended"></a> `audiocontext:suspended` | AudioContext was suspended (e.g. tab backgrounded, autoplay policy, iOS audio interruption). Show a restart UI and call `recover()` when the user interacts. |
1721
+ | <a id="bufferpoolgrown"></a> `buffer:pool:grown` | Buffer pool grew on demand. Fired when the initial pool was exhausted and a new segment was added. |
1702
1722
  | <a id="debug"></a> `debug` | Debug text output from scsynth (e.g. synthdef compilation messages). Includes NTP timestamp and sequence number. |
1703
1723
  | <a id="destroy-1"></a> `destroy` | Engine has been destroyed. Only fired by `destroy()`, not by `shutdown()` or `reset()`. Last chance to clean up before all listeners are cleared. |
1704
1724
  | <a id="error"></a> `error` | Error from any component (worklet, transport, workers). |
@@ -3053,13 +3073,21 @@ Start capturing audio output to a buffer. SAB mode only.
3053
3073
 
3054
3074
  ##### stopCapture()
3055
3075
 
3056
- > **stopCapture**(): `Float32Array`
3076
+ > **stopCapture**(): `object`
3057
3077
 
3058
3078
  Stop capturing and return the captured audio data.
3059
3079
 
3060
3080
  ###### Returns
3061
3081
 
3062
- `Float32Array`
3082
+ `object`
3083
+
3084
+ | Name | Type |
3085
+ | ------------ | --------------------------------- |
3086
+ | `channels` | `number` |
3087
+ | `frames` | `number` |
3088
+ | `left` | `Float32Array` |
3089
+ | `right` | `Float32Array`<`ArrayBufferLike`> |
3090
+ | `sampleRate` | `number` |
3063
3091
 
3064
3092
  ##### suspend()
3065
3093
 
package/README.npm.md CHANGED
@@ -1,4 +1,4 @@
1
- # SuperSonic 0.63.0
1
+ # SuperSonic 0.65.0
2
2
 
3
3
  > SuperCollider's scsynth in the browser as an AudioWorklet.
4
4
  >
@@ -247,6 +247,7 @@ scsynth with low latency inside a web page.
247
247
  | [`ringBufferBase`](#ringbufferbase) | Ring buffer base offset in SharedArrayBuffer. |
248
248
  | [`sharedBuffer`](#sharedbuffer) | The SharedArrayBuffer (SAB mode) or null (postMessage mode). |
249
249
  | [`getLoadedBuffers()`](#getloadedbuffers) | Get info about all loaded audio buffers. |
250
+ | [`getSystemReport()`](#getsystemreport) | Get a comprehensive system performance report. |
250
251
  | [`nextNodeId()`](#nextnodeid) | Get the next unique node ID. |
251
252
  | [`getRawTreeSchema()`](#getrawtreeschema) | Get schema describing the raw flat node tree structure. |
252
253
  | [`getTreeSchema()`](#gettreeschema) | Get schema describing the hierarchical node tree structure. |
@@ -859,6 +860,7 @@ if (loaded.some(b => b.hash === info.hash)) {
859
860
  | <a id="audiocontextoptions"></a> `audioContextOptions?` | `AudioContextOptions` | Options passed to `new AudioContext()`. Ignored if `audioContext` is provided. | |
860
861
  | <a id="autoconnect"></a> `autoConnect?` | `boolean` | Auto-connect the AudioWorkletNode to the AudioContext destination. Default: true. | |
861
862
  | <a id="baseurl"></a> `baseURL?` | `string` | Convenience shorthand when all assets (WASM, workers, synthdefs, samples) are co-located. | Yes\* |
863
+ | <a id="buffergrowincrement"></a> `bufferGrowIncrement?` | `number` | Bytes to grow the buffer pool per growth event. Default: 32MB. | |
862
864
  | <a id="bypasslookaheadms"></a> `bypassLookaheadMs?` | `number` | 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. | |
863
865
  | <a id="corebaseurl"></a> `coreBaseURL?` | `string` | Base URL for GPL assets: WASM and AudioWorklet (supersonic-scsynth-core package). Defaults to `baseURL`. | |
864
866
  | <a id="debug-1"></a> `debug?` | `boolean` | Enable all debug console logging. Default: false. | |
@@ -867,6 +869,7 @@ if (loaded.some(b => b.hash === info.hash)) {
867
869
  | <a id="debugscsynth"></a> `debugScsynth?` | `boolean` | Log scsynth debug output to console. Default: false. | |
868
870
  | <a id="fetchmaxretries"></a> `fetchMaxRetries?` | `number` | Max fetch retries when loading assets. Default: 3. | |
869
871
  | <a id="fetchretrydelay"></a> `fetchRetryDelay?` | `number` | Base delay between retries in ms (exponential backoff). Default: 1000. | |
872
+ | <a id="maxbuffermemory"></a> `maxBufferMemory?` | `number` | Maximum buffer pool capacity in bytes. Pool grows on demand up to this limit. Default: 256MB. | |
870
873
  | <a id="mode-5"></a> `mode?` | [`TransportMode`](#transportmode) | Transport mode. - `'postMessage'` (default) — works everywhere, no special headers needed - `'sab'` — lowest latency, requires Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers See docs/MODES.md for a full comparison of communication modes. | |
871
874
  | <a id="preschedulercapacity-1"></a> `preschedulerCapacity?` | `number` | Max pending events in the JS prescheduler. Default: 65536. | |
872
875
  | <a id="samplebaseurl"></a> `sampleBaseURL?` | `string` | Base URL for audio sample files (used by [SuperSonic.loadSample](#loadsample)). | |
@@ -1329,6 +1332,30 @@ Useful for capturing state for bug reports or debugging timing issues.
1329
1332
 
1330
1333
  [`Snapshot`](#snapshot)
1331
1334
 
1335
+ ##### getSystemReport()
1336
+
1337
+ > **getSystemReport**(): [`SystemReport`](#systemreport)
1338
+
1339
+ Get a comprehensive system performance report.
1340
+
1341
+ Includes hardware info, audio configuration, Chrome playbackStats (if available),
1342
+ a cross-browser audio health percentage, and a human-readable health assessment.
1343
+ Useful for diagnosing audio crackling on constrained hardware.
1344
+
1345
+ ###### Returns
1346
+
1347
+ [`SystemReport`](#systemreport)
1348
+
1349
+ ###### Example
1350
+
1351
+ ```ts
1352
+ const report = sonic.getSystemReport();
1353
+ console.log(report.health.summary);
1354
+ if (report.health.audioHealthPct < 95) {
1355
+ console.warn('Audio thread struggling:', report.health.issues);
1356
+ }
1357
+ ```
1358
+
1332
1359
  ##### getTree()
1333
1360
 
1334
1361
  > **getTree**(): [`Tree`](#tree)
@@ -1573,11 +1600,7 @@ Subscribe to an event.
1573
1600
 
1574
1601
  Unsubscribe function — call it to remove the listener
1575
1602
 
1576
- > (): `void`
1577
-
1578
- ###### Returns
1579
-
1580
- `void`
1603
+ () => `void`
1581
1604
 
1582
1605
  ###### Example
1583
1606
 
@@ -1614,11 +1637,7 @@ Returns an unsubscribe function (matching [on](#on)).
1614
1637
 
1615
1638
  Unsubscribe function — call it to remove the listener before it fires
1616
1639
 
1617
- > (): `void`
1618
-
1619
- ###### Returns
1620
-
1621
- `void`
1640
+ () => `void`
1622
1641
 
1623
1642
  ##### purge()
1624
1643
 
@@ -1699,6 +1718,7 @@ Remove all listeners for an event, or all listeners entirely.
1699
1718
  | <a id="audiocontextresumed"></a> `audiocontext:resumed` | AudioContext resumed to 'running' state. |
1700
1719
  | <a id="audiocontextstatechange"></a> `audiocontext:statechange` | AudioContext state changed. State is one of: `'running'`, `'suspended'`, `'closed'`, or `'interrupted'`. |
1701
1720
  | <a id="audiocontextsuspended"></a> `audiocontext:suspended` | AudioContext was suspended (e.g. tab backgrounded, autoplay policy, iOS audio interruption). Show a restart UI and call `recover()` when the user interacts. |
1721
+ | <a id="bufferpoolgrown"></a> `buffer:pool:grown` | Buffer pool grew on demand. Fired when the initial pool was exhausted and a new segment was added. |
1702
1722
  | <a id="debug"></a> `debug` | Debug text output from scsynth (e.g. synthdef compilation messages). Includes NTP timestamp and sequence number. |
1703
1723
  | <a id="destroy-1"></a> `destroy` | Engine has been destroyed. Only fired by `destroy()`, not by `shutdown()` or `reset()`. Last chance to clean up before all listeners are cleared. |
1704
1724
  | <a id="error"></a> `error` | Error from any component (worklet, transport, workers). |
@@ -3053,13 +3073,21 @@ Start capturing audio output to a buffer. SAB mode only.
3053
3073
 
3054
3074
  ##### stopCapture()
3055
3075
 
3056
- > **stopCapture**(): `Float32Array`
3076
+ > **stopCapture**(): `object`
3057
3077
 
3058
3078
  Stop capturing and return the captured audio data.
3059
3079
 
3060
3080
  ###### Returns
3061
3081
 
3062
- `Float32Array`
3082
+ `object`
3083
+
3084
+ | Name | Type |
3085
+ | ------------ | --------------------------------- |
3086
+ | `channels` | `number` |
3087
+ | `frames` | `number` |
3088
+ | `left` | `Float32Array` |
3089
+ | `right` | `Float32Array`<`ArrayBufferLike`> |
3090
+ | `sampleRate` | `number` |
3063
3091
 
3064
3092
  ##### suspend()
3065
3093
 
@@ -1 +1 @@
1
- function u(a){if(!Number.isFinite(a)||a<=0)return"0 B";let l=["B","KB","MB","GB"],e=0;for(;a>=1024&&e<l.length-1;)a/=1024,e++;return`${a.toFixed(e>0?1:0)} ${l[e]}`}function h(a){return a===4294967295?"-":String(a)}function E(a,l){return l[a]??String(a)}function v(a){return String(a|0)}function C(a,l){if(a.format==="bytes")return e=>u(e);if(a.format==="headroom")return e=>h(e);if(a.format==="signed")return e=>v(e);if(a.format==="enum"&&l?.values){let e=l.values;return t=>E(t,e)}return e=>String(e)}var m=class extends HTMLElement{#e=null;#t=null;#s=[];#n=[];#l=!1;buildFromSchema(l){let e=l.getMetricsSchema(),t=e.metrics,n=e.layout;this.innerHTML="",this.#s=[],this.#n=[];for(let o of n.panels){let i=document.createElement("div");i.className="ssm-panel"+(o.class?` ssm-panel--${o.class}`:"");let r=document.createElement("div");r.className="ssm-title",r.textContent=o.title,i.appendChild(r);for(let s of o.rows)s.type==="bar"?this.#i(i,s,t):this.#a(i,s,t);this.appendChild(i)}this.#l=!0}connect(l,e={}){this.disconnect(),this.#e=l,this.#l||this.buildFromSchema(l.constructor);let t=e.refreshRate??10;this.#t=setInterval(()=>this.#c(),1e3/t)}disconnect(){this.#t!==null&&(clearInterval(this.#t),this.#t=null),this.#e=null}disconnectedCallback(){this.disconnect()}#a(l,e,t){let n=document.createElement("div");n.className="ssm-row";let o=e.cells?.find(s=>s.key);if(o){let s=t[o.key];s?.description&&(n.title=s.description)}let i=document.createElement("span");i.className="ssm-label",i.textContent=e.label,n.appendChild(i);let r=document.createElement("span");for(let s of e.cells||[])if(s.sep!==void 0){let c=document.createElement("span");c.className="ssm-sep",c.textContent=s.sep,r.appendChild(c)}else if(s.text!==void 0){let c=document.createElement("span");c.className="ssm-value",s.kind&&c.setAttribute("data-kind",s.kind),c.textContent=s.text,r.appendChild(c)}else if(s.key){let c=t[s.key];if(!c)continue;let d=document.createElement("span");d.className="ssm-value",s.kind&&d.setAttribute("data-kind",s.kind),d.textContent="-",r.appendChild(d),this.#s.push({offset:c.offset,format:C(s,c),el:d,prev:-1})}n.appendChild(r),l.appendChild(n)}#i(l,e,t){let n=document.createElement("div");n.className="ssm-bar";let o=t[e.usedKey],i=t[e.peakKey],r=t[e.capacityKey];o?.description&&(n.title=o.description);let s=document.createElement("span");s.className="ssm-bar-label",s.textContent=e.label,n.appendChild(s);let c=document.createElement("div");c.className="ssm-bar-track";let d=document.createElement("div");d.className=`ssm-bar-fill ssm-bar-fill--${e.color}`,c.appendChild(d);let p=document.createElement("div");p.className=`ssm-bar-peak ssm-bar-peak--${e.color}`,c.appendChild(p),n.appendChild(c);let f=document.createElement("span");f.className="ssm-bar-value ssm-value",e.color==="green"?f.setAttribute("data-kind","green"):e.color==="purple"&&f.setAttribute("data-kind","purple"),f.textContent="-",n.appendChild(f),l.appendChild(n),this.#n.push({usedOffset:o?.offset??0,peakOffset:i?.offset??0,capacityOffset:r?.offset??0,fillEl:d,peakEl:p,labelEl:f,prevUsed:-1,prevPeak:-1,prevCap:-1})}#c(){if(!this.#e)return;let l=this.#e.getMetricsArray();for(let e=0;e<this.#s.length;e++){let t=this.#s[e],n=l[t.offset];n!==t.prev&&(t.el.textContent=t.format(n),t.prev=n)}for(let e=0;e<this.#n.length;e++){let t=this.#n[e],n=l[t.usedOffset],o=l[t.peakOffset],i=l[t.capacityOffset];if(n!==t.prevUsed||i!==t.prevCap){if(i>0){let r=n/i*100;t.fillEl.style.width=r+"%",t.labelEl.textContent=r.toFixed(1)+"%"}else t.fillEl.style.width="0%",t.labelEl.textContent="N/A";t.prevUsed=n,t.prevCap=i}if(o!==t.prevPeak&&i>0){let r=o/i*100;t.peakEl.style.left=`calc(${r}% - 1px)`,t.prevPeak=o}}}};typeof customElements<"u"&&!customElements.get("supersonic-metrics")&&customElements.define("supersonic-metrics",m);export{m as SupersonicMetrics};
1
+ function h(n){if(!Number.isFinite(n)||n<=0)return"0 B";let c=["B","KB","MB","GB"],s=0;for(;n>=1024&&s<c.length-1;)n/=1024,s++;return`${n.toFixed(s>0?1:0)} ${c[s]}`}function E(n){return n===4294967295?"-":String(n)}function k(n,c){return c[n]??String(n)}function v(n){return String(n|0)}function u(n){return(n/1e3).toFixed(1)}function C(n,c,s){if(n.format==="bytes")return e=>h(e);if(n.format==="headroom")return e=>E(e);if(n.format==="signed")return e=>v(e);if(n.format==="percent")return e=>String(e);if(n.format==="latencyUs")return e=>u(e);if(n.format==="enum"&&c?.values){let e=c.values;return i=>k(i,e)}let t=s.hasPlaybackStats?.offset;return n.format==="chromeOnly"?(e,i)=>t!==void 0&&i&&!i[t]?"-":String(e):n.format==="chromeLatencyUs"?(e,i)=>t!==void 0&&i&&!i[t]?"-":u(e):e=>String(e)}var m=class extends HTMLElement{#e=null;#t=null;#s=[];#n=[];#a=!1;buildFromSchema(c){let s=c.getMetricsSchema(),t=s.metrics,e=s.layout;this.innerHTML="",this.#s=[],this.#n=[];for(let i of e.panels){let o=document.createElement("div");o.className="ssm-panel"+(i.class?` ssm-panel--${i.class}`:"");let a=document.createElement("div");a.className="ssm-title",a.textContent=i.title,o.appendChild(a);for(let l of i.rows)l.type==="bar"?this.#i(o,l,t):this.#l(o,l,t);this.appendChild(o)}this.#a=!0}connect(c,s={}){this.disconnect(),this.#e=c,this.#a||this.buildFromSchema(c.constructor);let t=s.refreshRate??10;this.#t=setInterval(()=>this.#c(),1e3/t)}disconnect(){this.#t!==null&&(clearInterval(this.#t),this.#t=null),this.#e=null}disconnectedCallback(){this.disconnect()}#l(c,s,t){let e=document.createElement("div");if(e.className="ssm-row",s.tooltip)e.title=s.tooltip;else{let a=s.cells?.find(l=>l.key);if(a){let l=t[a.key];l?.description&&(e.title=l.description)}}let i=document.createElement("span");i.className="ssm-label",i.textContent=s.label,e.appendChild(i);let o=document.createElement("span");for(let a of s.cells||[])if(a.sep!==void 0){let l=document.createElement("span");l.className="ssm-sep",l.textContent=a.sep,o.appendChild(l)}else if(a.text!==void 0){let l=document.createElement("span");l.className="ssm-value",a.kind&&l.setAttribute("data-kind",a.kind),l.textContent=a.text,o.appendChild(l)}else if(a.key){let l=t[a.key];if(!l)continue;let r=document.createElement("span");r.className="ssm-value",a.kind&&r.setAttribute("data-kind",a.kind),r.textContent="-",o.appendChild(r),this.#s.push({offset:l.offset,format:C(a,l,t),el:r,prev:-1})}e.appendChild(o),c.appendChild(e)}#i(c,s,t){let e=document.createElement("div");e.className="ssm-bar";let i=t[s.usedKey],o=t[s.peakKey],a=t[s.capacityKey];i?.description&&(e.title=i.description);let l=document.createElement("span");l.className="ssm-bar-label",l.textContent=s.label,e.appendChild(l);let r=document.createElement("div");r.className="ssm-bar-track";let f=document.createElement("div");f.className=`ssm-bar-fill ssm-bar-fill--${s.color}`,r.appendChild(f);let p=document.createElement("div");p.className=`ssm-bar-peak ssm-bar-peak--${s.color}`,r.appendChild(p),e.appendChild(r);let d=document.createElement("span");d.className="ssm-bar-value ssm-value",s.color==="green"?d.setAttribute("data-kind","green"):s.color==="purple"&&d.setAttribute("data-kind","purple"),d.textContent="-",e.appendChild(d),c.appendChild(e),this.#n.push({usedOffset:i?.offset??0,peakOffset:o?.offset??0,capacityOffset:a?.offset??0,fillEl:f,peakEl:p,labelEl:d,prevUsed:-1,prevPeak:-1,prevCap:-1})}#c(){if(!this.#e)return;let c=this.#e.getMetricsArray();for(let s=0;s<this.#s.length;s++){let t=this.#s[s],e=c[t.offset];e!==t.prev&&(t.el.textContent=t.format(e,c),t.prev=e)}for(let s=0;s<this.#n.length;s++){let t=this.#n[s],e=c[t.usedOffset],i=c[t.peakOffset],o=c[t.capacityOffset];if(e!==t.prevUsed||o!==t.prevCap){if(o>0){let a=e/o*100;t.fillEl.style.width=a+"%",t.labelEl.textContent=a.toFixed(1)+"%"}else t.fillEl.style.width="0%",t.labelEl.textContent="N/A";t.prevUsed=e,t.prevCap=o}if(i!==t.prevPeak&&o>0){let a=i/o*100;t.peakEl.style.left=`calc(${a}% - 1px)`,t.prevPeak=i}}}};typeof customElements<"u"&&!customElements.get("supersonic-metrics")&&customElements.define("supersonic-metrics",m);export{m as SupersonicMetrics};
@@ -1 +1 @@
1
- function U(s,t,e){return(e-1-s+t)%e}function B({uint8View:s,dataView:t,bufferStart:e,bufferSize:r,head:n,payload:o,sequence:E,messageMagic:c,headerSize:i,sourceId:S=0,headerScratch:u=null,headerScratchView:A=null}){let l=o.length,T=i+l+3&-4,a=r-n;if(T>a){let _=u||new Uint8Array(i),f=A||new DataView(_.buffer);f.setUint32(0,c,!0),f.setUint32(4,T,!0),f.setUint32(8,E,!0),f.setUint32(12,S,!0);let d=e+n,I=e;if(a>=i){s.set(_,d);let h=a-i;h>0&&s.set(o.subarray(0,h),d+i),s.set(o.subarray(h),I)}else{s.set(_.subarray(0,a),d),s.set(_.subarray(a),I);let h=i-a;s.set(o,I+h)}}else{let _=e+n;t.setUint32(_,c,!0),t.setUint32(_+4,T,!0),t.setUint32(_+8,E,!0),t.setUint32(_+12,S,!0),s.set(o,_+i)}return(n+T)%r}function b(s,t,e=0,r=!1){for(let n=0;n<=e;n++)if(Atomics.compareExchange(s,t,0,1)===0)return!0;if(r){for(let o=0;o<100;o++)if(Atomics.wait(s,t,1,100),Atomics.compareExchange(s,t,0,1)===0)return!0;return console.error("[RingBuffer] Lock acquisition timeout after 10s - possible deadlock"),!1}return!1}function Y(s,t){Atomics.store(s,t,0),Atomics.notify(s,t,1)}function N({atomicView:s,dataView:t,uint8View:e,bufferConstants:r,ringBufferBase:n,controlIndices:o,oscMessage:E,sourceId:c=0,maxSpins:i=0,useWait:S=!1}){let u=E.length,A=r.MESSAGE_HEADER_SIZE+u;if(A>r.IN_BUFFER_SIZE-r.MESSAGE_HEADER_SIZE||!b(s,o.IN_WRITE_LOCK,i,S))return!1;try{let l=Atomics.load(s,o.IN_HEAD),C=Atomics.load(s,o.IN_TAIL),T=A+3&-4;if(U(l,C,r.IN_BUFFER_SIZE)<T)return!1;let _=Atomics.add(s,o.IN_SEQUENCE,1),f=B({uint8View:e,dataView:t,bufferStart:n+r.IN_BUFFER_START,bufferSize:r.IN_BUFFER_SIZE,head:l,payload:E,sequence:_,messageMagic:r.MESSAGE_MAGIC,headerSize:r.MESSAGE_HEADER_SIZE,sourceId:c});return Atomics.load(s,o.IN_HEAD),Atomics.store(s,o.IN_HEAD,f),Atomics.notify(s,o.IN_HEAD,1),!0}finally{Y(s,o.IN_WRITE_LOCK)}}function L(s,t){let e=s+t;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 G(s){return s.length>=8&&s[0]===35&&s[1]===98&&s[2]===117&&s[3]===110&&s[4]===100&&s[5]===108&&s[6]===101&&s[7]===0}function w(s){if(s.length<16)return null;let t=new DataView(s.buffer,s.byteOffset,s.byteLength);return{ntpSeconds:t.getUint32(8,!1),ntpFraction:t.getUint32(12,!1)}}function p(){return(performance.timeOrigin+performance.now())/1e3+2208988800}function D(s,t={}){let{getCurrentNTP:e=p,bypassLookaheadS:r=.5}=t;if(!G(s))return"nonBundle";let n=w(s);if(!n)return"nonBundle";let{ntpSeconds:o,ntpFraction:E}=n;if(o===0&&E<=1)return"immediate";let c=e();if(c===null||c===0)return"immediate";let S=o+E/4294967296-c;return S<0?"late":S<r?"nearFuture":"farFuture"}function O(s){return s!=="farFuture"}var R=class s{#s;#r;#o;#T;#e;#t;#A;#E;#l;#d;#I;#p;#a;#_;#u;#c;#S;#i;#f;#n={messagesSent:0,bytesSent:0,nonBundle:0,immediate:0,nearFuture:0,late:0,bypassed:0};constructor(t,e){if(this.#s=t,this.#e=e.preschedulerPort||null,this.#A=e.bypassLookaheadS??.5,this.#E=e.sourceId??0,this.#l=e.blocking??this.#E!==0,this.#d=e.getCurrentNTP??p,this.#u=1e3,t==="postMessage")this.#r=e.port;else{if(this.#o={sharedBuffer:e.sharedBuffer,ringBufferBase:e.ringBufferBase,bufferConstants:e.bufferConstants,controlIndices:e.controlIndices},this.#N(),e.sharedBuffer&&e.bufferConstants){let r=e.ringBufferBase+e.bufferConstants.METRICS_START;this.#t=new Int32Array(e.sharedBuffer,r,e.bufferConstants.METRICS_SIZE/4)}if(e.sharedBuffer&&e.bufferConstants?.NODE_ID_COUNTER_START!==void 0){let r=e.ringBufferBase+e.bufferConstants.NODE_ID_COUNTER_START;this.#I=new Int32Array(e.sharedBuffer,r,1),this.#C()}}e.nodeIdSource&&(this.#c=e.nodeIdSource,this.#C()),e.nodeIdRange&&(this.#p=e.nodeIdRange.from,this.#a=e.nodeIdRange.to,this.#_=e.nodeIdRange.from),e.nodeIdPort&&(this.#S=e.nodeIdPort,this.#S.onmessage=r=>{r.data.type==="nodeIdRange"&&(this.#i={from:r.data.from,to:r.data.to})},this.#h())}#N(){let t=this.#o.sharedBuffer;this.#T={atomicView:new Int32Array(t),dataView:new DataView(t),uint8View:new Uint8Array(t)}}classify(t){return D(t,{getCurrentNTP:this.#d,bypassLookaheadS:this.#A})}#U(t,e=null){if(this.#s==="sab"&&this.#t){if(Atomics.add(this.#t,24,1),Atomics.add(this.#t,25,t),e){let n={nonBundle:38,immediate:39,nearFuture:40,late:41}[e];n!==void 0&&(Atomics.add(this.#t,n,1),Atomics.add(this.#t,22,1))}}else this.#n.messagesSent++,this.#n.bytesSent+=t,e&&e in this.#n&&(this.#n[e]++,this.#n.bypassed++)}getAndResetMetrics(){let t={...this.#n};return this.#n={messagesSent:0,bytesSent:0,nonBundle:0,immediate:0,nearFuture:0,late:0,bypassed:0},t}getMetrics(){return this.#s==="sab"&&this.#t?{messagesSent:Atomics.load(this.#t,24),bytesSent:Atomics.load(this.#t,25),nonBundle:Atomics.load(this.#t,38),immediate:Atomics.load(this.#t,39),nearFuture:Atomics.load(this.#t,40),late:Atomics.load(this.#t,41),bypassed:Atomics.load(this.#t,22)}:{...this.#n}}#R(t,e=null,r=!0){if(this.#s==="postMessage")return this.#r?(this.#r.postMessage({type:"osc",oscData:t,bypassCategory:e,sourceId:this.#E}),!0):!1;{let n=this.#l,o=N({atomicView:this.#T.atomicView,dataView:this.#T.dataView,uint8View:this.#T.uint8View,bufferConstants:this.#o.bufferConstants,ringBufferBase:this.#o.ringBufferBase,controlIndices:this.#o.controlIndices,oscMessage:t,sourceId:this.#E,maxSpins:n?10:0,useWait:n});return!o&&!n&&r&&this.#e?(this.#t&&Atomics.add(this.#t,45,1),this.#e.postMessage({type:"directDispatch",oscData:t,sourceId:this.#E}),!0):o}}#B(t){return this.#e?(this.#e.postMessage({type:"osc",oscData:t,sourceId:this.#E}),!0):(console.error("[OscChannel] No prescheduler port, sending direct"),this.#R(t))}send(t){let e=this.classify(t);if(O(e)){let r=this.#R(t,e);return r&&this.#U(t.length,e),r}else{let r=this.#B(t);return r&&this.#U(t.length,null),r}}sendDirect(t){return this.#R(t)}sendToPrescheduler(t){return this.#B(t)}nextNodeId(){if(this.#I)return Atomics.add(this.#I,0,1);this.#_>=this.#a&&this.#C();let t=this.#_++;return this.#S&&!this.#i&&this.#a-this.#_<=this.#u>>>1&&this.#h(),t}#C(){if(this.#c){let t=this.#c(this.#u);this.#p=t.from,this.#a=t.to,this.#_=t.from}else this.#i?(this.#p=this.#i.from,this.#a=this.#i.to,this.#_=this.#i.from,this.#i=null,this.#h()):this.#S&&(console.warn("[OscChannel] nextNodeId() range exhausted before async refill arrived. IDs may not be unique. Yield to the event loop between large batches of nextNodeId() calls."),this.#h())}#h(){this.#S&&this.#S.postMessage({type:"requestNodeIdRange"})}set getCurrentNTP(t){this.#d=t}get mode(){return this.#s}get transferable(){let t={mode:this.#s,preschedulerPort:this.#e,bypassLookaheadS:this.#A,sourceId:this.#E,blocking:this.#l};if(this.#s==="postMessage"){let e=this.#u*10,r,n;if(this.#c){let o=this.#c(e);r={from:o.from,to:o.to};let E=new MessageChannel,c=this.#c,i=this.#u;E.port1.onmessage=S=>{if(S.data.type==="requestNodeIdRange"){let u=c(i);E.port1.postMessage({type:"nodeIdRange",from:u.from,to:u.to})}},n=E.port2,this.#f=n}return{...t,port:this.#r,nodeIdRange:r,nodeIdPort:n}}else return{...t,sharedBuffer:this.#o.sharedBuffer,ringBufferBase:this.#o.ringBufferBase,bufferConstants:this.#o.bufferConstants,controlIndices:this.#o.controlIndices}}get transferList(){let t=[];return this.#s==="postMessage"&&this.#r&&t.push(this.#r),this.#e&&t.push(this.#e),this.#f&&(t.push(this.#f),this.#f=null),t}close(){this.#s==="postMessage"&&this.#r&&(this.#r.close(),this.#r=null),this.#e&&(this.#e.close(),this.#e=null)}static createPostMessage(t){return t instanceof MessagePort?new s("postMessage",{port:t}):new s("postMessage",t)}static createSAB(t){let e=t.controlIndices;return e||(e=L(t.ringBufferBase,t.bufferConstants.CONTROL_START)),new s("sab",{sharedBuffer:t.sharedBuffer,ringBufferBase:t.ringBufferBase,bufferConstants:t.bufferConstants,controlIndices:e,preschedulerPort:t.preschedulerPort,bypassLookaheadS:t.bypassLookaheadS,sourceId:t.sourceId,blocking:t.blocking})}static fromTransferable(t){return t.mode==="postMessage"?new s("postMessage",{port:t.port,preschedulerPort:t.preschedulerPort,bypassLookaheadS:t.bypassLookaheadS,sourceId:t.sourceId,blocking:t.blocking,nodeIdRange:t.nodeIdRange,nodeIdPort:t.nodeIdPort}):new s("sab",{sharedBuffer:t.sharedBuffer,ringBufferBase:t.ringBufferBase,bufferConstants:t.bufferConstants,controlIndices:t.controlIndices,preschedulerPort:t.preschedulerPort,bypassLookaheadS:t.bypassLookaheadS,sourceId:t.sourceId,blocking:t.blocking})}};export{R as OscChannel};
1
+ function g(s,t,e){return(e-1-s+t)%e}function Y({uint8View:s,dataView:t,bufferStart:e,bufferSize:r,head:n,payload:o,sequence:E,messageMagic:c,headerSize:i,sourceId:S=0,headerScratch:a=null,headerScratchView:I=null}){let f=o.length,u=i+f+3&-4,T=r-n;if(u>T){let _=a||new Uint8Array(i),A=I||new DataView(_.buffer);A.setUint32(0,c,!0),A.setUint32(4,u,!0),A.setUint32(8,E,!0),A.setUint32(12,S,!0);let d=e+n,l=e;if(T>=i){s.set(_,d);let h=T-i;h>0&&s.set(o.subarray(0,h),d+i),s.set(o.subarray(h),l)}else{s.set(_.subarray(0,T),d),s.set(_.subarray(T),l);let h=i-T;s.set(o,l+h)}}else{let _=e+n;t.setUint32(_,c,!0),t.setUint32(_+4,u,!0),t.setUint32(_+8,E,!0),t.setUint32(_+12,S,!0),s.set(o,_+i)}return(n+u)%r}function b(s,t,e=0,r=!1){for(let n=0;n<=e;n++)if(Atomics.compareExchange(s,t,0,1)===0)return!0;if(r){for(let o=0;o<100;o++)if(Atomics.wait(s,t,1,100),Atomics.compareExchange(s,t,0,1)===0)return!0;return console.error("[RingBuffer] Lock acquisition timeout after 10s - possible deadlock"),!1}return!1}function y(s,t){Atomics.store(s,t,0),Atomics.notify(s,t,1)}function N({atomicView:s,dataView:t,uint8View:e,bufferConstants:r,ringBufferBase:n,controlIndices:o,oscMessage:E,sourceId:c=0,maxSpins:i=0,useWait:S=!1}){let a=E.length,I=r.MESSAGE_HEADER_SIZE+a;if(I>r.IN_BUFFER_SIZE-r.MESSAGE_HEADER_SIZE||!b(s,o.IN_WRITE_LOCK,i,S))return!1;try{let f=Atomics.load(s,o.IN_HEAD),U=Atomics.load(s,o.IN_TAIL),u=I+3&-4;if(g(f,U,r.IN_BUFFER_SIZE)<u)return!1;let _=Atomics.add(s,o.IN_SEQUENCE,1),A=Y({uint8View:e,dataView:t,bufferStart:n+r.IN_BUFFER_START,bufferSize:r.IN_BUFFER_SIZE,head:f,payload:E,sequence:_,messageMagic:r.MESSAGE_MAGIC,headerSize:r.MESSAGE_HEADER_SIZE,sourceId:c});return Atomics.load(s,o.IN_HEAD),Atomics.store(s,o.IN_HEAD,A),Atomics.notify(s,o.IN_HEAD,1),!0}finally{y(s,o.IN_WRITE_LOCK)}}function B(s,t){let e=s+t;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 X(s){return s.length>=8&&s[0]===35&&s[1]===98&&s[2]===117&&s[3]===110&&s[4]===100&&s[5]===108&&s[6]===101&&s[7]===0}var p=.5;function w(s){if(s.length<16)return null;let t=new DataView(s.buffer,s.byteOffset,s.byteLength);return{ntpSeconds:t.getUint32(8,!1),ntpFraction:t.getUint32(12,!1)}}function C(){return(performance.timeOrigin+performance.now())/1e3+2208988800}function O(s,t={}){let{getCurrentNTP:e=C,bypassLookaheadS:r=p}=t;if(!X(s))return"nonBundle";let n=w(s);if(!n)return"nonBundle";let{ntpSeconds:o,ntpFraction:E}=n;if(o===0&&E<=1)return"immediate";let c=e();if(c===null||c===0)return"immediate";let S=o+E/4294967296-c;return S<0?"late":S<r?"nearFuture":"farFuture"}function L(s){return s!=="farFuture"}var K={nonBundle:38,immediate:39,nearFuture:40,late:41},R=class s{#s;#r;#o;#u;#e;#t;#I;#E;#f;#d;#l;#p;#T;#_;#a;#c;#S;#i;#A;#n={messagesSent:0,bytesSent:0,nonBundle:0,immediate:0,nearFuture:0,late:0,bypassed:0};constructor(t,e){if(this.#s=t,this.#e=e.preschedulerPort||null,this.#I=e.bypassLookaheadS??p,this.#E=e.sourceId??0,this.#f=e.blocking??this.#E!==0,this.#d=e.getCurrentNTP??C,this.#a=1e3,t==="postMessage")this.#r=e.port;else{if(this.#o={sharedBuffer:e.sharedBuffer,ringBufferBase:e.ringBufferBase,bufferConstants:e.bufferConstants,controlIndices:e.controlIndices},this.#B(),e.sharedBuffer&&e.bufferConstants){let r=e.ringBufferBase+e.bufferConstants.METRICS_START;this.#t=new Int32Array(e.sharedBuffer,r,e.bufferConstants.METRICS_SIZE/4)}if(e.sharedBuffer&&e.bufferConstants?.NODE_ID_COUNTER_START!==void 0){let r=e.ringBufferBase+e.bufferConstants.NODE_ID_COUNTER_START;this.#l=new Int32Array(e.sharedBuffer,r,1),this.#R()}}e.nodeIdSource&&(this.#c=e.nodeIdSource,this.#R()),e.nodeIdRange&&(this.#p=e.nodeIdRange.from,this.#T=e.nodeIdRange.to,this.#_=e.nodeIdRange.from),e.nodeIdPort&&(this.#S=e.nodeIdPort,this.#S.onmessage=r=>{r.data.type==="nodeIdRange"&&(this.#i={from:r.data.from,to:r.data.to})},this.#h())}#B(){let t=this.#o.sharedBuffer;this.#u={atomicView:new Int32Array(t),dataView:new DataView(t),uint8View:new Uint8Array(t)}}classify(t){return O(t,{getCurrentNTP:this.#d,bypassLookaheadS:this.#I})}#U(t,e=null){if(this.#s==="sab"&&this.#t){if(Atomics.add(this.#t,24,1),Atomics.add(this.#t,25,t),e){let r=K[e];r!==void 0&&(Atomics.add(this.#t,r,1),Atomics.add(this.#t,22,1))}}else this.#n.messagesSent++,this.#n.bytesSent+=t,e&&e in this.#n&&(this.#n[e]++,this.#n.bypassed++)}getAndResetMetrics(){let t={...this.#n};return this.#n={messagesSent:0,bytesSent:0,nonBundle:0,immediate:0,nearFuture:0,late:0,bypassed:0},t}getMetrics(){return this.#s==="sab"&&this.#t?{messagesSent:Atomics.load(this.#t,24),bytesSent:Atomics.load(this.#t,25),nonBundle:Atomics.load(this.#t,38),immediate:Atomics.load(this.#t,39),nearFuture:Atomics.load(this.#t,40),late:Atomics.load(this.#t,41),bypassed:Atomics.load(this.#t,22)}:{...this.#n}}#C(t,e=null,r=!0){if(this.#s==="postMessage")return this.#r?(this.#r.postMessage({type:"osc",oscData:t,bypassCategory:e,sourceId:this.#E}),!0):!1;{let n=this.#f,o=N({atomicView:this.#u.atomicView,dataView:this.#u.dataView,uint8View:this.#u.uint8View,bufferConstants:this.#o.bufferConstants,ringBufferBase:this.#o.ringBufferBase,controlIndices:this.#o.controlIndices,oscMessage:t,sourceId:this.#E,maxSpins:n?10:0,useWait:n});return!o&&!n&&r&&this.#e?(this.#t&&Atomics.add(this.#t,45,1),this.#e.postMessage({type:"directDispatch",oscData:t,sourceId:this.#E}),!0):o}}#N(t){return this.#e?(this.#e.postMessage({type:"osc",oscData:t,sourceId:this.#E}),!0):(console.error("[OscChannel] No prescheduler port, sending direct"),this.#C(t))}send(t){let e=this.classify(t);if(L(e)){let r=this.#C(t,e);return r&&this.#U(t.length,e),r}else{let r=this.#N(t);return r&&this.#U(t.length,null),r}}sendDirect(t){return this.#C(t)}sendToPrescheduler(t){return this.#N(t)}nextNodeId(){if(this.#l)return Atomics.add(this.#l,0,1);this.#_>=this.#T&&this.#R();let t=this.#_++;return this.#S&&!this.#i&&this.#T-this.#_<=this.#a>>>1&&this.#h(),t}#R(){if(this.#c){let t=this.#c(this.#a);this.#p=t.from,this.#T=t.to,this.#_=t.from}else this.#i?(this.#p=this.#i.from,this.#T=this.#i.to,this.#_=this.#i.from,this.#i=null,this.#h()):this.#S&&(console.warn("[OscChannel] nextNodeId() range exhausted before async refill arrived. IDs may not be unique. Yield to the event loop between large batches of nextNodeId() calls."),this.#h())}#h(){this.#S&&this.#S.postMessage({type:"requestNodeIdRange"})}set getCurrentNTP(t){this.#d=t}get mode(){return this.#s}get transferable(){let t={mode:this.#s,preschedulerPort:this.#e,bypassLookaheadS:this.#I,sourceId:this.#E,blocking:this.#f};if(this.#s==="postMessage"){let e=this.#a*10,r,n;if(this.#c){let o=this.#c(e);r={from:o.from,to:o.to};let E=new MessageChannel,c=this.#c,i=this.#a;E.port1.onmessage=S=>{if(S.data.type==="requestNodeIdRange"){let a=c(i);E.port1.postMessage({type:"nodeIdRange",from:a.from,to:a.to})}},n=E.port2,this.#A=n}return{...t,port:this.#r,nodeIdRange:r,nodeIdPort:n}}else return{...t,sharedBuffer:this.#o.sharedBuffer,ringBufferBase:this.#o.ringBufferBase,bufferConstants:this.#o.bufferConstants,controlIndices:this.#o.controlIndices}}get transferList(){let t=[];return this.#s==="postMessage"&&this.#r&&t.push(this.#r),this.#e&&t.push(this.#e),this.#A&&(t.push(this.#A),this.#A=null),t}close(){this.#s==="postMessage"&&this.#r&&(this.#r.close(),this.#r=null),this.#e&&(this.#e.close(),this.#e=null)}static createPostMessage(t){return t instanceof MessagePort?new s("postMessage",{port:t}):new s("postMessage",t)}static createSAB(t){let e=t.controlIndices;return e||(e=B(t.ringBufferBase,t.bufferConstants.CONTROL_START)),new s("sab",{sharedBuffer:t.sharedBuffer,ringBufferBase:t.ringBufferBase,bufferConstants:t.bufferConstants,controlIndices:e,preschedulerPort:t.preschedulerPort,bypassLookaheadS:t.bypassLookaheadS,sourceId:t.sourceId,blocking:t.blocking})}static fromTransferable(t){return t.mode==="postMessage"?new s("postMessage",{port:t.port,preschedulerPort:t.preschedulerPort,bypassLookaheadS:t.bypassLookaheadS,sourceId:t.sourceId,blocking:t.blocking,nodeIdRange:t.nodeIdRange,nodeIdPort:t.nodeIdPort}):new s("sab",{sharedBuffer:t.sharedBuffer,ringBufferBase:t.ringBufferBase,bufferConstants:t.bufferConstants,controlIndices:t.controlIndices,preschedulerPort:t.preschedulerPort,bypassLookaheadS:t.bypassLookaheadS,sourceId:t.sourceId,blocking:t.blocking})}};export{R as OscChannel};
package/dist/osc_fast.js CHANGED
@@ -1 +1 @@
1
- var S=new Uint8Array(2097152),C=new DataView(S.buffer),f=S,l=C,a=new Map,O=1e3,Z=new TextDecoder,W=new TextEncoder,X=2208988800,b=4294967296,_=new Uint8Array([35,98,117,110,100,108,101,0]),z=44,g=105,x=102,U=115,T=98,E=84,k=70,G=104,D=100,P=116,L=117;function y(e,t){let n=e.length+4;n+=t.length+4;for(let r of t)if(r instanceof Uint8Array)n+=4+r.length+3;else if(r instanceof ArrayBuffer)n+=4+r.byteLength+3;else if(typeof r=="string")n+=r.length*3+4;else if(r&&r.type==="string")n+=r.value.length*3+4;else if(r&&r.type==="blob"){let i=r.value,c=i instanceof Uint8Array?i.length:i.byteLength;n+=4+c+3}else r&&r.type==="uuid"?n+=16:n+=8;return n}function M(e){let t=16;for(let n of e)t+=4,Array.isArray(n)?t+=y(n[0],n.slice(1)):n.packets!==void 0?t+=M(n.packets):t+=y(n.address,n.args||[]);return t}function I(e){if(e<=2097152){f=S,l=C;return}f=new Uint8Array(e),l=new DataView(f.buffer)}function j(e,t=[]){let n=y(e,t);I(n);let r=0;r=F(e,r),r=v(t,r);for(let i=0;i<t.length;i++)r=d(t[i],r);return f.subarray(0,r)}function q(e,t){let n=M(t);I(n);let r=0;f.set(_,r),r+=8,r=w(e,r);for(let i=0;i<t.length;i++){let c=t[i],u=r;r+=4;let s=r;Array.isArray(c)?r=h(c[0],c.slice(1),r):c.packets!==void 0?r=N(c.timeTag,c.packets,r):r=h(c.address,c.args||[],r);let o=r-s;l.setUint32(u,o,!1)}return f.subarray(0,r)}function ee(e,t,n=[]){let r=20+y(t,n);I(r);let i=0;f.set(_,i),i+=8,i=w(e,i);let c=i;i+=4;let u=i;i=F(t,i),i=v(n,i);for(let s=0;s<n.length;s++)i=d(n[s],i);return l.setUint32(c,i-u,!1),f.subarray(0,i)}function h(e,t,n){n=F(e,n),n=v(t,n);for(let r=0;r<t.length;r++)n=d(t[r],n);return n}function N(e,t,n){f.set(_,n),n+=8,n=w(e,n);for(let r=0;r<t.length;r++){let i=t[r],c=n;n+=4;let u=n;Array.isArray(i)?n=h(i[0],i.slice(1),n):i.packets!==void 0?n=N(i.timeTag,i.packets,n):n=h(i.address,i.args||[],n),l.setUint32(c,n-u,!1)}return n}function F(e,t){let n=a.get(e);if(n)return f.set(n,t),t+n.length;let r=t;if(t=B(e,t),a.size<O){let i=f.slice(r,t);a.set(e,i)}return t}function B(e,t){let n=!1;for(let r=0;r<e.length;r++)if(e.charCodeAt(r)>=128){n=!0;break}if(n){let r=W.encodeInto(e,f.subarray(t));t+=r.written}else for(let r=0;r<e.length;r++)f[t++]=e.charCodeAt(r);for(f[t++]=0;t&3;)f[t++]=0;return t}function v(e,t){f[t++]=z;for(let n=0;n<e.length;n++){let r=e[n],i=typeof r;if(i==="number")f[t++]=Number.isInteger(r)?g:x;else if(i==="string")f[t++]=U;else if(i==="boolean")f[t++]=r?E:k;else if(r instanceof Uint8Array||r instanceof ArrayBuffer)f[t++]=T;else if(r&&r.type==="int")f[t++]=g;else if(r&&r.type==="float")f[t++]=x;else if(r&&r.type==="string")f[t++]=U;else if(r&&r.type==="blob")f[t++]=T;else if(r&&r.type==="bool")f[t++]=r.value?E:k;else if(r&&r.type==="int64")f[t++]=G;else if(r&&r.type==="double")f[t++]=D;else if(r&&r.type==="timetag")f[t++]=P;else if(r&&r.type==="uuid")f[t++]=L;else throw r==null?new Error(`OSC argument at index ${n} is ${r}`):new Error(`Unknown OSC argument type at index ${n}: ${i}`)}for(f[t++]=0;t&3;)f[t++]=0;return t}function d(e,t){let n=typeof e;if(n==="number")return Number.isInteger(e)?(l.setInt32(t,e,!1),t+4):(l.setFloat32(t,e,!1),t+4);if(n==="string")return B(e,t);if(n==="boolean")return t;if(e instanceof Uint8Array){let r=e.length;for(l.setUint32(t,r,!1),t+=4,f.set(e,t),t+=r;t&3;)f[t++]=0;return t}if(e instanceof ArrayBuffer)return d(new Uint8Array(e),t);if(e&&e.type==="int")return l.setInt32(t,e.value,!1),t+4;if(e&&e.type==="float")return l.setFloat32(t,e.value,!1),t+4;if(e&&e.type==="string")return B(e.value,t);if(e&&e.type==="blob"){let r=e.value instanceof Uint8Array?e.value:new Uint8Array(e.value),i=r.length;for(l.setUint32(t,i,!1),t+=4,f.set(r,t),t+=i;t&3;)f[t++]=0;return t}return e&&e.type==="bool"?t:e&&e.type==="int64"?(l.setBigInt64(t,BigInt(e.value),!1),t+8):e&&e.type==="double"?(l.setFloat64(t,e.value,!1),t+8):e&&e.type==="timetag"?w(e.value,t):e&&e.type==="uuid"?(f.set(e.value,t),t+16):t}function w(e,t){if(e===1||e===null||e===void 0)return l.setUint32(t,0,!1),l.setUint32(t+4,1,!1),t+8;if(Array.isArray(e)){if(e.length!==2)throw new Error(`TimeTag array must have exactly 2 elements [seconds, fraction], got ${e.length}`);return l.setUint32(t,e[0]>>>0,!1),l.setUint32(t+4,e[1]>>>0,!1),t+8}if(typeof e!="number")throw new TypeError(`TimeTag must be a number, array, null, or undefined, got ${typeof e}`);e>1&&e<X&&console.warn(`TimeTag ${e} looks like a Unix timestamp (< NTP_EPOCH_OFFSET). Did you mean to add NTP_EPOCH_OFFSET (2208988800)?`);let n=e>>>0,r=(e-Math.floor(e))*b>>>0;return l.setUint32(t,n,!1),l.setUint32(t+4,r,!1),t+8}function J(e){return e instanceof Uint8Array||(e=new Uint8Array(e)),e[0]===35&&e[1]===98?Q(e):K(e)}function K(e){e instanceof Uint8Array||(e=new Uint8Array(e));let t=new DataView(e.buffer,e.byteOffset,e.byteLength),n=0,[r,i]=A(e,n);if(n=i,n>=e.length||e[n]!==z)return[r];let[c,u]=A(e,n);n=u;let s=[r];for(let o=1;o<c.length;o++)switch(c.charCodeAt(o)){case g:s.push(t.getInt32(n,!1)),n+=4;break;case x:s.push(t.getFloat32(n,!1)),n+=4;break;case U:let[V,R]=A(e,n);s.push(V),n=R;break;case T:let m=t.getUint32(n,!1);n+=4,s.push(e.slice(n,n+m)),n+=m,n=n+3&-4;break;case G:s.push(t.getBigInt64(n,!1)),n+=8;break;case D:s.push(t.getFloat64(n,!1)),n+=8;break;case E:s.push(!0);break;case k:s.push(!1);break;case P:let $=t.getUint32(n,!1),H=t.getUint32(n+4,!1);s.push($+H/b),n+=8;break;case L:s.push({type:"uuid",value:e.slice(n,n+16)}),n+=16;break}return s}function Q(e){e instanceof Uint8Array||(e=new Uint8Array(e));let t=new DataView(e.buffer,e.byteOffset,e.byteLength),n=8,r=t.getUint32(n,!1),i=t.getUint32(n+4,!1),c=r+i/b;n+=8;let u=[];for(;n<e.length;){let s=t.getUint32(n,!1);if(n+=4,s>0&&n+s<=e.length){let o=e.subarray(n,n+s);u.push(J(o))}n+=s}return{timeTag:c,packets:u}}function A(e,t){let n=t;for(;n<e.length&&e[n]!==0;)n++;let r=Z.decode(e.subarray(t,n));return n++,n=n+3&-4,[r,n]}function te(e){return e.slice()}function ne(e,t,n,r){let i=j(e,t);return n.set(i,r),i.length}function re(e,t,n,r){let i=q(e,t);return n.set(i,r),i.length}function ie(){a.clear()}function fe(){return{stringCacheSize:a.size,maxSize:O}}function Y(e){return!e||e.length<8?!1:e[0]===35&&e[1]===98}function se(e){if(!Y(e))return null;let t=new DataView(e.buffer,e.byteOffset,e.byteLength),n=t.getUint32(8,!1),r=t.getUint32(12,!1);return n+r/b}export{X as NTP_EPOCH_OFFSET,b as TWO_POW_32,ie as clearCache,te as copyEncoded,Q as decodeBundle,K as decodeMessage,J as decodePacket,q as encodeBundle,re as encodeBundleIntoBuffer,j as encodeMessage,ne as encodeMessageIntoBuffer,ee as encodeSingleBundle,se as getBundleTimeTag,fe as getCacheStats,Y as isBundle};
1
+ var B=new Uint8Array(2097152),O=new DataView(B.buffer),l=B,c=O,a=new Map,z=1e3,W=new TextDecoder,X=new TextEncoder,U=[null,0],j=2208988800,b=4294967296,F=new Uint8Array([35,98,117,110,100,108,101,0]),G=44,T=105,d=102,g=115,E=98,k=84,_=70,D=104,P=100,L=116,M=117;function y(e,t,n=0){let i=e.length+4;i+=t.length-n+4;for(let r=n;r<t.length;r++){let f=t[r];if(f instanceof Uint8Array)i+=4+f.length+3;else if(f instanceof ArrayBuffer)i+=4+f.byteLength+3;else if(typeof f=="string")i+=f.length*3+4;else if(f&&f.type==="string")i+=f.value.length*3+4;else if(f&&f.type==="blob"){let u=f.value,s=u instanceof Uint8Array?u.length:u.byteLength;i+=4+s+3}else f&&f.type==="uuid"?i+=16:i+=8}return i}function N(e){let t=16;for(let n of e)t+=4,Array.isArray(n)?t+=y(n[0],n,1):n.packets!==void 0?t+=N(n.packets):t+=y(n.address,n.args||[]);return t}function I(e){if(e<=2097152){l=B,c=O;return}l=new Uint8Array(e),c=new DataView(l.buffer)}function p(e,t=[]){let n=y(e,t);I(n);let i=0;i=v(e,i),i=m(t,i);for(let r=0;r<t.length;r++)i=w(t[r],i);return l.subarray(0,i)}function ee(e,t){let n=N(t);I(n);let i=0;l.set(F,i),i+=8,i=A(e,i);for(let r=0;r<t.length;r++){let f=t[r],u=i;i+=4;let s=i;Array.isArray(f)?i=h(f[0],f,i,1):f.packets!==void 0?i=V(f.timeTag,f.packets,i):i=h(f.address,f.args||[],i);let o=i-s;c.setUint32(u,o,!1)}return l.subarray(0,i)}function te(e,t,n=[]){let i=20+y(t,n);I(i);let r=0;l.set(F,r),r+=8,r=A(e,r);let f=r;r+=4;let u=r;r=v(t,r),r=m(n,r);for(let s=0;s<n.length;s++)r=w(n[s],r);return c.setUint32(f,r-u,!1),l.subarray(0,r)}function h(e,t,n,i=0){n=v(e,n),n=m(t,n,i);for(let r=i;r<t.length;r++)n=w(t[r],n);return n}function V(e,t,n){l.set(F,n),n+=8,n=A(e,n);for(let i=0;i<t.length;i++){let r=t[i],f=n;n+=4;let u=n;Array.isArray(r)?n=h(r[0],r,n,1):r.packets!==void 0?n=V(r.timeTag,r.packets,n):n=h(r.address,r.args||[],n),c.setUint32(f,n-u,!1)}return n}function v(e,t){let n=a.get(e);if(n)return l.set(n,t),t+n.length;let i=t;if(t=S(e,t),a.size<z){let r=l.slice(i,t);a.set(e,r)}return t}function S(e,t){let n=!1;for(let i=0;i<e.length;i++)if(e.charCodeAt(i)>=128){n=!0;break}if(n){let i=X.encodeInto(e,l.subarray(t));t+=i.written}else for(let i=0;i<e.length;i++)l[t++]=e.charCodeAt(i);for(l[t++]=0;t&3;)l[t++]=0;return t}function m(e,t,n=0){l[t++]=G;for(let i=n;i<e.length;i++){let r=e[i],f=typeof r;if(f==="number")l[t++]=Number.isInteger(r)?T:d;else if(f==="string")l[t++]=g;else if(f==="boolean")l[t++]=r?k:_;else if(r instanceof Uint8Array||r instanceof ArrayBuffer)l[t++]=E;else if(r&&r.type==="int")l[t++]=T;else if(r&&r.type==="float")l[t++]=d;else if(r&&r.type==="string")l[t++]=g;else if(r&&r.type==="blob")l[t++]=E;else if(r&&r.type==="bool")l[t++]=r.value?k:_;else if(r&&r.type==="int64")l[t++]=D;else if(r&&r.type==="double")l[t++]=P;else if(r&&r.type==="timetag")l[t++]=L;else if(r&&r.type==="uuid")l[t++]=M;else throw r==null?new Error(`OSC argument at index ${i} is ${r}`):new Error(`Unknown OSC argument type at index ${i}: ${f}`)}for(l[t++]=0;t&3;)l[t++]=0;return t}function w(e,t){let n=typeof e;if(n==="number")return Number.isInteger(e)?(c.setInt32(t,e,!1),t+4):(c.setFloat32(t,e,!1),t+4);if(n==="string")return S(e,t);if(n==="boolean")return t;if(e instanceof Uint8Array){let i=e.length;for(c.setUint32(t,i,!1),t+=4,l.set(e,t),t+=i;t&3;)l[t++]=0;return t}if(e instanceof ArrayBuffer)return w(new Uint8Array(e),t);if(e&&e.type==="int")return c.setInt32(t,e.value,!1),t+4;if(e&&e.type==="float")return c.setFloat32(t,e.value,!1),t+4;if(e&&e.type==="string")return S(e.value,t);if(e&&e.type==="blob"){let i=e.value instanceof Uint8Array?e.value:new Uint8Array(e.value),r=i.length;for(c.setUint32(t,r,!1),t+=4,l.set(i,t),t+=r;t&3;)l[t++]=0;return t}return e&&e.type==="bool"?t:e&&e.type==="int64"?(c.setBigInt64(t,BigInt(e.value),!1),t+8):e&&e.type==="double"?(c.setFloat64(t,e.value,!1),t+8):e&&e.type==="timetag"?A(e.value,t):e&&e.type==="uuid"?(l.set(e.value,t),t+16):t}function A(e,t){if(e===1||e===null||e===void 0)return c.setUint32(t,0,!1),c.setUint32(t+4,1,!1),t+8;if(Array.isArray(e)){if(e.length!==2)throw new Error(`TimeTag array must have exactly 2 elements [seconds, fraction], got ${e.length}`);return c.setUint32(t,e[0]>>>0,!1),c.setUint32(t+4,e[1]>>>0,!1),t+8}if(typeof e!="number")throw new TypeError(`TimeTag must be a number, array, null, or undefined, got ${typeof e}`);e>1&&e<j&&console.warn(`TimeTag ${e} looks like a Unix timestamp (< NTP_EPOCH_OFFSET). Did you mean to add NTP_EPOCH_OFFSET (2208988800)?`);let n=e>>>0,i=(e-Math.floor(e))*b>>>0;return c.setUint32(t,n,!1),c.setUint32(t+4,i,!1),t+8}function q(e){return e instanceof Uint8Array||(e=new Uint8Array(e)),e[0]===35&&e[1]===98?K(e):J(e)}function J(e){e instanceof Uint8Array||(e=new Uint8Array(e));let t=new DataView(e.buffer,e.byteOffset,e.byteLength),n=0,[i,r]=x(e,n);if(n=r,n>=e.length||e[n]!==G)return[i];let[f,u]=x(e,n);n=u;let s=[i];for(let o=1;o<f.length;o++)switch(f.charCodeAt(o)){case T:s.push(t.getInt32(n,!1)),n+=4;break;case d:s.push(t.getFloat32(n,!1)),n+=4;break;case g:let[R,$]=x(e,n);s.push(R),n=$;break;case E:let C=t.getUint32(n,!1);n+=4,s.push(e.slice(n,n+C)),n+=C,n=n+3&-4;break;case D:s.push(t.getBigInt64(n,!1)),n+=8;break;case P:s.push(t.getFloat64(n,!1)),n+=8;break;case k:s.push(!0);break;case _:s.push(!1);break;case L:let H=t.getUint32(n,!1),Z=t.getUint32(n+4,!1);s.push(H+Z/b),n+=8;break;case M:s.push({type:"uuid",value:e.slice(n,n+16)}),n+=16;break}return s}function K(e){e instanceof Uint8Array||(e=new Uint8Array(e));let t=new DataView(e.buffer,e.byteOffset,e.byteLength),n=8,i=t.getUint32(n,!1),r=t.getUint32(n+4,!1),f=i+r/b;n+=8;let u=[];for(;n<e.length;){let s=t.getUint32(n,!1);if(n+=4,s>0&&n+s<=e.length){let o=e.subarray(n,n+s);u.push(q(o))}n+=s}return{timeTag:f,packets:u}}function x(e,t){let n=t;for(;n<e.length&&e[n]!==0;)n++;let i=W.decode(e.subarray(t,n));return n++,n=n+3&-4,U[0]=i,U[1]=n,U}function ne(e){return e.slice()}function ie(){a.clear()}function re(){return{stringCacheSize:a.size,maxSize:z}}function Q(e){return!e||e.length<8?!1:e[0]===35&&e[1]===98}function fe(e){if(!Q(e))return null;let t=new DataView(e.buffer,e.byteOffset,e.byteLength),n=t.getUint32(8,!1),i=t.getUint32(12,!1);return n+i/b}export{j as NTP_EPOCH_OFFSET,b as TWO_POW_32,ie as clearCache,ne as copyEncoded,K as decodeBundle,J as decodeMessage,q as decodePacket,ee as encodeBundle,p as encodeMessage,te as encodeSingleBundle,fe as getBundleTimeTag,re as getCacheStats,Q as isBundle};