@libraz/libsonare 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +2 -2
  2. package/dist/_effects_common.d.ts +2 -1
  3. package/dist/_effects_common.d.ts.map +1 -1
  4. package/dist/analysis.js +1 -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 +3 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +216 -60
  17. package/dist/index.js.map +1 -1
  18. package/dist/mastering_core.d.ts +27 -1
  19. package/dist/mastering_core.d.ts.map +1 -1
  20. package/dist/mixer.d.ts.map +1 -1
  21. package/dist/project_types.d.ts +7 -5
  22. package/dist/project_types.d.ts.map +1 -1
  23. package/dist/public_types_mastering.d.ts +19 -1
  24. package/dist/public_types_mastering.d.ts.map +1 -1
  25. package/dist/public_types_playback.d.ts +1 -0
  26. package/dist/public_types_playback.d.ts.map +1 -1
  27. package/dist/public_types_spectral.d.ts +5 -5
  28. package/dist/public_types_spectral.d.ts.map +1 -1
  29. package/dist/realtime_engine.d.ts +49 -6
  30. package/dist/realtime_engine.d.ts.map +1 -1
  31. package/dist/sonare-analysis.wasm +0 -0
  32. package/dist/sonare.d.ts +36 -0
  33. package/dist/sonare.js +1 -1
  34. package/dist/sonare.wasm +0 -0
  35. package/dist/streaming_processors.d.ts +5 -0
  36. package/dist/streaming_processors.d.ts.map +1 -1
  37. package/dist/worker.js.map +1 -1
  38. package/dist/worklet/engine-mixer-facade.d.ts +40 -4
  39. package/dist/worklet/engine-mixer-facade.d.ts.map +1 -1
  40. package/dist/worklet/engine-node.d.ts +2 -0
  41. package/dist/worklet/engine-node.d.ts.map +1 -1
  42. package/dist/worklet/engine-processor.d.ts +4 -0
  43. package/dist/worklet/engine-processor.d.ts.map +1 -1
  44. package/dist/worklet/engine-strips.d.ts +4 -2
  45. package/dist/worklet/engine-strips.d.ts.map +1 -1
  46. package/dist/worklet/engine-sync.d.ts +4 -2
  47. package/dist/worklet/engine-sync.d.ts.map +1 -1
  48. package/dist/worklet/engine.d.ts +27 -6
  49. package/dist/worklet/engine.d.ts.map +1 -1
  50. package/dist/worklet/guards.d.ts +7 -1
  51. package/dist/worklet/guards.d.ts.map +1 -1
  52. package/dist/worklet/messages.d.ts +4 -4
  53. package/dist/worklet/messages.d.ts.map +1 -1
  54. package/dist/worklet/mixer-processor.d.ts +2 -2
  55. package/dist/worklet/mixer-processor.d.ts.map +1 -1
  56. package/dist/worklet/protocol.d.ts +10 -2
  57. package/dist/worklet/protocol.d.ts.map +1 -1
  58. package/dist/worklet.js +585 -188
  59. package/dist/worklet.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/_effects_common.ts +36 -6
  62. package/src/effects_mastering.ts +4 -0
  63. package/src/effects_note_ops.ts +92 -44
  64. package/src/effects_timepitch.ts +42 -31
  65. package/src/feature_decompose.ts +2 -2
  66. package/src/index.ts +6 -0
  67. package/src/mastering_core.ts +95 -0
  68. package/src/mixer.ts +39 -19
  69. package/src/project_types.ts +7 -5
  70. package/src/public_types_mastering.ts +21 -0
  71. package/src/public_types_playback.ts +1 -0
  72. package/src/public_types_spectral.ts +5 -5
  73. package/src/realtime_engine.ts +71 -15
  74. package/src/sonare.js.d.ts +36 -0
  75. package/src/streaming_processors.ts +8 -0
  76. package/src/worklet/engine-mixer-facade.ts +420 -86
  77. package/src/worklet/engine-node.ts +22 -4
  78. package/src/worklet/engine-processor.ts +83 -33
  79. package/src/worklet/engine-strips.ts +5 -10
  80. package/src/worklet/engine-sync.ts +6 -1
  81. package/src/worklet/engine.ts +49 -37
  82. package/src/worklet/guards.ts +55 -0
  83. package/src/worklet/messages.ts +4 -2
  84. package/src/worklet/mixer-processor.ts +10 -3
  85. package/src/worklet/protocol.ts +29 -8
@@ -2,6 +2,7 @@ import { ErrorCode, SonareError } from './errors';
2
2
  import { getSonareModule } from './module_state';
