Gap 7: master volume automation lane — render_engine automation.volume_db (dB interp -30..+3 sau fader truoc normalize), schema master.automation, UI AUTOM panel MasterStripConsole + realtime scheduling tren masterBus.output.gain (play/seek/stop), serialize/parse, test_65 (177 passed)
This commit is contained in:
@@ -806,3 +806,54 @@ def test_64_soundfont_render_via_bridge(monkeypatch, tmp_path):
|
||||
engine.render_project(project, out)
|
||||
data, _ = _read_wav(out)
|
||||
assert float(np.max(np.abs(data))) > 0.01, "engine render soundfont im lặng — fallback oscillator?"
|
||||
|
||||
# ── 5.11 Gap 7: master volume automation lane ─────────────────────────────
|
||||
def test_65_master_volume_automation(monkeypatch, tmp_path, fx_bridge):
|
||||
"""Gap 7: master.automation.volume_db [{time,db}] — envelope áp sau fader,
|
||||
trước normalize: trước node 1s giữ nguyên, sau node 1s gain ~ -12dB (0.25)."""
|
||||
monkeypatch.setenv("SF_FX_BRIDGE_PATH", fx_bridge)
|
||||
if not os.path.isfile(DAW_BRIDGE):
|
||||
pytest.skip("daw_vst_bridge.exe chưa build")
|
||||
monkeypatch.setenv("SF_BRIDGE_PATH", DAW_BRIDGE)
|
||||
from app.api.v1 import plugins as pl
|
||||
monkeypatch.setattr(pl.settings, "RENDER_ENGINE", "bridge")
|
||||
from app.core.render_engine import PythonRenderEngine
|
||||
engine = PythonRenderEngine()
|
||||
|
||||
def project(auto_nodes):
|
||||
track = _midi_track()
|
||||
track["items"][0]["source_data"]["notes"][0]["duration_beats"] = 4.0 # phủ cả 2s
|
||||
track["volume_db"] = -12.0 # tránh normalize (>1.0)
|
||||
p = {
|
||||
"metadata": {"bpm": 120.0, "time_signature_numerator": 4},
|
||||
"main_session": {
|
||||
"length_bars": 1.0,
|
||||
"tracks": [track],
|
||||
"master": {"fx_chain": [], "bypass": False, "volume_db": 0.0},
|
||||
},
|
||||
"section_store": {},
|
||||
}
|
||||
if auto_nodes is not None:
|
||||
p["main_session"]["master"]["automation"] = {"volume_db": auto_nodes}
|
||||
return p
|
||||
|
||||
def rms(path, start_s, dur_s):
|
||||
data, sr = _read_wav(path)
|
||||
i0, i1 = int(start_s * sr), int((start_s + dur_s) * sr)
|
||||
return float(np.sqrt(np.mean(data[:, i0:i1] ** 2)))
|
||||
|
||||
out_base = str(tmp_path / "auto_base.wav")
|
||||
out_env = str(tmp_path / "auto_env.wav")
|
||||
engine.render_project(project(None), out_base)
|
||||
# Hai đoạn phẳng + chuyển nhanh 0dB→-12dB quanh t=1s (interp dB tuyến tính
|
||||
# giữa node liền kề; đoạn phẳng không bị ảnh hưởng bởi ramp lân cận).
|
||||
engine.render_project(project([
|
||||
{"time": 0.0, "db": 0.0}, {"time": 0.9, "db": 0.0},
|
||||
{"time": 1.0, "db": -12.0}, {"time": 2.0, "db": -12.0},
|
||||
]), out_env)
|
||||
base0 = rms(out_base, 0.1, 0.6)
|
||||
env0 = rms(out_env, 0.1, 0.6)
|
||||
env1 = rms(out_env, 1.15, 0.6)
|
||||
assert env0 == pytest.approx(base0, rel=0.05), f"đoạn 0dB phải giữ nguyên: {env0} vs {base0}"
|
||||
ratio = env1 / max(env0, 1e-9)
|
||||
assert 0.20 < ratio < 0.30, f"envelope -12dB → gain ~0.25: {ratio}"
|
||||
|
||||
Reference in New Issue
Block a user