@libraz/libsonare 1.8.0 → 1.8.2

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.
Files changed (138) hide show
  1. package/README.md +5 -5
  2. package/dist/_effects_common.d.ts +2 -1
  3. package/dist/_effects_common.d.ts.map +1 -1
  4. package/dist/analysis.js +3 -0
  5. package/dist/analysis.js.map +1 -1
  6. package/dist/effects_mastering.d.ts +2 -2
  7. package/dist/effects_mastering.d.ts.map +1 -1
  8. package/dist/effects_note_ops.d.ts +55 -30
  9. package/dist/effects_note_ops.d.ts.map +1 -1
  10. package/dist/effects_timepitch.d.ts +17 -12
  11. package/dist/effects_timepitch.d.ts.map +1 -1
  12. package/dist/feature_decompose.d.ts +1 -1
  13. package/dist/feature_decompose.d.ts.map +1 -1
  14. package/dist/index.d.ts +11 -5
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2098 -155
  17. package/dist/index.js.map +1 -1
  18. package/dist/instrument_types.d.ts +27 -3
  19. package/dist/instrument_types.d.ts.map +1 -1
  20. package/dist/mastering_core.d.ts +30 -3
  21. package/dist/mastering_core.d.ts.map +1 -1
  22. package/dist/mixer.d.ts +2 -2
  23. package/dist/mixer.d.ts.map +1 -1
  24. package/dist/project.d.ts +5 -4
  25. package/dist/project.d.ts.map +1 -1
  26. package/dist/project_class.d.ts +57 -3
  27. package/dist/project_class.d.ts.map +1 -1
  28. package/dist/project_internal.d.ts +35 -1
  29. package/dist/project_internal.d.ts.map +1 -1
  30. package/dist/project_types.d.ts +17 -6
  31. package/dist/project_types.d.ts.map +1 -1
  32. package/dist/public_types.d.ts +1 -0
  33. package/dist/public_types.d.ts.map +1 -1
  34. package/dist/public_types_mastering.d.ts +19 -1
  35. package/dist/public_types_mastering.d.ts.map +1 -1
  36. package/dist/public_types_playback.d.ts +1 -0
  37. package/dist/public_types_playback.d.ts.map +1 -1
  38. package/dist/public_types_spectral.d.ts +5 -5
  39. package/dist/public_types_spectral.d.ts.map +1 -1
  40. package/dist/public_types_vocal_edit.d.ts +250 -0
  41. package/dist/public_types_vocal_edit.d.ts.map +1 -0
  42. package/dist/public_types_vocal_project.d.ts +63 -0
  43. package/dist/public_types_vocal_project.d.ts.map +1 -0
  44. package/dist/realtime_engine.d.ts +106 -16
  45. package/dist/realtime_engine.d.ts.map +1 -1
  46. package/dist/repair_dereverb.d.ts +1 -0
  47. package/dist/repair_dereverb.d.ts.map +1 -1
  48. package/dist/repair_noise.d.ts +1 -0
  49. package/dist/repair_noise.d.ts.map +1 -1
  50. package/dist/sonare-analysis.wasm +0 -0
  51. package/dist/sonare.d.ts +89 -0
  52. package/dist/sonare.js +1 -1
  53. package/dist/sonare.wasm +0 -0
  54. package/dist/streaming_processors.d.ts +5 -0
  55. package/dist/streaming_processors.d.ts.map +1 -1
  56. package/dist/vocal_edit.d.ts +131 -0
  57. package/dist/vocal_edit.d.ts.map +1 -0
  58. package/dist/vocal_edit_worker.d.ts +51 -0
  59. package/dist/vocal_edit_worker.d.ts.map +1 -0
  60. package/dist/vocal_edit_worker.js +1462 -0
  61. package/dist/vocal_edit_worker.js.map +1 -0
  62. package/dist/vocal_edit_worker_client.d.ts +118 -0
  63. package/dist/vocal_edit_worker_client.d.ts.map +1 -0
  64. package/dist/vocal_edit_worker_protocol.d.ts +139 -0
  65. package/dist/vocal_edit_worker_protocol.d.ts.map +1 -0
  66. package/dist/vocal_project.d.ts +17 -0
  67. package/dist/vocal_project.d.ts.map +1 -0
  68. package/dist/web_midi.d.ts +4 -0
  69. package/dist/web_midi.d.ts.map +1 -1
  70. package/dist/worker.js.map +1 -1
  71. package/dist/worklet/engine-mixer-facade.d.ts +42 -5
  72. package/dist/worklet/engine-mixer-facade.d.ts.map +1 -1
  73. package/dist/worklet/engine-node.d.ts +10 -0
  74. package/dist/worklet/engine-node.d.ts.map +1 -1
  75. package/dist/worklet/engine-processor.d.ts +6 -1
  76. package/dist/worklet/engine-processor.d.ts.map +1 -1
  77. package/dist/worklet/engine-register.d.ts.map +1 -1
  78. package/dist/worklet/engine-strips.d.ts +8 -4
  79. package/dist/worklet/engine-strips.d.ts.map +1 -1
  80. package/dist/worklet/engine-sync.d.ts +4 -2
  81. package/dist/worklet/engine-sync.d.ts.map +1 -1
  82. package/dist/worklet/engine.d.ts +42 -9
  83. package/dist/worklet/engine.d.ts.map +1 -1
  84. package/dist/worklet/guards.d.ts +10 -2
  85. package/dist/worklet/guards.d.ts.map +1 -1
  86. package/dist/worklet/messages.d.ts +54 -9
  87. package/dist/worklet/messages.d.ts.map +1 -1
  88. package/dist/worklet/mixer-processor.d.ts +2 -2
  89. package/dist/worklet/mixer-processor.d.ts.map +1 -1
  90. package/dist/worklet/protocol.d.ts +20 -2
  91. package/dist/worklet/protocol.d.ts.map +1 -1
  92. package/dist/worklet.d.ts +1 -1
  93. package/dist/worklet.d.ts.map +1 -1
  94. package/dist/worklet.js +808 -204
  95. package/dist/worklet.js.map +1 -1
  96. package/package.json +6 -1
  97. package/src/_effects_common.ts +36 -6
  98. package/src/effects_mastering.ts +4 -0
  99. package/src/effects_note_ops.ts +92 -44
  100. package/src/effects_timepitch.ts +42 -31
  101. package/src/feature_decompose.ts +2 -2
  102. package/src/index.ts +39 -0
  103. package/src/instrument_types.ts +29 -3
  104. package/src/mastering_core.ts +98 -2
  105. package/src/mixer.ts +41 -21
  106. package/src/project.ts +8 -1
  107. package/src/project_class.ts +136 -1
  108. package/src/project_internal.ts +90 -0
  109. package/src/project_types.ts +18 -6
  110. package/src/public_types.ts +1 -0
  111. package/src/public_types_mastering.ts +23 -0
  112. package/src/public_types_playback.ts +1 -0
  113. package/src/public_types_spectral.ts +5 -5
  114. package/src/public_types_vocal_edit.ts +289 -0
  115. package/src/public_types_vocal_project.ts +69 -0
  116. package/src/realtime_engine.ts +157 -24
  117. package/src/repair_dereverb.ts +1 -0
  118. package/src/repair_noise.ts +1 -0
  119. package/src/sonare.js.d.ts +89 -0
  120. package/src/streaming_processors.ts +8 -0
  121. package/src/vocal_edit.ts +885 -0
  122. package/src/vocal_edit_worker.ts +832 -0
  123. package/src/vocal_edit_worker_client.ts +812 -0
  124. package/src/vocal_edit_worker_protocol.ts +206 -0
  125. package/src/vocal_project.ts +327 -0
  126. package/src/web_midi.ts +100 -5
  127. package/src/worklet/engine-mixer-facade.ts +422 -87
  128. package/src/worklet/engine-node.ts +69 -4
  129. package/src/worklet/engine-processor.ts +129 -34
  130. package/src/worklet/engine-register.ts +3 -0
  131. package/src/worklet/engine-strips.ts +36 -14
  132. package/src/worklet/engine-sync.ts +6 -1
  133. package/src/worklet/engine.ts +76 -40
  134. package/src/worklet/guards.ts +81 -0
  135. package/src/worklet/messages.ts +63 -4
  136. package/src/worklet/mixer-processor.ts +10 -3
  137. package/src/worklet/protocol.ts +39 -8
  138. package/src/worklet.ts +2 -0
package/dist/worklet.js CHANGED
@@ -720,6 +720,13 @@ var SampleBank = class {
720
720
  };
721
721
 
722
722
  // src/project_internal.ts
