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