@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/src/worklet.ts
CHANGED
|
@@ -7,8 +7,9 @@ import type {
|
|
|
7
7
|
EngineParameterInfo,
|
|
8
8
|
EngineTelemetry,
|
|
9
9
|
MixerRealtimeBuffer,
|
|
10
|
+
RealtimeVoiceChangerConfigInput,
|
|
10
11
|
} from './index';
|
|
11
|
-
import { engineCapabilities, Mixer, RealtimeEngine } from './index';
|
|
12
|
+
import { engineCapabilities, Mixer, RealtimeEngine, RealtimeVoiceChanger } from './index';
|
|
12
13
|
import type { AutomationCurve } from './public_types';
|
|
13
14
|
import type { SonareRtModule } from './sonare-rt';
|
|
14
15
|
|
|
@@ -47,6 +48,31 @@ export interface SonareRealtimeEngineWorkletProcessorOptions {
|
|
|
47
48
|
telemetryRingCapacity?: number;
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
export interface SonareRealtimeVoiceChangerWorkletProcessorOptions {
|
|
52
|
+
preset?: RealtimeVoiceChangerConfigInput;
|
|
53
|
+
sampleRate?: number;
|
|
54
|
+
blockSize?: number;
|
|
55
|
+
channelCount?: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SonareRealtimeVoiceChangerSetConfigMessage {
|
|
59
|
+
type: 'setConfig';
|
|
60
|
+
preset: RealtimeVoiceChangerConfigInput;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SonareRealtimeVoiceChangerResetMessage {
|
|
64
|
+
type: 'reset';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface SonareRealtimeVoiceChangerDestroyMessage {
|
|
68
|
+
type: 'destroy';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type SonareRealtimeVoiceChangerMessage =
|
|
72
|
+
| SonareRealtimeVoiceChangerSetConfigMessage
|
|
73
|
+
| SonareRealtimeVoiceChangerResetMessage
|
|
74
|
+
| SonareRealtimeVoiceChangerDestroyMessage;
|
|
75
|
+
|
|
50
76
|
export interface SonareRealtimeEngineNodeCapabilities {
|
|
51
77
|
mode: 'sab' | 'postMessage';
|
|
52
78
|
runtimeTarget: 'embind' | 'sonare-rt';
|
|
@@ -317,6 +343,13 @@ function isEngineCommandRecord(value: unknown): value is SonareEngineCommandReco
|
|
|
317
343
|
return isRecord(value) && typeof value.type === 'number';
|
|
318
344
|
}
|
|
319
345
|
|
|
346
|
+
function isRealtimeVoiceChangerMessage(value: unknown): value is SonareRealtimeVoiceChangerMessage {
|
|
347
|
+
if (!isRecord(value) || typeof value.type !== 'string') {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
return value.type === 'setConfig' || value.type === 'reset' || value.type === 'destroy';
|
|
351
|
+
}
|
|
352
|
+
|
|
320
353
|
function isEngineTelemetryRecord(value: unknown): value is SonareEngineTelemetryRecord {
|
|
321
354
|
return (
|
|
322
355
|
isRecord(value) &&
|
|
@@ -981,6 +1014,7 @@ export class SonareWorkletProcessor {
|
|
|
981
1014
|
* load the dedicated Emscripten AudioWorklet module.
|
|
982
1015
|
*/
|
|
983
1016
|
export class SonareRealtimeEngineWorkletProcessor {
|
|
1017
|
+
private static warnedChannelScratchOverflow = false;
|
|
984
1018
|
readonly sampleRate: number;
|
|
985
1019
|
readonly blockSize: number;
|
|
986
1020
|
readonly channelCount: number;
|
|
@@ -992,6 +1026,14 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
992
1026
|
private transport?: WorkletTransport;
|
|
993
1027
|
private meterIntervalFrames: number;
|
|
994
1028
|
private lastMeterFrame = Number.NEGATIVE_INFINITY;
|
|
1029
|
+
// Pre-allocated worst-case input scratch buffers. The main thread allocates
|
|
1030
|
+
// these in the constructor; process() reuses them via subarray() so it never
|
|
1031
|
+
// touches the V8 heap allocator (which would risk GC stalls on the audio
|
|
1032
|
+
// thread). One scratch buffer per channel sized to blockSize.
|
|
1033
|
+
private readonly channelScratch: Float32Array[];
|
|
1034
|
+
// Reused array of subarray views passed to engine.process() each block.
|
|
1035
|
+
// Pre-allocating this array avoids growing-array allocations from .push().
|
|
1036
|
+
private readonly channelScratchViews: Float32Array[];
|
|
995
1037
|
|
|
996
1038
|
constructor(
|
|
997
1039
|
options: SonareRealtimeEngineWorkletProcessorOptions = {},
|
|
@@ -1018,6 +1060,13 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
1018
1060
|
)
|
|
1019
1061
|
: undefined;
|
|
1020
1062
|
this.engine = new RealtimeEngine(this.sampleRate, this.blockSize);
|
|
1063
|
+
// Worst-case allocation: channelCount full-blockSize Float32Arrays.
|
|
1064
|
+
this.channelScratch = new Array(this.channelCount);
|
|
1065
|
+
this.channelScratchViews = new Array(this.channelCount);
|
|
1066
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1067
|
+
this.channelScratch[ch] = new Float32Array(this.blockSize);
|
|
1068
|
+
this.channelScratchViews[ch] = this.channelScratch[ch];
|
|
1069
|
+
}
|
|
1021
1070
|
}
|
|
1022
1071
|
|
|
1023
1072
|
process(inputs: WorkletInput, outputs: WorkletOutput): boolean {
|
|
@@ -1040,18 +1089,38 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
1040
1089
|
|
|
1041
1090
|
this.drainCommands();
|
|
1042
1091
|
|
|
1043
|
-
|
|
1092
|
+
// Clamp `frames` to the pre-allocated scratch capacity. The earlier
|
|
1093
|
+
// `frames > this.blockSize` branch already returns early, so this is
|
|
1094
|
+
// defensive — but we warn once if it ever fires so the contract violation
|
|
1095
|
+
// is visible.
|
|
1096
|
+
const scratchCapacity = this.channelScratch[0]?.length ?? 0;
|
|
1097
|
+
let usableFrames = frames;
|
|
1098
|
+
if (usableFrames > scratchCapacity) {
|
|
1099
|
+
if (!SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow) {
|
|
1100
|
+
SonareRealtimeEngineWorkletProcessor.warnedChannelScratchOverflow = true;
|
|
1101
|
+
// biome-ignore lint/suspicious/noConsole: realtime-safety diagnostic.
|
|
1102
|
+
console.warn(
|
|
1103
|
+
`SonareRealtimeEngineWorkletProcessor: requested ${usableFrames} frames ` +
|
|
1104
|
+
`exceeds pre-allocated capacity ${scratchCapacity}; clamping.`,
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
usableFrames = scratchCapacity;
|
|
1108
|
+
}
|
|
1044
1109
|
const input = inputs[0];
|
|
1110
|
+
// Reuse the scratch buffers via subarray() — these are views over the
|
|
1111
|
+
// pre-allocated Float32Array storage and do not allocate on the heap.
|
|
1045
1112
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1113
|
+
const scratch = this.channelScratch[ch];
|
|
1046
1114
|
const source = input?.[ch];
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1115
|
+
if (source && source.length === usableFrames) {
|
|
1116
|
+
scratch.set(source, 0);
|
|
1117
|
+
} else {
|
|
1118
|
+
scratch.fill(0, 0, usableFrames);
|
|
1050
1119
|
}
|
|
1051
|
-
|
|
1120
|
+
this.channelScratchViews[ch] = scratch.subarray(0, usableFrames);
|
|
1052
1121
|
}
|
|
1053
1122
|
|
|
1054
|
-
const processed = this.engine.process(
|
|
1123
|
+
const processed = this.engine.process(this.channelScratchViews);
|
|
1055
1124
|
for (let ch = 0; ch < output.length; ch++) {
|
|
1056
1125
|
const target = output[ch];
|
|
1057
1126
|
const source = processed[ch] ?? processed[0];
|
|
@@ -1990,6 +2059,181 @@ export class SonareEngine {
|
|
|
1990
2059
|
}
|
|
1991
2060
|
}
|
|
1992
2061
|
|
|
2062
|
+
export class SonareRealtimeVoiceChangerWorkletProcessor {
|
|
2063
|
+
private static warnedMonoOverflow = false;
|
|
2064
|
+
private static warnedInterleavedOverflow = false;
|
|
2065
|
+
private changer: RealtimeVoiceChanger;
|
|
2066
|
+
private readonly sampleRate: number;
|
|
2067
|
+
private readonly blockSize: number;
|
|
2068
|
+
private readonly channelCount: number;
|
|
2069
|
+
// WASM-heap typed-memory views, sized to the worst case (blockSize *
|
|
2070
|
+
// channelCount). Acquired on the main thread (constructor) so the
|
|
2071
|
+
// audio-thread process() never crosses an allocation boundary.
|
|
2072
|
+
private monoInput: Float32Array;
|
|
2073
|
+
private monoOutput: Float32Array;
|
|
2074
|
+
// Planar heap-backed views (one Float32Array per channel) used by the
|
|
2075
|
+
// multi-channel path. AudioWorklet inputs/outputs are already planar
|
|
2076
|
+
// Float32Arrays, so this avoids the per-sample interleave/deinterleave
|
|
2077
|
+
// passes that the older interleaved path needed.
|
|
2078
|
+
private planarChannels: Float32Array[];
|
|
2079
|
+
private destroyed = false;
|
|
2080
|
+
|
|
2081
|
+
constructor(options: SonareRealtimeVoiceChangerWorkletProcessorOptions = {}) {
|
|
2082
|
+
this.sampleRate = options.sampleRate ?? 48000;
|
|
2083
|
+
this.blockSize = options.blockSize ?? 128;
|
|
2084
|
+
this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 1));
|
|
2085
|
+
this.changer = new RealtimeVoiceChanger(options.preset ?? 'neutral-monitor');
|
|
2086
|
+
this.changer.prepare(this.sampleRate, this.blockSize, this.channelCount);
|
|
2087
|
+
// Acquire WASM-heap views once, sized to the worst case. These are alive
|
|
2088
|
+
// for the lifetime of the changer; if the host requests more frames per
|
|
2089
|
+
// process() than blockSize, we clamp (see ensure*Capacity).
|
|
2090
|
+
this.monoInput = this.changer.getMonoInputBuffer(this.blockSize);
|
|
2091
|
+
this.monoOutput = this.changer.getMonoOutputBuffer(this.blockSize);
|
|
2092
|
+
this.planarChannels = [];
|
|
2093
|
+
if (this.channelCount > 1) {
|
|
2094
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
2095
|
+
this.planarChannels.push(this.changer.getPlanarChannelBuffer(ch, this.blockSize));
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
/**
|
|
2101
|
+
* Handles a control-plane message from the main thread. Runs on the
|
|
2102
|
+
* AudioWorklet global scope but OUTSIDE of `process()` (i.e. outside the
|
|
2103
|
+
* realtime audio callback), so it is safe to perform JSON parsing and
|
|
2104
|
+
* DSP coefficient recomputation here. `setConfig` MUST NOT be deferred
|
|
2105
|
+
* into `process()` because that would block the audio thread for longer
|
|
2106
|
+
* than one render quantum (e.g. 128 samples / 44.1 kHz = ~2.9 ms).
|
|
2107
|
+
*/
|
|
2108
|
+
receiveMessage(message: SonareRealtimeVoiceChangerMessage): void {
|
|
2109
|
+
if (this.destroyed) {
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
if (message.type === 'setConfig') {
|
|
2113
|
+
// Apply synchronously on the message-handler thread. `setConfig` may
|
|
2114
|
+
// allocate and parse JSON internally; doing it here keeps `process()`
|
|
2115
|
+
// realtime-safe.
|
|
2116
|
+
this.changer.setConfig(message.preset);
|
|
2117
|
+
} else if (message.type === 'reset') {
|
|
2118
|
+
this.changer.reset();
|
|
2119
|
+
} else if (message.type === 'destroy') {
|
|
2120
|
+
this.destroy();
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
process(inputs: WorkletInput, outputs: WorkletOutput): boolean {
|
|
2125
|
+
const output = outputs[0];
|
|
2126
|
+
if (this.destroyed || !output || output.length === 0) {
|
|
2127
|
+
return !this.destroyed;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
const input = inputs[0];
|
|
2131
|
+
const requestedFrames = output[0]?.length ?? 0;
|
|
2132
|
+
const requestedChannels = Math.min(this.channelCount, output.length);
|
|
2133
|
+
if (requestedFrames === 0 || requestedChannels === 0) {
|
|
2134
|
+
return true;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
if (requestedChannels === 1) {
|
|
2138
|
+
// Clamp to the pre-allocated capacity; warn (once) if the host violated
|
|
2139
|
+
// the contract. We never reallocate on the audio thread.
|
|
2140
|
+
const frames = this.ensureMonoCapacity(requestedFrames);
|
|
2141
|
+
const source = input?.[0];
|
|
2142
|
+
if (source) {
|
|
2143
|
+
this.monoInput.set(source.subarray(0, frames));
|
|
2144
|
+
} else {
|
|
2145
|
+
this.monoInput.fill(0, 0, frames);
|
|
2146
|
+
}
|
|
2147
|
+
this.changer.processMonoInto(
|
|
2148
|
+
this.monoInput.subarray(0, frames),
|
|
2149
|
+
this.monoOutput.subarray(0, frames),
|
|
2150
|
+
);
|
|
2151
|
+
output[0].set(this.monoOutput.subarray(0, frames));
|
|
2152
|
+
return true;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
const frames = this.ensureInterleavedCapacity(requestedFrames, requestedChannels);
|
|
2156
|
+
const channels = requestedChannels;
|
|
2157
|
+
// Planar zero-copy path: AudioWorklet's input[ch] is already a
|
|
2158
|
+
// Float32Array per channel, so we set() straight into the heap-backed
|
|
2159
|
+
// planar view and processPreparedPlanar runs in place.
|
|
2160
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
2161
|
+
const src = input?.[ch];
|
|
2162
|
+
const dst = this.planarChannels[ch];
|
|
2163
|
+
if (!dst) {
|
|
2164
|
+
continue;
|
|
2165
|
+
}
|
|
2166
|
+
if (src) {
|
|
2167
|
+
dst.set(src.subarray(0, frames));
|
|
2168
|
+
} else {
|
|
2169
|
+
dst.fill(0, 0, frames);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
this.changer.processPreparedPlanar(frames);
|
|
2173
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
2174
|
+
const src = this.planarChannels[ch];
|
|
2175
|
+
if (src) {
|
|
2176
|
+
output[ch].set(src.subarray(0, frames));
|
|
2177
|
+
}
|
|
2178
|
+
// No `for frame` inner loop needed; output[ch] is a Float32Array.
|
|
2179
|
+
}
|
|
2180
|
+
return true;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
destroy(): void {
|
|
2184
|
+
if (this.destroyed) {
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
2187
|
+
this.destroyed = true;
|
|
2188
|
+
this.changer.delete();
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
/**
|
|
2192
|
+
* Returns the number of frames we can actually process given the
|
|
2193
|
+
* pre-allocated capacity. If the host requests more frames than the
|
|
2194
|
+
* worst-case block size declared at construction time, we clamp to the
|
|
2195
|
+
* available capacity and warn once — we MUST NOT reallocate on the
|
|
2196
|
+
* realtime audio thread.
|
|
2197
|
+
*/
|
|
2198
|
+
private ensureMonoCapacity(frames: number): number {
|
|
2199
|
+
const capacity = this.monoInput.length;
|
|
2200
|
+
if (frames <= capacity) {
|
|
2201
|
+
return frames;
|
|
2202
|
+
}
|
|
2203
|
+
if (!SonareRealtimeVoiceChangerWorkletProcessor.warnedMonoOverflow) {
|
|
2204
|
+
SonareRealtimeVoiceChangerWorkletProcessor.warnedMonoOverflow = true;
|
|
2205
|
+
// biome-ignore lint/suspicious/noConsole: realtime-safety diagnostic.
|
|
2206
|
+
console.warn(
|
|
2207
|
+
`SonareRealtimeVoiceChangerWorkletProcessor: requested ${frames} mono frames ` +
|
|
2208
|
+
`exceeds pre-allocated capacity ${capacity}; clamping. ` +
|
|
2209
|
+
'Increase blockSize at construction time to avoid this.',
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
return capacity;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
* Same contract as ensureMonoCapacity but for the planar per-channel
|
|
2217
|
+
* scratch. Returns the number of frames that fit in the available capacity.
|
|
2218
|
+
*/
|
|
2219
|
+
private ensureInterleavedCapacity(frames: number, channels: number): number {
|
|
2220
|
+
const capacity = this.planarChannels[0]?.length ?? 0;
|
|
2221
|
+
if (frames <= capacity) {
|
|
2222
|
+
return frames;
|
|
2223
|
+
}
|
|
2224
|
+
if (!SonareRealtimeVoiceChangerWorkletProcessor.warnedInterleavedOverflow) {
|
|
2225
|
+
SonareRealtimeVoiceChangerWorkletProcessor.warnedInterleavedOverflow = true;
|
|
2226
|
+
// biome-ignore lint/suspicious/noConsole: realtime-safety diagnostic.
|
|
2227
|
+
console.warn(
|
|
2228
|
+
`SonareRealtimeVoiceChangerWorkletProcessor: requested ${frames}x${channels} ` +
|
|
2229
|
+
`planar frames exceeds pre-allocated capacity ${capacity}; clamping. ` +
|
|
2230
|
+
'Increase blockSize or channelCount at construction time to avoid this.',
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
return capacity;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
|
|
1993
2237
|
export function registerSonareWorkletProcessor(name = 'sonare-worklet-processor'): void {
|
|
1994
2238
|
const scope = globalThis as unknown as {
|
|
1995
2239
|
AudioWorkletProcessor?: new () => object;
|
|
@@ -2029,6 +2273,47 @@ export function registerSonareWorkletProcessor(name = 'sonare-worklet-processor'
|
|
|
2029
2273
|
scope.registerProcessor(name, RegisteredSonareWorkletProcessor);
|
|
2030
2274
|
}
|
|
2031
2275
|
|
|
2276
|
+
export function registerSonareRealtimeVoiceChangerWorkletProcessor(
|
|
2277
|
+
name = 'sonare-realtime-voice-changer-processor',
|
|
2278
|
+
): void {
|
|
2279
|
+
const scope = globalThis as unknown as {
|
|
2280
|
+
AudioWorkletProcessor?: new () => object;
|
|
2281
|
+
registerProcessor?: (processorName: string, processorCtor: unknown) => void;
|
|
2282
|
+
};
|
|
2283
|
+
if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
|
|
2284
|
+
throw new Error('AudioWorkletProcessor is not available in this context.');
|
|
2285
|
+
}
|
|
2286
|
+
const Base = scope.AudioWorkletProcessor;
|
|
2287
|
+
class RegisteredSonareRealtimeVoiceChangerWorkletProcessor extends Base {
|
|
2288
|
+
private bridge: SonareRealtimeVoiceChangerWorkletProcessor;
|
|
2289
|
+
readonly port?: WorkletPort;
|
|
2290
|
+
|
|
2291
|
+
constructor(options?: {
|
|
2292
|
+
processorOptions?: SonareRealtimeVoiceChangerWorkletProcessorOptions;
|
|
2293
|
+
}) {
|
|
2294
|
+
super();
|
|
2295
|
+
const port = this.port;
|
|
2296
|
+
this.bridge = new SonareRealtimeVoiceChangerWorkletProcessor(options?.processorOptions ?? {});
|
|
2297
|
+
const onMessage = (event: { data: unknown }) => {
|
|
2298
|
+
if (isRealtimeVoiceChangerMessage(event.data)) {
|
|
2299
|
+
this.bridge.receiveMessage(event.data);
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
if (port?.addEventListener) {
|
|
2303
|
+
port.addEventListener('message', onMessage);
|
|
2304
|
+
port.start?.();
|
|
2305
|
+
} else if (port) {
|
|
2306
|
+
port.onmessage = onMessage;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
process(inputs: WorkletInput, outputs: WorkletOutput): boolean {
|
|
2311
|
+
return this.bridge.process(inputs, outputs);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
scope.registerProcessor(name, RegisteredSonareRealtimeVoiceChangerWorkletProcessor);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2032
2317
|
export function registerSonareRealtimeEngineWorkletProcessor(
|
|
2033
2318
|
name = 'sonare-realtime-engine-processor',
|
|
2034
2319
|
): void {
|
|
@@ -2092,6 +2377,7 @@ export function registerSonareRealtimeEngineWorkletProcessor(
|
|
|
2092
2377
|
if (!options.rtModuleUrl) {
|
|
2093
2378
|
throw new Error('rtModuleUrl is required for sonare-rt AudioWorklet runtime.');
|
|
2094
2379
|
}
|
|
2380
|
+
const rtModuleUrl = options.rtModuleUrl;
|
|
2095
2381
|
const memory = new WebAssembly.Memory({ initial: 1024, maximum: 1024, shared: true });
|
|
2096
2382
|
const globalFactory = (
|
|
2097
2383
|
globalThis as typeof globalThis & {
|
|
@@ -2104,7 +2390,7 @@ export function registerSonareRealtimeEngineWorkletProcessor(
|
|
|
2104
2390
|
).SonareRtModuleFactory;
|
|
2105
2391
|
const moduleFactory = globalFactory
|
|
2106
2392
|
? { default: globalFactory }
|
|
2107
|
-
: ((await import(
|
|
2393
|
+
: ((await import(rtModuleUrl)) as {
|
|
2108
2394
|
default: (options?: {
|
|
2109
2395
|
wasmMemory?: WebAssembly.Memory;
|
|
2110
2396
|
wasmBinary?: ArrayBuffer | Uint8Array;
|
|
@@ -2114,7 +2400,7 @@ export function registerSonareRealtimeEngineWorkletProcessor(
|
|
|
2114
2400
|
const module = await moduleFactory.default({
|
|
2115
2401
|
wasmMemory: memory,
|
|
2116
2402
|
wasmBinary: options.rtWasmBinary,
|
|
2117
|
-
locateFile: (path) =>
|
|
2403
|
+
locateFile: (path) => rtModuleUrl.replace(/[^/]*$/, path),
|
|
2118
2404
|
});
|
|
2119
2405
|
this.rtBridge = new SonareRtRealtimeEngineRuntime({
|
|
2120
2406
|
module,
|