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).
9.4 KiB
PLAN: Nhúng native VST FX GUI vào Mastering Panel + FX Rack Panel
Ngày: 2026-08-17
Trạng thái: LINUX PHẦN HOÀN THÀNH (0L1, 0L2, 1L1, 2L1, 3L1, 4L1) — commit
<pending>; WINDOWS PHẦN CÒN LẠI (0W1, 0W2, 1W1, 2W1, 3W1, 4W1) — làm theo
WALKTHROUGH_WINDOWS.md. Tách task: TASKS_LINUX_WINDOWS.md.
Kế thừa: PLAN_MASTERBUS_FX_RACK_VST.md Phase 0-3 HOÀN THÀNH (commit
b261eb0 render FX, 38f4cbd mở native VST GUI dạng cửa sổ rời qua
--open-fx-gui).
1. Mục tiêu
Hoàn thiện vòng đời VST FX trong MASTERING PANEL (MasteringModal) và
FX RACK PANEL (FXRackModal):
- Nút X trên slot plugin → xóa plugin khỏi ô slot.
→ ĐÃ CÓ (
removeVst/removeMasterVst+<button>X trongvstRow,app/static/js/app.jsx:11757). Chỉ xác minh, không code lại. - Nút + → thêm VST trực tiếp vào panel VÀ load native VST GUI ngay
trong panel — thay vì cửa sổ rời như hiện tại (
SonicAPI.openFxGui→ bridge--open-fx-guimở window 800x600 tách biệt). - Đóng GUI → panel trở về graph GUI. Click slot VST đã load → mở lại GUI nhúng, thay thế vùng graph GUI của panel bằng native VST GUI ở đó.
2. Hiện trạng
| Hạng mục | Hiện tại | Delta cần |
|---|---|---|
| Slot VST masterbus | masterVstChain (vstFxChain) + vstRow |
giữ |
| Slot VST per-track | track.vstFxChain + vstRow |
giữ |
| Nút X xóa slot | có (removeV*) |
xác minh |
| Nút + thêm + mở GUI | có (addVst/addMasterVst → openVstGui/openMasterVstGui) |
đổi từ cửa sổ rời → GUI nhúng trong panel |
| GUI mở | bridge --open-fx-gui (RenderFxJob.cpp:889+, FxGuiWndProc, fxGuiAttachSafe, IPlugView attach HWND) |
giữ làm nền, thêm capture + input relay |
| Graph GUI của panel | MasteringModal: vùng canvas EQ/imager/meters (eqCanvasRef…); FXRackModal: scopeCanvasRef/eqCurveRef |
vùng này bị thay thế khi GUI mở |
| Tauri | src-tauri v2 có, app cũng chạy qua LAN browser | GUI nhúng phải chạy cả 2 môi trường → không dùng overlay native window |
Quyết định kiến trúc: app là web (browser/Tauri webview) → không thể nhúng
HWND trực tiếp. Dùng frame-capture + input relay (kiểu VNC cho plugin
window): bridge giữ native editor window, chụp bitmap → HTTP, frontend hiển
thị trong panel và gửi pointer/key về bridge → SendMessage tới HWND plugin.
3. Kiến trúc đích
3.1. Bridge: mode --fx-gui <job.json> (C++, Windows, MIT)
Mở rộng từ --open-fx-gui hiện có (giữ nguyên phần attach IPlugView):
- Sau khi attach view (đã có
fxGuiAttachSafe), chạy HTTP server mini (port ngẫu nhiên, ghi vào job output/stdout để Python đọc):GET /frame→ PNG/JPEG chụp từ HWND plugin bằngBitBlt(fallbackPrintWindowvớiPW_RENDERFULLCONTENT) — trảimage/jpeg, chất lượng ~70 để giảm băng thông LAN.POST /input{ type: "mousedown|mousemove|mouseup|wheel|key", x, y, button, deltaY, keyCode }→PostMessage(hwnd, WM_...)theo tọa độ đã scale (window plugin có thể lớn hơn vùng hiển thị → scale client rect).POST /close→ detach view, thoát tiến trình (exit 0).GET /ping→ alive check.
- Giữ nguyên SEH guard + registry chống mở 2 GUI (
_FX_GUI_PROCESSESphía Python). - Vị trí:
native_bridge/src/RenderFxJob.cpp(hoặc file mớiFxGuiServer.cppcạnh nó — theo kích thước, tách nếu >300 dòng).
3.2. Python API (app/api/v1/plugins.py — mở rộng open_fx_gui)
POST /api/v1/plugins/fx-gui/open{ path, name }→ spawn bridge--fx-gui, đọc port, trả{ success, embed_url: "http://127.0.0.1:<port>", already_running }. Registry_FX_GUI_PROCESSESnhư hiện có.POST /api/v1/plugins/fx-gui/close{ path }→ kill process (đã có luồng cleanup ở_register_fx_gui_process).- Không cần proxy qua FastAPI: frontend gọi thẳng
embed_url(CORS:plugins.pyđã có middleware — kiểm tra thêmAccess-Control-Allow-Origincho port động).
3.3. UI — app/static/js/app.jsx (dùng chung cho 2 panel qua vstRow)
vstRowthêm state per-slot:guiOpen: { url } | null(state nằm ở panel component, không phải trongvstRow— truyền qua props).- Render slot: nút GUI hiện tại → mở/đóng toggle: nếu
guiOpennull → gọiopenFxGuiEmbed(API mới); nếu đang mở → hiển thị vùng embed. - Vùng embed:
<div className="...">chứa<img src={url + '/frame'}>refresh bằngsetInterval~100ms (10fps), overlay pointer handlers (onMouseDown/Move/Up,onWheel) →fetch(url + '/input', POST)với tọa độ relative tính theo bounding rect; keydown/keyup trên container.- Trong
MasteringModal: vùng này thay thế vùng graph (khu vực canvas EQ/imager — xác định container chính xác lúc implement; ẩn bằnghiddenclass khi GUI mở, hiện lại khi close). - Trong
FXRackModal: thay thế vùng scope/eq canvas.
- Trong
- Click vào tên VST đã load trong slot (span tên hoặc chính slot) khi GUI
đang đóng → mở lại embed (re-attach qua
/framepoll, ping trước). - Nút đóng GUI: nút X riêng trên vùng embed (hoặc nút GUI toggle) → gọi
/close, dừng interval, trả graph về. - Xóa slot (X) khi GUI đang mở → cũng phải đóng GUI (gọi
/close). - Nếu
/pingfail (bridge chết ngoài ý muốn) → tự trả về graph + toast.
3.4. Không đổi
- Offline render (
--render-fx), preset upload, Carla bridge,--scan— giữ nguyên. GUI nhúng chỉ là hiển thị/điều khiển, không đổi data model (vstFxChaingiữ nguyên shape).
4. Phases
| Phase | Nội dung | Deliverable | Test |
|---|---|---|---|
| 0 | Bridge --fx-gui: HTTP server + BitBlt/PrintWindow capture + input relay |
mở GUI Ozone trong browser tab thủ công qua URL | self-check: /frame trả JPEG kích thước khớp; /input click đúng vị trí |
| 1 | Python: open trả embed_url, registry, close, CORS |
curl: open → frame 200 | unit: process spawn/cleanup |
| 2 | MasteringModal: embed + swap graph↔GUI + click mở lại | UI mastering hoàn chỉnh | E2E thủ công |
| 3 | FXRackModal: tương tự | UI fx rack hoàn chỉnh | E2E thủ công |
| 4 | Dọn: toast lỗi, cleanup interval, docs | commit | full test suite hiện có không vỡ |
Phase 0-1 có thể merge (cùng chạm bridge + plugins.py).
5. Rủi ro / giới hạn
- Plugin dùng layered window / DirectX surface →
PrintWindowra đen: fallback capture child control HWND hoặc dùngBitBlttừGetDC(NULL)vùng rect — xử lý theo plugin cụ thể (Ozone chuẩn GDI, khả năng cao OK). - Input tọa độ: scale theo client rect; plugin bắt absolute tọa độ cần
WM_MOUSEMOVEtrước click (gửi mousemove kèm). - FPS ~10-15 đủ chỉnh thông số; không phải realtime video — ghi chú UI.
- Chỉ Windows desktop có VST GUI (Linux bridge SF2/SFZ — giữ nguyên).
- Keyboard input: chỉ forward khi panel đang focus, tránh nuốt phím toàn app.
6. License
- Không đổi:
native_bridgeMIT, không nhúng binary plugin, không quay lại pedalboard.
7. Ghi chú triển khai (Linux — đã làm)
native_bridge/src/FxGuiServer.cpp(mới, ~316 dòng): HTTP server Winsock trên worker thread;/frame=BitBlt+ fallbackPrintWindow PW_RENDERFULLCONTENTkhi ảnh toàn đen, JPEG quality 70 qua libjpeg-turbo (jpeg_mem_dest);/inputrelayPostMessage(mousedown/move/up, wheel deltaY*120, keydown/up, coords scale client rect);/closestop flag +WM_CLOSE;GET /404.int fxGuiServerLoop(HWND)chạy message pump STA, join HTTP thread.RenderFxJob.cpp: táchfxGuiSetup()dùng chung;run_open_fx_guigiữ hành vi cũ; thêmrun_fx_gui_server(jobPath)(offscreen, userData=0); stub#else !_WIN32trả 1.main.cpp: dispatch--fx-gui <job.json>.vcpkg.jsonthêmlibjpeg-turbo;CMakeLists.txtchỉ build FxGuiServer khi WIN32 +find_package(JPEG REQUIRED)+JPEG::JPEG.app/api/v1/plugins.py:FxGuiRequest.embed, modelFxGuiCloseRequest,open_fx_guispawn--fx-guiđọcSF_FXGUI_PORT=từ stdout →embed_url, trả{success, started, already_running, embed_url, cmd}; thêmPOST /api/v1/plugins/fx-gui/close(terminate → wait 5s → kill → prune). CORSallow_origins=["*"]+ bridge tự gửi ACAO*.app/static/js/app.jsx: componentVstGuiEmbed(module scope, dùng chung 2 panel) — img refresh 100ms, ping 2s, relay pointer/wheel/key với tọa độ scale, nút "✕ Đóng GUI" →/close. MasteringModal + FXRackModal: stateguiOpen {path,url}, swap vùng graph↔GUI bằng ternary, cleanup unmount, bridge chết → toast + trả về graph.- Tests:
tests/test_plugin_api.py+4 (mở embed trả url, không port → 500, close kill, close unknown) — mockfind_bridge_exe+Popen. - Kết quả:
node build.mjsBUILD OK; pytest 116 passed, 1 failed (test_find_bridge_exe_devpre-existing — Linux thiếu binary bridge). - Giới hạn: bridge C++ chưa build/test thật (Linux không có CMake/Win32) — Windows làm 0W1/0W2; E2E GUI nhúng thật chưa chạy — Windows làm 1W1-4W1.