Files
SonicForgeStudio/tests/test_render_engine.py
T

156 lines
5.7 KiB
Python

import os
import json
import pytest
from app.core.render_engine import PythonRenderEngine
import numpy as np
def test_render_engine_init():
engine = PythonRenderEngine()
assert engine.sample_rate == 44100
def test_bars_to_samples():
engine = PythonRenderEngine()
samples = engine.bars_to_samples(4.0, 120.0, 4)
assert samples == 352800
def test_render_session_container_empty():
engine = PythonRenderEngine()
session = {"tracks": []}
buf = engine.render_session_container(session, {}, 120.0, 4, 1000)
assert buf.shape == (2, 1000)
assert (buf == 0.0).all()
def _midi_track(instrument_id, fx_type=None):
return {
"id": "t1",
"type": "MIDI",
"instrument_id": instrument_id,
"instrument_source": "vsti",
"fx_type": fx_type,
"volume_db": 0.0,
"pan": 0.0,
"mute": False,
"items": [{
"type": "MIDI_ITEM",
"start_bar": 0.0,
"duration_bars": 1.0,
"clip_start_offset_bars": 0.0,
"source_data": {
"notes": [
{"pitch": 60, "velocity": 0.8, "start_beat": 0.0, "duration_beats": 1.0}
]
},
}],
}
def test_render_session_vsti_bridge_unknown_instrument_falls_back(monkeypatch):
"""SF_RENDER_ENGINE=bridge: track VSTi đi vào native_render; instrument lạ
(404) → fallback oscillator, không crash, không cần pedalboard."""
from app.api.v1 import plugins as pl
monkeypatch.setattr(pl.settings, "RENDER_ENGINE", "bridge")
engine = PythonRenderEngine()
session = {"tracks": [_midi_track("__no_such_plugin__")]}
buf = engine.render_session_container(session, {}, 120.0, 4, 44100)
assert buf.shape == (2, 44100)
# oscillator sinh âm thật (non-silent)
assert (buf != 0.0).any()
def test_render_session_vsti_non_bridge_engine_falls_back(monkeypatch):
"""Engine không phải bridge (vd 'pedalboard' cũ): VSTi → fallback oscillator (không crash)."""
from app.api.v1 import plugins as pl
monkeypatch.setattr(pl.settings, "RENDER_ENGINE", "pedalboard")
engine = PythonRenderEngine()
session = {"tracks": [_midi_track("__no_such_plugin__")]}
buf = engine.render_session_container(session, {}, 120.0, 4, 44100)
assert buf.shape == (2, 44100)
def test_render_session_fx_chorus_scipy_fallback():
"""FX chorus: fallback scipy (không pedalboard) không crash, thay đổi âm."""
from app.api.v1 import plugins as pl
pl.settings.RENDER_ENGINE = "bridge"
engine = PythonRenderEngine()
session = {"tracks": [_midi_track("__no_such_plugin__", fx_type="chorus")]}
buf = engine.render_session_container(session, {}, 120.0, 4, 44100)
assert buf.shape == (2, 44100)
assert (buf != 0.0).any()
def test_render_session_fx_reverb_scipy_fallback():
"""FX reverb: fallback scipy (không pedalboard) không crash, thay đổi âm."""
from app.api.v1 import plugins as pl
pl.settings.RENDER_ENGINE = "bridge"
engine = PythonRenderEngine()
session = {"tracks": [_midi_track("__no_such_plugin__", fx_type="reverb")]}
buf = engine.render_session_container(session, {}, 120.0, 4, 44100)
assert buf.shape == (2, 44100)
assert (buf != 0.0).any()
_BRIDGE_TEST_PATH = r"C:/Users/locpham/SonicForgeStudio/native_bridge/build/Release/daw_vst_bridge.exe"
def _peak(buf):
return float(np.max(np.abs(buf)))
def test_render_track_fx_chain_gain_via_bridge(monkeypatch):
"""Track fx_chain (builtin gain +6dB) qua native_bridge → biên độ x2."""
import numpy as np
if not os.path.isfile(_BRIDGE_TEST_PATH):
pytest.skip("bridge chưa build")
monkeypatch.setenv("SF_BRIDGE_PATH", _BRIDGE_TEST_PATH)
from app.api.v1 import plugins as pl
monkeypatch.setattr(pl.settings, "RENDER_ENGINE", "bridge")
engine = PythonRenderEngine()
base = _midi_track("__no_such_plugin__")
fx = _midi_track("__no_such_plugin__")
fx["vst_fx_chain"] = [{"type": "builtin", "id": "gain", "params": {"db": 6.0}}]
b0 = engine.render_session_container({"tracks": [base]}, {}, 120.0, 4, 44100)
b1 = engine.render_session_container({"tracks": [fx]}, {}, 120.0, 4, 44100)
assert _peak(b1) > 1.5 * _peak(b0)
def test_render_project_master_fx_chain_gain_via_bridge(monkeypatch, tmp_path):
"""Masterbus master.fx_chain (gain +6dB) qua native_bridge → peak x2."""
import numpy as np
if not os.path.isfile(_BRIDGE_TEST_PATH):
pytest.skip("bridge chưa build")
monkeypatch.setenv("SF_BRIDGE_PATH", _BRIDGE_TEST_PATH)
from app.api.v1 import plugins as pl
monkeypatch.setattr(pl.settings, "RENDER_ENGINE", "bridge")
engine = PythonRenderEngine()
def project(with_master):
track = _midi_track("__no_such_plugin__")
track["volume_db"] = -20.0 # tránh normalize (>1.0) làm hỏng tỉ lệ 2x
proj = {
"metadata": {"bpm": 120.0, "time_signature_numerator": 4},
"main_session": {"length_bars": 1.0, "tracks": [track]},
"section_store": {},
}
if with_master:
proj["main_session"]["master"] = {
"fx_chain": [{"type": "builtin", "id": "gain", "params": {"db": 6.0}}],
"bypass": False,
}
return proj
out0 = str(tmp_path / "no_master.wav")
out1 = str(tmp_path / "with_master.wav")
engine.render_project(project(False), out0)
engine.render_project(project(True), out1)
_, d0 = sf_read_peaks(out0)
_, d1 = sf_read_peaks(out1)
assert d1 > 1.5 * d0
import soundfile as _sf
def sf_read_peaks(path):
import numpy as np
data, _ = _sf.read(path, dtype="float32", always_2d=True)
return data, float(np.max(np.abs(data)))