fix(modular): register worklet via concatenated blob url
The globalThis.FaustWorkletProcessor attachment on the base class isn't enough in practice: Chrome's AudioWorkletGlobalScope isolates top-level class declarations between separate addModule() calls, so a subclass script loaded via a second addModule() still throws ReferenceError on its 'extends FaustWorkletProcessor' clause. Workaround: fetch both files, concatenate, and addModule() a single blob URL. The base class and subclass end up in the same script evaluation context and the extends clause resolves. Processor names are memoised on a static Set so repeated sub-engine swaps don't try to re-register (which would throw).
This commit is contained in:
parent
4507db00c8
commit
fbeedba2f3
1 changed files with 49 additions and 5 deletions
|
|
@ -108,6 +108,11 @@ const LFO_MLP_PARAMS = ['rate', 'morph'];
|
|||
// -----------------------------------------------------------------------------
|
||||
|
||||
export class ModularEngine extends SynthEngine {
|
||||
// Processor names we've already registered via _registerCombinedWorklet.
|
||||
// Shared across all ModularEngine instances since AudioWorklet deduplicates
|
||||
// processor names per AudioContext; double-registering throws.
|
||||
static _registeredProcessors = new Set();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
|
@ -738,11 +743,14 @@ export class ModularEngine extends SynthEngine {
|
|||
}
|
||||
const wasmBytes = await wasmResp.arrayBuffer();
|
||||
|
||||
// Register worklet modules (addModule is idempotent — browser dedupes).
|
||||
// faust-worklet-processor.js defines the FaustWorkletProcessor base class
|
||||
// that modular-subtractive-processor.js extends.
|
||||
await audioCtx.audioWorklet.addModule('faust/faust-worklet-processor.js');
|
||||
await audioCtx.audioWorklet.addModule(cfg.workletUrl);
|
||||
// Register the worklet. In practice Chrome's AudioWorkletGlobalScope
|
||||
// isolates classes declared at the top of separate addModule() scripts
|
||||
// (contrary to the spec's "shared global" phrasing), so a base class
|
||||
// defined in faust-worklet-processor.js is not visible when a subclass
|
||||
// script is loaded via a second addModule() call. To sidestep this we
|
||||
// fetch both files, concatenate them, and addModule() a single blob URL
|
||||
// — the base class and subclass end up in one script evaluation.
|
||||
await this._registerCombinedWorklet(audioCtx, cfg.workletUrl);
|
||||
|
||||
this._workletNode = new AudioWorkletNode(audioCtx, cfg.processorName, {
|
||||
numberOfInputs: 0,
|
||||
|
|
@ -769,6 +777,42 @@ export class ModularEngine extends SynthEngine {
|
|||
await this._waitForReady(10_000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch faust-worklet-processor.js (base class) and the sub-engine's
|
||||
* processor file, concatenate them, and addModule() the result as one
|
||||
* blob. Cached per processor name so repeated sub-engine swaps don't
|
||||
* re-register.
|
||||
*
|
||||
* Chrome has deduped addModule() by URL in the past, so a blob URL with
|
||||
* a fresh identity each call is safe but slightly wasteful; we memoise
|
||||
* on processorName to keep things tidy.
|
||||
*/
|
||||
async _registerCombinedWorklet(audioCtx, workletUrl) {
|
||||
const name = this._subCfg.processorName;
|
||||
if (ModularEngine._registeredProcessors.has(name)) return;
|
||||
|
||||
const [baseSrc, subSrc] = await Promise.all([
|
||||
fetch('faust/faust-worklet-processor.js').then(r => {
|
||||
if (!r.ok) throw new Error(`base worklet fetch failed: ${r.status}`);
|
||||
return r.text();
|
||||
}),
|
||||
fetch(workletUrl).then(r => {
|
||||
if (!r.ok) throw new Error(`sub worklet fetch failed: ${r.status}`);
|
||||
return r.text();
|
||||
}),
|
||||
]);
|
||||
|
||||
const combined = baseSrc + '\n' + subSrc;
|
||||
const blob = new Blob([combined], { type: 'application/javascript' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
try {
|
||||
await audioCtx.audioWorklet.addModule(blobUrl);
|
||||
ModularEngine._registeredProcessors.add(name);
|
||||
} finally {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
}
|
||||
|
||||
_teardownWorklet() {
|
||||
if (this._masterGain) {
|
||||
try { this._masterGain.disconnect(); } catch (_) { /* ignore */ }
|
||||
|
|
|
|||
Loading…
Reference in a new issue