fix(tcp): open VSTi GUI via bridge API on LAN browser; scale vol/pan ruler wheel by deltaY
This commit is contained in:
@@ -1291,3 +1291,137 @@ async def bridge_log(lines: int = Query(100, ge=1, le=2000), current_user: dict
|
||||
return {"lines": all_lines[-lines:], "path": log, "exists": True}
|
||||
except Exception as e:
|
||||
return {"lines": [], "path": log, "exists": False, "error": str(e)}
|
||||
|
||||
# ── E6: LAN/standalone fallback — mở GUI VSTi qua API (không cần Tauri) ─────
|
||||
# Browser client (LAN) không có window.__TAURI__ → NativeBridgeService chỉ log,
|
||||
# không invoke được open_vst_gui. Engine đẩy LOAD_INSTRUMENT + OPEN_GUI thẳng
|
||||
# vào SHM của daw_vst_bridge (layout SharedMemoryIPC.h: controlQueue @2836,
|
||||
# stride 1040, controlQueueCount @11156 — offsets verified bằng ctypes test).
|
||||
# GUI hiện trên desktop máy chạy server.
|
||||
_IT_ENUM = {"VST3": 0, "VST2": 1, "SF2": 2, "SF3": 2, "SFZ": 3}
|
||||
|
||||
class BridgeGuiRequest(BaseModel):
|
||||
name: str # plugin id / tên asset
|
||||
path: Optional[str] = None # path tuyệt đối nếu đã biết
|
||||
instrumentType: Optional[str] = None # VST3|VST2|SF2|SF3|SFZ; None → tự suy
|
||||
channel: int = 0 # MIDI channel bridge assign/open
|
||||
|
||||
def _resolve_bridge_asset_any(name: str, path: Optional[str], hint: Optional[str] = None):
|
||||
"""Thử resolve asset qua từng loại instrument (hint trước, rồi VST3→VST2→
|
||||
SF…). Client LAN không chắc type — backend tự tìm, tránh 404 nhầm (VST2
|
||||
.dll mà client đoán VST3). Trả (path, instrument_type suy từ extension)."""
|
||||
order = []
|
||||
if hint and hint.upper() in _IT_ENUM:
|
||||
order.append(hint.upper())
|
||||
for t in order + [x for x in ("VST3", "VST2", "SF2", "SF3", "SFZ") if x not in order]:
|
||||
p = _resolve_bridge_asset(name, t, path)
|
||||
if p:
|
||||
low = p.lower()
|
||||
if low.endswith(".dll"):
|
||||
t = "VST2"
|
||||
elif low.endswith(".sfz"):
|
||||
t = "SFZ"
|
||||
elif low.endswith(".sf2") or low.endswith(".sf3"):
|
||||
t = "SF2"
|
||||
else:
|
||||
t = "VST3"
|
||||
return p, t
|
||||
return None, None
|
||||
|
||||
def _running_sonicforge_pids() -> list:
|
||||
pids = []
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["powershell", "-NoProfile", "-Command",
|
||||
"Get-Process sonicforge-daw,daw_engine -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id"],
|
||||
capture_output=True, text=True, timeout=10).stdout
|
||||
for line in out.splitlines():
|
||||
line = line.strip()
|
||||
if line.isdigit():
|
||||
pids.append(int(line))
|
||||
except Exception:
|
||||
pass
|
||||
return pids
|
||||
|
||||
def _shm_exists(name: str) -> bool:
|
||||
import ctypes, ctypes.wintypes as w
|
||||
k = ctypes.windll.kernel32
|
||||
k.OpenFileMappingA.argtypes = [w.DWORD, w.BOOL, ctypes.c_char_p]
|
||||
k.OpenFileMappingA.restype = ctypes.c_void_p
|
||||
h = k.OpenFileMappingA(0xF001F, False, name.encode())
|
||||
if h:
|
||||
k.CloseHandle(h)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _shm_push_gui(shm_name: str, path: str, enum: int, plugin_id: str, channel: int):
|
||||
"""Push LOAD_INSTRUMENT (type=2) + OPEN_GUI (type=4) vào SHM bridge.
|
||||
C++ xử lý async — LOAD xong mới fulfil OPEN_GUI (pending-GUI sweep)."""
|
||||
import ctypes, ctypes.wintypes as w, struct
|
||||
k = ctypes.windll.kernel32
|
||||
k.OpenFileMappingA.argtypes = [w.DWORD, w.BOOL, ctypes.c_char_p]
|
||||
k.OpenFileMappingA.restype = ctypes.c_void_p
|
||||
k.MapViewOfFile.argtypes = [ctypes.c_void_p, w.DWORD, w.DWORD, w.DWORD, ctypes.c_size_t]
|
||||
k.MapViewOfFile.restype = ctypes.c_void_p
|
||||
k.UnmapViewOfFile.argtypes = [ctypes.c_void_p]
|
||||
k.CloseHandle.argtypes = [ctypes.c_void_p]
|
||||
h = k.OpenFileMappingA(0xF001F, False, shm_name.encode())
|
||||
if not h:
|
||||
return False, "open shm fail"
|
||||
ptr = k.MapViewOfFile(h, 0xF001F, 0, 0, 0)
|
||||
if not ptr:
|
||||
k.CloseHandle(h)
|
||||
return False, "map shm fail"
|
||||
try:
|
||||
for i in range(8): # zero queue trước — tránh slot cũ lọt vào count mới
|
||||
ctypes.memmove(ptr + 2836 + i * 1040, b"\x00" * 1040, 1040)
|
||||
# LOAD_INSTRUMENT: type=2, arg0=InstrumentType, arg1=0 (Rust bỏ qua), channel, arg2=path
|
||||
b = path.encode("utf-8")[:1023] + b"\x00"
|
||||
ctypes.memmove(ptr + 2836, struct.pack("IIII", 2, enum, 0, channel), 16)
|
||||
ctypes.memmove(ptr + 2836 + 16, b, len(b))
|
||||
# OPEN_GUI: type=4, arg0=0, arg1=0 (hwnd=0 → bridge tự tạo native window), channel, arg2=plugin id
|
||||
pb = plugin_id.encode("utf-8")[:1023] + b"\x00"
|
||||
off = 2836 + 1040
|
||||
ctypes.memmove(ptr + off, struct.pack("IIII", 4, 0, 0, channel), 16)
|
||||
ctypes.memmove(ptr + off + 16, pb, len(pb))
|
||||
ctypes.memmove(ptr + 11156, struct.pack("I", 2), 4) # controlQueueCount = 2
|
||||
return True, None
|
||||
finally:
|
||||
k.UnmapViewOfFile(ptr)
|
||||
k.CloseHandle(h)
|
||||
|
||||
@router.post("/bridge/gui")
|
||||
async def bridge_gui(req: BridgeGuiRequest, current_user: dict = Depends(get_current_user)):
|
||||
"""E6: mở native GUI VSTi trên máy chạy server — fallback cho browser LAN
|
||||
không có Tauri. Pipeline giống invoke open_vst_gui: LOAD_INSTRUMENT rồi
|
||||
OPEN_GUI qua SHM daw_vst_bridge."""
|
||||
resolved, itype = _resolve_bridge_asset_any(req.name, req.path, req.instrumentType)
|
||||
if not resolved:
|
||||
raise HTTPException(status_code=404, detail=f"Không tìm thấy asset: {req.name}")
|
||||
enum = _IT_ENUM.get(itype.upper(), 0)
|
||||
shm_name = None
|
||||
ipc = _bridge_ipc_dir()
|
||||
if ipc:
|
||||
st = os.path.join(ipc, "bridge_status")
|
||||
if os.path.exists(st):
|
||||
try:
|
||||
with open(st, "r", encoding="utf-8") as fh:
|
||||
data = json.loads(fh.read() or "{}")
|
||||
shm_name = data.get("shm_name")
|
||||
except Exception:
|
||||
pass
|
||||
if not shm_name:
|
||||
for pid in _running_sonicforge_pids():
|
||||
cand = f"SonicForge_DAW_IPC_{pid}"
|
||||
if _shm_exists(cand):
|
||||
shm_name = cand
|
||||
break
|
||||
if not shm_name:
|
||||
raise HTTPException(status_code=503,
|
||||
detail="Bridge chưa chạy (không tìm thấy SHM) — hãy mở app desktop trên máy chạy server trước")
|
||||
ch = req.channel & 0xF
|
||||
ok, err = _shm_push_gui(shm_name, resolved, enum, req.name, ch)
|
||||
if not ok:
|
||||
raise HTTPException(status_code=503, detail=f"Không ghi được SHM {shm_name}: {err}")
|
||||
return {"success": True, "path": resolved, "type": itype, "enum": enum,
|
||||
"channel": ch, "shm": shm_name}
|
||||
|
||||
Reference in New Issue
Block a user