fix: câm toàn cục (IN peak có OUT peak không) - WaveShaper luôn identity curve không null + clamp tần số biquad dưới Nyquist + watchdog tự bypass master chain khi hỏng
This commit is contained in:
+35
-14
@@ -128,12 +128,12 @@ function applyMasteringSettings(s) {
|
||||
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
|
||||
// Soft Clipper (identity passthrough when off — never null curve)
|
||||
if (s.maximizerActive && s.maxSoftClip > 0) {
|
||||
const k = 1 + (clamp(s.maxSoftClip, 0, 100) / 100) * 10;
|
||||
masterBus.maximizerSoftClipper.curve = makeDistortionCurve(k);
|
||||
} else {
|
||||
masterBus.maximizerSoftClipper.curve = null;
|
||||
masterBus.maximizerSoftClipper.curve = new Float32Array([-1, 1]);
|
||||
}
|
||||
|
||||
// Upward Compressor
|
||||
@@ -147,37 +147,45 @@ function applyMasteringSettings(s) {
|
||||
|
||||
function initMasterBus(ctx) {
|
||||
if (masterBus) return masterBus;
|
||||
|
||||
|
||||
// Clamp every filter frequency below Nyquist (0.45 * sampleRate). A biquad
|
||||
// with frequency ≥ Nyquist gets NaN coefficients → "BiquadFilterNode: state
|
||||
// is bad" → the whole mastering chain outputs silence (IN peak yes, OUT no).
|
||||
// Low sample-rate devices (8/11/16 kHz audio drivers) would otherwise break
|
||||
// the 10 kHz highshelf / 6 kHz imager crossover filters.
|
||||
const maxFilterFreq = (ctx.sampleRate || 44100) * 0.45;
|
||||
const clampF = v => Math.max(20, Math.min(v, maxFilterFreq));
|
||||
|
||||
// Create EQ filters
|
||||
const eqLowFilter = ctx.createBiquadFilter();
|
||||
eqLowFilter.type = 'lowshelf';
|
||||
eqLowFilter.frequency.value = 100;
|
||||
eqLowFilter.frequency.value = clampF(100);
|
||||
|
||||
const eqMid1Filter = ctx.createBiquadFilter();
|
||||
eqMid1Filter.type = 'peaking';
|
||||
eqMid1Filter.frequency.value = 822;
|
||||
eqMid1Filter.frequency.value = clampF(822);
|
||||
eqMid1Filter.Q.value = 0.7;
|
||||
|
||||
const eqMid2Filter = ctx.createBiquadFilter();
|
||||
eqMid2Filter.type = 'peaking';
|
||||
eqMid2Filter.frequency.value = 3200;
|
||||
eqMid2Filter.frequency.value = clampF(3200);
|
||||
eqMid2Filter.Q.value = 1.2;
|
||||
|
||||
const eqHighFilter = ctx.createBiquadFilter();
|
||||
eqHighFilter.type = 'highshelf';
|
||||
eqHighFilter.frequency.value = 10000;
|
||||
eqHighFilter.frequency.value = clampF(10000);
|
||||
|
||||
// Create Stereo Imager nodes
|
||||
const imagerInput = ctx.createGain();
|
||||
const imagerOutput = ctx.createGain();
|
||||
|
||||
// Imager Crossover Filters
|
||||
const f1_lp = ctx.createBiquadFilter(); f1_lp.type = 'lowpass'; f1_lp.frequency.value = 100;
|
||||
const f2_hp = ctx.createBiquadFilter(); f2_hp.type = 'highpass'; f2_hp.frequency.value = 100;
|
||||
const f2_lp = ctx.createBiquadFilter(); f2_lp.type = 'lowpass'; f2_lp.frequency.value = 1000;
|
||||
const f3_hp = ctx.createBiquadFilter(); f3_hp.type = 'highpass'; f3_hp.frequency.value = 1000;
|
||||
const f3_lp = ctx.createBiquadFilter(); f3_lp.type = 'lowpass'; f3_lp.frequency.value = 6000;
|
||||
const f4_hp = ctx.createBiquadFilter(); f4_hp.type = 'highpass'; f4_hp.frequency.value = 6000;
|
||||
const f1_lp = ctx.createBiquadFilter(); f1_lp.type = 'lowpass'; f1_lp.frequency.value = clampF(100);
|
||||
const f2_hp = ctx.createBiquadFilter(); f2_hp.type = 'highpass'; f2_hp.frequency.value = clampF(100);
|
||||
const f2_lp = ctx.createBiquadFilter(); f2_lp.type = 'lowpass'; f2_lp.frequency.value = clampF(1000);
|
||||
const f3_hp = ctx.createBiquadFilter(); f3_hp.type = 'highpass'; f3_hp.frequency.value = clampF(1000);
|
||||
const f3_lp = ctx.createBiquadFilter(); f3_lp.type = 'lowpass'; f3_lp.frequency.value = clampF(6000);
|
||||
const f4_hp = ctx.createBiquadFilter(); f4_hp.type = 'highpass'; f4_hp.frequency.value = clampF(6000);
|
||||
|
||||
const split1 = ctx.createChannelSplitter(2);
|
||||
const split2 = ctx.createChannelSplitter(2);
|
||||
@@ -235,7 +243,10 @@ function initMasterBus(ctx) {
|
||||
// Maximizer nodes
|
||||
const maximizerBoostGain = ctx.createGain();
|
||||
const maximizerSoftClipper = ctx.createWaveShaper();
|
||||
maximizerSoftClipper.curve = null;
|
||||
// NEVER leave the curve null: a WaveShaper with a null/identity curve can
|
||||
// output silence in some engines, which would kill the whole mastering path.
|
||||
// Use an explicit linear identity table for passthrough.
|
||||
maximizerSoftClipper.curve = new Float32Array([-1, 1]);
|
||||
maximizerSoftClipper.oversample = '4x';
|
||||
|
||||
const upwardCompressor = ctx.createDynamicsCompressor();
|
||||
@@ -8541,6 +8552,16 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin
|
||||
renderMeter(masterBus && masterBus.inputAnalyser, inMeterCanvasRef, 'inPeakText');
|
||||
renderMeter(masterBus && masterBus.outputAnalyser, outMeterCanvasRef, 'outPeakText');
|
||||
|
||||
// Safety watchdog: if the mastering chain is broken (signal in, silence
|
||||
// out — e.g. a biquad in a bad state), fall back to the direct routing so
|
||||
// audio is NEVER globally silent. The user can re-enable mastering after.
|
||||
const masterInPk = getPeakLevel(masterBus && masterBus.inputAnalyser);
|
||||
const masterOutPk = getPeakLevel(masterBus && masterBus.outputAnalyser);
|
||||
if (masterBus && masterBus.masteringActive && masterInPk > 0.01 && masterOutPk < 0.001) {
|
||||
console.warn('[Mastering] Chain broken (signal in, no signal out) — bypassing mastering to restore audio.');
|
||||
toggleMasteringOnMaster(false, false);
|
||||
}
|
||||
|
||||
// Wave Observer Oscilloscope Rendering
|
||||
const woCanvas = woCanvasRef.current;
|
||||
if (woCanvas) {
|
||||
|
||||
Reference in New Issue
Block a user