@libraz/libsonare 1.3.3 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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) => typeof lane === "number" ? { trackId: lane } : 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
- syncMixer() {
3678
- const lanes = this.trackLaneIds.map((trackId) => {
4176
+ mixerLanes() {
4177
+ return this.trackLaneIds.map((trackId) => {
3679
4178
  const sends = this.trackSends.get(trackId);
3680
- return sends && sends.length > 0 ? { trackId, sends: sends.map((send) => ({ ...send })) } : { trackId };
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
  };