@libraz/libsonare 1.2.2 → 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/dist/index.d.ts +132 -12
- package/dist/index.js +103 -8
- 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 +44 -5
- package/dist/worklet.js +348 -73
- package/dist/worklet.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +189 -8
- package/src/public_types.ts +67 -0
- package/src/sonare.js.d.ts +84 -0
- package/src/stream_types.ts +4 -1
- package/src/worklet.ts +525 -81
package/dist/worklet.js
CHANGED
|
@@ -229,6 +229,31 @@ var RealtimeEngine = class {
|
|
|
229
229
|
process(channels) {
|
|
230
230
|
return this.native.process(channels);
|
|
231
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
|
+
}
|
|
232
257
|
processWithMonitor(channels) {
|
|
233
258
|
return this.native.processWithMonitor(channels);
|
|
234
259
|
}
|
|
@@ -696,8 +721,20 @@ var Mixer = class _Mixer {
|
|
|
696
721
|
|
|
697
722
|
// src/worklet.ts
|
|
698
723
|
var SONARE_METER_RING_HEADER_INTS = 4;
|
|
699
|
-
var SONARE_METER_RING_RECORD_FLOATS =
|
|
724
|
+
var SONARE_METER_RING_RECORD_FLOATS = 7;
|
|
700
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
|
+
}
|
|
701
738
|
var SONARE_ENGINE_RING_HEADER_INTS = 5;
|
|
702
739
|
var SONARE_ENGINE_COMMAND_RECORD_BYTES = 32;
|
|
703
740
|
var SONARE_ENGINE_TELEMETRY_RECORD_BYTES = 48;
|
|
@@ -744,6 +781,18 @@ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2)
|
|
|
744
781
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["SmoothedParameterCapacity"] = 13] = "SmoothedParameterCapacity";
|
|
745
782
|
return SonareEngineTelemetryError2;
|
|
746
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
|
+
}
|
|
747
796
|
function toDb(value) {
|
|
748
797
|
return value > 0 ? 20 * Math.log10(value) : Number.NEGATIVE_INFINITY;
|
|
749
798
|
}
|
|
@@ -759,6 +808,12 @@ function isWorkletMessage(value) {
|
|
|
759
808
|
function isEngineCommandRecord(value) {
|
|
760
809
|
return isRecord(value) && typeof value.type === "number";
|
|
761
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
|
+
}
|
|
762
817
|
function isRealtimeVoiceChangerMessage(value) {
|
|
763
818
|
if (!isRecord(value) || typeof value.type !== "string") {
|
|
764
819
|
return false;
|
|
@@ -794,7 +849,7 @@ function readSonareMeterRingBuffer(ring, readIndex = 0) {
|
|
|
794
849
|
const offset = index % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
795
850
|
meters.push({
|
|
796
851
|
type: "meter",
|
|
797
|
-
frame: ring.records[offset],
|
|
852
|
+
frame: decodeFrame(ring.records[offset], ring.records[offset + 6]),
|
|
798
853
|
peakDbL: ring.records[offset + 1],
|
|
799
854
|
peakDbR: ring.records[offset + 2],
|
|
800
855
|
rmsDbL: ring.records[offset + 3],
|
|
@@ -807,7 +862,7 @@ function readSonareMeterRingBuffer(ring, readIndex = 0) {
|
|
|
807
862
|
function sonareSpectrumRingBufferByteLength(capacity, bands = 16) {
|
|
808
863
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
809
864
|
const clampedBands = Math.max(1, Math.floor(bands));
|
|
810
|
-
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;
|
|
811
866
|
}
|
|
812
867
|
function createSonareSpectrumRingBuffer(capacity = 128, bands = 16) {
|
|
813
868
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
@@ -831,7 +886,7 @@ function createSonareSpectrumRingBuffer(capacity = 128, bands = 16) {
|
|
|
831
886
|
}
|
|
832
887
|
function readSonareSpectrumRingBuffer(ring, readIndex = 0) {
|
|
833
888
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
834
|
-
const recordFloats = Atomics.load(ring.header, 2) ||
|
|
889
|
+
const recordFloats = Atomics.load(ring.header, 2) || 3 + ring.bands;
|
|
835
890
|
const bands = Atomics.load(ring.header, 3) || ring.bands;
|
|
836
891
|
const nextReadIndex = Math.max(0, Math.min(readIndex, writeIndex));
|
|
837
892
|
const firstReadable = Math.max(nextReadIndex, writeIndex - ring.capacity);
|
|
@@ -839,8 +894,12 @@ function readSonareSpectrumRingBuffer(ring, readIndex = 0) {
|
|
|
839
894
|
for (let index = firstReadable; index < writeIndex; index++) {
|
|
840
895
|
const offset = index % ring.capacity * recordFloats;
|
|
841
896
|
const values = new Float32Array(bands);
|
|
842
|
-
values.set(ring.records.subarray(offset +
|
|
843
|
-
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
|
+
});
|
|
844
903
|
}
|
|
845
904
|
return { nextReadIndex: writeIndex, spectra };
|
|
846
905
|
}
|
|
@@ -959,7 +1018,7 @@ function spectrumRingFromSharedBuffer(sharedBuffer, fallbackCapacity, fallbackBa
|
|
|
959
1018
|
const existingBands = Atomics.load(header, 3);
|
|
960
1019
|
const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
|
|
961
1020
|
const bands = Math.max(1, Math.floor(existingBands || fallbackBands || 16));
|
|
962
|
-
const recordFloats =
|
|
1021
|
+
const recordFloats = 3 + bands;
|
|
963
1022
|
const minBytes = sonareSpectrumRingBufferByteLength(capacity, bands);
|
|
964
1023
|
if (sharedBuffer.byteLength < minBytes) {
|
|
965
1024
|
throw new Error("spectrumSharedBuffer is too small for the requested ring capacity.");
|
|
@@ -1008,8 +1067,7 @@ function writeEngineCommandRecord(view, offset, command) {
|
|
|
1008
1067
|
view.setUint32(offset, command.type, true);
|
|
1009
1068
|
view.setUint32(offset + 4, command.targetId ?? 0, true);
|
|
1010
1069
|
view.setBigInt64(offset + 8, toBigInt64(command.sampleTime, -1n), true);
|
|
1011
|
-
view.
|
|
1012
|
-
view.setUint32(offset + 20, 0, true);
|
|
1070
|
+
view.setFloat64(offset + 16, command.argFloat ?? 0, true);
|
|
1013
1071
|
view.setBigInt64(offset + 24, toBigInt64(command.argInt, 0n), true);
|
|
1014
1072
|
}
|
|
1015
1073
|
function readEngineCommandRecord(view, offset) {
|
|
@@ -1017,7 +1075,7 @@ function readEngineCommandRecord(view, offset) {
|
|
|
1017
1075
|
type: view.getUint32(offset, true),
|
|
1018
1076
|
targetId: view.getUint32(offset + 4, true),
|
|
1019
1077
|
sampleTime: Number(view.getBigInt64(offset + 8, true)),
|
|
1020
|
-
argFloat: view.
|
|
1078
|
+
argFloat: view.getFloat64(offset + 16, true),
|
|
1021
1079
|
argInt: Number(view.getBigInt64(offset + 24, true))
|
|
1022
1080
|
};
|
|
1023
1081
|
}
|
|
@@ -1110,35 +1168,48 @@ var SonareWorkletProcessor = class {
|
|
|
1110
1168
|
return true;
|
|
1111
1169
|
}
|
|
1112
1170
|
const frames = leftOut.length;
|
|
1113
|
-
|
|
1114
|
-
return false;
|
|
1115
|
-
}
|
|
1171
|
+
const usable = Math.min(frames, this.blockSize);
|
|
1116
1172
|
for (let strip = 0; strip < this.realtime.leftInputs.length; strip++) {
|
|
1117
1173
|
const input = inputs[strip];
|
|
1118
1174
|
const left = input?.[0];
|
|
1119
1175
|
const right = input?.[1];
|
|
1120
1176
|
const leftTarget = this.realtime.leftInputs[strip];
|
|
1121
1177
|
const rightTarget = this.realtime.rightInputs[strip];
|
|
1122
|
-
if (left && left.length
|
|
1123
|
-
leftTarget.set(left);
|
|
1124
|
-
if (right && right.length
|
|
1125
|
-
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));
|
|
1126
1182
|
} else {
|
|
1127
|
-
rightTarget.set(left);
|
|
1183
|
+
rightTarget.set(left.subarray(0, usable));
|
|
1128
1184
|
}
|
|
1129
1185
|
} else {
|
|
1130
1186
|
leftTarget.fill(0);
|
|
1131
1187
|
rightTarget.fill(0);
|
|
1132
1188
|
}
|
|
1133
1189
|
}
|
|
1134
|
-
this.realtime.process(
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
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
|
+
}
|
|
1138
1203
|
}
|
|
1139
|
-
this.processedFrames +=
|
|
1140
|
-
this.publishMeter(
|
|
1141
|
-
|
|
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
|
+
);
|
|
1142
1213
|
return true;
|
|
1143
1214
|
}
|
|
1144
1215
|
receiveMessage(message) {
|
|
@@ -1224,16 +1295,14 @@ var SonareWorkletProcessor = class {
|
|
|
1224
1295
|
}
|
|
1225
1296
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
1226
1297
|
const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
1227
|
-
ring.records[offset] = meter.frame;
|
|
1298
|
+
ring.records[offset] = encodeFrameLo(meter.frame);
|
|
1228
1299
|
ring.records[offset + 1] = meter.peakDbL;
|
|
1229
1300
|
ring.records[offset + 2] = meter.peakDbR;
|
|
1230
1301
|
ring.records[offset + 3] = meter.rmsDbL;
|
|
1231
1302
|
ring.records[offset + 4] = meter.rmsDbR;
|
|
1232
1303
|
ring.records[offset + 5] = meter.correlation;
|
|
1304
|
+
ring.records[offset + 6] = encodeFrameHi(meter.frame);
|
|
1233
1305
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
1234
|
-
if (writeIndex + 1 > ring.capacity) {
|
|
1235
|
-
Atomics.store(ring.header, 3, writeIndex + 1 - ring.capacity);
|
|
1236
|
-
}
|
|
1237
1306
|
}
|
|
1238
1307
|
publishSpectrum(left, right) {
|
|
1239
1308
|
if (this.spectrumIntervalFrames <= 0) {
|
|
@@ -1258,7 +1327,12 @@ var SonareWorkletProcessor = class {
|
|
|
1258
1327
|
}
|
|
1259
1328
|
computeSpectrum(left, right) {
|
|
1260
1329
|
const n = Math.max(1, Math.min(left.length, right.length));
|
|
1330
|
+
const maxBand = Math.floor(n / 2);
|
|
1261
1331
|
for (let band = 0; band < this.spectrumBands.length; band++) {
|
|
1332
|
+
if (band >= maxBand) {
|
|
1333
|
+
this.spectrumBands[band] = magnitudeToDb(0);
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1262
1336
|
const bin = band + 1;
|
|
1263
1337
|
let real = 0;
|
|
1264
1338
|
let imag = 0;
|
|
@@ -1278,19 +1352,20 @@ var SonareWorkletProcessor = class {
|
|
|
1278
1352
|
}
|
|
1279
1353
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
1280
1354
|
const offset = writeIndex % ring.capacity * ring.recordFloats;
|
|
1281
|
-
ring.records[offset] = frame;
|
|
1282
|
-
ring.records[offset + 1] =
|
|
1283
|
-
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);
|
|
1284
1359
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
1285
|
-
if (writeIndex + 1 > ring.capacity) {
|
|
1286
|
-
Atomics.store(ring.header, 4, writeIndex + 1 - ring.capacity);
|
|
1287
|
-
}
|
|
1288
1360
|
}
|
|
1289
1361
|
};
|
|
1290
1362
|
var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletProcessor {
|
|
1291
1363
|
constructor(options = {}, transport) {
|
|
1292
1364
|
this.closed = false;
|
|
1293
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 };
|
|
1294
1369
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
1295
1370
|
this.blockSize = options.blockSize ?? 128;
|
|
1296
1371
|
this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
@@ -1307,12 +1382,12 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1307
1382
|
options.telemetrySharedBuffer,
|
|
1308
1383
|
options.telemetryRingCapacity
|
|
1309
1384
|
) : void 0;
|
|
1385
|
+
this.meterRing = options.meterSharedBuffer ? meterRingFromSharedBuffer(options.meterSharedBuffer, options.meterRingCapacity) : void 0;
|
|
1310
1386
|
this.engine = new RealtimeEngine(this.sampleRate, this.blockSize);
|
|
1311
|
-
this.
|
|
1312
|
-
this.
|
|
1387
|
+
this.engine.prepareChannels(this.channelCount, this.blockSize);
|
|
1388
|
+
this.channelBuffers = new Array(this.channelCount);
|
|
1313
1389
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1314
|
-
this.
|
|
1315
|
-
this.channelScratchViews[ch] = this.channelScratch[ch];
|
|
1390
|
+
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
1316
1391
|
}
|
|
1317
1392
|
}
|
|
1318
1393
|
process(inputs, outputs) {
|
|
@@ -1333,34 +1408,38 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1333
1408
|
return true;
|
|
1334
1409
|
}
|
|
1335
1410
|
this.drainCommands();
|
|
1336
|
-
const scratchCapacity = this.channelScratch[0]?.length ?? 0;
|
|
1337
1411
|
let usableFrames = frames;
|
|
1338
|
-
if (usableFrames >
|
|
1412
|
+
if (usableFrames > this.blockSize) {
|
|
1339
1413
|
if (!_SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow) {
|
|
1340
1414
|
_SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow = true;
|
|
1341
1415
|
console.warn(
|
|
1342
|
-
`SonareRealtimeEngineWorkletProcessor: requested ${usableFrames} frames exceeds pre-allocated capacity ${
|
|
1416
|
+
`SonareRealtimeEngineWorkletProcessor: requested ${usableFrames} frames exceeds pre-allocated capacity ${this.blockSize}; clamping.`
|
|
1343
1417
|
);
|
|
1344
1418
|
}
|
|
1345
|
-
usableFrames =
|
|
1419
|
+
usableFrames = this.blockSize;
|
|
1420
|
+
}
|
|
1421
|
+
if ((this.channelBuffers[0]?.byteLength ?? 0) === 0) {
|
|
1422
|
+
this.reacquireChannelBuffers();
|
|
1346
1423
|
}
|
|
1347
1424
|
const input = inputs[0];
|
|
1348
1425
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1349
|
-
const
|
|
1426
|
+
const dst = this.channelBuffers[ch];
|
|
1350
1427
|
const source = input?.[ch];
|
|
1351
1428
|
if (source && source.length === usableFrames) {
|
|
1352
|
-
|
|
1429
|
+
dst.set(source.subarray(0, usableFrames));
|
|
1353
1430
|
} else {
|
|
1354
|
-
|
|
1431
|
+
dst.fill(0, 0, usableFrames);
|
|
1355
1432
|
}
|
|
1356
|
-
this.channelScratchViews[ch] = scratch.subarray(0, usableFrames);
|
|
1357
1433
|
}
|
|
1358
|
-
|
|
1434
|
+
this.engine.processPrepared(usableFrames);
|
|
1359
1435
|
for (let ch = 0; ch < output.length; ch++) {
|
|
1360
1436
|
const target = output[ch];
|
|
1361
|
-
const source =
|
|
1437
|
+
const source = this.channelBuffers[ch] ?? this.channelBuffers[0];
|
|
1362
1438
|
if (source) {
|
|
1363
|
-
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
|
+
}
|
|
1364
1443
|
} else {
|
|
1365
1444
|
target.fill(0);
|
|
1366
1445
|
}
|
|
@@ -1369,11 +1448,41 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1369
1448
|
this.publishMeters();
|
|
1370
1449
|
return true;
|
|
1371
1450
|
}
|
|
1451
|
+
reacquireChannelBuffers() {
|
|
1452
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1453
|
+
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1372
1456
|
receiveCommand(command) {
|
|
1373
1457
|
if (!this.closed) {
|
|
1374
1458
|
this.applyCommand(command);
|
|
1375
1459
|
}
|
|
1376
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
|
+
}
|
|
1377
1486
|
destroy() {
|
|
1378
1487
|
if (!this.closed) {
|
|
1379
1488
|
this.engine.destroy();
|
|
@@ -1395,6 +1504,20 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1395
1504
|
applyCommand(command) {
|
|
1396
1505
|
const sampleTime = Number(command.sampleTime ?? -1);
|
|
1397
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;
|
|
1398
1521
|
case 2 /* TransportPlay */:
|
|
1399
1522
|
this.engine.play(sampleTime);
|
|
1400
1523
|
break;
|
|
@@ -1423,18 +1546,21 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1423
1546
|
case 14 /* Punch */:
|
|
1424
1547
|
this.engine.setCapturePunch(
|
|
1425
1548
|
Number(command.argInt ?? 0),
|
|
1426
|
-
Math.max(0, Math.round(Number(command.argFloat ?? 0)
|
|
1549
|
+
Math.max(0, Math.round(Number(command.argFloat ?? 0))),
|
|
1427
1550
|
true
|
|
1428
1551
|
);
|
|
1429
1552
|
break;
|
|
1430
1553
|
case 15 /* SetMetronome */:
|
|
1431
1554
|
this.engine.setMetronome({
|
|
1432
1555
|
enabled: Boolean(command.argInt),
|
|
1433
|
-
beatGain:
|
|
1434
|
-
accentGain:
|
|
1435
|
-
clickSamples:
|
|
1556
|
+
beatGain: this.metronomeConfig.beatGain,
|
|
1557
|
+
accentGain: this.metronomeConfig.accentGain,
|
|
1558
|
+
clickSamples: this.metronomeConfig.clickSamples
|
|
1436
1559
|
});
|
|
1437
1560
|
break;
|
|
1561
|
+
case 17 /* SeekMarker */:
|
|
1562
|
+
this.engine.seekMarker(Math.trunc(Number(command.targetId ?? 0)), sampleTime);
|
|
1563
|
+
break;
|
|
1438
1564
|
default:
|
|
1439
1565
|
this.publishTelemetryRecord({
|
|
1440
1566
|
type: 1 /* Error */,
|
|
@@ -1461,7 +1587,7 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1461
1587
|
this.transport?.postMessage?.(record);
|
|
1462
1588
|
}
|
|
1463
1589
|
publishMeters() {
|
|
1464
|
-
if (
|
|
1590
|
+
if (this.meterIntervalFrames <= 0 || !this.transport && !this.meterRing) {
|
|
1465
1591
|
return;
|
|
1466
1592
|
}
|
|
1467
1593
|
for (const item of this.engine.drainMeterTelemetry(64)) {
|
|
@@ -1470,10 +1596,30 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1470
1596
|
continue;
|
|
1471
1597
|
}
|
|
1472
1598
|
this.lastMeterFrame = meter.frame;
|
|
1473
|
-
this.
|
|
1474
|
-
|
|
1599
|
+
if (this.meterRing) {
|
|
1600
|
+
this.writeMeterRing(meter);
|
|
1601
|
+
} else {
|
|
1602
|
+
this.transport?.onMeter?.(meter);
|
|
1603
|
+
this.transport?.postMessage?.(meter);
|
|
1604
|
+
}
|
|
1475
1605
|
}
|
|
1476
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
|
+
}
|
|
1477
1623
|
commandRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
1478
1624
|
const ring = engineRingFromSharedBuffer(
|
|
1479
1625
|
sharedBuffer,
|
|
@@ -1495,6 +1641,7 @@ _SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow = false;
|
|
|
1495
1641
|
var SonareRealtimeEngineWorkletProcessor = _SonareRealtimeEngineWorkletProcessor;
|
|
1496
1642
|
var SonareRtRealtimeEngineRuntime = class {
|
|
1497
1643
|
constructor(options) {
|
|
1644
|
+
this.metronomeConfig = { ...DEFAULT_METRONOME_CONFIG };
|
|
1498
1645
|
this.closed = false;
|
|
1499
1646
|
this.module = options.module;
|
|
1500
1647
|
this.memory = options.memory;
|
|
@@ -1576,6 +1723,49 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1576
1723
|
this.publishTelemetry();
|
|
1577
1724
|
return true;
|
|
1578
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
|
+
}
|
|
1579
1769
|
destroy() {
|
|
1580
1770
|
if (this.closed) {
|
|
1581
1771
|
return;
|
|
@@ -1611,6 +1801,20 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1611
1801
|
applyCommand(command) {
|
|
1612
1802
|
const sampleTime = toBigInt64(command.sampleTime, -1n);
|
|
1613
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;
|
|
1614
1818
|
case 2 /* TransportPlay */:
|
|
1615
1819
|
this.module._sonare_rt_engine_play(this.engine, sampleTime);
|
|
1616
1820
|
break;
|
|
@@ -1649,7 +1853,7 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1649
1853
|
this.module._sonare_rt_engine_set_capture_punch(
|
|
1650
1854
|
this.engine,
|
|
1651
1855
|
toBigInt64(command.argInt, 0n),
|
|
1652
|
-
BigInt(Math.
|
|
1856
|
+
BigInt(Math.max(0, Math.round(Number(command.argFloat ?? 0)))),
|
|
1653
1857
|
1
|
|
1654
1858
|
);
|
|
1655
1859
|
break;
|
|
@@ -1657,9 +1861,9 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1657
1861
|
this.module._sonare_rt_engine_set_metronome_enabled(
|
|
1658
1862
|
this.engine,
|
|
1659
1863
|
command.argInt ? 1 : 0,
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1864
|
+
this.metronomeConfig.beatGain,
|
|
1865
|
+
this.metronomeConfig.accentGain,
|
|
1866
|
+
this.metronomeConfig.clickSamples
|
|
1663
1867
|
);
|
|
1664
1868
|
break;
|
|
1665
1869
|
case 17 /* SeekMarker */:
|
|
@@ -1734,8 +1938,9 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
1734
1938
|
}
|
|
1735
1939
|
};
|
|
1736
1940
|
var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
1737
|
-
constructor(node, capabilities, commandRing, telemetryRing) {
|
|
1941
|
+
constructor(node, capabilities, commandRing, telemetryRing, meterRing) {
|
|
1738
1942
|
this.telemetryReadIndex = 0;
|
|
1943
|
+
this.meterReadIndex = 0;
|
|
1739
1944
|
this.telemetryListeners = /* @__PURE__ */ new Set();
|
|
1740
1945
|
this.meterListeners = /* @__PURE__ */ new Set();
|
|
1741
1946
|
this.destroyed = false;
|
|
@@ -1743,6 +1948,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1743
1948
|
this.capabilities = capabilities;
|
|
1744
1949
|
this.commandRing = commandRing;
|
|
1745
1950
|
this.telemetryRing = telemetryRing;
|
|
1951
|
+
this.meterRing = meterRing;
|
|
1746
1952
|
this.ready = new Promise((resolve, reject) => {
|
|
1747
1953
|
this.resolveReady = resolve;
|
|
1748
1954
|
this.rejectReady = reject;
|
|
@@ -1791,6 +1997,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1791
1997
|
}
|
|
1792
1998
|
const commandRing = mode === "sab" ? createSonareEngineCommandRingBuffer(options.commandRingCapacity ?? 128) : void 0;
|
|
1793
1999
|
const telemetryRing = mode === "sab" ? createSonareEngineTelemetryRingBuffer(options.telemetryRingCapacity ?? 128) : void 0;
|
|
2000
|
+
const meterRing = mode === "sab" && runtimeTarget === "embind" ? createSonareMeterRingBuffer(options.meterRingCapacity ?? 128) : void 0;
|
|
1794
2001
|
const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
1795
2002
|
const processorOptions = {
|
|
1796
2003
|
runtimeTarget,
|
|
@@ -1802,7 +2009,9 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1802
2009
|
commandSharedBuffer: commandRing?.sharedBuffer,
|
|
1803
2010
|
commandRingCapacity: commandRing?.capacity,
|
|
1804
2011
|
telemetrySharedBuffer: telemetryRing?.sharedBuffer,
|
|
1805
|
-
telemetryRingCapacity: telemetryRing?.capacity
|
|
2012
|
+
telemetryRingCapacity: telemetryRing?.capacity,
|
|
2013
|
+
meterSharedBuffer: meterRing?.sharedBuffer,
|
|
2014
|
+
meterRingCapacity: meterRing?.capacity
|
|
1806
2015
|
};
|
|
1807
2016
|
const factory = options.nodeFactory ?? ((ctx, name, nodeOptions) => new AudioWorkletNode(ctx, name, nodeOptions));
|
|
1808
2017
|
const node = factory(context, processorName, {
|
|
@@ -1825,7 +2034,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1825
2034
|
degradedReason
|
|
1826
2035
|
},
|
|
1827
2036
|
commandRing,
|
|
1828
|
-
telemetryRing
|
|
2037
|
+
telemetryRing,
|
|
2038
|
+
meterRing
|
|
1829
2039
|
);
|
|
1830
2040
|
}
|
|
1831
2041
|
play(sampleTime = -1) {
|
|
@@ -1869,6 +2079,20 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
1869
2079
|
}
|
|
1870
2080
|
return read.telemetry;
|
|
1871
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
|
+
}
|
|
1872
2096
|
onTelemetry(callback) {
|
|
1873
2097
|
this.telemetryListeners.add(callback);
|
|
1874
2098
|
return () => {
|
|
@@ -1983,10 +2207,14 @@ var SonareEngine = class _SonareEngine {
|
|
|
1983
2207
|
});
|
|
1984
2208
|
}
|
|
1985
2209
|
setParam(nodeId, param, value) {
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
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
|
+
});
|
|
1990
2218
|
}
|
|
1991
2219
|
scheduleParam(nodeId, param, ppq, value, curve = "linear") {
|
|
1992
2220
|
const paramId = this.resolveParamId(nodeId, param);
|
|
@@ -1995,6 +2223,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
1995
2223
|
lane.sort((a, b) => a.ppq - b.ppq);
|
|
1996
2224
|
this.automationLanes.set(paramId, lane);
|
|
1997
2225
|
this.offlineEngine.setAutomationLane(paramId, lane);
|
|
2226
|
+
this.postSync({ type: "syncAutomation", paramId, points: lane });
|
|
1998
2227
|
}
|
|
1999
2228
|
addAutomationPoint(laneId, ppq, value, curve = "linear") {
|
|
2000
2229
|
this.scheduleParam("", laneId, ppq, value, curve);
|
|
@@ -2010,7 +2239,9 @@ var SonareEngine = class _SonareEngine {
|
|
|
2010
2239
|
void target;
|
|
2011
2240
|
void solo;
|
|
2012
2241
|
void mute;
|
|
2013
|
-
|
|
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
|
+
);
|
|
2014
2245
|
}
|
|
2015
2246
|
addClip(trackId, buffer, startPpq, opts = {}) {
|
|
2016
2247
|
const id = opts.id ?? this.nextClipId++;
|
|
@@ -2046,11 +2277,12 @@ var SonareEngine = class _SonareEngine {
|
|
|
2046
2277
|
type: 14 /* Punch */,
|
|
2047
2278
|
sampleTime: -1,
|
|
2048
2279
|
argInt: inSample,
|
|
2049
|
-
argFloat:
|
|
2280
|
+
argFloat: outSample
|
|
2050
2281
|
});
|
|
2051
2282
|
}
|
|
2052
2283
|
setMetronome(opts) {
|
|
2053
2284
|
this.offlineEngine.setMetronome(opts);
|
|
2285
|
+
this.postSync({ type: "syncMetronome", config: opts });
|
|
2054
2286
|
this.realtimeNode.sendCommand({
|
|
2055
2287
|
type: 15 /* SetMetronome */,
|
|
2056
2288
|
sampleTime: -1,
|
|
@@ -2065,7 +2297,11 @@ var SonareEngine = class _SonareEngine {
|
|
|
2065
2297
|
}
|
|
2066
2298
|
seekMarker(markerId) {
|
|
2067
2299
|
this.offlineEngine.seekMarker(markerId);
|
|
2068
|
-
return
|
|
2300
|
+
return this.realtimeNode.sendCommand({
|
|
2301
|
+
type: 17 /* SeekMarker */,
|
|
2302
|
+
targetId: markerId,
|
|
2303
|
+
sampleTime: -1
|
|
2304
|
+
});
|
|
2069
2305
|
}
|
|
2070
2306
|
async renderOffline(totalFrames) {
|
|
2071
2307
|
const frames = Math.max(0, Math.floor(totalFrames));
|
|
@@ -2084,6 +2320,9 @@ var SonareEngine = class _SonareEngine {
|
|
|
2084
2320
|
pollTelemetry() {
|
|
2085
2321
|
return this.realtimeNode.pollTelemetry();
|
|
2086
2322
|
}
|
|
2323
|
+
pollMeters() {
|
|
2324
|
+
return this.realtimeNode.pollMeters();
|
|
2325
|
+
}
|
|
2087
2326
|
destroy() {
|
|
2088
2327
|
if (this.destroyed) {
|
|
2089
2328
|
return;
|
|
@@ -2095,10 +2334,23 @@ var SonareEngine = class _SonareEngine {
|
|
|
2095
2334
|
this.offlineEngine.destroy();
|
|
2096
2335
|
}
|
|
2097
2336
|
syncClips() {
|
|
2098
|
-
|
|
2337
|
+
const clips = Array.from(this.clips.values());
|
|
2338
|
+
this.offlineEngine.setClips(clips);
|
|
2339
|
+
this.postSync({ type: "syncClips", clips });
|
|
2099
2340
|
}
|
|
2100
2341
|
syncMarkers() {
|
|
2101
|
-
|
|
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);
|
|
2102
2354
|
}
|
|
2103
2355
|
resolveParamId(nodeId, param) {
|
|
2104
2356
|
if (typeof param === "number") {
|
|
@@ -2169,6 +2421,9 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
2169
2421
|
if (this.destroyed || !output || output.length === 0) {
|
|
2170
2422
|
return !this.destroyed;
|
|
2171
2423
|
}
|
|
2424
|
+
if (this.monoInput.byteLength === 0) {
|
|
2425
|
+
this.reacquireBuffers();
|
|
2426
|
+
}
|
|
2172
2427
|
const input = inputs[0];
|
|
2173
2428
|
const requestedFrames = output[0]?.length ?? 0;
|
|
2174
2429
|
const requestedChannels = Math.min(this.channelCount, output.length);
|
|
@@ -2220,6 +2475,19 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
2220
2475
|
this.destroyed = true;
|
|
2221
2476
|
this.changer.delete();
|
|
2222
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
|
+
}
|
|
2223
2491
|
/**
|
|
2224
2492
|
* Returns the number of frames we can actually process given the
|
|
2225
2493
|
* pre-allocated capacity. If the host requests more frames than the
|
|
@@ -2343,6 +2611,10 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
2343
2611
|
const onMessage = (event) => {
|
|
2344
2612
|
if (isEngineCommandRecord(event.data)) {
|
|
2345
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);
|
|
2346
2618
|
}
|
|
2347
2619
|
};
|
|
2348
2620
|
if (port?.addEventListener) {
|
|
@@ -2421,6 +2693,9 @@ export {
|
|
|
2421
2693
|
createSonareEngineTelemetryRingBuffer,
|
|
2422
2694
|
createSonareMeterRingBuffer,
|
|
2423
2695
|
createSonareSpectrumRingBuffer,
|
|
2696
|
+
decodeFrame,
|
|
2697
|
+
encodeFrameHi,
|
|
2698
|
+
encodeFrameLo,
|
|
2424
2699
|
init,
|
|
2425
2700
|
isInitialized,
|
|
2426
2701
|
popSonareEngineCommandRingBuffer,
|