From 7e5f406226e8ad31eeb79b5203b24d1619829cac Mon Sep 17 00:00:00 2001 From: 3dtours Date: Fri, 31 Jul 2026 10:52:22 +0700 Subject: [PATCH] fix: crackling - time-based render buffer, no voice stacking, lower synth gain/verbose --- app/static/js/services/soundfontPlayer.js | 30 +++++++++------------ app/static/js/worklets/fluidsynth-bridge.js | 6 +---- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/app/static/js/services/soundfontPlayer.js b/app/static/js/services/soundfontPlayer.js index 4481a6a..8e4b6d7 100644 --- a/app/static/js/services/soundfontPlayer.js +++ b/app/static/js/services/soundfontPlayer.js @@ -1,6 +1,6 @@ (function () { const RENDER_BLOCK = 512; - const QUEUE_TARGET = 8; + const QUEUE_TARGET = 16; let _audioCtx = null; let _fluidModule = null; let _synthPtr = null; @@ -114,9 +114,9 @@ _settingsPtr = _fluidModule._new_fluid_settings(); _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_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_setstr(_settingsPtr, "player.timing-source", "audio"); console.log("[SonicSF] FluidSynth settings configured"); @@ -443,9 +443,6 @@ if (!_activeNotes[noteMapKey]) _activeNotes[noteMapKey] = []; if (_activeNotes[noteMapKey].indexOf(ch) === -1) _activeNotes[noteMapKey].push(ch); if (durationMs > 0 && durationMs < 60000) { - var releaseMs = 400; - var totalDurMs = durationMs + releaseMs; - if (totalDurMs > 60000) totalDurMs = 60000; scheduledNote.off = setTimeout(function () { try { _fluidModule._fluid_synth_noteoff(_synthPtr, ch, midiPitch); @@ -456,7 +453,7 @@ if (arr.length === 0) delete _activeNotes[noteMapKey]; } } catch (e) {} - }, totalDurMs); + }, durationMs); } } catch (e) { console.warn("[SonicSF] FluidSynth noteOn error:", e); @@ -570,15 +567,11 @@ var block = RENDER_BLOCK; var maxQueue = QUEUE_TARGET; var queueDepth = 0; + var lastTick = performance.now(); + var frameMs = (block / _audioCtx.sampleRate) * 1000; - // Worklet reports consumed frames so the queue depth stays accurate and - // we never underrun (silence gaps → crackle) while the main thread is busy. - node.port.onmessage = function (e) { - if (e.data && e.data.type === 'CONSUMED' && e.data.n) { - queueDepth = Math.max(0, queueDepth - e.data.n); - } - }; - + // Track consumption by wall-clock time instead of async messages — immune + // to message-latency races that could underrun (silence gaps → crackle). function pushFrame() { if (!Module || !synth || !node) return; try { @@ -599,13 +592,16 @@ _renderTimer = null; 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++) { pushFrame(); } } - _renderTimer = setInterval(fillLoop, Math.max(4, (block / _audioCtx.sampleRate) * 1000 * 0.5)); + _renderTimer = setInterval(fillLoop, Math.max(4, frameMs * 0.5)); } function _stopRenderLoop() { diff --git a/app/static/js/worklets/fluidsynth-bridge.js b/app/static/js/worklets/fluidsynth-bridge.js index f4bf0a3..3d4a18d 100644 --- a/app/static/js/worklets/fluidsynth-bridge.js +++ b/app/static/js/worklets/fluidsynth-bridge.js @@ -29,11 +29,7 @@ class FluidSynthBridge extends AudioWorkletProcessor { si++; if (si >= qL[fi].length) { fi++; si = 0; } } - if (fi > 0) { - this.leftQ.splice(0, fi); - this.rightQ.splice(0, fi); - this.port.postMessage({ type: 'CONSUMED', n: fi }); - } + if (fi > 0) { this.leftQ.splice(0, fi); this.rightQ.splice(0, fi); } return true; } }