diff --git a/app/static/js/services/soundfontPlayer.js b/app/static/js/services/soundfontPlayer.js index 015b750..4481a6a 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 = 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() { diff --git a/app/static/js/worklets/fluidsynth-bridge.js b/app/static/js/worklets/fluidsynth-bridge.js index 088dfd8..f4bf0a3 100644 --- a/app/static/js/worklets/fluidsynth-bridge.js +++ b/app/static/js/worklets/fluidsynth-bridge.js @@ -29,14 +29,10 @@ 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); } - if (this.called % 50 === 0) { - var pk = 0; - for (var j = 0; j < len; j++) { - var v = out[0][j] > 0 ? out[0][j] : -out[0][j]; - if (v > pk) pk = v; - } - if (pk > 0) console.log('[FluidSynth:bridge] process #' + this.called + ' peak:' + pk.toFixed(6) + ' q:' + qL.length); + if (fi > 0) { + this.leftQ.splice(0, fi); + this.rightQ.splice(0, fi); + this.port.postMessage({ type: 'CONSUMED', n: fi }); } return true; }