feat: VSTi live playback 100% client-side (không Carla) — autosample SF2 backend (pedalboard) → FluidSynth WASM qua masterBus → mastering → main out; SonicVstiAutosample ensure+dedup+cooldown; runtime gating __enableCarlaLivePlayback; âm bắt qua ensureMidiCapture → clientSideExport

This commit is contained in:
2026-08-11 14:28:42 +07:00
parent 7293d7ac7e
commit cadb5402a3
12 changed files with 676 additions and 44 deletions
+82
View File
@@ -32,3 +32,85 @@ def test_render_project_bit_depth(tmp_path):
out = str(tmp_path / f"render_{bd}.wav")
engine.render_project(project, out, bit_depth=bd)
assert sf.info(out).subtype == subtype
def _default_mastering():
return {
"masterConnected": True, "isBypassed": False,
"eqActive": True, "eqLowGain": 1.5, "eqMid1Gain": -1.0, "eqMid2Gain": 2.0, "eqHighGain": 1.8,
"imagerActive": True, "w1": 0, "w2": 115, "w3": 135, "w4": 150,
"maximizerActive": True, "maxGain": 5.4, "maxUpward": 2.0, "maxSoftClip": 15, "ceiling": -0.1,
"compActive": False, "compThreshold": -16, "compRatio": 3, "compMakeup": 2,
"limActive": False, "limThreshold": -1.0,
"excActive": False, "excDrive": 30,
"rebalActive": False, "rebalMid": 0, "rebalSide": 0,
"chain": [
{"id": "mod_eq", "type": "eq", "active": True},
{"id": "mod_imager", "type": "imager", "active": True},
{"id": "mod_maximizer", "type": "maximizer", "active": True},
],
}
def test_apply_mastering_engine_gates_and_ceiling():
"""apply_mastering: gate masterConnected/isBypassed/chain rỗng → copy;
chain mặc định → khác input, peak ≤ ceiling (hard clip của Maximizer)."""
import numpy as np
from app.core.mastering_engine import apply_mastering
sr = 44100
t = np.arange(sr // 5) / sr
buf = np.stack([np.sin(2 * np.pi * 220 * t) * 0.9,
np.sin(2 * np.pi * 220 * t + 0.5) * 0.8]).astype(np.float32)
s = _default_mastering()
assert np.array_equal(apply_mastering(buf, None, sr), buf)
assert np.array_equal(apply_mastering(buf, {**s, "masterConnected": False}, sr), buf)
assert np.array_equal(apply_mastering(buf, {**s, "isBypassed": True}, sr), buf)
assert np.array_equal(apply_mastering(buf, {**s, "chain": []}, sr), buf)
out = apply_mastering(buf, s, sr)
assert out.shape == buf.shape and not np.array_equal(out, buf)
assert np.max(np.abs(out)) <= 10 ** (-0.1 / 20) + 1e-6 # ceiling -0.1 dB
def test_render_project_mastering(tmp_path):
"""render_project: có mastering_settings → WAV khác bản không mastering,
peak ≤ 1.0 (hard clip sau mastering như WAV encoder client)."""
import numpy as np
import soundfile as sf
from app.config import settings
engine = PythonRenderEngine()
# 1s 440Hz tone làm nguồn audio thật (project rỗng = silence → mastering
# của silence = silence, không test được gì)
sr = engine.sample_rate
t = np.arange(sr) / sr
tone = (0.9 * np.sin(2 * np.pi * 440 * t)).astype(np.float32)
src_path = os.path.join(settings.UPLOADS_DIR, "mastering_test_tone.wav")
sf.write(src_path, tone, sr)
try:
project = {
"metadata": {"bpm": 120, "time_signature_numerator": 4},
"main_session": {
"length_bars": 4.0,
"tracks": [{
"type": "AUDIO", "volume_db": 0.0, "pan": 0.0, "mute": False,
"items": [{
"type": "AUDIO_ITEM", "start_bar": 0.0, "duration_bars": 4.0,
"clip_start_offset_bars": 0.0,
"source_data": {"audio_file_url": "/static/audio/uploads/mastering_test_tone.wav", "gain": 1.0},
}],
}],
},
"section_store": {},
}
out_plain = str(tmp_path / "plain.wav")
engine.render_project(project, out_plain, bit_depth=16)
plain, _ = sf.read(out_plain, dtype="float32")
assert np.max(np.abs(plain)) > 0.01
out_mast = str(tmp_path / "mastered.wav")
engine.render_project({**project, "mastering_settings": _default_mastering()},
out_mast, bit_depth=16)
mast, _ = sf.read(out_mast, dtype="float32")
assert not np.array_equal(mast, plain)
assert np.max(np.abs(mast)) <= 1.0 + 1e-6
finally:
try:
os.remove(src_path)
except OSError:
pass