fix(fx-gui): meter VST chạy đúng — bỏ điều kiện inRead chặn peek + chống race lúc load + ẩn cửa sổ native floating
This commit is contained in:
+15
-1
@@ -1,4 +1,4 @@
|
|||||||
import os, sys, uuid, json, tempfile, subprocess, time as _time, threading, asyncio
|
import os, sys, uuid, json, tempfile, subprocess, time as _time, threading, asyncio, logging
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import soundfile as sf
|
import soundfile as sf
|
||||||
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, BackgroundTasks, Header, Query, WebSocket, WebSocketDisconnect
|
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, BackgroundTasks, Header, Query, WebSocket, WebSocketDisconnect
|
||||||
@@ -17,6 +17,8 @@ from app.core.auth import decode_token
|
|||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
UPLOAD_SF_DIR = os.path.join(settings.STORAGE_DIR, "soundfonts")
|
UPLOAD_SF_DIR = os.path.join(settings.STORAGE_DIR, "soundfonts")
|
||||||
os.makedirs(UPLOAD_SF_DIR, exist_ok=True)
|
os.makedirs(UPLOAD_SF_DIR, exist_ok=True)
|
||||||
|
|
||||||
@@ -871,6 +873,18 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
|
|||||||
except Exception:
|
except Exception:
|
||||||
proc.kill()
|
proc.kill()
|
||||||
raise HTTPException(status_code=500, detail="Bridge không báo port fx-gui (mở GUI thất bại).")
|
raise HTTPException(status_code=500, detail="Bridge không báo port fx-gui (mở GUI thất bại).")
|
||||||
|
# Keep draining stdout in a daemon thread: the bridge logs to
|
||||||
|
# stdout/stderr while running; if the pipe fills (64KB) the bridge
|
||||||
|
# blocks on any later write -> GUI freezes/breaks.
|
||||||
|
def _drain_fx_gui_stdout(pp):
|
||||||
|
try:
|
||||||
|
for ln in pp.stdout:
|
||||||
|
ln = (ln or "").strip()
|
||||||
|
if ln:
|
||||||
|
logger.info("[fx-gui] %s", ln)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
threading.Thread(target=_drain_fx_gui_stdout, args=(proc,), daemon=True).start()
|
||||||
proc._sf_plugin_path = plugin_path
|
proc._sf_plugin_path = plugin_path
|
||||||
proc._sf_embed_url = embed_url
|
proc._sf_embed_url = embed_url
|
||||||
_register_fx_gui_process(proc)
|
_register_fx_gui_process(proc)
|
||||||
|
|||||||
@@ -270,52 +270,14 @@ uint64_t frameHash(HWND hwnd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void danceStart(HWND hwnd, HWND target, int x, int y) {
|
void danceStart(HWND hwnd, HWND target, int x, int y) {
|
||||||
// Anchored mode: place the host window's client area exactly over the embed
|
// Embedded mode: the native window NEVER comes on-screen — showing it
|
||||||
// panel rect (borderless + topmost) so real mouse events hit the plugin
|
// topmost over the panel was the "floating always-on-top" bug. Real
|
||||||
// without moving the cursor, then ask the pump thread to size the plugin
|
// hardware input would require the window on-screen, so it is disabled;
|
||||||
// view to the panel. No park deadline -- the window stays until /close.
|
// relayInput always uses synthetic PostMessage.
|
||||||
// Re-runs on every click so a scrolled/resized panel re-anchors.
|
// ponytail: re-enable real input only with a proper sibling placement
|
||||||
if (g_embedRect.right > g_embedRect.left && g_embedRect.bottom > g_embedRect.top) {
|
// (owned window, WS_EX_NOACTIVATE, no WS_EX_TOPMOST) if a plugin rejects
|
||||||
const int cw = g_embedRect.right - g_embedRect.left;
|
// synthetic clicks.
|
||||||
const int ch = g_embedRect.bottom - g_embedRect.top;
|
(void)hwnd; (void)target; (void)x; (void)y;
|
||||||
LONG_PTR style = GetWindowLongPtrA(hwnd, GWL_STYLE);
|
|
||||||
if (style & (WS_CAPTION | WS_THICKFRAME | WS_SYSMENU |
|
|
||||||
WS_MINIMIZEBOX | WS_MAXIMIZEBOX)) {
|
|
||||||
style &= ~(WS_CAPTION | WS_THICKFRAME | WS_SYSMENU |
|
|
||||||
WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
|
|
||||||
SetWindowLongPtrA(hwnd, GWL_STYLE, style);
|
|
||||||
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
|
|
||||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
|
|
||||||
SWP_NOACTIVATE | SWP_FRAMECHANGED);
|
|
||||||
}
|
|
||||||
SetWindowPos(hwnd, HWND_TOPMOST, g_embedRect.left, g_embedRect.top,
|
|
||||||
cw, ch, SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
|
||||||
fxGuiRequestResize(hwnd, cw, ch);
|
|
||||||
Sleep(60); // let the compositor repaint so the editor sees itself active
|
|
||||||
g_dance = true;
|
|
||||||
g_anchored = true;
|
|
||||||
g_parkUntil = 0; // anchored: stay until /close
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (g_dance) return;
|
|
||||||
// Dat window ngay tai vi tri cursor (dang nam tren panel embed trong DAW)
|
|
||||||
// chu khong nhay ra goc man hinh: shift window sao cho client point (x,y)
|
|
||||||
// cua editor trung voi screen point dang click -> GUI xuat hien dung cho
|
|
||||||
// panel, khong con floating ngoai cua so DAW.
|
|
||||||
POINT pt = { x, y };
|
|
||||||
ClientToScreen(target, &pt); // screen cua diem click (window con offscreen)
|
|
||||||
POINT cur;
|
|
||||||
GetCursorPos(&cur);
|
|
||||||
RECT wr = {0};
|
|
||||||
GetWindowRect(hwnd, &wr);
|
|
||||||
const int nx = wr.left + (cur.x - pt.x);
|
|
||||||
const int ny = wr.top + (cur.y - pt.y);
|
|
||||||
GetCursorPos(&g_savedCursor);
|
|
||||||
SetWindowPos(hwnd, HWND_TOP, nx, ny, 0, 0,
|
|
||||||
SWP_NOSIZE | SWP_SHOWWINDOW);
|
|
||||||
Sleep(60); // let the compositor repaint so the editor sees itself active
|
|
||||||
g_dance = true;
|
|
||||||
g_parkUntil = GetTickCount() + 3000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void danceEnd(HWND hwnd) {
|
void danceEnd(HWND hwnd) {
|
||||||
@@ -382,12 +344,7 @@ void relayInput(HWND hwnd, const json_object_s* o) {
|
|||||||
// responds (reset miss); two consecutive no-response clicks mean the
|
// responds (reset miss); two consecutive no-response clicks mean the
|
||||||
// editor rejects synthetic input — switch to real hardware input.
|
// editor rejects synthetic input — switch to real hardware input.
|
||||||
if (!g_realInput) {
|
if (!g_realInput) {
|
||||||
if (g_embedRect.right > g_embedRect.left) {
|
if (isDown) {
|
||||||
// Anchored mode exists for plugins that need real input; the window
|
|
||||||
// appears exactly over the panel, so probing PostMessage first only
|
|
||||||
// wastes clicks.
|
|
||||||
g_realInput = true;
|
|
||||||
} else if (isDown) {
|
|
||||||
g_preClickHash = frameHash(hwnd);
|
g_preClickHash = frameHash(hwnd);
|
||||||
} else if (isUp && g_preClickHash) {
|
} else if (isUp && g_preClickHash) {
|
||||||
Sleep(250);
|
Sleep(250);
|
||||||
@@ -396,8 +353,7 @@ void relayInput(HWND hwnd, const json_object_s* o) {
|
|||||||
if (resp) {
|
if (resp) {
|
||||||
g_miss = 0; // PostMessage responded — keep it
|
g_miss = 0; // PostMessage responded — keep it
|
||||||
} else if (++g_miss >= 2) {
|
} else if (++g_miss >= 2) {
|
||||||
g_realInput = true; // synthetic input rejected — go real
|
g_miss = 0; // real-input mode disabled: window must stay off-screen
|
||||||
g_miss = 0;
|
|
||||||
}
|
}
|
||||||
g_preClickHash = 0;
|
g_preClickHash = 0;
|
||||||
}
|
}
|
||||||
@@ -556,29 +512,19 @@ void runHttpServer(HWND hwnd, std::atomic<bool>& stop) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sroot) std::free(sroot);
|
if (sroot) std::free(sroot);
|
||||||
if (rectOk) {
|
// Embedded mode: the native window NEVER comes on-screen — it stays
|
||||||
const int cw = g_embedRect.right - g_embedRect.left;
|
// parked off-screen at -20000,-20000 (GDI capture + synthetic input
|
||||||
const int ch = g_embedRect.bottom - g_embedRect.top;
|
// work regardless of position). The panel rect is still recorded
|
||||||
RECT cur = {};
|
// for the (disabled) real-input fallback and to keep the API
|
||||||
GetWindowRect(hwnd, &cur);
|
// contract; /hide hid the window, so re-show it off-screen so
|
||||||
const bool same = cur.left == g_embedRect.left && cur.top == g_embedRect.top &&
|
// /frame keeps capturing.
|
||||||
cur.right - cur.left == cw && cur.bottom - cur.top == ch;
|
(void)rectOk;
|
||||||
// Frontend re-POSTs /show every ~500ms to re-anchor on scroll;
|
if (!IsWindowVisible(hwnd))
|
||||||
// skip both when nothing moved so the plugin view is not
|
SetWindowPos(hwnd, HWND_NOTOPMOST, -20000, -20000, 0, 0,
|
||||||
// re-laid-out in a loop. Re-show after /hide regardless.
|
SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||||
if (!same || !IsWindowVisible(hwnd))
|
g_dance = false;
|
||||||
SetWindowPos(hwnd, HWND_TOPMOST, g_embedRect.left, g_embedRect.top,
|
g_anchored = false;
|
||||||
cw, ch, SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
g_parkUntil = 0;
|
||||||
if (cur.right - cur.left != cw || cur.bottom - cur.top != ch)
|
|
||||||
fxGuiRequestResize(hwnd, cw, ch);
|
|
||||||
g_dance = true;
|
|
||||||
g_anchored = true;
|
|
||||||
g_parkUntil = 0;
|
|
||||||
} else {
|
|
||||||
ShowWindow(hwnd, SW_SHOW);
|
|
||||||
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
|
|
||||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
|
||||||
}
|
|
||||||
sendResponse(c, 200, "application/json", "{\"ok\":true}", 11);
|
sendResponse(c, 200, "application/json", "{\"ok\":true}", 11);
|
||||||
} else if (req.path == "/preset") {
|
} else if (req.path == "/preset") {
|
||||||
std::string b64;
|
std::string b64;
|
||||||
|
|||||||
@@ -1433,12 +1433,14 @@ static void fxGuiFeedLoop(Vst3Fx* fx, HWND hwnd, std::shared_ptr<FxGuiShmCtl> ct
|
|||||||
if (name != curName) {
|
if (name != curName) {
|
||||||
if (v) { fxGuiCloseShm(v); v = nullptr; ipc = nullptr; }
|
if (v) { fxGuiCloseShm(v); v = nullptr; ipc = nullptr; }
|
||||||
curName = name;
|
curName = name;
|
||||||
if (!name.empty()) v = fxGuiOpenShm(name, sizeof(FxRealtimeIPC));
|
|
||||||
ipc = v ? static_cast<FxRealtimeIPC*>(v->view) : nullptr;
|
|
||||||
lastWrite = 0; // session (re)start -> re-anchor
|
lastWrite = 0; // session (re)start -> re-anchor
|
||||||
}
|
}
|
||||||
|
if (!v && !name.empty()) { // retry open each pass: mapping may appear late
|
||||||
|
v = fxGuiOpenShm(name, sizeof(FxRealtimeIPC));
|
||||||
|
ipc = v ? static_cast<FxRealtimeIPC*>(v->view) : nullptr;
|
||||||
|
}
|
||||||
if (ipc && ipc->h.magic == FXRT_MAGIC &&
|
if (ipc && ipc->h.magic == FXRT_MAGIC &&
|
||||||
ipc->h.inWrite > lastWrite && ipc->h.inWrite > ipc->h.inRead) {
|
ipc->h.inWrite > lastWrite) {
|
||||||
if (ipc->h.inSlots == FXRT_IN_SLOTS && ipc->h.blockSize == FXRT_BLOCK) {
|
if (ipc->h.inSlots == FXRT_IN_SLOTS && ipc->h.blockSize == FXRT_BLOCK) {
|
||||||
const uint32_t slot = (ipc->h.inWrite - 1) & (ipc->h.inSlots - 1);
|
const uint32_t slot = (ipc->h.inWrite - 1) & (ipc->h.inSlots - 1);
|
||||||
float L[FXRT_BLOCK], R[FXRT_BLOCK], oL[FXRT_BLOCK], oR[FXRT_BLOCK];
|
float L[FXRT_BLOCK], R[FXRT_BLOCK], oL[FXRT_BLOCK], oR[FXRT_BLOCK];
|
||||||
@@ -1453,15 +1455,21 @@ static void fxGuiFeedLoop(Vst3Fx* fx, HWND hwnd, std::shared_ptr<FxGuiShmCtl> ct
|
|||||||
if (v) fxGuiCloseShm(v);
|
if (v) fxGuiCloseShm(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detached feeder thread. initialShm may be "" — the frontend pushes the
|
// Split feeder setup: fxGuiShmSetName runs EARLY (before the server loop) so
|
||||||
// live session name later via /shm once the realtime session is up.
|
// /shm pushes from the frontend are not lost, while the detached thread starts
|
||||||
static void fxGuiStartAudioFeeder(Vst3Fx* fx, HWND hwnd, const std::string& initialShm) {
|
// only AFTER load+attach succeeded — a feeder calling processAudio() mid-load
|
||||||
|
// can crash/corrupt a heavy plugin and break the editor attach.
|
||||||
|
// ponytail: feeder feeds the raw live pre-chain mix; add a separate audio path
|
||||||
|
// if GUI processing of the full insert chain is wanted later.
|
||||||
|
static void fxGuiShmSetName(const std::string& initialShm) {
|
||||||
|
auto p = fxGuiShmCtlRef();
|
||||||
|
if (!p) { p = std::make_shared<FxGuiShmCtl>(); fxGuiShmCtlRef() = p; }
|
||||||
|
std::lock_guard<std::mutex> lk(p->m);
|
||||||
|
p->name = initialShm;
|
||||||
|
}
|
||||||
|
static void fxGuiStartFeederThread(Vst3Fx* fx, HWND hwnd) {
|
||||||
auto p = fxGuiShmCtlRef();
|
auto p = fxGuiShmCtlRef();
|
||||||
if (!p) { p = std::make_shared<FxGuiShmCtl>(); fxGuiShmCtlRef() = p; }
|
if (!p) { p = std::make_shared<FxGuiShmCtl>(); fxGuiShmCtlRef() = p; }
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lk(p->m);
|
|
||||||
p->name = initialShm;
|
|
||||||
}
|
|
||||||
std::thread([fx, hwnd, p]() { fxGuiFeedLoop(fx, hwnd, p); }).detach();
|
std::thread([fx, hwnd, p]() { fxGuiFeedLoop(fx, hwnd, p); }).detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1560,7 +1568,8 @@ int run_open_fx_gui(const std::string& jobPath) {
|
|||||||
const int rc = fxGuiSetup(jobPath, &fx, &hwnd, /*offscreen=*/false, (LONG_PTR)&ctx, shm);
|
const int rc = fxGuiSetup(jobPath, &fx, &hwnd, /*offscreen=*/false, (LONG_PTR)&ctx, shm);
|
||||||
if (rc) return rc;
|
if (rc) return rc;
|
||||||
fxGuiStartWatchdog(hwnd, fxGuiParentPid());
|
fxGuiStartWatchdog(hwnd, fxGuiParentPid());
|
||||||
fxGuiStartAudioFeeder(&fx, hwnd, shm);
|
fxGuiShmSetName(shm);
|
||||||
|
fxGuiStartFeederThread(&fx, hwnd);
|
||||||
|
|
||||||
// 5. Pump messages until the window is destroyed.
|
// 5. Pump messages until the window is destroyed.
|
||||||
MSG msg;
|
MSG msg;
|
||||||
@@ -1598,11 +1607,12 @@ int run_fx_gui_server(const std::string& jobPath) {
|
|||||||
if (rc) return rc;
|
if (rc) return rc;
|
||||||
fxGuiStartWatchdog(hwnd, fxGuiParentPid());
|
fxGuiStartWatchdog(hwnd, fxGuiParentPid());
|
||||||
SetPropA(hwnd, "FXGUI_FX", &fx); // for WM_FXGUI_RESIZE on the pump thread
|
SetPropA(hwnd, "FXGUI_FX", &fx); // for WM_FXGUI_RESIZE on the pump thread
|
||||||
fxGuiStartAudioFeeder(&fx, hwnd, shm);
|
fxGuiShmSetName(shm); // EARLY — frontend may /shm push before load finishes
|
||||||
|
|
||||||
int loadRc = 0;
|
int loadRc = 0;
|
||||||
const int srv = fxGuiServerLoop(hwnd, [&]() {
|
const int srv = fxGuiServerLoop(hwnd, [&]() {
|
||||||
loadRc = fxGuiLoadAttach(&fx, path, name, hwnd);
|
loadRc = fxGuiLoadAttach(&fx, path, name, hwnd);
|
||||||
|
if (loadRc == 0) fxGuiStartFeederThread(&fx, hwnd); // after load+attach only
|
||||||
if (loadRc != 0) PostMessageA(hwnd, WM_CLOSE, 0, 0); // exit; port already printed
|
if (loadRc != 0) PostMessageA(hwnd, WM_CLOSE, 0, 0); // exit; port already printed
|
||||||
});
|
});
|
||||||
RemovePropA(hwnd, "FXGUI_FX");
|
RemovePropA(hwnd, "FXGUI_FX");
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user