Add optional JamesDSP audio effects
This commit is contained in:
55
frontend/vendor/jamesdsp/worklet.js
vendored
Normal file
55
frontend/vendor/jamesdsp/worklet.js
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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;offset<out[0].length;offset+=128) {
|
||||
const n=Math.min(128,out[0].length-offset);
|
||||
for(let i=0;i<n;i++) {this.audio[i*2]=input[0]?.[offset+i]||0;this.audio[i*2+1]=(input[1]||input[0])?.[offset+i]||0;}
|
||||
try {
|
||||
if(this.wasm._jdsp_process(this.handle,this.pointer,n)!==0) throw new Error('DSP produced nonfinite output');
|
||||
for(let i=0;i<n;i++) {out[0][offset+i]=this.audio[i*2];if(out[1]) out[1][offset+i]=this.audio[i*2+1];}
|
||||
} catch(error) {out.forEach(channel=>channel.fill(0));this.failed=true;this.port.postMessage({type:'error',error:String(error)});break;}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
registerProcessor('jamesdsp',JamesDSPProcessor);
|
||||
Reference in New Issue
Block a user