fix: reduce MIDI playback crackling (no per-note prog reload, underrun-proof render loop, gentle stopAll)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
(function () {
|
||||
const RENDER_BLOCK = 512;
|
||||
const QUEUE_TARGET = 4;
|
||||
const QUEUE_TARGET = 8;
|
||||
let _audioCtx = null;
|
||||
let _fluidModule = null;
|
||||
let _synthPtr = null;
|
||||
@@ -419,7 +419,11 @@
|
||||
}
|
||||
// Program change at note time, not call time — ensures correct
|
||||
// instrument for each item regardless of processing order.
|
||||
if (synthEngine || program !== undefined) {
|
||||
// Skip if the channel already has this exact instrument (avoids
|
||||
// per-note soundfont reloads that cause audible crackle/glitches).
|
||||
var cachedCh = _channels[ch];
|
||||
var progAlreadySet = cachedCh && cachedCh.program === finalProg && cachedCh.bank === finalBank && cachedCh.sfId === finalSfId;
|
||||
if ((synthEngine || program !== undefined) && !progAlreadySet) {
|
||||
var sfHandle = finalSfId ? _sfHandleMap.get(finalSfId) : undefined;
|
||||
if (sfHandle !== undefined) {
|
||||
try {
|
||||
@@ -429,8 +433,11 @@
|
||||
try { _fluidModule._fluid_synth_bank_select(_synthPtr, ch, finalBank); } catch (e) {}
|
||||
try { _fluidModule._fluid_synth_program_change(_synthPtr, ch, finalProg); } catch (e) {}
|
||||
}
|
||||
if (!_channels[ch]) _channels[ch] = {};
|
||||
_channels[ch].bank = finalBank;
|
||||
_channels[ch].program = finalProg;
|
||||
_channels[ch].sfId = finalSfId;
|
||||
}
|
||||
console.log("[SonicSF] noteOn ch:", ch, "pitch:", midiPitch, "vel:", midiVel);
|
||||
_fluidModule._fluid_synth_noteon(_synthPtr, ch, midiPitch, midiVel);
|
||||
var noteMapKey = (_origChannel !== undefined ? _origChannel : 0) + ':' + midiPitch;
|
||||
if (!_activeNotes[noteMapKey]) _activeNotes[noteMapKey] = [];
|
||||
@@ -521,7 +528,6 @@
|
||||
if (_initialized && _fluidModule) {
|
||||
for (var ch = 0; ch < 16; ch++) {
|
||||
try { _fluidModule._fluid_synth_all_notes_off(_synthPtr, ch); } catch (e) {}
|
||||
try { _fluidModule._fluid_synth_all_sounds_off(_synthPtr, ch); } catch (e) {}
|
||||
}
|
||||
}
|
||||
while (_scheduledNotes.length > 0) {
|
||||
@@ -562,10 +568,17 @@
|
||||
var leftPtr = _leftBufPtr;
|
||||
var rightPtr = _rightBufPtr;
|
||||
var block = RENDER_BLOCK;
|
||||
var queueDepth = 0;
|
||||
var maxQueue = QUEUE_TARGET;
|
||||
var queueDepth = 0;
|
||||
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
var _dbgPeak = 0;
|
||||
function pushFrame() {
|
||||
if (!Module || !synth || !node) return;
|
||||
try {
|
||||
@@ -576,18 +589,6 @@
|
||||
Module._fluid_synth_write_float(synth, block, leftPtr, 0, 1, rightPtr, 0, 1);
|
||||
var leftArr = new Float32Array(Module.HEAPF32.subarray(lpb, lpb + block));
|
||||
var rightArr = new Float32Array(Module.HEAPF32.subarray(rpb, rpb + block));
|
||||
var peak = 0;
|
||||
var avg = 0;
|
||||
for (var si = 0; si < leftArr.length; si++) {
|
||||
var abs = leftArr[si] > 0 ? leftArr[si] : -leftArr[si];
|
||||
if (abs > peak) peak = abs;
|
||||
avg += abs;
|
||||
}
|
||||
avg /= leftArr.length;
|
||||
if (!_dbgPeak) {
|
||||
_dbgPeak = 1;
|
||||
console.log("[SonicSF] FRAME peak:", peak.toFixed(6), "avg:", avg.toFixed(8), "gain check:", Module._fluid_synth_get_gain ? Module._fluid_synth_get_gain(synth) : 'N/A');
|
||||
}
|
||||
node.port.postMessage({ type: 'PCM', L: leftArr, R: rightArr }, [leftArr.buffer, rightArr.buffer]);
|
||||
queueDepth++;
|
||||
} catch (e) { console.warn("[SonicSF] pushFrame error:", e); }
|
||||
@@ -602,10 +603,9 @@
|
||||
for (var i = 0; i < needed; i++) {
|
||||
pushFrame();
|
||||
}
|
||||
queueDepth = Math.max(0, queueDepth - 1);
|
||||
}
|
||||
|
||||
_renderTimer = setInterval(fillLoop, Math.max(8, (block / _audioCtx.sampleRate) * 1000 * 0.75));
|
||||
_renderTimer = setInterval(fillLoop, Math.max(4, (block / _audioCtx.sampleRate) * 1000 * 0.5));
|
||||
}
|
||||
|
||||
function _stopRenderLoop() {
|
||||
|
||||
Reference in New Issue
Block a user