M7-M16: VST FX GUI native embed hoàn thiện — fix click-2-lần chuyển slot, anchor lần mở đầu, taskbar icon

- app.jsx: masterGuiOpenRef sync truoc setState — guard chuyen slot doc ref, het race closure (click 1 lan chuyen slot)
- JuceFxGuiHost.cpp: M14 applyAnchor truoc setVisible (het flash giua man hinh); M15 clear WS_EX_APPWINDOW + set TOOLWINDOW (het taskbar icon — JUCE set APPWINDOW 0x40000 thang TOOLWINDOW); M9 owner DAW + M9d skip applyOwner khi an
- JuceFxLoop.cpp: M16 bo clearAnchor khi ed.open (retry ed.open ~700ms xoa rect fresh -> window moi khong anchor duoc lan dau mo panel); chi clear khi Close
- JuceFxEngine/app.jsx/plugins.py/fx_realtime.py: editor session realtime, anchor rect routing, retry ed.open
- Rebuild engine PyInstaller + juce_fx_bridge cmake; binaries dong bo 5 noi (bridge 7464a7ef)
This commit is contained in:
2026-09-06 19:45:14 +07:00
parent ec2c925d09
commit ef521d9981
20 changed files with 2040 additions and 461 deletions
+89 -5
View File
@@ -142,6 +142,10 @@ class FxRealtimeSession:
# chủ đích → KHÔNG restart (cờ _closing / running=0 — user gọi lại).
self._closing = False
self._restarts = 0
# M8: bridge JUCE doc job 1 lan luc spawn (khong poll seq) -> set_chain
# phai reload (terminate -> watchdog respawn). Co chong dem crash.
self._juce_bridge = False
self._reload_planned = False
self._watch = threading.Thread(target=self._watch_loop, daemon=True)
self._spawn()
self._watch.start()
@@ -197,7 +201,9 @@ class FxRealtimeSession:
fd, self._job_path = tempfile.mkstemp(suffix=".json", prefix="fxrt_")
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(job, f)
if os.path.basename(exe).lower().startswith("juce_fx_bridge"):
self._juce_bridge = os.path.basename(exe).lower().startswith("juce_fx_bridge")
_log.info("[fxrt] spawn exe=%s juce=%s seq=%d shm=%s", os.path.basename(exe), self._juce_bridge, self._chain_seq, self.name)
if self._juce_bridge:
cmd = [exe, "--juce-fx", self._job_path, "--shm", self.name,
"--parent", str(os.getpid())]
else:
@@ -209,14 +215,22 @@ class FxRealtimeSession:
# stall (procMax=bridge/VST cham, outDepth=Python pump cham).
_blog = os.environ.get("FXRT_BRIDGE_LOG")
if not _blog:
# M13: per-session log (fxrt_bridge_<shm-suffix>.log) — trước đây 1
# file chung cho MỌI session → log 2 process lẫn nhau. Env override
# giữ nguyên để ép 1 file khi cần.
_ad = os.environ.get("APPDATA")
_blog = os.path.join(_ad, "SonicForgeDAW", "logs", "fxrt_bridge.log") if _ad else ""
if _ad:
_blog = os.path.join(_ad, "SonicForgeDAW", "logs",
"fxrt_bridge_" + self.name + ".log")
else:
_blog = ""
self.proc = subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=open(_blog, "ab", buffering=0) if _blog else subprocess.DEVNULL,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
)
_log.info("[fxrt] spawned pid=%d log=%s", self.proc.pid, _blog)
def set_chain(self, fx_chain):
"""Cập nhật chain trên session ĐANG CHẠY (không restart session):
@@ -238,8 +252,56 @@ class FxRealtimeSession:
}, f)
except Exception:
return False
# M8: juce_fx_bridge doc job CHI 1 LAN luc spawn (JuceFxLoop.cpp khong
# poll seq nhu RealtimeFxLoop cu) -> chi ghi job KHONG bao gio ap len
# bridge: chain bridge giu nguyen trong khi engine chain da doi -> GUI
# mo slot moi C++ bao "slot ngoai chain (M) - bo qua". Reload chu dong:
# terminate -> watchdog respawn doc chain hien tai (khong tinh crash,
# khong dung gioi han FXRT_MAX_RESTARTS). Legacy fx_vst_bridge poll seq
# giu nguyen (async swap muot - khong reload).
_log.info("[fxrt] set_chain seq=%d len=%d juce=%s proc_alive=%s reload_planned=%s",
self._chain_seq, len(self.chain), self._juce_bridge,
self.proc is not None and self.proc.poll() is None,
self._reload_planned)
if self._juce_bridge:
self._request_chain_reload()
_log.info("[fxrt] set_chain requested reload proc_pid=%s",
self.proc.pid if self.proc else None)
return True
def _request_chain_reload(self):
"""M8: yeu cau watchdog respawn sach voi chain hien tai (chi juce
bridge). Khong block: terminate proc -> vong watchdog (200ms) thay chet
+ co _reload_planned -> respawn doc job moi, khong tinh crash."""
with self._lock:
if self._closing or self.proc is None or self.proc.poll() is not None:
return
self._reload_planned = True
try:
self.proc.terminate()
except Exception:
self._reload_planned = False
def wait_chain_ready(self, timeout=20.0):
"""M8: cho chain moi thuc su ap tren bridge sau set_chain (juce reload
async): het co reload + proc respawn READY. Khong co reload -> tra ngay
True. False khi session dong/chet/qua han."""
deadline = time.time() + timeout
while time.time() < deadline:
with self._lock:
if self._closing:
return False
p = self.proc
reloading = self._reload_planned
alive = p is not None and p.poll() is None
ready = alive and self.h.state == FXRT_STATE_READY
if not reloading and ready:
return True
if not alive and not reloading:
return False
time.sleep(0.1)
return False
def wait_ready(self, timeout=30.0):
deadline = time.time() + timeout
while time.time() < deadline:
@@ -471,6 +533,24 @@ class FxRealtimeSession:
except Exception:
return False
def send_editor_rect(self, slot, x, y, w, h):
"""M7: anchor rect cho GUI native (window nằm ĐÚNG rect panel embed).
Gửi 4 key SHM ctrl ed.rx/ry/rw/rh — MỖI key 1 số nguyên float (chính
xác tuyệt đối |v| <= 2^24; x/y âm khi panel nằm màn hình trái OK vì C++
parse int trực tiếp — KHÔNG encode w*4096+h như ed.resize, giới hạn
4095). JuceFxGuiHost lưu anchor (thread-safe) + reconcile GUI thread
30ms SetWindowPos khi rect ĐỔI. Trả False nếu session không SHM."""
with self._lock:
if getattr(self, "shm", None) is None:
return False
try:
for f, v in (("x", int(x)), ("y", int(y)),
("w", int(w)), ("h", int(h))):
self._ctrl_enqueue_locked(int(slot), "ed.r" + f, float(v))
return True
except Exception:
return False
def chain_slot_for_path(self, plugin_path):
"""Slot index (theo chain bridge ĐANG CHẠY — đã lọc active=false, giữ
thứ tự) của plugin path; None nếu plugin không trong chain. Index khớp
@@ -539,11 +619,15 @@ class FxRealtimeSession:
if getattr(self.h, "running", 1) == 0:
_log.warning("[fxrt] bridge thoát rc=%s sau running=0 (user kill — không restart)", rc)
return
if self._restarts >= FXRT_MAX_RESTARTS:
if self._reload_planned:
self._reload_planned = False
_log.info("[fxrt] bridge rc=%s — reload chain chủ động (set_chain juce) → respawn đọc chain mới", rc)
elif self._restarts >= FXRT_MAX_RESTARTS:
_log.error("[fxrt] bridge crash rc=%s — restart tối đa %d lần đã đạt, bỏ", rc, FXRT_MAX_RESTARTS)
return
self._restarts += 1
_log.warning("[fxrt] bridge chết rc=%s — auto restart #%d", rc, self._restarts)
else:
self._restarts += 1
_log.warning("[fxrt] bridge chết rc=%s — auto restart #%d", rc, self._restarts)
try:
self._spawn()
except Exception as e: