Add JamesDSP playback controls and verification harness

This commit is contained in:
Jonathan Sykes
2026-10-09 20:29:37 +08:00
parent 27b32ff0c9
commit 6bb3271431
15 changed files with 376 additions and 29 deletions

View File

@@ -7,17 +7,12 @@ export class JamesDSPNode extends EventTarget {
static async create(context, options={}) {
if (![44100,48000].includes(context.sampleRate)) throw new RangeError('Use a 44.1 or 48 kHz AudioContext');
const wasmURL=options.wasmURL || assetURL('/vendor/jamesdsp/dsp/jamesdsp.wasm','./dsp/jamesdsp.wasm');
const engineURL=options.engineURL || assetURL('/vendor/jamesdsp/dsp/jamesdsp.mjs','./dsp/jamesdsp.mjs');
const paramsURL=options.paramsURL || assetURL('/vendor/jamesdsp/params.mjs','./params.mjs');
const baseWorkletURL=options.workletURL || assetURL('/vendor/jamesdsp/worklet.mjs','./worklet.mjs');
const paramApi=await loadParams(paramsURL);
const workletURL=new URL(baseWorkletURL,import.meta.url);
// Fragments are not sent over HTTP, so SW CacheStorage can keep the exact
// manifest URL while import.meta.url still carries the build-local engine URL.
workletURL.hash=new URLSearchParams({engine:String(engineURL)}).toString();
if (!moduleLoads.has(context)) moduleLoads.set(context,new Map());
const cache=moduleLoads.get(context), key=String(workletURL);
if (!cache.has(key)) cache.set(key,context.audioWorklet.addModule(workletURL).catch(e=>{cache.delete(key);throw e;}));
const cache=moduleLoads.get(context), key=String(baseWorkletURL);
if (!cache.has(key)) cache.set(key,context.audioWorklet.addModule(baseWorkletURL).catch(e=>{cache.delete(key);throw e;}));
const response=await fetch(wasmURL);
if (!response.ok) throw new Error(`WASM fetch failed: ${response.status}`);
const wasmBytes=await response.arrayBuffer();
@@ -26,7 +21,7 @@ export class JamesDSPNode extends EventTarget {
try { await wrapper.ready; return wrapper; } catch(e) { wrapper.destroy(); throw e; }
}
constructor(context,wasmBytes,paramApi) {
super(); this.context=context; this.defaultParams=paramApi.DEFAULT_PARAMS; this.validateParam=paramApi.validateParam; this.params={...this.defaultParams}; this.pending=new Map(); this.serial=0; this.destroyed=false;
super(); this.context=context; this.defaultParams=Object.fromEntries(Object.entries(paramApi.DEFAULT_PARAMS).filter(([name])=>!/^(?:liveprog|ddc|arbitrary)\./.test(name))); this.validateParam=paramApi.validateParam; this.params={...this.defaultParams}; this.pending=new Map(); this.serial=0; this.destroyed=false;
this.node=new AudioWorkletNode(context,'jamesdsp',{numberOfInputs:1,numberOfOutputs:1,outputChannelCount:[2],channelCount:2,channelCountMode:'explicit',processorOptions:{wasmBytes}});
this.ready=new Promise((resolve,reject)=> {
const timer=setTimeout(()=>reject(new Error('DSP initialization timed out')),15000);