Files
SonicForgeStudio/tests/test_vst_engine.py
T
admin dc331f39d8 phase5: mặc định RENDER_ENGINE=bridge, gỡ pedalboard khỏi requirements/source/LICENSE
- config.py: SF_RENDER_ENGINE default 'bridge' (pedalboard GPL-3.0 đã gỡ)
- requirements.txt: bỏ pedalboard==0.9.19
- vst_engine.py: bỏ HAS_PEDALBOARD/check_pedalboard_safe/load_vst/import pedalboard;
  midi_events_to_messages giữ làm utility thuần (không gate pedalboard)
- render_engine.py: bỏ nhánh elif vst and HAS_PEDALBOARD + import thừa
- plugins.py: preview/midi-render chỉ qua native_bridge; guard SF_RENDER_ENGINE=bridge
- runtime.py: _vst_render_available() check bridge exe (SF_BRIDGE_PATH/install/) thay find_spec('pedalboard')
- LICENSE.md/THIRD_PARTY_LICENSES.md: bỏ cảnh báo GPL-3.0 pedalboard, đánh số lại
- tests: bỏ test golden so pedalboard + test requires_pedalboard; 114 passed 0 skip
2026-08-17 12:58:51 +07:00

95 lines
3.6 KiB
Python

import os
import json
import pytest
import numpy as np
from unittest.mock import patch, MagicMock
from app.core.vst_engine import PluginManager, midi_note_to_freq, render_midi_events_to_audio
class TestPluginManager:
def test_init(self):
pm = PluginManager()
if os.name == "nt":
pf = os.environ.get("ProgramFiles", r"C:\Program Files")
assert pm.vst_dir == os.path.join(pf, "Common Files", "VST3")
root = os.environ.get("APPDATA") or os.path.expanduser("~")
assert pm.sf_dir == os.path.join(root, "SonicForgeDAW", "soundfonts")
else:
assert pm.vst_dir == "/opt/daw_engine/vst3"
assert pm.sf_dir == "/opt/daw_engine/soundfonts"
def test_midi_events_to_messages(self):
events = [{"note": 60, "start_beat": 0, "duration_beats": 1, "velocity": 100}]
bpm = 120
sr = 44100
msgs = PluginManager.midi_events_to_messages(events, bpm, sr)
beat_sec = 60.0 / 120
# pedalboard >= 0.9: message = (bytes raw MIDI, timestamp_seconds)
assert msgs[0] == (bytes([0x90, 60, 100]), 0.0)
assert msgs[1] == (bytes([0x80, 60, 0]), beat_sec)
def test_list_available_empty(self):
pm = PluginManager(vst_dir="/tmp/nonexistent_vst_dir_xyz", sf_dir="/tmp/nonexistent_sf_dir_xyz")
available = pm.list_available()
assert "vst_instruments" in available
assert "soundfonts" in available
assert available["vst_instruments"] == []
assert available["soundfonts"] == []
def test_validate_sf2_header_valid(self):
# Valid RIFF + sfbk header
valid = b'RIFF' + b'\x00' * 4 + b'sfbk' + b'\x00' * 100
assert PluginManager.validate_sf2_header(valid) is True
def test_validate_sf2_header_invalid_no_riff(self):
invalid = b'XXXX' + b'\x00' * 4 + b'sfbk' + b'\x00' * 100
assert PluginManager.validate_sf2_header(invalid) is False
def test_validate_sf2_header_invalid_no_sfbk(self):
invalid = b'RIFF' + b'\x00' * 4 + b'XXXX' + b'\x00' * 100
assert PluginManager.validate_sf2_header(invalid) is False
def test_validate_sf2_header_too_short(self):
assert PluginManager.validate_sf2_header(b'RIFF') is False
class TestMidiNoteToFreq:
def test_a4_440(self):
assert midi_note_to_freq(69) == 440.0
def test_c4(self):
# C4 = MIDI 60 = 261.63
freq = midi_note_to_freq(60)
assert abs(freq - 261.63) < 0.5
def test_note_zero_to_freq(self):
freq = midi_note_to_freq(0)
assert freq > 0 and freq < 10
class TestRenderMidiToAudio:
def test_render_single_note(self):
events = [{"note": 60, "start_beat": 0, "duration_beats": 1, "velocity": 100}]
audio = render_midi_events_to_audio(events, sr=44100, bpm=120)
assert audio.shape[0] == 2 # Stereo
assert audio.shape[1] > 0
# Should have non-zero samples
assert np.max(np.abs(audio)) > 0
def test_render_empty_events(self):
audio = render_midi_events_to_audio([], sr=44100, bpm=120)
assert audio.shape[0] == 2 # Stereo
# Empty events defaults to 2 seconds of silence (minimum length)
assert audio.shape[1] >= 0
def test_render_multiple_notes(self):
events = [
{"note": 60, "start_beat": 0, "duration_beats": 1, "velocity": 100},
{"note": 64, "start_beat": 1, "duration_beats": 1, "velocity": 80},
{"note": 67, "start_beat": 2, "duration_beats": 1, "velocity": 90},
]
audio = render_midi_events_to_audio(events, sr=44100, bpm=120)
assert audio.shape[0] == 2
assert audio.shape[1] > 44100 * 1