Faust DSP sources, compiled WASM + JSON descriptors, AudioWorklet processors, and EOCModule subclasses for all three effects.
138 lines
5.2 KiB
JavaScript
138 lines
5.2 KiB
JavaScript
// delay-module.js — EOC Stereo Delay module (meml-2sl).
|
|
//
|
|
// Faust source: playground/faust/eoc-delay.dsp
|
|
// Worklet processor: playground/faust/eoc-delay-processor.js
|
|
// Processor name: 'eoc-delay-processor'
|
|
//
|
|
// 7 parameters (in JSON / Faust alphabetical order):
|
|
// 0 feedback [0, 0.95] init=0.3
|
|
// 1 lp_cutoff [500, 20000] init=8000
|
|
// 2 mix [0, 1] init=0.3
|
|
// 3 ping_pong [0, 1] init=0.0
|
|
// 4 spread [0, 1] init=0.5
|
|
// 5 sync [0, 3] init=0 (nentry)
|
|
// 6 time [1, 2000] init=250
|
|
|
|
import { EOCModule } from '../eoc-module.js';
|
|
import { loadFaustParamMeta } from '../../synth/faust-param-meta.js';
|
|
|
|
export class DelayModule extends EOCModule {
|
|
constructor() {
|
|
super();
|
|
this._workletNode = null;
|
|
this._effectGain = null; // GainNode: muted when bypassed
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// EOCModule identity
|
|
// ---------------------------------------------------------------------------
|
|
|
|
get id() { return 'delay'; }
|
|
get displayName() { return 'Delay'; }
|
|
|
|
get paramMeta() {
|
|
// Normalized init values derived from Faust defaults
|
|
return [
|
|
{ id: 'feedback', name: 'Feedback', min: 0, max: 0.95, init: 0.3 / 0.95, curve: 0.5, group: 'Delay' },
|
|
{ id: 'lp_cutoff', name: 'LP Cutoff', min: 500, max: 20000, init: (8000 - 500) / 19500, curve: 0.35, group: 'Delay' },
|
|
{ id: 'mix', name: 'Mix', min: 0, max: 1, init: 0.3, curve: 0.5, group: 'Delay' },
|
|
{ id: 'ping_pong', name: 'Ping-Pong', min: 0, max: 1, init: 0.0, curve: 0.5, group: 'Delay' },
|
|
{ id: 'spread', name: 'Spread', min: 0, max: 1, init: 0.5, curve: 0.5, group: 'Delay' },
|
|
{ id: 'sync', name: 'Sync', min: 0, max: 3, init: 0.0, curve: 0.5, group: 'Delay' },
|
|
{ id: 'time', name: 'Time (ms)', min: 1, max: 2000, init: (250 - 1) / 1999, curve: 0.35, group: 'Delay' },
|
|
];
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Lifecycle
|
|
// ---------------------------------------------------------------------------
|
|
|
|
async init(audioCtx) {
|
|
await super.init(audioCtx);
|
|
|
|
// Fetch WASM bytes
|
|
const wasmResp = await fetch('faust/eoc-delay.wasm');
|
|
if (!wasmResp.ok) throw new Error(`[DelayModule] Failed to fetch eoc-delay.wasm: ${wasmResp.status}`);
|
|
const wasmBytes = await wasmResp.arrayBuffer();
|
|
|
|
// Register worklet (browser deduplicates)
|
|
await audioCtx.audioWorklet.addModule('faust/faust-worklet-processor.js');
|
|
await audioCtx.audioWorklet.addModule('faust/eoc-delay-processor.js');
|
|
|
|
// Create worklet node: 2-in / 2-out stereo effect
|
|
this._workletNode = new AudioWorkletNode(audioCtx, 'eoc-delay-processor', {
|
|
numberOfInputs: 1,
|
|
numberOfOutputs: 1,
|
|
outputChannelCount: [2],
|
|
channelCount: 2,
|
|
channelCountMode: 'explicit',
|
|
});
|
|
|
|
// Listen for ready / error messages
|
|
this._workletNode.port.onmessage = (e) => {
|
|
if (e.data?.type === 'error') {
|
|
console.error('[DelayModule] Worklet error:', e.data.message);
|
|
}
|
|
};
|
|
|
|
// Effect gain node — muted when bypassed
|
|
this._effectGain = audioCtx.createGain();
|
|
this._effectGain.gain.value = 1;
|
|
|
|
// Wire: bypassIn → worklet → effectGain → bypassOut
|
|
this._bypassIn.connect(this._workletNode);
|
|
this._workletNode.connect(this._effectGain);
|
|
this._effectGain.connect(this._bypassOut);
|
|
|
|
// Send init message to worklet
|
|
this._workletNode.port.postMessage(
|
|
{ type: 'init', wasmBytes, sampleRate: audioCtx.sampleRate },
|
|
[wasmBytes]
|
|
);
|
|
|
|
// Apply initial default param values
|
|
this.paramMeta.forEach((_, i) => {
|
|
this.setParam(i, this.getCurrentParamValue(i));
|
|
});
|
|
|
|
this._finishInit();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Real-time control
|
|
// ---------------------------------------------------------------------------
|
|
|
|
setParam(index, normalizedValue) {
|
|
super.setParam(index, normalizedValue);
|
|
if (!this._workletNode) return;
|
|
const raw = this._denormalize(index, normalizedValue);
|
|
this._workletNode.port.postMessage({ type: 'setParam', index, value: raw });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Bypass
|
|
// ---------------------------------------------------------------------------
|
|
|
|
_onBypassChange(enabled) {
|
|
if (!this._effectGain) return;
|
|
const t = this._audioCtx.currentTime;
|
|
this._effectGain.gain.setTargetAtTime(enabled ? 1 : 0, t, 0.005);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cleanup
|
|
// ---------------------------------------------------------------------------
|
|
|
|
dispose() {
|
|
if (this._workletNode) {
|
|
this._workletNode.disconnect();
|
|
this._workletNode.port.onmessage = null;
|
|
this._workletNode = null;
|
|
}
|
|
if (this._effectGain) {
|
|
this._effectGain.disconnect();
|
|
this._effectGain = null;
|
|
}
|
|
super.dispose();
|
|
}
|
|
}
|