From ec2c925d09457cf8201c93c3b1e0cf58bcf57400 Mon Sep 17 00:00:00 2001 From: locpham Date: Fri, 4 Sep 2026 18:26:40 +0700 Subject: [PATCH] =?UTF-8?q?M6:=20fix=202-GUI=20khi=20m=E1=BB=9F=20VST=20FX?= =?UTF-8?q?=20native=20(mastering)=20=E2=80=94=20m=E1=BB=99t=20plugin=20ch?= =?UTF-8?q?=E1=BB=89=20m=E1=BB=99t=20GUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug .24: mastering panel load VST FX → 1 floating native (FX slot N) + 1 embed cùng lúc; floating không đóng được qua UI. Backend (/fx-gui): - native branch terminate bridge fx-gui fallback cũ cùng plugin_path trước ed.open (loại instance VST thứ 2 + window rời) - fallback spawn chặn khi editor native đang mở ở session sống → trả native (không spawn embed/float thứ 2) - _terminate_fx_gui_for_path dùng chung cho /fx-gui/close Frontend (M6 — xử lý r.native): - openMasterVstGui/openVstGui/auto-open/reopen: nhánh native → state {path, url:null, native:true} (không còn toast 'Không mở được GUI embed'), marker gui_open_path/vstGuiOpenPath giữ như embed - closeMasterGui/closeFxGui native → SonicAPI.closeFxGui (engine capture preset + ed.close); unmount/hide panel → SonicAPI.hideFxGui - panel render: GUI native mở = indicator + nút Đóng GUI (thay VstGuiEmbed) - api.js: closeFxGui/hideFxGui/showFxGui endpoints --- app/api/v1/plugins.py | 47 ++++++++++++++++++++++------ app/static/js/app.jsx | 59 +++++++++++++++++++++++++++-------- app/static/js/services/api.js | 6 ++++ 3 files changed, 90 insertions(+), 22 deletions(-) diff --git a/app/api/v1/plugins.py b/app/api/v1/plugins.py index 836b86f..d15bdc5 100644 --- a/app/api/v1/plugins.py +++ b/app/api/v1/plugins.py @@ -722,6 +722,12 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren if _sess is not None and _sess.alive(): _slot = _sess.chain_slot_for_path(plugin_path) if _slot is not None: + # Bug 2-GUI: fallback bridge fx-gui (embed/float) còn sống cho cùng + # plugin — instance VST THỨ 2 + window riêng; mở native phải loại nó + # trước, nếu không 1 plugin hiện 2 GUI (1 floating native + 1 embed) + # và floating không tắt được qua UI. Terminate proc cũ → frontend + # embed panel của nó onDead → tự đóng. + _terminate_fx_gui_for_path(plugin_path) _already = fx_realtime.editor_find(plugin_path) _same = _already is not None and _already[0] == _sess.name _sess.send_editor_cmd("open", slot=_slot) @@ -729,6 +735,15 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren return {"success": True, "started": not _same, "already_running": bool(_same), "native": True, "embed_url": None, "session_id": _sess.name, "slot": _slot} + # Tránh mở 2 GUI cùng plugin: editor NATIVE (instance DSP trong session + # fxrt) của plugin này đang mở ở session SỐNG → KHÔNG spawn instance 2 / + # bridge fx-gui nữa (nguồn CPU 100% + 2 GUI); trả native để frontend hiện + # đúng trạng thái cửa sổ ngoài (M6). + _nsess, _nslot = _fxrt_native_editor(plugin_path) + if _nsess is not None: + return {"success": True, "started": False, "already_running": True, + "native": True, "embed_url": None, + "session_id": _nsess.name, "slot": _nslot} # Tránh mở 2 GUI cùng plugin: nếu tiến trình bridge fx-gui cho plugin này # đang chạy → trả started=False (frontend có thể focus window cũ). for proc in _FX_GUI_PROCESSES: @@ -842,15 +857,8 @@ async def close_fx_gui(req: FxGuiCloseRequest, current_user: dict = Depends(get_ return {"success": True, "closed": True, "native": True, "preset_b64": _preset} # session đã chết — rơi xuống spawn fallback (đóng bridge fx-gui cũ nếu có) - for proc in _FX_GUI_PROCESSES: - if proc is not None and proc.poll() is None and getattr(proc, "_sf_plugin_path", None) == plugin_path: - proc.terminate() - try: - proc.wait(timeout=5) - except Exception: - proc.kill() - _prune_fx_gui_processes() - return {"success": True, "closed": True} + if _terminate_fx_gui_for_path(plugin_path): + return {"success": True, "closed": True} return {"success": True, "closed": False} @@ -908,6 +916,27 @@ def _prune_fx_gui_processes(): global _FX_GUI_PROCESSES _FX_GUI_PROCESSES = [p for p in _FX_GUI_PROCESSES if p is not None and p.poll() is None] +def _terminate_fx_gui_for_path(plugin_path): + """Terminate MỌI bridge fx-gui (embed/float fallback) còn sống cho plugin. + Dùng khi mở native editor thay thế fallback — tránh 2 instance VST + 2 GUI + cùng 1 plugin (Bug 2-GUI). Trả True nếu có proc đã bị chấm dứt.""" + _prune_fx_gui_processes() + killed = False + for proc in list(_FX_GUI_PROCESSES): + if proc.poll() is None and getattr(proc, "_sf_plugin_path", None) == plugin_path: + try: + proc.terminate() + try: + proc.wait(timeout=5) + except Exception: + proc.kill() + killed = True + except Exception: + pass + if killed: + _prune_fx_gui_processes() + return killed + @router.post("/preview") diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 7c5f0ea..bce1558 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -13681,9 +13681,14 @@ const FXRackModal = ({ track, onUpdateTrack, onClose }) => { const closeFxGui = () => { const g = fxGuiOpenRef.current; if (g) { - // Capture preset CUỐI trước khi đóng (poll 1.5s có thể chưa kịp khi chỉnh - // xong đóng ngay) — gọi /preset TRƯỚC /close (close kill process bridge). - try { fetch(g.url + '/preset').then(r => r.json()).then(d => { if (d && d.preset_b64 && g.path) _capturedPresetByPath[g.path] = d.preset_b64; }).catch(() => {}).then(() => { try { fetch(g.url + '/close', { method: 'POST' }).catch(() => {}); } catch (e) {} }); } catch (e) { try { fetch(g.url + '/close', { method: 'POST' }).catch(() => {}); } catch (e2) {} } + if (g.url) { + // Capture preset CUỐI trước khi đóng (poll 1.5s có thể chưa kịp khi chỉnh + // xong đóng ngay) — gọi /preset TRƯỚC /close (close kill process bridge). + try { fetch(g.url + '/preset').then(r => r.json()).then(d => { if (d && d.preset_b64 && g.path) _capturedPresetByPath[g.path] = d.preset_b64; }).catch(() => {}).then(() => { try { fetch(g.url + '/close', { method: 'POST' }).catch(() => {}); } catch (e) {} }); } catch (e) { try { fetch(g.url + '/close', { method: 'POST' }).catch(() => {}); } catch (e2) {} } + } else if (g.native) { + // M6: GUI native (cửa sổ ngoài "FX slot N") — engine capture preset + đóng editor. + try { window.SonicAPI && window.SonicAPI.closeFxGui({ path: g.path }).then(d => { if (d && d.preset_b64 && g.path) _capturedPresetByPath[g.path] = d.preset_b64; }).catch(() => {}); } catch (e) {} + } } setFxGuiOpen(null); if (g && g.path) { @@ -13693,7 +13698,7 @@ const FXRackModal = ({ track, onUpdateTrack, onClose }) => { } if (onUpdateTrack && track) onUpdateTrack(track.id, { vstGuiOpenPath: null, vstGuiOpenName: null }); }; - React.useEffect(() => () => { const g = fxGuiOpenRef.current; if (g) { try { fetch(g.url + '/hide', { method: 'POST' }).catch(() => {}); } catch (e) {} const _tk = 'track:' + ((track && track.id) || '') + ':' + g.path; if (_presetRestartTimers[_tk]) { clearTimeout(_presetRestartTimers[_tk]); delete _presetRestartTimers[_tk]; } if (g.path) delete _fxGuiBaselineByPath[g.path]; } }, []); + React.useEffect(() => () => { const g = fxGuiOpenRef.current; if (g) { if (g.url) { try { fetch(g.url + '/hide', { method: 'POST' }).catch(() => {}); } catch (e) {} } else if (g.native) { try { window.SonicAPI && window.SonicAPI.hideFxGui({ path: g.path }).catch(() => {}); } catch (e) {} } const _tk = 'track:' + ((track && track.id) || '') + ':' + g.path; if (_presetRestartTimers[_tk]) { clearTimeout(_presetRestartTimers[_tk]); delete _presetRestartTimers[_tk]; } if (g.path) delete _fxGuiBaselineByPath[g.path]; } }, []); // Capture GUI knob tweaks while the embedded GUI is open (bridge /preset). React.useEffect(() => { if (!fxGuiOpen || !fxGuiOpen.url) return; @@ -13710,13 +13715,14 @@ const FXRackModal = ({ track, onUpdateTrack, onClose }) => { if (!s || !window.SonicAPI || !window.SonicAPI.openFxGui) return; window.SonicAPI.openFxGui({ path: s.path, name: s.name || s.path, embed: true, shm: fxRtTrackShmName(track && track.id), preset_b64: _capturedPresetByPath[s.path] || s.preset_b64 || '' }).then(r => { if (r && r.embed_url) setFxGuiOpen({ path: s.path, url: r.embed_url }); + else if (r && r.native) setFxGuiOpen({ path: s.path, url: null, name: s.name || s.path, native: true }); }).catch(() => {}); // eslint-disable-next-line react-hooks/exhaustive-deps }, [track && track.id, track && track.vstGuiOpenPath]); const toggleVst = (idx) => setVstChain(vstChain.map((s, i) => i === idx ? { ...s, bypass: !s.bypass } : s)); const removeVst = (idx) => { const rem = vstChain[idx]; if (rem && fxGuiOpen && fxGuiOpen.path === rem.path) closeFxGui(); if (rem) delete _capturedPresetByPath[rem.path]; if (idx === activeVstIdx) setActiveVstIdx(-1); setVstChain(vstChain.filter((_, i) => i !== idx)); }; const moveVst = (idx, dir) => { const j = idx + dir; if (j < 0 || j >= vstChain.length) return; const next = [...vstChain]; const mv = next.splice(idx, 1)[0]; next.splice(j, 0, mv); setVstChain(next); }; - const openVstGui = (s, idx) => { if (!s || !s.path) { window.showToast && window.showToast('Chọn VST FX trước', 'warning'); return; } if (fxGuiOpen && fxGuiOpen.path === s.path) { if (idx !== undefined) setActiveVstIdx(idx); return; } if (fxGuiOpen) closeFxGui(); if (idx !== undefined) setActiveVstIdx(idx); if (!window.SonicAPI || !window.SonicAPI.openFxGui) { window.showToast && window.showToast('Máy này không hỗ trợ mở GUI VST', 'warning'); return; } window.SonicAPI.openFxGui({ path: s.path, name: s.name || s.path, embed: true, shm: fxRtTrackShmName(track && track.id), preset_b64: _capturedPresetByPath[s.path] || s.preset_b64 || '' }).then(r => { if (r && r.embed_url) { setFxGuiOpen({ path: s.path, url: r.embed_url, name: s.name || s.path }); if (onUpdateTrack && track) onUpdateTrack(track.id, { vstGuiOpenPath: s.path, vstGuiOpenName: s.name || s.path }); } else if (r && r.already_running && r.embed_url) { setFxGuiOpen({ path: s.path, url: r.embed_url, name: s.name || s.path }); if (onUpdateTrack && track) onUpdateTrack(track.id, { vstGuiOpenPath: s.path, vstGuiOpenName: s.name || s.path }); } else if (r && r.already_running) window.showToast && window.showToast('GUI của plugin này đang mở sẵn (cửa sổ ngoài)', 'info'); else window.showToast && window.showToast('Không mở được GUI embed', 'error'); }).catch(err => window.showToast && window.showToast('Lỗi mở GUI: ' + (err.message || err), 'error')); }; + const openVstGui = (s, idx) => { if (!s || !s.path) { window.showToast && window.showToast('Chọn VST FX trước', 'warning'); return; } if (fxGuiOpen && fxGuiOpen.path === s.path) { if (idx !== undefined) setActiveVstIdx(idx); return; } if (fxGuiOpen) closeFxGui(); if (idx !== undefined) setActiveVstIdx(idx); if (!window.SonicAPI || !window.SonicAPI.openFxGui) { window.showToast && window.showToast('Máy này không hỗ trợ mở GUI VST', 'warning'); return; } window.SonicAPI.openFxGui({ path: s.path, name: s.name || s.path, embed: true, shm: fxRtTrackShmName(track && track.id), preset_b64: _capturedPresetByPath[s.path] || s.preset_b64 || '' }).then(r => { if (r && r.native) { setFxGuiOpen({ path: s.path, url: null, name: s.name || s.path, native: true }); if (onUpdateTrack && track) onUpdateTrack(track.id, { vstGuiOpenPath: s.path, vstGuiOpenName: s.name || s.path }); } else if (r && r.embed_url) { setFxGuiOpen({ path: s.path, url: r.embed_url, name: s.name || s.path }); if (onUpdateTrack && track) onUpdateTrack(track.id, { vstGuiOpenPath: s.path, vstGuiOpenName: s.name || s.path }); } else if (r && r.already_running && r.embed_url) { setFxGuiOpen({ path: s.path, url: r.embed_url, name: s.name || s.path }); if (onUpdateTrack && track) onUpdateTrack(track.id, { vstGuiOpenPath: s.path, vstGuiOpenName: s.name || s.path }); } else if (r && r.already_running) window.showToast && window.showToast('GUI của plugin này đang mở sẵn (cửa sổ ngoài)', 'info'); else window.showToast && window.showToast('Không mở được GUI embed', 'error'); }).catch(err => window.showToast && window.showToast('Lỗi mở GUI: ' + (err.message || err), 'error')); }; const addVst = (path) => { const fx = (fxList || []).find(f => f.path === path); if (!fx) return; const next = [...vstChain, { type: 'vst3', path: fx.path, name: fx.name || fx.id, preset_b64: '', bypass: false }]; setVstChain(next); setActiveVstIdx(next.length - 1); }; const changeVstPlugin = (idx, path) => { const fx = (fxList || []).find(f => f.path === path); delete _capturedPresetByPath[path]; setVstChain(vstChain.map((s, i) => i === idx ? { ...s, path, name: fx ? (fx.name || fx.id) : s.name, preset_b64: '' } : s)); }; const uploadVstPreset = (idx) => { @@ -14083,7 +14089,15 @@ const addVst = (path) => { const fx = (fxList || []).find(f => f.path === path); {/* WORKSPACE CONTROLS */} - {fxGuiOpen ? ( + {fxGuiOpen && !fxGuiOpen.url && fxGuiOpen.native ? ( +
+ +
GUI native đang mở (cửa sổ ngoài)
+
{fxGuiOpen.name || fxGuiOpen.path}
+
Chỉnh thông số trong cửa sổ "FX slot N" — bấm Đóng GUI để đóng editor (giữ DSP + preset).
+ +
+ ) : fxGuiOpen ? (
{ window.showToast && window.showToast('Plugin VST GUI lỗi khi mở (bridge chết/treo — check %TEMP%\\sf_dumps\\fx_bridge_crash_*.dmp) — đã trả về graph', 'error'); closeFxGui(); }} /> @@ -14294,8 +14308,13 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin const closeMasterGui = () => { const g = masterGuiOpenRef.current; if (g) { - // Capture preset CUỐI trước khi đóng (giống closeFxGui) — /preset rồi /close. - try { fetch(g.url + '/preset').then(r => r.json()).then(d => { if (d && d.preset_b64 && g.path) _capturedPresetByPath[g.path] = d.preset_b64; }).catch(() => {}).then(() => { try { fetch(g.url + '/close', { method: 'POST' }).catch(() => {}); } catch (e) {} }); } catch (e) { try { fetch(g.url + '/close', { method: 'POST' }).catch(() => {}); } catch (e2) {} } + if (g.url) { + // Capture preset CUỐI trước khi đóng (giống closeFxGui) — /preset rồi /close. + try { fetch(g.url + '/preset').then(r => r.json()).then(d => { if (d && d.preset_b64 && g.path) _capturedPresetByPath[g.path] = d.preset_b64; }).catch(() => {}).then(() => { try { fetch(g.url + '/close', { method: 'POST' }).catch(() => {}); } catch (e) {} }); } catch (e) { try { fetch(g.url + '/close', { method: 'POST' }).catch(() => {}); } catch (e2) {} } + } else if (g.native) { + // M6: GUI native (cửa sổ ngoài "FX slot N") — engine capture preset + đóng editor. + try { window.SonicAPI && window.SonicAPI.closeFxGui({ path: g.path }).then(d => { if (d && d.preset_b64 && g.path) _capturedPresetByPath[g.path] = d.preset_b64; }).catch(() => {}); } catch (e) {} + } } setMasterGuiOpen(null); if (g && g.path) { @@ -14306,7 +14325,7 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin setActiveMasterVstPath(null); setOzState(prev => { const n = { ...(prev || {}) }; delete n.gui_open_path; delete n.gui_open_name; return n; }); }; - React.useEffect(() => () => { const g = masterGuiOpenRef.current; if (g) { try { fetch(g.url + '/hide', { method: 'POST' }).catch(() => {}); } catch (e) {} const _mk = 'master:' + g.path; if (_presetRestartTimers[_mk]) { clearTimeout(_presetRestartTimers[_mk]); delete _presetRestartTimers[_mk]; } if (g.path) delete _fxGuiBaselineByPath[g.path]; } }, []); + React.useEffect(() => () => { const g = masterGuiOpenRef.current; if (g) { if (g.url) { try { fetch(g.url + '/hide', { method: 'POST' }).catch(() => {}); } catch (e) {} } else if (g.native) { try { window.SonicAPI && window.SonicAPI.hideFxGui({ path: g.path }).catch(() => {}); } catch (e) {} } const _mk = 'master:' + g.path; if (_presetRestartTimers[_mk]) { clearTimeout(_presetRestartTimers[_mk]); delete _presetRestartTimers[_mk]; } if (g.path) delete _fxGuiBaselineByPath[g.path]; } }, []); // Capture GUI knob tweaks while the embedded GUI is open (bridge /preset). React.useEffect(() => { if (!masterGuiOpen || !masterGuiOpen.url) return; @@ -14324,8 +14343,13 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin if (isOpen) return; const g = masterGuiOpenRef.current; if (!g) return; - try { if (g.path) { fetch(g.url + '/preset').then(r => r.json()).then(d => { if (d && d.preset_b64 && g.path) _capturedPresetByPath[g.path] = d.preset_b64; }).catch(() => {}); } } catch (e) {} - try { fetch(g.url + '/hide', { method: 'POST' }).catch(() => {}); } catch (e) {} + if (g.url) { + try { if (g.path) { fetch(g.url + '/preset').then(r => r.json()).then(d => { if (d && d.preset_b64 && g.path) _capturedPresetByPath[g.path] = d.preset_b64; }).catch(() => {}); } } catch (e) {} + try { fetch(g.url + '/hide', { method: 'POST' }).catch(() => {}); } catch (e) {} + } else if (g.native) { + // Ẩn GUI native khi đóng panel (giữ editor+instance, mở lại panel nhanh). + try { window.SonicAPI && window.SonicAPI.hideFxGui({ path: g.path }).catch(() => {}); } catch (e) {} + } const _mk = 'master:' + g.path; if (_presetRestartTimers[_mk]) { clearTimeout(_presetRestartTimers[_mk]); delete _presetRestartTimers[_mk]; } if (g.path) delete _fxGuiBaselineByPath[g.path]; @@ -14355,6 +14379,7 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin const _name = st.gui_open_name || s.name || _p; window.SonicAPI.openFxGui({ path: _p, name: _name, embed: true, shm: fxRt.sessionId || '', preset_b64: _capturedPresetByPath[_p] || s.preset_b64 || '' }).then(r => { _autoOpenRef.current.inFlight = false; + if (r && r.native && !masterGuiOpenRef.current) { _autoOpenRef.current.attempt = 0; setMasterGuiOpen({ path: _p, url: null, name: _name, native: true }); return; } if (r && r.embed_url && !masterGuiOpenRef.current) { _autoOpenRef.current.attempt = 0; setMasterGuiOpen({ path: _p, url: r.embed_url, name: _name }); return; } if (r && r.already_running && r.embed_url && !masterGuiOpenRef.current) { _autoOpenRef.current.attempt = 0; setMasterGuiOpen({ path: _p, url: r.embed_url, name: _name }); return; } if (r && r.already_running) { _autoOpenRef.current.attempt = 0; setActiveMasterVstPath(null); window.showToast && window.showToast('GUI của plugin này đang mở sẵn (cửa sổ ngoài)', 'info'); return; } @@ -14403,7 +14428,7 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin const toggleMasterVst = (idx) => setMasterVstChain(masterVstChain.map((s, i) => i === idx ? { ...s, bypass: !s.bypass } : s)); const removeMasterVst = (idx) => { const rem = masterVstChain[idx]; if (rem) delete _capturedPresetByPath[rem.path]; setMasterVstChain(masterVstChain.filter((_, i) => i !== idx)); }; const moveMasterVst = (idx, dir) => { const j = idx + dir; if (j < 0 || j >= masterVstChain.length) return; const next = [...masterVstChain]; const mv = next.splice(idx, 1)[0]; next.splice(j, 0, mv); setMasterVstChain(next); }; - const openMasterVstGui = (s) => { if (!s || !s.path) { window.showToast && window.showToast('Chọn VST FX trước', 'warning'); return; } if (masterGuiOpen && masterGuiOpen.path === s.path) { setActiveMasterVstPath(s.path); return; } if (masterGuiOpen) closeMasterGui(); if (s.path) setActiveMasterVstPath(s.path); if (!window.SonicAPI || !window.SonicAPI.openFxGui) { window.showToast && window.showToast('Máy này không hỗ trợ mở GUI VST', 'warning'); return; } window.SonicAPI.openFxGui({ path: s.path, name: s.name || s.path, embed: true, shm: fxRt.sessionId || '', preset_b64: _capturedPresetByPath[s.path] || s.preset_b64 || '' }).then(r => { if (r && r.embed_url) { setMasterGuiOpen({ path: s.path, url: r.embed_url, name: s.name || s.path }); setOzState(prev => ({ ...(prev || {}), gui_open_path: s.path, gui_open_name: s.name || s.path })); } else if (r && r.already_running && r.embed_url) { setMasterGuiOpen({ path: s.path, url: r.embed_url, name: s.name || s.path }); setOzState(prev => ({ ...(prev || {}), gui_open_path: s.path, gui_open_name: s.name || s.path })); } else if (r && r.already_running) window.showToast && window.showToast('GUI của plugin này đang mở sẵn (cửa sổ ngoài)', 'info'); else window.showToast && window.showToast('Không mở được GUI embed', 'error'); }).catch(err => window.showToast && window.showToast('Lỗi mở GUI: ' + (err.message || err), 'error')); }; + const openMasterVstGui = (s) => { if (!s || !s.path) { window.showToast && window.showToast('Chọn VST FX trước', 'warning'); return; } if (masterGuiOpen && masterGuiOpen.path === s.path) { setActiveMasterVstPath(s.path); return; } if (masterGuiOpen) closeMasterGui(); if (s.path) setActiveMasterVstPath(s.path); if (!window.SonicAPI || !window.SonicAPI.openFxGui) { window.showToast && window.showToast('Máy này không hỗ trợ mở GUI VST', 'warning'); return; } window.SonicAPI.openFxGui({ path: s.path, name: s.name || s.path, embed: true, shm: fxRt.sessionId || '', preset_b64: _capturedPresetByPath[s.path] || s.preset_b64 || '' }).then(r => { if (r && r.native) { setMasterGuiOpen({ path: s.path, url: null, name: s.name || s.path, native: true }); setOzState(prev => ({ ...(prev || {}), gui_open_path: s.path, gui_open_name: s.name || s.path })); } else if (r && r.embed_url) { setMasterGuiOpen({ path: s.path, url: r.embed_url, name: s.name || s.path }); setOzState(prev => ({ ...(prev || {}), gui_open_path: s.path, gui_open_name: s.name || s.path })); } else if (r && r.already_running && r.embed_url) { setMasterGuiOpen({ path: s.path, url: r.embed_url, name: s.name || s.path }); setOzState(prev => ({ ...(prev || {}), gui_open_path: s.path, gui_open_name: s.name || s.path })); } else if (r && r.already_running) window.showToast && window.showToast('GUI của plugin này đang mở sẵn (cửa sổ ngoài)', 'info'); else window.showToast && window.showToast('Không mở được GUI embed', 'error'); }).catch(err => window.showToast && window.showToast('Lỗi mở GUI: ' + (err.message || err), 'error')); }; const addMasterVst = (path) => { const fx = (masterFxList || []).find(f => f.path === path); if (!fx) return; setMasterVstChain([...masterVstChain, { type: 'vst3', path: fx.path, name: fx.name || fx.id, preset_b64: '', bypass: false }]); openMasterVstGui({ path: fx.path, name: fx.name || fx.id }); }; const changeMasterVstPlugin = (idx, path) => { const fx = (masterFxList || []).find(f => f.path === path); delete _capturedPresetByPath[path]; setMasterVstChain(masterVstChain.map((s, i) => i === idx ? { ...s, path, name: fx ? (fx.name || fx.id) : s.name, preset_b64: '' } : s)); }; @@ -15200,7 +15225,15 @@ const addMasterVst = (path) => { const fx = (masterFxList || []).find(f => f.pat
{/* LEFT: MODULE VIEWS */} - {masterGuiOpen ? ( + {masterGuiOpen && !masterGuiOpen.url && masterGuiOpen.native ? ( +
+ +
GUI native đang mở (cửa sổ ngoài)
+
{masterGuiOpen.name || masterGuiOpen.path}
+
Chỉnh thông số trong cửa sổ "FX slot N" của Ozone — thay đổi tự lưu vào project khi bấm Đóng GUI.
+ +
+ ) : masterGuiOpen ? (
{ window.showToast && window.showToast('Plugin VST GUI lỗi khi mở (bridge chết/treo — check %TEMP%\\sf_dumps\\fx_bridge_crash_*.dmp) — đã trả về graph', 'error'); closeMasterGui(); }} /> diff --git a/app/static/js/services/api.js b/app/static/js/services/api.js index 96bba21..41ce3e8 100644 --- a/app/static/js/services/api.js +++ b/app/static/js/services/api.js @@ -90,6 +90,12 @@ window.API_BASE_URL = (window.API_BASE_URL || window.location.origin).replace(/\ fxRealtimeSetChain: (payload) => apiRequest('/api/v1/plugins/fx-realtime/set-chain', { method: 'POST', body: JSON.stringify(payload) }), // Mở native GUI của VST FX (mastering chain / FX rack) qua bridge openFxGui: (payload) => apiRequest('/api/v1/plugins/fx-gui', { method: 'POST', body: JSON.stringify(payload) }), + // M6: đóng/ẩn/hiện GUI NATIVE (editor instance DSP trong session fxrt). + // Engine capture preset khi close → trả preset_b64. Embed cũ đóng thẳng + // qua HTTP của bridge (g.url + '/close') — chỉ dùng cho native. + closeFxGui: (payload) => apiRequest('/api/v1/plugins/fx-gui/close', { method: 'POST', body: JSON.stringify(payload) }), + hideFxGui: (payload) => apiRequest('/api/v1/plugins/fx-gui/hide', { method: 'POST', body: JSON.stringify(payload) }), + showFxGui: (payload) => apiRequest('/api/v1/plugins/fx-gui/show', { method: 'POST', body: JSON.stringify(payload) }), soundfontRender: (payload) => apiRequest('/api/v1/plugins/soundfont-render', { method: 'POST', body: JSON.stringify(payload) }), // Thư viện preset VST3 (.vstpreset) — cầu nối preset → pedalboard listPresets: () => apiRequest('/api/v1/presets', { method: 'GET' }),