fix: bật AudioWorklet hết deprecation ScriptProcessor + signature guard applyMasteringSettings hết BiquadFilterNode state is bad

This commit is contained in:
2026-08-03 12:11:11 +07:00
parent 71278f2aba
commit 36edc9daca
5 changed files with 30 additions and 11 deletions
+9
View File
@@ -76,6 +76,14 @@ function makeDistortionCurve(k) {
function applyMasteringSettings(s) {
if (!masterBus || !audioCtx || !s) return;
// getAudioContext() invokes this on EVERY call (stopAll, play, VU, ).
// Re-applying identical values in rapid bursts is "fast parameter automation"
// and makes Chromium flag the biquad EQ filters as unstable ("state is bad").
// Only touch the graph when a value actually changed.
const sig = [s.eqActive, s.eqLowGain, s.eqMid1Gain, s.eqMid2Gain, s.eqHighGain, s.imagerActive, s.w1, s.w2, s.w3, s.w4, s.maximizerActive, s.maxGain, s.maxSoftClip, s.maxUpward, s.ceiling].join('|');
if (sig === _lastMasteringSig) return;
_lastMasteringSig = sig;
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
@@ -329,6 +337,7 @@ function setMasterVolume(linear) {
}
let _lastMasteringActive = null;
let _lastMasteringSig = null;
function toggleMasteringOnMaster(activate, isBypassed) {
if (!masterBus) return;
+6 -2
View File
@@ -10,7 +10,11 @@ const trackMidiChannelsRef={current:{}};const ensureTrackMidiChannel=(track,trac
(function handleSfsDeepLink(){try{const params=new URLSearchParams(window.location.search);const sfsParam=params.get('sfs');if(!sfsParam)return;const decoded=JSON.parse(decodeURIComponent(sfsParam));window.__pendingSfsProject=decoded;// consumed after auth in App
if(window.history.replaceState){window.history.replaceState({},document.title,window.location.pathname);}}catch(e){window.__pendingSfsProject=null;}})();// Storage for server-side file IDs mapped to track IDs
let serverFileIdMap={};let audioCtx;let masterBus=null;// { input, compressor, analyser, output, masteringActive }
function makeDistortionCurve(k){const n_samples=44100;const curve=new Float32Array(n_samples);for(let i=0;i<n_samples;++i){const x=i*2/n_samples-1;curve[i]=Math.atan(x*k)/(Math.atan(k)||1);}return curve;}function applyMasteringSettings(s){if(!masterBus||!audioCtx||!s)return;const now=audioCtx.currentTime;// Clamp every parameter so a stale/incomplete settings object can never push
function makeDistortionCurve(k){const n_samples=44100;const curve=new Float32Array(n_samples);for(let i=0;i<n_samples;++i){const x=i*2/n_samples-1;curve[i]=Math.atan(x*k)/(Math.atan(k)||1);}return curve;}function applyMasteringSettings(s){if(!masterBus||!audioCtx||!s)return;// getAudioContext() invokes this on EVERY call (stopAll, play, VU, …).
// Re-applying identical values in rapid bursts is "fast parameter automation"
// and makes Chromium flag the biquad EQ filters as unstable ("state is bad").
// Only touch the graph when a value actually changed.
const sig=[s.eqActive,s.eqLowGain,s.eqMid1Gain,s.eqMid2Gain,s.eqHighGain,s.imagerActive,s.w1,s.w2,s.w3,s.w4,s.maximizerActive,s.maxGain,s.maxSoftClip,s.maxUpward,s.ceiling].join('|');if(sig===_lastMasteringSig)return;_lastMasteringSig=sig;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
@@ -38,7 +42,7 @@ eqLowFilter.connect(eqMid1Filter);eqMid1Filter.connect(eqMid2Filter);eqMid2Filte
eqHighFilter.connect(imagerInput);// Connect Imager to Maximizer
imagerOutput.connect(maximizerBoostGain);// Setup default non-mastered routing:
// input -> compressor -> inputAnalyser -> outputAnalyser -> output -> analyser -> destination
masterBus.input.connect(masterBus.compressor);masterBus.compressor.connect(masterBus.inputAnalyser);masterBus.inputAnalyser.connect(masterBus.outputAnalyser);masterBus.outputAnalyser.connect(masterBus.output);masterBus.output.connect(masterBus.analyser);masterBus.analyser.connect(ctx.destination);window.masterBus=masterBus;return masterBus;}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
masterBus.input.connect(masterBus.compressor);masterBus.compressor.connect(masterBus.inputAnalyser);masterBus.inputAnalyser.connect(masterBus.outputAnalyser);masterBus.outputAnalyser.connect(masterBus.output);masterBus.output.connect(masterBus.analyser);masterBus.analyser.connect(ctx.destination);window.masterBus=masterBus;return masterBus;}function setMasterVolume(linear){if(masterBus)masterBus.output.gain.setValueAtTime(linear,audioCtx.currentTime);}let _lastMasteringActive=null;let _lastMasteringSig=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
+8 -7
View File
@@ -86,14 +86,15 @@
console.log("[SonicSF] AudioCtx state:", _audioCtx.state, "sampleRate:", _audioCtx.sampleRate);
// ScriptProcessor is the proven-stable renderer in this app.
// The AudioWorklet path (fluidsynth-bridge) is implemented and
// mono-safe, but it caused device-specific audio failures, so
// keep it opt-in (flip to false to try it) — the deprecation
// console warning is cosmetic.
var _useScriptNode = true;
// Use the AudioWorklet (fluidsynth-bridge) — ScriptProcessor
// is deprecated and logs a console warning. The worklet is
// now mono-safe (handles 1/2/N channel outputs) and the node
// is forced stereo, and the URL is cache-busted so the fixed
// module always loads. Falls back to ScriptProcessor if the
// worklet cannot be created (older browsers).
var _useScriptNode = false;
try {
await _audioCtx.audioWorklet.addModule('/static/js/worklets/fluidsynth-bridge.js?v=202608031240');
await _audioCtx.audioWorklet.addModule('/static/js/worklets/fluidsynth-bridge.js?v=202608031315');
console.log("[SonicSF] Worklet registered OK");
} catch (e) {
console.warn("[SonicSF] Worklet reg failed:", e);