fix(ui): GUI button inline same row + same height as Synth/File/FX in TCP

This commit is contained in:
2026-08-14 08:04:33 +07:00
parent 7a8122f99d
commit 29f7fecb35
6 changed files with 221 additions and 144 deletions
+16 -11
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
// ca s bridge t to control type=4, hwnd=0).
if (ok && type === 'VST3' && window.NativeBridgeService.openNativeGUI) {
try { await openVstGuiRetry(name, 0, 8); } catch (e) {}
try { await openVstGuiRetry(name, 0, 20); } catch (e) {}
}
return ok;
} catch (err) {
@@ -6210,14 +6210,14 @@ const ensureAndOpenVstGui = async (trackId, instrumentId) => {
const ok = await loadVstToBridge({ id: trackId, instrumentId });
if (!ok) return;
}
try { await openVstGuiRetry(instrumentId, bch, 8); } catch (e) { console.warn('[Bridge] openNativeGUI fail:', e); }
try { await openVstGuiRetry(instrumentId, bch, 20); } catch (e) { console.warn('[Bridge] openNativeGUI fail:', e); }
};
// V8 bug 4 + V9 bug 3/6/7: C++ load instrument ASYNC (worker thread) OPEN_GUI
// som C++ defer (chi log stderr, invoke van tra Ok) retry PHAI chay DU so
// 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.
// (Nexus ~1-2s) 20 lan x 500ms, window reuse khi load xong.
const openVstGuiRetry = async (instrumentId, bch, attempts) => {
const n = (attempts && attempts > 0) ? attempts : 8;
const n = (attempts && attempts > 0) ? attempts : 20;
for (let i = 0; i < n; i++) {
try { await window.NativeBridgeService.openNativeGUI(instrumentId, bch); }
catch (e) { console.warn('[Bridge] openNativeGUI retry', i + 1, e); }
@@ -9919,8 +9919,8 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
ensureAndOpenVstGui(st.trackId, st.instrumentId);
},
title: "Mở lại GUI VSTi",
className: "px-1 py-0.5 text-[9px] rounded font-mono font-bold border transition flex items-center gap-0.5 shrink-0 bg-zinc-800 text-violet-300 border-violet-800 hover:bg-violet-900/40"
}, React.createElement("i", { "data-lucide": "sliders-horizontal", className: "w-3 h-3 shrink-0" }), React.createElement("span", { className: "truncate text-[9px]" }, "GUI")) : null)), React.createElement("div", {
className: "px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 shrink-0 bg-zinc-800 text-violet-300 border-violet-800 hover:bg-violet-900/40"
}, React.createElement("i", { "data-lucide": "sliders-horizontal", className: "w-3 h-3 shrink-0" }), React.createElement("span", { className: "truncate text-[10px]" }, "GUI")) : null)), React.createElement("div", {
className: "flex items-center gap-1 ml-1 text-xs"
}, React.createElement("span", { className: "text-zinc-500" }, "AI:"), React.createElement("input", {
type: "number", value: aiBarStart, onChange: e => setAiBarStart(parseInt(e.target.value) || 0),
@@ -15474,6 +15474,11 @@ const App = () => {
if (!window.SonicMidiRouter || !window.SonicMidiRouter.isBridgeActive() || !instrumentId) return;
const isSf = typeof instrumentId === 'string' && instrumentId.startsWith('sf_');
const bch = window.SonicMidiRouter.allocateChannel(trackId, bank === 128);
// V9 GUI-button bug: switch VSTi SF2 phi xóa flag "đã load VST" ca
// channel, nếu không ensureAndOpenVstGui s b qua load khi chn li VST
// (flag cũ còn true) OPEN_GUI đến bridge khi channel chưa có instrument
// deferred mãi, nút GUI không m gì.
if (isSf && window.__bridgeLoadedChannels) delete window.__bridgeLoadedChannels[bch];
const btype = isSf ? 'SF2' : 'VST3';
let bpath = null;
if (instrumentSelectorData) {
@@ -15513,7 +15518,7 @@ const App = () => {
// 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).
if (ok && btype === 'VST3' && window.NativeBridgeService.openNativeGUI) {
try { await openVstGuiRetry(instrumentId, bch, 8); } catch (e) {}
try { await openVstGuiRetry(instrumentId, bch, 20); } catch (e) {}
}
}
})();
@@ -29843,20 +29848,20 @@ STRICT CONSTRAINTS:
className: "text-zinc-500 font-normal"
}, track.fxType || "None")), /*#__PURE__*/React.createElement("button", {
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-[90px] shrink-0"
className: "px-1.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-amber-400 border border-amber-800 rounded text-xs font-bold flex items-center gap-1 max-w-[90px] shrink-0"
}, /*#__PURE__*/React.createElement("span", {
className: "inline-flex items-center shrink-0"
}, /*#__PURE__*/React.createElement("i", {
"data-lucide": "music",
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" }))), /*#__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", {
})), /*#__PURE__*/React.createElement("span", { className: "truncate text-xs 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", {
onClick: (e) => {
e.stopPropagation();
ensureAndOpenVstGui(track.id, track.instrumentId);
},
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"
}, /*#__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", {
className: "px-1.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-violet-300 border border-violet-800 rounded text-xs font-bold flex items-center gap-1 shrink-0"
}, /*#__PURE__*/React.createElement("i", { "data-lucide": "sliders-horizontal", className: "w-3 h-3 shrink-0" }), /*#__PURE__*/React.createElement("span", { className: "truncate text-xs min-w-0" }, "GUI")) : null)), /*#__PURE__*/React.createElement("div", {
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",
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/promptTemplateManager.js?v=202607281039"></script>
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
<script src="/static/js/app.precompiled.js?v=202608132205" defer></script>
<script src="/static/js/app.precompiled.js?v=202608141030" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {
+162 -122
View File
@@ -77,6 +77,17 @@ static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA
}
#endif
// Serialize plugin loads across channels: concurrent VST3 createInstance on
// separate worker threads (Nexus) deadlocks inside the SDK factory - all
// stuck forever (observed: 3 concurrent Nexus loads, zero assign returned).
// One load at a time; SF2/SFZ loads are fast, contention negligible.
static std::mutex g_loadMutex;
// Pending OPEN_GUI requests whose channel instrument was not loaded yet.
// The main loop fulfils them as soon as the load job completes (assign ok),
// so the GUI button works regardless of JS retry timing.
static std::mutex g_pendingGuiMutex;
static std::map<uint32_t, std::pair<void*, std::string>> g_pendingGui;
// B9: native Win32 window for the VST editor (replaces the WebView2 surface —
// the HTML window was drawn ON TOP of the plugin GUI). MUST be created on the
// ChannelWorker thread so the worker's idle message pump services its messages.
@@ -333,6 +344,137 @@ int main(int argc, char* argv[]) {
auto startTime = std::chrono::steady_clock::now();
uint64_t blockCount = 0;
// OPEN_GUI handling - shared by the control queue and the pending-GUI
// sweep. Gate on the MAIN thread before creating a window: instrument
// load is ASYNC (worker thread); OPEN_GUI too early would attach to an
// empty channel and leave a blank window. Not loaded yet => remember the
// request; the sweep retries once the load job completes. Window MUST
// belong to the MAIN thread (audio loop pump dispatches its messages);
// a window on a worker whose pump is idle during jobs hangs
// view->attached() (gui_probe: two_workers_close TIMEOUT). The attach
// job runs on the channel worker (COM STA apartment stays alive).
auto handleOpenGui = [&](uint32_t guiCh, uintptr_t arg1, const std::string& pluginId) {
if (!instruments.get(guiCh)) {
std::cerr << "[NativeBridge] GUI deferred ch=" << guiCh
<< " plugin=" << pluginId << " (no instrument yet) - queued" << std::endl;
std::lock_guard<std::mutex> lock(g_pendingGuiMutex);
g_pendingGui[guiCh] = { (void*)arg1, pluginId };
return;
}
if (!workers[guiCh]) workers[guiCh] = std::make_unique<ChannelWorker>();
void* hwnd = (void*)arg1;
#ifdef _WIN32
// Window PHAI thuoc MAIN thread (audio loop pump nay dispatch
// messages cua no moi vong lap). Tao/cap nhat window ngay tai day.
HWND nativeHwnd = nullptr;
{
std::lock_guard<std::mutex> lock(g_guiMutex);
auto it = g_guiWindows.find(guiCh);
if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second;
}
if (hwnd == 0) {
if (nativeHwnd && IsWindow(nativeHwnd)) {
hwnd = (void*)nativeHwnd;
SetWindowTextA(nativeHwnd, pluginId.c_str());
ShowWindow(nativeHwnd, SW_SHOW);
SetForegroundWindow(nativeHwnd);
// Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay
// the boi assign() -> WM_DESTROY sau nay lookup inst MOI.
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
} else {
nativeHwnd = (HWND)create_native_vst_window(pluginId.c_str());
if (!nativeHwnd) {
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << pluginId << std::endl;
return;
}
{
std::lock_guard<std::mutex> lock(g_guiMutex);
g_guiWindows[guiCh] = nativeHwnd; // keep window alive
g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup
}
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
hwnd = (void*)nativeHwnd;
}
}
#endif
workers[guiCh]->post([&instruments, guiCh, hwnd, arg2 = pluginId]() {
if (!instruments.get(guiCh)) {
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
<< " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
return;
}
std::cerr << "[dbg] openGUI thread start hwnd=" << hwnd
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
#ifdef _WIN32
// Option B: chi 1 editor VST mo tai 1 thoi diem toan
// bridge. Instance thu 2 cua CUNG plugin (Nexus) attach
// view o apartment/worker khac -> treo. Dong editor cua
// channel khac TRUOC khi attach: WM_CLOSE -> main pump
// (audio loop) destroy window -> WM_DESTROY -> closeGUI()
// + xoa registry. Chay tren worker job de khong stall
// writeIndex cua real-time loop.
{
std::vector<uint32_t> others;
{
std::lock_guard<std::mutex> lock(g_guiMutex);
for (const auto& kv : g_guiWindows)
if (kv.first != guiCh) others.push_back(kv.first);
}
for (uint32_t y : others) {
HWND yHwnd = nullptr;
{
std::lock_guard<std::mutex> lock(g_guiMutex);
auto it = g_guiWindows.find(y);
if (it != g_guiWindows.end()) yHwnd = (HWND)it->second;
}
if (!yHwnd || !IsWindow(yHwnd)) continue;
PostMessage(yHwnd, WM_CLOSE, 0, 0);
std::cerr << "[dbg] openGUI: closing editor ch=" << y
<< " before attach ch=" << guiCh << std::endl;
bool closed = false;
for (int i = 0; i < 500; ++i) {
{
std::lock_guard<std::mutex> lock(g_guiMutex);
if (g_guiWindows.find(y) == g_guiWindows.end()) { closed = true; break; }
}
Sleep(10);
}
if (!closed)
std::cerr << "[dbg] openGUI: editor ch=" << y
<< " not closed in 5s, proceeding" << std::endl;
}
}
#else
(void)0;
#endif
if (auto* inst = instruments.get(guiCh)) {
// Reopen sau khi dong: reload() (terminate + loadPlugin)
// PHAI chay tren worker thread nay - COM STA apartment
// cua channel song o day. view->attached() cung chay o
// day; window thuoc main thread nen main pump (audio
// loop) dispatch messages cua no - khong can pump worker.
// Chan UAF bang flag reloading_ (set/clear duoi engine
// mutex; renderAll giu mutex khi process).
bool guard = inst->needsReload();
if (guard) instruments.setReloading(guiCh, true);
bool ok = false;
{
// Serialize createInstance (reload) with other loads.
std::lock_guard<std::mutex> lg(g_loadMutex);
ok = inst->reloadForGUI();
}
if (ok) ok = inst->attachView(hwnd);
if (guard) instruments.setReloading(guiCh, false);
if (ok)
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
else
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
<< " plugin=" << arg2 << std::endl;
}
});
};
// 2. REAL-TIME AUDIO PROCESSING ENGINE LOOP
while (true) {
#ifdef _WIN32
@@ -389,6 +531,7 @@ int main(int argc, char* argv[]) {
#endif
std::cerr << "[dbg] load thread start ch=" << ch
<< " type=" << (int)t << " path=" << path << std::endl;
std::lock_guard<std::mutex> lg(g_loadMutex);
bool ok = instruments.assign(ch, t, path, sampleRate, block);
std::cerr << "[dbg] assign returned ch=" << ch << " ok=" << (ok ? 1 : 0) << std::endl;
if (ok) {
@@ -423,132 +566,29 @@ 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
uint32_t guiCh = c.channel;
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>();
void* hwnd = (void*)(uintptr_t)c.arg1;
#ifdef _WIN32
// Window PHAI thuoc MAIN thread (audio loop pump nay dispatch
// messages cua no moi vong lap). Window tren worker + worker
// khong pump trong luc job chay -> view->attached() treo
// (gui_probe: two_workers_close TIMEOUT; same_thread /
// two_instances_close — window tren main — OK). Tao/cap nhat
// window ngay tai day tren main thread.
HWND nativeHwnd = nullptr;
{
std::lock_guard<std::mutex> lock(g_guiMutex);
auto it = g_guiWindows.find(guiCh);
if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second;
}
if (hwnd == 0) {
if (nativeHwnd && IsWindow(nativeHwnd)) {
hwnd = (void*)nativeHwnd;
SetWindowTextA(nativeHwnd, std::string(c.arg2).c_str());
ShowWindow(nativeHwnd, SW_SHOW);
SetForegroundWindow(nativeHwnd);
// Reuse: cap nhat USERDATA (channel+1) — inst CU da bi
// thay the boi assign() -> WM_DESTROY sau nay lookup
// inst MOI, khong dung con tro dangling.
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
} else {
nativeHwnd = (HWND)create_native_vst_window(std::string(c.arg2).c_str());
if (!nativeHwnd) {
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << c.arg2 << std::endl;
continue;
}
{
std::lock_guard<std::mutex> lock(g_guiMutex);
g_guiWindows[guiCh] = nativeHwnd; // keep window alive
g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup
}
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
hwnd = (void*)nativeHwnd;
}
}
#endif
workers[guiCh]->post([&instruments, guiCh, hwnd, arg2 = std::string(c.arg2)]() {
if (!instruments.get(guiCh)) {
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
<< " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
return;
}
std::cerr << "[dbg] openGUI thread start hwnd=" << hwnd
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
#ifdef _WIN32
// Option B: chi 1 editor VST mo tai 1 thoi diem toan
// bridge. Instance thu 2 cua CUNG plugin (Nexus) attach
// view o apartment/worker khac -> treo. Dong editor cua
// channel khac TRUOC khi attach: WM_CLOSE -> main pump
// (audio loop) destroy window -> WM_DESTROY -> closeGUI()
// + xoa registry. Chay tren worker job de khong stall
// writeIndex cua real-time loop.
{
std::vector<uint32_t> others;
{
std::lock_guard<std::mutex> lock(g_guiMutex);
for (const auto& kv : g_guiWindows)
if (kv.first != guiCh) others.push_back(kv.first);
}
for (uint32_t y : others) {
HWND yHwnd = nullptr;
{
std::lock_guard<std::mutex> lock(g_guiMutex);
auto it = g_guiWindows.find(y);
if (it != g_guiWindows.end()) yHwnd = (HWND)it->second;
}
if (!yHwnd || !IsWindow(yHwnd)) continue;
PostMessage(yHwnd, WM_CLOSE, 0, 0);
std::cerr << "[dbg] openGUI: closing editor ch=" << y
<< " before attach ch=" << guiCh << std::endl;
bool closed = false;
for (int i = 0; i < 500; ++i) {
{
std::lock_guard<std::mutex> lock(g_guiMutex);
if (g_guiWindows.find(y) == g_guiWindows.end()) { closed = true; break; }
}
Sleep(10);
}
if (!closed)
std::cerr << "[dbg] openGUI: editor ch=" << y
<< " not closed in 5s, proceeding" << std::endl;
}
}
#else
(void)0;
#endif
if (auto* inst = instruments.get(guiCh)) {
// Reopen sau khi dong: reload() (terminate + loadPlugin)
// PHAI chay tren worker thread nay — COM STA apartment
// cua channel song o day. view->attached() cung chay o
// day; window thuoc main thread nen main pump (audio
// loop) dispatch messages cua no — khong can pump worker.
// Chan UAF bang flag reloading_ (set/clear duoi engine
// mutex; renderAll giu mutex khi process).
bool guard = inst->needsReload();
if (guard) instruments.setReloading(guiCh, true);
bool ok = inst->reloadForGUI() && inst->attachView(hwnd);
if (guard) instruments.setReloading(guiCh, false);
if (ok)
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
else
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
<< " plugin=" << arg2 << std::endl;
}
});
handleOpenGui(guiCh, (uintptr_t)c.arg1, std::string(c.arg2));
}
}
shmIPC->controlQueueCount = 0;
// Pending OPEN_GUI requests: fulfilled as soon as the channel's
// instrument becomes available (load job completed on its worker).
{
std::vector<std::pair<uint32_t, std::pair<void*, std::string>>> readyGui;
{
std::lock_guard<std::mutex> lock(g_pendingGuiMutex);
for (auto it = g_pendingGui.begin(); it != g_pendingGui.end(); ) {
if (instruments.get(it->first)) {
readyGui.push_back({ it->first, it->second });
it = g_pendingGui.erase(it);
} else ++it;
}
}
for (auto& g : readyGui)
handleOpenGui(g.first, (uintptr_t)g.second.first, g.second.second);
}
// B. Snapshot queued MIDI events (bounded copy, queue reset immediately)
uint32_t nEvents = shmIPC->midiQueueCount > 64 ? 64 : shmIPC->midiQueueCount;
SharedAudioBufferIPC::MidiEventIPC evts[64];
+22
View File
@@ -15,9 +15,15 @@ use tauri_plugin_dialog::{DialogExt, FilePath};
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
use tauri_plugin_shell::ShellExt;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
mod shm;
/// App đang tắt (cửa sổ chính đã Destroyed) — pump thread phải dừng ngay,
/// không respawn bridge mới sau khi sidecar đã bị giết (fix: app còn sống
/// sau khi đóng window → pump thấy stall 3s → spawn bridge mới → orphan).
static SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
use shm::{Shm, ShmState};
struct EngineProcess(Mutex<Option<CommandChild>>);
@@ -356,6 +362,10 @@ pub fn run() {
let mut down_emitted = false;
let mut restart_attempts = 0u32;
loop {
// App đang tắt (window Destroyed) — dừng pump, không respawn bridge.
if SHUTTING_DOWN.load(Ordering::Relaxed) {
break;
}
let state = pump_handle.state::<ShmState>();
let guard = match state.0.lock() {
Ok(g) => g,
@@ -385,6 +395,10 @@ pub fn run() {
AudioFrame { l: l.to_vec(), r: r.to_vec() },
);
} else if last_change.elapsed() >= std::time::Duration::from_secs(3) && !down_emitted {
// Check lai truoc khi respawn — Destroyed co the vua set flag.
if SHUTTING_DOWN.load(Ordering::Relaxed) {
break;
}
down_emitted = true;
if restart_attempts == 0 {
restart_attempts += 1;
@@ -483,6 +497,8 @@ pub fn run() {
.on_window_event(|window, event| {
// Terminate sidecar khi DAW window dong — tranh orphan process
if let tauri::WindowEvent::Destroyed = event {
// Dung pump thread: khong respawn bridge sau khi sidecar da bi giet.
SHUTTING_DOWN.store(true, Ordering::Relaxed);
let child = window
.state::<EngineProcess>()
.0
@@ -506,6 +522,12 @@ pub fn run() {
let _ = child.kill();
println!("daw_vst_bridge sidecar terminated.");
}
// Cua so chinh da dong — ep app thoat han. Neu con window
// phu/an giu event loop song, Tauri khong tu exit; exit(0)
// ket thuc dut diem (pump da dung, khong con respawn bridge).
if window.label() == "main" {
window.app_handle().exit(0);
}
}
})
.run(tauri::generate_context!())
+8 -2
View File
@@ -10,8 +10,14 @@ from app.core.vst_engine import PluginManager, midi_note_to_freq, render_midi_ev
class TestPluginManager:
def test_init(self):
pm = PluginManager()
assert pm.vst_dir == "/opt/daw_engine/vst3"
assert pm.sf_dir == "/opt/daw_engine/soundfonts"
if os.name == "nt":
pf = os.environ.get("ProgramFiles", r"C:\Program Files")
assert pm.vst_dir == os.path.join(pf, "Common Files", "VST3")
root = os.environ.get("APPDATA") or os.path.expanduser("~")
assert pm.sf_dir == os.path.join(root, "SonicForgeDAW", "soundfonts")
else:
assert pm.vst_dir == "/opt/daw_engine/vst3"
assert pm.sf_dir == "/opt/daw_engine/soundfonts"
def test_midi_events_to_messages(self):
if not HAS_PEDALBOARD: