memlnaut-nisps/playground/faust/eoc-delay-processor.js
w1n5t0n a9e2b5a8f9 feat(playground/eoc): Delay + Saturation + Master Bus modules (meml-2sl, meml-2zm)
Faust DSP sources, compiled WASM + JSON descriptors, AudioWorklet processors,
and EOCModule subclasses for all three effects.
2026-04-03 18:19:32 +01:00

159 lines
4.7 KiB
JavaScript

/**
* eoc-delay-processor.js — AudioWorklet processor for the EOC Stereo Delay.
*
* Extends FaustWorkletProcessor (faust-worklet-processor.js).
* Loads eoc-delay.wasm compiled from eoc-delay.dsp.
*
* Parameter index order (alphabetical within group, matching eoc-delay.json):
* 0 feedback [0, 0.95]
* 1 lp_cutoff [500, 20000]
* 2 mix [0, 1]
* 3 ping_pong [0, 1]
* 4 spread [0, 1]
* 5 sync [0, 3] (nentry)
* 6 time [1, 2000]
*/
// Must be loaded in AudioWorkletGlobalScope after faust-worklet-processor.js.
class EOCDelayProcessor extends FaustWorkletProcessor {
constructor(options) {
super(options);
this._dsp = null;
this._paramAddresses = [];
this._blockSize = 128;
this._sampleRate = 48000;
}
// ---------------------------------------------------------------------------
// FaustWorkletProcessor overrides
// ---------------------------------------------------------------------------
async _initWasm(wasmBytes, sr) {
this._sampleRate = sr;
const module = await WebAssembly.compile(wasmBytes);
const memory = new WebAssembly.Memory({ initial: 32, maximum: 256 });
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);
const exports = instance.exports;
this._exports = exports;
this._heap = new Float32Array(memory.buffer);
this._heapi32 = new Int32Array(memory.buffer);
this._mem = memory;
// Create DSP instance
if (exports.createDSPInstance) {
this._dsp = exports.createDSPInstance();
} else if (exports.eoc_delay) {
this._dsp = exports.eoc_delay();
} else {
console.warn('[EOCDelayProcessor] No DSP factory found; exports:', Object.keys(exports));
return;
}
exports.init(this._dsp, sr);
this._buildParamIndex(exports, 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, path) {
for (const item of items) {
const label = item.label ?? '';
const type = item.type ?? '';
const addr = item.address ?? (path + '/' + label);
if (['hslider', 'vslider', 'nentry', 'button', 'checkbox'].includes(type)) {
addresses.push(addr);
} else if (item.items) {
walk(item.items, path + '/' + label);
}
}
}
walk(desc.ui ?? [], '');
this._paramAddresses = addresses;
}
_onSetParam(index, value) {
if (!this._exports || !this._dsp) return;
const addr = this._paramAddresses[index];
if (addr && this._exports.setParamValue) {
this._exports.setParamValue(this._dsp, addr, value);
}
}
_renderBlock(outL, outR, blockSize) {
if (!this._exports || !this._dsp) return;
const exports = this._exports;
const mem = this._mem;
const heap = this._heap;
const heapBytes = mem.buffer.byteLength;
const inLOff = (heapBytes >> 2) - blockSize * 4 - 128;
const inROff = inLOff + blockSize;
const outLOff = inROff + blockSize;
const outROff = outLOff + blockSize;
// Zero input buffers (delay is an effect — pass through audio)
// Input pointers
const i32 = this._heapi32;
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;
exports.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];
}
}
}
registerProcessor('eoc-delay-processor', EOCDelayProcessor);