@libraz/libsonare 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -4
- package/dist/index.d.ts +728 -284
- package/dist/index.js +712 -122
- 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 +61 -2
- package/dist/worklet.js +371 -9
- package/dist/worklet.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +1666 -305
- package/src/public_types.ts +71 -0
- package/src/sonare.js.d.ts +508 -78
- package/src/worklet.ts +295 -9
- package/src/wasm_types.ts +0 -1248
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
|
}
|
|
@@ -236,6 +251,141 @@ var RealtimeEngine = class {
|
|
|
236
251
|
this.native.delete();
|
|
237
252
|
}
|
|
238
253
|
};
|
|
254
|
+
var RealtimeVoiceChanger = class {
|
|
255
|
+
constructor(config = "neutral-monitor") {
|
|
256
|
+
if (!module) {
|
|
257
|
+
throw new Error("Module not initialized. Call init() first.");
|
|
258
|
+
}
|
|
259
|
+
this.changer = module.createRealtimeVoiceChanger(config);
|
|
260
|
+
}
|
|
261
|
+
prepare(sampleRate, maxBlockSize = 128, channels = 1) {
|
|
262
|
+
this.changer.prepare(sampleRate, maxBlockSize, channels);
|
|
263
|
+
}
|
|
264
|
+
reset() {
|
|
265
|
+
this.changer.reset();
|
|
266
|
+
}
|
|
267
|
+
setConfig(config) {
|
|
268
|
+
this.changer.setConfig(config);
|
|
269
|
+
}
|
|
270
|
+
configJson() {
|
|
271
|
+
return this.changer.configJson();
|
|
272
|
+
}
|
|
273
|
+
latencySamples() {
|
|
274
|
+
return this.changer.latencySamples();
|
|
275
|
+
}
|
|
276
|
+
processMono(samples) {
|
|
277
|
+
return this.changer.processMono(samples);
|
|
278
|
+
}
|
|
279
|
+
processMonoInto(samples, output) {
|
|
280
|
+
this.changer.processMonoInto(samples, output);
|
|
281
|
+
}
|
|
282
|
+
processInterleaved(samples, channels) {
|
|
283
|
+
return this.changer.processInterleaved(samples, channels);
|
|
284
|
+
}
|
|
285
|
+
processInterleavedInto(samples, channels, output) {
|
|
286
|
+
this.changer.processInterleavedInto(samples, channels, output);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Acquire a typed-memory view onto the WASM heap for mono input.
|
|
290
|
+
*
|
|
291
|
+
* Write your input samples into the returned `Float32Array` directly (e.g.
|
|
292
|
+
* via `input.set(source)`); no copy crosses the JS↔C++ bridge until
|
|
293
|
+
* {@link processPreparedMono} is called. The view is owned by this
|
|
294
|
+
* RealtimeVoiceChanger and becomes invalid after {@link delete}; it may
|
|
295
|
+
* also be invalidated if you later call this method with a larger
|
|
296
|
+
* `numSamples` value (the underlying buffer may be reallocated).
|
|
297
|
+
*/
|
|
298
|
+
getMonoInputBuffer(numSamples) {
|
|
299
|
+
return this.changer.getMonoInputBuffer(numSamples);
|
|
300
|
+
}
|
|
301
|
+
/** Mono output view counterpart to {@link getMonoInputBuffer}. */
|
|
302
|
+
getMonoOutputBuffer(numSamples) {
|
|
303
|
+
return this.changer.getMonoOutputBuffer(numSamples);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Process the previously-acquired mono input buffer in place. The output
|
|
307
|
+
* appears in the buffer returned by {@link getMonoOutputBuffer}. No JS↔C++
|
|
308
|
+
* sample-level crossings happen on this call — it just hands control to
|
|
309
|
+
* the underlying DSP on already-on-heap data.
|
|
310
|
+
*/
|
|
311
|
+
processPreparedMono(numSamples) {
|
|
312
|
+
this.changer.processPreparedMono(numSamples);
|
|
313
|
+
}
|
|
314
|
+
/** Interleaved input view (layout L0,R0,L1,R1,...). */
|
|
315
|
+
getInterleavedInputBuffer(numFrames, numChannels) {
|
|
316
|
+
return this.changer.getInterleavedInputBuffer(numFrames, numChannels);
|
|
317
|
+
}
|
|
318
|
+
/** Interleaved output view counterpart. */
|
|
319
|
+
getInterleavedOutputBuffer(numFrames, numChannels) {
|
|
320
|
+
return this.changer.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Process the previously-acquired interleaved buffer in place. Output
|
|
324
|
+
* appears in the buffer returned by {@link getInterleavedOutputBuffer}.
|
|
325
|
+
*/
|
|
326
|
+
processPreparedInterleaved(numFrames, numChannels) {
|
|
327
|
+
this.changer.processPreparedInterleaved(numFrames, numChannels);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Planar-channel input/output view (one Float32Array per channel). Matches
|
|
331
|
+
* AudioWorklet's native layout; processing happens in place.
|
|
332
|
+
*/
|
|
333
|
+
getPlanarChannelBuffer(channel, numFrames) {
|
|
334
|
+
return this.changer.getPlanarChannelBuffer(channel, numFrames);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Process the previously-acquired planar channel buffers in place. Each
|
|
338
|
+
* channel must have been obtained from {@link getPlanarChannelBuffer}
|
|
339
|
+
* with the same `numFrames`. Output replaces input in the same buffers.
|
|
340
|
+
*/
|
|
341
|
+
processPreparedPlanar(numFrames) {
|
|
342
|
+
this.changer.processPreparedPlanar(numFrames);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Convenience factory for the mono zero-copy path: returns the input/output
|
|
346
|
+
* heap views plus a `process()` thunk wired to the same `numSamples`. The
|
|
347
|
+
* views are reused across calls and become invalid after {@link delete}.
|
|
348
|
+
*/
|
|
349
|
+
createRealtimeMonoBuffer(numSamples) {
|
|
350
|
+
const input = this.getMonoInputBuffer(numSamples);
|
|
351
|
+
const output = this.getMonoOutputBuffer(numSamples);
|
|
352
|
+
return {
|
|
353
|
+
input,
|
|
354
|
+
output,
|
|
355
|
+
process: () => this.processPreparedMono(numSamples)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** Same as {@link createRealtimeMonoBuffer} but for interleaved I/O. */
|
|
359
|
+
createRealtimeInterleavedBuffer(numFrames, numChannels) {
|
|
360
|
+
const input = this.getInterleavedInputBuffer(numFrames, numChannels);
|
|
361
|
+
const output = this.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
362
|
+
return {
|
|
363
|
+
input,
|
|
364
|
+
output,
|
|
365
|
+
channels: numChannels,
|
|
366
|
+
process: () => this.processPreparedInterleaved(numFrames, numChannels)
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Convenience factory for the planar zero-copy path. Acquires one
|
|
371
|
+
* heap-backed Float32Array per channel and returns a `process()` thunk
|
|
372
|
+
* wired to the same `numFrames`. Buffers are reused across calls and
|
|
373
|
+
* become invalid after {@link delete}.
|
|
374
|
+
*/
|
|
375
|
+
createRealtimePlanarBuffer(numFrames, numChannels) {
|
|
376
|
+
const channels = [];
|
|
377
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
378
|
+
channels.push(this.getPlanarChannelBuffer(ch, numFrames));
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
channels,
|
|
382
|
+
process: () => this.processPreparedPlanar(numFrames)
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
delete() {
|
|
386
|
+
this.changer.delete();
|
|
387
|
+
}
|
|
388
|
+
};
|
|
239
389
|
var Mixer = class _Mixer {
|
|
240
390
|
constructor(mixer) {
|
|
241
391
|
this.mixer = mixer;
|
|
@@ -379,6 +529,26 @@ var Mixer = class _Mixer {
|
|
|
379
529
|
vcaGroupCount() {
|
|
380
530
|
return this.mixer.vcaGroupCount();
|
|
381
531
|
}
|
|
532
|
+
/** Set the strip's input trim in dB. */
|
|
533
|
+
setInputTrimDb(stripIndex, db) {
|
|
534
|
+
this.mixer.setInputTrimDb(stripIndex, db);
|
|
535
|
+
}
|
|
536
|
+
/** Set the strip's fader level in dB. */
|
|
537
|
+
setFaderDb(stripIndex, db) {
|
|
538
|
+
this.mixer.setFaderDb(stripIndex, db);
|
|
539
|
+
}
|
|
540
|
+
/** Set the strip's pan position. */
|
|
541
|
+
setPan(stripIndex, pan, panMode = 0) {
|
|
542
|
+
this.mixer.setPan(stripIndex, pan, panModeCode(panMode));
|
|
543
|
+
}
|
|
544
|
+
/** Set the strip's stereo width. */
|
|
545
|
+
setWidth(stripIndex, width) {
|
|
546
|
+
this.mixer.setWidth(stripIndex, width);
|
|
547
|
+
}
|
|
548
|
+
/** Set the strip's mute state. */
|
|
549
|
+
setMuted(stripIndex, muted) {
|
|
550
|
+
this.mixer.setMuted(stripIndex, muted);
|
|
551
|
+
}
|
|
382
552
|
/**
|
|
383
553
|
* Set a strip's solo state. Takes effect on the next process without a
|
|
384
554
|
* graph recompile.
|
|
@@ -589,6 +759,12 @@ function isWorkletMessage(value) {
|
|
|
589
759
|
function isEngineCommandRecord(value) {
|
|
590
760
|
return isRecord(value) && typeof value.type === "number";
|
|
591
761
|
}
|
|
762
|
+
function isRealtimeVoiceChangerMessage(value) {
|
|
763
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
return value.type === "setConfig" || value.type === "reset" || value.type === "destroy";
|
|
767
|
+
}
|
|
592
768
|
function isEngineTelemetryRecord(value) {
|
|
593
769
|
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
770
|
}
|
|
@@ -1111,7 +1287,7 @@ var SonareWorkletProcessor = class {
|
|
|
1111
1287
|
}
|
|
1112
1288
|
}
|
|
1113
1289
|
};
|
|
1114
|
-
var
|
|
1290
|
+
var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletProcessor {
|
|
1115
1291
|
constructor(options = {}, transport) {
|
|
1116
1292
|
this.closed = false;
|
|
1117
1293
|
this.lastMeterFrame = Number.NEGATIVE_INFINITY;
|
|
@@ -1132,6 +1308,12 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1132
1308
|
options.telemetryRingCapacity
|
|
1133
1309
|
) : void 0;
|
|
1134
1310
|
this.engine = new RealtimeEngine(this.sampleRate, this.blockSize);
|
|
1311
|
+
this.channelScratch = new Array(this.channelCount);
|
|
1312
|
+
this.channelScratchViews = new Array(this.channelCount);
|
|
1313
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1314
|
+
this.channelScratch[ch] = new Float32Array(this.blockSize);
|
|
1315
|
+
this.channelScratchViews[ch] = this.channelScratch[ch];
|
|
1316
|
+
}
|
|
1135
1317
|
}
|
|
1136
1318
|
process(inputs, outputs) {
|
|
1137
1319
|
if (this.closed) {
|
|
@@ -1151,17 +1333,29 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1151
1333
|
return true;
|
|
1152
1334
|
}
|
|
1153
1335
|
this.drainCommands();
|
|
1154
|
-
const
|
|
1336
|
+
const scratchCapacity = this.channelScratch[0]?.length ?? 0;
|
|
1337
|
+
let usableFrames = frames;
|
|
1338
|
+
if (usableFrames > scratchCapacity) {
|
|
1339
|
+
if (!_SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow) {
|
|
1340
|
+
_SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow = true;
|
|
1341
|
+
console.warn(
|
|
1342
|
+
`SonareRealtimeEngineWorkletProcessor: requested ${usableFrames} frames exceeds pre-allocated capacity ${scratchCapacity}; clamping.`
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
usableFrames = scratchCapacity;
|
|
1346
|
+
}
|
|
1155
1347
|
const input = inputs[0];
|
|
1156
1348
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1349
|
+
const scratch = this.channelScratch[ch];
|
|
1157
1350
|
const source = input?.[ch];
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1351
|
+
if (source && source.length === usableFrames) {
|
|
1352
|
+
scratch.set(source, 0);
|
|
1353
|
+
} else {
|
|
1354
|
+
scratch.fill(0, 0, usableFrames);
|
|
1161
1355
|
}
|
|
1162
|
-
|
|
1356
|
+
this.channelScratchViews[ch] = scratch.subarray(0, usableFrames);
|
|
1163
1357
|
}
|
|
1164
|
-
const processed = this.engine.process(
|
|
1358
|
+
const processed = this.engine.process(this.channelScratchViews);
|
|
1165
1359
|
for (let ch = 0; ch < output.length; ch++) {
|
|
1166
1360
|
const target = output[ch];
|
|
1167
1361
|
const source = processed[ch] ?? processed[0];
|
|
@@ -1297,6 +1491,8 @@ var SonareRealtimeEngineWorkletProcessor = class {
|
|
|
1297
1491
|
return { sharedBuffer, header: ring.header, view: ring.view, capacity: ring.capacity };
|
|
1298
1492
|
}
|
|
1299
1493
|
};
|
|
1494
|
+
_SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow = false;
|
|
1495
|
+
var SonareRealtimeEngineWorkletProcessor = _SonareRealtimeEngineWorkletProcessor;
|
|
1300
1496
|
var SonareRtRealtimeEngineRuntime = class {
|
|
1301
1497
|
constructor(options) {
|
|
1302
1498
|
this.closed = false;
|
|
@@ -1931,6 +2127,140 @@ var SonareEngine = class _SonareEngine {
|
|
|
1931
2127
|
return Math.max(0, Math.round(ppq * 60 / 120 * this.sampleRate));
|
|
1932
2128
|
}
|
|
1933
2129
|
};
|
|
2130
|
+
var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChangerWorkletProcessor {
|
|
2131
|
+
constructor(options = {}) {
|
|
2132
|
+
this.destroyed = false;
|
|
2133
|
+
this.sampleRate = options.sampleRate ?? 48e3;
|
|
2134
|
+
this.blockSize = options.blockSize ?? 128;
|
|
2135
|
+
this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 1));
|
|
2136
|
+
this.changer = new RealtimeVoiceChanger(options.preset ?? "neutral-monitor");
|
|
2137
|
+
this.changer.prepare(this.sampleRate, this.blockSize, this.channelCount);
|
|
2138
|
+
this.monoInput = this.changer.getMonoInputBuffer(this.blockSize);
|
|
2139
|
+
this.monoOutput = this.changer.getMonoOutputBuffer(this.blockSize);
|
|
2140
|
+
this.planarChannels = [];
|
|
2141
|
+
if (this.channelCount > 1) {
|
|
2142
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
2143
|
+
this.planarChannels.push(this.changer.getPlanarChannelBuffer(ch, this.blockSize));
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Handles a control-plane message from the main thread. Runs on the
|
|
2149
|
+
* AudioWorklet global scope but OUTSIDE of `process()` (i.e. outside the
|
|
2150
|
+
* realtime audio callback), so it is safe to perform JSON parsing and
|
|
2151
|
+
* DSP coefficient recomputation here. `setConfig` MUST NOT be deferred
|
|
2152
|
+
* into `process()` because that would block the audio thread for longer
|
|
2153
|
+
* than one render quantum (e.g. 128 samples / 44.1 kHz = ~2.9 ms).
|
|
2154
|
+
*/
|
|
2155
|
+
receiveMessage(message) {
|
|
2156
|
+
if (this.destroyed) {
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
if (message.type === "setConfig") {
|
|
2160
|
+
this.changer.setConfig(message.preset);
|
|
2161
|
+
} else if (message.type === "reset") {
|
|
2162
|
+
this.changer.reset();
|
|
2163
|
+
} else if (message.type === "destroy") {
|
|
2164
|
+
this.destroy();
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
process(inputs, outputs) {
|
|
2168
|
+
const output = outputs[0];
|
|
2169
|
+
if (this.destroyed || !output || output.length === 0) {
|
|
2170
|
+
return !this.destroyed;
|
|
2171
|
+
}
|
|
2172
|
+
const input = inputs[0];
|
|
2173
|
+
const requestedFrames = output[0]?.length ?? 0;
|
|
2174
|
+
const requestedChannels = Math.min(this.channelCount, output.length);
|
|
2175
|
+
if (requestedFrames === 0 || requestedChannels === 0) {
|
|
2176
|
+
return true;
|
|
2177
|
+
}
|
|
2178
|
+
if (requestedChannels === 1) {
|
|
2179
|
+
const frames2 = this.ensureMonoCapacity(requestedFrames);
|
|
2180
|
+
const source = input?.[0];
|
|
2181
|
+
if (source) {
|
|
2182
|
+
this.monoInput.set(source.subarray(0, frames2));
|
|
2183
|
+
} else {
|
|
2184
|
+
this.monoInput.fill(0, 0, frames2);
|
|
2185
|
+
}
|
|
2186
|
+
this.changer.processMonoInto(
|
|
2187
|
+
this.monoInput.subarray(0, frames2),
|
|
2188
|
+
this.monoOutput.subarray(0, frames2)
|
|
2189
|
+
);
|
|
2190
|
+
output[0].set(this.monoOutput.subarray(0, frames2));
|
|
2191
|
+
return true;
|
|
2192
|
+
}
|
|
2193
|
+
const frames = this.ensureInterleavedCapacity(requestedFrames, requestedChannels);
|
|
2194
|
+
const channels = requestedChannels;
|
|
2195
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
2196
|
+
const src = input?.[ch];
|
|
2197
|
+
const dst = this.planarChannels[ch];
|
|
2198
|
+
if (!dst) {
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
if (src) {
|
|
2202
|
+
dst.set(src.subarray(0, frames));
|
|
2203
|
+
} else {
|
|
2204
|
+
dst.fill(0, 0, frames);
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
this.changer.processPreparedPlanar(frames);
|
|
2208
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
2209
|
+
const src = this.planarChannels[ch];
|
|
2210
|
+
if (src) {
|
|
2211
|
+
output[ch].set(src.subarray(0, frames));
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
return true;
|
|
2215
|
+
}
|
|
2216
|
+
destroy() {
|
|
2217
|
+
if (this.destroyed) {
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
this.destroyed = true;
|
|
2221
|
+
this.changer.delete();
|
|
2222
|
+
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Returns the number of frames we can actually process given the
|
|
2225
|
+
* pre-allocated capacity. If the host requests more frames than the
|
|
2226
|
+
* worst-case block size declared at construction time, we clamp to the
|
|
2227
|
+
* available capacity and warn once — we MUST NOT reallocate on the
|
|
2228
|
+
* realtime audio thread.
|
|
2229
|
+
*/
|
|
2230
|
+
ensureMonoCapacity(frames) {
|
|
2231
|
+
const capacity = this.monoInput.length;
|
|
2232
|
+
if (frames <= capacity) {
|
|
2233
|
+
return frames;
|
|
2234
|
+
}
|
|
2235
|
+
if (!_SonareRealtimeVoiceChangerWorkletProcessor.warnedMonoOverflow) {
|
|
2236
|
+
_SonareRealtimeVoiceChangerWorkletProcessor.warnedMonoOverflow = true;
|
|
2237
|
+
console.warn(
|
|
2238
|
+
`SonareRealtimeVoiceChangerWorkletProcessor: requested ${frames} mono frames exceeds pre-allocated capacity ${capacity}; clamping. Increase blockSize at construction time to avoid this.`
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
return capacity;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Same contract as ensureMonoCapacity but for the planar per-channel
|
|
2245
|
+
* scratch. Returns the number of frames that fit in the available capacity.
|
|
2246
|
+
*/
|
|
2247
|
+
ensureInterleavedCapacity(frames, channels) {
|
|
2248
|
+
const capacity = this.planarChannels[0]?.length ?? 0;
|
|
2249
|
+
if (frames <= capacity) {
|
|
2250
|
+
return frames;
|
|
2251
|
+
}
|
|
2252
|
+
if (!_SonareRealtimeVoiceChangerWorkletProcessor.warnedInterleavedOverflow) {
|
|
2253
|
+
_SonareRealtimeVoiceChangerWorkletProcessor.warnedInterleavedOverflow = true;
|
|
2254
|
+
console.warn(
|
|
2255
|
+
`SonareRealtimeVoiceChangerWorkletProcessor: requested ${frames}x${channels} planar frames exceeds pre-allocated capacity ${capacity}; clamping. Increase blockSize or channelCount at construction time to avoid this.`
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
return capacity;
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
_SonareRealtimeVoiceChangerWorkletProcessor.warnedMonoOverflow = false;
|
|
2262
|
+
_SonareRealtimeVoiceChangerWorkletProcessor.warnedInterleavedOverflow = false;
|
|
2263
|
+
var SonareRealtimeVoiceChangerWorkletProcessor = _SonareRealtimeVoiceChangerWorkletProcessor;
|
|
1934
2264
|
function registerSonareWorkletProcessor(name = "sonare-worklet-processor") {
|
|
1935
2265
|
const scope = globalThis;
|
|
1936
2266
|
if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
|
|
@@ -1962,6 +2292,35 @@ function registerSonareWorkletProcessor(name = "sonare-worklet-processor") {
|
|
|
1962
2292
|
}
|
|
1963
2293
|
scope.registerProcessor(name, RegisteredSonareWorkletProcessor);
|
|
1964
2294
|
}
|
|
2295
|
+
function registerSonareRealtimeVoiceChangerWorkletProcessor(name = "sonare-realtime-voice-changer-processor") {
|
|
2296
|
+
const scope = globalThis;
|
|
2297
|
+
if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
|
|
2298
|
+
throw new Error("AudioWorkletProcessor is not available in this context.");
|
|
2299
|
+
}
|
|
2300
|
+
const Base = scope.AudioWorkletProcessor;
|
|
2301
|
+
class RegisteredSonareRealtimeVoiceChangerWorkletProcessor extends Base {
|
|
2302
|
+
constructor(options) {
|
|
2303
|
+
super();
|
|
2304
|
+
const port = this.port;
|
|
2305
|
+
this.bridge = new SonareRealtimeVoiceChangerWorkletProcessor(options?.processorOptions ?? {});
|
|
2306
|
+
const onMessage = (event) => {
|
|
2307
|
+
if (isRealtimeVoiceChangerMessage(event.data)) {
|
|
2308
|
+
this.bridge.receiveMessage(event.data);
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
if (port?.addEventListener) {
|
|
2312
|
+
port.addEventListener("message", onMessage);
|
|
2313
|
+
port.start?.();
|
|
2314
|
+
} else if (port) {
|
|
2315
|
+
port.onmessage = onMessage;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
process(inputs, outputs) {
|
|
2319
|
+
return this.bridge.process(inputs, outputs);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
scope.registerProcessor(name, RegisteredSonareRealtimeVoiceChangerWorkletProcessor);
|
|
2323
|
+
}
|
|
1965
2324
|
function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-engine-processor") {
|
|
1966
2325
|
const scope = globalThis;
|
|
1967
2326
|
if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
|
|
@@ -2011,13 +2370,14 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
2011
2370
|
if (!options.rtModuleUrl) {
|
|
2012
2371
|
throw new Error("rtModuleUrl is required for sonare-rt AudioWorklet runtime.");
|
|
2013
2372
|
}
|
|
2373
|
+
const rtModuleUrl = options.rtModuleUrl;
|
|
2014
2374
|
const memory = new WebAssembly.Memory({ initial: 1024, maximum: 1024, shared: true });
|
|
2015
2375
|
const globalFactory = globalThis.SonareRtModuleFactory;
|
|
2016
|
-
const moduleFactory = globalFactory ? { default: globalFactory } : await import(
|
|
2376
|
+
const moduleFactory = globalFactory ? { default: globalFactory } : await import(rtModuleUrl);
|
|
2017
2377
|
const module2 = await moduleFactory.default({
|
|
2018
2378
|
wasmMemory: memory,
|
|
2019
2379
|
wasmBinary: options.rtWasmBinary,
|
|
2020
|
-
locateFile: (path) =>
|
|
2380
|
+
locateFile: (path) => rtModuleUrl.replace(/[^/]*$/, path)
|
|
2021
2381
|
});
|
|
2022
2382
|
this.rtBridge = new SonareRtRealtimeEngineRuntime({
|
|
2023
2383
|
module: module2,
|
|
@@ -2054,6 +2414,7 @@ export {
|
|
|
2054
2414
|
SonareEngineTelemetryType,
|
|
2055
2415
|
SonareRealtimeEngineNode,
|
|
2056
2416
|
SonareRealtimeEngineWorkletProcessor,
|
|
2417
|
+
SonareRealtimeVoiceChangerWorkletProcessor,
|
|
2057
2418
|
SonareRtRealtimeEngineRuntime,
|
|
2058
2419
|
SonareWorkletProcessor,
|
|
2059
2420
|
createSonareEngineCommandRingBuffer,
|
|
@@ -2068,6 +2429,7 @@ export {
|
|
|
2068
2429
|
readSonareMeterRingBuffer,
|
|
2069
2430
|
readSonareSpectrumRingBuffer,
|
|
2070
2431
|
registerSonareRealtimeEngineWorkletProcessor,
|
|
2432
|
+
registerSonareRealtimeVoiceChangerWorkletProcessor,
|
|
2071
2433
|
registerSonareWorkletProcessor,
|
|
2072
2434
|
sonareEngineCommandRingBufferByteLength,
|
|
2073
2435
|
sonareEngineTelemetryRingBufferByteLength,
|