fix: mất soundfont khi ARM - clamp tham số master chain chặn NaN làm biquad state bad, toggleMasteringOnMaster idempotent, loadSoundFont retry khi lỗi thoáng qua

This commit is contained in:
2026-08-03 11:44:13 +07:00
parent 022ba38a5e
commit 71278f2aba
5 changed files with 67 additions and 33 deletions
+37 -21
View File
@@ -75,50 +75,58 @@ function makeDistortionCurve(k) {
}
function applyMasteringSettings(s) {
if (!masterBus || !audioCtx) return;
if (!masterBus || !audioCtx || !s) return;
const now = audioCtx.currentTime;
// Clamp every parameter so a stale/incomplete settings object can never push
// NaN or an extreme value into the biquad filters that puts the master
// chain into a bad state and silences ALL audio (the "mt soundfont" symptom).
const clamp = (v, lo, hi) => {
const n = Number(v);
if (!isFinite(n)) return 0; // missing/NaN neutral, never a filter-breaking value
return Math.min(hi, Math.max(lo, n));
};
// 1. EQ Settings
masterBus.eqLowFilter.gain.setTargetAtTime(s.eqActive ? s.eqLowGain : 0, now, 0.01);
masterBus.eqMid1Filter.gain.setTargetAtTime(s.eqActive ? s.eqMid1Gain : 0, now, 0.01);
masterBus.eqMid2Filter.gain.setTargetAtTime(s.eqActive ? s.eqMid2Gain : 0, now, 0.01);
masterBus.eqHighFilter.gain.setTargetAtTime(s.eqActive ? s.eqHighGain : 0, now, 0.01);
masterBus.eqLowFilter.gain.setTargetAtTime(s.eqActive ? clamp(s.eqLowGain, -24, 24) : 0, now, 0.01);
masterBus.eqMid1Filter.gain.setTargetAtTime(s.eqActive ? clamp(s.eqMid1Gain, -24, 24) : 0, now, 0.01);
masterBus.eqMid2Filter.gain.setTargetAtTime(s.eqActive ? clamp(s.eqMid2Gain, -24, 24) : 0, now, 0.01);
masterBus.eqHighFilter.gain.setTargetAtTime(s.eqActive ? clamp(s.eqHighGain, -24, 24) : 0, now, 0.01);
// 2. Imager Settings (Mid/Side matrix width control for each band)
const updateImagerBand = (w, active, gainLL, gainRL, gainLR, gainRR) => {
const widthVal = (s.imagerActive && active) ? w : 0;
const widthVal = (s.imagerActive && active) ? clamp(w, -100, 100) : 0;
const g1 = 1 + widthVal / 200;
const g2 = -widthVal / 200;
gainLL.gain.setTargetAtTime(g1, now, 0.01);
gainRR.gain.setTargetAtTime(g1, now, 0.01);
gainRL.gain.setTargetAtTime(g2, now, 0.01);
gainLR.gain.setTargetAtTime(g2, now, 0.01);
};
updateImagerBand(s.w1, true, masterBus.gainLL1, masterBus.gainRL1, masterBus.gainLR1, masterBus.gainRR1);
updateImagerBand(s.w2, true, masterBus.gainLL2, masterBus.gainRL2, masterBus.gainLR2, masterBus.gainRR2);
updateImagerBand(s.w3, true, masterBus.gainLL3, masterBus.gainRL3, masterBus.gainLR3, masterBus.gainRR3);
updateImagerBand(s.w4, true, masterBus.gainLL4, masterBus.gainRL4, masterBus.gainLR4, masterBus.gainRR4);
// 3. Maximizer Settings
const boostLinear = (s.maximizerActive) ? Math.pow(10, s.maxGain / 20) : 1.0;
const boostLinear = (s.maximizerActive) ? Math.pow(10, clamp(s.maxGain, -60, 30) / 20) : 1.0;
masterBus.maximizerBoostGain.gain.setTargetAtTime(boostLinear, now, 0.01);
// Soft Clipper
if (s.maximizerActive && s.maxSoftClip > 0) {
const k = 1 + (s.maxSoftClip / 100) * 10;
const k = 1 + (clamp(s.maxSoftClip, 0, 100) / 100) * 10;
masterBus.maximizerSoftClipper.curve = makeDistortionCurve(k);
} else {
masterBus.maximizerSoftClipper.curve = null;
}
// Upward Compressor
const upwardGainLinear = (s.maximizerActive && s.maxUpward > 0) ? (Math.pow(10, s.maxUpward / 20) - 1.0) : 0.0;
const upwardGainLinear = (s.maximizerActive && s.maxUpward > 0) ? (Math.pow(10, clamp(s.maxUpward, 0, 30) / 20) - 1.0) : 0.0;
masterBus.upwardGain.gain.setTargetAtTime(upwardGainLinear, now, 0.01);
// Limiter Threshold
const ceilingVal = s.maximizerActive ? s.ceiling : -0.1;
const ceilingVal = s.maximizerActive ? clamp(s.ceiling, -60, 0) : -0.1;
masterBus.maximizerCompressor.threshold.setTargetAtTime(ceilingVal, now, 0.01);
}
@@ -320,14 +328,22 @@ function setMasterVolume(linear) {
if (masterBus) masterBus.output.gain.setValueAtTime(linear, audioCtx.currentTime);
}
let _lastMasteringActive = null;
function toggleMasteringOnMaster(activate, isBypassed) {
if (!masterBus) return;
const active = !!(activate && !isBypassed);
// Idempotent: don't disconnect/reconnect the mastering chain on every call
// (getAudioContext invokes this constantly). Rapid re-connection while audio
// flows destabilizes the biquad filters "BiquadFilterNode: state is bad".
if (_lastMasteringActive === active && masterBus.masteringActive === active) return;
_lastMasteringActive = active;
// Disconnect the dynamic junction
masterBus.inputAnalyser.disconnect();
masterBus.maximizerCompressor.disconnect();
if (activate && !isBypassed) {
if (active) {
// Active routing: inputAnalyser -> EQ -> Imager -> Maximizer -> outputAnalyser
masterBus.inputAnalyser.connect(masterBus.eqLowFilter);
masterBus.maximizerCompressor.connect(masterBus.outputAnalyser);