fix(v9): 5 bugs — GUI deferred until VST loaded (no white window), fixed retry 8x500ms, VST3 folder resolve for auto-load, sustain-CC64 dropped after STOP, taskkill process tree on exit, Synth+GUI same row in TCP

This commit is contained in:
2026-08-13 21:57:23 +07:00
parent 96d31ee685
commit 7a8122f99d
6 changed files with 72 additions and 29 deletions
+10
View File
@@ -1269,6 +1269,16 @@ def _resolve_bridge_asset(name: str, instrument_type: str, path: Optional[str])
if fn.lower().endswith(ext) and \ if fn.lower().endswith(ext) and \
(fn.lower() == base.lower() or os.path.splitext(fn)[0].lower() == base_noext): (fn.lower() == base.lower() or os.path.splitext(fn)[0].lower() == base_noext):
return os.path.join(root, fn) return os.path.join(root, fn)
# V9 bug 8: Windows VST3 = FOLDER "X.vst3" (chua X.vst3.dll) —
# khong la file nen file-walk tren khong thay -> resolve null ->
# bridge load fail -> VSTi khong auto-load khi mo project. Scan
# sub-folder trung ten: "x.vst3" == base.lower() hoac "x" ==
# base_noext.
for sub in os.listdir(root):
sp = os.path.join(root, sub)
if os.path.isdir(sp) and sub.lower().endswith(ext) and \
(sub.lower() == base.lower() or os.path.splitext(sub)[0].lower() == base_noext):
return sp
return None return None
@router.post("/bridge/load") @router.post("/bridge/load")
+16 -13
View File
@@ -5768,7 +5768,7 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData, onInsertInstrument }
// Requirement 2: VST3 load m native GUI ngay (C++ attach editor vào // Requirement 2: VST3 load m native GUI ngay (C++ attach editor vào
// ca s bridge t to control type=4, hwnd=0). // ca s bridge t to control type=4, hwnd=0).
if (ok && type === 'VST3' && window.NativeBridgeService.openNativeGUI) { if (ok && type === 'VST3' && window.NativeBridgeService.openNativeGUI) {
try { await window.NativeBridgeService.openNativeGUI(name); } catch (e) {} try { await openVstGuiRetry(name, 0, 8); } catch (e) {}
} }
return ok; return ok;
} catch (err) { } catch (err) {
@@ -6210,17 +6210,20 @@ const ensureAndOpenVstGui = async (trackId, instrumentId) => {
const ok = await loadVstToBridge({ id: trackId, instrumentId }); const ok = await loadVstToBridge({ id: trackId, instrumentId });
if (!ok) return; if (!ok) return;
} }
try { await openVstGuiRetry(instrumentId, bch, 5); } catch (e) { console.warn('[Bridge] openNativeGUI fail:', e); } try { await openVstGuiRetry(instrumentId, bch, 8); } catch (e) { console.warn('[Bridge] openNativeGUI fail:', e); }
}; };
// V8 bug 4: C++ load instrument ASYNC (worker thread) OPEN_GUI gui toi truoc // V8 bug 4 + V9 bug 3/6/7: C++ load instrument ASYNC (worker thread) OPEN_GUI
// khi assign() xong instruments.get(guiCh) null "GUI attach FAILED" // som C++ defer (chi log stderr, invoke van tra Ok) retry PHAI chay DU so
// nen trang. Retry mo GUI nhieu lan, cach 400ms cho load xong. // lan, khong `return true` som. Mo GUI co the can cho den khi assign() xong
// (Nexus ~1-2s) 8 lan x 500ms, window reuse khi load xong.
const openVstGuiRetry = async (instrumentId, bch, attempts) => { const openVstGuiRetry = async (instrumentId, bch, attempts) => {
for (let i = 0; i < (attempts || 5); i++) { const n = (attempts && attempts > 0) ? attempts : 8;
try { await window.NativeBridgeService.openNativeGUI(instrumentId, bch); return true; } catch (e) { console.warn('[Bridge] openNativeGUI retry', i + 1, e); } for (let i = 0; i < n; i++) {
await new Promise(r => setTimeout(r, 400)); try { await window.NativeBridgeService.openNativeGUI(instrumentId, bch); }
catch (e) { console.warn('[Bridge] openNativeGUI retry', i + 1, e); }
await new Promise(r => setTimeout(r, 500));
} }
return false; return true;
}; };
// Module-level so both ProfileModal (open project) and App (auto-restore) can // Module-level so both ProfileModal (open project) and App (auto-restore) can
// warm the FluidSynth font cache for each track's instrument. This only loads // warm the FluidSynth font cache for each track's instrument. This only loads
@@ -15510,7 +15513,7 @@ const App = () => {
// Requirement 2: chn VST3 qua nút Synth load xong m native GUI // Requirement 2: chn VST3 qua nút Synth load xong m native GUI
// (C++ attach editor vào ca s bridge t to control type=4, hwnd=0). // (C++ attach editor vào ca s bridge t to control type=4, hwnd=0).
if (ok && btype === 'VST3' && window.NativeBridgeService.openNativeGUI) { if (ok && btype === 'VST3' && window.NativeBridgeService.openNativeGUI) {
try { await openVstGuiRetry(instrumentId, bch, 5); } catch (e) {} try { await openVstGuiRetry(instrumentId, bch, 8); } catch (e) {}
} }
} }
})(); })();
@@ -29840,20 +29843,20 @@ STRICT CONSTRAINTS:
className: "text-zinc-500 font-normal" className: "text-zinc-500 font-normal"
}, track.fxType || "None")), /*#__PURE__*/React.createElement("button", { }, track.fxType || "None")), /*#__PURE__*/React.createElement("button", {
onClick: (e) => { e.stopPropagation(); openInstrumentSelector(track.id); }, onClick: (e) => { e.stopPropagation(); openInstrumentSelector(track.id); },
className: "px-1.5 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-amber-400 border border-amber-800 rounded text-[10px] font-bold flex items-center gap-1 max-w-[70px] shrink-0" className: "px-1.5 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-amber-400 border border-amber-800 rounded text-[10px] font-bold flex items-center gap-1 max-w-[90px] shrink-0"
}, /*#__PURE__*/React.createElement("span", { }, /*#__PURE__*/React.createElement("span", {
className: "inline-flex items-center shrink-0" className: "inline-flex items-center shrink-0"
}, /*#__PURE__*/React.createElement("i", { }, /*#__PURE__*/React.createElement("i", {
"data-lucide": "music", "data-lucide": "music",
className: "w-2.5 h-2.5 shrink-0" className: "w-2.5 h-2.5 shrink-0"
})), /*#__PURE__*/React.createElement("span", { className: "truncate text-[10px] min-w-0" }, track.instrumentName || track.instrumentId || "Synth"), /*#__PURE__*/React.createElement("i", { "data-lucide": "chevron-down", className: "w-3 h-3 shrink-0" }))), (track.instrumentId && typeof track.instrumentId === 'string' && !track.instrumentId.startsWith('sf_') ? /*#__PURE__*/React.createElement("button", { })), /*#__PURE__*/React.createElement("span", { className: "truncate text-[10px] min-w-0" }, track.instrumentName || track.instrumentId || "Synth"), /*#__PURE__*/React.createElement("i", { "data-lucide": "chevron-down", className: "w-3 h-3 shrink-0" }))), /*#__PURE__*/React.createElement("div", { className: "flex items-center gap-0.5 shrink-0 min-w-0" }, (track.instrumentId && typeof track.instrumentId === 'string' && !track.instrumentId.startsWith('sf_') ? /*#__PURE__*/React.createElement("button", {
onClick: (e) => { onClick: (e) => {
e.stopPropagation(); e.stopPropagation();
ensureAndOpenVstGui(track.id, track.instrumentId); ensureAndOpenVstGui(track.id, track.instrumentId);
}, },
title: "Mở lại GUI VSTi", title: "Mở lại GUI VSTi",
className: "px-1 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-violet-300 border border-violet-800 rounded text-[9px] font-bold flex items-center gap-0.5 shrink-0" className: "px-1 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-violet-300 border border-violet-800 rounded text-[9px] font-bold flex items-center gap-0.5 shrink-0"
}, /*#__PURE__*/React.createElement("i", { "data-lucide": "sliders-horizontal", className: "w-2.5 h-2.5 shrink-0" }), /*#__PURE__*/React.createElement("span", { className: "truncate text-[8px] min-w-0" }, "GUI")) : null), /*#__PURE__*/React.createElement("div", { }, /*#__PURE__*/React.createElement("i", { "data-lucide": "sliders-horizontal", className: "w-2.5 h-2.5 shrink-0" }), /*#__PURE__*/React.createElement("span", { className: "truncate text-[8px] min-w-0" }, "GUI")) : null)), /*#__PURE__*/React.createElement("div", {
onMouseDown: e => handleTrackResizeMouseDown(e, track.id), onMouseDown: e => handleTrackResizeMouseDown(e, track.id),
className: "absolute bottom-0 left-0 right-0 h-1 cursor-ns-resize z-30 hover:bg-cyan-500/50 transition-colors", className: "absolute bottom-0 left-0 right-0 h-1 cursor-ns-resize z-30 hover:bg-cyan-500/50 transition-colors",
onClick: e => e.stopPropagation() onClick: e => e.stopPropagation()
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -50,7 +50,7 @@
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script> <script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script> <script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script> <script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
<script src="/static/js/app.precompiled.js?v=202608132130" defer></script> <script src="/static/js/app.precompiled.js?v=202608132205" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016"> <link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style> <style>
:root { :root {
+16
View File
@@ -306,6 +306,11 @@ int main(int argc, char* argv[]) {
inst->noteOff(evt.channel, evt.pitch, 0); inst->noteOff(evt.channel, evt.pitch, 0);
break; break;
case 0xB: // CC: controller number in pitch, value in data2 case 0xB: // CC: controller number in pitch, value in data2
// V9 bug 4: sau STOP, drop sustain-down (CC64>0) — JS co the
// gui CC64 xuong sau STOP (note-on/CC timer tre); VSTi giu
// note khi pedal down -> am treo loop. CC64=0 (sustain-up)
// van cho qua.
if (transportStopped && evt.pitch == 64 && evt.data2 > 0) return;
inst->controlChange(evt.channel, evt.pitch, evt.data2); inst->controlChange(evt.channel, evt.pitch, evt.data2);
break; break;
case 0xC: // program change: program in data2 case 0xC: // program change: program in data2
@@ -418,6 +423,17 @@ int main(int argc, char* argv[]) {
} else if (c.type == 4) { // OPEN_GUI (A7): arg1 = parent HWND (0 → bridge tự tạo native window), arg2 = plugin id } else if (c.type == 4) { // OPEN_GUI (A7): arg1 = parent HWND (0 → bridge tự tạo native window), arg2 = plugin id
uint32_t guiCh = c.channel; uint32_t guiCh = c.channel;
if (guiCh >= 16) guiCh = 0; if (guiCh >= 16) guiCh = 0;
// V9 bug 3/6/7: gate tren MAIN thread TRUOC khi tao window —
// instrument load ASYNC (worker thread); OPEN_GUI som -> attach
// job fail -> cua so trang van con song. Chua load xong =>
// defer: KHONG tao window, KHONG post job. JS retry
// openNativeGUI 8x500ms; khi assign xong nhay lai day, tao
// window 1 lan va attach OK.
if (!instruments.get(guiCh)) {
std::cerr << "[NativeBridge] GUI deferred ch=" << guiCh
<< " plugin=" << c.arg2 << " (no instrument yet)" << std::endl;
continue;
}
if (!workers[guiCh]) workers[guiCh] = std::make_unique<ChannelWorker>(); if (!workers[guiCh]) workers[guiCh] = std::make_unique<ChannelWorker>();
void* hwnd = (void*)(uintptr_t)c.arg1; void* hwnd = (void*)(uintptr_t)c.arg1;
#ifdef _WIN32 #ifdef _WIN32
+20 -7
View File
@@ -48,20 +48,31 @@ fn kill_bridge(app: &AppHandle) -> bool {
Err(_) => return false, Err(_) => return false,
}; };
match guard.take() { match guard.take() {
Some(child) => match child.kill() { Some(child) => {
Ok(()) => { let ok = kill_process_tree(&child) || child.kill().is_ok();
if ok {
println!("Native Host Bridge killed (restart)"); println!("Native Host Bridge killed (restart)");
true } else {
println!("Bridge kill failed");
} }
Err(e) => { ok
println!("Bridge kill failed: {e}");
false
} }
},
None => false, None => false,
} }
} }
/// Kill a child plus its whole process tree via taskkill /T /F. The engine
/// (PyInstaller ONEDIR) and bridge are direct children, but /T guarantees no
/// orphan subprocess stays behind when the DAW window closes.
fn kill_process_tree(child: &CommandChild) -> bool {
let pid = child.pid();
std::process::Command::new("taskkill")
.args(["/PID", &pid.to_string(), "/T", "/F"])
.status()
.map(|s| s.success())
.unwrap_or(false)
}
/// Audio frame pushed to the WebView (bridge SHM -> `bridge-audio` event). /// Audio frame pushed to the WebView (bridge SHM -> `bridge-audio` event).
#[derive(Clone, serde::Serialize)] #[derive(Clone, serde::Serialize)]
struct AudioFrame { struct AudioFrame {
@@ -479,6 +490,7 @@ pub fn run() {
.ok() .ok()
.and_then(|mut lock| lock.take()); .and_then(|mut lock| lock.take());
if let Some(child) = child { if let Some(child) = child {
kill_process_tree(&child);
let _ = child.kill(); let _ = child.kill();
println!("daw_engine sidecar terminated."); println!("daw_engine sidecar terminated.");
} }
@@ -490,6 +502,7 @@ pub fn run() {
.ok() .ok()
.and_then(|mut lock| lock.take()); .and_then(|mut lock| lock.take());
if let Some(child) = bridge_child { if let Some(child) = bridge_child {
kill_process_tree(&child);
let _ = child.kill(); let _ = child.kill();
println!("daw_vst_bridge sidecar terminated."); println!("daw_vst_bridge sidecar terminated.");
} }