Add JamesDSP playback controls and verification harness

This commit is contained in:
Jonathan Sykes
2026-10-09 20:29:37 +08:00
parent 27b32ff0c9
commit 6bb3271431
15 changed files with 376 additions and 29 deletions

View File

@@ -16,7 +16,7 @@
function createController(options) {
const graph = options.graph;
let desired = false, active = false, nodeEntry = null, gainNode = null, context = null;
let desired = false, active = false, bypassed = false, nodeEntry = null, gainNode = null, context = null;
let elements = [], standardInput = null, levelDb = 0, settings = null;
let tail = Promise.resolve(), maxLiveHeaps = 0, liveHeaps = 0, nodesCreated = 0, nodesDestroyed = 0;
let faultListener = null, notifying = false, dspAnalyser = null;
@@ -27,7 +27,7 @@
return result;
};
function snapshot() { return { active, desired, liveHeaps, maxLiveHeaps, nodesCreated, nodesDestroyed, sampleRate: context?.sampleRate || null }; }
function snapshot() { return { active, desired, bypassed, liveHeaps, maxLiveHeaps, nodesCreated, nodesDestroyed, sampleRate: context?.sampleRate || null }; }
function routeStandard() {
if (!elements.length) return;
@@ -58,6 +58,7 @@
function fail(error) {
desired = false;
active = false;
bypassed = false;
try { routeStandard(); } catch { /* graph route has already tried a direct audible fallback */ }
releaseNode();
if (!notifying) {
@@ -92,7 +93,7 @@
await instance.setBypass(true);
await instance.applyPreset(params);
await instance.setParam('gain', params.gain);
if (desired) await instance.setBypass(false);
if (desired) await instance.setBypass(bypassed);
if (desired) rampGain(1);
}
@@ -101,7 +102,6 @@
const { JamesDSPNode } = await import(moduleURL);
return JamesDSPNode.create(audioContext, {
wasmURL: root.Lazy.url('/vendor/jamesdsp/dsp/jamesdsp.wasm'),
engineURL: root.Lazy.url('/vendor/jamesdsp/dsp/jamesdsp.mjs'),
paramsURL: root.Lazy.url('/vendor/jamesdsp/params.mjs'),
workletURL: root.Lazy.url('/vendor/jamesdsp/worklet.mjs'),
});
@@ -144,6 +144,7 @@
function disable() {
desired = false;
bypassed = false;
if (active) {
active = false;
try { routeStandard(); } catch { /* routeStandard tried its direct fallback */ }
@@ -159,7 +160,23 @@
function setBypass(value) {
if (!nodeEntry) return Promise.resolve(false);
return nodeEntry.node.setBypass(Boolean(value));
return serial(async () => { bypassed = Boolean(value); await nodeEntry.node.setBypass(bypassed); return bypassed; }).catch(error => { fail(error); throw error; });
}
function toggleBypass() {
if (!nodeEntry) return Promise.resolve(false);
return serial(async () => { bypassed = !bypassed; await nodeEntry.node.setBypass(bypassed); return bypassed; }).catch(error => { fail(error); throw error; });
}
function loadIR(buffer) {
if (!active || !nodeEntry) return Promise.reject(new Error('Enable JamesDSP before loading an impulse response'));
return serial(async () => {
if (!active || !nodeEntry) throw new Error('JamesDSP is no longer active');
rampGain(0);
try { await nodeEntry.node.loadIR(buffer); await nodeEntry.node.setParam('convolver.enabled', 1); }
finally { if (active && desired) rampGain(1); }
return true;
}).catch(error => { fail(error); throw error; });
}
function analyser() {
@@ -175,7 +192,7 @@
return dspAnalyser;
}
return { enable, disable, setLevelDb, setBypass, analyser, get active() { return active; }, get desired() { return desired; }, state: snapshot };
return { enable, disable, setLevelDb, setBypass, toggleBypass, loadIR, analyser, get active() { return active; }, get desired() { return desired; }, state: snapshot };
}
const api = { PRESETS, createController };