@libraz/libsonare 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -54,6 +54,7 @@ function makeSonareError(raw, thrown) {
54
54
  }
55
55
  function wrapModuleErrors(raw) {
56
56
  const cache = /* @__PURE__ */ new Map();
57
+ const objectCache = /* @__PURE__ */ new WeakMap();
57
58
  const convert = (error) => {
58
59
  const ptr = nativeExceptionPtr(error);
59
60
  if (ptr !== null) {
@@ -61,6 +62,83 @@ function wrapModuleErrors(raw) {
61
62
  }
62
63
  throw error;
63
64
  };
65
+ const wrapNativeObject = (value) => {
66
+ if (value === null || typeof value !== "object") {
67
+ return value;
68
+ }
69
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer || value instanceof Promise) {
70
+ return value;
71
+ }
72
+ const objectValue = value;
73
+ const cached = objectCache.get(objectValue);
74
+ if (cached) {
75
+ return cached;
76
+ }
77
+ const methodCache = /* @__PURE__ */ new Map();
78
+ const wrapped = new Proxy(objectValue, {
79
+ get(target, prop, receiver) {
80
+ const member = Reflect.get(target, prop, receiver);
81
+ if (typeof member !== "function") {
82
+ return member;
83
+ }
84
+ const cachedMethod = methodCache.get(prop);
85
+ if (cachedMethod) {
86
+ return cachedMethod;
87
+ }
88
+ const method = member;
89
+ const wrappedMethod = (...args) => {
90
+ try {
91
+ return wrapNativeObject(Reflect.apply(method, target, args));
92
+ } catch (error) {
93
+ return convert(error);
94
+ }
95
+ };
96
+ methodCache.set(prop, wrappedMethod);
97
+ return wrappedMethod;
98
+ }
99
+ });
100
+ objectCache.set(objectValue, wrapped);
101
+ return wrapped;
102
+ };
103
+ const wrapFunction = (value) => {
104
+ const fnCache = /* @__PURE__ */ new Map();
105
+ return new Proxy(value, {
106
+ get(target, prop, receiver) {
107
+ const member = Reflect.get(target, prop, receiver);
108
+ if (typeof member !== "function") {
109
+ return member;
110
+ }
111
+ const cachedMember = fnCache.get(prop);
112
+ if (cachedMember) {
113
+ return cachedMember;
114
+ }
115
+ const fn = member;
116
+ const wrappedMember = (...args) => {
117
+ try {
118
+ return wrapNativeObject(Reflect.apply(fn, target, args));
119
+ } catch (error) {
120
+ return convert(error);
121
+ }
122
+ };
123
+ fnCache.set(prop, wrappedMember);
124
+ return wrappedMember;
125
+ },
126
+ apply(t, thisArg, args) {
127
+ try {
128
+ return wrapNativeObject(Reflect.apply(t, thisArg, args));
129
+ } catch (error) {
130
+ return convert(error);
131
+ }
132
+ },
133
+ construct(t, args, newTarget) {
134
+ try {
135
+ return wrapNativeObject(Reflect.construct(t, args, newTarget));
136
+ } catch (error) {
137
+ return convert(error);
138
+ }
139
+ }
140
+ });
141
+ };
64
142
  return new Proxy(raw, {
65
143
  get(target, prop, receiver) {
66
144
  const value = Reflect.get(target, prop, receiver);
@@ -71,23 +149,7 @@ function wrapModuleErrors(raw) {
71
149
  if (cached) {
72
150
  return cached;
73
151
  }
74
- const fn = value;
75
- const wrapped = new Proxy(fn, {
76
- apply(t, thisArg, args) {
77
- try {
78
- return Reflect.apply(t, thisArg, args);
79
- } catch (error) {
80
- return convert(error);
81
- }
82
- },
83
- construct(t, args, newTarget) {
84
- try {
85
- return Reflect.construct(t, args, newTarget);
86
- } catch (error) {
87
- return convert(error);
88
- }
89
- }
90
- });
152
+ const wrapped = wrapFunction(value);
91
153
  cache.set(prop, wrapped);
92
154
  return wrapped;
93
155
  }
@@ -152,7 +214,10 @@ function meterTapCode(tap) {
152
214
  return tap === "preFader" || tap === 0 ? 0 : 1;
153
215
  }
154
216
  function sendTimingCode(timing) {
155
- return timing === "preFader" || timing === 0 ? 0 : 1;
217
+ if (typeof timing === "number") {
218
+ return timing;
219
+ }
220
+ return timing === "preFader" ? 1 : 0;
156
221
  }
157
222
 
158
223
  // src/mixer.ts
@@ -405,6 +470,13 @@ var Mixer = class _Mixer {
405
470
  setDualPan(stripIndex, leftPan, rightPan) {
406
471
  this.mixer.setDualPan(stripIndex, leftPan, rightPan);
407
472
  }
473
+ /**
474
+ * Set the strip's surround pan position, used when it feeds a >2-channel bus.
475
+ * Stored on the scene; inert until the surround DSP path applies it.
476
+ */
477
+ setSurroundPan(stripIndex, pan) {
478
+ this.mixer.setSurroundPan(stripIndex, pan);
479
+ }
408
480
  /**
409
481
  * Add a send to a strip after construction.
410
482
  *
@@ -1107,6 +1179,11 @@ function normalize(samples, sampleRate, targetDb = 0, options = {}) {
1107
1179
  assertSamples("normalize", samples, options.validate !== false);
1108
1180
  return requireModule().normalize(samples, sampleRate, targetDb);
1109
1181
  }
1182
+ function spectralEdit(samples, sampleRate, ops = [], options = {}) {
1183
+ assertSamples("spectralEdit", samples, options.validate !== false);
1184
+ assertSampleRate("spectralEdit", sampleRate);
1185
+ return requireModule().spectralEdit(samples, sampleRate, ops, options);
1186
+ }
1110
1187
  function mastering(samples, sampleRate = 22050, options = {}) {
1111
1188
  return requireModule().mastering(
1112
1189
  samples,
@@ -1125,6 +1202,14 @@ function masteringInsertNames() {
1125
1202
  function masteringInsertParamNames(name) {
1126
1203
  return requireModule().masteringInsertParamNames(name);
1127
1204
  }
1205
+ function masteringInsertParamInfo(name) {
1206
+ const json = requireModule().masteringInsertParamInfo(name);
1207
+ return JSON.parse(json);
1208
+ }
1209
+ function masteringProcessorCatalog() {
1210
+ const json = requireModule().masteringProcessorCatalog();
1211
+ return JSON.parse(json);
1212
+ }
1128
1213
  function masteringPairProcessorNames() {
1129
1214
  return requireModule().masteringPairProcessorNames();
1130
1215
  }
@@ -1469,11 +1554,18 @@ function analyzeMelody(samples, sampleRate = 22050, options = {}) {
1469
1554
  const fmin = options.fmin ?? 65;
1470
1555
  const fmax = options.fmax ?? 2093;
1471
1556
  validateFrequencyBounds("analyzeMelody", fmin, fmax);
1557
+ if (fmin <= 0) {
1558
+ throw new RangeError("analyzeMelody: fmin must be positive");
1559
+ }
1472
1560
  validatePositiveIntegers("analyzeMelody", {
1473
1561
  frameLength: options.frameLength ?? 2048,
1474
1562
  hopLength: options.hopLength ?? 256
1475
1563
  });
1476
- assertFiniteScalar("analyzeMelody", options.threshold ?? 0.1, "threshold");
1564
+ const threshold = options.threshold ?? 0.1;
1565
+ assertFiniteScalar("analyzeMelody", threshold, "threshold");
1566
+ if (threshold <= 0) {
1567
+ throw new RangeError("analyzeMelody: threshold must be positive");
1568
+ }
1477
1569
  return requireModule3().analyzeMelody(
1478
1570
  samples,
1479
1571
  sampleRate,
@@ -2609,10 +2701,25 @@ function waveformPeakPyramid(samples, channels, options = {}) {
2609
2701
 
2610
2702
  // src/opfs_clip_pages.ts
2611
2703
  var opfsClipPageWorkerSource = `
2704
+ const sonareClipPageReadQueues = new Map();
2705
+
2706
+ function sonareEnqueueClipPageRead(key, task) {
2707
+ const previous = sonareClipPageReadQueues.get(key) || Promise.resolve();
2708
+ const next = previous.catch(() => undefined).then(task);
2709
+ const queued = next.finally(() => {
2710
+ if (sonareClipPageReadQueues.get(key) === queued) {
2711
+ sonareClipPageReadQueues.delete(key);
2712
+ }
2713
+ });
2714
+ sonareClipPageReadQueues.set(key, queued);
2715
+ return next;
2716
+ }
2717
+
2612
2718
  self.onmessage = async (event) => {
2613
2719
  const message = event.data;
2614
2720
  if (!message || message.type !== 'sonare:read-clip-page') return;
2615
2721
  const { requestId, path, pageIndex, numChannels, numSamples, pageFrames, dataOffsetBytes = 0 } = message;
2722
+ await sonareEnqueueClipPageRead(String(path), async () => {
2616
2723
  try {
2617
2724
  if (pageIndex < 0) {
2618
2725
  self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });
@@ -2675,6 +2782,7 @@ self.onmessage = async (event) => {
2675
2782
  error: error instanceof Error ? error.message : String(error),
2676
2783
  });
2677
2784
  }
2785
+ });
2678
2786
  };
2679
2787
  `;
2680
2788
  function createOpfsClipPageWorker() {
@@ -2694,6 +2802,7 @@ function createOpfsClipPageProvider(engine, options) {
2694
2802
  const ownsWorker = options.worker === void 0 || options.terminateWorkerOnClose === true;
2695
2803
  let nextRequestId = 1;
2696
2804
  let closed = false;
2805
+ let readQueue = Promise.resolve();
2697
2806
  const pending = /* @__PURE__ */ new Map();
2698
2807
  const onMessage = (event) => {
2699
2808
  const response = event.data;
@@ -2733,15 +2842,29 @@ function createOpfsClipPageProvider(engine, options) {
2733
2842
  const promise = new Promise((resolve, reject) => {
2734
2843
  pending.set(requestId, { resolve, reject });
2735
2844
  });
2736
- worker.postMessage({
2737
- type: "sonare:read-clip-page",
2738
- requestId,
2739
- path: options.path,
2740
- pageIndex,
2741
- numChannels: options.numChannels,
2742
- numSamples: options.numSamples,
2743
- pageFrames: options.pageFrames,
2744
- dataOffsetBytes: options.dataOffsetBytes ?? 0
2845
+ readQueue = readQueue.catch(() => void 0).then(() => {
2846
+ if (closed) {
2847
+ const entry = pending.get(requestId);
2848
+ pending.delete(requestId);
2849
+ entry?.reject(new Error("OpfsClipPageProvider is closed"));
2850
+ return;
2851
+ }
2852
+ worker.postMessage({
2853
+ type: "sonare:read-clip-page",
2854
+ requestId,
2855
+ path: options.path,
2856
+ pageIndex,
2857
+ numChannels: options.numChannels,
2858
+ numSamples: options.numSamples,
2859
+ pageFrames: options.pageFrames,
2860
+ dataOffsetBytes: options.dataOffsetBytes ?? 0
2861
+ });
2862
+ return promise.then(
2863
+ () => void 0,
2864
+ () => void 0
2865
+ );
2866
+ });
2867
+ readQueue.catch(() => {
2745
2868
  });
2746
2869
  return promise;
2747
2870
  };
@@ -2771,6 +2894,13 @@ function createOpfsClipPageProvider(engine, options) {
2771
2894
 
2772
2895
  // src/project.ts
2773
2896
  var EXPECTED_PROJECT_ABI_VERSION = 1;
2897
+ var MarkerKind = {
2898
+ marker: 0,
2899
+ text: 1,
2900
+ lyric: 2,
2901
+ cuePoint: 3,
2902
+ keySignature: 4
2903
+ };
2774
2904
  var SYNTH_ENGINE_MODES = [
2775
2905
  "default",
2776
2906
  "subtractive",
@@ -3110,6 +3240,22 @@ var Project = class _Project {
3110
3240
  setTrackMidiDestination(trackId, destinationId) {
3111
3241
  this.native.setTrackMidiDestination(trackId, destinationId);
3112
3242
  }
3243
+ /** Set a track's linear playback gain (1.0 = unity; >= 0) via an undoable edit. */
3244
+ setTrackGain(trackId, gain) {
3245
+ this.native.setTrackGain(trackId, gain);
3246
+ }
3247
+ /** Set a track's mute flag via an undoable edit (a muted track is silent). */
3248
+ setTrackMute(trackId, mute) {
3249
+ this.native.setTrackMute(trackId, mute);
3250
+ }
3251
+ /** Set a track's solo flag via an undoable edit (when any track is soloed, only soloed tracks sound). */
3252
+ setTrackSolo(trackId, solo) {
3253
+ this.native.setTrackSolo(trackId, solo);
3254
+ }
3255
+ /** Set a track's stereo balance in [-1, +1] (0 = center) via an undoable edit. */
3256
+ setTrackPan(trackId, pan) {
3257
+ this.native.setTrackPan(trackId, pan);
3258
+ }
3113
3259
  /** Undo the most recent edit. */
3114
3260
  undo() {
3115
3261
  this.native.undo();
@@ -3399,6 +3545,22 @@ var Project = class _Project {
3399
3545
  setMarker(markerId, ppq, name) {
3400
3546
  return this.native.setMarker(markerId, ppq, name);
3401
3547
  }
3548
+ /**
3549
+ * Add or replace a marker from a full {@link ProjectMarker}, including its
3550
+ * {@link MarkerKind} and (for key signatures) the key. Pass `id` 0 to allocate
3551
+ * a new id; returns the stable marker id.
3552
+ */
3553
+ setMarkerEx(marker) {
3554
+ return this.native.setMarkerEx(marker);
3555
+ }
3556
+ /** Read a project marker by index (0-based, in stored order). */
3557
+ markerByIndex(index) {
3558
+ return this.native.markerByIndex(index);
3559
+ }
3560
+ /** Number of markers in the project. */
3561
+ markerCount() {
3562
+ return this.native.markerCount();
3563
+ }
3402
3564
  /** Number of tracks in the project. */
3403
3565
  trackCount() {
3404
3566
  return this.native.trackCount();
@@ -3492,9 +3654,6 @@ function engineCapabilities() {
3492
3654
  };
3493
3655
  }
3494
3656
  var RealtimeEngine = class {
3495
- nativeExt() {
3496
- return this.native;
3497
- }
3498
3657
  constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024) {
3499
3658
  const module2 = getSonareModule();
3500
3659
  const capabilities = engineCapabilities();
@@ -3521,8 +3680,14 @@ var RealtimeEngine = class {
3521
3680
  setParameterSmoothed(paramId, value, renderFrame = -1) {
3522
3681
  this.native.setParameterSmoothed(paramId, value, renderFrame);
3523
3682
  }
3683
+ setSoloMute(laneIndex, solo, mute, renderFrame = -1) {
3684
+ this.native.setSoloMute(laneIndex, solo, mute, renderFrame);
3685
+ }
3686
+ setMidiClips(clips) {
3687
+ this.native.setMidiClips(clips);
3688
+ }
3524
3689
  setBuiltinInstrument(config = {}, destinationId = config.destinationId ?? 0) {
3525
- this.nativeExt().setBuiltinInstrument(destinationId, config);
3690
+ this.native.setBuiltinInstrument(destinationId, config);
3526
3691
  }
3527
3692
  /**
3528
3693
  * Bind the patch-driven NativeSynth to a realtime MIDI destination. `patch`
@@ -3534,7 +3699,7 @@ var RealtimeEngine = class {
3534
3699
  * binding convenience, not part of the NativeSynth patch itself.
3535
3700
  */
3536
3701
  setSynthInstrument(patch = {}, destinationId = (typeof patch === "object" ? patch.destinationId : void 0) ?? 0) {
3537
- this.nativeExt().setSynthInstrument(destinationId, patch);
3702
+ this.native.setSynthInstrument(destinationId, patch);
3538
3703
  }
3539
3704
  /**
3540
3705
  * Load (parse) SoundFont 2 bytes into the engine so SF2 instruments can be
@@ -3543,7 +3708,7 @@ var RealtimeEngine = class {
3543
3708
  * not referenced afterwards. Replaces any previously loaded SoundFont.
3544
3709
  */
3545
3710
  loadSoundFont(data) {
3546
- this.nativeExt().loadSoundFont(data);
3711
+ this.native.loadSoundFont(data);
3547
3712
  }
3548
3713
  /**
3549
3714
  * Bind a GS-compatible SoundFont player to a realtime MIDI destination, fed
@@ -3555,13 +3720,13 @@ var RealtimeEngine = class {
3555
3720
  * synthesizer GM fallback bank (the data-free floor).
3556
3721
  */
3557
3722
  setSf2Instrument(config = {}, destinationId = config.destinationId ?? 0) {
3558
- this.nativeExt().setSf2Instrument(destinationId, config);
3723
+ this.native.setSf2Instrument(destinationId, config);
3559
3724
  }
3560
3725
  clearMidiInstrument(destinationId = 0) {
3561
- this.nativeExt().clearMidiInstrument(destinationId);
3726
+ this.native.clearMidiInstrument(destinationId);
3562
3727
  }
3563
3728
  midiInstrumentCount() {
3564
- return this.nativeExt().midiInstrumentCount();
3729
+ return this.native.midiInstrumentCount();
3565
3730
  }
3566
3731
  /**
3567
3732
  * Bind a live MIDI CC to an engine automation parameter. The MIDI event still
@@ -3569,7 +3734,7 @@ var RealtimeEngine = class {
3569
3734
  * mapped into [minValue, maxValue] for `paramId`.
3570
3735
  */
3571
3736
  bindMidiCc(channel, controller, paramId, options = {}) {
3572
- this.nativeExt().bindMidiCc(
3737
+ this.native.bindMidiCc(
3573
3738
  channel,
3574
3739
  controller,
3575
3740
  paramId,
@@ -3578,42 +3743,42 @@ var RealtimeEngine = class {
3578
3743
  );
3579
3744
  }
3580
3745
  clearMidiCcBindings() {
3581
- this.nativeExt().clearMidiCcBindings();
3746
+ this.native.clearMidiCcBindings();
3582
3747
  }
3583
3748
  midiCcBindingCount() {
3584
- return this.nativeExt().midiCcBindingCount();
3749
+ return this.native.midiCcBindingCount();
3585
3750
  }
3586
3751
  /** Install/replace a live non-destructive MIDI-FX insert for one destination. */
3587
3752
  setMidiFx(destinationId, configJson) {
3588
- this.nativeExt().setMidiFx(destinationId, configJson);
3753
+ this.native.setMidiFx(destinationId, configJson);
3589
3754
  }
3590
3755
  clearMidiFx(destinationId = 0) {
3591
- this.nativeExt().clearMidiFx(destinationId);
3756
+ this.native.clearMidiFx(destinationId);
3592
3757
  }
3593
3758
  /** Enable the engine-owned live MIDI input source for a destination. */
3594
3759
  setMidiInputSource(destinationId = 0) {
3595
- this.nativeExt().setMidiInputSource(destinationId);
3760
+ this.native.setMidiInputSource(destinationId);
3596
3761
  }
3597
3762
  clearMidiInputSource() {
3598
- this.nativeExt().clearMidiInputSource();
3763
+ this.native.clearMidiInputSource();
3599
3764
  }
3600
3765
  midiInputPendingCount() {
3601
- return this.nativeExt().midiInputPendingCount();
3766
+ return this.native.midiInputPendingCount();
3602
3767
  }
3603
3768
  pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples = 0) {
3604
- this.nativeExt().pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples);
3769
+ this.native.pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples);
3605
3770
  }
3606
3771
  pushMidiInputNoteOff(group, channel, note, velocity = 0, portTimeSamples = 0) {
3607
- this.nativeExt().pushMidiInputNoteOff(group, channel, note, velocity, portTimeSamples);
3772
+ this.native.pushMidiInputNoteOff(group, channel, note, velocity, portTimeSamples);
3608
3773
  }
3609
3774
  pushMidiInputCc(group, channel, controller, value, portTimeSamples = 0) {
3610
- this.nativeExt().pushMidiInputCc(group, channel, controller, value, portTimeSamples);
3775
+ this.native.pushMidiInputCc(group, channel, controller, value, portTimeSamples);
3611
3776
  }
3612
3777
  pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame = -1) {
3613
- this.nativeExt().pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
3778
+ this.native.pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
3614
3779
  }
3615
3780
  pushMidiNoteOff(destinationId, group, channel, note, velocity = 0, renderFrame = -1) {
3616
- this.nativeExt().pushMidiNoteOff(destinationId, group, channel, note, velocity, renderFrame);
3781
+ this.native.pushMidiNoteOff(destinationId, group, channel, note, velocity, renderFrame);
3617
3782
  }
3618
3783
  /**
3619
3784
  * Queue an immediate (live) MIDI control change to a MIDI destination
@@ -3622,21 +3787,21 @@ var RealtimeEngine = class {
3622
3787
  * immediate. Mirrors the Node/Python/C-ABI `pushMidiCc`.
3623
3788
  */
3624
3789
  pushMidiCc(destinationId, group, channel, controller, value, renderFrame = -1) {
3625
- this.nativeExt().pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
3790
+ this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
3626
3791
  }
3627
3792
  /**
3628
3793
  * Queue a MIDI panic (all-notes-off) releasing every sounding note at
3629
3794
  * `renderFrame` (-1 = immediate). Mirrors the C-ABI `pushMidiPanic`.
3630
3795
  */
3631
3796
  pushMidiPanic(renderFrame = -1) {
3632
- this.nativeExt().pushMidiPanic(renderFrame);
3797
+ this.native.pushMidiPanic(renderFrame);
3633
3798
  }
3634
3799
  /**
3635
3800
  * Remove all registered parameters (and their automation lanes). Control-thread
3636
3801
  * only; not realtime-safe. Mirrors the C-ABI `clearParameters`.
3637
3802
  */
3638
3803
  clearParameters() {
3639
- this.nativeExt().clearParameters();
3804
+ this.native.clearParameters();
3640
3805
  }
3641
3806
  /** Read back the current transport state snapshot. */
3642
3807
  getTransportState() {
@@ -3651,15 +3816,33 @@ var RealtimeEngine = class {
3651
3816
  seekSample(timelineSample, renderFrame = -1) {
3652
3817
  this.native.seekSample(timelineSample, renderFrame);
3653
3818
  }
3819
+ /**
3820
+ * Snaps every in-flight parameter ramp (engine-level smoothed params, mixer
3821
+ * lane fader/pan/gate, bus gains) to its target value. Offline renders call
3822
+ * this after a priming process() block so the first audible block renders at
3823
+ * settled values instead of ramping in from defaults.
3824
+ */
3825
+ settleParameters() {
3826
+ this.native.settleParameters();
3827
+ }
3654
3828
  seekPpq(ppq, renderFrame = -1) {
3655
3829
  this.native.seekPpq(ppq, renderFrame);
3656
3830
  }
3657
3831
  setTempo(bpm) {
3658
3832
  this.native.setTempo(bpm);
3659
3833
  }
3834
+ setTempoSegments(segments) {
3835
+ this.native.setTempoSegments([...segments]);
3836
+ }
3660
3837
  setTimeSignature(numerator, denominator) {
3661
3838
  this.native.setTimeSignature(numerator, denominator);
3662
3839
  }
3840
+ setTimeSignatureSegments(segments) {
3841
+ this.native.setTimeSignatureSegments([...segments]);
3842
+ }
3843
+ sampleAtPpq(ppq) {
3844
+ return Number(this.native.sampleAtPpq(ppq));
3845
+ }
3663
3846
  setLoop(startPpq, endPpq, enabled = true) {
3664
3847
  this.native.setLoop(startPpq, endPpq, enabled);
3665
3848
  }
@@ -3728,21 +3911,140 @@ var RealtimeEngine = class {
3728
3911
  clipCount() {
3729
3912
  return this.native.clipCount();
3730
3913
  }
3914
+ setTrackLanes(lanes) {
3915
+ this.native.setTrackLanes(
3916
+ lanes.map((lane) => {
3917
+ if (typeof lane === "number") {
3918
+ return { trackId: lane };
3919
+ }
3920
+ if (!lane.sends) {
3921
+ return lane;
3922
+ }
3923
+ return {
3924
+ ...lane,
3925
+ sends: lane.sends.map((send) => ({
3926
+ ...send,
3927
+ // Post-fader (0) is the default for an omitted sendTiming.
3928
+ sendTiming: send.sendTiming === void 0 ? 0 : sendTimingCode(send.sendTiming)
3929
+ }))
3930
+ };
3931
+ })
3932
+ );
3933
+ }
3934
+ /**
3935
+ * Keys one insert of a lane strip from another lane's post-strip audio
3936
+ * (ducking/sidechainRouter inserts). sourceTrackId 0 removes the binding.
3937
+ */
3938
+ setLaneSidechain(trackId, insertIndex, sourceTrackId) {
3939
+ this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
3940
+ }
3941
+ setTrackBuses(buses) {
3942
+ this.native.setTrackBuses(buses);
3943
+ }
3944
+ setBusStripJson(busId, sceneJson) {
3945
+ try {
3946
+ JSON.parse(sceneJson);
3947
+ } catch (error) {
3948
+ const message = error instanceof Error ? error.message : "invalid bus strip JSON";
3949
+ throw new SonareError(2 /* InvalidFormat */, "InvalidFormat", message);
3950
+ }
3951
+ this.native.setBusStripJson(busId, sceneJson);
3952
+ }
3953
+ setTrackStripJson(trackId, sceneJson) {
3954
+ try {
3955
+ JSON.parse(sceneJson);
3956
+ } catch (error) {
3957
+ const message = error instanceof Error ? error.message : "invalid track strip JSON";
3958
+ throw new SonareError(2 /* InvalidFormat */, "InvalidFormat", message);
3959
+ }
3960
+ this.native.setTrackStripJson(trackId, sceneJson);
3961
+ }
3962
+ setTrackStripEqBand(trackId, bandIndex, band) {
3963
+ this.native.setTrackStripEqBandJson(
3964
+ trackId,
3965
+ bandIndex,
3966
+ typeof band === "string" ? band : JSON.stringify(band)
3967
+ );
3968
+ }
3969
+ setTrackStripEqBandJson(trackId, bandIndex, bandJson) {
3970
+ this.native.setTrackStripEqBandJson(trackId, bandIndex, bandJson);
3971
+ }
3972
+ setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass = false) {
3973
+ this.native.setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass);
3974
+ }
3975
+ setMasterStripJson(sceneJson) {
3976
+ try {
3977
+ JSON.parse(sceneJson);
3978
+ } catch (error) {
3979
+ const message = error instanceof Error ? error.message : "invalid master strip JSON";
3980
+ throw new SonareError(2 /* InvalidFormat */, "InvalidFormat", message);
3981
+ }
3982
+ this.native.setMasterStripJson(sceneJson);
3983
+ }
3984
+ setMasterStripEqBand(bandIndex, band) {
3985
+ this.native.setMasterStripEqBandJson(
3986
+ bandIndex,
3987
+ typeof band === "string" ? band : JSON.stringify(band)
3988
+ );
3989
+ }
3990
+ setMasterStripEqBandJson(bandIndex, bandJson) {
3991
+ this.native.setMasterStripEqBandJson(bandIndex, bandJson);
3992
+ }
3993
+ setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass = false) {
3994
+ this.native.setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass);
3995
+ }
3996
+ /**
3997
+ * Changes one track-strip insert parameter in realtime, addressed by the
3998
+ * processor's JSON-key parameter name (see {@link masteringInsertParamInfo}).
3999
+ * Applied at the next block head via the engine command queue; safe during
4000
+ * playback. Throws if the track, insert, or name is unknown, the param is not
4001
+ * realtime-safe, or the command queue is full.
4002
+ */
4003
+ setTrackStripInsertParamByName(trackId, insertIndex, paramName, value) {
4004
+ this.native.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
4005
+ }
4006
+ /** Master-strip counterpart of {@link setTrackStripInsertParamByName}. */
4007
+ setMasterStripInsertParamByName(insertIndex, paramName, value) {
4008
+ this.native.setMasterStripInsertParamByName(insertIndex, paramName, value);
4009
+ }
4010
+ /** Sets a track lane strip's pan position in realtime (glitch-free). */
4011
+ setTrackStripPan(trackId, pan) {
4012
+ this.native.setTrackStripPan(trackId, pan);
4013
+ }
4014
+ /** Sets a track lane strip's pan law in realtime. */
4015
+ setTrackStripPanLaw(trackId, panLaw) {
4016
+ this.native.setTrackStripPanLaw(trackId, panLawCode(panLaw));
4017
+ }
4018
+ /** Sets a track lane strip's pan mode in realtime. */
4019
+ setTrackStripPanMode(trackId, panMode) {
4020
+ this.native.setTrackStripPanMode(trackId, panModeCode(panMode));
4021
+ }
4022
+ /** Sets a track lane strip's dual-pan left/right positions in realtime. */
4023
+ setTrackStripDualPan(trackId, leftPan, rightPan) {
4024
+ this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
4025
+ }
4026
+ /**
4027
+ * Sets a track lane strip's inter-channel alignment delay (whole samples).
4028
+ * Adjusts strip latency, so PDC and reported graph latency are refreshed.
4029
+ */
4030
+ setTrackStripChannelDelaySamples(trackId, delaySamples) {
4031
+ this.native.setTrackStripChannelDelaySamples(trackId, delaySamples);
4032
+ }
3731
4033
  createClipPageProvider(numChannels, numSamples, pageFrames) {
3732
- const id = this.nativeExt().createClipPageProvider(numChannels, numSamples, pageFrames);
4034
+ const id = this.native.createClipPageProvider(numChannels, numSamples, pageFrames);
3733
4035
  return new ClipPageProvider(this, id);
3734
4036
  }
3735
4037
  supplyClipPage(providerId, pageIndex, channels) {
3736
- this.nativeExt().supplyClipPage(providerId, pageIndex, channels);
4038
+ this.native.supplyClipPage(providerId, pageIndex, channels);
3737
4039
  }
3738
4040
  clearClipPage(providerId, pageIndex) {
3739
- this.nativeExt().clearClipPage(providerId, pageIndex);
4041
+ this.native.clearClipPage(providerId, pageIndex);
3740
4042
  }
3741
4043
  destroyClipPageProvider(providerId) {
3742
- this.nativeExt().destroyClipPageProvider(providerId);
4044
+ this.native.destroyClipPageProvider(providerId);
3743
4045
  }
3744
4046
  popClipPageRequest() {
3745
- return this.nativeExt().popClipPageRequest();
4047
+ return this.native.popClipPageRequest();
3746
4048
  }
3747
4049
  setCaptureBuffer(numChannels, capacityFrames) {
3748
4050
  this.native.setCaptureBuffer(numChannels, capacityFrames);
@@ -3817,6 +4119,30 @@ var RealtimeEngine = class {
3817
4119
  drainMeterTelemetry(maxRecords = 1024) {
3818
4120
  return this.native.drainMeterTelemetry(maxRecords);
3819
4121
  }
4122
+ /**
4123
+ * Drains pending meter telemetry as per-plane (wide) records for a surround
4124
+ * target. Use this for a surround mix target; {@link drainMeterTelemetry}
4125
+ * stays the stereo fast path. The two share one queue — call only one per
4126
+ * target. The live AudioWorklet path owns the queue via the stereo drain, so
4127
+ * this wide drain is for an offline (non-worklet) engine instance; per-plane
4128
+ * surround meters are not delivered over the live worklet meter ring.
4129
+ */
4130
+ drainMeterTelemetryWide(maxRecords = 1024) {
4131
+ return this.native.drainMeterTelemetryWide(maxRecords);
4132
+ }
4133
+ /**
4134
+ * Enables per-target spectrum + vectorscope capture. @param intervalFrames is
4135
+ * the minimum render-frame gap between snapshots (0 disables). @param bandCount
4136
+ * is the FFT band resolution (1..64); changing it re-prepares the tap. Returns
4137
+ * the band count actually applied.
4138
+ */
4139
+ configureScopeTelemetry(intervalFrames, bandCount) {
4140
+ return this.native.configureScopeTelemetry(intervalFrames, bandCount);
4141
+ }
4142
+ /** Drains pending spectrum + vectorscope snapshots (per mix target). */
4143
+ drainScopeTelemetry(maxRecords = 1024) {
4144
+ return this.native.drainScopeTelemetry(maxRecords);
4145
+ }
3820
4146
  destroy() {
3821
4147
  this.native.delete();
3822
4148
  }
@@ -4105,7 +4431,6 @@ async function bindWebMidi(engine, options = {}) {
4105
4431
  engine.setMidiInputSource(destinationId);
4106
4432
  const bound = /* @__PURE__ */ new Map();
4107
4433
  let closed = false;
4108
- let runningStatus = 0;
4109
4434
  const shouldBind = (input) => input.state !== "disconnected" && (selectedIds.size === 0 || selectedIds.has(input.id));
4110
4435
  const snapshotInputs = () => Array.from(iterInputs(access), ([id, input]) => ({
4111
4436
  id,
@@ -4118,22 +4443,26 @@ async function bindWebMidi(engine, options = {}) {
4118
4443
  if (bound.has(input.id) || !shouldBind(input)) {
4119
4444
  return;
4120
4445
  }
4121
- const listener = (event) => {
4122
- const status = dispatchMidiMessage(
4123
- engine,
4124
- event,
4125
- group,
4126
- runningStatus,
4127
- options.timestampToSamples
4128
- );
4129
- runningStatus = status;
4446
+ const entry = {
4447
+ input,
4448
+ listener: (event) => {
4449
+ entry.runningStatus = dispatchMidiMessage(
4450
+ engine,
4451
+ event,
4452
+ group,
4453
+ entry.runningStatus,
4454
+ options.timestampToSamples
4455
+ );
4456
+ },
4457
+ runningStatus: 0
4130
4458
  };
4459
+ const listener = entry.listener;
4131
4460
  if (input.addEventListener) {
4132
4461
  input.addEventListener("midimessage", listener);
4133
4462
  } else {
4134
4463
  input.onmidimessage = listener;
4135
4464
  }
4136
- bound.set(input.id, { input, listener });
4465
+ bound.set(input.id, entry);
4137
4466
  };
4138
4467
  const unbindInput = (input) => {
4139
4468
  const entry = bound.get(input.id);
@@ -4318,7 +4647,7 @@ async function init(options) {
4318
4647
  }
4319
4648
  initPromise = (async () => {
4320
4649
  try {
4321
- const createModule = (await import("./sonare.js")).default;
4650
+ const createModule = options?.moduleFactory ?? (await import("./sonare.js")).default;
4322
4651
  module = await createModule(options);
4323
4652
  setSonareModule(module);
4324
4653
  } catch (error) {
@@ -4386,6 +4715,7 @@ export {
4386
4715
  EXPECTED_PROJECT_ABI_VERSION,
4387
4716
  ErrorCode,
4388
4717
  KeyProfile,
4718
+ MarkerKind,
4389
4719
  Mixer,
4390
4720
  Mode,
4391
4721
  PitchClass as Pitch,
@@ -4479,6 +4809,7 @@ export {
4479
4809
  masteringDynamicsGate,
4480
4810
  masteringDynamicsTransientShaper,
4481
4811
  masteringInsertNames,
4812
+ masteringInsertParamInfo,
4482
4813
  masteringInsertParamNames,
4483
4814
  masteringPairAnalysisNames,
4484
4815
  masteringPairAnalyze,
@@ -4487,6 +4818,7 @@ export {
4487
4818
  masteringPresetNames,
4488
4819
  masteringProcess,
4489
4820
  masteringProcessStereo,
4821
+ masteringProcessorCatalog,
4490
4822
  masteringProcessorNames,
4491
4823
  masteringRepairDeclick,
4492
4824
  masteringRepairDeclip,
@@ -4565,6 +4897,7 @@ export {
4565
4897
  spectralBandwidth,
4566
4898
  spectralCentroid,
4567
4899
  spectralContrast,
4900
+ spectralEdit,
4568
4901
  spectralFlatness,
4569
4902
  spectralRolloff,
4570
4903
  splitSilence,