@libraz/libsonare 1.3.3 → 1.4.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/dist/index.d.ts +1 -1
- package/dist/index.js +168 -3
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt-module.js +2 -2
- package/dist/sonare-rt.js +2 -2
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.js +2 -2
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +579 -154
- package/dist/worklet.js +622 -110
- package/dist/worklet.js.map +1 -1
- package/package.json +1 -1
- package/src/codes.ts +6 -1
- package/src/effects_mastering.ts +103 -1
- package/src/feature_music.ts +18 -4
- package/src/feature_spectral.ts +7 -1
- package/src/index.ts +14 -0
- package/src/mixer.ts +9 -0
- package/src/project.ts +74 -0
- package/src/public_types.ts +52 -0
- package/src/realtime_engine.ts +141 -2
- package/src/sonare.js.d.ts +81 -0
- package/src/stream_types.ts +7 -0
- package/src/validation.ts +7 -0
- package/src/worklet/audio_types.ts +2 -0
- package/src/worklet/guards.ts +146 -0
- package/src/worklet/messages.ts +461 -0
- package/src/worklet/protocol.ts +767 -0
- package/src/worklet.ts +541 -1106
package/dist/worklet.js
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
|
+
// src/codes.ts
|
|
2
|
+
function automationCurveCode(curve) {
|
|
3
|
+
switch (curve) {
|
|
4
|
+
case "linear":
|
|
5
|
+
return 0;
|
|
6
|
+
case "exponential":
|
|
7
|
+
return 1;
|
|
8
|
+
case "hold":
|
|
9
|
+
return 2;
|
|
10
|
+
case "s-curve":
|
|
11
|
+
return 3;
|
|
12
|
+
default:
|
|
13
|
+
throw new Error(`Invalid automation curve: ${curve}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function panLawCode(panLaw) {
|
|
17
|
+
if (typeof panLaw === "number") {
|
|
18
|
+
return panLaw;
|
|
19
|
+
}
|
|
20
|
+
switch (panLaw) {
|
|
21
|
+
case "const4.5dB":
|
|
22
|
+
return 1;
|
|
23
|
+
case "const6dB":
|
|
24
|
+
return 2;
|
|
25
|
+
case "linear0dB":
|
|
26
|
+
return 3;
|
|
27
|
+
default:
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function panModeCode(panMode) {
|
|
32
|
+
if (typeof panMode === "number") {
|
|
33
|
+
return panMode;
|
|
34
|
+
}
|
|
35
|
+
switch (panMode) {
|
|
36
|
+
case "stereoPan":
|
|
37
|
+
case "stereo-pan":
|
|
38
|
+
return 1;
|
|
39
|
+
case "dualPan":
|
|
40
|
+
case "dual-pan":
|
|
41
|
+
return 2;
|
|
42
|
+
default:
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function meterTapCode(tap) {
|
|
47
|
+
return tap === "preFader" || tap === 0 ? 0 : 1;
|
|
48
|
+
}
|
|
49
|
+
function sendTimingCode(timing) {
|
|
50
|
+
if (typeof timing === "number") {
|
|
51
|
+
return timing;
|
|
52
|
+
}
|
|
53
|
+
return timing === "preFader" ? 1 : 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
1
56
|
// src/errors.ts
|
|
2
57
|
var SonareError = class extends Error {
|
|
3
58
|
constructor(code, codeName, message) {
|
|
@@ -150,58 +205,6 @@ function getSonareModule() {
|
|
|
150
205
|
return wrappedModule;
|
|
151
206
|
}
|
|
152
207
|
|
|
153
|
-
// src/codes.ts
|
|
154
|
-
function automationCurveCode(curve) {
|
|
155
|
-
switch (curve) {
|
|
156
|
-
case "linear":
|
|
157
|
-
return 0;
|
|
158
|
-
case "exponential":
|
|
159
|
-
return 1;
|
|
160
|
-
case "hold":
|
|
161
|
-
return 2;
|
|
162
|
-
case "s-curve":
|
|
163
|
-
return 3;
|
|
164
|
-
default:
|
|
165
|
-
throw new Error(`Invalid automation curve: ${curve}`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function panLawCode(panLaw) {
|
|
169
|
-
if (typeof panLaw === "number") {
|
|
170
|
-
return panLaw;
|
|
171
|
-
}
|
|
172
|
-
switch (panLaw) {
|
|
173
|
-
case "const4.5dB":
|
|
174
|
-
return 1;
|
|
175
|
-
case "const6dB":
|
|
176
|
-
return 2;
|
|
177
|
-
case "linear0dB":
|
|
178
|
-
return 3;
|
|
179
|
-
default:
|
|
180
|
-
return 0;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function panModeCode(panMode) {
|
|
184
|
-
if (typeof panMode === "number") {
|
|
185
|
-
return panMode;
|
|
186
|
-
}
|
|
187
|
-
switch (panMode) {
|
|
188
|
-
case "stereoPan":
|
|
189
|
-
case "stereo-pan":
|
|
190
|
-
return 1;
|
|
191
|
-
case "dualPan":
|
|
192
|
-
case "dual-pan":
|
|
193
|
-
return 2;
|
|
194
|
-
default:
|
|
195
|
-
return 0;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
function meterTapCode(tap) {
|
|
199
|
-
return tap === "preFader" || tap === 0 ? 0 : 1;
|
|
200
|
-
}
|
|
201
|
-
function sendTimingCode(timing) {
|
|
202
|
-
return timing === "preFader" || timing === 0 ? 0 : 1;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
208
|
// src/mixer.ts
|
|
206
209
|
var Mixer = class _Mixer {
|
|
207
210
|
constructor(mixer) {
|
|
@@ -452,6 +455,13 @@ var Mixer = class _Mixer {
|
|
|
452
455
|
setDualPan(stripIndex, leftPan, rightPan) {
|
|
453
456
|
this.mixer.setDualPan(stripIndex, leftPan, rightPan);
|
|
454
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Set the strip's surround pan position, used when it feeds a >2-channel bus.
|
|
460
|
+
* Stored on the scene; inert until the surround DSP path applies it.
|
|
461
|
+
*/
|
|
462
|
+
setSurroundPan(stripIndex, pan) {
|
|
463
|
+
this.mixer.setSurroundPan(stripIndex, pan);
|
|
464
|
+
}
|
|
455
465
|
/**
|
|
456
466
|
* Add a send to a strip after construction.
|
|
457
467
|
*
|
|
@@ -956,6 +966,15 @@ var RealtimeEngine = class {
|
|
|
956
966
|
seekSample(timelineSample, renderFrame = -1) {
|
|
957
967
|
this.native.seekSample(timelineSample, renderFrame);
|
|
958
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Snaps every in-flight parameter ramp (engine-level smoothed params, mixer
|
|
971
|
+
* lane fader/pan/gate, bus gains) to its target value. Offline renders call
|
|
972
|
+
* this after a priming process() block so the first audible block renders at
|
|
973
|
+
* settled values instead of ramping in from defaults.
|
|
974
|
+
*/
|
|
975
|
+
settleParameters() {
|
|
976
|
+
this.native.settleParameters();
|
|
977
|
+
}
|
|
959
978
|
seekPpq(ppq, renderFrame = -1) {
|
|
960
979
|
this.native.seekPpq(ppq, renderFrame);
|
|
961
980
|
}
|
|
@@ -1044,9 +1063,31 @@ var RealtimeEngine = class {
|
|
|
1044
1063
|
}
|
|
1045
1064
|
setTrackLanes(lanes) {
|
|
1046
1065
|
this.native.setTrackLanes(
|
|
1047
|
-
lanes.map((lane) =>
|
|
1066
|
+
lanes.map((lane) => {
|
|
1067
|
+
if (typeof lane === "number") {
|
|
1068
|
+
return { trackId: lane };
|
|
1069
|
+
}
|
|
1070
|
+
if (!lane.sends) {
|
|
1071
|
+
return lane;
|
|
1072
|
+
}
|
|
1073
|
+
return {
|
|
1074
|
+
...lane,
|
|
1075
|
+
sends: lane.sends.map((send) => ({
|
|
1076
|
+
...send,
|
|
1077
|
+
// Post-fader (0) is the default for an omitted sendTiming.
|
|
1078
|
+
sendTiming: send.sendTiming === void 0 ? 0 : sendTimingCode(send.sendTiming)
|
|
1079
|
+
}))
|
|
1080
|
+
};
|
|
1081
|
+
})
|
|
1048
1082
|
);
|
|
1049
1083
|
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Keys one insert of a lane strip from another lane's post-strip audio
|
|
1086
|
+
* (ducking/sidechainRouter inserts). sourceTrackId 0 removes the binding.
|
|
1087
|
+
*/
|
|
1088
|
+
setLaneSidechain(trackId, insertIndex, sourceTrackId) {
|
|
1089
|
+
this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
1090
|
+
}
|
|
1050
1091
|
setTrackBuses(buses) {
|
|
1051
1092
|
this.native.setTrackBuses(buses);
|
|
1052
1093
|
}
|
|
@@ -1102,6 +1143,43 @@ var RealtimeEngine = class {
|
|
|
1102
1143
|
setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass = false) {
|
|
1103
1144
|
this.native.setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass);
|
|
1104
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Changes one track-strip insert parameter in realtime, addressed by the
|
|
1148
|
+
* processor's JSON-key parameter name (see {@link masteringInsertParamInfo}).
|
|
1149
|
+
* Applied at the next block head via the engine command queue; safe during
|
|
1150
|
+
* playback. Throws if the track, insert, or name is unknown, the param is not
|
|
1151
|
+
* realtime-safe, or the command queue is full.
|
|
1152
|
+
*/
|
|
1153
|
+
setTrackStripInsertParamByName(trackId, insertIndex, paramName, value) {
|
|
1154
|
+
this.native.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
|
|
1155
|
+
}
|
|
1156
|
+
/** Master-strip counterpart of {@link setTrackStripInsertParamByName}. */
|
|
1157
|
+
setMasterStripInsertParamByName(insertIndex, paramName, value) {
|
|
1158
|
+
this.native.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
1159
|
+
}
|
|
1160
|
+
/** Sets a track lane strip's pan position in realtime (glitch-free). */
|
|
1161
|
+
setTrackStripPan(trackId, pan) {
|
|
1162
|
+
this.native.setTrackStripPan(trackId, pan);
|
|
1163
|
+
}
|
|
1164
|
+
/** Sets a track lane strip's pan law in realtime. */
|
|
1165
|
+
setTrackStripPanLaw(trackId, panLaw) {
|
|
1166
|
+
this.native.setTrackStripPanLaw(trackId, panLawCode(panLaw));
|
|
1167
|
+
}
|
|
1168
|
+
/** Sets a track lane strip's pan mode in realtime. */
|
|
1169
|
+
setTrackStripPanMode(trackId, panMode) {
|
|
1170
|
+
this.native.setTrackStripPanMode(trackId, panModeCode(panMode));
|
|
1171
|
+
}
|
|
1172
|
+
/** Sets a track lane strip's dual-pan left/right positions in realtime. */
|
|
1173
|
+
setTrackStripDualPan(trackId, leftPan, rightPan) {
|
|
1174
|
+
this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Sets a track lane strip's inter-channel alignment delay (whole samples).
|
|
1178
|
+
* Adjusts strip latency, so PDC and reported graph latency are refreshed.
|
|
1179
|
+
*/
|
|
1180
|
+
setTrackStripChannelDelaySamples(trackId, delaySamples) {
|
|
1181
|
+
this.native.setTrackStripChannelDelaySamples(trackId, delaySamples);
|
|
1182
|
+
}
|
|
1105
1183
|
createClipPageProvider(numChannels, numSamples, pageFrames) {
|
|
1106
1184
|
const id = this.native.createClipPageProvider(numChannels, numSamples, pageFrames);
|
|
1107
1185
|
return new ClipPageProvider(this, id);
|
|
@@ -1191,6 +1269,30 @@ var RealtimeEngine = class {
|
|
|
1191
1269
|
drainMeterTelemetry(maxRecords = 1024) {
|
|
1192
1270
|
return this.native.drainMeterTelemetry(maxRecords);
|
|
1193
1271
|
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Drains pending meter telemetry as per-plane (wide) records for a surround
|
|
1274
|
+
* target. Use this for a surround mix target; {@link drainMeterTelemetry}
|
|
1275
|
+
* stays the stereo fast path. The two share one queue — call only one per
|
|
1276
|
+
* target. The live AudioWorklet path owns the queue via the stereo drain, so
|
|
1277
|
+
* this wide drain is for an offline (non-worklet) engine instance; per-plane
|
|
1278
|
+
* surround meters are not delivered over the live worklet meter ring.
|
|
1279
|
+
*/
|
|
1280
|
+
drainMeterTelemetryWide(maxRecords = 1024) {
|
|
1281
|
+
return this.native.drainMeterTelemetryWide(maxRecords);
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Enables per-target spectrum + vectorscope capture. @param intervalFrames is
|
|
1285
|
+
* the minimum render-frame gap between snapshots (0 disables). @param bandCount
|
|
1286
|
+
* is the FFT band resolution (1..64); changing it re-prepares the tap. Returns
|
|
1287
|
+
* the band count actually applied.
|
|
1288
|
+
*/
|
|
1289
|
+
configureScopeTelemetry(intervalFrames, bandCount) {
|
|
1290
|
+
return this.native.configureScopeTelemetry(intervalFrames, bandCount);
|
|
1291
|
+
}
|
|
1292
|
+
/** Drains pending spectrum + vectorscope snapshots (per mix target). */
|
|
1293
|
+
drainScopeTelemetry(maxRecords = 1024) {
|
|
1294
|
+
return this.native.drainScopeTelemetry(maxRecords);
|
|
1295
|
+
}
|
|
1194
1296
|
destroy() {
|
|
1195
1297
|
this.native.delete();
|
|
1196
1298
|
}
|
|
@@ -1248,7 +1350,7 @@ function isInitialized() {
|
|
|
1248
1350
|
return module !== null;
|
|
1249
1351
|
}
|
|
1250
1352
|
|
|
1251
|
-
// src/worklet.ts
|
|
1353
|
+
// src/worklet/protocol.ts
|
|
1252
1354
|
var ENGINE_MIXER_TARGET_BASE = 1297612800;
|
|
1253
1355
|
var ENGINE_MIXER_PARAM_FADER_DB = 1;
|
|
1254
1356
|
var ENGINE_MIXER_PARAM_PAN = 2;
|
|
@@ -1264,6 +1366,8 @@ function engineMixerMasterTarget(paramKind) {
|
|
|
1264
1366
|
var SONARE_METER_RING_HEADER_INTS = 4;
|
|
1265
1367
|
var SONARE_METER_RING_RECORD_FLOATS = 14;
|
|
1266
1368
|
var SONARE_SPECTRUM_RING_HEADER_INTS = 5;
|
|
1369
|
+
var SONARE_SCOPE_RING_HEADER_INTS = 6;
|
|
1370
|
+
var SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS = 5;
|
|
1267
1371
|
var SONARE_FRAME_LANE_BASE = 16777216;
|
|
1268
1372
|
function encodeFrameLo(frame) {
|
|
1269
1373
|
const f = Math.max(0, Math.floor(frame));
|
|
@@ -1322,63 +1426,12 @@ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2)
|
|
|
1322
1426
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["SmoothedParameterCapacity"] = 13] = "SmoothedParameterCapacity";
|
|
1323
1427
|
return SonareEngineTelemetryError2;
|
|
1324
1428
|
})(SonareEngineTelemetryError || {});
|
|
1325
|
-
var DEFAULT_METRONOME_CONFIG = {
|
|
1326
|
-
beatGain: 0.35,
|
|
1327
|
-
accentGain: 0.7,
|
|
1328
|
-
clickSamples: 96
|
|
1329
|
-
};
|
|
1330
|
-
function resolveMetronomeConfig(config) {
|
|
1331
|
-
return {
|
|
1332
|
-
beatGain: config.beatGain ?? DEFAULT_METRONOME_CONFIG.beatGain,
|
|
1333
|
-
accentGain: config.accentGain ?? DEFAULT_METRONOME_CONFIG.accentGain,
|
|
1334
|
-
clickSamples: config.clickSamples ?? DEFAULT_METRONOME_CONFIG.clickSamples
|
|
1335
|
-
};
|
|
1336
|
-
}
|
|
1337
1429
|
function toDb(value) {
|
|
1338
1430
|
return value > 0 ? 20 * Math.log10(value) : Number.NEGATIVE_INFINITY;
|
|
1339
1431
|
}
|
|
1340
1432
|
function isRecord(value) {
|
|
1341
1433
|
return typeof value === "object" && value !== null;
|
|
1342
1434
|
}
|
|
1343
|
-
function isWorkletMessage(value) {
|
|
1344
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1345
|
-
return false;
|
|
1346
|
-
}
|
|
1347
|
-
return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
|
|
1348
|
-
}
|
|
1349
|
-
function isEngineCommandRecord(value) {
|
|
1350
|
-
return isRecord(value) && typeof value.type === "number";
|
|
1351
|
-
}
|
|
1352
|
-
function isEngineSyncMessage(value) {
|
|
1353
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1354
|
-
return false;
|
|
1355
|
-
}
|
|
1356
|
-
return value.type === "syncClips" || value.type === "syncClipsDelta" || value.type === "syncMidiClips" || value.type === "syncMarkers" || value.type === "syncMetronome" || value.type === "syncAutomation" || value.type === "syncTempo" || value.type === "syncMixer" || value.type === "syncCapture" || value.type === "syncTrackStripEqBand" || value.type === "syncMasterStripEqBand" || value.type === "syncTrackStripInsertBypassed" || value.type === "syncMasterStripInsertBypassed" || value.type === "syncBuiltinInstrument" || value.type === "syncSynthInstrument" || value.type === "syncSf2Instrument" || value.type === "syncLoadSoundFont" || value.type === "syncMidiNoteOn" || value.type === "syncMidiNoteOff" || value.type === "syncMidiCc" || value.type === "syncMidiPanic";
|
|
1357
|
-
}
|
|
1358
|
-
function isEngineCaptureRequestMessage(value) {
|
|
1359
|
-
return isRecord(value) && value.type === "captureRequest" && typeof value.requestId === "number" && (value.op === "status" || value.op === "read" || value.op === "reset");
|
|
1360
|
-
}
|
|
1361
|
-
function isEngineCaptureResponseMessage(value) {
|
|
1362
|
-
return isRecord(value) && value.type === "captureResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
1363
|
-
}
|
|
1364
|
-
function isEngineTransportRequestMessage(value) {
|
|
1365
|
-
return isRecord(value) && value.type === "transportRequest" && typeof value.requestId === "number" && value.op === "state";
|
|
1366
|
-
}
|
|
1367
|
-
function isEngineTransportResponseMessage(value) {
|
|
1368
|
-
return isRecord(value) && value.type === "transportResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
1369
|
-
}
|
|
1370
|
-
function isRealtimeVoiceChangerMessage(value) {
|
|
1371
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1372
|
-
return false;
|
|
1373
|
-
}
|
|
1374
|
-
return value.type === "setConfig" || value.type === "reset" || value.type === "destroy";
|
|
1375
|
-
}
|
|
1376
|
-
function isEngineTelemetryRecord(value) {
|
|
1377
|
-
return isRecord(value) && typeof value.type === "number" && typeof value.error === "number" && typeof value.renderFrame === "number" && typeof value.timelineSample === "number" && typeof value.audibleTimelineSample === "number" && typeof value.graphLatencySamplesQ8 === "number" && typeof value.value === "number";
|
|
1378
|
-
}
|
|
1379
|
-
function isMeterSnapshot(value) {
|
|
1380
|
-
return isRecord(value) && value.type === "meter" && typeof value.frame === "number" && typeof value.peakDbL === "number" && typeof value.peakDbR === "number" && typeof value.rmsDbL === "number" && typeof value.rmsDbR === "number" && typeof value.correlation === "number" && (typeof value.targetId === "number" || value.targetId === void 0);
|
|
1381
|
-
}
|
|
1382
1435
|
function sonareMeterRingBufferByteLength(capacity) {
|
|
1383
1436
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1384
1437
|
return SONARE_METER_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_METER_RING_RECORD_FLOATS * Float32Array.BYTES_PER_ELEMENT;
|
|
@@ -1464,6 +1517,102 @@ function readSonareSpectrumRingBuffer(ring, readIndex = 0) {
|
|
|
1464
1517
|
}
|
|
1465
1518
|
return { nextReadIndex: writeIndex, spectra };
|
|
1466
1519
|
}
|
|
1520
|
+
function sonareScopeRingRecordFloats(bands, maxPoints) {
|
|
1521
|
+
return SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS + bands + 2 * maxPoints;
|
|
1522
|
+
}
|
|
1523
|
+
function sonareScopeRingBufferByteLength(capacity, bands = 48, maxPoints = 32) {
|
|
1524
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1525
|
+
const clampedBands = Math.max(1, Math.floor(bands));
|
|
1526
|
+
const clampedPoints = Math.max(0, Math.floor(maxPoints));
|
|
1527
|
+
return SONARE_SCOPE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * sonareScopeRingRecordFloats(clampedBands, clampedPoints) * Float32Array.BYTES_PER_ELEMENT;
|
|
1528
|
+
}
|
|
1529
|
+
function createSonareScopeRingBuffer(capacity = 64, bands = 48, maxPoints = 32) {
|
|
1530
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1531
|
+
const clampedBands = Math.max(1, Math.floor(bands));
|
|
1532
|
+
const clampedPoints = Math.max(0, Math.floor(maxPoints));
|
|
1533
|
+
const sharedBuffer = new SharedArrayBuffer(
|
|
1534
|
+
sonareScopeRingBufferByteLength(clampedCapacity, clampedBands, clampedPoints)
|
|
1535
|
+
);
|
|
1536
|
+
const ring = scopeRingFromSharedBuffer(
|
|
1537
|
+
sharedBuffer,
|
|
1538
|
+
clampedCapacity,
|
|
1539
|
+
clampedBands,
|
|
1540
|
+
clampedPoints
|
|
1541
|
+
);
|
|
1542
|
+
Atomics.store(ring.header, 0, 0);
|
|
1543
|
+
Atomics.store(ring.header, 1, clampedCapacity);
|
|
1544
|
+
Atomics.store(ring.header, 2, ring.recordFloats);
|
|
1545
|
+
Atomics.store(ring.header, 3, clampedBands);
|
|
1546
|
+
Atomics.store(ring.header, 4, clampedPoints);
|
|
1547
|
+
Atomics.store(ring.header, 5, 0);
|
|
1548
|
+
return {
|
|
1549
|
+
sharedBuffer,
|
|
1550
|
+
header: ring.header,
|
|
1551
|
+
records: ring.records,
|
|
1552
|
+
capacity: ring.capacity,
|
|
1553
|
+
bands: ring.bands,
|
|
1554
|
+
maxPoints: ring.maxPoints
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
function readSonareScopeRingBuffer(ring, readIndex = 0) {
|
|
1558
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
1559
|
+
const bands = Atomics.load(ring.header, 3) || ring.bands;
|
|
1560
|
+
const maxPoints = Atomics.load(ring.header, 4);
|
|
1561
|
+
const recordFloats = Atomics.load(ring.header, 2) || sonareScopeRingRecordFloats(bands, maxPoints);
|
|
1562
|
+
const nextReadIndex = Math.max(0, Math.min(readIndex, writeIndex));
|
|
1563
|
+
const firstReadable = Math.max(nextReadIndex, writeIndex - ring.capacity);
|
|
1564
|
+
const scopes = [];
|
|
1565
|
+
for (let index = firstReadable; index < writeIndex; index++) {
|
|
1566
|
+
const offset = index % ring.capacity * recordFloats;
|
|
1567
|
+
const bandCount = Math.min(bands, Math.max(0, ring.records[offset + 3]));
|
|
1568
|
+
const pointCount = Math.min(maxPoints, Math.max(0, ring.records[offset + 4]));
|
|
1569
|
+
const bandsView = new Float32Array(bandCount);
|
|
1570
|
+
bandsView.set(
|
|
1571
|
+
ring.records.subarray(
|
|
1572
|
+
offset + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS,
|
|
1573
|
+
offset + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS + bandCount
|
|
1574
|
+
)
|
|
1575
|
+
);
|
|
1576
|
+
const pointsBase = offset + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS + bands;
|
|
1577
|
+
const pointsView = new Float32Array(pointCount * 2);
|
|
1578
|
+
pointsView.set(ring.records.subarray(pointsBase, pointsBase + pointCount * 2));
|
|
1579
|
+
scopes.push({
|
|
1580
|
+
type: "scope",
|
|
1581
|
+
frame: decodeFrame(ring.records[offset], ring.records[offset + 1]),
|
|
1582
|
+
targetId: ring.records[offset + 2],
|
|
1583
|
+
bands: bandsView,
|
|
1584
|
+
points: pointsView
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
return { nextReadIndex: writeIndex, scopes };
|
|
1588
|
+
}
|
|
1589
|
+
function scopeRingFromSharedBuffer(sharedBuffer, fallbackCapacity, fallbackBands, fallbackMaxPoints) {
|
|
1590
|
+
const headerBytes = SONARE_SCOPE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT;
|
|
1591
|
+
const header = new Int32Array(sharedBuffer, 0, SONARE_SCOPE_RING_HEADER_INTS);
|
|
1592
|
+
const existingCapacity = Atomics.load(header, 1);
|
|
1593
|
+
const existingBands = Atomics.load(header, 3);
|
|
1594
|
+
const existingMaxPoints = Atomics.load(header, 4);
|
|
1595
|
+
const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
|
|
1596
|
+
const bands = Math.max(1, Math.floor(existingBands || fallbackBands || 48));
|
|
1597
|
+
const maxPoints = Math.max(0, Math.floor(existingMaxPoints || (fallbackMaxPoints ?? 32)));
|
|
1598
|
+
const recordFloats = sonareScopeRingRecordFloats(bands, maxPoints);
|
|
1599
|
+
const minBytes = sonareScopeRingBufferByteLength(capacity, bands, maxPoints);
|
|
1600
|
+
if (sharedBuffer.byteLength < minBytes) {
|
|
1601
|
+
throw new Error("scopeSharedBuffer is too small for the requested ring capacity.");
|
|
1602
|
+
}
|
|
1603
|
+
Atomics.store(header, 1, capacity);
|
|
1604
|
+
Atomics.store(header, 2, recordFloats);
|
|
1605
|
+
Atomics.store(header, 3, bands);
|
|
1606
|
+
Atomics.store(header, 4, maxPoints);
|
|
1607
|
+
return {
|
|
1608
|
+
header,
|
|
1609
|
+
records: new Float32Array(sharedBuffer, headerBytes, capacity * recordFloats),
|
|
1610
|
+
capacity,
|
|
1611
|
+
bands,
|
|
1612
|
+
maxPoints,
|
|
1613
|
+
recordFloats
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1467
1616
|
function sonareEngineCommandRingBufferByteLength(capacity) {
|
|
1468
1617
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1469
1618
|
return SONARE_ENGINE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_ENGINE_COMMAND_RECORD_BYTES;
|
|
@@ -1693,6 +1842,63 @@ function meterFromEngine(meter) {
|
|
|
1693
1842
|
function magnitudeToDb(value) {
|
|
1694
1843
|
return value > 1e-12 ? 20 * Math.log10(value) : -120;
|
|
1695
1844
|
}
|
|
1845
|
+
|
|
1846
|
+
// src/worklet/guards.ts
|
|
1847
|
+
function isWorkletMessage(value) {
|
|
1848
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1849
|
+
return false;
|
|
1850
|
+
}
|
|
1851
|
+
return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
|
|
1852
|
+
}
|
|
1853
|
+
function isEngineCommandRecord(value) {
|
|
1854
|
+
return isRecord(value) && typeof value.type === "number";
|
|
1855
|
+
}
|
|
1856
|
+
function isEngineSyncMessage(value) {
|
|
1857
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1858
|
+
return false;
|
|
1859
|
+
}
|
|
1860
|
+
return value.type === "syncClips" || value.type === "syncClipsDelta" || value.type === "syncMidiClips" || value.type === "syncMarkers" || value.type === "syncMetronome" || value.type === "syncAutomation" || value.type === "syncTempo" || value.type === "syncMixer" || value.type === "syncCapture" || value.type === "syncTrackStripEqBand" || value.type === "syncMasterStripEqBand" || value.type === "syncTrackStripInsertBypassed" || value.type === "syncMasterStripInsertBypassed" || value.type === "syncTrackStripInsertParamByName" || value.type === "syncMasterStripInsertParamByName" || value.type === "syncTrackStripPan" || value.type === "syncTrackStripPanLaw" || value.type === "syncTrackStripPanMode" || value.type === "syncTrackStripDualPan" || value.type === "syncTrackStripChannelDelaySamples" || value.type === "syncBuiltinInstrument" || value.type === "syncSynthInstrument" || value.type === "syncSf2Instrument" || value.type === "syncLoadSoundFont" || value.type === "syncMidiNoteOn" || value.type === "syncMidiNoteOff" || value.type === "syncMidiCc" || value.type === "syncMidiPanic";
|
|
1861
|
+
}
|
|
1862
|
+
function isEngineCaptureRequestMessage(value) {
|
|
1863
|
+
return isRecord(value) && value.type === "captureRequest" && typeof value.requestId === "number" && (value.op === "status" || value.op === "read" || value.op === "reset");
|
|
1864
|
+
}
|
|
1865
|
+
function isEngineCaptureResponseMessage(value) {
|
|
1866
|
+
return isRecord(value) && value.type === "captureResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
1867
|
+
}
|
|
1868
|
+
function isEngineTransportRequestMessage(value) {
|
|
1869
|
+
return isRecord(value) && value.type === "transportRequest" && typeof value.requestId === "number" && value.op === "state";
|
|
1870
|
+
}
|
|
1871
|
+
function isEngineTransportResponseMessage(value) {
|
|
1872
|
+
return isRecord(value) && value.type === "transportResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
1873
|
+
}
|
|
1874
|
+
function isRealtimeVoiceChangerMessage(value) {
|
|
1875
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
return value.type === "setConfig" || value.type === "reset" || value.type === "destroy";
|
|
1879
|
+
}
|
|
1880
|
+
function isEngineTelemetryRecord(value) {
|
|
1881
|
+
return isRecord(value) && typeof value.type === "number" && typeof value.error === "number" && typeof value.renderFrame === "number" && typeof value.timelineSample === "number" && typeof value.audibleTimelineSample === "number" && typeof value.graphLatencySamplesQ8 === "number" && typeof value.value === "number";
|
|
1882
|
+
}
|
|
1883
|
+
function isMeterSnapshot(value) {
|
|
1884
|
+
return isRecord(value) && value.type === "meter" && typeof value.frame === "number" && typeof value.peakDbL === "number" && typeof value.peakDbR === "number" && typeof value.rmsDbL === "number" && typeof value.rmsDbR === "number" && typeof value.correlation === "number" && (typeof value.targetId === "number" || value.targetId === void 0);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// src/worklet/messages.ts
|
|
1888
|
+
var DEFAULT_METRONOME_CONFIG = {
|
|
1889
|
+
beatGain: 0.35,
|
|
1890
|
+
accentGain: 0.7,
|
|
1891
|
+
clickSamples: 96
|
|
1892
|
+
};
|
|
1893
|
+
function resolveMetronomeConfig(config) {
|
|
1894
|
+
return {
|
|
1895
|
+
beatGain: config.beatGain ?? DEFAULT_METRONOME_CONFIG.beatGain,
|
|
1896
|
+
accentGain: config.accentGain ?? DEFAULT_METRONOME_CONFIG.accentGain,
|
|
1897
|
+
clickSamples: config.clickSamples ?? DEFAULT_METRONOME_CONFIG.clickSamples
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
// src/worklet.ts
|
|
1696
1902
|
var SonareWorkletProcessor = class {
|
|
1697
1903
|
constructor(options, transport) {
|
|
1698
1904
|
this.closed = false;
|
|
@@ -1966,12 +2172,21 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1966
2172
|
options.telemetryRingCapacity
|
|
1967
2173
|
) : void 0;
|
|
1968
2174
|
this.meterRing = options.meterSharedBuffer ? meterRingFromSharedBuffer(options.meterSharedBuffer, options.meterRingCapacity) : void 0;
|
|
2175
|
+
this.scopeRing = options.scopeSharedBuffer ? scopeRingFromSharedBuffer(
|
|
2176
|
+
options.scopeSharedBuffer,
|
|
2177
|
+
options.scopeRingCapacity,
|
|
2178
|
+
options.scopeBands
|
|
2179
|
+
) : void 0;
|
|
1969
2180
|
this.engine = new RealtimeEngine(this.sampleRate, this.blockSize);
|
|
1970
2181
|
this.engine.prepareChannels(this.channelCount, this.blockSize);
|
|
1971
2182
|
this.channelBuffers = new Array(this.channelCount);
|
|
1972
2183
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1973
2184
|
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
1974
2185
|
}
|
|
2186
|
+
if (this.scopeRing) {
|
|
2187
|
+
const interval = Math.max(1, Math.floor(options.scopeIntervalFrames ?? this.blockSize));
|
|
2188
|
+
this.engine.configureScopeTelemetry(interval, this.scopeRing.bands);
|
|
2189
|
+
}
|
|
1975
2190
|
}
|
|
1976
2191
|
process(inputs, outputs) {
|
|
1977
2192
|
if (this.closed) {
|
|
@@ -2029,6 +2244,7 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
2029
2244
|
}
|
|
2030
2245
|
this.publishTelemetry();
|
|
2031
2246
|
this.publishMeters();
|
|
2247
|
+
this.publishScope();
|
|
2032
2248
|
return true;
|
|
2033
2249
|
}
|
|
2034
2250
|
reacquireChannelBuffers() {
|
|
@@ -2113,6 +2329,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
2113
2329
|
if (message.masterStripJson) {
|
|
2114
2330
|
this.engine.setMasterStripJson(message.masterStripJson);
|
|
2115
2331
|
}
|
|
2332
|
+
for (const binding of message.laneSidechains ?? []) {
|
|
2333
|
+
this.engine.setLaneSidechain(binding.trackId, binding.insertIndex, binding.sourceTrackId);
|
|
2334
|
+
}
|
|
2116
2335
|
break;
|
|
2117
2336
|
case "syncCapture":
|
|
2118
2337
|
this.engine.setCaptureBuffer(message.channels, message.bufferFrames);
|
|
@@ -2141,6 +2360,36 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
2141
2360
|
message.resetOnBypass
|
|
2142
2361
|
);
|
|
2143
2362
|
break;
|
|
2363
|
+
case "syncTrackStripInsertParamByName":
|
|
2364
|
+
this.engine.setTrackStripInsertParamByName(
|
|
2365
|
+
message.trackId,
|
|
2366
|
+
message.insertIndex,
|
|
2367
|
+
message.paramName,
|
|
2368
|
+
message.value
|
|
2369
|
+
);
|
|
2370
|
+
break;
|
|
2371
|
+
case "syncMasterStripInsertParamByName":
|
|
2372
|
+
this.engine.setMasterStripInsertParamByName(
|
|
2373
|
+
message.insertIndex,
|
|
2374
|
+
message.paramName,
|
|
2375
|
+
message.value
|
|
2376
|
+
);
|
|
2377
|
+
break;
|
|
2378
|
+
case "syncTrackStripPan":
|
|
2379
|
+
this.engine.setTrackStripPan(message.trackId, message.pan);
|
|
2380
|
+
break;
|
|
2381
|
+
case "syncTrackStripPanLaw":
|
|
2382
|
+
this.engine.setTrackStripPanLaw(message.trackId, message.panLaw);
|
|
2383
|
+
break;
|
|
2384
|
+
case "syncTrackStripPanMode":
|
|
2385
|
+
this.engine.setTrackStripPanMode(message.trackId, message.panMode);
|
|
2386
|
+
break;
|
|
2387
|
+
case "syncTrackStripDualPan":
|
|
2388
|
+
this.engine.setTrackStripDualPan(message.trackId, message.leftPan, message.rightPan);
|
|
2389
|
+
break;
|
|
2390
|
+
case "syncTrackStripChannelDelaySamples":
|
|
2391
|
+
this.engine.setTrackStripChannelDelaySamples(message.trackId, message.delaySamples);
|
|
2392
|
+
break;
|
|
2144
2393
|
case "syncBuiltinInstrument":
|
|
2145
2394
|
this.engine.setBuiltinInstrument(message.config, message.destinationId);
|
|
2146
2395
|
break;
|
|
@@ -2375,6 +2624,17 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
2375
2624
|
}
|
|
2376
2625
|
this.transport?.postMessage?.(record);
|
|
2377
2626
|
}
|
|
2627
|
+
// Drains the engine meter telemetry queue into the stereo meter ring / transport.
|
|
2628
|
+
//
|
|
2629
|
+
// Shared-queue contract: `drainMeterTelemetry` and `drainMeterTelemetryWide`
|
|
2630
|
+
// pop the SAME single-consumer telemetry queue, so exactly ONE of them may run
|
|
2631
|
+
// per engine. The live worklet path owns the queue via the stereo drain below;
|
|
2632
|
+
// the worklet meter ring (SONARE_METER_RING_RECORD_FLOATS) is a fixed stereo
|
|
2633
|
+
// layout carrying planes 0/1 plus the correlation/LUFS summary. Per-plane
|
|
2634
|
+
// surround meters are NOT delivered over the live worklet ring — a host that
|
|
2635
|
+
// needs them must use the offline `drainMeterTelemetryWide()` API on a
|
|
2636
|
+
// non-worklet engine instance (do not also call it on a worklet-driven engine,
|
|
2637
|
+
// or the two drains will starve each other).
|
|
2378
2638
|
publishMeters() {
|
|
2379
2639
|
if (this.meterIntervalFrames <= 0 || !this.transport && !this.meterRing) {
|
|
2380
2640
|
return;
|
|
@@ -2418,6 +2678,41 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
2418
2678
|
ring.records[offset + 13] = meter.gainReductionDb;
|
|
2419
2679
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
2420
2680
|
}
|
|
2681
|
+
// Drains the engine's scope producer (FFT spectrum + goniometer points) into
|
|
2682
|
+
// the lock-free SAB scope ring. Only the embind runtime publishes scope
|
|
2683
|
+
// telemetry; the sonare-rt runtime owns its own transport. No allocation on
|
|
2684
|
+
// the render path: records are written field-by-field into the ring.
|
|
2685
|
+
publishScope() {
|
|
2686
|
+
const ring = this.scopeRing;
|
|
2687
|
+
if (!ring) {
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
for (const item of this.engine.drainScopeTelemetry(64)) {
|
|
2691
|
+
this.writeScopeRing(ring, item);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
writeScopeRing(ring, record) {
|
|
2695
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
2696
|
+
const base = writeIndex % ring.capacity * ring.recordFloats;
|
|
2697
|
+
ring.records[base] = encodeFrameLo(record.renderFrame);
|
|
2698
|
+
ring.records[base + 1] = encodeFrameHi(record.renderFrame);
|
|
2699
|
+
ring.records[base + 2] = record.targetId;
|
|
2700
|
+
const bandCount = Math.min(ring.bands, record.bands.length);
|
|
2701
|
+
ring.records[base + 3] = bandCount;
|
|
2702
|
+
const pointCount = Math.min(ring.maxPoints, record.points.length);
|
|
2703
|
+
ring.records[base + 4] = pointCount;
|
|
2704
|
+
const bandsBase = base + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS;
|
|
2705
|
+
for (let i = 0; i < bandCount; i++) {
|
|
2706
|
+
ring.records[bandsBase + i] = record.bands[i];
|
|
2707
|
+
}
|
|
2708
|
+
const pointsBase = bandsBase + ring.bands;
|
|
2709
|
+
for (let i = 0; i < pointCount; i++) {
|
|
2710
|
+
const point = record.points[i];
|
|
2711
|
+
ring.records[pointsBase + 2 * i] = point.left;
|
|
2712
|
+
ring.records[pointsBase + 2 * i + 1] = point.right;
|
|
2713
|
+
}
|
|
2714
|
+
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
2715
|
+
}
|
|
2421
2716
|
commandRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
2422
2717
|
const ring = engineRingFromSharedBuffer(
|
|
2423
2718
|
sharedBuffer,
|
|
@@ -2777,11 +3072,13 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
2777
3072
|
}
|
|
2778
3073
|
};
|
|
2779
3074
|
var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
2780
|
-
constructor(node, capabilities, commandRing, telemetryRing, meterRing) {
|
|
3075
|
+
constructor(node, capabilities, commandRing, telemetryRing, meterRing, scopeRing) {
|
|
2781
3076
|
this.telemetryReadIndex = 0;
|
|
2782
3077
|
this.meterReadIndex = 0;
|
|
3078
|
+
this.scopeReadIndex = 0;
|
|
2783
3079
|
this.telemetryListeners = /* @__PURE__ */ new Set();
|
|
2784
3080
|
this.meterListeners = /* @__PURE__ */ new Set();
|
|
3081
|
+
this.scopeListeners = /* @__PURE__ */ new Set();
|
|
2785
3082
|
this.captureRequestId = 1;
|
|
2786
3083
|
this.captureRequests = /* @__PURE__ */ new Map();
|
|
2787
3084
|
this.transportRequestId = 1;
|
|
@@ -2792,6 +3089,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2792
3089
|
this.commandRing = commandRing;
|
|
2793
3090
|
this.telemetryRing = telemetryRing;
|
|
2794
3091
|
this.meterRing = meterRing;
|
|
3092
|
+
this.scopeRing = scopeRing;
|
|
2795
3093
|
this.ready = new Promise((resolve, reject) => {
|
|
2796
3094
|
this.resolveReady = resolve;
|
|
2797
3095
|
this.rejectReady = reject;
|
|
@@ -2861,6 +3159,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2861
3159
|
const commandRing = mode === "sab" ? createSonareEngineCommandRingBuffer(options.commandRingCapacity ?? 128) : void 0;
|
|
2862
3160
|
const telemetryRing = mode === "sab" ? createSonareEngineTelemetryRingBuffer(options.telemetryRingCapacity ?? 128) : void 0;
|
|
2863
3161
|
const meterRing = mode === "sab" && runtimeTarget === "embind" ? createSonareMeterRingBuffer(options.meterRingCapacity ?? 128) : void 0;
|
|
3162
|
+
const scopeIntervalFrames = Math.max(0, Math.floor(options.scopeIntervalFrames ?? 0));
|
|
3163
|
+
const scopeRing = mode === "sab" && runtimeTarget === "embind" && scopeIntervalFrames > 0 ? createSonareScopeRingBuffer(options.scopeRingCapacity ?? 64, options.scopeBands ?? 48) : void 0;
|
|
2864
3164
|
const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
2865
3165
|
const processorOptions = {
|
|
2866
3166
|
runtimeTarget,
|
|
@@ -2875,6 +3175,10 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2875
3175
|
telemetryRingCapacity: telemetryRing?.capacity,
|
|
2876
3176
|
meterSharedBuffer: meterRing?.sharedBuffer,
|
|
2877
3177
|
meterRingCapacity: meterRing?.capacity,
|
|
3178
|
+
scopeSharedBuffer: scopeRing?.sharedBuffer,
|
|
3179
|
+
scopeRingCapacity: scopeRing?.capacity,
|
|
3180
|
+
scopeBands: scopeRing?.bands,
|
|
3181
|
+
scopeIntervalFrames: scopeRing ? scopeIntervalFrames : void 0,
|
|
2878
3182
|
wasmBinary: options.wasmBinary,
|
|
2879
3183
|
initialSyncMessages: options.initialSyncMessages,
|
|
2880
3184
|
initialCommands: options.initialCommands
|
|
@@ -2902,7 +3206,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2902
3206
|
},
|
|
2903
3207
|
commandRing,
|
|
2904
3208
|
telemetryRing,
|
|
2905
|
-
meterRing
|
|
3209
|
+
meterRing,
|
|
3210
|
+
scopeRing
|
|
2906
3211
|
);
|
|
2907
3212
|
}
|
|
2908
3213
|
play(sampleTime = -1) {
|
|
@@ -2986,6 +3291,20 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2986
3291
|
}
|
|
2987
3292
|
return read.meters;
|
|
2988
3293
|
}
|
|
3294
|
+
// Drains scope telemetry (FFT spectrum + goniometer points) published into the
|
|
3295
|
+
// SAB scope ring and forwards each record to onScope listeners. A no-op unless
|
|
3296
|
+
// the node was created with scopeIntervalFrames > 0 (embind SAB mode).
|
|
3297
|
+
pollScope() {
|
|
3298
|
+
if (!this.scopeRing) {
|
|
3299
|
+
return [];
|
|
3300
|
+
}
|
|
3301
|
+
const read = readSonareScopeRingBuffer(this.scopeRing, this.scopeReadIndex);
|
|
3302
|
+
this.scopeReadIndex = read.nextReadIndex;
|
|
3303
|
+
for (const scope of read.scopes) {
|
|
3304
|
+
this.emitScope(scope);
|
|
3305
|
+
}
|
|
3306
|
+
return read.scopes;
|
|
3307
|
+
}
|
|
2989
3308
|
onTelemetry(callback) {
|
|
2990
3309
|
this.telemetryListeners.add(callback);
|
|
2991
3310
|
return () => {
|
|
@@ -2998,6 +3317,12 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2998
3317
|
this.meterListeners.delete(callback);
|
|
2999
3318
|
};
|
|
3000
3319
|
}
|
|
3320
|
+
onScope(callback) {
|
|
3321
|
+
this.scopeListeners.add(callback);
|
|
3322
|
+
return () => {
|
|
3323
|
+
this.scopeListeners.delete(callback);
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3001
3326
|
destroy() {
|
|
3002
3327
|
if (this.destroyed) {
|
|
3003
3328
|
return;
|
|
@@ -3015,6 +3340,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3015
3340
|
this.transportRequests.clear();
|
|
3016
3341
|
this.telemetryListeners.clear();
|
|
3017
3342
|
this.meterListeners.clear();
|
|
3343
|
+
this.scopeListeners.clear();
|
|
3018
3344
|
}
|
|
3019
3345
|
emitTelemetry(telemetry) {
|
|
3020
3346
|
for (const listener of this.telemetryListeners) {
|
|
@@ -3026,6 +3352,11 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3026
3352
|
listener(meter);
|
|
3027
3353
|
}
|
|
3028
3354
|
}
|
|
3355
|
+
emitScope(scope) {
|
|
3356
|
+
for (const listener of this.scopeListeners) {
|
|
3357
|
+
listener(scope);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3029
3360
|
sendCaptureRequest(op) {
|
|
3030
3361
|
if (this.destroyed) {
|
|
3031
3362
|
return Promise.reject(new Error("Realtime engine node is destroyed."));
|
|
@@ -3057,6 +3388,8 @@ var SonareEngine = class _SonareEngine {
|
|
|
3057
3388
|
this.markers = /* @__PURE__ */ new Map();
|
|
3058
3389
|
this.trackLaneIds = [];
|
|
3059
3390
|
this.trackSends = /* @__PURE__ */ new Map();
|
|
3391
|
+
this.trackOutputBus = /* @__PURE__ */ new Map();
|
|
3392
|
+
this.laneSidechains = /* @__PURE__ */ new Map();
|
|
3060
3393
|
this.buses = [];
|
|
3061
3394
|
this.trackStripJson = /* @__PURE__ */ new Map();
|
|
3062
3395
|
this.busStripJson = /* @__PURE__ */ new Map();
|
|
@@ -3214,6 +3547,64 @@ var SonareEngine = class _SonareEngine {
|
|
|
3214
3547
|
addAutomationPoint(laneId, ppq, value, curve = "linear") {
|
|
3215
3548
|
this.scheduleParam("", laneId, ppq, value, curve);
|
|
3216
3549
|
}
|
|
3550
|
+
/**
|
|
3551
|
+
* Replaces the automation lane for `paramId` with the given breakpoints.
|
|
3552
|
+
*
|
|
3553
|
+
* Unlike scheduleParam (which appends a single point), this sets the whole
|
|
3554
|
+
* lane at once; an empty array clears the lane. The points are defensively
|
|
3555
|
+
* copied and sorted by ppq before being mirrored to the offline engine and
|
|
3556
|
+
* the live worklet engine.
|
|
3557
|
+
*
|
|
3558
|
+
* @param paramId Automation target id (registered parameter or a reserved
|
|
3559
|
+
* engine mixer target from automationParamId/busAutomationParamId).
|
|
3560
|
+
* @param points Lane breakpoints; order does not matter.
|
|
3561
|
+
*/
|
|
3562
|
+
setAutomationLane(paramId, points) {
|
|
3563
|
+
const sorted = points.map((point) => ({ ...point })).sort((a, b) => a.ppq - b.ppq);
|
|
3564
|
+
if (sorted.length === 0) {
|
|
3565
|
+
this.automationLanes.delete(paramId);
|
|
3566
|
+
} else {
|
|
3567
|
+
this.automationLanes.set(paramId, sorted);
|
|
3568
|
+
}
|
|
3569
|
+
this.offlineEngine.setAutomationLane(paramId, sorted);
|
|
3570
|
+
this.postSync({ type: "syncAutomation", paramId, points: sorted });
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* Returns the automation target id for a mixer strip parameter.
|
|
3574
|
+
*
|
|
3575
|
+
* The id addresses the engine's reserved mixer namespace, so it can be fed
|
|
3576
|
+
* straight to setAutomationLane to automate a fader or pan without
|
|
3577
|
+
* registering a parameter.
|
|
3578
|
+
*
|
|
3579
|
+
* @param target Track id (declares a mixer lane on first use) or 'master'.
|
|
3580
|
+
* @param kind Strip parameter to address.
|
|
3581
|
+
* @returns Reserved engine parameter id for the strip parameter.
|
|
3582
|
+
*/
|
|
3583
|
+
automationParamId(target, kind) {
|
|
3584
|
+
const paramKind = kind === "pan" ? ENGINE_MIXER_PARAM_PAN : ENGINE_MIXER_PARAM_FADER_DB;
|
|
3585
|
+
if (target === "master") {
|
|
3586
|
+
return engineMixerMasterTarget(paramKind);
|
|
3587
|
+
}
|
|
3588
|
+
return engineMixerLaneTarget(this.ensureTrackLane(target), paramKind);
|
|
3589
|
+
}
|
|
3590
|
+
/**
|
|
3591
|
+
* Returns the automation target id for a bus fader.
|
|
3592
|
+
*
|
|
3593
|
+
* @param busId Bus id (declares the mixer bus on first use).
|
|
3594
|
+
* @returns Reserved engine parameter id for the bus fader gain (dB).
|
|
3595
|
+
*/
|
|
3596
|
+
busAutomationParamId(busId) {
|
|
3597
|
+
return engineMixerBusTarget(this.ensureBus(busId), ENGINE_MIXER_PARAM_FADER_DB);
|
|
3598
|
+
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Returns the number of automation lanes installed on the engine, including
|
|
3601
|
+
* lanes whose breakpoint list is currently empty.
|
|
3602
|
+
*
|
|
3603
|
+
* @returns Engine-side automation lane count.
|
|
3604
|
+
*/
|
|
3605
|
+
automationLaneCount() {
|
|
3606
|
+
return this.offlineEngine.automationLaneCount();
|
|
3607
|
+
}
|
|
3217
3608
|
listParameters() {
|
|
3218
3609
|
const parameters = [];
|
|
3219
3610
|
for (let index = 0; index < this.offlineEngine.parameterCount(); index++) {
|
|
@@ -3310,10 +3701,57 @@ var SonareEngine = class _SonareEngine {
|
|
|
3310
3701
|
entry.sends.map((send) => ({ ...send }))
|
|
3311
3702
|
);
|
|
3312
3703
|
}
|
|
3704
|
+
if (entry.outputBusId !== void 0) {
|
|
3705
|
+
if (entry.outputBusId === 0) {
|
|
3706
|
+
this.trackOutputBus.delete(entry.trackId);
|
|
3707
|
+
} else {
|
|
3708
|
+
this.trackOutputBus.set(entry.trackId, entry.outputBusId);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3313
3711
|
}
|
|
3314
3712
|
this.trackLaneIds.splice(0, this.trackLaneIds.length, ...ids);
|
|
3315
3713
|
this.syncMixer();
|
|
3316
3714
|
}
|
|
3715
|
+
/**
|
|
3716
|
+
* Routes a track lane's post-fader output into a declared bus instead of
|
|
3717
|
+
* the master mix (group/folder routing); busId 0 restores the master mix.
|
|
3718
|
+
*/
|
|
3719
|
+
setTrackOutputBus(target, busId) {
|
|
3720
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3721
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3722
|
+
if (busId === 0) {
|
|
3723
|
+
this.trackOutputBus.delete(trackId);
|
|
3724
|
+
} else {
|
|
3725
|
+
this.trackOutputBus.set(trackId, busId);
|
|
3726
|
+
}
|
|
3727
|
+
this.syncMixer();
|
|
3728
|
+
}
|
|
3729
|
+
/**
|
|
3730
|
+
* Keys one insert of a lane strip from another lane's post-strip pre-fader
|
|
3731
|
+
* audio (ducking/sidechainRouter inserts). sourceTarget null removes the
|
|
3732
|
+
* binding.
|
|
3733
|
+
*/
|
|
3734
|
+
setLaneSidechain(target, insertIndex, sourceTarget) {
|
|
3735
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3736
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3737
|
+
const key = `${trackId}:${insertIndex}`;
|
|
3738
|
+
let sourceTrackId = 0;
|
|
3739
|
+
if (sourceTarget !== null) {
|
|
3740
|
+
const sourceIndex = this.ensureTrackLane(sourceTarget);
|
|
3741
|
+
sourceTrackId = this.trackLaneIds[sourceIndex];
|
|
3742
|
+
}
|
|
3743
|
+
if (sourceTrackId === 0) {
|
|
3744
|
+
this.laneSidechains.delete(key);
|
|
3745
|
+
} else {
|
|
3746
|
+
this.laneSidechains.set(key, { trackId, insertIndex, sourceTrackId });
|
|
3747
|
+
}
|
|
3748
|
+
this.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
3749
|
+
this.postSync({
|
|
3750
|
+
type: "syncMixer",
|
|
3751
|
+
lanes: this.mixerLanes(),
|
|
3752
|
+
laneSidechains: [{ trackId, insertIndex, sourceTrackId }]
|
|
3753
|
+
});
|
|
3754
|
+
}
|
|
3317
3755
|
setSends(target, sends) {
|
|
3318
3756
|
const laneIndex = this.ensureTrackLane(target);
|
|
3319
3757
|
const trackId = this.trackLaneIds[laneIndex];
|
|
@@ -3366,6 +3804,45 @@ var SonareEngine = class _SonareEngine {
|
|
|
3366
3804
|
resetOnBypass
|
|
3367
3805
|
});
|
|
3368
3806
|
}
|
|
3807
|
+
setTrackStripInsertParamByName(target, insertIndex, paramName, value) {
|
|
3808
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3809
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3810
|
+
this.offlineEngine.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
|
|
3811
|
+
this.postSync({
|
|
3812
|
+
type: "syncTrackStripInsertParamByName",
|
|
3813
|
+
trackId,
|
|
3814
|
+
insertIndex,
|
|
3815
|
+
paramName,
|
|
3816
|
+
value
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3819
|
+
setTrackStripPan(target, pan) {
|
|
3820
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3821
|
+
this.offlineEngine.setTrackStripPan(trackId, pan);
|
|
3822
|
+
this.postSync({ type: "syncTrackStripPan", trackId, pan });
|
|
3823
|
+
}
|
|
3824
|
+
setTrackStripPanLaw(target, panLaw) {
|
|
3825
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3826
|
+
const code = panLawCode(panLaw);
|
|
3827
|
+
this.offlineEngine.setTrackStripPanLaw(trackId, code);
|
|
3828
|
+
this.postSync({ type: "syncTrackStripPanLaw", trackId, panLaw: code });
|
|
3829
|
+
}
|
|
3830
|
+
setTrackStripPanMode(target, panMode) {
|
|
3831
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3832
|
+
const code = panModeCode(panMode);
|
|
3833
|
+
this.offlineEngine.setTrackStripPanMode(trackId, code);
|
|
3834
|
+
this.postSync({ type: "syncTrackStripPanMode", trackId, panMode: code });
|
|
3835
|
+
}
|
|
3836
|
+
setTrackStripDualPan(target, leftPan, rightPan) {
|
|
3837
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3838
|
+
this.offlineEngine.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
3839
|
+
this.postSync({ type: "syncTrackStripDualPan", trackId, leftPan, rightPan });
|
|
3840
|
+
}
|
|
3841
|
+
setTrackStripChannelDelaySamples(target, delaySamples) {
|
|
3842
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3843
|
+
this.offlineEngine.setTrackStripChannelDelaySamples(trackId, delaySamples);
|
|
3844
|
+
this.postSync({ type: "syncTrackStripChannelDelaySamples", trackId, delaySamples });
|
|
3845
|
+
}
|
|
3369
3846
|
setStripEq(target, bandIndex, band) {
|
|
3370
3847
|
if (target === "master") {
|
|
3371
3848
|
this.setMasterStripEqBand(bandIndex, band);
|
|
@@ -3412,6 +3889,22 @@ var SonareEngine = class _SonareEngine {
|
|
|
3412
3889
|
resetOnBypass
|
|
3413
3890
|
});
|
|
3414
3891
|
}
|
|
3892
|
+
setMasterStripInsertParamByName(insertIndex, paramName, value) {
|
|
3893
|
+
this.offlineEngine.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
3894
|
+
this.postSync({
|
|
3895
|
+
type: "syncMasterStripInsertParamByName",
|
|
3896
|
+
insertIndex,
|
|
3897
|
+
paramName,
|
|
3898
|
+
value
|
|
3899
|
+
});
|
|
3900
|
+
}
|
|
3901
|
+
setStripInsertParamByName(target, insertIndex, paramName, value) {
|
|
3902
|
+
if (target === "master") {
|
|
3903
|
+
this.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
this.setTrackStripInsertParamByName(target, insertIndex, paramName, value);
|
|
3907
|
+
}
|
|
3415
3908
|
setMasterChain(sceneJson) {
|
|
3416
3909
|
this.setMasterStripJson(sceneJson);
|
|
3417
3910
|
}
|
|
@@ -3641,6 +4134,9 @@ var SonareEngine = class _SonareEngine {
|
|
|
3641
4134
|
onMeter(callback) {
|
|
3642
4135
|
return this.realtimeNode.onMeter(callback);
|
|
3643
4136
|
}
|
|
4137
|
+
onScope(callback) {
|
|
4138
|
+
return this.realtimeNode.onScope(callback);
|
|
4139
|
+
}
|
|
3644
4140
|
onTelemetry(callback) {
|
|
3645
4141
|
return this.realtimeNode.onTelemetry(callback);
|
|
3646
4142
|
}
|
|
@@ -3650,6 +4146,9 @@ var SonareEngine = class _SonareEngine {
|
|
|
3650
4146
|
pollMeters() {
|
|
3651
4147
|
return this.realtimeNode.pollMeters();
|
|
3652
4148
|
}
|
|
4149
|
+
pollScope() {
|
|
4150
|
+
return this.realtimeNode.pollScope();
|
|
4151
|
+
}
|
|
3653
4152
|
destroy() {
|
|
3654
4153
|
if (this.destroyed) {
|
|
3655
4154
|
return;
|
|
@@ -3674,11 +4173,19 @@ var SonareEngine = class _SonareEngine {
|
|
|
3674
4173
|
this.offlineEngine.setMidiClips(clips);
|
|
3675
4174
|
this.postSync({ type: "syncMidiClips", clips });
|
|
3676
4175
|
}
|
|
3677
|
-
|
|
3678
|
-
|
|
4176
|
+
mixerLanes() {
|
|
4177
|
+
return this.trackLaneIds.map((trackId) => {
|
|
3679
4178
|
const sends = this.trackSends.get(trackId);
|
|
3680
|
-
|
|
4179
|
+
const outputBusId = this.trackOutputBus.get(trackId);
|
|
4180
|
+
return {
|
|
4181
|
+
trackId,
|
|
4182
|
+
...sends && sends.length > 0 ? { sends: sends.map((send) => ({ ...send })) } : {},
|
|
4183
|
+
...outputBusId !== void 0 ? { outputBusId } : {}
|
|
4184
|
+
};
|
|
3681
4185
|
});
|
|
4186
|
+
}
|
|
4187
|
+
syncMixer() {
|
|
4188
|
+
const lanes = this.mixerLanes();
|
|
3682
4189
|
const buses = this.buses.map((bus) => ({ ...bus }));
|
|
3683
4190
|
this.offlineEngine.setTrackBuses(buses);
|
|
3684
4191
|
if (lanes.length > 0) {
|
|
@@ -3697,6 +4204,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
3697
4204
|
lanes,
|
|
3698
4205
|
buses,
|
|
3699
4206
|
trackStrips,
|
|
4207
|
+
laneSidechains: Array.from(this.laneSidechains.values()),
|
|
3700
4208
|
busStrips,
|
|
3701
4209
|
masterStripJson: this.masterStripJson
|
|
3702
4210
|
});
|
|
@@ -4158,6 +4666,7 @@ export {
|
|
|
4158
4666
|
SONARE_ENGINE_TELEMETRY_RECORD_BYTES,
|
|
4159
4667
|
SONARE_METER_RING_HEADER_INTS,
|
|
4160
4668
|
SONARE_METER_RING_RECORD_FLOATS,
|
|
4669
|
+
SONARE_SCOPE_RING_HEADER_INTS,
|
|
4161
4670
|
SONARE_SPECTRUM_RING_HEADER_INTS,
|
|
4162
4671
|
SonareEngine,
|
|
4163
4672
|
SonareEngineCommandType,
|
|
@@ -4171,6 +4680,7 @@ export {
|
|
|
4171
4680
|
createSonareEngineCommandRingBuffer,
|
|
4172
4681
|
createSonareEngineTelemetryRingBuffer,
|
|
4173
4682
|
createSonareMeterRingBuffer,
|
|
4683
|
+
createSonareScopeRingBuffer,
|
|
4174
4684
|
createSonareSpectrumRingBuffer,
|
|
4175
4685
|
decodeFrame,
|
|
4176
4686
|
encodeFrameHi,
|
|
@@ -4181,6 +4691,7 @@ export {
|
|
|
4181
4691
|
pushSonareEngineCommandRingBuffer,
|
|
4182
4692
|
readSonareEngineTelemetryRingBuffer,
|
|
4183
4693
|
readSonareMeterRingBuffer,
|
|
4694
|
+
readSonareScopeRingBuffer,
|
|
4184
4695
|
readSonareSpectrumRingBuffer,
|
|
4185
4696
|
registerSonareRealtimeEngineWorkletProcessor,
|
|
4186
4697
|
registerSonareRealtimeVoiceChangerWorkletProcessor,
|
|
@@ -4188,6 +4699,7 @@ export {
|
|
|
4188
4699
|
sonareEngineCommandRingBufferByteLength,
|
|
4189
4700
|
sonareEngineTelemetryRingBufferByteLength,
|
|
4190
4701
|
sonareMeterRingBufferByteLength,
|
|
4702
|
+
sonareScopeRingBufferByteLength,
|
|
4191
4703
|
sonareSpectrumRingBufferByteLength,
|
|
4192
4704
|
writeSonareEngineTelemetryRingBuffer
|
|
4193
4705
|
};
|