fix: câm toàn cục + BiquadFilterNode state is bad - gỡ mastering khỏi getAudioContext (chỉ áp dụng ở initMasterBus/effect), time constant chậm hơn + cancelScheduledValues, toggleMasteringOnMaster luôn reconnect dù lỗi

This commit is contained in:
2026-08-03 12:26:03 +07:00
parent f616edce49
commit 94b2d2ef41
4 changed files with 75 additions and 33 deletions
+51 -24
View File
@@ -95,10 +95,17 @@ function applyMasteringSettings(s) {
};
// 1. EQ Settings
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);
// cancelScheduledValues + a slower time constant keeps rapid slider drags
// from piling up automation events on the biquad filters (the trigger for
// Chromium's "BiquadFilterNode: state is bad").
masterBus.eqLowFilter.gain.cancelScheduledValues(now);
masterBus.eqLowFilter.gain.setTargetAtTime(s.eqActive ? clamp(s.eqLowGain, -24, 24) : 0, now, 0.05);
masterBus.eqMid1Filter.gain.cancelScheduledValues(now);
masterBus.eqMid1Filter.gain.setTargetAtTime(s.eqActive ? clamp(s.eqMid1Gain, -24, 24) : 0, now, 0.05);
masterBus.eqMid2Filter.gain.cancelScheduledValues(now);
masterBus.eqMid2Filter.gain.setTargetAtTime(s.eqActive ? clamp(s.eqMid2Gain, -24, 24) : 0, now, 0.05);
masterBus.eqHighFilter.gain.cancelScheduledValues(now);
masterBus.eqHighFilter.gain.setTargetAtTime(s.eqActive ? clamp(s.eqHighGain, -24, 24) : 0, now, 0.05);
// 2. Imager Settings (Mid/Side matrix width control for each band)
const updateImagerBand = (w, active, gainLL, gainRL, gainLR, gainRR) => {
@@ -329,6 +336,16 @@ function initMasterBus(ctx) {
masterBus.analyser.connect(ctx.destination);
window.masterBus = masterBus;
// Apply mastering once when the chain is first created (and on the
// masteringSettings effect for subsequent changes see useEffect).
if (window.currentMasteringSettings) {
try {
toggleMasteringOnMaster(window.currentMasteringSettings.masterConnected, window.currentMasteringSettings.isBypassed);
applyMasteringSettings(window.currentMasteringSettings);
} catch (e) {
console.warn('initMasterBus apply mastering error:', e);
}
}
return masterBus;
}
@@ -342,25 +359,33 @@ 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".
// Idempotent: don't disconnect/reconnect the mastering chain on every call.
if (_lastMasteringActive === active && masterBus.masteringActive === active) return;
_lastMasteringActive = active;
// Disconnect the dynamic junction
masterBus.inputAnalyser.disconnect();
masterBus.maximizerCompressor.disconnect();
if (active) {
// Active routing: inputAnalyser -> EQ -> Imager -> Maximizer -> outputAnalyser
masterBus.inputAnalyser.connect(masterBus.eqLowFilter);
masterBus.maximizerCompressor.connect(masterBus.outputAnalyser);
masterBus.masteringActive = true;
} else {
// Bypassed routing: inputAnalyser -> outputAnalyser
masterBus.inputAnalyser.connect(masterBus.outputAnalyser);
masterBus.masteringActive = false;
// Disconnect + immediately reconnect in one synchronous block so the master
// routing can NEVER be left broken (a mid-swap exception would otherwise
// disconnect inputAnalyser and silence ALL audio globally).
try {
masterBus.inputAnalyser.disconnect();
masterBus.maximizerCompressor.disconnect();
if (active) {
masterBus.inputAnalyser.connect(masterBus.eqLowFilter);
masterBus.maximizerCompressor.connect(masterBus.outputAnalyser);
masterBus.masteringActive = true;
} else {
masterBus.inputAnalyser.connect(masterBus.outputAnalyser);
masterBus.masteringActive = false;
}
} catch (e) {
console.warn('toggleMasteringOnMaster error:', e);
// Restore a guaranteed-valid default routing regardless of the failure.
try {
masterBus.inputAnalyser.disconnect();
masterBus.maximizerCompressor.disconnect();
masterBus.inputAnalyser.connect(masterBus.outputAnalyser);
masterBus.masteringActive = false;
} catch (e2) {}
}
}
@@ -377,10 +402,12 @@ function getAudioContext() {
if (!masterBus) {
initMasterBus(audioCtx);
}
if (window.currentMasteringSettings) {
toggleMasteringOnMaster(window.currentMasteringSettings.masterConnected, window.currentMasteringSettings.isBypassed);
applyMasteringSettings(window.currentMasteringSettings);
}
// The mastering chain is ONLY managed by the masteringSettings effect NOT
// here. getAudioContext runs constantly (play, stopAll, VU, double-click);
// re-toggling/re-automating the biquad EQ here in bursts is "fast parameter
// automation" that makes Chromium flag the filters as unstable
// ("BiquadFilterNode: state is bad") and can leave the master routing broken
// global silence. The React effect applies it once per settings change.
if (window.SonicSF && window.SonicSF.init) {
window.SonicSF.init(audioCtx);
}