3
3
  import type {
4
4
  LoudnessMatchResult,
5
+ LoudnessMatchStereoResult,
5
6
  MasteringAssistantParams,
6
7
  MasteringInsertParamChoice,
7
8
  MasteringInsertSlot,
@@ -13,6 +14,7 @@ import type {
13
14
  PairProcessor,
14
15
  SoloProcessor,
15
16
  StereoAnalysis,
17
+ StereoPairProcessor,
16
18
  StreamingPlatform,
17
19
  } from './public_types';
18
20
 
@@ -52,6 +54,17 @@ export interface MasteringPairProcessRequest {
52
54
  params?: MasteringProcessorParams;
53
55
  }
54
56
 
57
+ /** Canonical request form for a stereo two-input match processor. */
58
+ export interface MasteringPairProcessStereoRequest {
59
+ processorName: StereoPairProcessor;
60
+ sourceLeft: Float32Array;
61
+ sourceRight: Float32Array;
62
+ referenceLeft: Float32Array;
63
+ referenceRight: Float32Array;
64
+ sampleRate?: number;
65
+ params?: MasteringProcessorParams;
66
+ }
67
+
55
68
  /** Canonical request form for {@link masteringAbMatchLoudness}. */
56
69
  export interface MasteringAbMatchLoudnessRequest {
57
70
  /** The take to gain-match. */
@@ -61,6 +74,15 @@ export interface MasteringAbMatchLoudnessRequest {
61
74
  sampleRate?: number;
62
75
  }
63
76
 
77
+ /** Canonical request form for stereo AB loudness matching. */
78
+ export interface MasteringAbMatchLoudnessStereoRequest {
79
+ sourceLeft: Float32Array;
80
+ sourceRight: Float32Array;
81
+ referenceLeft: Float32Array;
82
+ referenceRight: Float32Array;
83
+ sampleRate?: number;
84
+ }
85
+
64
86
  /** Canonical request form for a two-input match analysis. */
65
87
  export interface MasteringPairAnalyzeRequest {
66
88
  analysisName: PairAnalysis;
@@ -548,6 +570,60 @@ export function masteringPairProcess(
548
570
  );
549
571
  }
550
572
 
573
+ /**
574
+ * Apply the stereo `match.abCrossfade` processor. Source and reference stereo
575
+ * pairs may have independent lengths, but each pair must have equal channels.
576
+ */
577
+ export function masteringPairProcessStereo(
578
+ request: MasteringPairProcessStereoRequest,
579
+ ): MasteringStereoResult;
580
+ export function masteringPairProcessStereo(
581
+ processorName: StereoPairProcessor,
582
+ sourceLeft: Float32Array,
583
+ sourceRight: Float32Array,
584
+ referenceLeft: Float32Array,
585
+ referenceRight: Float32Array,
586
+ sampleRate?: number,
587
+ params?: MasteringProcessorParams,
588
+ ): MasteringStereoResult;
589
+ export function masteringPairProcessStereo(
590
+ processorName: StereoPairProcessor | MasteringPairProcessStereoRequest,
591
+ sourceLeft?: Float32Array,
592
+ sourceRight?: Float32Array,
593
+ referenceLeft?: Float32Array,
594
+ referenceRight?: Float32Array,
595
+ sampleRate = 22050,
596
+ params: MasteringProcessorParams = {},
597
+ ): MasteringStereoResult {
598
+ const request =
599
+ typeof processorName === 'string'
600
+ ? {
601
+ processorName,
602
+ sourceLeft: sourceLeft as Float32Array,
603
+ sourceRight: sourceRight as Float32Array,
604
+ referenceLeft: referenceLeft as Float32Array,
605
+ referenceRight: referenceRight as Float32Array,
606
+ sampleRate,
607
+ params,
608
+ }
609
+ : processorName;
610
+ if (request.sourceLeft.length !== request.sourceRight.length) {
611
+ throw new Error('Source left and right channel lengths must match.');
612
+ }
613
+ if (request.referenceLeft.length !== request.referenceRight.length) {
614
+ throw new Error('Reference left and right channel lengths must match.');
615
+ }
616
+ return requireModule().masteringPairProcessStereo(
617
+ request.processorName,
618
+ request.sourceLeft,
619
+ request.sourceRight,
620
+ request.referenceLeft,
621
+ request.referenceRight,
622
+ request.sampleRate ?? 22050,
623
+ request.params ?? {},
624
+ );
625
+ }
626
+
551
627
  /**
552
628
  * Analyze a `source` against a `reference` with a two-input analysis. The two
553
629
  * buffers may have independent lengths.
@@ -616,6 +692,25 @@ export function masteringAbMatchLoudness(
616
692
  );
617
693
  }
618
694
 
695
+ /** Gain-match a stereo source to a stereo reference with one shared gain. */
696
+ export function masteringAbMatchLoudnessStereo(
697
+ request: MasteringAbMatchLoudnessStereoRequest,
698
+ ): LoudnessMatchStereoResult {
699
+ if (request.sourceLeft.length !== request.sourceRight.length) {
700
+ throw new Error('Source left and right channel lengths must match.');
701
+ }
702
+ if (request.referenceLeft.length !== request.referenceRight.length) {
703
+ throw new Error('Reference left and right channel lengths must match.');
704
+ }
705
+ return requireModule().masteringAbMatchLoudnessStereo(
706
+ request.sourceLeft,
707
+ request.sourceRight,
708
+ request.referenceLeft,
709
+ request.referenceRight,
710
+ request.sampleRate ?? 22050,
711
+ );
712
+ }
713
+
619
714
  export function masteringStereoAnalyze(request: MasteringStereoAnalyzeRequest): string;
620
715
  export function masteringStereoAnalyze(
621
716
  analysisName: StereoAnalysis,
package/src/mixer.ts CHANGED
@@ -188,50 +188,70 @@ export class Mixer {
188
188
  * after {@link delete}.
189
189
  */
190
190
  createRealtimeBuffer(): MixerRealtimeBuffer {
191
- const stripCount = this.stripCount();
192
- let leftInputs: Float32Array[] = [];
193
- let rightInputs: Float32Array[] = [];
191
+ const leftInputs: Float32Array[] = [];
192
+ const rightInputs: Float32Array[] = [];
194
193
  let outLeft = this.mixer.outputLeftView();
195
194
  let outRight = this.mixer.outputRightView();
195
+ let acquiredStripCount = -1;
196
+
197
+ // Every view shares one heap buffer, so a growth detaches the output views too.
198
+ const viewsDetached = (): boolean => outLeft.byteLength === 0 || outRight.byteLength === 0;
199
+
196
200
  const acquire = (): void => {
197
- leftInputs = [];
198
- rightInputs = [];
199
- for (let index = 0; index < stripCount; index++) {
201
+ const stripCount = this.stripCount();
202
+ const detached = viewsDetached();
203
+ if (detached) {
204
+ // A heap growth detached every view: reacquire all planes.
205
+ leftInputs.length = 0;
206
+ rightInputs.length = 0;
207
+ } else {
208
+ // Topology growth keeps existing planes in place: append views for new strips only.
209
+ leftInputs.length = Math.min(leftInputs.length, stripCount);
210
+ rightInputs.length = Math.min(rightInputs.length, stripCount);
211
+ }
212
+ for (let index = leftInputs.length; index < stripCount; index++) {
200
213
  leftInputs.push(this.mixer.inputLeftView(index));
214
+ }
215
+ for (let index = rightInputs.length; index < stripCount; index++) {
201
216
  rightInputs.push(this.mixer.inputRightView(index));
202
217
  }
203
- outLeft = this.mixer.outputLeftView();
204
- outRight = this.mixer.outputRightView();
218
+ if (detached) {
219
+ outLeft = this.mixer.outputLeftView();
220
+ outRight = this.mixer.outputRightView();
221
+ }
222
+ acquiredStripCount = stripCount;
205
223
  };
206
224
  acquire();
225
+
207
226
  // The cached heap views can detach if WASM linear memory grows (the embind
208
- // module is built ALLOW_MEMORY_GROWTH). Re-acquire them if detached
209
- // (byteLength === 0) before use, mirroring the worklet RT path.
210
- const reacquireIfDetached = (): void => {
211
- if (outLeft.byteLength === 0 || (leftInputs[0]?.byteLength ?? 1) === 0) {
227
+ // module is built ALLOW_MEMORY_GROWTH). Also refresh the view list when a
228
+ // caller adds a strip and recompiles the graph after this buffer was made.
229
+ const acquireIfNeeded = (): void => {
230
+ if (acquiredStripCount !== this.stripCount() || viewsDetached()) {
212
231
  acquire();
213
232
  }
214
233
  };
215
234
  return {
216
235
  get leftInputs(): Float32Array[] {
217
- reacquireIfDetached();
236
+ acquireIfNeeded();
218
237
  return leftInputs;
219
238
  },
220
239
  get rightInputs(): Float32Array[] {
221
- reacquireIfDetached();
240
+ acquireIfNeeded();
222
241
  return rightInputs;
223
242
  },
224
243
  get outLeft(): Float32Array {
225
- reacquireIfDetached();
244
+ acquireIfNeeded();
226
245
  return outLeft;
227
246
  },
228
247
  get outRight(): Float32Array {
229
- reacquireIfDetached();
248
+ acquireIfNeeded();
230
249
  return outRight;
231
250
  },
232
- process: (numSamples = outLeft.length) => {
233
- reacquireIfDetached();
234
- this.mixer.processPreparedStereo(numSamples);
251
+ process: (numSamples?: number) => {
252
+ acquireIfNeeded();
253
+ // Resolve the default only after reacquiring: a detached view reports length 0.
254
+ this.mixer.processPreparedStereo(numSamples ?? outLeft.length);
235
255
  },
236
256
  };
237
257
  }
@@ -532,7 +532,7 @@ export interface MidiCcLearnOptions {
532
532
  minValue?: number;
533
533
  /** Upper end of the mapped parameter range. Default `1`. */
534
534
  maxValue?: number;
535
- /** Minimum normalized CC movement required to learn a binding. Default `0`. */
535
+ /** Activity threshold in 7-bit CC units (integer 0-127) a controller must move to be learned. Default `0`. */
536
536
  minMovement?: number;
537
537
  }
538
538
 
@@ -630,12 +630,14 @@ export interface TranscribeOptions {
630
630
  */
631
631
  referenceHz?: number;
632
632
  /**
633
- * Monophonic tracker range in Hz. Defaults `65` and `2093`; both must be
634
- * finite and positive, and `fmax` must exceed `fmin`. The polyphonic chain
635
- * sets its own range and reads neither.
633
+ * F0 tracker range in Hz. Both paths use these bounds; the polyphonic path
634
+ * applies them to the salience estimator's F0 axis while its cent-spectrum
635
+ * bounds remain independent. When omitted, the monophonic defaults are `65`
636
+ * and `2093`, while the polyphonic defaults are `55` and `1760`. Explicit
637
+ * values must be finite and positive, and `fmax` must exceed `fmin`.
636
638
  */
637
639
  fmin?: number;
638
- /** Upper end of the monophonic tracker range in Hz. Default `2093`. */
640
+ /** Upper end of the F0 tracker range in Hz; omitted defaults to `2093` mono or `1760` poly. */
639
641
  fmax?: number;
640
642
  /** Shortest span kept as a note, in milliseconds. Default `30`; must be positive. */
641
643
  minNoteMs?: number;
@@ -80,6 +80,7 @@ export const SOLO_PROCESSORS = [
80
80
  'effects.acoustic.roomMorph',
81
81
  'effects.delay.stereo',
82
82
  'effects.filter.vowel',
83
+ 'effects.gsEfx',
83
84
  'effects.modulation.autoWah',
84
85
  'effects.modulation.chorus',
85
86
  'effects.modulation.ensemble',
@@ -162,6 +163,9 @@ export type PairProcessor =
162
163
  | 'match.abSwitch'
163
164
  | 'match.abCrossfade';
164
165
 
166
+ /** Pair processors with a stereo entry point. */
167
+ export type StereoPairProcessor = 'match.abCrossfade';
168
+
165
169
  export type PairAnalysis =
166
170
  | 'match.referenceLoudness'
167
171
  | 'match.tonalBalance'
@@ -213,6 +217,23 @@ export interface LoudnessMatchResult {
213
217
  matchedTruePeakDbtp: number;
214
218
  }
215
219
 
220
+ /** Stereo counterpart of {@link LoudnessMatchResult}. */
221
+ export interface LoudnessMatchStereoResult {
222
+ /** The left source channel, gain-matched to the reference loudness. */
223
+ left: Float32Array;
224
+ /** The right source channel, gain-matched with the same gain as `left`. */
225
+ right: Float32Array;
226
+ sampleRate: number;
227
+ /** The reference program's BS.1770 integrated loudness. */
228
+ referenceLufs: number;
229
+ /** The stereo source program's loudness before the gain. */
230
+ sourceLufs: number;
231
+ /** One gain applied to both source channels, in dB. */
232
+ appliedGainDb: number;
233
+ /** Maximum true peak across the matched left and right channels, in dBTP. */
234
+ matchedTruePeakDbtp: number;
235
+ }
236
+
216
237
  /**
217
238
  * Mastering loudness/true-peak processing result
218
239
  */
@@ -158,6 +158,7 @@ export interface PlaybackDiagnostics {
158
158
  loudness_gain_clamped: boolean;
159
159
  /** True when an `hrtf` was supplied to a speakers target, which ignores it. */
160
160
  hrtf_ignored: boolean;
161
+ /** Deepest last-block gain reduction across the main and LFE limiters, in dB (<= 0). */
161
162
  limiter_gain_reduction_db: number;
162
163
  non_finite_discards: number;
163
164
  }
@@ -34,10 +34,10 @@ export interface PitchCorrectOptions extends ValidateOptions {
34
34
  retuneSpeedMs?: number;
35
35
  /** Corrections below this are bypassed to preserve vibrato (cents). Default 20. */
36
36
  vibratoThresholdCents?: number;
37
- /** Per-frame voiced flags (truthy = voiced); omit to treat all frames as voiced. */
38
- voiced?: VoicedFlags;
39
- /** Per-frame voicing probability in `[0, 1]`; omit to derive from `voiced`. */
40
- voicedProb?: Float32Array;
37
+ /** Per-frame voiced flags (truthy = voiced); when supplied, these take precedence over `voicedProb`. Omit or pass `null` to use `voicedProb`; with neither, all frames are treated as voiced. */
38
+ voiced?: VoicedFlags | null;
39
+ /** Per-frame voicing probability in `[0, 1]`; used only when `voiced` is omitted or `null`, and ignored when flags are supplied. */
40
+ voicedProb?: Float32Array | null;
41
41
  }
42
42
 
43
43
  /** Options for `noteStretch`. All fields are optional. */
@@ -68,7 +68,7 @@ export interface NoteExtractorOptions {
68
68
  referenceHz?: number;
69
69
  /**
70
70
  * Value of `voicedProb` at or above which a frame counts as voiced, in
71
- * `[0, 1]`. Read only when `voiced` is omitted. Default 0.5.
71
+ * `[0, 1]`. Read only when `voiced` is omitted or `null`. Default 0.5.
72
72
  *
73
73
  * pYIN's `voicedProb` is a frame's voiced observation mass and rises with F0
74
74
  * for a fixed frame length, so this default silently drops low registers —
@@ -29,6 +29,7 @@ import type {
29
29
  WasmEngineAutomationPoint,
30
30
  WasmEngineBounceOptions,
31
31
  WasmEngineBounceResult,
32
+ WasmEngineBus,
32
33
  WasmEngineCaptureStatus,
33
34
  WasmEngineClip,
34
35
  WasmEngineFreezeOptions,
@@ -44,6 +45,7 @@ import type {
44
45
  WasmEngineTelemetry,
45
46
  WasmEngineTempoSegment,
46
47
  WasmEngineTimeSignatureSegment,
48
+ WasmEngineTrackSend,
47
49
  WasmEngineTransportState,
48
50
  WasmExternalMidiEvent,
49
51
  WasmRealtimeEngine,
@@ -626,7 +628,10 @@ export class RealtimeEngine {
626
628
  /**
627
629
  * Route a destination's (track lane's) MIDI to the external output queue
628
630
  * instead of the internal instrument rack, so the track plays an external
629
- * device. Clearing it restores internal-synth playback.
631
+ * device. Clearing it restores internal-synth playback. Control-thread only.
632
+ * The change takes effect at the next processed block; switching a
633
+ * destination's route first releases its notes and resets its controllers
634
+ * through the old route, and drops its pending MIDI-FX events. Single writer.
630
635
  */
631
636
  setMidiDestinationExternal(destinationId: number, external: boolean): void {
632
637
  this.native.setMidiDestinationExternal(destinationId, external);
@@ -883,8 +888,11 @@ export class RealtimeEngine {
883
888
  /**
884
889
  * Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
885
890
  * the full message including the leading 0xF0 and trailing 0xF7 (1..512
886
- * bytes). `renderFrame` is the frame to fire at, or -1 for immediate. Mirrors
887
- * the Node/Python/C-ABI `pushMidiSysex`.
891
+ * bytes). `renderFrame` is the frame to fire at, or -1 for immediate. Throws
892
+ * `InvalidParameter` when the destination instrument cannot prepare
893
+ * the SysEx (retrying cannot help), and `OutOfMemory` when the payload
894
+ * slots or the command queue are full (retry after a processed block).
895
+ * Mirrors the Node/Python/C-ABI `pushMidiSysex`.
888
896
  */
889
897
  pushMidiSysex(destinationId: number, data: Uint8Array, renderFrame = -1): void {
890
898
  this.native.pushMidiSysex(destinationId, data, renderFrame);
@@ -911,14 +919,34 @@ export class RealtimeEngine {
911
919
  return this.native.getTransportState();
912
920
  }
913
921
 
922
+ /** Queues an integrated-loudness reset; short-term and momentary windows are retained. */
923
+ resetMasterLoudnessMeter(renderFrame = -1): void {
924
+ this.native.resetMasterLoudnessMeter(renderFrame);
925
+ }
926
+
927
+ /** Reads the immutable factory value for a resolved insert parameter id. */
928
+ insertParameterConstructedValue(paramId: number): number {
929
+ return this.native.insertParameterConstructedValue(paramId);
930
+ }
931
+
914
932
  play(renderFrame = -1): void {
915
933
  this.native.play(renderFrame);
916
934
  }
917
935
 
936
+ /**
937
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
938
+ * centred pitch bend on every channel played since the last reset, so
939
+ * controller values set before a loop region are not restored at the wrap.
940
+ */
918
941
  stop(renderFrame = -1): void {
919
942
  this.native.stop(renderFrame);
920
943
  }
921
944
 
945
+ /**
946
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
947
+ * centred pitch bend on every channel played since the last reset, so
948
+ * controller values set before a loop region are not restored at the wrap.
949
+ */
922
950
  seekSample(timelineSample: number, renderFrame = -1): void {
923
951
  this.native.seekSample(timelineSample, renderFrame);
924
952
  }
@@ -948,6 +976,11 @@ export class RealtimeEngine {
948
976
  this.native.applyCommandsDueNowPreservingFuture();
949
977
  }
950
978
 
979
+ /**
980
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
981
+ * centred pitch bend on every channel played since the last reset, so
982
+ * controller values set before a loop region are not restored at the wrap.
983
+ */
951
984
  seekPpq(ppq: number, renderFrame = -1): void {
952
985
  this.native.seekPpq(ppq, renderFrame);
953
986
  }
@@ -973,6 +1006,11 @@ export class RealtimeEngine {
973
1006
  return Number(this.native.sampleAtPpq(ppq));
974
1007
  }
975
1008
 
1009
+ /**
1010
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
1011
+ * centred pitch bend on every channel played since the last reset, so
1012
+ * controller values set before a loop region are not restored at the wrap.
1013
+ */
976
1014
  setLoop(startPpq: number, endPpq: number, enabled = true): void {
977
1015
  this.native.setLoop(startPpq, endPpq, enabled);
978
1016
  }
@@ -1079,7 +1117,7 @@ export class RealtimeEngine {
1079
1117
  * reads (defaults to post-fader when omitted). Shared by track lanes and
1080
1118
  * buses, which carry the same send shape.
1081
1119
  */
1082
- private static normalizeSends(sends: EngineTrackSend[]): EngineTrackSend[] {
1120
+ private static normalizeSends(sends: EngineTrackSend[]): WasmEngineTrackSend[] {
1083
1121
  return sends.map((send) => ({
1084
1122
  ...send,
1085
1123
  // Post-fader (0) is the default for an omitted sendTiming.
@@ -1093,10 +1131,10 @@ export class RealtimeEngine {
1093
1131
  if (typeof lane === 'number') {
1094
1132
  return { trackId: lane };
1095
1133
  }
1096
- if (!lane.sends) {
1097
- return lane;
1098
- }
1099
- return { ...lane, sends: RealtimeEngine.normalizeSends(lane.sends) };
1134
+ return {
1135
+ ...lane,
1136
+ sends: lane.sends ? RealtimeEngine.normalizeSends(lane.sends) : undefined,
1137
+ };
1100
1138
  }),
1101
1139
  );
1102
1140
  }
@@ -1116,10 +1154,11 @@ export class RealtimeEngine {
1116
1154
  // failing here on a `.map` that array-likes do not implement.
1117
1155
  this.native.setTrackBuses(
1118
1156
  Array.isArray(buses)
1119
- ? buses.map((bus) =>
1120
- bus.sends ? { ...bus, sends: RealtimeEngine.normalizeSends(bus.sends) } : bus,
1121
- )
1122
- : buses,
1157
+ ? buses.map((bus) => ({
1158
+ ...bus,
1159
+ sends: bus.sends ? RealtimeEngine.normalizeSends(bus.sends) : undefined,
1160
+ }))
1161
+ : (buses as WasmEngineBus[]),
1123
1162
  );
1124
1163
  }
1125
1164
 
@@ -1357,6 +1396,16 @@ export class RealtimeEngine {
1357
1396
  * stages of the offline mastering chain (`repair.*`, `loudness`, and the
1358
1397
  * match stages) have no insert form and no automation id: they buffer the
1359
1398
  * entire signal by construction and do not run on the realtime path.
1399
+ *
1400
+ * The returned id uses the track's current positional lane selector. When
1401
+ * `setTrackLanes` successfully changes lane order or membership, the engine
1402
+ * remaps already queued and published track automation by track id, but it
1403
+ * cannot update a numeric id retained by the caller. Re-resolve every track
1404
+ * insert id after such a topology change before passing it to
1405
+ * `setAutomationLane`, `setParameter`, or `setParameterSmoothed`. Use
1406
+ * `setTrackStripInsertParamByName` when the operation needs a stable track
1407
+ * identity. Master and bus insert ids are separate and are not invalidated by
1408
+ * track-lane changes.
1360
1409
  */
1361
1410
  resolveTrackInsertAutomationId(trackId: number, insertIndex: number, paramName: string): number {
1362
1411
  return this.native.resolveTrackInsertAutomationId(trackId, insertIndex, paramName);
@@ -1757,6 +1806,12 @@ export class RealtimeEngine {
1757
1806
  meterScratchRenderFrame(): number {
1758
1807
  return Number(this.native.meterScratchRenderFrame());
1759
1808
  }
1809
+ meterScratchInputPeakDbL(): number {
1810
+ return this.native.meterScratchInputPeakDbL();
1811
+ }
1812
+ meterScratchInputPeakDbR(): number {
1813
+ return this.native.meterScratchInputPeakDbR();
1814
+ }
1760
1815
  meterScratchValue(field: number): number {
1761
1816
  return this.native.meterScratchValue(field);
1762
1817
  }
@@ -1768,9 +1823,10 @@ export class RealtimeEngine {
1768
1823
  /**
1769
1824
  * Drains pending meter telemetry as per-plane (wide) records for a surround
1770
1825
  * target. Use this for a surround mix target; {@link drainMeterTelemetry}
1771
- * stays the stereo fast path. The two share one queue — call only one per
1772
- * target. The live AudioWorklet path owns the queue via the stereo drain, so
1773
- * this wide drain is for an offline (non-worklet) engine instance; per-plane
1826
+ * stays the stereo fast path. The two share one queue and each consumes every
1827
+ * target's records, so an engine uses only one of them. The live AudioWorklet
1828
+ * path owns the queue via the stereo drain, so this wide drain is for an
1829
+ * offline (non-worklet) engine instance; per-plane
1774
1830
  * surround meters are not delivered over the live worklet meter ring.
1775
1831
  */
1776
1832
  drainMeterTelemetryWide(maxRecords = 1024): EngineMeterTelemetryWide[] {
@@ -829,6 +829,11 @@ export interface WasmLoudnessMatchResult {
829
829
  matchedTruePeakDbtp: number;
830
830
  }
831
831
 
832
+ export interface WasmLoudnessMatchStereoResult extends Omit<WasmLoudnessMatchResult, 'samples'> {
833
+ left: Float32Array;
834
+ right: Float32Array;
835
+ }
836
+
832
837
  export interface WasmStageGainReduction {
833
838
  stage: string;
834
839
  gainReductionDb: number;
@@ -965,6 +970,8 @@ export interface WasmEngineTrackSend {
965
970
  busId: number;
966
971
  levelDb?: number;
967
972
  enabled?: boolean;
973
+ /** Native send tap: 0 post-fader (default), 1 pre-fader. */
974
+ sendTiming?: number;
968
975
  }
969
976
 
970
977
  export interface WasmEngineTrackLane {
@@ -1165,6 +1172,10 @@ export interface WasmEngineMeterTelemetry {
1165
1172
  shortTermLufs: number;
1166
1173
  integratedLufs: number;
1167
1174
  gainReductionDb: number;
1175
+ /** Maximum pre-trim input peak for the left channel, in dBFS. */
1176
+ inputPeakDbL: number;
1177
+ /** Maximum pre-trim input peak for the right channel, in dBFS. */
1178
+ inputPeakDbR: number;
1168
1179
  droppedRecords: number;
1169
1180
  }
1170
1181
 
@@ -1199,6 +1210,8 @@ export interface WasmEngineMeterTelemetryWide {
1199
1210
  shortTermLufs: number;
1200
1211
  integratedLufs: number;
1201
1212
  gainReductionDb: number;
1213
+ /** Pre-trim input peak for each valid plane, in dBFS. */
1214
+ inputPeakDb: number[];
1202
1215
  droppedRecords: number;
1203
1216
  }
1204
1217
 
@@ -1363,6 +1376,8 @@ export interface WasmRealtimeEngine {
1363
1376
  getTransportState: () => WasmEngineTransportState;
1364
1377
  play: (renderFrame?: number) => void;
1365
1378
  stop: (renderFrame?: number) => void;
1379
+ resetMasterLoudnessMeter: (renderFrame?: number) => void;
1380
+ insertParameterConstructedValue: (paramId: number) => number;
1366
1381
  seekSample: (timelineSample: number, renderFrame?: number) => void;
1367
1382
  settleParameters: () => void;
1368
1383
  settleInsertParameters: () => void;
@@ -1707,6 +1722,8 @@ export interface WasmRealtimeEngine {
1707
1722
  meterScratchTargetId: () => number;
1708
1723
  meterScratchRenderFrame: () => number;
1709
1724
  meterScratchValue: (field: number) => number;
1725
+ meterScratchInputPeakDbL: () => number;
1726
+ meterScratchInputPeakDbR: () => number;
1710
1727
  drainMeterTelemetry: (maxRecords: number) => WasmEngineMeterTelemetry[];
1711
1728
  drainMeterTelemetryWide: (maxRecords: number) => WasmEngineMeterTelemetryWide[];
1712
1729
  configureScopeTelemetry: (intervalFrames: number, bandCount: number) => number;
@@ -2329,12 +2346,14 @@ export interface SonareModule {
2329
2346
  options: {
2330
2347
  fadeMs?: number;
2331
2348
  f0Hz?: Float32Array;
2349
+ voiced?: Float32Array;
2332
2350
  frameRate?: number;
2333
2351
  vibratoCutoffHz?: number;
2334
2352
  },
2335
2353
  ) => Float32Array;
2336
2354
  decomposeNotePitch: (
2337
2355
  f0Hz: Float32Array,
2356
+ voiced: Float32Array | undefined,
2338
2357
  frameRate: number,
2339
2358
  medianHz: number,
2340
2359
  vibratoCutoffHz: number,
@@ -2493,11 +2512,27 @@ export interface SonareModule {
2493
2512
  sampleRate: number,
2494
2513
  params: Record<string, number | boolean>,
2495
2514
  ) => string;
2515
+ masteringPairProcessStereo: (
2516
+ processorName: string,
2517
+ sourceLeft: Float32Array,
2518
+ sourceRight: Float32Array,
2519
+ referenceLeft: Float32Array,
2520
+ referenceRight: Float32Array,
2521
+ sampleRate: number,
2522
+ params: Record<string, number | boolean>,
2523
+ ) => WasmMasteringStereoResult;
2496
2524
  masteringAbMatchLoudness: (
2497
2525
  source: Float32Array,
2498
2526
  reference: Float32Array,
2499
2527
  sampleRate: number,
2500
2528
  ) => WasmLoudnessMatchResult;
2529
+ masteringAbMatchLoudnessStereo: (
2530
+ sourceLeft: Float32Array,
2531
+ sourceRight: Float32Array,
2532
+ referenceLeft: Float32Array,
2533
+ referenceRight: Float32Array,
2534
+ sampleRate: number,
2535
+ ) => WasmLoudnessMatchStereoResult;
2501
2536
  masteringStereoAnalyze: (
2502
2537
  analysisName: string,
2503
2538
  left: Float32Array,
@@ -3521,6 +3556,7 @@ export interface WasmPolyphonicAnalysis {
3521
3556
 
3522
3557
  export interface WasmStreamingMasteringChain {
3523
3558
  prepare: (sampleRate: number, maxBlockSize: number, numChannels: number) => void;
3559
+ setParameter: (key: string, value: number) => void;
3524
3560
  processMono: (samples: Float32Array) => Float32Array;
3525
3561
  processStereo: (
3526
3562
  left: Float32Array,
@@ -171,6 +171,14 @@ export class StreamingMasteringChain {
171
171
  this.chain.reset();
172
172
  }
173
173
 
174
+ /**
175
+ * Set one realtime-safe full-chain parameter at a serialized block boundary.
176
+ * Structural, disabled-stage and unknown parameters are rejected.
177
+ */
178
+ setParameter(key: string, value: number): void {
179
+ this.chain.setParameter(key, value);
180
+ }
181
+
174
182
  /** Total reported latency in samples across all active processors. */
175
183
  latencySamples(): number {
176
184
  return this.chain.latencySamples();