fix crash bridge khi đổi SF→VSTi đang play: silence same-plugin theo path MỚI trước assign() (createInstance) — track restore giữ cùng DLL process song song audio loop = 2 threads 1 DLL Nexus crash; không tự mở GUI khi đang play (C++ mute channel tới khi đóng GUI → tắt âm)

This commit is contained in:
2026-08-14 17:08:17 +07:00
parent af59aaf257
commit 2596372ab1
4 changed files with 49 additions and 20 deletions
+7
View File
@@ -86,3 +86,10 @@ chạy từng mục trên máy Windows (hoặc OS native có đủ toolchain),
- ⚠️ Nexus VST3 silent (license). Dùng ACDD demo để chứng minh âm.
- ⚠️ daw_engine.exe serve index.html từ memory lúc start (v=202608121600) dù file trên đĩa đã bump v=202608121700 → restart app mới nhận build mới.
- Ghi chú deploy: copy `app/static/js/app.precompiled.js` + `app/templates/index.html` → `dist/`, `src-tauri/resources/`, `src-tauri/target/release/` (cùng dưới `daw_engine/_internal/app/`), bump `?v=` chống cache.
## Kết quả test THẬT Windows 2026-08-13 (build af59aaf + hotfix SF→VSTi khi play)
- 🐛 **BUG (user báo): auto-restore project → track SF2 đang play → nút Synth đổi SF→VSTi → crash bridge, tắt âm, không play lại được.**
- Nguyên nhân C++: LOAD job (type=2) silence same-plugin channels CHỈ SAU `assign()` — mà `assign()` chứa `createInstance` (loadPlugin) chạy trên worker. Nếu track KHÁC (từ restore) đang giữ CÙNG DLL VST3 với instrument mới → trong lúc createInstance, audio loop vẫn `process()` inst của track đó → 2 threads trong 1 DLL (Nexus) → crash thoát im lặng.
- Fix C++ (`main.cpp` LOAD job): tính `spNew` (channels giữ path mới, lowercase so sánh) TRƯỚC `assign()`, silence chúng cùng `spOld`; restore sau assign dùng `unmute_if_not_closing`. Tách `lower_path_str()` khỏi `lower_plugin_path()`.
- Nguyên nhân "tắt âm + không tải lại được": sau load VST3 OK, JS tự mở native GUI (`openVstGuiRetry`) dù đang play → C++ mute channel tới khi đóng GUI (reloading_=true, note-on drop) → track câm vĩnh viễn giữa lúc play.
- Fix JS (`app.jsx` loadTrackInstrumentToBridge): chỉ tự mở GUI khi `!isPlayingRef.current`; đang play thì chỉ load instrument im lặng, GUI mở qua nút GUI riêng (`ensureAndOpenVstGui`).
- ✅ **Windows verify**: mở project cũ (track SF2 + track VST3), play, đổi track SF2 → VSTi cùng DLL track kia → không crash, âm VSTi mới phát; đổi lại SF2 → play tiếp được. Mở GUI lúc đang play → track mute (trade-off có chủ đích, âm lại sau khi đóng GUI).
+5 -1
View File
@@ -15548,7 +15548,11 @@ const App = () => {
}
// Requirement 2: chọn VST3 qua nút Synth → load xong mở native GUI
// (C++ attach editor vào cửa sổ bridge tự tạo — control type=4, hwnd=0).
if (ok && btype === 'VST3' && window.NativeBridgeService.openNativeGUI) {
// SF→VSTi BUG 2026-08-13: GUI mở → C++ mute channel tới khi đóng GUI →
// đang play thì âm tắt ngay (reloading_=true, note-on drop). Khi đang
// play chỉ load instrument im lặng; GUI mở qua nút GUI riêng
// (ensureAndOpenVstGui) — không tự mở giữa lúc phát.
if (ok && btype === 'VST3' && !isPlayingRef.current && window.NativeBridgeService.openNativeGUI) {
try { await openVstGuiRetry(instrumentId, bch, 20); } catch (e) {}
}
}
+5 -1
View File
@@ -809,7 +809,11 @@ if(isSf&&window.__bridgeLoadedChannels)delete window.__bridgeLoadedChannels[bch]
// phải gửi lại program change để ra ĐÚNG instrument.
if(ok&&window.__bridgeLastProgram){const _pfx=trackId+':';Object.keys(window.__bridgeLastProgram).forEach(k=>{if(k.indexOf(_pfx)===0)delete window.__bridgeLastProgram[k];});}if(ok){if(!window.__bridgeLoadedChannels)window.__bridgeLoadedChannels={};window.__bridgeLoadedChannels[bch]=true;}if(ok&&btype==='VST3'&&p){try{if(!window.__bridgePluginPaths)window.__bridgePluginPaths={};window.__bridgePluginPaths[instrumentId]=p;updateActiveTracks(prev=>prev.map(t=>t.id===trackId?{...t,synth_engine:{...(t.synth_engine||{}),plugin_path:p}}:t));}catch(e){}}// Requirement 2: chọn VST3 qua nút Synth → load xong mở native GUI
// (C++ attach editor vào cửa sổ bridge tự tạo — control type=4, hwnd=0).
if(ok&&btype==='VST3'&&window.NativeBridgeService.openNativeGUI){try{await openVstGuiRetry(instrumentId,bch,20);}catch(e){}}}})();};const setTrackInstrumentWithProgram=(trackId,instrumentId,programNumber,displayName,bankNumber)=>{const isSfInstrument=instrumentId&&typeof instrumentId==='string'&&instrumentId.startsWith('sf_');const sfBank=bankNumber!==undefined?bankNumber:isSfInstrument?0:undefined;const sfProg=programNumber!==undefined?programNumber:undefined;var mt=activeTracksRef.current||tracks;var curTrk=null;for(var ci=0;ci<mt.length;ci++){if(mt[ci].id===trackId){curTrk=mt[ci];break;}}// ── Unload Carla bridge khi chuyển từ VSTi sang instrument KHÔNG phải VST ──
// SF→VSTi BUG 2026-08-13: GUI mở → C++ mute channel tới khi đóng GUI →
// đang play thì âm tắt ngay (reloading_=true, note-on drop). Khi đang
// play chỉ load instrument im lặng; GUI mở qua nút GUI riêng
// (ensureAndOpenVstGui) — không tự mở giữa lúc phát.
if(ok&&btype==='VST3'&&!isPlayingRef.current&&window.NativeBridgeService.openNativeGUI){try{await openVstGuiRetry(instrumentId,bch,20);}catch(e){}}}})();};const setTrackInstrumentWithProgram=(trackId,instrumentId,programNumber,displayName,bankNumber)=>{const isSfInstrument=instrumentId&&typeof instrumentId==='string'&&instrumentId.startsWith('sf_');const sfBank=bankNumber!==undefined?bankNumber:isSfInstrument?0:undefined;const sfProg=programNumber!==undefined?programNumber:undefined;var mt=activeTracksRef.current||tracks;var curTrk=null;for(var ci=0;ci<mt.length;ci++){if(mt[ci].id===trackId){curTrk=mt[ci];break;}}// ── Unload Carla bridge khi chuyển từ VSTi sang instrument KHÔNG phải VST ──
// (soundfont/GM/default). Nếu không, Carla vẫn chạy với VSTi cũ → MIDI vẫn
// play qua Carla bridge (âm sai instrument + âm kẹt không dừng được).
try{const _hasInst=!!instrumentId;const _wasVst=curTrk&&curTrk.synth_engine&&String(curTrk.synth_engine.type||'').indexOf('vst')!==-1;const _nowVst=_hasInst&&!isSfInstrument;if(_wasVst&&!_nowVst&&window.SonicCarlaMidi&&window.SonicCarlaMidi.stopBridge){window.SonicCarlaMidi.stopBridge();try{if(window.SonicMidiRouter)window.SonicMidiRouter.panic();else if(window.SonicSF&&window.SonicSF.stopAll)window.SonicSF.stopAll();}catch(e){}stopAllNativeSfNotes();console.log('[Instrument] Carla bridge unloaded — track',trackId,'switched from VSTi to non-VST');}}catch(e){console.warn('[Instrument] carla-stop on instrument switch error:',e);}var mch=curTrk?assignTrackMidiChannel(curTrk,mt):sfBank===128?9:0;updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const hasInstrument=!!instrumentId;const instrType=isSfInstrument?'soundfont':hasInstrument?'vst3':'default';const synthEngine=hasInstrument?{type:instrType,plugin_id:instrumentId,soundfont_bank:sfBank!==undefined?sfBank:0,soundfont_program:sfProg!==undefined?sfProg:0,soundfont_id:isSfInstrument?instrumentId.replace('sf_',''):''}:undefined;return{...t,midiChannel:mch,instrumentId,instrumentProgram:sfProg,instrumentName:displayName,soundfont_bank:sfBank,soundfont_program:sfProg,synth_engine:synthEngine,type:hasInstrument?'MIDI':t.type==='MIDI'?'audio':t.type};}));setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setSynthCategory(null);// Trigger FluidSynth load + program change when soundfont instrument selected
+32 -18
View File
@@ -227,13 +227,16 @@ private:
// Two threads inside one VST3 DLL (Nexus) crash/deadlock: the audio loop's
// process() on one instance must not race createInstance/createView/terminate
// on another. Windows plugin paths are case-insensitive — compare lowercased.
static std::string lower_plugin_path(uint32_t ch) {
std::string p = g_engine ? g_engine->pathOf(ch) : std::string();
static std::string lower_path_str(std::string p) {
std::transform(p.begin(), p.end(), p.begin(),
[](unsigned char c) { return (char)::tolower(c); });
return p;
}
static std::string lower_plugin_path(uint32_t ch) {
return g_engine ? lower_path_str(g_engine->pathOf(ch)) : std::string();
}
// Channels (other than ch) currently assigned the same plugin DLL path.
static std::vector<uint32_t> same_plugin_channels(uint32_t ch) {
std::vector<uint32_t> out;
@@ -820,8 +823,14 @@ int main(int argc, char* argv[]) {
// session-restore instances). Silence this channel (its old
// instance is being torn down / replaced) and every
// same-plugin channel for the whole job. Old path covers
// the old-instance teardown; the new path (after assign)
// covers the freshly created instance.
// the old-instance teardown; the new path (computed BEFORE
// assign — see spNew below) covers the freshly created
// instance. CRASH FIX (SF→VSTi while playing): the new
// path must be silenced BEFORE assign() — assign() itself
// runs createInstance (loadPlugin) on this worker, and a
// restored channel on ANOTHER track holding the SAME DLL
// kept process()ing on the audio loop during that window
// (old code silenced spNew only AFTER assign → crash).
instruments.setReloading(ch, true);
std::vector<uint32_t> spOld = same_plugin_channels(ch);
for (uint32_t y : spOld) {
@@ -829,6 +838,24 @@ int main(int argc, char* argv[]) {
std::cerr << "[dbg] load: silenced same-plugin ch=" << y
<< " during load ch=" << ch << std::endl;
}
// Channels (other than ch) already assigned the NEW path —
// silence BEFORE createInstance enters that DLL.
std::vector<uint32_t> spNew;
if (t == InstrumentType::VST3) {
std::string np = lower_path_str(path);
if (!np.empty()) {
for (uint32_t y = 0; y < 16; ++y) {
if (y == ch) continue;
if (!g_engine->get(y)) continue;
if (std::find(spOld.begin(), spOld.end(), y) != spOld.end()) continue;
if (lower_plugin_path(y) != np) continue;
g_engine->setReloading(y, true);
spNew.push_back(y);
std::cerr << "[dbg] load: silenced same-plugin (new path) ch=" << y
<< " during load ch=" << ch << std::endl;
}
}
}
#ifdef _WIN32
// DONG cua so editor dang mo cua channel TRUOC khi assign():
// thay the inst (VST3 -> SF2/inst khac) ma editor con song ->
@@ -879,25 +906,12 @@ int main(int argc, char* argv[]) {
std::cerr << "[NativeBridge] instrument load FAILED ch=" << ch
<< " type=" << (int)t << " " << path << std::endl;
}
// New-path channels (the freshly created instance's DLL) —
// silence any not already covered by the old path.
std::vector<uint32_t> spNew;
if (ok) spNew = same_plugin_channels(ch);
for (uint32_t y : spNew) {
if (std::find(spOld.begin(), spOld.end(), y) == spOld.end()) {
g_engine->setReloading(y, true);
std::cerr << "[dbg] load: silenced same-plugin ch=" << y
<< " (new path) during load ch=" << ch << std::endl;
}
}
// Restore. Failed load keeps the OLD instance — unmute it
// back (silenced at job start); a successful assign leaves
// the fresh instance unmuted by construction.
if (!ok) g_engine->setReloading(ch, false);
for (uint32_t y : spOld) unmute_if_not_closing(y, "load");
for (uint32_t y : spNew)
if (std::find(spOld.begin(), spOld.end(), y) == spOld.end())
unmute_if_not_closing(y, "load");
for (uint32_t y : spNew) unmute_if_not_closing(y, "load");
});
} else if (c.type == 1) { // PANIC
instruments.allNotesOff();