memlnaut-nisps/playground/faust/eoc-saturation-processor.js

150 lines
4.3 KiB
JavaScript
Raw Normal View History

/**
* eoc-saturation-processor.js AudioWorklet processor for the EOC Saturation.
*
* Extends FaustWorkletProcessor (faust-worklet-processor.js).
* Loads eoc-saturation.wasm compiled from eoc-saturation.dsp.
*
* Parameter index order (alphabetical within group, matching eoc-saturation.json):
* 0 character [0, 1]
* 1 drive [0, 1]
* 2 mix [0, 1]
* 3 tone [0, 1]
*/
// Must be loaded in AudioWorkletGlobalScope after faust-worklet-processor.js.
class EOCSaturationProcessor extends FaustWorkletProcessor {
constructor(options) {
super(options);
this._dsp = null;
this._paramAddresses = [];
this._blockSize = 128;
this._sampleRate = 48000;
}
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;
if (exports.createDSPInstance) {
this._dsp = exports.createDSPInstance();
} else if (exports.eoc_saturation) {
this._dsp = exports.eoc_saturation();
} else {
console.warn('[EOCSaturationProcessor] 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;
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-saturation-processor', EOCSaturationProcessor);