feat: migrate SpessaSynth → FluidSynth WASM (SF3 native, loop Gen 54 fix)

Replace SpessaSynth JS/AudioWorklet with FluidSynth C++ WASM
(@enikey87/fluidsynth-emscripten@0.1.1). CDN for dev, self-host
for prod. Fixes stuck Tremolo/Saxophone notes via compliant
Gen 44 loop mode processing. 100% audio parity with server
pyfluidsynth render (same C++ core).

- soundfontPlayer.js: FluidSynth WASM engine, preserve SonicSF API
- fluidsynthLoader.js: auto-select CDN (dev) vs self-host (prod)
- fluidsynth-bridge.js: PCM bridge AudioWorklet processor
- index.html: remove SpessaSynth importmap + module, add loader
- vendor/: libfluidsynth-2.3.0-sf3.js + .wasm (1.8MB self-host)
This commit is contained in:
2026-07-27 12:48:30 +07:00
parent 81dc9a0354
commit ef1238b87d
7 changed files with 341 additions and 181 deletions
@@ -0,0 +1,33 @@
class FluidSynthBridge extends AudioWorkletProcessor {
constructor() {
super();
this.leftQ = [];
this.rightQ = [];
this.port.onmessage = (e) => {
const d = e.data;
if (d.type === 'PCM') {
this.leftQ.push(d.L);
this.rightQ.push(d.R);
}
};
}
process(inputs, outputs) {
const out = outputs[0];
if (!out) return true;
const len = out[0].length;
const l = this.leftQ;
const r = this.rightQ;
let ri = 0;
for (let i = 0; i < len; i++) {
if (ri >= l.length) { out[0][i] = 0; out[1][i] = 0; continue; }
out[0][i] = l[ri];
out[1][i] = r[ri];
ri++;
}
if (ri > 0) { this.leftQ.splice(0, ri); this.rightQ.splice(0, ri); }
return true;
}
}
registerProcessor('fluidsynth-bridge', FluidSynthBridge);