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 () {
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() {
+1 -5
View File
@@ -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;
}
}