fix: crackling - time-based render buffer, no voice stacking, lower synth gain/verbose
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user