@libraz/libsonare 1.2.1 → 1.2.3
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/README.md +56 -4
- package/dist/index.d.ts +820 -296
- package/dist/index.js +770 -128
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +101 -3
- package/dist/worklet.js +706 -69
- package/dist/worklet.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +1764 -287
- package/src/public_types.ts +128 -0
- package/src/sonare.js.d.ts +580 -79
- package/src/stream_types.ts +4 -1
- package/src/worklet.ts +796 -66
- package/src/wasm_types.ts +0 -1259
package/dist/worklet.js
CHANGED
|
@@ -29,6 +29,21 @@ function panLawCode(panLaw) {
|
|
|
29
29
|
return 0;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
function panModeCode(panMode) {
|
|
33
|
+
if (typeof panMode === "number") {
|
|
34
|
+
return panMode;
|
|
35
|
+
}
|
|
36
|
+
switch (panMode) {
|
|
37
|
+
case "stereoPan":
|
|
38
|
+
case "stereo-pan":
|
|
39
|
+
return 1;
|
|
40
|
+
case "dualPan":
|
|
41
|
+
case "dual-pan":
|
|
42
|
+
return 2;
|
|
43
|
+
default:
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
32
47
|
function meterTapCode(tap) {
|
|
33
48
|
return tap === "preFader" || tap === 0 ? 0 : 1;
|
|
34
49
|
}
|
|
@@ -214,6 +229,31 @@ var RealtimeEngine = class {
|
|
|
214
229
|
process(channels) {
|
|
215
230
|
return this.native.process(channels);
|
|
216
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Allocates persistent per-channel WASM-heap scratch for the zero-copy
|
|
234
|
+
* `getChannelBuffer` / `processPrepared` realtime path. Call once (off the
|
|
235
|
+
* audio thread) before driving `processPrepared` from an AudioWorklet so the
|
|
236
|
+
* render callback never allocates on the C++/JS heap.
|
|
237
|
+
*/
|
|
238
|
+
prepareChannels(numChannels, maxFrames) {
|
|
239
|
+
this.native.prepareChannels(numChannels, maxFrames);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Returns a Float32Array view onto the persistent WASM-heap scratch for one
|
|
243
|
+
* channel (valid for up to `numFrames`). Fill it, call `processPrepared`, then
|
|
244
|
+
* read the same view back. Re-acquire after WASM memory growth.
|
|
245
|
+
*/
|
|
246
|
+
getChannelBuffer(channel, numFrames) {
|
|
247
|
+
return this.native.getChannelBuffer(channel, numFrames);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Runs the engine in place over the prepared per-channel scratch buffers.
|
|
251
|
+
* Allocation-free: safe to call on the AudioWorklet render thread after
|
|
252
|
+
* `prepareChannels`.
|
|
253
|
+
*/
|
|
254
|
+
processPrepared(numFrames) {
|
|
255
|
+
this.native.processPrepared(numFrames);
|
|
256
|
+
}
|
|
217
257
|
processWithMonitor(channels) {
|
|
218
258
|
return this.native.processWithMonitor(channels);
|
|
219
259
|
}
|
|
@@ -236,6 +276,141 @@ var RealtimeEngine = class {
|
|
|
236
276
|
this.native.delete();
|
|
237
277
|
}
|
|
238
278
|
};
|
|
279
|
+
var RealtimeVoiceChanger = class {
|
|
280
|
+
constructor(config = "neutral-monitor") {
|
|
281
|
+
if (!module) {
|
|
282
|
+
throw new Error("Module not initialized. Call init() first.");
|
|
283
|
+
}
|
|
284
|
+
this.changer = module.createRealtimeVoiceChanger(config);
|
|
285
|
+
}
|
|
286
|
+
prepare(sampleRate, maxBlockSize = 128, channels = 1) {
|
|
287
|
+
this.changer.prepare(sampleRate, maxBlockSize, channels);
|
|
288
|
+
}
|
|
289
|
+
reset() {
|
|
290
|
+
this.changer.reset();
|
|
291
|
+
}
|
|
292
|
+
setConfig(config) {
|
|
293
|
+
this.changer.setConfig(config);
|
|
294
|
+
}
|
|
295
|
+
configJson() {
|
|
296
|
+
return this.changer.configJson();
|
|
297
|
+
}
|
|
298
|
+
latencySamples() {
|
|
299
|
+
return this.changer.latencySamples();
|
|
300
|
+
}
|
|
301
|
+
processMono(samples) {
|
|
302
|
+
return this.changer.processMono(samples);
|
|
303
|
+
}
|
|
304
|
+
processMonoInto(samples, output) {
|
|
305
|
+
this.changer.processMonoInto(samples, output);
|
|
306
|
+
}
|
|
307
|
+
processInterleaved(samples, channels) {
|
|
308
|
+
return this.changer.processInterleaved(samples, channels);
|
|
309
|
+
}
|
|
310
|
+
processInterleavedInto(samples, channels, output) {
|
|
311
|
+
this.changer.processInterleavedInto(samples, channels, output);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Acquire a typed-memory view onto the WASM heap for mono input.
|
|
315
|
+
*
|
|
316
|
+
* Write your input samples into the returned `Float32Array` directly (e.g.
|
|
317
|
+
* via `input.set(source)`); no copy crosses the JS↔C++ bridge until
|
|
318
|
+
* {@link processPreparedMono} is called. The view is owned by this
|
|
319
|
+
* RealtimeVoiceChanger and becomes invalid after {@link delete}; it may
|
|
320
|
+
* also be invalidated if you later call this method with a larger
|
|
321
|
+
* `numSamples` value (the underlying buffer may be reallocated).
|
|
322
|
+
*/
|
|
323
|
+
getMonoInputBuffer(numSamples) {
|
|
324
|
+
return this.changer.getMonoInputBuffer(numSamples);
|
|
325
|
+
}
|
|
326
|
+
/** Mono output view counterpart to {@link getMonoInputBuffer}. */
|
|
327
|
+
getMonoOutputBuffer(numSamples) {
|
|
328
|
+
return this.changer.getMonoOutputBuffer(numSamples);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Process the previously-acquired mono input buffer in place. The output
|
|
332
|
+
* appears in the buffer returned by {@link getMonoOutputBuffer}. No JS↔C++
|
|
333
|
+
* sample-level crossings happen on this call — it just hands control to
|
|
334
|
+
* the underlying DSP on already-on-heap data.
|
|
335
|
+
*/
|
|
336
|
+
processPreparedMono(numSamples) {
|
|
337
|
+
this.changer.processPreparedMono(numSamples);
|
|
338
|
+
}
|
|
339
|
+
/** Interleaved input view (layout L0,R0,L1,R1,...). */
|
|
340
|
+
getInterleavedInputBuffer(numFrames, numChannels) {
|
|
341
|
+
return this.changer.getInterleavedInputBuffer(numFrames, numChannels);
|
|
342
|
+
}
|
|
343
|
+
/** Interleaved output view counterpart. */
|
|
344
|
+
getInterleavedOutputBuffer(numFrames, numChannels) {
|
|
345
|
+
return this.changer.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Process the previously-acquired interleaved buffer in place. Output
|
|
349
|
+
* appears in the buffer returned by {@link getInterleavedOutputBuffer}.
|
|
350
|
+
*/
|
|
351
|
+
processPreparedInterleaved(numFrames, numChannels) {
|
|
352
|
+
this.changer.processPreparedInterleaved(numFrames, numChannels);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Planar-channel input/output view (one Float32Array per channel). Matches
|
|
356
|
+
* AudioWorklet's native layout; processing happens in place.
|
|
357
|
+
*/
|
|
358
|
+
getPlanarChannelBuffer(channel, numFrames) {
|
|
359
|
+
return this.changer.getPlanarChannelBuffer(channel, numFrames);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Process the previously-acquired planar channel buffers in place. Each
|
|
363
|
+
* channel must have been obtained from {@link getPlanarChannelBuffer}
|
|
364
|
+
* with the same `numFrames`. Output replaces input in the same buffers.
|
|
365
|
+
*/
|
|
366
|
+
processPreparedPlanar(numFrames) {
|
|
367
|
+
this.changer.processPreparedPlanar(numFrames);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Convenience factory for the mono zero-copy path: returns the input/output
|
|
371
|
+
* heap views plus a `process()` thunk wired to the same `numSamples`. The
|
|
372
|
+
* views are reused across calls and become invalid after {@link delete}.
|
|
373
|
+
*/
|
|
374
|
+
createRealtimeMonoBuffer(numSamples) {
|
|
375
|
+
const input = this.getMonoInputBuffer(numSamples);
|
|
376
|
+
const output = this.getMonoOutputBuffer(numSamples);
|
|
377
|
+
return {
|
|
378
|
+
input,
|
|
379
|
+
output,
|
|
380
|
+
process: () => this.processPreparedMono(numSamples)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/** Same as {@link createRealtimeMonoBuffer} but for interleaved I/O. */
|
|
384
|
+
createRealtimeInterleavedBuffer(numFrames, numChannels) {
|
|
385
|
+
const input = this.getInterleavedInputBuffer(numFrames, numChannels);
|
|
386
|
+
const output = this.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
387
|
+
return {
|
|
388
|
+
input,
|
|
389
|
+
output,
|
|
390
|
+
channels: numChannels,
|
|
391
|
+
process: () => this.processPreparedInterleaved(numFrames, numChannels)
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Convenience factory for the planar zero-copy path. Acquires one
|
|
396
|
+
* heap-backed Float32Array per channel and returns a `process()` thunk
|
|
397
|
+
* wired to the same `numFrames`. Buffers are reused across calls and
|
|
398
|
+
* become invalid after {@link delete}.
|
|
399
|
+
*/
|
|
400
|
+
createRealtimePlanarBuffer(numFrames, numChannels) {
|
|
401
|
+
const channels = [];
|
|
402
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
403
|
+
channels.push(this.getPlanarChannelBuffer(ch, numFrames));
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
channels,
|
|
407
|
+
process: () => this.processPreparedPlanar(numFrames)
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
delete() {
|
|
411
|
+
this.changer.delete();
|
|
412
|
+
}
|
|
413
|
+
};
|
|
239
414
|
var Mixer = class _Mixer {
|
|
240
415
|
constructor(mixer) {
|
|
241
416
|
this.mixer = mixer;
|
|
@@ -379,6 +554,26 @@ var Mixer = class _Mixer {
|
|
|
379
554
|
vcaGroupCount() {
|
|
380
555
|
return this.mixer.vcaGroupCount();
|
|
381
556
|
}
|
|
557
|
+
/** Set the strip's input trim in dB. */
|
|
558
|
+
setInputTrimDb(stripIndex, db) {
|
|
559
|
+
this.mixer.setInputTrimDb(stripIndex, db);
|
|
560
|
+
}
|
|
561
|
+
/** Set the strip's fader level in dB. */
|
|
562
|
+
setFaderDb(stripIndex, db) {
|
|
563
|
+
this.mixer.setFaderDb(stripIndex, db);
|
|
564
|
+
}
|
|
565
|
+
/** Set the strip's pan position. */
|
|
566
|
+
setPan(stripIndex, pan, panMode = 0) {
|
|
567
|
+
this.mixer.setPan(stripIndex, pan, panModeCode(panMode));
|
|
568
|
+
}
|
|
569
|
+
/** Set the strip's stereo width. */
|
|
570
|
+
setWidth(stripIndex, width) {
|
|
571
|
+
this.mixer.setWidth(stripIndex, width);
|
|
572
|
+
}
|
|
573
|
+
/** Set the strip's mute state. */
|
|
574
|
+
setMuted(stripIndex, muted) {
|
|
575
|
+
this.mixer.setMuted(stripIndex, muted);
|
|
576
|
+
}
|
|
382
577
|
/**
|
|
383
578
|
* Set a strip's solo state. Takes effect on the next process without a
|
|
384
579
|
* graph recompile.
|
|
@@ -526,8 +721,20 @@ var Mixer = class _Mixer {
|
|
|
526
721
|
|
|
527
722
|
// src/worklet.ts
|
|
528
723
|
var SONARE_METER_RING_HEADER_INTS = 4;
|
|
529
|
-
var SONARE_METER_RING_RECORD_FLOATS =
|
|
724
|
+
var SONARE_METER_RING_RECORD_FLOATS = 7;
|
|
530
725
|
var SONARE_SPECTRUM_RING_HEADER_INTS = 5;
|
|
726
|
+
var SONARE_FRAME_LANE_BASE = 16777216;
|
|
727
|
+
function encodeFrameLo(frame) {
|
|
728
|
+
const f = Math.max(0, Math.floor(frame));
|
|
729
|
+
return f % SONARE_FRAME_LANE_BASE;
|
|
730
|
+
}
|
|
731
|
+
function encodeFrameHi(frame) {
|
|
732
|
+
const f = Math.max(0, Math.floor(frame));
|
|
733
|
+
return Math.floor(f / SONARE_FRAME_LANE_BASE);
|
|
734
|
+
}
|
|
735
|
+
function decodeFrame(lo, hi) {
|
|
736
|
+
return hi * SONARE_FRAME_LANE_BASE + lo;
|
|
737
|
+
}
|
|
531
738
|
var SONARE_ENGINE_RING_HEADER_INTS = 5;
|
|
532
739
|
var SONARE_ENGINE_COMMAND_RECORD_BYTES = 32;
|
|
533
740
|
var SONARE_ENGINE_TELEMETRY_RECORD_BYTES = 48;
|
|
@@ -574,6 +781,18 @@ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2)
|
|
|
574
781
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["SmoothedParameterCapacity"] = 13] = "SmoothedParameterCapacity";
|
|
575
782
|
return SonareEngineTelemetryError2;
|
|
576
783
|
})(SonareEngineTelemetryError || {});
|
|
784
|
+
var DEFAULT_METRONOME_CONFIG = {
|
|
785
|
+
beatGain: 0.35,
|
|
786
|
+
accentGain: 0.7,
|
|
787
|
+
clickSamples: 96
|
|
788
|
+
};
|
|
789
|
+
function resolveMetronomeConfig(config) {
|
|
790
|
+
return {
|
|
791
|
+
beatGain: config.beatGain ?? DEFAULT_METRONOME_CONFIG.beatGain,
|
|
792
|
+
accentGain: config.accentGain ?? DEFAULT_METRONOME_CONFIG.accentGain,
|
|
793
|
+
clickSamples: config.clickSamples ?? DEFAULT_METRONOME_CONFIG.clickSamples
|
|
794
|
+
};
|
|
795
|
+
}
|
|
577
796
|
function toDb(value) {
|
|
578
797
|
return value > 0 ? 20 * Math.log10(value) : Number.NEGATIVE_INFINITY;
|
|
579
798
|
}
|
|
@@ -589,6 +808,18 @@ function isWorkletMessage(value) {
|
|
|
589
808
|
function isEngineCommandRecord(value) {
|
|
590
809
|
return isRecord(value) && typeof value.type === "number";
|
|
591
810
|
}
|
|
811
|
+
function isEngineSyncMessage(value) {
|
|
812
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
return value.type === "syncClips" || value.type === "syncMarkers" || value.type === "syncMetronome" || value.type === "syncAutomation";
|
|
816
|
+
}
|
|
817
|
+
function isRealtimeVoiceChangerMessage(value) {
|
|
818
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
return value.type === "setConfig" || value.type === "reset" || value.type === "destroy";
|
|
822
|
+
}
|
|
592
823
|
function isEngineTelemetryRecord(value) {
|
|
593
824
|
return isRecord(value) && typeof value.type === "number" && typeof value.error === "number" && typeof value.renderFrame === "number" && typeof value.timelineSample === "number" && typeof value.audibleTimelineSample === "number" && typeof value.graphLatencySamplesQ8 === "number" && typeof value.value === "number";
|
|
594
825
|
}
|
|
@@ -618,7 +849,7 @@ function readSonareMeterRingBuffer(ring, readIndex = 0) {
|
|
|
618
849
|
const offset = index % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
619
850
|
meters.push({
|
|
620
851
|
type: "meter",
|
|
621
|
-
frame: ring.records[offset],
|
|
852
|
+
frame: decodeFrame(ring.records[offset], ring.records[offset + 6]),
|
|
622
853
|
peakDbL: ring.records[offset + 1],
|
|
623
854
|
peakDbR: ring.records[offset + 2],
|
|
624
855
|
rmsDbL: ring.records[offset + 3],
|
|
@@ -631,7 +862,7 @@ function readSonareMeterRingBuffer(ring, readIndex = 0) {
|
|
|
631
862
|
function sonareSpectrumRingBufferByteLength(capacity, bands = 16) {
|
|
632
863
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
633
864
|
const clampedBands = Math.max(1, Math.floor(bands));
|
|
634
|
-
return SONARE_SPECTRUM_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * (
|
|
865
|
+
return SONARE_SPECTRUM_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * (3 + clampedBands) * Float32Array.BYTES_PER_ELEMENT;
|
|
635
866
|
}
|
|
636
867
|
function createSonareSpectrumRingBuffer(capacity = 128, bands = 16) {
|
|
637
868
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
@@ -655,7 +886,7 @@ function createSonareSpectrumRingBuffer(capacity = 128, bands = 16) {
|
|
|
655
886
|
}
|
|
656
887
|
function readSonareSpectrumRingBuffer(ring, readIndex = 0) {
|
|
657
888
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
658
|
-
const recordFloats = Atomics.load(ring.header, 2) ||
|
|
889
|
+
const recordFloats = Atomics.load(ring.header, 2) || 3 + ring.bands;
|
|
659
890
|
const bands = Atomics.load(ring.header, 3) || ring.bands;
|
|
660
891
|
const nextReadIndex = Math.max(0, Math.min(readIndex, writeIndex));
|
|
661
892
|
const firstReadable = Math.max(nextReadIndex, writeIndex - ring.capacity);
|
|
@@ -663,8 +894,12 @@ function readSonareSpectrumRingBuffer(ring, readIndex = 0) {
|
|
|
663
894
|
for (let index = firstReadable; index < writeIndex; index++) {
|
|
664
895
|
const offset = index % ring.capacity * recordFloats;
|
|
665
896
|
const values = new Float32Array(bands);
|
|
666
|
-
values.set(ring.records.subarray(offset +
|
|
667
|
-
spectra.push({
|
|
897
|
+
values.set(ring.records.subarray(offset + 3, offset + 3 + bands));
|
|
898
|
+
spectra.push({
|
|
899
|
+
type: "spectrum",
|
|
900
|
+
frame: decodeFrame(ring.records[offset], ring.records[offset + 1]),
|
|
901
|
+
bands: values
|
|
902
|
+
});
|
|
668
903
|
}
|
|
669
904
|
return { nextReadIndex: writeIndex, spectra };
|
|
670
905
|
}
|
|
@@ -783,7 +1018,7 @@ function spectrumRingFromSharedBuffer(sharedBuffer, fallbackCapacity, fallbackBa
|
|
|
783
1018
|
const existingBands = Atomics.load(header, 3);
|
|
784
1019
|
const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
|
|
785
1020
|
const bands = Math.max(1, Math.floor(existingBands || fallbackBands || 16));
|
|
786
|
-
const recordFloats =
|
|
1021
|
+
const recordFloats = 3 + bands;
|
|
787
1022
|
const minBytes = sonareSpectrumRingBufferByteLength(capacity, bands);
|
|
788
1023
|
if (sharedBuffer.byteLength < minBytes) {
|
|
789
1024
|
throw new Error("spectrumSharedBuffer is too small for the requested ring capacity.");
|
|
@@ -832,8 +1067,7 @@ function writeEngineCommandRecord(view, offset, command) {
|
|
|
832
1067
|
view.setUint32(offset, command.type, true);
|
|
833
1068
|
view.setUint32(offset + 4, command.targetId ?? 0, true);
|
|
834
1069
|
view.setBigInt64(offset + 8, toBigInt64(command.sampleTime, -1n), true);
|
|
835
|
-
view.
|
|
836
|
-
view.setUint32(offset + 20, 0, true);
|
|
1070
|
+
view.setFloat64(offset + 16, command.argFloat ?? 0, true);
|
|
837
1071
|
view.setBigInt64(offset + 24, toBigInt64(command.argInt, 0n), true);
|
|
838
1072
|
}
|
|
839
1073
|
function readEngineCommandRecord(view, offset) {
|
|
@@ -841,7 +1075,7 @@ function readEngineCommandRecord(view, offset) {
|
|
|
841
1075
|
type: view.getUint32(offset, true),
|
|
842
1076
|
targetId: view.getUint32(offset + 4, true),
|
|
843
1077
|
sampleTime: Number(view.getBigInt64(offset + 8, true)),
|
|
844
|
-
argFloat: view.
|
|
1078
|
+
argFloat: view.getFloat64(offset + 16, true),
|
|
845
1079
|
argInt: Number(view.getBigInt64(offset + 24, true))
|
|
846
1080
|
};
|
|
847
1081
|
}
|
|
@@ -934,35 +1168,48 @@ var SonareWorkletProcessor = class {
|
|
|
934
1168
|
return true;
|
|
935
1169
|
}
|
|
936
1170
|
const frames = leftOut.length;
|
|
937
|
-
|
|
938
|
-
return false;
|
|
939
|
-
}
|
|
1171
|
+
const usable = Math.min(frames, this.blockSize);
|
|
940
1172
|
for (let strip = 0; strip < this.realtime.leftInputs.length; strip++) {
|
|
941
1173
|
const input = inputs[strip];
|
|
942
1174
|
const left = input?.[0];
|
|
943
1175
|
const right = input?.[1];
|
|
944
1176
|
const leftTarget = this.realtime.leftInputs[strip];
|
|
945
1177
|
const rightTarget = this.realtime.rightInputs[strip];
|
|
946
|
-
if (left && left.length
|
|
947
|
-
leftTarget.set(left);
|
|
948
|
-
if (right && right.length
|
|
949
|
-
rightTarget.set(right);
|
|
1178
|
+
if (left && left.length >= usable) {
|
|
1179
|
+
leftTarget.set(left.subarray(0, usable));
|
|
1180
|
+
if (right && right.length >= usable) {
|
|
1181
|
+
rightTarget.set(right.subarray(0, usable));
|
|
950
1182
|
} else {
|
|
951
|
-
rightTarget.set(left);
|
|
1183
|
+
rightTarget.set(left.subarray(0, usable));
|
|
952
1184
|
}
|
|
953
1185
|
} else {
|
|
954
1186
|
leftTarget.fill(0);
|
|
955
1187
|
rightTarget.fill(0);
|
|
956
1188
|
}
|
|
957
1189
|
}
|
|
958
|
-
this.realtime.process(
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
rightOut
|
|
1190
|
+
this.realtime.process(usable);
|
|
1191
|
+
if (usable === frames) {
|
|
1192
|
+
leftOut.set(this.realtime.outLeft.subarray(0, usable));
|
|
1193
|
+
if (rightOut) {
|
|
1194
|
+
rightOut.set(this.realtime.outRight.subarray(0, usable));
|
|
1195
|
+
}
|
|
1196
|
+
} else {
|
|
1197
|
+
leftOut.fill(0);
|
|
1198
|
+
leftOut.set(this.realtime.outLeft.subarray(0, usable));
|
|
1199
|
+
if (rightOut) {
|
|
1200
|
+
rightOut.fill(0);
|
|
1201
|
+
rightOut.set(this.realtime.outRight.subarray(0, usable));
|
|
1202
|
+
}
|
|
962
1203
|
}
|
|
963
|
-
this.processedFrames +=
|
|
964
|
-
this.publishMeter(
|
|
965
|
-
|
|
1204
|
+
this.processedFrames += usable;
|
|
1205
|
+
this.publishMeter(
|
|
1206
|
+
this.realtime.outLeft.subarray(0, usable),
|
|
1207
|
+
this.realtime.outRight.subarray(0, usable)
|
|
1208
|
+
);
|
|
1209
|
+
this.publishSpectrum(
|
|
1210
|
+
this.realtime.outLeft.subarray(0, usable),
|
|
1211
|
+
this.realtime.outRight.subarray(0, usable)
|
|
1212
|
+
);
|
|
966
1213
|
return true;
|
|
967
1214
|
}
|
|
968
1215
|
receiveMessage(message) {
|
|
@@ -1048,16 +1295,14 @@ var SonareWorkletProcessor = class {
|
|
|
1048
1295
|
}
|
|
1049
1296
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
1050
1297
|
const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
1051
|
-
ring.records[offset] = meter.frame;
|
|
1298
|
+
ring.records[offset] = encodeFrameLo(meter.frame);
|
|
1052
1299
|
ring.records[offset + 1] = meter.peakDbL;
|
|
1053
1300
|
ring.records[offset + 2] = meter.peakDbR;
|
|
1054
1301
|
ring.records[offset + 3] = meter.rmsDbL;
|
|
1055
1302
|
ring.records[offset + 4] = meter.rmsDbR;
|
|
1056
1303
|
ring.records[offset + 5] = meter.correlation;
|
|
1304
|
+
ring.records[offset + 6] = encodeFrameHi(meter.frame);
|
|
1057
1305
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
1058
|
-
if (writeIndex + 1 > ring.capacity) {
|
|
1059
|
-
Atomics.store(ring.header, 3, writeIndex + 1 - ring.capacity);
|
|
1060
|
-
}
|
|
1061
1306
|
}
|
|
1062
1307
|
publishSpectrum(left, right) {
|
|
1063
1308
|
if (this.spectrumIntervalFrames <= 0) {
|
|
@@ -1082,7 +1327,12 @@ var SonareWorkletProcessor = class {
|
|
|
1082
1327
|
}
|
|
1083
1328
|
computeSpectrum(left, right) {
|
|
1084
1329
|
const n = Math.max(1, Math.min(left.length, right.length));
|
|
1330
|
+
const maxBand = Math.floor(n / 2);
|
|
1085
1331
|
for (let band = 0; band < this.spectrumBands.length; band++) {
|
|
1332
|
+
if (band >= maxBand) {
|
|
1333
|
+
this.spectrumBands[band] = magnitudeToDb(0);
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1086
1336
|
const bin = band + 1;
|
|
1087
1337
|
let real = 0;
|
|
1088
1338
|
let imag = 0;
|
|
@@ -1102,19 +1352,20 @@ var SonareWorkletProcessor = class {
|
|
|
1102
1352
|
}
|
|
1103
1353
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
1104
1354
|
const offset = writeIndex % ring.capacity * ring.recordFloats;
|
|
1105
|
-
ring.records[offset] = frame;
|
|
1106
|
-
ring.records[offset + 1] =
|
|
1107
|
-
ring.records
|
|
1355
|
+
ring.records[offset] = encodeFrameLo(frame);
|
|
1356
|
+
ring.records[offset + 1] = encodeFrameHi(frame);
|
|
1357
|
+
ring.records[offset + 2] = bands.length;
|
|
1358
|
+
ring.records.set(bands.subarray(0, ring.bands), offset + 3);
|
|
1108
1359
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
1109
|
-
if (writeIndex + 1 > ring.capacity) {
|
|
1110
|
-
Atomics.store(ring.header, 4, writeIndex + 1 - ring.capacity);
|
|
1111
|
-
}
|
|
1112
1360
|
}
|
|
1113
1361
|
};
|
|
1114
|
-
var
|
|
1362
|
+
var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletProcessor {
|
|
1115
1363
|
constructor(options = {}, transport) {
|
|
1116
1364
|
this.closed = false;
|
|
1117
1365
|
this.lastMeterFrame = Number.NEGATIVE_INFINITY;
|
|
1366
|
+
// Latest metronome gains/click length pushed via 'syncMetronome'. The
|
|
1367
|
+
// SetMetronome command only toggles enabled state; the config arrives here.
|
|
1368
|
+
this.metronomeConfig = { ...DEFAULT_METRONOME_CONFIG };
|
|
1118
1369
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
1119
1370
|
this.blockSize = options.blockSize ?? 128;
|
|
1120
1371
|
this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
@@ -1131,7 +1382,13 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1131
1382
|
options.telemetrySharedBuffer,
|
|
1132
1383
|
options.telemetryRingCapacity
|
|
1133
1384
|
) : void 0;
|
|
1385
|
+
this.meterRing = options.meterSharedBuffer ? meterRingFromSharedBuffer(options.meterSharedBuffer, options.meterRingCapacity) : void 0;
|
|
1134
1386
|
this.engine = new RealtimeEngine(this.sampleRate, this.blockSize);
|
|
1387
|
+
this.engine.prepareChannels(this.channelCount, this.blockSize);
|
|
1388
|
+
this.channelBuffers = new Array(this.channelCount);
|
|
1389
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1390
|
+
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
1391
|
+
}
|
|
1135
1392
|
}
|
|
1136
1393
|
process(inputs, outputs) {
|
|
1137
1394
|
if (this.closed) {
|
|
@@ -1151,22 +1408,38 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1151
1408
|
return true;
|
|
1152
1409
|
}
|
|
1153
1410
|
this.drainCommands();
|
|
1154
|
-
|
|
1411
|
+
let usableFrames = frames;
|
|
1412
|
+
if (usableFrames > this.blockSize) {
|
|
1413
|
+
if (!_SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow) {
|
|
1414
|
+
_SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow = true;
|
|
1415
|
+
console.warn(
|
|
1416
|
+
`SonareRealtimeEngineWorkletProcessor: requested ${usableFrames} frames exceeds pre-allocated capacity ${this.blockSize}; clamping.`
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
usableFrames = this.blockSize;
|
|
1420
|
+
}
|
|
1421
|
+
if ((this.channelBuffers[0]?.byteLength ?? 0) === 0) {
|
|
1422
|
+
this.reacquireChannelBuffers();
|
|
1423
|
+
}
|
|
1155
1424
|
const input = inputs[0];
|
|
1156
1425
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1426
|
+
const dst = this.channelBuffers[ch];
|
|
1157
1427
|
const source = input?.[ch];
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1428
|
+
if (source && source.length === usableFrames) {
|
|
1429
|
+
dst.set(source.subarray(0, usableFrames));
|
|
1430
|
+
} else {
|
|
1431
|
+
dst.fill(0, 0, usableFrames);
|
|
1161
1432
|
}
|
|
1162
|
-
channels.push(channel);
|
|
1163
1433
|
}
|
|
1164
|
-
|
|
1434
|
+
this.engine.processPrepared(usableFrames);
|
|
1165
1435
|
for (let ch = 0; ch < output.length; ch++) {
|
|
1166
1436
|
const target = output[ch];
|
|
1167
|
-
const source =
|
|
1437
|
+
const source = this.channelBuffers[ch] ?? this.channelBuffers[0];
|
|
1168
1438
|
if (source) {
|
|
1169
|
-
target.set(source.subarray(0, target.length));
|
|
1439
|
+
target.set(source.subarray(0, Math.min(target.length, usableFrames)));
|
|
1440
|
+
if (target.length > usableFrames) {
|
|
1441
|
+
target.fill(0, usableFrames);
|
|
1442
|
+
}
|
|
1170
1443
|
} else {
|
|
1171
1444
|
target.fill(0);
|
|
1172
1445
|
}
|
|
@@ -1175,11 +1448,41 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1175
1448
|
this.publishMeters();
|
|
1176
1449
|
return true;
|
|
1177
1450
|
}
|
|
1451
|
+
reacquireChannelBuffers() {
|
|
1452
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1453
|
+
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1178
1456
|
receiveCommand(command) {
|
|
1179
1457
|
if (!this.closed) {
|
|
1180
1458
|
this.applyCommand(command);
|
|
1181
1459
|
}
|
|
1182
1460
|
}
|
|
1461
|
+
// Applies an out-of-band control-plane sync message. Runs on the AudioWorklet
|
|
1462
|
+
// global scope but OUTSIDE process() (the message-port callback), so the
|
|
1463
|
+
// bulk/allocating engine setters (setClips/setMarkers) are safe here — they
|
|
1464
|
+
// never run on the realtime render path. This is the audio-thread equivalent
|
|
1465
|
+
// of the engine's control-thread RtPublisher setters.
|
|
1466
|
+
receiveSync(message) {
|
|
1467
|
+
if (this.closed) {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
switch (message.type) {
|
|
1471
|
+
case "syncClips":
|
|
1472
|
+
this.engine.setClips(message.clips);
|
|
1473
|
+
break;
|
|
1474
|
+
case "syncMarkers":
|
|
1475
|
+
this.engine.setMarkers(message.markers);
|
|
1476
|
+
break;
|
|
1477
|
+
case "syncMetronome":
|
|
1478
|
+
this.metronomeConfig = resolveMetronomeConfig(message.config);
|
|
1479
|
+
this.engine.setMetronome(message.config);
|
|
1480
|
+
break;
|
|
1481
|
+
case "syncAutomation":
|
|
1482
|
+
this.engine.setAutomationLane(message.paramId, message.points);
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1183
1486
|
destroy() {
|
|
1184
1487
|
if (!this.closed) {
|
|
1185
1488
|
this.engine.destroy();
|
|
@@ -1201,6 +1504,20 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1201
1504
|
applyCommand(command) {
|
|
1202
1505
|
const sampleTime = Number(command.sampleTime ?? -1);
|
|
1203
1506
|
switch (command.type) {
|
|
1507
|
+
case 0 /* SetParam */:
|
|
1508
|
+
this.engine.setParameter(
|
|
1509
|
+
Math.trunc(Number(command.targetId ?? 0)),
|
|
1510
|
+
Number(command.argFloat ?? 0),
|
|
1511
|
+
sampleTime
|
|
1512
|
+
);
|
|
1513
|
+
break;
|
|
1514
|
+
case 1 /* SetParamSmoothed */:
|
|
1515
|
+
this.engine.setParameterSmoothed(
|
|
1516
|
+
Math.trunc(Number(command.targetId ?? 0)),
|
|
1517
|
+
Number(command.argFloat ?? 0),
|
|
1518
|
+
sampleTime
|
|
1519
|
+
);
|
|
1520
|
+
break;
|
|
1204
1521
|
case 2 /* TransportPlay */:
|
|
1205
1522
|
this.engine.play(sampleTime);
|
|
1206
1523
|
break;
|
|
@@ -1229,18 +1546,21 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1229
1546
|
case 14 /* Punch */:
|
|
1230
1547
|
this.engine.setCapturePunch(
|
|
1231
1548
|
Number(command.argInt ?? 0),
|
|
1232
|
-
Math.max(0, Math.round(Number(command.argFloat ?? 0)
|
|
1549
|
+
Math.max(0, Math.round(Number(command.argFloat ?? 0))),
|
|
1233
1550
|
true
|
|
1234
1551
|
);
|
|
1235
1552
|
break;
|
|
1236
1553
|
case 15 /* SetMetronome */:
|
|
1237
1554
|
this.engine.setMetronome({
|
|
1238
1555
|
enabled: Boolean(command.argInt),
|
|
1239
|
-
beatGain:
|
|
1240
|
-
accentGain:
|
|
1241
|
-
clickSamples:
|
|
1556
|
+
beatGain: this.metronomeConfig.beatGain,
|
|
1557
|
+
accentGain: this.metronomeConfig.accentGain,
|
|
1558
|
+
clickSamples: this.metronomeConfig.clickSamples
|
|
1242
1559
|
});
|
|
1243
1560
|
break;
|
|
1561
|
+
case 17 /* SeekMarker */:
|
|
1562
|
+
this.engine.seekMarker(Math.trunc(Number(command.targetId ?? 0)), sampleTime);
|
|
1563
|
+
break;
|
|
1244
1564
|
default:
|
|
1245
1565
|
this.publishTelemetryRecord({
|
|
1246
1566
|
type: 1 /* Error */,
|
|
@@ -1267,7 +1587,7 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1267
1587
|
this.transport?.postMessage?.(record);
|
|
1268
1588
|
}
|
|
1269
1589
|
publishMeters() {
|
|
1270
|
-
if (
|
|
1590
|
+
if (this.meterIntervalFrames <= 0 || !this.transport && !this.meterRing) {
|
|
1271
1591
|
return;
|
|
1272
1592
|
}
|
|
1273
1593
|
for (const item of this.engine.drainMeterTelemetry(64)) {
|
|
@@ -1276,10 +1596,30 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1276
1596
|
continue;
|
|
1277
1597
|
}
|
|
1278
1598
|
this.lastMeterFrame = meter.frame;
|
|
1279
|
-
this.
|
|
1280
|
-
|
|
1599
|
+
if (this.meterRing) {
|
|
1600
|
+
this.writeMeterRing(meter);
|
|
1601
|
+
} else {
|
|
1602
|
+
this.transport?.onMeter?.(meter);
|
|
1603
|
+
this.transport?.postMessage?.(meter);
|
|
1604
|
+
}
|
|
1281
1605
|
}
|
|
1282
1606
|
}
|
|
1607
|
+
writeMeterRing(meter) {
|
|
1608
|
+
const ring = this.meterRing;
|
|
1609
|
+
if (!ring) {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
1613
|
+
const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
1614
|
+
ring.records[offset] = encodeFrameLo(meter.frame);
|
|
1615
|
+
ring.records[offset + 1] = meter.peakDbL;
|
|
1616
|
+
ring.records[offset + 2] = meter.peakDbR;
|
|
1617
|
+
ring.records[offset + 3] = meter.rmsDbL;
|
|
1618
|
+
ring.records[offset + 4] = meter.rmsDbR;
|
|
1619
|
+
ring.records[offset + 5] = meter.correlation;
|
|
1620
|
+
ring.records[offset + 6] = encodeFrameHi(meter.frame);
|
|
1621
|
+
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
1622
|
+
}
|
|
1283
1623
|
commandRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
1284
1624
|
const ring = engineRingFromSharedBuffer(
|
|
1285
1625
|
sharedBuffer,
|
|
@@ -1297,8 +1637,11 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1297
1637
|
return { sharedBuffer, header: ring.header, view: ring.view, capacity: ring.capacity };
|
|
1298
1638
|
}
|
|
1299
1639
|
};
|
|
1640
|
+
_SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow = false;
|
|
1641
|
+
var SonareRealtimeEngineWorkletProcessor = _SonareRealtimeEngineWorkletProcessor;
|
|
1300
1642
|
var SonareRtRealtimeEngineRuntime = class {
|
|
1301
1643
|
constructor(options) {
|
|
1644
|
+
this.metronomeConfig = { ...DEFAULT_METRONOME_CONFIG };
|
|
1302
1645
|
this.closed = false;
|
|
1303
1646
|
this.module = options.module;
|
|
1304
1647
|
this.memory = options.memory;
|
|
@@ -1380,6 +1723,49 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1380
1723
|
this.publishTelemetry();
|
|
1381
1724
|
return true;
|
|
1382
1725
|
}
|
|
1726
|
+
receiveCommand(command) {
|
|
1727
|
+
if (!this.closed) {
|
|
1728
|
+
this.applyCommand(command);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
// Out-of-band control sync for the sonare-rt runtime. The sonare-rt C ABI
|
|
1732
|
+
// (src/wasm/rt_bindings.cpp) exposes set_metronome_enabled and seek_marker but
|
|
1733
|
+
// NOT set_clips / set_markers, so clip/marker mutations cannot be applied to a
|
|
1734
|
+
// live sonare-rt engine. We honor the metronome config and surface a clear
|
|
1735
|
+
// telemetry error for the unsupported clip/marker paths instead of silently
|
|
1736
|
+
// dropping them. The default 'embind' runtime wires all three fully.
|
|
1737
|
+
receiveSync(message) {
|
|
1738
|
+
if (this.closed) {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
switch (message.type) {
|
|
1742
|
+
case "syncMetronome":
|
|
1743
|
+
this.metronomeConfig = resolveMetronomeConfig(message.config);
|
|
1744
|
+
this.module._sonare_rt_engine_set_metronome_enabled(
|
|
1745
|
+
this.engine,
|
|
1746
|
+
message.config.enabled ? 1 : 0,
|
|
1747
|
+
this.metronomeConfig.beatGain,
|
|
1748
|
+
this.metronomeConfig.accentGain,
|
|
1749
|
+
this.metronomeConfig.clickSamples
|
|
1750
|
+
);
|
|
1751
|
+
break;
|
|
1752
|
+
case "syncClips":
|
|
1753
|
+
case "syncMarkers":
|
|
1754
|
+
case "syncAutomation":
|
|
1755
|
+
if (this.telemetryRing) {
|
|
1756
|
+
writeSonareEngineTelemetryRingBuffer(this.telemetryRing, {
|
|
1757
|
+
type: 1 /* Error */,
|
|
1758
|
+
error: 7 /* UnknownTarget */,
|
|
1759
|
+
renderFrame: 0,
|
|
1760
|
+
timelineSample: 0,
|
|
1761
|
+
audibleTimelineSample: 0,
|
|
1762
|
+
graphLatencySamplesQ8: 0,
|
|
1763
|
+
value: 0
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
break;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1383
1769
|
destroy() {
|
|
1384
1770
|
if (this.closed) {
|
|
1385
1771
|
return;
|
|
@@ -1415,6 +1801,20 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1415
1801
|
applyCommand(command) {
|
|
1416
1802
|
const sampleTime = toBigInt64(command.sampleTime, -1n);
|
|
1417
1803
|
switch (command.type) {
|
|
1804
|
+
case 0 /* SetParam */:
|
|
1805
|
+
case 1 /* SetParamSmoothed */:
|
|
1806
|
+
if (this.telemetryRing) {
|
|
1807
|
+
writeSonareEngineTelemetryRingBuffer(this.telemetryRing, {
|
|
1808
|
+
type: 1 /* Error */,
|
|
1809
|
+
error: 7 /* UnknownTarget */,
|
|
1810
|
+
renderFrame: 0,
|
|
1811
|
+
timelineSample: 0,
|
|
1812
|
+
audibleTimelineSample: 0,
|
|
1813
|
+
graphLatencySamplesQ8: 0,
|
|
1814
|
+
value: Number(command.type)
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
break;
|
|
1418
1818
|
case 2 /* TransportPlay */:
|
|
1419
1819
|
this.module._sonare_rt_engine_play(this.engine, sampleTime);
|
|
1420
1820
|
break;
|
|
@@ -1453,7 +1853,7 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1453
1853
|
this.module._sonare_rt_engine_set_capture_punch(
|
|
1454
1854
|
this.engine,
|
|
1455
1855
|
toBigInt64(command.argInt, 0n),
|
|
1456
|
-
BigInt(Math.
|
|
1856
|
+
BigInt(Math.max(0, Math.round(Number(command.argFloat ?? 0)))),
|
|
1457
1857
|
1
|
|
1458
1858
|
);
|
|
1459
1859
|
break;
|
|
@@ -1461,9 +1861,9 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1461
1861
|
this.module._sonare_rt_engine_set_metronome_enabled(
|
|
1462
1862
|
this.engine,
|
|
1463
1863
|
command.argInt ? 1 : 0,
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1864
|
+
this.metronomeConfig.beatGain,
|
|
1865
|
+
this.metronomeConfig.accentGain,
|
|
1866
|
+
this.metronomeConfig.clickSamples
|
|
1467
1867
|
);
|
|
1468
1868
|
break;
|
|
1469
1869
|
case 17 /* SeekMarker */:
|
|
@@ -1538,8 +1938,9 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1538
1938
|
}
|
|
1539
1939
|
};
|
|
1540
1940
|
var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
1541
|
-
constructor(node, capabilities, commandRing, telemetryRing) {
|
|
1941
|
+
constructor(node, capabilities, commandRing, telemetryRing, meterRing) {
|
|
1542
1942
|
this.telemetryReadIndex = 0;
|
|
1943
|
+
this.meterReadIndex = 0;
|
|
1543
1944
|
this.telemetryListeners = /* @__PURE__ */ new Set();
|
|
1544
1945
|
this.meterListeners = /* @__PURE__ */ new Set();
|
|
1545
1946
|
this.destroyed = false;
|
|
@@ -1547,6 +1948,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1547
1948
|
this.capabilities = capabilities;
|
|
1548
1949
|
this.commandRing = commandRing;
|
|
1549
1950
|
this.telemetryRing = telemetryRing;
|
|
1951
|
+
this.meterRing = meterRing;
|
|
1550
1952
|
this.ready = new Promise((resolve, reject) => {
|
|
1551
1953
|
this.resolveReady = resolve;
|
|
1552
1954
|
this.rejectReady = reject;
|
|
@@ -1595,6 +1997,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1595
1997
|
}
|
|
1596
1998
|
const commandRing = mode === "sab" ? createSonareEngineCommandRingBuffer(options.commandRingCapacity ?? 128) : void 0;
|
|
1597
1999
|
const telemetryRing = mode === "sab" ? createSonareEngineTelemetryRingBuffer(options.telemetryRingCapacity ?? 128) : void 0;
|
|
2000
|
+
const meterRing = mode === "sab" && runtimeTarget === "embind" ? createSonareMeterRingBuffer(options.meterRingCapacity ?? 128) : void 0;
|
|
1598
2001
|
const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
1599
2002
|
const processorOptions = {
|
|
1600
2003
|
runtimeTarget,
|
|
@@ -1606,7 +2009,9 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1606
2009
|
commandSharedBuffer: commandRing?.sharedBuffer,
|
|
1607
2010
|
commandRingCapacity: commandRing?.capacity,
|
|
1608
2011
|
telemetrySharedBuffer: telemetryRing?.sharedBuffer,
|
|
1609
|
-
telemetryRingCapacity: telemetryRing?.capacity
|
|
2012
|
+
telemetryRingCapacity: telemetryRing?.capacity,
|
|
2013
|
+
meterSharedBuffer: meterRing?.sharedBuffer,
|
|
2014
|
+
meterRingCapacity: meterRing?.capacity
|
|
1610
2015
|
};
|
|
1611
2016
|
const factory = options.nodeFactory ?? ((ctx, name, nodeOptions) => new AudioWorkletNode(ctx, name, nodeOptions));
|
|
1612
2017
|
const node = factory(context, processorName, {
|
|
@@ -1629,7 +2034,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1629
2034
|
degradedReason
|
|
1630
2035
|
},
|
|
1631
2036
|
commandRing,
|
|
1632
|
-
telemetryRing
|
|
2037
|
+
telemetryRing,
|
|
2038
|
+
meterRing
|
|
1633
2039
|
);
|
|
1634
2040
|
}
|
|
1635
2041
|
play(sampleTime = -1) {
|
|
@@ -1673,6 +2079,20 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1673
2079
|
}
|
|
1674
2080
|
return read.telemetry;
|
|
1675
2081
|
}
|
|
2082
|
+
// Drains any meters published into the SAB meter ring (embind SAB mode) and
|
|
2083
|
+
// forwards them to onMeter listeners. In postMessage mode meters arrive via
|
|
2084
|
+
// node.port.onmessage instead, so this is a no-op then.
|
|
2085
|
+
pollMeters() {
|
|
2086
|
+
if (!this.meterRing) {
|
|
2087
|
+
return [];
|
|
2088
|
+
}
|
|
2089
|
+
const read = readSonareMeterRingBuffer(this.meterRing, this.meterReadIndex);
|
|
2090
|
+
this.meterReadIndex = read.nextReadIndex;
|
|
2091
|
+
for (const meter of read.meters) {
|
|
2092
|
+
this.emitMeter(meter);
|
|
2093
|
+
}
|
|
2094
|
+
return read.meters;
|
|
2095
|
+
}
|
|
1676
2096
|
onTelemetry(callback) {
|
|
1677
2097
|
this.telemetryListeners.add(callback);
|
|
1678
2098
|
return () => {
|
|
@@ -1787,10 +2207,14 @@ var SonareEngine = class _SonareEngine {
|
|
|
1787
2207
|
});
|
|
1788
2208
|
}
|
|
1789
2209
|
setParam(nodeId, param, value) {
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
2210
|
+
const paramId = this.resolveParamId(nodeId, param);
|
|
2211
|
+
this.offlineEngine.setParameter(paramId, value);
|
|
2212
|
+
return this.realtimeNode.sendCommand({
|
|
2213
|
+
type: 0 /* SetParam */,
|
|
2214
|
+
targetId: paramId,
|
|
2215
|
+
sampleTime: -1,
|
|
2216
|
+
argFloat: value
|
|
2217
|
+
});
|
|
1794
2218
|
}
|
|
1795
2219
|
scheduleParam(nodeId, param, ppq, value, curve = "linear") {
|
|
1796
2220
|
const paramId = this.resolveParamId(nodeId, param);
|
|
@@ -1799,6 +2223,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
1799
2223
|
lane.sort((a, b) => a.ppq - b.ppq);
|
|
1800
2224
|
this.automationLanes.set(paramId, lane);
|
|
1801
2225
|
this.offlineEngine.setAutomationLane(paramId, lane);
|
|
2226
|
+
this.postSync({ type: "syncAutomation", paramId, points: lane });
|
|
1802
2227
|
}
|
|
1803
2228
|
addAutomationPoint(laneId, ppq, value, curve = "linear") {
|
|
1804
2229
|
this.scheduleParam("", laneId, ppq, value, curve);
|
|
@@ -1814,7 +2239,9 @@ var SonareEngine = class _SonareEngine {
|
|
|
1814
2239
|
void target;
|
|
1815
2240
|
void solo;
|
|
1816
2241
|
void mute;
|
|
1817
|
-
|
|
2242
|
+
throw new Error(
|
|
2243
|
+
"SonareEngine.setSoloMute is not supported: solo/mute is a Mixer feature; use Mixer.setSoloed(stripIndex, ...) / Mixer.setMuted(stripIndex, ...) instead."
|
|
2244
|
+
);
|
|
1818
2245
|
}
|
|
1819
2246
|
addClip(trackId, buffer, startPpq, opts = {}) {
|
|
1820
2247
|
const id = opts.id ?? this.nextClipId++;
|
|
@@ -1850,11 +2277,12 @@ var SonareEngine = class _SonareEngine {
|
|
|
1850
2277
|
type: 14 /* Punch */,
|
|
1851
2278
|
sampleTime: -1,
|
|
1852
2279
|
argInt: inSample,
|
|
1853
|
-
argFloat:
|
|
2280
|
+
argFloat: outSample
|
|
1854
2281
|
});
|
|
1855
2282
|
}
|
|
1856
2283
|
setMetronome(opts) {
|
|
1857
2284
|
this.offlineEngine.setMetronome(opts);
|
|
2285
|
+
this.postSync({ type: "syncMetronome", config: opts });
|
|
1858
2286
|
this.realtimeNode.sendCommand({
|
|
1859
2287
|
type: 15 /* SetMetronome */,
|
|
1860
2288
|
sampleTime: -1,
|
|
@@ -1869,7 +2297,11 @@ var SonareEngine = class _SonareEngine {
|
|
|
1869
2297
|
}
|
|
1870
2298
|
seekMarker(markerId) {
|
|
1871
2299
|
this.offlineEngine.seekMarker(markerId);
|
|
1872
|
-
return
|
|
2300
|
+
return this.realtimeNode.sendCommand({
|
|
2301
|
+
type: 17 /* SeekMarker */,
|
|
2302
|
+
targetId: markerId,
|
|
2303
|
+
sampleTime: -1
|
|
2304
|
+
});
|
|
1873
2305
|
}
|
|
1874
2306
|
async renderOffline(totalFrames) {
|
|
1875
2307
|
const frames = Math.max(0, Math.floor(totalFrames));
|
|
@@ -1888,6 +2320,9 @@ var SonareEngine = class _SonareEngine {
|
|
|
1888
2320
|
pollTelemetry() {
|
|
1889
2321
|
return this.realtimeNode.pollTelemetry();
|
|
1890
2322
|
}
|
|
2323
|
+
pollMeters() {
|
|
2324
|
+
return this.realtimeNode.pollMeters();
|
|
2325
|
+
}
|
|
1891
2326
|
destroy() {
|
|
1892
2327
|
if (this.destroyed) {
|
|
1893
2328
|
return;
|
|
@@ -1899,10 +2334,23 @@ var SonareEngine = class _SonareEngine {
|
|
|
1899
2334
|
this.offlineEngine.destroy();
|
|
1900
2335
|
}
|
|
1901
2336
|
syncClips() {
|
|
1902
|
-
|
|
2337
|
+
const clips = Array.from(this.clips.values());
|
|
2338
|
+
this.offlineEngine.setClips(clips);
|
|
2339
|
+
this.postSync({ type: "syncClips", clips });
|
|
1903
2340
|
}
|
|
1904
2341
|
syncMarkers() {
|
|
1905
|
-
|
|
2342
|
+
const markers = Array.from(this.markers.values()).sort((a, b) => a.ppq - b.ppq);
|
|
2343
|
+
this.offlineEngine.setMarkers(markers);
|
|
2344
|
+
this.postSync({ type: "syncMarkers", markers });
|
|
2345
|
+
}
|
|
2346
|
+
// Posts an out-of-band control-sync message to the worklet engine processor.
|
|
2347
|
+
// Sync messages use a string `type` so the worklet's message handler routes
|
|
2348
|
+
// them to receiveSync() (numeric `type` is reserved for SonareEngineCommandRecord).
|
|
2349
|
+
postSync(message) {
|
|
2350
|
+
if (this.destroyed) {
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
this.realtimeNode.node.port.postMessage(message);
|
|
1906
2354
|
}
|
|
1907
2355
|
resolveParamId(nodeId, param) {
|
|
1908
2356
|
if (typeof param === "number") {
|
|
@@ -1931,6 +2379,156 @@ var SonareEngine = class _SonareEngine {
|
|
|
1931
2379
|
return Math.max(0, Math.round(ppq * 60 / 120 * this.sampleRate));
|
|
1932
2380
|
}
|
|
1933
2381
|
};
|
|
2382
|
+
var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChangerWorkletProcessor {
|
|
2383
|
+
constructor(options = {}) {
|
|
2384
|
+
this.destroyed = false;
|
|
2385
|
+
this.sampleRate = options.sampleRate ?? 48e3;
|
|
2386
|
+
this.blockSize = options.blockSize ?? 128;
|
|
2387
|
+
this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 1));
|
|
2388
|
+
this.changer = new RealtimeVoiceChanger(options.preset ?? "neutral-monitor");
|
|
2389
|
+
this.changer.prepare(this.sampleRate, this.blockSize, this.channelCount);
|
|
2390
|
+
this.monoInput = this.changer.getMonoInputBuffer(this.blockSize);
|
|
2391
|
+
this.monoOutput = this.changer.getMonoOutputBuffer(this.blockSize);
|
|
2392
|
+
this.planarChannels = [];
|
|
2393
|
+
if (this.channelCount > 1) {
|
|
2394
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
2395
|
+
this.planarChannels.push(this.changer.getPlanarChannelBuffer(ch, this.blockSize));
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Handles a control-plane message from the main thread. Runs on the
|
|
2401
|
+
* AudioWorklet global scope but OUTSIDE of `process()` (i.e. outside the
|
|
2402
|
+
* realtime audio callback), so it is safe to perform JSON parsing and
|
|
2403
|
+
* DSP coefficient recomputation here. `setConfig` MUST NOT be deferred
|
|
2404
|
+
* into `process()` because that would block the audio thread for longer
|
|
2405
|
+
* than one render quantum (e.g. 128 samples / 44.1 kHz = ~2.9 ms).
|
|
2406
|
+
*/
|
|
2407
|
+
receiveMessage(message) {
|
|
2408
|
+
if (this.destroyed) {
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
if (message.type === "setConfig") {
|
|
2412
|
+
this.changer.setConfig(message.preset);
|
|
2413
|
+
} else if (message.type === "reset") {
|
|
2414
|
+
this.changer.reset();
|
|
2415
|
+
} else if (message.type === "destroy") {
|
|
2416
|
+
this.destroy();
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
process(inputs, outputs) {
|
|
2420
|
+
const output = outputs[0];
|
|
2421
|
+
if (this.destroyed || !output || output.length === 0) {
|
|
2422
|
+
return !this.destroyed;
|
|
2423
|
+
}
|
|
2424
|
+
if (this.monoInput.byteLength === 0) {
|
|
2425
|
+
this.reacquireBuffers();
|
|
2426
|
+
}
|
|
2427
|
+
const input = inputs[0];
|
|
2428
|
+
const requestedFrames = output[0]?.length ?? 0;
|
|
2429
|
+
const requestedChannels = Math.min(this.channelCount, output.length);
|
|
2430
|
+
if (requestedFrames === 0 || requestedChannels === 0) {
|
|
2431
|
+
return true;
|
|
2432
|
+
}
|
|
2433
|
+
if (requestedChannels === 1) {
|
|
2434
|
+
const frames2 = this.ensureMonoCapacity(requestedFrames);
|
|
2435
|
+
const source = input?.[0];
|
|
2436
|
+
if (source) {
|
|
2437
|
+
this.monoInput.set(source.subarray(0, frames2));
|
|
2438
|
+
} else {
|
|
2439
|
+
this.monoInput.fill(0, 0, frames2);
|
|
2440
|
+
}
|
|
2441
|
+
this.changer.processMonoInto(
|
|
2442
|
+
this.monoInput.subarray(0, frames2),
|
|
2443
|
+
this.monoOutput.subarray(0, frames2)
|
|
2444
|
+
);
|
|
2445
|
+
output[0].set(this.monoOutput.subarray(0, frames2));
|
|
2446
|
+
return true;
|
|
2447
|
+
}
|
|
2448
|
+
const frames = this.ensureInterleavedCapacity(requestedFrames, requestedChannels);
|
|
2449
|
+
const channels = requestedChannels;
|
|
2450
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
2451
|
+
const src = input?.[ch];
|
|
2452
|
+
const dst = this.planarChannels[ch];
|
|
2453
|
+
if (!dst) {
|
|
2454
|
+
continue;
|
|
2455
|
+
}
|
|
2456
|
+
if (src) {
|
|
2457
|
+
dst.set(src.subarray(0, frames));
|
|
2458
|
+
} else {
|
|
2459
|
+
dst.fill(0, 0, frames);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
this.changer.processPreparedPlanar(frames);
|
|
2463
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
2464
|
+
const src = this.planarChannels[ch];
|
|
2465
|
+
if (src) {
|
|
2466
|
+
output[ch].set(src.subarray(0, frames));
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
return true;
|
|
2470
|
+
}
|
|
2471
|
+
destroy() {
|
|
2472
|
+
if (this.destroyed) {
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
this.destroyed = true;
|
|
2476
|
+
this.changer.delete();
|
|
2477
|
+
}
|
|
2478
|
+
// Re-acquires the cached WASM-heap views after a memory-growth detachment.
|
|
2479
|
+
// The underlying C++ vectors are pre-warmed (ensure_*_capacity ran at prepare
|
|
2480
|
+
// time), so getMono*/getPlanar* return fresh views onto the SAME storage
|
|
2481
|
+
// without reallocating it.
|
|
2482
|
+
reacquireBuffers() {
|
|
2483
|
+
this.monoInput = this.changer.getMonoInputBuffer(this.blockSize);
|
|
2484
|
+
this.monoOutput = this.changer.getMonoOutputBuffer(this.blockSize);
|
|
2485
|
+
if (this.channelCount > 1) {
|
|
2486
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
2487
|
+
this.planarChannels[ch] = this.changer.getPlanarChannelBuffer(ch, this.blockSize);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Returns the number of frames we can actually process given the
|
|
2493
|
+
* pre-allocated capacity. If the host requests more frames than the
|
|
2494
|
+
* worst-case block size declared at construction time, we clamp to the
|
|
2495
|
+
* available capacity and warn once — we MUST NOT reallocate on the
|
|
2496
|
+
* realtime audio thread.
|
|
2497
|
+
*/
|
|
2498
|
+
ensureMonoCapacity(frames) {
|
|
2499
|
+
const capacity = this.monoInput.length;
|
|
2500
|
+
if (frames <= capacity) {
|
|
2501
|
+
return frames;
|
|
2502
|
+
}
|
|
2503
|
+
if (!_SonareRealtimeVoiceChangerWorkletProcessor.warnedMonoOverflow) {
|
|
2504
|
+
_SonareRealtimeVoiceChangerWorkletProcessor.warnedMonoOverflow = true;
|
|
2505
|
+
console.warn(
|
|
2506
|
+
`SonareRealtimeVoiceChangerWorkletProcessor: requested ${frames} mono frames exceeds pre-allocated capacity ${capacity}; clamping. Increase blockSize at construction time to avoid this.`
|
|
2507
|
+
);
|
|
2508
|
+
}
|
|
2509
|
+
return capacity;
|
|
2510
|
+
}
|
|
2511
|
+
/**
|
|
2512
|
+
* Same contract as ensureMonoCapacity but for the planar per-channel
|
|
2513
|
+
* scratch. Returns the number of frames that fit in the available capacity.
|
|
2514
|
+
*/
|
|
2515
|
+
ensureInterleavedCapacity(frames, channels) {
|
|
2516
|
+
const capacity = this.planarChannels[0]?.length ?? 0;
|
|
2517
|
+
if (frames <= capacity) {
|
|
2518
|
+
return frames;
|
|
2519
|
+
}
|
|
2520
|
+
if (!_SonareRealtimeVoiceChangerWorkletProcessor.warnedInterleavedOverflow) {
|
|
2521
|
+
_SonareRealtimeVoiceChangerWorkletProcessor.warnedInterleavedOverflow = true;
|
|
2522
|
+
console.warn(
|
|
2523
|
+
`SonareRealtimeVoiceChangerWorkletProcessor: requested ${frames}x${channels} planar frames exceeds pre-allocated capacity ${capacity}; clamping. Increase blockSize or channelCount at construction time to avoid this.`
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
return capacity;
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
_SonareRealtimeVoiceChangerWorkletProcessor.warnedMonoOverflow = false;
|
|
2530
|
+
_SonareRealtimeVoiceChangerWorkletProcessor.warnedInterleavedOverflow = false;
|
|
2531
|
+
var SonareRealtimeVoiceChangerWorkletProcessor = _SonareRealtimeVoiceChangerWorkletProcessor;
|
|
1934
2532
|
function registerSonareWorkletProcessor(name = "sonare-worklet-processor") {
|
|
1935
2533
|
const scope = globalThis;
|
|
1936
2534
|
if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
|
|
@@ -1962,6 +2560,35 @@ function registerSonareWorkletProcessor(name = "sonare-worklet-processor") {
|
|
|
1962
2560
|
}
|
|
1963
2561
|
scope.registerProcessor(name, RegisteredSonareWorkletProcessor);
|
|
1964
2562
|
}
|
|
2563
|
+
function registerSonareRealtimeVoiceChangerWorkletProcessor(name = "sonare-realtime-voice-changer-processor") {
|
|
2564
|
+
const scope = globalThis;
|
|
2565
|
+
if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
|
|
2566
|
+
throw new Error("AudioWorkletProcessor is not available in this context.");
|
|
2567
|
+
}
|
|
2568
|
+
const Base = scope.AudioWorkletProcessor;
|
|
2569
|
+
class RegisteredSonareRealtimeVoiceChangerWorkletProcessor extends Base {
|
|
2570
|
+
constructor(options) {
|
|
2571
|
+
super();
|
|
2572
|
+
const port = this.port;
|
|
2573
|
+
this.bridge = new SonareRealtimeVoiceChangerWorkletProcessor(options?.processorOptions ?? {});
|
|
2574
|
+
const onMessage = (event) => {
|
|
2575
|
+
if (isRealtimeVoiceChangerMessage(event.data)) {
|
|
2576
|
+
this.bridge.receiveMessage(event.data);
|
|
2577
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
if (port?.addEventListener) {
|
|
2580
|
+
port.addEventListener("message", onMessage);
|
|
2581
|
+
port.start?.();
|
|
2582
|
+
} else if (port) {
|
|
2583
|
+
port.onmessage = onMessage;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
process(inputs, outputs) {
|
|
2587
|
+
return this.bridge.process(inputs, outputs);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
scope.registerProcessor(name, RegisteredSonareRealtimeVoiceChangerWorkletProcessor);
|
|
2591
|
+
}
|
|
1965
2592
|
function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-engine-processor") {
|
|
1966
2593
|
const scope = globalThis;
|
|
1967
2594
|
if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
|
|
@@ -1984,6 +2611,10 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
1984
2611
|
const onMessage = (event) => {
|
|
1985
2612
|
if (isEngineCommandRecord(event.data)) {
|
|
1986
2613
|
this.bridge?.receiveCommand(event.data);
|
|
2614
|
+
this.rtBridge?.receiveCommand(event.data);
|
|
2615
|
+
} else if (isEngineSyncMessage(event.data)) {
|
|
2616
|
+
this.bridge?.receiveSync(event.data);
|
|
2617
|
+
this.rtBridge?.receiveSync(event.data);
|
|
1987
2618
|
}
|
|
1988
2619
|
};
|
|
1989
2620
|
if (port?.addEventListener) {
|
|
@@ -2011,13 +2642,14 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
2011
2642
|
if (!options.rtModuleUrl) {
|
|
2012
2643
|
throw new Error("rtModuleUrl is required for sonare-rt AudioWorklet runtime.");
|
|
2013
2644
|
}
|
|
2645
|
+
const rtModuleUrl = options.rtModuleUrl;
|
|
2014
2646
|
const memory = new WebAssembly.Memory({ initial: 1024, maximum: 1024, shared: true });
|
|
2015
2647
|
const globalFactory = globalThis.SonareRtModuleFactory;
|
|
2016
|
-
const moduleFactory = globalFactory ? { default: globalFactory } : await import(
|
|
2648
|
+
const moduleFactory = globalFactory ? { default: globalFactory } : await import(rtModuleUrl);
|
|
2017
2649
|
const module2 = await moduleFactory.default({
|
|
2018
2650
|
wasmMemory: memory,
|
|
2019
2651
|
wasmBinary: options.rtWasmBinary,
|
|
2020
|
-
locateFile: (path) =>
|
|
2652
|
+
locateFile: (path) => rtModuleUrl.replace(/[^/]*$/, path)
|
|
2021
2653
|
});
|
|
2022
2654
|
this.rtBridge = new SonareRtRealtimeEngineRuntime({
|
|
2023
2655
|
module: module2,
|
|
@@ -2054,12 +2686,16 @@ export {
|
|
|
2054
2686
|
SonareEngineTelemetryType,
|
|
2055
2687
|
SonareRealtimeEngineNode,
|
|
2056
2688
|
SonareRealtimeEngineWorkletProcessor,
|
|
2689
|
+
SonareRealtimeVoiceChangerWorkletProcessor,
|
|
2057
2690
|
SonareRtRealtimeEngineRuntime,
|
|
2058
2691
|
SonareWorkletProcessor,
|
|
2059
2692
|
createSonareEngineCommandRingBuffer,
|
|
2060
2693
|
createSonareEngineTelemetryRingBuffer,
|
|
2061
2694
|
createSonareMeterRingBuffer,
|
|
2062
2695
|
createSonareSpectrumRingBuffer,
|
|
2696
|
+
decodeFrame,
|
|
2697
|
+
encodeFrameHi,
|
|
2698
|
+
encodeFrameLo,
|
|
2063
2699
|
init,
|
|
2064
2700
|
isInitialized,
|
|
2065
2701
|
popSonareEngineCommandRingBuffer,
|
|
@@ -2068,6 +2704,7 @@ export {
|
|
|
2068
2704
|
readSonareMeterRingBuffer,
|
|
2069
2705
|
readSonareSpectrumRingBuffer,
|
|
2070
2706
|
registerSonareRealtimeEngineWorkletProcessor,
|
|
2707
|
+
registerSonareRealtimeVoiceChangerWorkletProcessor,
|
|
2071
2708
|
registerSonareWorkletProcessor,
|
|
2072
2709
|
sonareEngineCommandRingBufferByteLength,
|
|
2073
2710
|
sonareEngineTelemetryRingBufferByteLength,
|