723
+ var projectTimelineHandles = /* @__PURE__ */ new WeakMap();
724
+ function projectTimelineNativeId(owner) {
725
+ if (owner === null || typeof owner !== "object" && typeof owner !== "function") {
726
+ return void 0;
727
+ }
728
+ return projectTimelineHandles.get(owner)?.id;
729
+ }
723
730
  function normalizeSynthInstrument(patch) {
724
731
  if (patch === null || typeof patch !== "object") {
725
732
  return patch;
@@ -736,6 +743,20 @@ function normalizeSynthInstrument(patch) {
736
743
  }
737
744
  return { ...rest, sampleBankId: sampleBank.nativeId };
738
745
  }
746
+ function partRigInsertsJson(inserts) {
747
+ if (inserts === void 0 || inserts === null) {
748
+ return void 0;
749
+ }
750
+ if (!Array.isArray(inserts)) {
751
+ throw new TypeError("inserts must be an array");
752
+ }
753
+ return JSON.stringify(
754
+ inserts.map((insert) => {
755
+ const { processor, params } = insert;
756
+ return params === void 0 ? { processor } : { processor, params: typeof params === "string" ? params : JSON.stringify(params) };
757
+ })
758
+ );
759
+ }
739
760
  function projectModule() {
740
761
  const candidate = getSonareModule();
741
762
  if (typeof candidate.projectAbiVersion !== "function" || candidate.Project === void 0) {
@@ -1149,6 +1170,17 @@ var RealtimeEngine = class _RealtimeEngine {
1149
1170
  setArticulation(destinationId, channel, articulation) {
1150
1171
  this.native.setArticulation(destinationId, channel, articulation);
1151
1172
  }
1173
+ /**
1174
+ * Set the rig of one part (0-15), or of the destination's default when `part`
1175
+ * is `PART_RIG_ALL_PARTS`, on the instrument bound to `destinationId`. A
1176
+ * direct call, not a stored edit: a destination with no instrument is an
1177
+ * invalid parameter, and an instrument without part rigs (a builtin, a
1178
+ * host-callback instrument) is refused as not implemented rather than
1179
+ * succeeding quietly. `'chain'` takes 1-8 `inserts`; no other mode does.
1180
+ */
1181
+ setPartRig(destinationId, part, mode, inserts) {
1182
+ this.native.setPartRig(destinationId, part, mode, partRigInsertsJson(inserts));
1183
+ }
1152
1184
  /**
1153
1185
  * Read back {@link setArticulation} as the canonical name. An ordinal this
1154
1186
  * build cannot spell is handed back as the number, the way every other enum
@@ -1194,7 +1226,10 @@ var RealtimeEngine = class _RealtimeEngine {
1194
1226
  /**
1195
1227
  * Route a destination's (track lane's) MIDI to the external output queue
1196
1228
  * instead of the internal instrument rack, so the track plays an external
1197
- * device. Clearing it restores internal-synth playback.
1229
+ * device. Clearing it restores internal-synth playback. Control-thread only.
1230
+ * The change takes effect at the next processed block; switching a
1231
+ * destination's route first releases its notes and resets its controllers
1232
+ * through the old route, and drops its pending MIDI-FX events. Single writer.
1198
1233
  */
1199
1234
  setMidiDestinationExternal(destinationId, external) {
1200
1235
  this.native.setMidiDestinationExternal(destinationId, external);
@@ -1348,8 +1383,11 @@ var RealtimeEngine = class _RealtimeEngine {
1348
1383
  /**
1349
1384
  * Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
1350
1385
  * the full message including the leading 0xF0 and trailing 0xF7 (1..512
1351
- * bytes). `renderFrame` is the frame to fire at, or -1 for immediate. Mirrors
1352
- * the Node/Python/C-ABI `pushMidiSysex`.
1386
+ * bytes). `renderFrame` is the frame to fire at, or -1 for immediate. Throws
1387
+ * `InvalidParameter` when the destination instrument cannot prepare
1388
+ * the SysEx (retrying cannot help), and `OutOfMemory` when the payload
1389
+ * slots or the command queue are full (retry after a processed block).
1390
+ * Mirrors the Node/Python/C-ABI `pushMidiSysex`.
1353
1391
  */
1354
1392
  pushMidiSysex(destinationId, data, renderFrame = -1) {
1355
1393
  this.native.pushMidiSysex(destinationId, data, renderFrame);
@@ -1372,12 +1410,30 @@ var RealtimeEngine = class _RealtimeEngine {
1372
1410
  getTransportState() {
1373
1411
  return this.native.getTransportState();
1374
1412
  }
1413
+ /** Queues an integrated-loudness reset; short-term and momentary windows are retained. */
1414
+ resetMasterLoudnessMeter(renderFrame = -1) {
1415
+ this.native.resetMasterLoudnessMeter(renderFrame);
1416
+ }
1417
+ /** Reads the immutable factory value for a resolved insert parameter id. */
1418
+ insertParameterConstructedValue(paramId) {
1419
+ return this.native.insertParameterConstructedValue(paramId);
1420
+ }
1375
1421
  play(renderFrame = -1) {
1376
1422
  this.native.play(renderFrame);
1377
1423
  }
1424
+ /**
1425
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
1426
+ * centred pitch bend on every channel played since the last reset, so
1427
+ * controller values set before a loop region are not restored at the wrap.
1428
+ */
1378
1429
  stop(renderFrame = -1) {
1379
1430
  this.native.stop(renderFrame);
1380
1431
  }
1432
+ /**
1433
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
1434
+ * centred pitch bend on every channel played since the last reset, so
1435
+ * controller values set before a loop region are not restored at the wrap.
1436
+ */
1381
1437
  seekSample(timelineSample, renderFrame = -1) {
1382
1438
  this.native.seekSample(timelineSample, renderFrame);
1383
1439
  }
@@ -1402,6 +1458,11 @@ var RealtimeEngine = class _RealtimeEngine {
1402
1458
  applyCommandsDueNowPreservingFuture() {
1403
1459
  this.native.applyCommandsDueNowPreservingFuture();
1404
1460
  }
1461
+ /**
1462
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
1463
+ * centred pitch bend on every channel played since the last reset, so
1464
+ * controller values set before a loop region are not restored at the wrap.
1465
+ */
1405
1466
  seekPpq(ppq, renderFrame = -1) {
1406
1467
  this.native.seekPpq(ppq, renderFrame);
1407
1468
  }
@@ -1421,6 +1482,11 @@ var RealtimeEngine = class _RealtimeEngine {
1421
1482
  sampleAtPpq(ppq) {
1422
1483
  return Number(this.native.sampleAtPpq(ppq));
1423
1484
  }
1485
+ /**
1486
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
1487
+ * centred pitch bend on every channel played since the last reset, so
1488
+ * controller values set before a loop region are not restored at the wrap.
1489
+ */
1424
1490
  setLoop(startPpq, endPpq, enabled = true) {
1425
1491
  this.native.setLoop(startPpq, endPpq, enabled);
1426
1492
  }
@@ -1515,25 +1581,56 @@ var RealtimeEngine = class _RealtimeEngine {
1515
1581
  if (typeof lane === "number") {
1516
1582
  return { trackId: lane };
1517
1583
  }
1518
- if (!lane.sends) {
1519
- return lane;
1520
- }
1521
- return { ...lane, sends: _RealtimeEngine.normalizeSends(lane.sends) };
1584
+ return {
1585
+ ...lane,
1586
+ sends: lane.sends ? _RealtimeEngine.normalizeSends(lane.sends) : void 0
1587
+ };
1522
1588
  })
1523
1589
  );
1524
1590
  }
1525
1591
  /**
1526
1592
  * Keys one insert of a lane strip from another lane's post-strip audio
1527
1593
  * (ducking/sidechainRouter inserts). sourceTrackId 0 removes the binding.
1594
+ * Lanes are processed in key order and the key is delay-compensated to the
1595
+ * destination strip input, so the result depends on neither the lane order
1596
+ * nor the block size. Throws, leaving the bindings unchanged, for a lane
1597
+ * keying itself, a binding that would close a cycle over the lane bindings,
1598
+ * a full binding table and an alignment past the delay ceiling. Not callable
1599
+ * while a block is being processed.
1528
1600
  */
1529
1601
  setLaneSidechain(trackId, insertIndex, sourceTrackId) {
1530
1602
  this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
1531
1603
  }
1604
+ /**
1605
+ * Installs a compiled project timeline into this stopped engine, all or
1606
+ * nothing: tempo and time-signature segments, markers, track lanes, track
1607
+ * automation, audio and MIDI clips, and the per-track strips of the project's
1608
+ * mixer scene are replaced in full (a later low-level setter on those domains
1609
+ * is overwritten by the next apply). Instruments, buses, the master strip,
1610
+ * metronome, loop and capture are not touched; bind instruments separately.
1611
+ *
1612
+ * Throws while the transport is playing, for a timeline the engine cannot
1613
+ * hold, and for a scene strip routed from several tracks. A validation
1614
+ * failure leaves the engine unchanged. The engine keeps its own reference, so
1615
+ * {@link ProjectTimeline.dispose} may follow immediately.
1616
+ *
1617
+ * Also throws on an unprepared engine and on a timeline whose sample rate
1618
+ * differs from the engine's. Once a timeline is applied, a later prepare at
1619
+ * another rate is refused until the timeline's clips are cleared.
1620
+ */
1621
+ applyProjectTimeline(timeline) {
1622
+ const timelineId = projectTimelineNativeId(timeline);
1623
+ if (timelineId === void 0) {
1624
+ throw new TypeError("timeline must be a ProjectTimeline instance");
1625
+ }
1626
+ this.native.applyProjectTimeline(timelineId);
1627
+ }
1532
1628
  setTrackBuses(buses) {
1533
1629
  this.native.setTrackBuses(
1534
- Array.isArray(buses) ? buses.map(
1535
- (bus) => bus.sends ? { ...bus, sends: _RealtimeEngine.normalizeSends(bus.sends) } : bus
1536
- ) : buses
1630
+ Array.isArray(buses) ? buses.map((bus) => ({
1631
+ ...bus,
1632
+ sends: bus.sends ? _RealtimeEngine.normalizeSends(bus.sends) : void 0
1633
+ })) : buses
1537
1634
  );
1538
1635
  }
1539
1636
  /**
@@ -1687,6 +1784,16 @@ var RealtimeEngine = class _RealtimeEngine {
1687
1784
  * stages of the offline mastering chain (`repair.*`, `loudness`, and the
1688
1785
  * match stages) have no insert form and no automation id: they buffer the
1689
1786
  * entire signal by construction and do not run on the realtime path.
1787
+ *
1788
+ * The returned id uses the track's current positional lane selector. When
1789
+ * `setTrackLanes` successfully changes lane order or membership, the engine
1790
+ * remaps already queued and published track automation by track id, but it
1791
+ * cannot update a numeric id retained by the caller. Re-resolve every track
1792
+ * insert id after such a topology change before passing it to
1793
+ * `setAutomationLane`, `setParameter`, or `setParameterSmoothed`. Use
1794
+ * `setTrackStripInsertParamByName` when the operation needs a stable track
1795
+ * identity. Master and bus insert ids are separate and are not invalidated by
1796
+ * track-lane changes.
1690
1797
  */
1691
1798
  resolveTrackInsertAutomationId(trackId, insertIndex, paramName) {
1692
1799
  return this.native.resolveTrackInsertAutomationId(trackId, insertIndex, paramName);
@@ -1719,12 +1826,9 @@ var RealtimeEngine = class _RealtimeEngine {
1719
1826
  * return `-1`: they resize voice pools or swap DSP topology, which is not
1720
1827
  * audio-thread safe. Rebind the instrument with a new patch instead.
1721
1828
  *
1722
- * `gain`, `busDrive`, `cutoffHz`, `resonanceQ`, `envToCutoffCents`,
1723
- * `lfoToPitchCents` and `pitchOffsetCents` reach voices that are already
1724
- * sounding from the next block; the rest are cached into per-voice state at
1725
- * note-on and take effect from the next note, so a lane that moves one of
1726
- * them under a held note looks inert until the next one speaks — that is the
1727
- * behaviour, not a dropped write.
1829
+ * All automatable parameters of a NativeSynth reach already-sounding voices
1830
+ * from the next processed block. Voices using GM program patches are
1831
+ * unaffected; parameters apply to NativeSynth's own patch only.
1728
1832
  *
1729
1833
  * The id survives an unbind/rebind of the same destination and applies
1730
1834
  * nothing while that destination is unbound.
@@ -1748,6 +1852,16 @@ var RealtimeEngine = class _RealtimeEngine {
1748
1852
  setTrackStripDualPan(trackId, leftPan, rightPan) {
1749
1853
  this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
1750
1854
  }
1855
+ /**
1856
+ * Sets a track lane strip's surround placement in realtime (glitch-free; a
1857
+ * constant-power 5 ms one-pole glide). Used when the destination has more than
1858
+ * two channels. Omitted fields default to a centered point source
1859
+ * (`distance` 1); a `distance` of 0 or less is treated as 1. Throws for an
1860
+ * unknown track, a track without a lane strip, or a non-finite field.
1861
+ */
1862
+ setTrackStripSurroundPan(trackId, pan) {
1863
+ this.native.setTrackStripSurroundPan(trackId, pan);
1864
+ }
1751
1865
  /**
1752
1866
  * Sets a bus strip's output pan position in realtime (glitch-free). Throws
1753
1867
  * for an unknown bus or one wider than stereo.
@@ -1768,8 +1882,9 @@ var RealtimeEngine = class _RealtimeEngine {
1768
1882
  this.native.setBusStripDualPan(busId, leftPan, rightPan);
1769
1883
  }
1770
1884
  /**
1771
- * Sets a track lane strip's inter-channel alignment delay (whole samples).
1772
- * Adjusts strip latency, so PDC and reported graph latency are refreshed.
1885
+ * Sets a track lane strip's alignment delay (whole samples), moving this lane
1886
+ * later relative to every other lane. It is not latency: PDC does not
1887
+ * compensate it and the reported graph latency is unchanged.
1773
1888
  */
1774
1889
  setTrackStripChannelDelaySamples(trackId, delaySamples) {
1775
1890
  this.native.setTrackStripChannelDelaySamples(trackId, delaySamples);
@@ -2013,6 +2128,12 @@ var RealtimeEngine = class _RealtimeEngine {
2013
2128
  meterScratchRenderFrame() {
2014
2129
  return Number(this.native.meterScratchRenderFrame());
2015
2130
  }
2131
+ meterScratchInputPeakDbL() {
2132
+ return this.native.meterScratchInputPeakDbL();
2133
+ }
2134
+ meterScratchInputPeakDbR() {
2135
+ return this.native.meterScratchInputPeakDbR();
2136
+ }
2016
2137
  meterScratchValue(field) {
2017
2138
  return this.native.meterScratchValue(field);
2018
2139
  }
@@ -2022,14 +2143,24 @@ var RealtimeEngine = class _RealtimeEngine {
2022
2143
  /**
2023
2144
  * Drains pending meter telemetry as per-plane (wide) records for a surround
2024
2145
  * target. Use this for a surround mix target; {@link drainMeterTelemetry}
2025
- * stays the stereo fast path. The two share one queue — call only one per
2026
- * target. The live AudioWorklet path owns the queue via the stereo drain, so
2027
- * this wide drain is for an offline (non-worklet) engine instance; per-plane
2146
+ * stays the stereo fast path. The two share one queue and each consumes every
2147
+ * target's records, so an engine uses only one of them. The live AudioWorklet
2148
+ * path owns the queue via the stereo drain, so this wide drain is for an
2149
+ * offline (non-worklet) engine instance; per-plane
2028
2150
  * surround meters are not delivered over the live worklet meter ring.
2029
2151
  */
2030
2152
  drainMeterTelemetryWide(maxRecords = 1024) {
2031
2153
  return this.native.drainMeterTelemetryWide(maxRecords);
2032
2154
  }
2155
+ /**
2156
+ * Per-insert gain reduction in dB (each <= 0) of a meter target's strip from the
2157
+ * last rendered block, in combined pre-to-post insert order. `targetId` uses the
2158
+ * meter-record encoding (0 master, 1..32 lanes, 33..40 buses; 0xFFFF monitor
2159
+ * yields an empty array). Throws for an id outside that range.
2160
+ */
2161
+ meterTargetInsertGainReduction(targetId) {
2162
+ return this.native.meterTargetInsertGainReduction(targetId);
2163
+ }
2033
2164
  /**
2034
2165
  * Enables per-target spectrum + vectorscope capture. @param intervalFrames is
2035
2166
  * the minimum render-frame gap between snapshots (0 disables). @param bandCount
@@ -2186,47 +2317,60 @@ var Mixer = class _Mixer {
2186
2317
  * after {@link delete}.
2187
2318
  */
2188
2319
  createRealtimeBuffer() {
2189
- const stripCount = this.stripCount();
2190
- let leftInputs = [];
2191
- let rightInputs = [];
2320
+ const leftInputs = [];
2321
+ const rightInputs = [];
2192
2322
  let outLeft = this.mixer.outputLeftView();
2193
2323
  let outRight = this.mixer.outputRightView();
2324
+ let acquiredStripCount = -1;
2325
+ const viewsDetached = () => outLeft.byteLength === 0 || outRight.byteLength === 0;
2194
2326
  const acquire = () => {
2195
- leftInputs = [];
2196
- rightInputs = [];
2197
- for (let index = 0; index < stripCount; index++) {
2327
+ const stripCount = this.stripCount();
2328
+ const detached = viewsDetached();
2329
+ if (detached) {
2330
+ leftInputs.length = 0;
2331
+ rightInputs.length = 0;
2332
+ } else {
2333
+ leftInputs.length = Math.min(leftInputs.length, stripCount);
2334
+ rightInputs.length = Math.min(rightInputs.length, stripCount);
2335
+ }
2336
+ for (let index = leftInputs.length; index < stripCount; index++) {
2198
2337
  leftInputs.push(this.mixer.inputLeftView(index));
2338
+ }
2339
+ for (let index = rightInputs.length; index < stripCount; index++) {
2199
2340
  rightInputs.push(this.mixer.inputRightView(index));
2200
2341
  }
2201
- outLeft = this.mixer.outputLeftView();
2202
- outRight = this.mixer.outputRightView();
2342
+ if (detached) {
2343
+ outLeft = this.mixer.outputLeftView();
2344
+ outRight = this.mixer.outputRightView();
2345
+ }
2346
+ acquiredStripCount = stripCount;
2203
2347
  };
2204
2348
  acquire();
2205
- const reacquireIfDetached = () => {
2206
- if (outLeft.byteLength === 0 || (leftInputs[0]?.byteLength ?? 1) === 0) {
2349
+ const acquireIfNeeded = () => {
2350
+ if (acquiredStripCount !== this.stripCount() || viewsDetached()) {
2207
2351
  acquire();
2208
2352
  }
2209
2353
  };
2210
2354
  return {
2211
2355
  get leftInputs() {
2212
- reacquireIfDetached();
2356
+ acquireIfNeeded();
2213
2357
  return leftInputs;
2214
2358
  },
2215
2359
  get rightInputs() {
2216
- reacquireIfDetached();
2360
+ acquireIfNeeded();
2217
2361
  return rightInputs;
2218
2362
  },
2219
2363
  get outLeft() {
2220
- reacquireIfDetached();
2364
+ acquireIfNeeded();
2221
2365
  return outLeft;
2222
2366
  },
2223
2367
  get outRight() {
2224
- reacquireIfDetached();
2368
+ acquireIfNeeded();
2225
2369
  return outRight;
2226
2370
  },
2227
- process: (numSamples = outLeft.length) => {
2228
- reacquireIfDetached();
2229
- this.mixer.processPreparedStereo(numSamples);
2371
+ process: (numSamples) => {
2372
+ acquireIfNeeded();
2373
+ this.mixer.processPreparedStereo(numSamples ?? outLeft.length);
2230
2374
  }
2231
2375
  };
2232
2376
  }
@@ -2444,8 +2588,8 @@ var Mixer = class _Mixer {
2444
2588
  this.mixer.setPanLaw(stripIndex, panLawCode(panLaw));
2445
2589
  }
2446
2590
  /**
2447
- * Set a per-strip channel delay in samples. This changes the strip's reported
2448
- * latency; recompile to re-run latency compensation.
2591
+ * Set a per-strip channel delay in samples, moving the strip later relative to
2592
+ * the others. It is not latency: latency compensation does not undo it.
2449
2593
  */
2450
2594
  setChannelDelaySamples(stripIndex, delaySamples) {
2451
2595
  this.mixer.setChannelDelaySamples(stripIndex, delaySamples);
@@ -2999,6 +3143,13 @@ var StreamingMasteringChain = class {
2999
3143
  reset() {
3000
3144
  this.chain.reset();
3001
3145
  }
3146
+ /**
3147
+ * Set one realtime-safe full-chain parameter at a serialized block boundary.
3148
+ * Structural, disabled-stage and unknown parameters are rejected.
3149
+ */
3150
+ setParameter(key, value) {
3151
+ this.chain.setParameter(key, value);
3152
+ }
3002
3153
  /** Total reported latency in samples across all active processors. */
3003
3154
  latencySamples() {
3004
3155
  return this.chain.latencySamples();
@@ -3105,14 +3256,16 @@ function isInitialized() {
3105
3256
  }
3106
3257
 
3107
3258
  // src/worklet/engine-sync.ts
3108
- function buildMixerLanes(trackLaneIds, trackSends, trackOutputBus) {
3259
+ function buildMixerLanes(trackLaneIds, trackSends, trackOutputBus, trackSourceChannelLayout) {
3109
3260
  return trackLaneIds.map((trackId) => {
3110
3261
  const sends = trackSends.get(trackId);
3111
3262
  const outputBusId = trackOutputBus.get(trackId);
3263
+ const sourceChannelLayout = trackSourceChannelLayout.get(trackId);
3112
3264
  return {
3113
3265
  trackId,
3114
3266
  ...sends && sends.length > 0 ? { sends: sends.map((send) => ({ ...send })) } : {},
3115
- ...outputBusId !== void 0 ? { outputBusId } : {}
3267
+ ...outputBusId !== void 0 ? { outputBusId } : {},
3268
+ ...sourceChannelLayout !== void 0 ? { sourceChannelLayout } : {}
3116
3269
  };
3117
3270
  });
3118
3271
  }
@@ -3187,10 +3340,12 @@ function engineMixerMasterTarget(paramKind) {
3187
3340
  return ENGINE_MIXER_TARGET_BASE | 255 << 8 | paramKind & 255;
3188
3341
  }
3189
3342
  var SONARE_METER_RING_HEADER_INTS = 4;
3190
- var SONARE_METER_RING_RECORD_FLOATS = 14;
3343
+ var SONARE_METER_RING_RECORD_FLOATS = 16;
3191
3344
  var SONARE_SPECTRUM_RING_HEADER_INTS = 5;
3192
3345
  var SONARE_SCOPE_RING_HEADER_INTS = 6;
3193
3346
  var SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS = 5;
3347
+ var SONARE_SCOPE_DEFAULT_BANDS = 48;
3348
+ var SONARE_SCOPE_DEFAULT_MAX_POINTS = 32;
3194
3349
  var SONARE_FRAME_LANE_BASE = 16777216;
3195
3350
  function encodeFrameLo(frame) {
3196
3351
  const f = Math.max(0, Math.floor(frame));
@@ -3230,6 +3385,7 @@ var SonareEngineCommandType = /* @__PURE__ */ ((SonareEngineCommandType2) => {
3230
3385
  SonareEngineCommandType2[SonareEngineCommandType2["SetMarker"] = 16] = "SetMarker";
3231
3386
  SonareEngineCommandType2[SonareEngineCommandType2["SeekMarker"] = 17] = "SeekMarker";
3232
3387
  SonareEngineCommandType2[SonareEngineCommandType2["SetTrackMonitorMode"] = 26] = "SetTrackMonitorMode";
3388
+ SonareEngineCommandType2[SonareEngineCommandType2["ResetMasterLoudnessMeter"] = 28] = "ResetMasterLoudnessMeter";
3233
3389
  return SonareEngineCommandType2;
3234
3390
  })(SonareEngineCommandType || {});
3235
3391
  var SonareEngineTelemetryType = /* @__PURE__ */ ((SonareEngineTelemetryType2) => {
@@ -3262,6 +3418,7 @@ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2)
3262
3418
  SonareEngineTelemetryError2[SonareEngineTelemetryError2["ParameterBaseOverflow"] = 21] = "ParameterBaseOverflow";
3263
3419
  return SonareEngineTelemetryError2;
3264
3420
  })(SonareEngineTelemetryError || {});
3421
+ var SONARE_FLOOR_DB = -120;
3265
3422
  function isRecord(value) {
3266
3423
  return typeof value === "object" && value !== null;
3267
3424
  }
@@ -3301,7 +3458,9 @@ function readSonareMeterRingBuffer(ring, readIndex = 0) {
3301
3458
  momentaryLufs: ring.records[offset + 10],
3302
3459
  shortTermLufs: ring.records[offset + 11],
3303
3460
  integratedLufs: ring.records[offset + 12],
3304
- gainReductionDb: ring.records[offset + 13]
3461
+ gainReductionDb: ring.records[offset + 13],
3462
+ inputPeakDbL: ring.records[offset + 14],
3463
+ inputPeakDbR: ring.records[offset + 15]
3305
3464
  });
3306
3465
  }
3307
3466
  return { nextReadIndex: writeIndex, meters };
@@ -3353,13 +3512,13 @@ function readSonareSpectrumRingBuffer(ring, readIndex = 0) {
3353
3512
  function sonareScopeRingRecordFloats(bands, maxPoints) {
3354
3513
  return SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS + bands + 2 * maxPoints;
3355
3514
  }
3356
- function sonareScopeRingBufferByteLength(capacity, bands = 48, maxPoints = 32) {
3515
+ function sonareScopeRingBufferByteLength(capacity, bands = SONARE_SCOPE_DEFAULT_BANDS, maxPoints = SONARE_SCOPE_DEFAULT_MAX_POINTS) {
3357
3516
  const clampedCapacity = Math.max(1, Math.floor(capacity));
3358
3517
  const clampedBands = Math.max(1, Math.floor(bands));
3359
3518
  const clampedPoints = Math.max(0, Math.floor(maxPoints));
3360
3519
  return SONARE_SCOPE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * sonareScopeRingRecordFloats(clampedBands, clampedPoints) * Float32Array.BYTES_PER_ELEMENT;
3361
3520
  }
3362
- function createSonareScopeRingBuffer(capacity = 64, bands = 48, maxPoints = 32) {
3521
+ function createSonareScopeRingBuffer(capacity = 64, bands = SONARE_SCOPE_DEFAULT_BANDS, maxPoints = SONARE_SCOPE_DEFAULT_MAX_POINTS) {
3363
3522
  const clampedCapacity = Math.max(1, Math.floor(capacity));
3364
3523
  const clampedBands = Math.max(1, Math.floor(bands));
3365
3524
  const clampedPoints = Math.max(0, Math.floor(maxPoints));
@@ -3426,8 +3585,14 @@ function scopeRingFromSharedBuffer(sharedBuffer, fallbackCapacity, fallbackBands
3426
3585
  const existingBands = Atomics.load(header, 3);
3427
3586
  const existingMaxPoints = Atomics.load(header, 4);
3428
3587
  const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
3429
- const bands = Math.max(1, Math.floor(existingBands || fallbackBands || 48));
3430
- const maxPoints = Math.max(0, Math.floor(existingMaxPoints || (fallbackMaxPoints ?? 32)));
3588
+ const bands = Math.max(
3589
+ 1,
3590
+ Math.floor(existingBands || fallbackBands || SONARE_SCOPE_DEFAULT_BANDS)
3591
+ );
3592
+ const maxPoints = Math.max(
3593
+ 0,
3594
+ Math.floor(existingMaxPoints || (fallbackMaxPoints ?? SONARE_SCOPE_DEFAULT_MAX_POINTS))
3595
+ );
3431
3596
  const recordFloats = sonareScopeRingRecordFloats(bands, maxPoints);
3432
3597
  const minBytes = sonareScopeRingBufferByteLength(capacity, bands, maxPoints);
3433
3598
  if (sharedBuffer.byteLength < minBytes) {
@@ -3832,7 +3997,9 @@ function meterFromEngine(meter) {
3832
3997
  momentaryLufs: meter.momentaryLufs,
3833
3998
  shortTermLufs: meter.shortTermLufs,
3834
3999
  integratedLufs: meter.integratedLufs,
3835
- gainReductionDb: meter.gainReductionDb
4000
+ gainReductionDb: meter.gainReductionDb,
4001
+ inputPeakDbL: meter.inputPeakDbL,
4002
+ inputPeakDbR: meter.inputPeakDbR
3836
4003
  };
3837
4004
  }
3838
4005
  function magnitudeToDb(value) {
@@ -3891,6 +4058,7 @@ var ENGINE_SYNC_MESSAGE_TYPES = {
3891
4058
  syncMidiInputPitchBend: true,
3892
4059
  syncMidiInputPolyPressure: true,
3893
4060
  syncMidiInputSource: true,
4061
+ syncMidiInputUmp: true,
3894
4062
  syncMidiNoteOff: true,
3895
4063
  syncMidiNoteOn: true,
3896
4064
  syncMidiPanic: true,
@@ -3911,6 +4079,7 @@ var ENGINE_SYNC_MESSAGE_TYPES = {
3911
4079
  syncTrackStripPan: true,
3912
4080
  syncTrackStripPanLaw: true,
3913
4081
  syncTrackStripPanMode: true,
4082
+ syncTrackStripSurroundPan: true,
3914
4083
  syncWarpVoiceCapacity: true
3915
4084
  };
3916
4085
  var engineSyncMessageTypes = new Set(Object.keys(ENGINE_SYNC_MESSAGE_TYPES));
@@ -3981,6 +4150,12 @@ function isEngineTransportRequestMessage(value) {
3981
4150
  function isEngineTransportResponseMessage(value) {
3982
4151
  return isRecord(value) && value.type === "transportResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
3983
4152
  }
4153
+ function isEngineInsertGainReductionRequestMessage(value) {
4154
+ return isRecord(value) && value.type === "insertGainReductionRequest" && typeof value.requestId === "number" && typeof value.targetId === "number";
4155
+ }
4156
+ function isEngineInsertGainReductionResponseMessage(value) {
4157
+ return isRecord(value) && value.type === "insertGainReductionResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
4158
+ }
3984
4159
  function isRealtimeVoiceChangerMessage(value) {
3985
4160
  if (!isRecord(value) || typeof value.type !== "string") {
3986
4161
  return false;
@@ -4017,7 +4192,23 @@ function isClipPageRequestMessage(value) {
4017
4192
  );
4018
4193
  }
4019
4194
  function isMeterSnapshot(value) {
4020
- 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);
4195
+ 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" && Number.isFinite(value.inputPeakDbL) && Number.isFinite(value.inputPeakDbR) && (typeof value.targetId === "number" || value.targetId === void 0);
4196
+ }
4197
+ var SONARE_SCOPE_MAX_BANDS = 64;
4198
+ var SONARE_SCOPE_MAX_POINTS = 32;
4199
+ function isPlainFloat32Array(value, maxLength, evenLength = false) {
4200
+ if (!(value instanceof Float32Array) || typeof ArrayBuffer === "undefined" || !(value.buffer instanceof ArrayBuffer) || value.length > maxLength || evenLength && value.length % 2 !== 0) {
4201
+ return false;
4202
+ }
4203
+ for (const sample of value) {
4204
+ if (!Number.isFinite(sample)) {
4205
+ return false;
4206
+ }
4207
+ }
4208
+ return true;
4209
+ }
4210
+ function isScopeSnapshot(value) {
4211
+ return isRecord(value) && value.type === "scope" && typeof value.targetId === "number" && Number.isSafeInteger(value.targetId) && value.targetId >= 0 && value.targetId <= 4294967295 && typeof value.frame === "number" && Number.isSafeInteger(value.frame) && value.frame >= 0 && isPlainFloat32Array(value.bands, SONARE_SCOPE_MAX_BANDS) && value.bands.length > 0 && isPlainFloat32Array(value.points, SONARE_SCOPE_MAX_POINTS * 2, true);
4021
4212
  }
4022
4213
  function requireIntegerOption(value, fallback, name, minimum) {
4023
4214
  const resolved = value ?? fallback;
@@ -4026,6 +4217,9 @@ function requireIntegerOption(value, fallback, name, minimum) {
4026
4217
  }
4027
4218
  return resolved;
4028
4219
  }
4220
+ function resolveScopeBandCount(value, fallback = SONARE_SCOPE_DEFAULT_BANDS) {
4221
+ return Math.min(requireIntegerOption(value, fallback, "scopeBands", 1), SONARE_SCOPE_MAX_BANDS);
4222
+ }
4029
4223
  function requireInteger(value, fallback, name) {
4030
4224
  const resolved = value ?? fallback;
4031
4225
  if (!Number.isSafeInteger(resolved)) {
@@ -4325,6 +4519,156 @@ function syncMarkers(ctx) {
4325
4519
  }
4326
4520
 
4327
4521
  // src/worklet/engine-mixer-facade.ts
4522
+ function isSceneObject(value) {
4523
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4524
+ }
4525
+ function hasOwn2(object, key) {
4526
+ return Object.getOwnPropertyDescriptor(object, key) !== void 0;
4527
+ }
4528
+ function canonicalJson(value) {
4529
+ if (Array.isArray(value)) {
4530
+ return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
4531
+ }
4532
+ if (value !== null && typeof value === "object") {
4533
+ const object = value;
4534
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`).join(",")}}`;
4535
+ }
4536
+ return JSON.stringify(value);
4537
+ }
4538
+ function aliasedValue(object, primary, legacy) {
4539
+ if (hasOwn2(object, primary)) {
4540
+ return { present: true, value: object[primary] };
4541
+ }
4542
+ if (hasOwn2(object, legacy)) {
4543
+ return { present: true, value: object[legacy] };
4544
+ }
4545
+ return { present: false, value: void 0 };
4546
+ }
4547
+ function insertSignature(insert) {
4548
+ const object = insert !== null && typeof insert === "object" ? insert : {};
4549
+ const slot = object.slot === void 0 ? "pre" : object.slot;
4550
+ const processor = aliasedValue(object, "processor", "processor_name");
4551
+ const params = aliasedValue(object, "params", "params_json");
4552
+ const sidechain = aliasedValue(object, "sidechainKey", "sidechain_key");
4553
+ const processorValue = typeof processor.value === "string" ? processor.value : "";
4554
+ const sidechainValue = typeof sidechain.value === "string" ? sidechain.value : "";
4555
+ let paramsValue;
4556
+ if (!params.present) {
4557
+ paramsValue = ["absent"];
4558
+ } else if (typeof params.value === "string") {
4559
+ if (params.value.length === 0) {
4560
+ paramsValue = ["absent"];
4561
+ } else {
4562
+ try {
4563
+ paramsValue = ["params", canonicalJson(JSON.parse(params.value))];
4564
+ } catch {
4565
+ paramsValue = ["params-invalid", params.value];
4566
+ }
4567
+ }
4568
+ } else {
4569
+ paramsValue = ["params", canonicalJson(params.value)];
4570
+ }
4571
+ return JSON.stringify([slot, processorValue, paramsValue, sidechainValue]);
4572
+ }
4573
+ function decodedInsertSignature(sceneJson, target) {
4574
+ const scene = JSON.parse(sceneJson);
4575
+ const entries = target.kind === "bus" ? scene.buses : scene.strips;
4576
+ const strip = Array.isArray(entries) ? entries.find(isSceneObject) : void 0;
4577
+ const object = isSceneObject(strip) ? strip : {};
4578
+ const inserts = Array.isArray(object.inserts) ? object.inserts.filter(isSceneObject) : [];
4579
+ const ordered = target.kind === "bus" ? inserts : [
4580
+ ...inserts.filter((insert) => insert.slot !== "post"),
4581
+ ...inserts.filter((insert) => insert.slot === "post")
4582
+ ];
4583
+ return JSON.stringify(ordered.map(insertSignature));
4584
+ }
4585
+ function constructionValue(engine, target, override) {
4586
+ let parameterId = -1;
4587
+ switch (target.kind) {
4588
+ case "track":
4589
+ parameterId = engine.resolveTrackInsertAutomationId(
4590
+ target.trackId,
4591
+ override.insertIndex,
4592
+ override.paramName
4593
+ );
4594
+ break;
4595
+ case "bus":
4596
+ parameterId = engine.resolveBusInsertAutomationId(
4597
+ target.busId,
4598
+ override.insertIndex,
4599
+ override.paramName
4600
+ );
4601
+ break;
4602
+ case "master":
4603
+ parameterId = engine.resolveMasterInsertAutomationId(
4604
+ override.insertIndex,
4605
+ override.paramName
4606
+ );
4607
+ break;
4608
+ }
4609
+ if (parameterId < 0) {
4610
+ return void 0;
4611
+ }
4612
+ return engine.insertParameterConstructedValue(parameterId);
4613
+ }
4614
+ function constructionInsertOverrides(engine, target, overrides) {
4615
+ const restored = [];
4616
+ for (const override of overrides.values()) {
4617
+ if (!sameStripTarget(override.target, target)) {
4618
+ continue;
4619
+ }
4620
+ const value = constructionValue(engine, target, override);
4621
+ if (value !== void 0) {
4622
+ restored.push({ ...override, value });
4623
+ }
4624
+ }
4625
+ return restored;
4626
+ }
4627
+ function applyInsertParamOverride(engine, override) {
4628
+ switch (override.target.kind) {
4629
+ case "track":
4630
+ engine.restoreTrackStripInsertParamByName(
4631
+ override.target.trackId,
4632
+ override.insertIndex,
4633
+ override.paramName,
4634
+ override.value
4635
+ );
4636
+ break;
4637
+ case "bus":
4638
+ engine.restoreBusStripInsertParamByName(
4639
+ override.target.busId,
4640
+ override.insertIndex,
4641
+ override.paramName,
4642
+ override.value
4643
+ );
4644
+ break;
4645
+ case "master":
4646
+ engine.restoreMasterStripInsertParamByName(
4647
+ override.insertIndex,
4648
+ override.paramName,
4649
+ override.value
4650
+ );
4651
+ break;
4652
+ }
4653
+ }
4654
+ function applyConstructionInsertOverrides(engine, overrides) {
4655
+ for (const override of overrides) {
4656
+ applyInsertParamOverride(engine, override);
4657
+ }
4658
+ }
4659
+ function clearInsertParameterBases(engine, target) {
4660
+ switch (target.kind) {
4661
+ case "track":
4662
+ engine.clearTrackInsertParameterBases(target.trackId);
4663
+ break;
4664
+ case "bus":
4665
+ engine.clearBusInsertParameterBases(target.busId);
4666
+ break;
4667
+ case "master":
4668
+ engine.clearMasterInsertParameterBases();
4669
+ break;
4670
+ }
4671
+ }
4328
4672
  function emptyStripJson(target) {
4329
4673
  switch (target.kind) {
4330
4674
  case "track":
@@ -4367,7 +4711,7 @@ function hasInsertParamOverrides(overrides, target) {
4367
4711
  }
4368
4712
  return false;
4369
4713
  }
4370
- function applyFullStripJson(engine, target, sceneJson, resetInserts) {
4714
+ function applyFullStripJson(engine, target, sceneJson) {
4371
4715
  const apply = (json) => {
4372
4716
  switch (target.kind) {
4373
4717
  case "track":
@@ -4383,21 +4727,6 @@ function applyFullStripJson(engine, target, sceneJson, resetInserts) {
4383
4727
  };
4384
4728
  engine.applyCommandsDueNowPreservingFuture();
4385
4729
  apply(sceneJson);
4386
- if (resetInserts) {
4387
- switch (target.kind) {
4388
- case "track":
4389
- engine.clearTrackInsertParameterBases(target.trackId);
4390
- break;
4391
- case "bus":
4392
- engine.clearBusInsertParameterBases(target.busId);
4393
- break;
4394
- case "master":
4395
- engine.clearMasterInsertParameterBases();
4396
- break;
4397
- }
4398
- apply(emptyStripJson(target));
4399
- apply(sceneJson);
4400
- }
4401
4730
  }
4402
4731
  function insertParamOverrideKey(override) {
4403
4732
  const targetId = override.target.kind === "track" ? override.target.trackId : override.target.kind === "bus" ? override.target.busId : "master";
@@ -4473,42 +4802,64 @@ function cacheStripJson(ctx, target, sceneJson) {
4473
4802
  }
4474
4803
  }
4475
4804
  function mixerLanes(ctx) {
4476
- return buildMixerLanes(ctx.trackLaneIds, ctx.trackSends, ctx.trackOutputBus);
4805
+ return buildMixerLanes(
4806
+ ctx.trackLaneIds,
4807
+ ctx.trackSends,
4808
+ ctx.trackOutputBus,
4809
+ ctx.trackSourceChannelLayout
4810
+ );
4811
+ }
4812
+ function cloneMixerRouting(ctx) {
4813
+ return {
4814
+ trackLaneIds: [...ctx.trackLaneIds],
4815
+ trackSends: new Map(
4816
+ Array.from(ctx.trackSends, ([trackId, sends]) => [
4817
+ trackId,
4818
+ sends.map((send) => ({ ...send }))
4819
+ ])
4820
+ ),
4821
+ trackOutputBus: new Map(ctx.trackOutputBus),
4822
+ trackSourceChannelLayout: new Map(ctx.trackSourceChannelLayout)
4823
+ };
4824
+ }
4825
+ function commitMixerRouting(ctx, draft) {
4826
+ ctx.trackLaneIds.splice(0, ctx.trackLaneIds.length, ...draft.trackLaneIds);
4827
+ ctx.trackSends.clear();
4828
+ for (const [trackId, sends] of draft.trackSends) {
4829
+ ctx.trackSends.set(
4830
+ trackId,
4831
+ sends.map((send) => ({ ...send }))
4832
+ );
4833
+ }
4834
+ ctx.trackOutputBus.clear();
4835
+ for (const [trackId, busId] of draft.trackOutputBus) {
4836
+ ctx.trackOutputBus.set(trackId, busId);
4837
+ }
4838
+ ctx.trackSourceChannelLayout.clear();
4839
+ for (const [trackId, layout] of draft.trackSourceChannelLayout) {
4840
+ ctx.trackSourceChannelLayout.set(trackId, layout);
4841
+ }
4477
4842
  }
4478
- function syncMixer(ctx, busesAlreadyApplied = false, forceInsertResets = []) {
4479
- const lanes = mixerLanes(ctx);
4843
+ function buildDraftMixerLanes(draft) {
4844
+ return buildMixerLanes(
4845
+ draft.trackLaneIds,
4846
+ draft.trackSends,
4847
+ draft.trackOutputBus,
4848
+ draft.trackSourceChannelLayout
4849
+ );
4850
+ }
4851
+ function applyMixerRouting(ctx, draft, busesAlreadyApplied) {
4852
+ const lanes = buildDraftMixerLanes(draft);
4480
4853
  const buses = ctx.buses.map((bus) => ({ ...bus }));
4481
4854
  if (!busesAlreadyApplied) {
4482
4855
  ctx.offlineEngine.setTrackBuses(buses);
4483
4856
  }
4484
4857
  if (lanes.length > 0) {
4485
- ctx.offlineEngine.settleInsertParameters();
4486
4858
  ctx.offlineEngine.setTrackLanes(lanes);
4487
4859
  }
4488
- if (forceInsertResets.length > 0) {
4489
- for (const binding of ctx.laneSidechains.values()) {
4490
- ctx.offlineEngine.setLaneSidechain(
4491
- binding.trackId,
4492
- binding.insertIndex,
4493
- binding.sourceTrackId
4494
- );
4495
- }
4496
- for (const binding of ctx.busSidechains.values()) {
4497
- ctx.offlineEngine.setBusSidechain(
4498
- binding.busId,
4499
- binding.insertIndex,
4500
- binding.sourceKind,
4501
- binding.sourceId
4502
- );
4503
- }
4504
- for (const binding of ctx.masterSidechains.values()) {
4505
- ctx.offlineEngine.setMasterSidechain(
4506
- binding.insertIndex,
4507
- binding.sourceKind,
4508
- binding.sourceId
4509
- );
4510
- }
4511
- }
4860
+ return { lanes, buses };
4861
+ }
4862
+ function postMixerSync(ctx, lanes, buses, insertBaseResets, oneShotInsertParamOverrides) {
4512
4863
  replayInsertParamOverrides(ctx, lanes, buses);
4513
4864
  const trackStrips = Array.from(ctx.trackStripJson, ([trackId, sceneJson]) => ({
4514
4865
  trackId,
@@ -4518,10 +4869,10 @@ function syncMixer(ctx, busesAlreadyApplied = false, forceInsertResets = []) {
4518
4869
  busId,
4519
4870
  sceneJson
4520
4871
  }));
4521
- const insertParamOverrides = Array.from(
4522
- ctx.insertParamOverrides.values(),
4523
- flattenInsertParamOverride
4524
- );
4872
+ const insertParamOverrides = [
4873
+ ...Array.from(ctx.insertParamOverrides.values(), flattenInsertParamOverride),
4874
+ ...oneShotInsertParamOverrides.map(flattenInsertParamOverride)
4875
+ ];
4525
4876
  ctx.postSync({
4526
4877
  type: "syncMixer",
4527
4878
  lanes,
@@ -4533,9 +4884,23 @@ function syncMixer(ctx, busesAlreadyApplied = false, forceInsertResets = []) {
4533
4884
  busSidechains: Array.from(ctx.busSidechains.values()),
4534
4885
  masterSidechains: Array.from(ctx.masterSidechains.values()),
4535
4886
  ...insertParamOverrides.length > 0 ? { insertParamOverrides } : {},
4536
- ...forceInsertResets.length > 0 ? { forceInsertResets } : {}
4887
+ ...insertBaseResets.length > 0 ? { insertBaseResets } : {}
4537
4888
  });
4538
4889
  }
4890
+ function syncMixerDraft(ctx, draft, busesAlreadyApplied = false, insertBaseResets = [], oneShotInsertParamOverrides = []) {
4891
+ const { lanes, buses } = applyMixerRouting(ctx, draft, busesAlreadyApplied);
4892
+ commitMixerRouting(ctx, draft);
4893
+ postMixerSync(ctx, lanes, buses, insertBaseResets, oneShotInsertParamOverrides);
4894
+ }
4895
+ function syncMixer(ctx, busesAlreadyApplied = false, insertBaseResets = [], oneShotInsertParamOverrides = []) {
4896
+ syncMixerDraft(
4897
+ ctx,
4898
+ cloneMixerRouting(ctx),
4899
+ busesAlreadyApplied,
4900
+ insertBaseResets,
4901
+ oneShotInsertParamOverrides
4902
+ );
4903
+ }
4539
4904
  function replayInsertParamOverrides(ctx, lanes = mixerLanes(ctx), buses = ctx.buses) {
4540
4905
  const activeTrackIds = new Set(lanes.map((lane) => lane.trackId));
4541
4906
  const activeBusIds = new Set(buses.map((bus) => bus.busId));
@@ -4573,34 +4938,59 @@ function replayInsertParamOverrides(ctx, lanes = mixerLanes(ctx), buses = ctx.bu
4573
4938
  }
4574
4939
  }
4575
4940
  function setTrackLanes(ctx, lanes) {
4576
- const { entries, ids } = normalizeTrackLanes(ctx.trackLaneIds, lanes);
4941
+ const draft = cloneMixerRouting(ctx);
4942
+ const { entries, ids } = normalizeTrackLanes(draft.trackLaneIds, lanes);
4577
4943
  for (const entry of entries) {
4578
4944
  if (entry.sends) {
4579
- ctx.trackSends.set(
4945
+ draft.trackSends.set(
4580
4946
  entry.trackId,
4581
4947
  entry.sends.map((send) => ({ ...send }))
4582
4948
  );
4583
4949
  }
4584
4950
  if (entry.outputBusId !== void 0) {
4585
4951
  if (entry.outputBusId === 0) {
4586
- ctx.trackOutputBus.delete(entry.trackId);
4952
+ draft.trackOutputBus.delete(entry.trackId);
4587
4953
  } else {
4588
- ctx.trackOutputBus.set(entry.trackId, entry.outputBusId);
4954
+ draft.trackOutputBus.set(entry.trackId, entry.outputBusId);
4589
4955
  }
4590
4956
  }
4957
+ if (entry.sourceChannelLayout !== void 0) {
4958
+ draft.trackSourceChannelLayout.set(entry.trackId, entry.sourceChannelLayout);
4959
+ }
4591
4960
  }
4592
- ctx.trackLaneIds.splice(0, ctx.trackLaneIds.length, ...ids);
4593
- ctx.syncMixer();
4961
+ draft.trackLaneIds = ids;
4962
+ syncMixerDraft(ctx, draft, true);
4963
+ }
4964
+ function ensureTrackLaneInDraft(draft, target) {
4965
+ const trackId = resolveTargetId(target);
4966
+ if (!Number.isInteger(trackId) || trackId <= 0) {
4967
+ throw new RangeError(`Invalid track id for mixer lane: ${String(target)}`);
4968
+ }
4969
+ const existing = draft.trackLaneIds.indexOf(trackId);
4970
+ if (existing >= 0) {
4971
+ return existing;
4972
+ }
4973
+ draft.trackLaneIds.push(trackId);
4974
+ return draft.trackLaneIds.length - 1;
4975
+ }
4976
+ function ensureTrackLane(ctx, target) {
4977
+ const draft = cloneMixerRouting(ctx);
4978
+ const laneIndex = ensureTrackLaneInDraft(draft, target);
4979
+ if (draft.trackLaneIds.length !== ctx.trackLaneIds.length) {
4980
+ syncMixerDraft(ctx, draft, true);
4981
+ }
4982
+ return laneIndex;
4594
4983
  }
4595
4984
  function setTrackOutputBus(ctx, target, busId) {
4596
- const laneIndex = ctx.ensureTrackLane(target);
4597
- const trackId = ctx.trackLaneIds[laneIndex];
4985
+ const draft = cloneMixerRouting(ctx);
4986
+ const laneIndex = ensureTrackLaneInDraft(draft, target);
4987
+ const trackId = draft.trackLaneIds[laneIndex];
4598
4988
  if (busId === 0) {
4599
- ctx.trackOutputBus.delete(trackId);
4989
+ draft.trackOutputBus.delete(trackId);
4600
4990
  } else {
4601
- ctx.trackOutputBus.set(trackId, busId);
4991
+ draft.trackOutputBus.set(trackId, busId);
4602
4992
  }
4603
- ctx.syncMixer();
4993
+ syncMixerDraft(ctx, draft, true);
4604
4994
  }
4605
4995
  function setLaneSidechain(ctx, target, insertIndex, sourceTarget) {
4606
4996
  const laneIndex = ctx.ensureTrackLane(target);
@@ -4669,13 +5059,14 @@ function ensureSidechainSource(ctx, sourceKind, sourceId) {
4669
5059
  }
4670
5060
  }
4671
5061
  function setSends(ctx, target, sends) {
4672
- const laneIndex = ctx.ensureTrackLane(target);
4673
- const trackId = ctx.trackLaneIds[laneIndex];
4674
- ctx.trackSends.set(
5062
+ const draft = cloneMixerRouting(ctx);
5063
+ const laneIndex = ensureTrackLaneInDraft(draft, target);
5064
+ const trackId = draft.trackLaneIds[laneIndex];
5065
+ draft.trackSends.set(
4675
5066
  trackId,
4676
5067
  sends.map((send) => ({ ...send }))
4677
5068
  );
4678
- ctx.syncMixer();
5069
+ syncMixerDraft(ctx, draft, true);
4679
5070
  }
4680
5071
  function setTrackBuses(ctx, buses) {
4681
5072
  ctx.offlineEngine.setTrackBuses(buses);
@@ -4709,18 +5100,45 @@ function setBusGain(ctx, busId, db) {
4709
5100
  ctx.buses[busIndex] = { ...ctx.buses[busIndex], busId, gainDb: db };
4710
5101
  return ctx.sendSmoothedParam(engineMixerBusTarget(busIndex, ENGINE_MIXER_PARAM_FADER_DB), db);
4711
5102
  }
5103
+ function replaceStripScene(ctx, target, previousJson, sceneJson) {
5104
+ const chainChanged = previousJson !== void 0 && decodedInsertSignature(previousJson, target) !== decodedInsertSignature(sceneJson, target);
5105
+ const hasOverrides = hasInsertParamOverrides(ctx.insertParamOverrides, target);
5106
+ applyFullStripJson(ctx.offlineEngine, target, sceneJson);
5107
+ if (!chainChanged && !hasOverrides) {
5108
+ return { insertBaseResets: [], constructionOverrides: [] };
5109
+ }
5110
+ const constructionOverrides = chainChanged ? [] : constructionInsertOverrides(ctx.offlineEngine, target, ctx.insertParamOverrides);
5111
+ ctx.clearInsertAutomationLanes(target);
5112
+ clearInsertParameterBases(ctx.offlineEngine, target);
5113
+ applyConstructionInsertOverrides(ctx.offlineEngine, constructionOverrides);
5114
+ clearInsertParamOverrides(ctx.insertParamOverrides, target);
5115
+ return { insertBaseResets: [target], constructionOverrides };
5116
+ }
4712
5117
  function setBusStripJson(ctx, busId, sceneJson) {
4713
- ctx.ensureBus(busId);
4714
5118
  const target = { kind: "bus", busId };
4715
- const resetInserts = ctx.busStripJson.has(busId) && ctx.busStripJson.get(busId) !== sceneJson || hasInsertParamOverrides(ctx.insertParamOverrides, target);
4716
- applyFullStripJson(ctx.offlineEngine, target, sceneJson, resetInserts);
4717
- if (resetInserts) {
4718
- ctx.clearInsertAutomationLanes(target);
4719
- }
5119
+ decodedInsertSignature(sceneJson, target);
5120
+ ctx.ensureBus(busId);
5121
+ const { insertBaseResets, constructionOverrides } = replaceStripScene(
5122
+ ctx,
5123
+ target,
5124
+ ctx.busStripJson.get(busId),
5125
+ sceneJson
5126
+ );
4720
5127
  pruneStripSidechains(ctx, target, sceneJson);
4721
5128
  ctx.busStripJson.set(busId, sceneJson);
4722
- clearInsertParamOverrides(ctx.insertParamOverrides, target);
4723
- ctx.syncMixer(resetInserts ? [target] : []);
5129
+ ctx.syncMixer(insertBaseResets, constructionOverrides);
5130
+ }
5131
+ function setMasterStripJson(ctx, sceneJson) {
5132
+ const target = { kind: "master" };
5133
+ const { insertBaseResets, constructionOverrides } = replaceStripScene(
5134
+ ctx,
5135
+ target,
5136
+ ctx.getMasterStripJson(),
5137
+ sceneJson
5138
+ );
5139
+ pruneStripSidechains(ctx, target, sceneJson);
5140
+ ctx.cacheMasterStripJson(sceneJson);
5141
+ ctx.syncMixer(insertBaseResets, constructionOverrides);
4724
5142
  }
4725
5143
 
4726
5144
  // src/worklet/engine-node.ts
@@ -4739,7 +5157,7 @@ function isTrackMonitorMode(value) {
4739
5157
  }
4740
5158
  function isValidCommandRecord(command) {
4741
5159
  const type = Number(command.type);
4742
- if (!Number.isSafeInteger(type) || type < 0 /* SetParam */ || type > 17 /* SeekMarker */ && type !== 26 /* SetTrackMonitorMode */) {
5160
+ if (!Number.isSafeInteger(type) || type < 0 /* SetParam */ || type > 17 /* SeekMarker */ && type !== 26 /* SetTrackMonitorMode */ && type !== 28 /* ResetMasterLoudnessMeter */) {
4743
5161
  return false;
4744
5162
  }
4745
5163
  return (
@@ -4775,6 +5193,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
4775
5193
  this.captureRequests = /* @__PURE__ */ new Map();
4776
5194
  this.transportRequestId = 1;
4777
5195
  this.transportRequests = /* @__PURE__ */ new Map();
5196
+ this.insertGainReductionRequestId = 1;
5197
+ this.insertGainReductionRequests = /* @__PURE__ */ new Map();
4778
5198
  this.destroyed = false;
4779
5199
  this.node = node;
4780
5200
  this.capabilities = capabilities;
@@ -4820,10 +5240,22 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
4820
5240
  pending.reject(new Error(event.data.error ?? "Transport request failed"));
4821
5241
  }
4822
5242
  }
5243
+ } else if (isEngineInsertGainReductionResponseMessage(event.data)) {
5244
+ const pending = this.insertGainReductionRequests.get(event.data.requestId);
5245
+ if (pending) {
5246
+ this.insertGainReductionRequests.delete(event.data.requestId);
5247
+ if (event.data.ok && Array.isArray(event.data.values)) {
5248
+ pending.resolve(event.data.values);
5249
+ } else {
5250
+ pending.reject(new Error(event.data.error ?? "Insert gain reduction request failed"));
5251
+ }
5252
+ }
4823
5253
  } else if (isEngineTelemetryRecord(event.data)) {
4824
5254
  this.emitTelemetry(event.data);
4825
5255
  } else if (isMeterSnapshot(event.data)) {
4826
5256
  this.emitMeter(event.data);
5257
+ } else if (isScopeSnapshot(event.data)) {
5258
+ this.emitScope(event.data);
4827
5259
  } else if (isExternalMidiBatchMessage(event.data)) {
4828
5260
  this.emitMidiOut(event.data.events);
4829
5261
  } else if (isClipPageRequestMessage(event.data)) {
@@ -4876,6 +5308,12 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
4876
5308
  "SharedArrayBuffer mode requested but SharedArrayBuffer/Atomics are unavailable."
4877
5309
  );
4878
5310
  }
5311
+ const meterIntervalFrames = requireIntegerOption(
5312
+ options.meterIntervalFrames,
5313
+ 2048,
5314
+ "meterIntervalFrames",
5315
+ 0
5316
+ );
4879
5317
  const commandRingCapacity = requireIntegerOption(
4880
5318
  options.commandRingCapacity,
4881
5319
  128,
@@ -4903,7 +5341,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
4903
5341
  "scopeRingCapacity",
4904
5342
  1
4905
5343
  );
4906
- const scopeBands = requireIntegerOption(options.scopeBands, 48, "scopeBands", 1);
5344
+ const scopeBands = resolveScopeBandCount(options.scopeBands);
4907
5345
  const clipPageRequestRingCapacity = requireIntegerOption(
4908
5346
  options.clipPageRequestRingCapacity,
4909
5347
  128,
@@ -4934,6 +5372,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
4934
5372
  sampleRate: options.sampleRate ?? context.sampleRate,
4935
5373
  blockSize,
4936
5374
  channelCount,
5375
+ meterIntervalFrames,
4937
5376
  cueOutput,
4938
5377
  commandSharedBuffer: commandRing?.sharedBuffer,
4939
5378
  commandRingCapacity: commandRing?.capacity,
@@ -4943,8 +5382,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
4943
5382
  meterRingCapacity: meterRing?.capacity,
4944
5383
  scopeSharedBuffer: scopeRing?.sharedBuffer,
4945
5384
  scopeRingCapacity: scopeRing?.capacity,
4946
- scopeBands: scopeRing?.bands,
4947
- scopeIntervalFrames: scopeRing ? scopeIntervalFrames : void 0,
5385
+ scopeBands: scopeIntervalFrames > 0 ? scopeRing?.bands ?? scopeBands : void 0,
5386
+ scopeIntervalFrames: scopeIntervalFrames > 0 ? scopeIntervalFrames : void 0,
4948
5387
  clipPageRequestSharedBuffer: clipPageRequestRing?.sharedBuffer,
4949
5388
  clipPageRequestRingCapacity: clipPageRequestRing?.capacity,
4950
5389
  externalMidiSharedBuffer: externalMidiRing?.sharedBuffer,
@@ -4993,6 +5432,10 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
4993
5432
  play(sampleTime = -1) {
4994
5433
  return this.sendCommand({ type: 2 /* TransportPlay */, sampleTime });
4995
5434
  }
5435
+ /** Queues a reset of the master's integrated-loudness accumulator. */
5436
+ resetMasterLoudnessMeter(sampleTime = -1) {
5437
+ return this.sendCommand({ type: 28 /* ResetMasterLoudnessMeter */, sampleTime });
5438
+ }
4996
5439
  stop(sampleTime = -1) {
4997
5440
  return this.sendCommand({ type: 3 /* TransportStop */, sampleTime });
4998
5441
  }
@@ -5200,6 +5643,10 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
5200
5643
  pending.reject(new Error("Realtime engine node is destroyed."));
5201
5644
  }
5202
5645
  this.transportRequests.clear();
5646
+ for (const pending of this.insertGainReductionRequests.values()) {
5647
+ pending.reject(new Error("Realtime engine node is destroyed."));
5648
+ }
5649
+ this.insertGainReductionRequests.clear();
5203
5650
  this.telemetryListeners.clear();
5204
5651
  this.meterListeners.clear();
5205
5652
  this.scopeListeners.clear();
@@ -5249,6 +5696,29 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
5249
5696
  listener(message);
5250
5697
  }
5251
5698
  }
5699
+ /**
5700
+ * Reads one target's per-insert gain reduction (dB, each <= 0, pre-to-post insert order)
5701
+ * from the worklet. Works in both ring and postMessage meter modes; rejects when the
5702
+ * target is invalid or mixing is not compiled in.
5703
+ */
5704
+ pollInsertGainReduction(targetId) {
5705
+ if (this.destroyed) {
5706
+ return Promise.reject(new Error("Realtime engine node is destroyed."));
5707
+ }
5708
+ const requestId = this.insertGainReductionRequestId++;
5709
+ let rejectRequest;
5710
+ const promise = new Promise((resolve, reject) => {
5711
+ rejectRequest = reject;
5712
+ this.insertGainReductionRequests.set(requestId, { resolve, reject });
5713
+ });
5714
+ try {
5715
+ this.node.port.postMessage({ type: "insertGainReductionRequest", requestId, targetId });
5716
+ } catch (error) {
5717
+ this.insertGainReductionRequests.delete(requestId);
5718
+ rejectRequest(error);
5719
+ }
5720
+ return promise;
5721
+ }
5252
5722
  emitScope(scope) {
5253
5723
  for (const listener of this.scopeListeners) {
5254
5724
  listener(scope);
@@ -5397,11 +5867,9 @@ function setInsertParamByName(ctx, target, insertIndex, paramName, value, applyN
5397
5867
  }
5398
5868
  function setTrackStripJson(ctx, trackId, sceneJson, trackStripJson) {
5399
5869
  const target = { kind: "track", trackId };
5400
- const resetInserts = trackStripJson.has(trackId) && trackStripJson.get(trackId) !== sceneJson || hasInsertParamOverrides(ctx.insertParamOverrides, target);
5401
- applyFullStripJson(ctx.offlineEngine, target, sceneJson, resetInserts);
5402
- clearInsertParamOverrides(ctx.insertParamOverrides, target);
5870
+ const result = replaceStripScene(ctx, target, trackStripJson.get(trackId), sceneJson);
5403
5871
  trackStripJson.set(trackId, sceneJson);
5404
- return { resetInserts };
5872
+ return result;
5405
5873
  }
5406
5874
  function setTrackStripEqBand(ctx, target, bandIndex, band) {
5407
5875
  const trackId = trackIdFor(ctx, target);
@@ -5468,6 +5936,21 @@ function setTrackStripDualPan(ctx, target, leftPan, rightPan) {
5468
5936
  });
5469
5937
  ctx.postSync({ type: "syncTrackStripDualPan", trackId, leftPan, rightPan });
5470
5938
  }
5939
+ function setTrackStripSurroundPan(ctx, target, pan) {
5940
+ const trackId = trackIdFor(ctx, target);
5941
+ ctx.offlineEngine.setTrackStripSurroundPan(trackId, pan);
5942
+ const resolved = {
5943
+ azimuth: pan.azimuth ?? 0,
5944
+ elevation: pan.elevation ?? 0,
5945
+ divergence: pan.divergence ?? 0,
5946
+ lfe: pan.lfe ?? 0,
5947
+ distance: pan.distance === void 0 || pan.distance <= 0 ? 1 : pan.distance
5948
+ };
5949
+ mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
5950
+ entry.surroundPan = resolved;
5951
+ });
5952
+ ctx.postSync({ type: "syncTrackStripSurroundPan", trackId, pan: resolved });
5953
+ }
5471
5954
  function setTrackStripChannelDelaySamples(ctx, target, delaySamples) {
5472
5955
  const trackId = trackIdFor(ctx, target);
5473
5956
  ctx.offlineEngine.setTrackStripChannelDelaySamples(trackId, delaySamples);
@@ -5641,8 +6124,14 @@ function pushMidiPolyPressure(ctx, trackId, group, channel, note, pressure, rend
5641
6124
  }
5642
6125
  function pushMidiUmp(ctx, trackId, word0, renderFrame) {
5643
6126
  const destinationId = ctx.resolveTargetId(trackId);
5644
- ctx.offlineEngine.pushMidiUmp(destinationId, [word0], renderFrame);
5645
- ctx.postSync({ type: "syncMidiUmp", destinationId, word0, renderFrame });
6127
+ if (typeof word0 === "number") {
6128
+ ctx.offlineEngine.pushMidiUmp(destinationId, word0, renderFrame);
6129
+ ctx.postSync({ type: "syncMidiUmp", destinationId, word0, renderFrame });
6130
+ return;
6131
+ }
6132
+ const words = word0 instanceof Uint32Array ? new Uint32Array(word0) : word0.slice();
6133
+ ctx.offlineEngine.pushMidiUmp(destinationId, words, renderFrame);
6134
+ ctx.postSync({ type: "syncMidiUmp", destinationId, words, renderFrame });
5646
6135
  }
5647
6136
  function setBuiltinInstrument(ctx, trackId, config) {
5648
6137
  const destinationId = ctx.resolveTargetId(trackId);
@@ -5774,6 +6263,7 @@ var SonareEngine = class _SonareEngine {
5774
6263
  this.trackLaneIds = [];
5775
6264
  this.trackSends = /* @__PURE__ */ new Map();
5776
6265
  this.trackOutputBus = /* @__PURE__ */ new Map();
6266
+ this.trackSourceChannelLayout = /* @__PURE__ */ new Map();
5777
6267
  this.laneSidechains = /* @__PURE__ */ new Map();
5778
6268
  this.busSidechains = /* @__PURE__ */ new Map();
5779
6269
  this.masterSidechains = /* @__PURE__ */ new Map();
@@ -5875,6 +6365,11 @@ var SonareEngine = class _SonareEngine {
5875
6365
  setTimeSignatureSegments(segments) {
5876
6366
  setTimeSignatureSegments(this.tempoContext, segments);
5877
6367
  }
6368
+ /**
6369
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
6370
+ * centred pitch bend on every channel played since the last reset, so
6371
+ * controller values set before a loop region are not restored at the wrap.
6372
+ */
5878
6373
  setLoop(startPpq, endPpq, enabled = true) {
5879
6374
  return setLoop(this.tempoContext, startPpq, endPpq, enabled);
5880
6375
  }
@@ -5933,6 +6428,15 @@ var SonareEngine = class _SonareEngine {
5933
6428
  * mixer lane first (like automationParamId) so the offline engine resolves the
5934
6429
  * same strip selector the realtime engine uses.
5935
6430
  *
6431
+ * The returned id uses the track's current positional lane selector. When
6432
+ * setTrackLanes successfully changes lane order or membership, the engine
6433
+ * remaps already queued and published track automation by track id, but it
6434
+ * cannot update a numeric id retained by the caller. Re-resolve every track
6435
+ * insert id after such a topology change before passing it to
6436
+ * setAutomationLane. Use setTrackStripInsertParamByName when the operation
6437
+ * needs a stable track identity. Master and bus insert ids are separate and
6438
+ * are not invalidated by track-lane changes.
6439
+ *
5936
6440
  * @param target Track id (declares a mixer lane on first use).
5937
6441
  * @param insertIndex Index into the strip's combined insert sequence.
5938
6442
  * @param paramName Processor JSON-key parameter name.
@@ -6046,14 +6550,23 @@ var SonareEngine = class _SonareEngine {
6046
6550
  }
6047
6551
  return sent;
6048
6552
  }
6553
+ /** Resets the master's integrated loudness accumulator in both engine mirrors. */
6554
+ resetMasterLoudnessMeter(renderFrame = -1) {
6555
+ this.offlineEngine.resetMasterLoudnessMeter(renderFrame);
6556
+ return this.sendMirroredCommand({
6557
+ type: 28 /* ResetMasterLoudnessMeter */,
6558
+ sampleTime: renderFrame
6559
+ });
6560
+ }
6049
6561
  /**
6050
6562
  * Declares the mixer track lanes in an explicit order.
6051
6563
  *
6052
- * Lane indices are append-only: once a track id occupies a lane, its index
6053
- * stays fixed for the engine's lifetime. The given list must therefore start
6054
- * with the already-declared lane ids in their current order and may only
6055
- * append new track ids after them. Entries carrying `sends` replace that
6056
- * track's send list; entries without `sends` leave existing sends untouched.
6564
+ * A successful call may reorder existing track ids, remove track ids, or add
6565
+ * new ones. Existing queued and published track automation follows the track
6566
+ * id, while numeric track-insert automation ids retained by the caller do
6567
+ * not; re-resolve those ids after every topology change. Entries carrying
6568
+ * `sends` replace that track's send list; entries without `sends` leave
6569
+ * existing sends untouched.
6057
6570
  *
6058
6571
  * @param lanes Track ids or lane descriptors in the desired lane order.
6059
6572
  */
@@ -6070,7 +6583,9 @@ var SonareEngine = class _SonareEngine {
6070
6583
  /**
6071
6584
  * Keys one insert of a lane strip from another lane's post-strip pre-fader
6072
6585
  * audio (ducking/sidechainRouter inserts). sourceTarget null removes the
6073
- * binding.
6586
+ * binding. Throws, leaving the existing bindings unchanged, for a lane keying
6587
+ * itself or a binding that would close a cycle over the lane bindings; a
6588
+ * refused binding is never recorded, so it is not replayed on resync.
6074
6589
  */
6075
6590
  setLaneSidechain(target, insertIndex, sourceTarget) {
6076
6591
  setLaneSidechain(this.mixerContext, target, insertIndex, sourceTarget);
@@ -6099,19 +6614,17 @@ var SonareEngine = class _SonareEngine {
6099
6614
  return setBusGain(this.mixerContext, busId, db);
6100
6615
  }
6101
6616
  setTrackStripJson(target, sceneJson) {
6617
+ decodedInsertSignature(sceneJson, { kind: "track", trackId: 0 });
6102
6618
  const laneIndex = this.ensureTrackLane(target);
6103
6619
  const trackId = this.trackLaneIds[laneIndex];
6104
- const { resetInserts } = setTrackStripJson(
6620
+ const { insertBaseResets, constructionOverrides } = setTrackStripJson(
6105
6621
  this.stripContext,
6106
6622
  trackId,
6107
6623
  sceneJson,
6108
6624
  this.trackStripJson
6109
6625
  );
6110
- if (resetInserts) {
6111
- this.clearInsertAutomationLanes({ kind: "track", trackId });
6112
- }
6113
6626
  pruneStripSidechains(this.mixerContext, { kind: "track", trackId }, sceneJson);
6114
- this.syncMixer(resetInserts ? [{ kind: "track", trackId }] : []);
6627
+ this.syncMixer(insertBaseResets, constructionOverrides);
6115
6628
  }
6116
6629
  setTrackStripEqBand(target, bandIndex, band) {
6117
6630
  setTrackStripEqBand(this.stripContext, target, bandIndex, band);
@@ -6140,6 +6653,14 @@ var SonareEngine = class _SonareEngine {
6140
6653
  setTrackStripDualPan(target, leftPan, rightPan) {
6141
6654
  setTrackStripDualPan(this.stripContext, target, leftPan, rightPan);
6142
6655
  }
6656
+ /**
6657
+ * Sets a track lane strip's surround placement (glitch-free, 5 ms placement
6658
+ * glide). Takes effect on a destination wider than stereo, so set `channelCount`
6659
+ * to the layout's plane count. Omitted fields default to a centered point source.
6660
+ */
6661
+ setTrackStripSurroundPan(target, pan) {
6662
+ setTrackStripSurroundPan(this.stripContext, target, pan);
6663
+ }
6143
6664
  setTrackStripChannelDelaySamples(target, delaySamples) {
6144
6665
  setTrackStripChannelDelaySamples(this.stripContext, target, delaySamples);
6145
6666
  }
@@ -6168,16 +6689,7 @@ var SonareEngine = class _SonareEngine {
6168
6689
  setBusStripJson(this.mixerContext, busId, sceneJson);
6169
6690
  }
6170
6691
  setMasterStripJson(sceneJson) {
6171
- const target = { kind: "master" };
6172
- const resetInserts = this.masterStripJson !== void 0 && this.masterStripJson !== sceneJson || hasInsertParamOverrides(this.insertParamOverrides, target);
6173
- applyFullStripJson(this.offlineEngine, target, sceneJson, resetInserts);
6174
- if (resetInserts) {
6175
- this.clearInsertAutomationLanes(target);
6176
- }
6177
- pruneStripSidechains(this.mixerContext, target, sceneJson);
6178
- clearInsertParamOverrides(this.insertParamOverrides, target);
6179
- this.masterStripJson = sceneJson;
6180
- this.syncMixer(resetInserts ? [target] : []);
6692
+ setMasterStripJson(this.mixerContext, sceneJson);
6181
6693
  }
6182
6694
  setMasterStripEqBand(bandIndex, band) {
6183
6695
  setMasterStripEqBand(this.stripContext, bandIndex, band);
@@ -6380,7 +6892,10 @@ var SonareEngine = class _SonareEngine {
6380
6892
  /**
6381
6893
  * Route a track's MIDI to the external output (drained via {@link onMidiOut})
6382
6894
  * instead of an internal instrument, so the track plays an external device.
6383
- * Pass `external=false` to restore internal-synth playback.
6895
+ * Pass `external=false` to restore internal-synth playback. The change takes
6896
+ * effect at the next processed block; switching a destination's route first
6897
+ * releases its notes and resets its controllers through the old route, and
6898
+ * drops its pending MIDI-FX events.
6384
6899
  */
6385
6900
  setMidiDestinationExternal(trackId, external) {
6386
6901
  setMidiDestinationExternal(this.stripContext, trackId, external);
@@ -6441,8 +6956,8 @@ var SonareEngine = class _SonareEngine {
6441
6956
  renderFrame
6442
6957
  );
6443
6958
  }
6444
- pushMidiUmp(trackId, word0, renderFrame = -1) {
6445
- pushMidiUmp(this.stripContext, trackId, word0, renderFrame);
6959
+ pushMidiUmp(trackId, words, renderFrame = -1) {
6960
+ pushMidiUmp(this.stripContext, trackId, words, renderFrame);
6446
6961
  }
6447
6962
  pushMidiSysex(trackId, data, renderFrame = -1) {
6448
6963
  pushMidiSysex(this.stripContext, trackId, data, renderFrame);
@@ -6527,6 +7042,11 @@ var SonareEngine = class _SonareEngine {
6527
7042
  portTimeSamples
6528
7043
  });
6529
7044
  }
7045
+ pushMidiInputUmp(words, portTimeSamples = 0) {
7046
+ const copiedWords = words instanceof Uint32Array ? new Uint32Array(words) : words.slice();
7047
+ this.offlineEngine.pushMidiInputUmp(copiedWords, portTimeSamples);
7048
+ this.postSync({ type: "syncMidiInputUmp", words: copiedWords, portTimeSamples });
7049
+ }
6530
7050
  pushMidiPanic(renderFrame = -1) {
6531
7051
  this.offlineEngine.pushMidiPanic(renderFrame);
6532
7052
  this.postSync({ type: "syncMidiPanic", renderFrame });
@@ -6622,6 +7142,10 @@ var SonareEngine = class _SonareEngine {
6622
7142
  pollScope() {
6623
7143
  return this.realtimeNode.pollScope();
6624
7144
  }
7145
+ /** Per-insert gain reduction for a meter target; see {@link SonareRealtimeEngineNode.pollInsertGainReduction}. */
7146
+ pollInsertGainReduction(targetId) {
7147
+ return this.realtimeNode.pollInsertGainReduction(targetId);
7148
+ }
6625
7149
  destroy() {
6626
7150
  if (this.destroyed) {
6627
7151
  return;
@@ -6724,8 +7248,8 @@ var SonareEngine = class _SonareEngine {
6724
7248
  mixerLanes() {
6725
7249
  return mixerLanes(this.mixerContext);
6726
7250
  }
6727
- syncMixer(forceInsertResets = []) {
6728
- syncMixer(this.mixerContext, false, forceInsertResets);
7251
+ syncMixer(insertBaseResets = [], oneShotInsertParamOverrides = []) {
7252
+ syncMixer(this.mixerContext, false, insertBaseResets, oneShotInsertParamOverrides);
6729
7253
  }
6730
7254
  insertAutomationTargetKey(target) {
6731
7255
  return target.kind === "track" ? `track:${target.trackId}` : target.kind === "bus" ? `bus:${target.busId}` : "master";
@@ -6803,6 +7327,7 @@ var SonareEngine = class _SonareEngine {
6803
7327
  trackLaneIds: this.trackLaneIds,
6804
7328
  trackSends: this.trackSends,
6805
7329
  trackOutputBus: this.trackOutputBus,
7330
+ trackSourceChannelLayout: this.trackSourceChannelLayout,
6806
7331
  laneSidechains: this.laneSidechains,
6807
7332
  busSidechains: this.busSidechains,
6808
7333
  masterSidechains: this.masterSidechains,
@@ -6815,7 +7340,7 @@ var SonareEngine = class _SonareEngine {
6815
7340
  ensureTrackLane: (target) => this.ensureTrackLane(target),
6816
7341
  ensureBus: (busId) => this.ensureBus(busId),
6817
7342
  mixerLanes: () => this.mixerLanes(),
6818
- syncMixer: (forceInsertResets) => this.syncMixer(forceInsertResets),
7343
+ syncMixer: (insertBaseResets, oneShotInsertParamOverrides) => this.syncMixer(insertBaseResets, oneShotInsertParamOverrides),
6819
7344
  clearInsertAutomationLanes: (target, forgetResolvedIds) => this.clearInsertAutomationLanes(target, forgetResolvedIds),
6820
7345
  sendSmoothedParam: (paramId, value) => this.sendSmoothedParam(paramId, value),
6821
7346
  getMasterStripJson: () => this.masterStripJson,
@@ -6837,7 +7362,8 @@ var SonareEngine = class _SonareEngine {
6837
7362
  resolveTargetId: (target) => this.resolveTargetId(target),
6838
7363
  readStripJson: (target) => cachedStripJson(this.mixerContext, target),
6839
7364
  writeStripJson: (target, sceneJson) => cacheStripJson(this.mixerContext, target, sceneJson),
6840
- insertParamOverrides: this.insertParamOverrides
7365
+ insertParamOverrides: this.insertParamOverrides,
7366
+ clearInsertAutomationLanes: (target) => this.clearInsertAutomationLanes(target)
6841
7367
  };
6842
7368
  }
6843
7369
  // Collaborator surface handed to the automation-lane free functions so they
@@ -6977,17 +7503,7 @@ var SonareEngine = class _SonareEngine {
6977
7503
  return resolveTargetId(target);
6978
7504
  }
6979
7505
  ensureTrackLane(target) {
6980
- const trackId = this.resolveTargetId(target);
6981
- if (!Number.isInteger(trackId) || trackId <= 0) {
6982
- throw new RangeError(`Invalid track id for mixer lane: ${String(target)}`);
6983
- }
6984
- const existing = this.trackLaneIds.indexOf(trackId);
6985
- if (existing >= 0) {
6986
- return existing;
6987
- }
6988
- this.trackLaneIds.push(trackId);
6989
- this.syncMixer();
6990
- return this.trackLaneIds.length - 1;
7506
+ return ensureTrackLane(this.mixerContext, target);
6991
7507
  }
6992
7508
  ensureBus(busId) {
6993
7509
  if (!Number.isInteger(busId) || busId <= 0) {
@@ -7099,6 +7615,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
7099
7615
  options.scopeRingCapacity,
7100
7616
  options.scopeBands
7101
7617
  ) : void 0;
7618
+ this.scopeIntervalFrames = requireIntegerOption(
7619
+ options.scopeIntervalFrames,
7620
+ this.scopeRing ? this.blockSize : 0,
7621
+ "scopeIntervalFrames",
7622
+ 0
7623
+ );
7624
+ this.scopeBands = resolveScopeBandCount(options.scopeBands);
7625
+ this.scopeEnabled = this.scopeIntervalFrames > 0 && (this.scopeRing !== void 0 || typeof this.transport?.postMessage === "function");
7102
7626
  this.clipPageRequestRing = options.clipPageRequestSharedBuffer ? clipPageRequestRingFromSharedBuffer(
7103
7627
  options.clipPageRequestSharedBuffer,
7104
7628
  options.clipPageRequestRingCapacity
@@ -7130,14 +7654,11 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
7130
7654
  this.monitorBuffers[ch] = this.engine.getMonitorChannelBuffer(ch, this.blockSize);
7131
7655
  }
7132
7656
  }
7133
- if (this.scopeRing) {
7134
- const interval = requireIntegerOption(
7135
- options.scopeIntervalFrames,
7136
- this.blockSize,
7137
- "scopeIntervalFrames",
7138
- 0
7657
+ if (this.scopeEnabled) {
7658
+ this.engine.configureScopeTelemetry(
7659
+ this.scopeIntervalFrames,
7660
+ Math.min(this.scopeRing?.bands ?? this.scopeBands, SONARE_SCOPE_MAX_BANDS)
7139
7661
  );
7140
- this.engine.configureScopeTelemetry(interval, this.scopeRing.bands);
7141
7662
  }
7142
7663
  }
7143
7664
  process(inputs, outputs) {
@@ -7398,37 +7919,32 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
7398
7919
  this.drainCommands(Math.min(this.commandRing.capacity, Math.max(0, write - read)));
7399
7920
  }
7400
7921
  this.engine.applyCommandsDueNowPreservingFuture();
7401
- this.engine.settleInsertParameters();
7402
7922
  if (message.buses) {
7403
7923
  this.engine.setTrackBuses(message.buses);
7404
7924
  }
7405
7925
  this.engine.setTrackLanes(message.lanes);
7406
- for (const target of message.forceInsertResets ?? []) {
7407
- const emptyJson = emptyStripJson(target);
7926
+ for (const strip of message.trackStrips ?? []) {
7927
+ this.engine.setTrackStripJson(strip.trackId, strip.sceneJson);
7928
+ }
7929
+ for (const strip of message.busStrips ?? []) {
7930
+ this.engine.setBusStripJson(strip.busId, strip.sceneJson);
7931
+ }
7932
+ if (message.masterStripJson) {
7933
+ this.engine.setMasterStripJson(message.masterStripJson);
7934
+ }
7935
+ for (const target of message.insertBaseResets ?? []) {
7408
7936
  switch (target.kind) {
7409
7937
  case "track":
7410
7938
  this.engine.clearTrackInsertParameterBases(target.trackId);
7411
- this.engine.setTrackStripJson(target.trackId, emptyJson);
7412
7939
  break;
7413
7940
  case "bus":
7414
7941
  this.engine.clearBusInsertParameterBases(target.busId);
7415
- this.engine.setBusStripJson(target.busId, emptyJson);
7416
7942
  break;
7417
7943
  case "master":
7418
7944
  this.engine.clearMasterInsertParameterBases();
7419
- this.engine.setMasterStripJson(emptyJson);
7420
7945
  break;
7421
7946
  }
7422
7947
  }
7423
- for (const strip of message.trackStrips ?? []) {
7424
- this.engine.setTrackStripJson(strip.trackId, strip.sceneJson);
7425
- }
7426
- for (const strip of message.busStrips ?? []) {
7427
- this.engine.setBusStripJson(strip.busId, strip.sceneJson);
7428
- }
7429
- if (message.masterStripJson) {
7430
- this.engine.setMasterStripJson(message.masterStripJson);
7431
- }
7432
7948
  for (const binding of message.laneSidechains ?? []) {
7433
7949
  this.engine.setLaneSidechain(binding.trackId, binding.insertIndex, binding.sourceTrackId);
7434
7950
  }
@@ -7568,6 +8084,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
7568
8084
  case "syncTrackStripDualPan":
7569
8085
  this.engine.setTrackStripDualPan(message.trackId, message.leftPan, message.rightPan);
7570
8086
  break;
8087
+ case "syncTrackStripSurroundPan":
8088
+ this.engine.setTrackStripSurroundPan(message.trackId, message.pan);
8089
+ break;
7571
8090
  case "syncBusStripPan":
7572
8091
  this.engine.setBusStripPan(message.busId, message.pan);
7573
8092
  break;
@@ -7660,7 +8179,11 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
7660
8179
  );
7661
8180
  break;
7662
8181
  case "syncMidiUmp":
7663
- this.engine.pushMidiUmp(message.destinationId, [message.word0], message.renderFrame);
8182
+ this.engine.pushMidiUmp(
8183
+ message.destinationId,
8184
+ message.words ?? [message.word0],
8185
+ message.renderFrame
8186
+ );
7664
8187
  break;
7665
8188
  case "syncMidiSysex":
7666
8189
  this.engine.pushMidiSysex(message.destinationId, message.data, message.renderFrame);
@@ -7738,6 +8261,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
7738
8261
  message.portTimeSamples
7739
8262
  );
7740
8263
  break;
8264
+ case "syncMidiInputUmp":
8265
+ this.engine.pushMidiInputUmp(message.words, message.portTimeSamples);
8266
+ break;
7741
8267
  }
7742
8268
  }
7743
8269
  receiveCaptureRequest(message) {
@@ -7811,6 +8337,26 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
7811
8337
  });
7812
8338
  }
7813
8339
  }
8340
+ receiveInsertGainReductionRequest(message) {
8341
+ if (this.closed) {
8342
+ return;
8343
+ }
8344
+ try {
8345
+ this.transport?.postMessage?.({
8346
+ type: "insertGainReductionResponse",
8347
+ requestId: message.requestId,
8348
+ ok: true,
8349
+ values: this.engine.meterTargetInsertGainReduction(message.targetId)
8350
+ });
8351
+ } catch (error) {
8352
+ this.transport?.postMessage?.({
8353
+ type: "insertGainReductionResponse",
8354
+ requestId: message.requestId,
8355
+ ok: false,
8356
+ error: error instanceof Error ? error.message : String(error)
8357
+ });
8358
+ }
8359
+ }
7814
8360
  destroy() {
7815
8361
  if (!this.closed) {
7816
8362
  this.clearPagedClipProviders();
@@ -7924,6 +8470,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
7924
8470
  this.engine.setTrackMonitorMode(laneIndex, mode, sampleTime);
7925
8471
  break;
7926
8472
  }
8473
+ case 28 /* ResetMasterLoudnessMeter */:
8474
+ this.engine.resetMasterLoudnessMeter(sampleTime);
8475
+ break;
7927
8476
  default:
7928
8477
  this.publishTelemetryRecord({
7929
8478
  type: 1 /* Error */,
@@ -8001,6 +8550,8 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
8001
8550
  }
8002
8551
  return;
8003
8552
  }
8553
+ const delivered = [];
8554
+ const newestByTarget = /* @__PURE__ */ new Map();
8004
8555
  for (const item of this.engine.drainMeterTelemetry(64)) {
8005
8556
  const meter = meterFromEngine(item);
8006
8557
  if (meter.frame !== this.lastMeterFrame && meter.frame - this.lastMeterFrame < this.meterIntervalFrames) {
@@ -8009,6 +8560,13 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
8009
8560
  if (meter.frame !== this.lastMeterFrame) {
8010
8561
  this.lastMeterFrame = meter.frame;
8011
8562
  }
8563
+ delivered.push(meter);
8564
+ newestByTarget.set(meter.targetId, meter);
8565
+ }
8566
+ for (const meter of delivered) {
8567
+ if (newestByTarget.get(meter.targetId) === meter) {
8568
+ meter.insertGainReductionDb = this.engine.meterTargetInsertGainReduction(meter.targetId);
8569
+ }
8012
8570
  if (this.transport?.onMeter) {
8013
8571
  this.transport.onMeter(meter);
8014
8572
  } else {
@@ -8026,6 +8584,8 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
8026
8584
  for (let field = 0; field < 11; field++) {
8027
8585
  ring.records[offset + 3 + field] = this.engine.meterScratchValue(field);
8028
8586
  }
8587
+ ring.records[offset + 14] = this.engine.meterScratchInputPeakDbL();
8588
+ ring.records[offset + 15] = this.engine.meterScratchInputPeakDbR();
8029
8589
  Atomics.store(ring.header, 0, writeIndex + 1);
8030
8590
  }
8031
8591
  // A snapshot-object ring writer used to live here as well. It was
@@ -8041,16 +8601,52 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
8041
8601
  // silent overrun via firstReadable = max(readIndex, writeIndex - capacity),
8042
8602
  // so header slot 3 is left at its initial 0.
8043
8603
  // Drains the engine's scope producer (FFT spectrum + goniometer points) into
8044
- // the lock-free SAB scope ring. No allocation on the render path: records are
8045
- // written field-by-field into the ring.
8604
+ // the lock-free SAB scope ring or the bounded postMessage fallback. The SAB
8605
+ // path remains allocation-free; postMessage materialises only records that
8606
+ // are ready to publish.
8046
8607
  publishScope() {
8047
- const ring = this.scopeRing;
8048
- if (!ring) {
8608
+ if (!this.scopeEnabled) {
8049
8609
  return;
8050
8610
  }
8611
+ const ring = this.scopeRing;
8612
+ const transport = this.transport;
8051
8613
  for (let count = 0; count < 64 && this.engine.popScopeTelemetryToScratch(); count++) {
8052
- this.writeScopeScratch(ring);
8614
+ if (ring) {
8615
+ this.writeScopeScratch(ring);
8616
+ } else {
8617
+ this.postScopeScratch(transport);
8618
+ }
8619
+ }
8620
+ }
8621
+ postScopeScratch(transport) {
8622
+ if (!transport?.postMessage) {
8623
+ return;
8053
8624
  }
8625
+ const bandCount = Math.min(
8626
+ SONARE_SCOPE_MAX_BANDS,
8627
+ Math.max(0, Math.trunc(Number(this.engine.scopeScratchBandCount())))
8628
+ );
8629
+ const pointCount = Math.min(
8630
+ SONARE_SCOPE_MAX_POINTS,
8631
+ Math.max(0, Math.trunc(Number(this.engine.scopeScratchPointCount())))
8632
+ );
8633
+ const bands = new Float32Array(bandCount);
8634
+ for (let i = 0; i < bandCount; i++) {
8635
+ bands[i] = this.engine.scopeScratchBand(i);
8636
+ }
8637
+ const points = new Float32Array(pointCount * 2);
8638
+ for (let i = 0; i < pointCount; i++) {
8639
+ points[2 * i] = this.engine.scopeScratchPointLeft(i);
8640
+ points[2 * i + 1] = this.engine.scopeScratchPointRight(i);
8641
+ }
8642
+ const scope = {
8643
+ type: "scope",
8644
+ targetId: this.engine.scopeScratchTargetId(),
8645
+ frame: Number(this.engine.scopeScratchRenderFrame()),
8646
+ bands,
8647
+ points
8648
+ };
8649
+ transport.postMessage(scope, [bands.buffer, points.buffer]);
8054
8650
  }
8055
8651
  writeScopeScratch(ring) {
8056
8652
  const writeIndex = Atomics.load(ring.header, 0);
@@ -8283,6 +8879,8 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
8283
8879
  bridge.receiveCaptureRequest(data);
8284
8880
  } else if (isEngineTransportRequestMessage(data)) {
8285
8881
  bridge.receiveTransportRequest(data);
8882
+ } else if (isEngineInsertGainReductionRequestMessage(data)) {
8883
+ bridge.receiveInsertGainReductionRequest(data);
8286
8884
  } else if (isRecord(data) && typeof data.type === "string" && data.type.startsWith("sync")) {
8287
8885
  this.port?.postMessage?.({
8288
8886
  type: "syncError",
@@ -8352,8 +8950,8 @@ var SonareWorkletProcessor = class {
8352
8950
  *
8353
8951
  * `targetId` is always the master and the four LUFS / gain-reduction fields
8354
8952
  * are always unavailable here — the mixer worklet does not run the
8355
- * K-weighting filters, and a floor value would read as silence — so both are
8356
- * set once rather than per interval.
8953
+ * K-weighting filters or the engine's pre-trim input tap — so finite floor
8954
+ * values are set once rather than per interval.
8357
8955
  */
8358
8956
  this.meterScratch = {
8359
8957
  type: "meter",
@@ -8369,7 +8967,9 @@ var SonareWorkletProcessor = class {
8369
8967
  momentaryLufs: Number.NaN,
8370
8968
  shortTermLufs: Number.NaN,
8371
8969
  integratedLufs: Number.NaN,
8372
- gainReductionDb: Number.NaN
8970
+ gainReductionDb: Number.NaN,
8971
+ inputPeakDbL: SONARE_FLOOR_DB,
8972
+ inputPeakDbR: SONARE_FLOOR_DB
8373
8973
  };
8374
8974
  if (!options.sceneJson) {
8375
8975
  throw new Error("sceneJson is required.");
@@ -8532,7 +9132,9 @@ var SonareWorkletProcessor = class {
8532
9132
  momentaryLufs: Number.NaN,
8533
9133
  shortTermLufs: Number.NaN,
8534
9134
  integratedLufs: Number.NaN,
8535
- gainReductionDb: Number.NaN
9135
+ gainReductionDb: Number.NaN,
9136
+ inputPeakDbL: SONARE_FLOOR_DB,
9137
+ inputPeakDbR: SONARE_FLOOR_DB
8536
9138
  };
8537
9139
  if (this.transport?.onMeter) {
8538
9140
  this.transport.onMeter(meter);
@@ -8561,6 +9163,8 @@ var SonareWorkletProcessor = class {
8561
9163
  ring.records[offset + 11] = meter.shortTermLufs;
8562
9164
  ring.records[offset + 12] = meter.integratedLufs;
8563
9165
  ring.records[offset + 13] = meter.gainReductionDb;
9166
+ ring.records[offset + 14] = meter.inputPeakDbL;
9167
+ ring.records[offset + 15] = meter.inputPeakDbR;
8564
9168
  Atomics.store(ring.header, 0, writeIndex + 1);
8565
9169
  }
8566
9170
  publishSpectrum(left, right) {
@@ -8613,7 +9217,7 @@ var SonareWorkletProcessor = class {
8613
9217
  return;
8614
9218
  }
8615
9219
  const writeIndex = Atomics.load(ring.header, 0);
8616
- const offset = writeIndex % ring.capacity * ring.recordFloats;
9220
+ const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
8617
9221
  ring.records[offset] = encodeFrameLo(frame);
8618
9222
  ring.records[offset + 1] = encodeFrameHi(frame);
8619
9223
  ring.records[offset + 2] = bands.length;