@libraz/libsonare 1.8.1 → 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 (107) hide show
  1. package/README.md +5 -5
  2. package/dist/analysis.js +2 -0
  3. package/dist/analysis.js.map +1 -1
  4. package/dist/index.d.ts +8 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1927 -140
  7. package/dist/index.js.map +1 -1
  8. package/dist/instrument_types.d.ts +27 -3
  9. package/dist/instrument_types.d.ts.map +1 -1
  10. package/dist/mastering_core.d.ts +3 -2
  11. package/dist/mastering_core.d.ts.map +1 -1
  12. package/dist/mixer.d.ts +2 -2
  13. package/dist/project.d.ts +5 -4
  14. package/dist/project.d.ts.map +1 -1
  15. package/dist/project_class.d.ts +57 -3
  16. package/dist/project_class.d.ts.map +1 -1
  17. package/dist/project_internal.d.ts +35 -1
  18. package/dist/project_internal.d.ts.map +1 -1
  19. package/dist/project_types.d.ts +10 -1
  20. package/dist/project_types.d.ts.map +1 -1
  21. package/dist/public_types.d.ts +1 -0
  22. package/dist/public_types.d.ts.map +1 -1
  23. package/dist/public_types_mastering.d.ts +1 -1
  24. package/dist/public_types_mastering.d.ts.map +1 -1
  25. package/dist/public_types_vocal_edit.d.ts +250 -0
  26. package/dist/public_types_vocal_edit.d.ts.map +1 -0
  27. package/dist/public_types_vocal_project.d.ts +63 -0
  28. package/dist/public_types_vocal_project.d.ts.map +1 -0
  29. package/dist/realtime_engine.d.ts +57 -10
  30. package/dist/realtime_engine.d.ts.map +1 -1
  31. package/dist/repair_dereverb.d.ts +1 -0
  32. package/dist/repair_dereverb.d.ts.map +1 -1
  33. package/dist/repair_noise.d.ts +1 -0
  34. package/dist/repair_noise.d.ts.map +1 -1
  35. package/dist/sonare-analysis.wasm +0 -0
  36. package/dist/sonare.d.ts +53 -0
  37. package/dist/sonare.js +1 -1
  38. package/dist/sonare.wasm +0 -0
  39. package/dist/vocal_edit.d.ts +131 -0
  40. package/dist/vocal_edit.d.ts.map +1 -0
  41. package/dist/vocal_edit_worker.d.ts +51 -0
  42. package/dist/vocal_edit_worker.d.ts.map +1 -0
  43. package/dist/vocal_edit_worker.js +1462 -0
  44. package/dist/vocal_edit_worker.js.map +1 -0
  45. package/dist/vocal_edit_worker_client.d.ts +118 -0
  46. package/dist/vocal_edit_worker_client.d.ts.map +1 -0
  47. package/dist/vocal_edit_worker_protocol.d.ts +139 -0
  48. package/dist/vocal_edit_worker_protocol.d.ts.map +1 -0
  49. package/dist/vocal_project.d.ts +17 -0
  50. package/dist/vocal_project.d.ts.map +1 -0
  51. package/dist/web_midi.d.ts +4 -0
  52. package/dist/web_midi.d.ts.map +1 -1
  53. package/dist/worker.js.map +1 -1
  54. package/dist/worklet/engine-mixer-facade.d.ts +2 -1
  55. package/dist/worklet/engine-mixer-facade.d.ts.map +1 -1
  56. package/dist/worklet/engine-node.d.ts +8 -0
  57. package/dist/worklet/engine-node.d.ts.map +1 -1
  58. package/dist/worklet/engine-processor.d.ts +2 -1
  59. package/dist/worklet/engine-processor.d.ts.map +1 -1
  60. package/dist/worklet/engine-register.d.ts.map +1 -1
  61. package/dist/worklet/engine-strips.d.ts +4 -2
  62. package/dist/worklet/engine-strips.d.ts.map +1 -1
  63. package/dist/worklet/engine.d.ts +15 -3
  64. package/dist/worklet/engine.d.ts.map +1 -1
  65. package/dist/worklet/guards.d.ts +3 -1
  66. package/dist/worklet/guards.d.ts.map +1 -1
  67. package/dist/worklet/messages.d.ts +50 -5
  68. package/dist/worklet/messages.d.ts.map +1 -1
  69. package/dist/worklet/protocol.d.ts +10 -0
  70. package/dist/worklet/protocol.d.ts.map +1 -1
  71. package/dist/worklet.d.ts +1 -1
  72. package/dist/worklet.d.ts.map +1 -1
  73. package/dist/worklet.js +223 -16
  74. package/dist/worklet.js.map +1 -1
  75. package/package.json +6 -1
  76. package/src/index.ts +33 -0
  77. package/src/instrument_types.ts +29 -3
  78. package/src/mastering_core.ts +3 -2
  79. package/src/mixer.ts +2 -2
  80. package/src/project.ts +8 -1
  81. package/src/project_class.ts +136 -1
  82. package/src/project_internal.ts +90 -0
  83. package/src/project_types.ts +11 -1
  84. package/src/public_types.ts +1 -0
  85. package/src/public_types_mastering.ts +2 -0
  86. package/src/public_types_vocal_edit.ts +289 -0
  87. package/src/public_types_vocal_project.ts +69 -0
  88. package/src/realtime_engine.ts +86 -9
  89. package/src/repair_dereverb.ts +1 -0
  90. package/src/repair_noise.ts +1 -0
  91. package/src/sonare.js.d.ts +53 -0
  92. package/src/vocal_edit.ts +885 -0
  93. package/src/vocal_edit_worker.ts +832 -0
  94. package/src/vocal_edit_worker_client.ts +812 -0
  95. package/src/vocal_edit_worker_protocol.ts +206 -0
  96. package/src/vocal_project.ts +327 -0
  97. package/src/web_midi.ts +100 -5
  98. package/src/worklet/engine-mixer-facade.ts +2 -1
  99. package/src/worklet/engine-node.ts +47 -0
  100. package/src/worklet/engine-processor.ts +46 -1
  101. package/src/worklet/engine-register.ts +3 -0
  102. package/src/worklet/engine-strips.ts +31 -4
  103. package/src/worklet/engine.ts +27 -3
  104. package/src/worklet/guards.ts +26 -0
  105. package/src/worklet/messages.ts +59 -2
  106. package/src/worklet/protocol.ts +10 -0
  107. package/src/worklet.ts +2 -0
