feat: embed native VST GUI in mastering + fx rack panels (bridge --fx-gui frame capture + input relay)
Linux parts (0L1-4L1 per TASKS_LINUX_WINDOWS.md): - native_bridge: FxGuiServer.cpp HTTP server (Winsock) — /frame BitBlt + PrintWindow fallback -> JPEG (libjpeg-turbo q70), /input PostMessage relay (mouse/wheel/key, coords scaled), /close, /ping; RenderFxJob fxGuiSetup + run_fx_gui_server (offscreen); main.cpp --fx-gui dispatch; vcpkg + CMakeLists WIN32-only + JPEG. - api: open_fx_gui embed flag spawn --fx-gui, parse SF_FXGUI_PORT -> embed_url, fx-gui/close endpoint; 4 unit tests. - ui: VstGuiEmbed component (frame refresh 100ms, ping 2s, input relay, scale coords) shared by MasteringModal + FXRackModal; swap graph<->GUI, cleanup on unmount, dead-bridge toast fallback. - docs: TASKS_LINUX_WINDOWS.md, WALKTHROUGH_WINDOWS.md, PLAN status. Windows tasks 0W1-4W1 remain (build + E2E real VST).
This commit is contained in:
@@ -197,3 +197,71 @@ class TestMidiRenderVSTi:
|
||||
resp = client.post("/api/v1/plugins/carla-play-notes", headers={"Authorization": f"Bearer {token}"},
|
||||
json={"notes": [{"pitch": 60, "start_beat": 0, "duration_beats": 1}], "bpm": 120})
|
||||
assert resp.status_code == 409
|
||||
|
||||
# --- fx-gui embed (PLAN_MASTERBUS_VST_GUI_EMBED.md Phase 1) --------------
|
||||
def test_fx_gui_open_embed_returns_embed_url(self):
|
||||
token = get_admin_token()
|
||||
if not token:
|
||||
pytest.skip("Cannot get admin token")
|
||||
fake_proc = MagicMock()
|
||||
fake_proc.stdout = iter(["some log\n", "SF_FXGUI_PORT=54321\n"])
|
||||
with patch("app.core.native_render.find_bridge_exe",
|
||||
return_value="C:\\bridge\\daw_vst_bridge.exe"), \
|
||||
patch("app.api.v1.plugins.subprocess.Popen", return_value=fake_proc):
|
||||
resp = client.post("/api/v1/plugins/fx-gui",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
json={"path": "C:\\VST\\Ozone11.vst3", "name": "Ozone 11", "embed": True})
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["embed_url"] == "http://127.0.0.1:54321"
|
||||
assert data["started"] is True
|
||||
# registry có process + path để close tìm thấy
|
||||
from app.api.v1 import plugins as plugins_mod
|
||||
assert any(getattr(p, "_sf_plugin_path", None) == "C:\\VST\\Ozone11.vst3"
|
||||
for p in plugins_mod._FX_GUI_PROCESSES)
|
||||
plugins_mod._FX_GUI_PROCESSES.clear()
|
||||
|
||||
def test_fx_gui_open_no_port_is_error(self):
|
||||
token = get_admin_token()
|
||||
if not token:
|
||||
pytest.skip("Cannot get admin token")
|
||||
fake_proc = MagicMock()
|
||||
fake_proc.stdout = iter(["never prints port\n"])
|
||||
with patch("app.core.native_render.find_bridge_exe",
|
||||
return_value="C:\\bridge\\daw_vst_bridge.exe"), \
|
||||
patch("app.api.v1.plugins.subprocess.Popen", return_value=fake_proc):
|
||||
resp = client.post("/api/v1/plugins/fx-gui",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
json={"path": "C:\\VST\\Ozone11.vst3", "embed": True})
|
||||
assert resp.status_code == 500
|
||||
assert "port" in resp.json()["detail"]
|
||||
fake_proc.terminate.assert_called_once()
|
||||
|
||||
def test_fx_gui_close_kills_process(self):
|
||||
token = get_admin_token()
|
||||
if not token:
|
||||
pytest.skip("Cannot get admin token")
|
||||
from app.api.v1 import plugins as plugins_mod
|
||||
proc = MagicMock()
|
||||
proc.poll.return_value = None
|
||||
proc._sf_plugin_path = "C:\\VST\\Ozone11.vst3"
|
||||
plugins_mod._FX_GUI_PROCESSES.append(proc)
|
||||
try:
|
||||
resp = client.post("/api/v1/plugins/fx-gui/close",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
json={"path": "C:\\VST\\Ozone11.vst3"})
|
||||
finally:
|
||||
plugins_mod._FX_GUI_PROCESSES.clear()
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["closed"] is True
|
||||
proc.terminate.assert_called_once()
|
||||
|
||||
def test_fx_gui_close_unknown_plugin(self):
|
||||
token = get_admin_token()
|
||||
if not token:
|
||||
pytest.skip("Cannot get admin token")
|
||||
resp = client.post("/api/v1/plugins/fx-gui/close",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
json={"path": "C:\\VST\\NoSuchPlugin.vst3"})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["closed"] is False
|
||||
|
||||
Reference in New Issue
Block a user