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:
+37
-21
@@ -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 "mất 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);
|
||||
|
||||
Reference in New Issue
Block a user