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:
+51
-24
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user