// SPDX-License-Identifier: GPL-3.0-or-later import createJamesDSP from './dsp/jamesdsp.js'; class JamesDSPProcessor extends AudioWorkletProcessor { constructor(options) { super(); this.closed=false;this.failed=false; this.initialize(options); } async initialize(options) { try { this.wasm=await createJamesDSP({wasmBinary:options.processorOptions.wasmBytes,locateFile:()=>'jamesdsp.wasm',print:()=>{},printErr:()=>{}}); this.handle=this.wasm._jdsp_create(sampleRate); this.pointer=this.wasm._malloc(128*2*4); if(!this.handle || !this.pointer) throw new Error('DSP allocation failed or sample rate unsupported'); this.audio=this.wasm.HEAPF32.subarray(this.pointer/4,this.pointer/4+256); this.port.onmessage=({data})=>this.message(data); this.port.postMessage({type:'ready'}); } catch(error) {this.failed=true;this.port.postMessage({type:'error',error:String(error)});} } string(value,fn) { const utf8=encodeURIComponent(value+'\0').replace(/%([0-9A-F]{2})/g,(_,hex)=>String.fromCharCode(parseInt(hex,16))); const bytes=Uint8Array.from(utf8,c=>c.charCodeAt(0)), p=this.wasm._malloc(bytes.length); if(!p) throw new Error('DSP text allocation failed'); try {this.wasm.HEAPU8.set(bytes,p);return fn(p);} finally {this.wasm._free(p);} } message(data) { if(data.type==='destroy') {if(this.handle) this.wasm._jdsp_destroy(this.handle);if(this.pointer) this.wasm._free(this.pointer);this.closed=true;return;} try { let result; if(data.type==='param') result=this.string(data.name,p=>this.wasm._jdsp_set_param(this.handle,p,data.value)); else if(data.type==='text') result=this.string(data.effect,e=>this.string(data.text,t=>this.wasm._jdsp_set_text(this.handle,e,t))); else if(data.type==='ir') { const p=this.wasm._malloc(data.pcm.byteLength);if(!p) throw new Error('IR allocation failed'); try {this.wasm.HEAPF32.set(data.pcm,p/4);result=this.wasm._jdsp_load_ir(this.handle,p,data.frames,data.channels);} finally {this.wasm._free(p);} } else throw new Error('Unknown DSP message'); if(result!==0) throw new Error(`DSP rejected ${data.name||data.effect||data.type}`); this.port.postMessage({type:'reply',id:data.id,result}); } catch(error) {this.port.postMessage({type:'reply',id:data.id,error:String(error)});} } process(inputs,outputs) { if(this.closed) return false; const out=outputs[0], input=inputs[0]; if(!out?.length || this.failed || !this.handle) return true; // Mono duplicates to stereo; missing input becomes silence, preserving tails. for(let offset=0;offsetchannel.fill(0));this.failed=true;this.port.postMessage({type:'error',error:String(error)});break;} } return true; } } registerProcessor('jamesdsp',JamesDSPProcessor);