M6: fix 2-GUI khi mở VST FX native (mastering) — một plugin chỉ một GUI
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
This commit is contained in:
+38
-9
@@ -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")
|
||||
|
||||
+46
-13
@@ -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);
|
||||
</div>
|
||||
|
||||
{/* WORKSPACE CONTROLS */}
|
||||
{fxGuiOpen ? (
|
||||
{fxGuiOpen && !fxGuiOpen.url && fxGuiOpen.native ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col items-center justify-center gap-3 text-slate-400 select-none">
|
||||
<i data-lucide="external-link" className="w-10 h-10 text-teal-400"></i>
|
||||
<div className="text-sm font-bold text-slate-200">GUI native đang mở (cửa sổ ngoài)</div>
|
||||
<div className="text-[11px] font-mono text-slate-500 max-w-md truncate">{fxGuiOpen.name || fxGuiOpen.path}</div>
|
||||
<div className="text-[10px] text-slate-500">Chỉnh thông số trong cửa sổ "FX slot N" — bấm Đóng GUI để đóng editor (giữ DSP + preset).</div>
|
||||
<button onClick={closeFxGui} className="px-4 py-2 bg-teal-700 hover:bg-teal-600 text-white text-xs font-bold rounded-lg transition">Đóng GUI</button>
|
||||
</div>
|
||||
) : fxGuiOpen ? (
|
||||
<div className="flex-1 min-h-0">
|
||||
<VstGuiEmbed url={fxGuiOpen.url} onClose={closeFxGui}
|
||||
onDead={() => { 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
|
||||
<main className="flex-1 flex overflow-hidden min-h-0">
|
||||
|
||||
{/* LEFT: MODULE VIEWS */}
|
||||
{masterGuiOpen ? (
|
||||
{masterGuiOpen && !masterGuiOpen.url && masterGuiOpen.native ? (
|
||||
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-slate-400 bg-slate-950 select-none min-h-0">
|
||||
<i data-lucide="external-link" className="w-10 h-10 text-cyan-400"></i>
|
||||
<div className="text-sm font-bold text-slate-200">GUI native đang mở (cửa sổ ngoài)</div>
|
||||
<div className="text-[11px] font-mono text-slate-500 max-w-md truncate">{masterGuiOpen.name || masterGuiOpen.path}</div>
|
||||
<div className="text-[10px] text-slate-500 text-center max-w-md">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.</div>
|
||||
<button onClick={closeMasterGui} className="px-4 py-2 bg-cyan-700 hover:bg-cyan-600 text-white text-xs font-bold rounded-lg transition">Đóng GUI</button>
|
||||
</div>
|
||||
) : masterGuiOpen ? (
|
||||
<div className="flex-1 flex flex-col bg-slate-950 relative min-h-0">
|
||||
<VstGuiEmbed url={masterGuiOpen.url} onClose={closeMasterGui}
|
||||
onDead={() => { 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(); }} />
|
||||
|
||||
@@ -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' }),
|
||||
|
||||
Reference in New Issue
Block a user