fix: crackling - time-based render buffer, no voice stacking, lower synth gain/verbose

This commit is contained in:
2026-07-31 10:52:22 +07:00
parent ad5cda5a40
commit 7e5f406226
2 changed files with 14 additions and 22 deletions
+13 -17
View File
@@ -1,6 +1,6 @@
(function () { (function () {
const RENDER_BLOCK = 512; const RENDER_BLOCK = 512;
const QUEUE_TARGET = 8; const QUEUE_TARGET = 16;
let _audioCtx = null; let _audioCtx = null;
let _fluidModule = null; let _fluidModule = null;
let _synthPtr = null; let _synthPtr = null;
@@ -114,9 +114,9 @@
_settingsPtr = _fluidModule._new_fluid_settings(); _settingsPtr = _fluidModule._new_fluid_settings();
_fluidModule._fluid_settings_setnum(_settingsPtr, "synth.sample-rate", _audioCtx.sampleRate || 44100); _fluidModule._fluid_settings_setnum(_settingsPtr, "synth.sample-rate", _audioCtx.sampleRate || 44100);
_fluidModule._fluid_settings_setnum(_settingsPtr, "synth.gain", 2.0); _fluidModule._fluid_settings_setnum(_settingsPtr, "synth.gain", 1.0);
_fluidModule._fluid_settings_setnum(_settingsPtr, "synth.polyphony", 256); _fluidModule._fluid_settings_setnum(_settingsPtr, "synth.polyphony", 256);
_fluidModule._fluid_settings_setint(_settingsPtr, "synth.verbose", 1); _fluidModule._fluid_settings_setint(_settingsPtr, "synth.verbose", 0);
_fluidModule._fluid_settings_setint(_settingsPtr, "synth.ladspa.active", 0); _fluidModule._fluid_settings_setint(_settingsPtr, "synth.ladspa.active", 0);
_fluidModule._fluid_settings_setstr(_settingsPtr, "player.timing-source", "audio"); _fluidModule._fluid_settings_setstr(_settingsPtr, "player.timing-source", "audio");
console.log("[SonicSF] FluidSynth settings configured"); console.log("[SonicSF] FluidSynth settings configured");
@@ -443,9 +443,6 @@
if (!_activeNotes[noteMapKey]) _activeNotes[noteMapKey] = []; if (!_activeNotes[noteMapKey]) _activeNotes[noteMapKey] = [];
if (_activeNotes[noteMapKey].indexOf(ch) === -1) _activeNotes[noteMapKey].push(ch); if (_activeNotes[noteMapKey].indexOf(ch) === -1) _activeNotes[noteMapKey].push(ch);
if (durationMs > 0 && durationMs < 60000) { if (durationMs > 0 && durationMs < 60000) {
var releaseMs = 400;
var totalDurMs = durationMs + releaseMs;
if (totalDurMs > 60000) totalDurMs = 60000;
scheduledNote.off = setTimeout(function () { scheduledNote.off = setTimeout(function () {
try { try {
_fluidModule._fluid_synth_noteoff(_synthPtr, ch, midiPitch); _fluidModule._fluid_synth_noteoff(_synthPtr, ch, midiPitch);
@@ -456,7 +453,7 @@
if (arr.length === 0) delete _activeNotes[noteMapKey]; if (arr.length === 0) delete _activeNotes[noteMapKey];
} }
} catch (e) {} } catch (e) {}
}, totalDurMs); }, durationMs);
} }
} catch (e) { } catch (e) {
console.warn("[SonicSF] FluidSynth noteOn error:", e); console.warn("[SonicSF] FluidSynth noteOn error:", e);
@@ -570,15 +567,11 @@
var block = RENDER_BLOCK; var block = RENDER_BLOCK;
var maxQueue = QUEUE_TARGET; var maxQueue = QUEUE_TARGET;
var queueDepth = 0; var queueDepth = 0;
var lastTick = performance.now();
var frameMs = (block / _audioCtx.sampleRate) * 1000;
// Worklet reports consumed frames so the queue depth stays accurate and // Track consumption by wall-clock time instead of async messages — immune
// we never underrun (silence gaps → crackle) while the main thread is busy. // to message-latency races that could underrun (silence gaps → crackle).
node.port.onmessage = function (e) {
if (e.data && e.data.type === 'CONSUMED' && e.data.n) {
queueDepth = Math.max(0, queueDepth - e.data.n);
}
};
function pushFrame() { function pushFrame() {
if (!Module || !synth || !node) return; if (!Module || !synth || !node) return;
try { try {
@@ -599,13 +592,16 @@
_renderTimer = null; _renderTimer = null;
return; return;
} }
var needed = maxQueue - queueDepth; var now = performance.now();
queueDepth = Math.max(0, queueDepth - (now - lastTick) / frameMs);
lastTick = now;
var needed = Math.min(maxQueue - queueDepth, maxQueue);
for (var i = 0; i < needed; i++) { for (var i = 0; i < needed; i++) {
pushFrame(); pushFrame();
} }
} }
_renderTimer = setInterval(fillLoop, Math.max(4, (block / _audioCtx.sampleRate) * 1000 * 0.5)); _renderTimer = setInterval(fillLoop, Math.max(4, frameMs * 0.5));
} }
function _stopRenderLoop() { function _stopRenderLoop() {
+1 -5
View File
@@ -29,11 +29,7 @@ class FluidSynthBridge extends AudioWorkletProcessor {
si++; si++;
if (si >= qL[fi].length) { fi++; si = 0; } if (si >= qL[fi].length) { fi++; si = 0; }
} }
if (fi > 0) { if (fi > 0) { this.leftQ.splice(0, fi); this.rightQ.splice(0, fi); }
this.leftQ.splice(0, fi);
this.rightQ.splice(0, fi);
this.port.postMessage({ type: 'CONSUMED', n: fi });
}
return true; return true;
} }
} }