FIX: Piano roll tab không xuất âm thanh qua mastering chain được recovery nhưng âm thanh rất nhỏ
This commit is contained in:
+64
-28
@@ -336,9 +336,9 @@ function applyMasteringSettings(s) {
|
||||
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
|
||||
// Limiter Threshold (WaveShaper ceiling — _setCeiling rebuild curve)
|
||||
const ceilingVal = s.maximizerActive ? clamp(s.ceiling, -60, 0) : -0.1;
|
||||
masterBus.maximizerCompressor.threshold.setTargetAtTime(ceilingVal, now, 0.01);
|
||||
if (masterBus.maximizerCompressor._setCeiling) masterBus.maximizerCompressor._setCeiling(ceilingVal);
|
||||
|
||||
// 4. Bus Compressor module (mastering_expand.md §II.2)
|
||||
if (masterBus.compNode) {
|
||||
@@ -348,11 +348,14 @@ function applyMasteringSettings(s) {
|
||||
masterBus.compMakeup.gain.setTargetAtTime(compOn ? Math.pow(10, clamp(s.compMakeup, 0, 12) / 20) : 1.0, now, 0.02);
|
||||
}
|
||||
|
||||
// 5. Brickwall Limiter module (ratio 20:1, knee 0)
|
||||
// 5. Brickwall Limiter module (WaveShaper tanh — threshold = mức clip; OFF = identity)
|
||||
if (masterBus.limNode) {
|
||||
const limOn = !!s.limActive;
|
||||
masterBus.limNode.threshold.setTargetAtTime(limOn ? clamp(s.limThreshold, -24, 0) : 0, now, 0.02);
|
||||
masterBus.limNode.ratio.setTargetAtTime(limOn ? 20 : 1, now, 0.02);
|
||||
if (limOn) {
|
||||
if (masterBus.limNode._setThreshold) masterBus.limNode._setThreshold(clamp(s.limThreshold, -24, 0));
|
||||
} else {
|
||||
try { masterBus.limNode.curve = new Float32Array([-1, 1]); } catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Harmonic Exciter module (dry/wet mix; dry stays 1.0 for subtle warmth)
|
||||
@@ -501,12 +504,21 @@ function initMasterBus(ctx) {
|
||||
upwardCompressor.connect(upwardGain);
|
||||
upwardGain.connect(upwardSummingGain);
|
||||
|
||||
const maximizerCompressor = ctx.createDynamicsCompressor();
|
||||
maximizerCompressor.threshold.value = -0.1;
|
||||
maximizerCompressor.knee.value = 0.0;
|
||||
maximizerCompressor.ratio.value = 20.0;
|
||||
maximizerCompressor.attack.value = 0.001;
|
||||
maximizerCompressor.release.value = 0.05;
|
||||
// Brickwall Limiter tại ceiling: WaveShaper HARD CLIP (slope 1 — không boost,
|
||||
// clip chính xác tại ceiling) — KHÔNG DynamicsCompressor (NaN trên bass
|
||||
// transient → chain state-bad → CÂM + stuck).
|
||||
const maximizerCompressor = ctx.createWaveShaper();
|
||||
maximizerCompressor.oversample = '2x';
|
||||
let _maxCeil = -0.1;
|
||||
const _buildMaxCurve = (db) => {
|
||||
const c = Math.pow(10, Math.max(-60, Math.min(0, db)) / 20);
|
||||
const _c = new Float32Array(4096);
|
||||
for (let _i = 0; _i < 4096; _i++) { const _x = (_i / 4095) * 2 - 1; _c[_i] = Math.max(-c, Math.min(c, _x)); }
|
||||
maximizerCompressor.curve = _c;
|
||||
_maxCeil = db;
|
||||
};
|
||||
_buildMaxCurve(-0.1);
|
||||
maximizerCompressor._setCeiling = (db) => { if (db !== _maxCeil) _buildMaxCurve(db); };
|
||||
|
||||
upwardSummingGain.connect(maximizerCompressor);
|
||||
|
||||
@@ -525,14 +537,23 @@ function initMasterBus(ctx) {
|
||||
compNode.connect(compMakeup);
|
||||
compMakeup.connect(compOutput);
|
||||
|
||||
// ── Brickwall Limiter module (ratio 20:1, knee 0) ──
|
||||
// ── Brickwall Limiter module (WaveShaper tanh soft-clip — KHÔNG
|
||||
// DynamicsCompressor: NaN trên bass transient → chain stuck) ──
|
||||
const limInput = ctx.createGain();
|
||||
const limNode = ctx.createDynamicsCompressor();
|
||||
limNode.threshold.value = -1.0;
|
||||
limNode.knee.value = 0;
|
||||
limNode.ratio.value = 20;
|
||||
limNode.attack.value = 0.001;
|
||||
limNode.release.value = 0.05;
|
||||
const limNode = ctx.createWaveShaper();
|
||||
limNode.oversample = '2x';
|
||||
let _limLastThresh = null;
|
||||
const _buildLimCurve = (db) => {
|
||||
const tLin = Math.pow(10, Math.max(-24, Math.min(0, db)) / 20);
|
||||
const k = 1 / Math.max(0.02, tLin);
|
||||
const _c = new Float32Array(4096);
|
||||
const _tk = Math.tanh(k);
|
||||
for (let _i = 0; _i < 4096; _i++) { const _x = (_i / 4095) * 2 - 1; _c[_i] = Math.tanh(_x * k) / _tk; }
|
||||
limNode.curve = _c;
|
||||
_limLastThresh = db;
|
||||
};
|
||||
_buildLimCurve(-1.0);
|
||||
limNode._setThreshold = (db) => { if (db !== _limLastThresh) _buildLimCurve(db); };
|
||||
const limOutput = ctx.createGain();
|
||||
limInput.connect(limNode);
|
||||
limNode.connect(limOutput);
|
||||
@@ -643,10 +664,12 @@ function initMasterBus(ctx) {
|
||||
eqMid1Filter.connect(eqMid2Filter);
|
||||
eqMid2Filter.connect(eqHighFilter);
|
||||
|
||||
// Setup default non-mastered routing:
|
||||
// input -> compressor -> inputAnalyser -> outputAnalyser -> output -> analyser -> destination
|
||||
masterBus.input.connect(masterBus.compressor);
|
||||
masterBus.compressor.connect(masterBus.inputAnalyser);
|
||||
// Setup default non-mastered routing (KHÔNG compressor mặc định trong path):
|
||||
// input -> inputAnalyser -> outputAnalyser -> output -> analyser -> destination
|
||||
// Compressor mặc định (ratio 12, threshold -24 — LUÔN-ON) vừa (a) pump-down
|
||||
// tín hiệu → âm nhỏ/méo, vừa (b) phát NaN khi gặp bass transient → 11 biquad
|
||||
// "state is bad" → CÂM + stuck. Mastering chain có comp/lim module riêng khi bật.
|
||||
masterBus.input.connect(masterBus.inputAnalyser);
|
||||
masterBus.inputAnalyser.connect(masterBus.outputAnalyser);
|
||||
masterBus.outputAnalyser.connect(masterBus.output);
|
||||
masterBus.output.connect(masterBus.analyser);
|
||||
@@ -867,12 +890,25 @@ function createTrackFxModule(type, ctx, params) {
|
||||
input.connect(comp); comp.connect(makeup); makeup.connect(output);
|
||||
nodes = { comp, makeup };
|
||||
} else if (type === 'limiter') {
|
||||
const lim = ctx.createDynamicsCompressor();
|
||||
lim.threshold.value = num(p.ceiling, -1.0);
|
||||
lim.knee.value = 0; lim.ratio.value = 20;
|
||||
lim.attack.value = 0.001; lim.release.value = 0.05;
|
||||
input.connect(lim); lim.connect(output);
|
||||
nodes = { lim };
|
||||
// Brickwall Limiter bằng WaveShaper tanh soft-clip — KHÔNG DynamicsCompressor:
|
||||
// Chromium compressor phát NaN với bass transient mạnh (pitch thấp + vel cao
|
||||
// đồng loạt) → NaN vào master chain → 11 biquad "state is bad" → CÂM + stuck.
|
||||
const shaper = ctx.createWaveShaper();
|
||||
shaper.oversample = '2x';
|
||||
const ceilingDb = Math.min(0, num(p.ceiling, -1.0));
|
||||
const threshLin = Math.pow(10, ceilingDb / 20);
|
||||
const k = 1 / Math.max(0.02, threshLin);
|
||||
const _curve = new Float32Array(4096);
|
||||
const _tanhK = Math.tanh(k);
|
||||
for (let _i = 0; _i < 4096; _i++) {
|
||||
const _x = (_i / 4095) * 2 - 1;
|
||||
_curve[_i] = Math.tanh(_x * k) / _tanhK;
|
||||
}
|
||||
shaper.curve = _curve;
|
||||
const makeup = ctx.createGain();
|
||||
makeup.gain.value = 1.0;
|
||||
input.connect(shaper); shaper.connect(makeup); makeup.connect(output);
|
||||
nodes = { shaper, makeup };
|
||||
} else if (type === 'exciter') {
|
||||
const hp = ctx.createBiquadFilter();
|
||||
hp.type = 'highpass'; hp.frequency.value = clampF(2000); hp.Q.value = 0.7;
|
||||
|
||||
Reference in New Issue
Block a user