Gap 10: multiband compressor + de-esser (LR4 crossover, UI, test_62)

This commit is contained in:
2026-08-23 08:25:07 +07:00
parent c8c49b6b9c
commit 3ad379f0ba
6 changed files with 305 additions and 6 deletions
+46
View File
@@ -656,3 +656,49 @@ def test_60_vst3_setparam_live(fx_bridge, adelay, monkeypatch):
e_old = 103 * 256 + SR # echo delay cũ 1s ≈ 70468 (restart/no-apply)
assert _peak(e_old - 400, e_old + 400) < 0.3, "param không áp live — echo vẫn ở delay 1s"
assert _peak(0, e2 - 700) < 0.3, "có tín hiệu lạ trước echo 0.25s"
# ── 5.9 Multiband compressor + De-Esser (PLAN_MASTER_ENHANCE B.10) ──────
def test_62_multiband_deesser(fx_bridge):
"""Multiband: GR on active band + unity transparent (LR4 flat).
DeEsser: GR at 8kHz (sibilance), 500Hz untouched."""
def peak_db(x):
# steady-state: nua cuoi (bo transient khoi dau cua HP/crossover filters)
m = float(np.max(np.abs(x[:, x.shape[1] // 2:])))
return 20.0 * math.log10(max(m, 1e-12))
def render(chain, freq, amp):
t = np.arange(SR * 2) / SR
sine = (amp * np.sin(2 * np.pi * freq * t)).astype(np.float32)
audio = np.stack([sine, sine])
with tempfile.TemporaryDirectory() as td:
wav_in = os.path.join(td, "in.wav")
_write_wav(wav_in, audio, SR)
return _render_bridge(fx_bridge, wav_in, SR, chain, block=512)
# 1) multiband: low band comp 100Hz (thr -20 ratio 10) -> GR
mb_lo = [{"type": "builtin", "id": "multiband", "params": {"lf_cross": 200, "hf_cross": 4000,
"low_thr": -20, "low_ratio": 10, "low_makeup": 0, "mid_thr": -80, "mid_ratio": 1,
"mid_makeup": 0, "high_thr": -80, "high_ratio": 1, "high_makeup": 0}}]
out = render(mb_lo, 100.0, 0.5)
g = peak_db(out)
assert g < -12.0, f"low comp GR weak: {g:.1f}dBFS (expect <-12)"
# 2) multiband: unity transparent +-0.5dB (LR4 crossover flat)
mb_unity = [{"type": "builtin", "id": "multiband", "params": {"lf_cross": 200, "hf_cross": 4000,
"low_thr": -10, "low_ratio": 1, "low_makeup": 0, "mid_thr": -10, "mid_ratio": 1,
"mid_makeup": 0, "high_thr": -10, "high_ratio": 1, "high_makeup": 0}}]
for freq in (100.0, 1000.0, 8000.0):
out = render(mb_unity, freq, 0.5)
g = peak_db(out)
assert -6.5 <= g <= -5.5, f"multiband unity {freq}Hz: {g:.2f}dBFS (expect ~-6.02)"
# 3) deesser: 8kHz GR (thr -30 ratio 100 freq 6000)
de = [{"type": "builtin", "id": "deesser", "params": {"threshold": -30, "ratio": 100, "freq": 6000, "makeup": 0}}]
out = render(de, 8000.0, 0.7)
g = peak_db(out)
assert g < -15.0, f"deesser 8kHz GR weak: {g:.1f}dBFS (expect <-15)"
# 4) deesser: 500Hz unchanged +-0.5dB
out = render(de, 500.0, 0.5)
g = peak_db(out)
assert -6.5 <= g <= -5.5, f"deesser 500Hz changed: {g:.2f}dBFS (expect ~-6.02)"