179 lines
5.3 KiB
JavaScript
179 lines
5.3 KiB
JavaScript
|
|
/**
|
|||
|
|
* eoc-reverb-processor.js — AudioWorklet processor for the zita reverb
|
|||
|
|
*
|
|||
|
|
* Extends FaustWorkletProcessor (faust-worklet-processor.js).
|
|||
|
|
* Loads eoc-reverb.wasm compiled from eoc-reverb.dsp.
|
|||
|
|
*
|
|||
|
|
* 8 exposed params (alphabetical order as emitted by Faust JSON):
|
|||
|
|
* 0: decay (s)
|
|||
|
|
* 1: diffusion (0–1)
|
|||
|
|
* 2: hi_damp (0–1)
|
|||
|
|
* 3: lo_damp (0–1)
|
|||
|
|
* 4: mix (0–1)
|
|||
|
|
* 5: predelay (ms)
|
|||
|
|
* 6: size (0–1)
|
|||
|
|
* 7: width (0–1)
|
|||
|
|
*
|
|||
|
|
* Note: mod_rate is declared in the DSP file but has no effect in this version
|
|||
|
|
* (zita_rev1_stereo does not expose modulation rate externally).
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// Runs in AudioWorkletGlobalScope — faust-worklet-processor.js must be loaded first.
|
|||
|
|
|
|||
|
|
class EOCReverbProcessor extends FaustWorkletProcessor {
|
|||
|
|
constructor(options) {
|
|||
|
|
super(options);
|
|||
|
|
this._dsp = null;
|
|||
|
|
this._exports = null;
|
|||
|
|
this._heap = null;
|
|||
|
|
this._heapi32 = null;
|
|||
|
|
this._mem = null;
|
|||
|
|
this._paramAddresses = [];
|
|||
|
|
this._inputL = null;
|
|||
|
|
this._inputR = null;
|
|||
|
|
this._sampleRate = 48000;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// FaustWorkletProcessor overrides
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
async _initWasm(wasmBytes, sr) {
|
|||
|
|
this._sampleRate = sr;
|
|||
|
|
|
|||
|
|
const module = await WebAssembly.compile(wasmBytes);
|
|||
|
|
// Reverb needs more memory due to large delay lines in zita
|
|||
|
|
const memory = new WebAssembly.Memory({ initial: 64, maximum: 512 });
|
|||
|
|
|
|||
|
|
const imports = {
|
|||
|
|
env: {
|
|||
|
|
memory,
|
|||
|
|
memoryBase: 0,
|
|||
|
|
tableBase: 0,
|
|||
|
|
_abs: Math.abs,
|
|||
|
|
_acosf: Math.acos,
|
|||
|
|
_asinf: Math.asin,
|
|||
|
|
_atanf: Math.atan,
|
|||
|
|
_atan2f: Math.atan2,
|
|||
|
|
_ceilf: Math.ceil,
|
|||
|
|
_cosf: Math.cos,
|
|||
|
|
_expf: Math.exp,
|
|||
|
|
_floorf: Math.floor,
|
|||
|
|
_fmodf: (x, y) => x % y,
|
|||
|
|
_logf: Math.log,
|
|||
|
|
_log10f: Math.log10,
|
|||
|
|
_max_f: Math.max,
|
|||
|
|
_min_f: Math.min,
|
|||
|
|
_remainderf: (x, y) => x - Math.round(x / y) * y,
|
|||
|
|
_powf: Math.pow,
|
|||
|
|
_roundf: Math.round,
|
|||
|
|
_sinf: Math.sin,
|
|||
|
|
_sqrtf: Math.sqrt,
|
|||
|
|
_tanf: Math.tan,
|
|||
|
|
_fabs: Math.abs,
|
|||
|
|
table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }),
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const instance = await WebAssembly.instantiate(module, imports);
|
|||
|
|
this._exports = instance.exports;
|
|||
|
|
this._mem = memory;
|
|||
|
|
this._heap = new Float32Array(memory.buffer);
|
|||
|
|
this._heapi32 = new Int32Array(memory.buffer);
|
|||
|
|
|
|||
|
|
const exps = this._exports;
|
|||
|
|
|
|||
|
|
if (exps.createDSPInstance) {
|
|||
|
|
this._dsp = exps.createDSPInstance();
|
|||
|
|
} else if (exps.eoc_reverb) {
|
|||
|
|
this._dsp = exps.eoc_reverb();
|
|||
|
|
} else {
|
|||
|
|
const fnKeys = Object.keys(exps).filter(k => typeof exps[k] === 'function');
|
|||
|
|
console.warn('[EOCReverbProcessor] No DSP constructor found; exports:', fnKeys);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
exps.init(this._dsp, sr);
|
|||
|
|
this._buildParamIndex(exps, memory);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_buildParamIndex(exports, memory) {
|
|||
|
|
if (!exports.getJSON) return;
|
|||
|
|
const ptr = exports.getJSON(this._dsp);
|
|||
|
|
const buf = new Uint8Array(memory.buffer);
|
|||
|
|
let str = '';
|
|||
|
|
let i = ptr;
|
|||
|
|
while (buf[i] !== 0) { str += String.fromCharCode(buf[i++]); }
|
|||
|
|
|
|||
|
|
let desc;
|
|||
|
|
try { desc = JSON.parse(str); } catch { return; }
|
|||
|
|
|
|||
|
|
const addresses = [];
|
|||
|
|
|
|||
|
|
function walk(items) {
|
|||
|
|
for (const item of items) {
|
|||
|
|
const type = item.type ?? '';
|
|||
|
|
if (['hslider', 'vslider', 'nentry'].includes(type)) {
|
|||
|
|
addresses.push(item.address ?? '');
|
|||
|
|
} else if (item.items) {
|
|||
|
|
walk(item.items);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
walk(desc.ui ?? []);
|
|||
|
|
this._paramAddresses = addresses;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_onSetParam(index, value) {
|
|||
|
|
if (!this._exports || !this._dsp) return;
|
|||
|
|
const addr = this._paramAddresses[index];
|
|||
|
|
if (addr !== undefined && this._exports.setParamValue) {
|
|||
|
|
this._exports.setParamValue(this._dsp, addr, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_renderBlock(outL, outR, blockSize) {
|
|||
|
|
if (!this._exports || !this._dsp) return;
|
|||
|
|
|
|||
|
|
const exps = this._exports;
|
|||
|
|
const mem = this._mem;
|
|||
|
|
const heap = this._heap;
|
|||
|
|
const i32 = this._heapi32;
|
|||
|
|
|
|||
|
|
const heapWords = mem.buffer.byteLength >> 2;
|
|||
|
|
const inLOff = heapWords - blockSize * 6 - 32;
|
|||
|
|
const inROff = inLOff + blockSize;
|
|||
|
|
const outLOff = inROff + blockSize;
|
|||
|
|
const outROff = outLOff + blockSize;
|
|||
|
|
const inPtrsOff = outROff + blockSize;
|
|||
|
|
const outPtrsOff = inPtrsOff + 2;
|
|||
|
|
|
|||
|
|
i32[inPtrsOff] = inLOff * 4;
|
|||
|
|
i32[inPtrsOff + 1] = inROff * 4;
|
|||
|
|
i32[outPtrsOff] = outLOff * 4;
|
|||
|
|
i32[outPtrsOff + 1] = outROff * 4;
|
|||
|
|
|
|||
|
|
const srcL = this._inputL;
|
|||
|
|
const srcR = this._inputR;
|
|||
|
|
if (srcL) for (let i = 0; i < blockSize; i++) heap[inLOff + i] = srcL[i];
|
|||
|
|
if (srcR) for (let i = 0; i < blockSize; i++) heap[inROff + i] = srcR[i];
|
|||
|
|
|
|||
|
|
exps.compute(this._dsp, blockSize, inPtrsOff * 4, outPtrsOff * 4);
|
|||
|
|
|
|||
|
|
for (let i = 0; i < blockSize; i++) {
|
|||
|
|
outL[i] = heap[outLOff + i];
|
|||
|
|
outR[i] = heap[outROff + i];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
process(inputs, outputs, params) {
|
|||
|
|
const inp = inputs[0];
|
|||
|
|
this._inputL = inp?.[0] ?? null;
|
|||
|
|
this._inputR = inp?.[1] ?? inp?.[0] ?? null;
|
|||
|
|
return super.process(inputs, outputs, params);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
registerProcessor('eoc-reverb-processor', EOCReverbProcessor);
|