@@ -5,6 +5,7 @@ import {
5
5
  isClipPageRequestMessage,
6
6
  isEngineCaptureResponseForOperation,
7
7
  isEngineCaptureResponseMessage,
8
+ isEngineInsertGainReductionResponseMessage,
8
9
  isEngineTelemetryRecord,
9
10
  isEngineTransportResponseMessage,
10
11
  isExternalMidiBatchMessage,
@@ -144,6 +145,14 @@ export class SonareRealtimeEngineNode {
144
145
  reject: (reason?: unknown) => void;
145
146
  }
146
147
  >();
148
+ private insertGainReductionRequestId = 1;
149
+ private readonly insertGainReductionRequests = new Map<
150
+ number,
151
+ {
152
+ resolve: (values: number[]) => void;
153
+ reject: (reason?: unknown) => void;
154
+ }
155
+ >();
147
156
  private resolveReady!: () => void;
148
157
  private rejectReady!: (reason?: unknown) => void;
149
158
  private destroyed = false;
@@ -205,6 +214,16 @@ export class SonareRealtimeEngineNode {
205
214
  pending.reject(new Error(event.data.error ?? 'Transport request failed'));
206
215
  }
207
216
  }
217
+ } else if (isEngineInsertGainReductionResponseMessage(event.data)) {
218
+ const pending = this.insertGainReductionRequests.get(event.data.requestId);
219
+ if (pending) {
220
+ this.insertGainReductionRequests.delete(event.data.requestId);
221
+ if (event.data.ok && Array.isArray(event.data.values)) {
222
+ pending.resolve(event.data.values);
223
+ } else {
224
+ pending.reject(new Error(event.data.error ?? 'Insert gain reduction request failed'));
225
+ }
226
+ }
208
227
  } else if (isEngineTelemetryRecord(event.data)) {
209
228
  this.emitTelemetry(event.data);
210
229
  } else if (isMeterSnapshot(event.data)) {
@@ -659,6 +678,10 @@ export class SonareRealtimeEngineNode {
659
678
  pending.reject(new Error('Realtime engine node is destroyed.'));
660
679
  }
661
680
  this.transportRequests.clear();
681
+ for (const pending of this.insertGainReductionRequests.values()) {
682
+ pending.reject(new Error('Realtime engine node is destroyed.'));
683
+ }
684
+ this.insertGainReductionRequests.clear();
662
685
  this.telemetryListeners.clear();
663
686
  this.meterListeners.clear();
664
687
  this.scopeListeners.clear();
@@ -728,6 +751,30 @@ export class SonareRealtimeEngineNode {
728
751
  }
729
752
  }
730
753
 
754
+ /**
755
+ * Reads one target's per-insert gain reduction (dB, each <= 0, pre-to-post insert order)
756
+ * from the worklet. Works in both ring and postMessage meter modes; rejects when the
757
+ * target is invalid or mixing is not compiled in.
758
+ */
759
+ pollInsertGainReduction(targetId: number): Promise<number[]> {
760
+ if (this.destroyed) {
761
+ return Promise.reject(new Error('Realtime engine node is destroyed.'));
762
+ }
763
+ const requestId = this.insertGainReductionRequestId++;
764
+ let rejectRequest!: (reason?: unknown) => void;
765
+ const promise = new Promise<number[]>((resolve, reject) => {
766
+ rejectRequest = reject;
767
+ this.insertGainReductionRequests.set(requestId, { resolve, reject });
768
+ });
769
+ try {
770
+ this.node.port.postMessage({ type: 'insertGainReductionRequest', requestId, targetId });
771
+ } catch (error) {
772
+ this.insertGainReductionRequests.delete(requestId);
773
+ rejectRequest(error);
774
+ }
775
+ return promise;
776
+ }
777
+
731
778
  private emitScope(scope: SonareWorkletScopeSnapshot): void {
732
779
  for (const listener of this.scopeListeners) {
733
780
  listener(scope);
@@ -14,6 +14,8 @@ import {
14
14
  resolveMetronomeConfig,
15
15
  type SonareEngineCaptureRequestMessage,
16
16
  type SonareEngineCaptureResponseMessageInternal,
17
+ type SonareEngineInsertGainReductionRequestMessage,
18
+ type SonareEngineInsertGainReductionResponseMessage,
17
19
  type SonareEngineSyncMessage,
18
20
  type SonareEngineTransportRequestMessage,
19
21
  type SonareEngineTransportResponseMessage,
@@ -46,6 +48,7 @@ import {
46
48
  type SonareEngineTelemetryRecord,
47
49
  type SonareEngineTelemetryRingBuffer,
48
50
  SonareEngineTelemetryType,
51
+ type SonareWorkletMeterSnapshot,
49
52
  type SonareWorkletScopeSnapshot,
50
53
  scopeRingFromSharedBuffer,
51
54
  telemetryFromEngine,
@@ -682,6 +685,9 @@ export class SonareRealtimeEngineWorkletProcessor {
682
685
  case 'syncTrackStripDualPan':
683
686
  this.engine.setTrackStripDualPan(message.trackId, message.leftPan, message.rightPan);
684
687
  break;
688
+ case 'syncTrackStripSurroundPan':
689
+ this.engine.setTrackStripSurroundPan(message.trackId, message.pan);
690
+ break;
685
691
  case 'syncBusStripPan':
686
692
  this.engine.setBusStripPan(message.busId, message.pan);
687
693
  break;
@@ -774,7 +780,11 @@ export class SonareRealtimeEngineWorkletProcessor {
774
780
  );
775
781
  break;
776
782
  case 'syncMidiUmp':
777
- this.engine.pushMidiUmp(message.destinationId, [message.word0], message.renderFrame);
783
+ this.engine.pushMidiUmp(
784
+ message.destinationId,
785
+ message.words ?? [message.word0],
786
+ message.renderFrame,
787
+ );
778
788
  break;
779
789
  case 'syncMidiSysex':
780
790
  this.engine.pushMidiSysex(message.destinationId, message.data, message.renderFrame);
@@ -852,6 +862,9 @@ export class SonareRealtimeEngineWorkletProcessor {
852
862
  message.portTimeSamples,
853
863
  );
854
864
  break;
865
+ case 'syncMidiInputUmp':
866
+ this.engine.pushMidiInputUmp(message.words, message.portTimeSamples);
867
+ break;
855
868
  }
856
869
  }
857
870
 
@@ -931,6 +944,27 @@ export class SonareRealtimeEngineWorkletProcessor {
931
944
  }
932
945
  }
933
946
 
947
+ receiveInsertGainReductionRequest(message: SonareEngineInsertGainReductionRequestMessage): void {
948
+ if (this.closed) {
949
+ return;
950
+ }
951
+ try {
952
+ this.transport?.postMessage?.({
953
+ type: 'insertGainReductionResponse',
954
+ requestId: message.requestId,
955
+ ok: true,
956
+ values: this.engine.meterTargetInsertGainReduction(message.targetId),
957
+ } satisfies SonareEngineInsertGainReductionResponseMessage);
958
+ } catch (error) {
959
+ this.transport?.postMessage?.({
960
+ type: 'insertGainReductionResponse',
961
+ requestId: message.requestId,
962
+ ok: false,
963
+ error: error instanceof Error ? error.message : String(error),
964
+ } satisfies SonareEngineInsertGainReductionResponseMessage);
965
+ }
966
+ }
967
+
934
968
  destroy(): void {
935
969
  if (!this.closed) {
936
970
  this.clearPagedClipProviders();
@@ -1151,6 +1185,8 @@ export class SonareRealtimeEngineWorkletProcessor {
1151
1185
  }
1152
1186
  return;
1153
1187
  }
1188
+ const delivered: SonareWorkletMeterSnapshot[] = [];
1189
+ const newestByTarget = new Map<number, SonareWorkletMeterSnapshot>();
1154
1190
  for (const item of this.engine.drainMeterTelemetry(64)) {
1155
1191
  const meter = meterFromEngine(item);
1156
1192
  if (
@@ -1162,6 +1198,15 @@ export class SonareRealtimeEngineWorkletProcessor {
1162
1198
  if (meter.frame !== this.lastMeterFrame) {
1163
1199
  this.lastMeterFrame = meter.frame;
1164
1200
  }
1201
+ delivered.push(meter);
1202
+ newestByTarget.set(meter.targetId, meter);
1203
+ }
1204
+ for (const meter of delivered) {
1205
+ // The board holds only the latest block, so only the newest record per
1206
+ // target in this drain can carry it; the ring layout is fixed and cannot.
1207
+ if (newestByTarget.get(meter.targetId) === meter) {
1208
+ meter.insertGainReductionDb = this.engine.meterTargetInsertGainReduction(meter.targetId);
1209
+ }
1165
1210
  // The ring branch returned above, so this is the structured-clone
1166
1211
  // fallback. `onMeter` and `postMessage` are alternative channels for the
1167
1212
  // same record, not a broadcast pair: the AudioWorklet registration
@@ -5,6 +5,7 @@ import { SonareRealtimeEngineWorkletProcessor } from './engine-processor';
5
5
  import {
6
6
  isEngineCaptureRequestMessage,
7
7
  isEngineCommandRecord,
8
+ isEngineInsertGainReductionRequestMessage,
8
9
  isEngineSyncMessage,
9
10
  isEngineTransportRequestMessage,
10
11
  } from './guards';
@@ -82,6 +83,8 @@ export function registerSonareRealtimeEngineWorkletProcessor(
82
83
  bridge.receiveCaptureRequest(data);
83
84
  } else if (isEngineTransportRequestMessage(data)) {
84
85
  bridge.receiveTransportRequest(data);
86
+ } else if (isEngineInsertGainReductionRequestMessage(data)) {
87
+ bridge.receiveInsertGainReductionRequest(data);
85
88
  } else if (isRecord(data) && typeof data.type === 'string' && data.type.startsWith('sync')) {
86
89
  // A sync the guard does not accept would otherwise vanish, leaving the
87
90
  // live engine out of step with the offline mirror and nothing to see.
@@ -1,5 +1,6 @@
1
1
  import { panLawCode, panModeCode } from '../codes';
2
- import type { EqBand, PanLawInput, PanMode, RealtimeEngine } from '../index';
2
+ import type { EqBand, PanLawInput, PanMode, RealtimeEngine, UmpWords } from '../index';
3
+ import type { SurroundPan } from '../public_types';
3
4
  import type { InsertParamOverrideMap } from './engine-mixer-facade';
4
5
  import {
5
6
  emptyStripJson,
@@ -205,6 +206,26 @@ export function setTrackStripDualPan(
205
206
  ctx.postSync({ type: 'syncTrackStripDualPan', trackId, leftPan, rightPan });
206
207
  }
207
208
 
209
+ export function setTrackStripSurroundPan(
210
+ ctx: EngineStripContext,
211
+ target: string | number,
212
+ pan: SurroundPan,
213
+ ): void {
214
+ const trackId = trackIdFor(ctx, target);
215
+ ctx.offlineEngine.setTrackStripSurroundPan(trackId, pan);
216
+ const resolved = {
217
+ azimuth: pan.azimuth ?? 0,
218
+ elevation: pan.elevation ?? 0,
219
+ divergence: pan.divergence ?? 0,
220
+ lfe: pan.lfe ?? 0,
221
+ distance: pan.distance === undefined || pan.distance <= 0 ? 1 : pan.distance,
222
+ };
223
+ mergeStripJson(ctx, { kind: 'track', trackId }, (entry) => {
224
+ entry.surroundPan = resolved;
225
+ });
226
+ ctx.postSync({ type: 'syncTrackStripSurroundPan', trackId, pan: resolved });
227
+ }
228
+
208
229
  export function setTrackStripChannelDelaySamples(
209
230
  ctx: EngineStripContext,
210
231
  target: string | number,
@@ -499,12 +520,18 @@ export function pushMidiPolyPressure(
499
520
  export function pushMidiUmp(
500
521
  ctx: EngineStripContext,
501
522
  trackId: string | number,
502
- word0: number,
523
+ word0: number | UmpWords,
503
524
  renderFrame: number,
504
525
  ): void {
505
526
  const destinationId = ctx.resolveTargetId(trackId);
506
- ctx.offlineEngine.pushMidiUmp(destinationId, [word0], renderFrame);
507
- ctx.postSync({ type: 'syncMidiUmp', destinationId, word0, renderFrame });
527
+ if (typeof word0 === 'number') {
528
+ ctx.offlineEngine.pushMidiUmp(destinationId, word0, renderFrame);
529
+ ctx.postSync({ type: 'syncMidiUmp', destinationId, word0, renderFrame });
530
+ return;
531
+ }
532
+ const words = word0 instanceof Uint32Array ? new Uint32Array(word0) : word0.slice();
533
+ ctx.offlineEngine.pushMidiUmp(destinationId, words, renderFrame);
534
+ ctx.postSync({ type: 'syncMidiUmp', destinationId, words, renderFrame });
508
535
  }
509
536
 
510
537
  export function setBuiltinInstrument(
@@ -23,9 +23,11 @@ import type {
23
23
  PanLaw,
24
24
  PanMode,
25
25
  SidechainSourceKind,
26
+ UmpWords,
26
27
  } from '../index';
27
28
  import { RealtimeEngine } from '../index';
28
29
  import { createOpfsClipPageProvider, type OpfsClipPageProviderBinding } from '../opfs_clip_pages';
30
+ import type { SurroundPan } from '../public_types';
29
31
  import type { ClipPageProvider } from '../realtime_engine';
30
32
  import type { EngineAutomationContext } from './engine-automation';
31
33
  import * as automation from './engine-automation';
@@ -505,7 +507,9 @@ export class SonareEngine {
505
507
  /**
506
508
  * Keys one insert of a lane strip from another lane's post-strip pre-fader
507
509
  * audio (ducking/sidechainRouter inserts). sourceTarget null removes the
508
- * binding.
510
+ * binding. Throws, leaving the existing bindings unchanged, for a lane keying
511
+ * itself or a binding that would close a cycle over the lane bindings; a
512
+ * refused binding is never recorded, so it is not replayed on resync.
509
513
  */
510
514
  setLaneSidechain(
511
515
  target: string | number,
@@ -611,6 +615,15 @@ export class SonareEngine {
611
615
  strips.setTrackStripDualPan(this.stripContext, target, leftPan, rightPan);
612
616
  }
613
617
 
618
+ /**
619
+ * Sets a track lane strip's surround placement (glitch-free, 5 ms placement
620
+ * glide). Takes effect on a destination wider than stereo, so set `channelCount`
621
+ * to the layout's plane count. Omitted fields default to a centered point source.
622
+ */
623
+ setTrackStripSurroundPan(target: string | number, pan: SurroundPan): void {
624
+ strips.setTrackStripSurroundPan(this.stripContext, target, pan);
625
+ }
626
+
614
627
  setTrackStripChannelDelaySamples(target: string | number, delaySamples: number): void {
615
628
  strips.setTrackStripChannelDelaySamples(this.stripContext, target, delaySamples);
616
629
  }
@@ -1031,8 +1044,8 @@ export class SonareEngine {
1031
1044
  );
1032
1045
  }
1033
1046
 
1034
- pushMidiUmp(trackId: string | number, word0: number, renderFrame = -1): void {
1035
- strips.pushMidiUmp(this.stripContext, trackId, word0, renderFrame);
1047
+ pushMidiUmp(trackId: string | number, words: number | UmpWords, renderFrame = -1): void {
1048
+ strips.pushMidiUmp(this.stripContext, trackId, words, renderFrame);
1036
1049
  }
1037
1050
 
1038
1051
  pushMidiSysex(trackId: string | number, data: Uint8Array, renderFrame = -1): void {
@@ -1167,6 +1180,12 @@ export class SonareEngine {
1167
1180
  });
1168
1181
  }
1169
1182
 
1183
+ pushMidiInputUmp(words: UmpWords, portTimeSamples = 0): void {
1184
+ const copiedWords = words instanceof Uint32Array ? new Uint32Array(words) : words.slice();
1185
+ this.offlineEngine.pushMidiInputUmp(copiedWords, portTimeSamples);
1186
+ this.postSync({ type: 'syncMidiInputUmp', words: copiedWords, portTimeSamples });
1187
+ }
1188
+
1170
1189
  pushMidiPanic(renderFrame = -1): void {
1171
1190
  this.offlineEngine.pushMidiPanic(renderFrame);
1172
1191
  this.postSync({ type: 'syncMidiPanic', renderFrame });
@@ -1288,6 +1307,11 @@ export class SonareEngine {
1288
1307
  return this.realtimeNode.pollScope();
1289
1308
  }
1290
1309
 
1310
+ /** Per-insert gain reduction for a meter target; see {@link SonareRealtimeEngineNode.pollInsertGainReduction}. */
1311
+ pollInsertGainReduction(targetId: number): Promise<number[]> {
1312
+ return this.realtimeNode.pollInsertGainReduction(targetId);
1313
+ }
1314
+
1291
1315
  destroy(): void {
1292
1316
  if (this.destroyed) {
1293
1317
  return;
@@ -3,6 +3,8 @@ import type {
3
3
  SonareEngineCaptureRequestMessage,
4
4
  SonareEngineCaptureResponseMessageInternal,
5
5
  SonareEngineClipPageRequestMessage,
6
+ SonareEngineInsertGainReductionRequestMessage,
7
+ SonareEngineInsertGainReductionResponseMessage,
6
8
  SonareEngineSyncMessage,
7
9
  SonareEngineTransportRequestMessage,
8
10
  SonareEngineTransportResponseMessage,
@@ -86,6 +88,7 @@ export const ENGINE_SYNC_MESSAGE_TYPES: Record<SonareEngineSyncMessage['type'],
86
88
  syncMidiInputPitchBend: true,
87
89
  syncMidiInputPolyPressure: true,
88
90
  syncMidiInputSource: true,
91
+ syncMidiInputUmp: true,
89
92
  syncMidiNoteOff: true,
90
93
  syncMidiNoteOn: true,
91
94
  syncMidiPanic: true,
@@ -106,6 +109,7 @@ export const ENGINE_SYNC_MESSAGE_TYPES: Record<SonareEngineSyncMessage['type'],
106
109
  syncTrackStripPan: true,
107
110
  syncTrackStripPanLaw: true,
108
111
  syncTrackStripPanMode: true,
112
+ syncTrackStripSurroundPan: true,
109
113
  syncWarpVoiceCapacity: true,
110
114
  };
111
115
 
@@ -254,6 +258,28 @@ export function isEngineTransportResponseMessage(
254
258
  );
255
259
  }
256
260
 
261
+ export function isEngineInsertGainReductionRequestMessage(
262
+ value: unknown,
263
+ ): value is SonareEngineInsertGainReductionRequestMessage {
264
+ return (
265
+ isRecord(value) &&
266
+ value.type === 'insertGainReductionRequest' &&
267
+ typeof value.requestId === 'number' &&
268
+ typeof value.targetId === 'number'
269
+ );
270
+ }
271
+
272
+ export function isEngineInsertGainReductionResponseMessage(
273
+ value: unknown,
274
+ ): value is SonareEngineInsertGainReductionResponseMessage {
275
+ return (
276
+ isRecord(value) &&
277
+ value.type === 'insertGainReductionResponse' &&
278
+ typeof value.requestId === 'number' &&
279
+ typeof value.ok === 'boolean'
280
+ );
281
+ }
282
+
257
283
  export function isRealtimeVoiceChangerMessage(
258
284
  value: unknown,
259
285
  ): value is SonareRealtimeVoiceChangerMessage {
@@ -15,8 +15,9 @@ import type {
15
15
  PlaybackRendererConfig,
16
16
  RealtimeVoiceChangerConfigInput,
17
17
  RealtimeVoiceChangerPodConfig,
18
+ UmpWords,
18
19
  } from '../index';
19
- import type { AutomationCurve } from '../public_types';
20
+ import type { AutomationCurve, SurroundPan } from '../public_types';
20
21
  import type {
21
22
  SonareEngineCommandRecord,
22
23
  SonareEngineTelemetryRecord,
@@ -45,6 +46,18 @@ export interface SonareRealtimeEngineWorkletProcessorOptions {
45
46
  initialCommands?: SonareEngineCommandRecord[];
46
47
  sampleRate?: number;
47
48
  blockSize?: number;
49
+ /**
50
+ * Output plane count (default 2). Planes follow the canonical order
51
+ * L R C LFE Ls Rs [Lss Rss] for 6 (5.1) and 8 (7.1) planes.
52
+ *
53
+ * The Web Audio default destination is 2-channel `'speakers'`, so a wider
54
+ * output is mixed down to stereo unless the app raises
55
+ * `audioContext.destination.channelCount` (up to its `maxChannelCount`) to
56
+ * match. Interpretation matters only while the counts differ: `'speakers'`
57
+ * applies the spec's down-mix for mono, stereo, quad and 5.1 sources only,
58
+ * and `'discrete'` copies the first planes and drops the rest. With equal
59
+ * counts the planes pass through unchanged in either mode.
60
+ */
48
61
  channelCount?: number;
49
62
  meterIntervalFrames?: number;
50
63
  commandSharedBuffer?: SharedArrayBuffer;
@@ -301,6 +314,7 @@ export interface WorkletTransport {
301
314
  | SonareWorkletTransportMessage
302
315
  | SonareEngineCaptureResponseMessageInternal
303
316
  | SonareEngineTransportResponseMessage
317
+ | SonareEngineInsertGainReductionResponseMessage
304
318
  | SonareEngineSyncErrorMessage,
305
319
  transfer?: Transferable[],
306
320
  ) => void;
@@ -633,6 +647,12 @@ export interface SonareEngineSyncTrackStripDualPanMessage {
633
647
  rightPan: number;
634
648
  }
635
649
 
650
+ export interface SonareEngineSyncTrackStripSurroundPanMessage {
651
+ type: 'syncTrackStripSurroundPan';
652
+ trackId: number;
653
+ pan: Required<SurroundPan>;
654
+ }
655
+
636
656
  export interface SonareEngineSyncBusStripPanMessage {
637
657
  type: 'syncBusStripPan';
638
658
  busId: number;
@@ -721,13 +741,26 @@ export interface SonareEngineSyncMidiCcMessage {
721
741
  renderFrame: number;
722
742
  }
723
743
 
724
- export interface SonareEngineSyncMidiUmpMessage {
744
+ export interface SonareEngineSyncMidiUmpWordMessage {
725
745
  type: 'syncMidiUmp';
726
746
  destinationId: number;
727
747
  word0: number;
748
+ words?: never;
728
749
  renderFrame: number;
729
750
  }
730
751
 
752
+ export interface SonareEngineSyncMidiUmpWordsMessage {
753
+ type: 'syncMidiUmp';
754
+ destinationId: number;
755
+ word0?: never;
756
+ words: UmpWords;
757
+ renderFrame: number;
758
+ }
759
+
760
+ export type SonareEngineSyncMidiUmpMessage =
761
+ | SonareEngineSyncMidiUmpWordMessage
762
+ | SonareEngineSyncMidiUmpWordsMessage;
763
+
731
764
  /**
732
765
  * One of the three per-note expression dimensions, addressed to a destination.
733
766
  * `data0`/`data1` carry the dimension's own payload the way the live-input
@@ -796,6 +829,12 @@ export interface SonareEngineSyncMidiInputEventMessage {
796
829
  portTimeSamples: number;
797
830
  }
798
831
 
832
+ export interface SonareEngineSyncMidiInputUmpMessage {
833
+ type: 'syncMidiInputUmp';
834
+ words: UmpWords;
835
+ portTimeSamples: number;
836
+ }
837
+
799
838
  /** Releases the realtime engine and all worklet-owned clip buffers. */
800
839
  export interface SonareEngineDestroyMessage {
801
840
  type: 'destroy';
@@ -839,6 +878,7 @@ export type SonareEngineSyncMessage =
839
878
  | SonareEngineSyncTrackStripPanLawMessage
840
879
  | SonareEngineSyncTrackStripPanModeMessage
841
880
  | SonareEngineSyncTrackStripDualPanMessage
881
+ | SonareEngineSyncTrackStripSurroundPanMessage
842
882
  | SonareEngineSyncBusStripPanMessage
843
883
  | SonareEngineSyncBusStripPanLawMessage
844
884
  | SonareEngineSyncBusStripPanModeMessage
@@ -860,6 +900,7 @@ export type SonareEngineSyncMessage =
860
900
  | SonareEngineSyncMidiInputSourceMessage
861
901
  | SonareEngineSyncMidiCcBindingMessage
862
902
  | SonareEngineSyncMidiInputEventMessage
903
+ | SonareEngineSyncMidiInputUmpMessage
863
904
  | SonareEngineDestroyMessage;
864
905
 
865
906
  export interface WorkletPort {
@@ -937,3 +978,19 @@ export interface SonareEngineTransportResponseMessage {
937
978
  state?: EngineTransportState;
938
979
  error?: string;
939
980
  }
981
+
982
+ /** Asks the worklet for one target's per-insert gain reduction (any meter delivery mode). */
983
+ export interface SonareEngineInsertGainReductionRequestMessage {
984
+ type: 'insertGainReductionRequest';
985
+ requestId: number;
986
+ targetId: number;
987
+ }
988
+
989
+ /** Reply to {@link SonareEngineInsertGainReductionRequestMessage}; `values` is present when `ok`. */
990
+ export interface SonareEngineInsertGainReductionResponseMessage {
991
+ type: 'insertGainReductionResponse';
992
+ requestId: number;
993
+ ok: boolean;
994
+ values?: number[];
995
+ error?: string;
996
+ }
@@ -45,6 +45,16 @@ export interface SonareWorkletMeterSnapshot {
45
45
  inputPeakDbL: number;
46
46
  /** Maximum pre-trim input peak for the right channel, in dBFS. */
47
47
  inputPeakDbR: number;
48
+ /**
49
+ * Per-insert gain reduction in dB (each <= 0) of the target's strip, in combined
50
+ * pre-to-post insert order, folded to the deepest value across the sub-blocks of the
51
+ * latest committed host block. It describes that latest block as of the drain, not
52
+ * necessarily this record's own block, so it is attached only to the newest record per
53
+ * target within one drain; older backlogged records omit it. Present only on snapshots
54
+ * delivered over postMessage; the SharedArrayBuffer meter ring has a fixed layout that
55
+ * does not carry it, so ring hosts read it with `pollInsertGainReduction(targetId)`.
56
+ */
57
+ insertGainReductionDb?: number[];
48
58
  }
49
59
 
50
60
  export interface SonareWorkletSpectrumSnapshot {
package/src/worklet.ts CHANGED
@@ -24,6 +24,8 @@ export type {
24
24
  SonareEngineCaptureRequestMessage,
25
25
  SonareEngineCaptureResponseMessage,
26
26
  SonareEngineClipPageRequestMessage,
27
+ SonareEngineInsertGainReductionRequestMessage,
28
+ SonareEngineInsertGainReductionResponseMessage,
27
29
  SonareEngineSyncAutomationMessage,
28
30
  SonareEngineSyncBuiltinInstrumentMessage,
29
31
  SonareEngineSyncBusStripEqBandMessage,