feat: native folder picker (Explorer) + Synth liet ke VSTi da scan + fix VU leak (ban 1.1.4)

1) Folder picker dung WINDOW EXPLORER (khong prompt nhap tay):
   - src-tauri/src/lib.rs: IPC bridge — thread watcher ipc/pick_dir.request
     -> run_on_main_thread -> dialog().file().blocking_pick_folder()
     (tauri-plugin-dialog = IFileDialog/Explorer) -> ghi pick_dir.response;
     ghi marker tauri_bridge_ready luc setup.
   - app/api/v1/plugins.py: POST /pick-dir (def sync -> threadpool, khong
     auth) — uu tien Tauri bridge, fallback PowerShell FolderBrowserDialog
     (Win) / osascript (macOS) / zenity-kdialog (Linux).
   - app.jsx pickPluginFolder: 1) pickPluginDir (native) -> 2) __TAURI__
     invoke -> 3) in-app browser -> 4) prompt (cuoi cung).

2) Nut Synth liet ke VSTi da scan (truoc day rong):
   - Root: list_available() chi quet settings.VST_DIR (mac dinh
     /opt/daw_engine/vst3) trong khi Plugin Manager scan plugin_dirs user.
   - plugins.py list_plugins: gop _scan_vst_in_dirs(plugin_dirs) (file
     .vst3/.dll/.so + folder X.vst3 Windows).
   - vst_engine.py: PluginManager.extra_vst_dirs + _scan_plugins quet them
     (ca folder .vst3) + get_plugin_manager doc plugin_dirs.json -> load_vst
     tim thay plugin user scan khi render.

3) VU meter leak: section-tab play -> sang MAIN SESSION -> track MAIN van
   animate theo am section.
   - Root: VU tick fallback _sub_ (sub-node section co analyser) chay cho
     ca canvas MAIN; startSubTabPlayback ghi node o key track MAIN.
   - Fix: fallback _sub_ chi ap dung cho canvas SECTION (isSessVu); 2 trigger
     piano-roll them prefix _sess_ theo st.parent_tab_id.

Verify: 86 tests pass; engine frozen STARTUP 1.35s, 1 engine, 0 spawn '-c';
pick-dir IPC mock tra dung path; scan VST user dirs OK.
This commit is contained in:
2026-08-09 13:05:53 +00:00
parent 5ae4fd6149
commit cc8b286f6c
7 changed files with 251 additions and 36 deletions
+19 -14
View File
@@ -5333,28 +5333,30 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData }) => {
setPmPicker(null);
};
const pickPluginFolder = async () => {
const addDir = (p) => { if (p && !pmDirs.includes(p)) setPmDirs(prev => [...prev, p]); };
try {
// 1) NATIVE dialog qua engine (Tauri bridge IFileDialog/Explorer;
// fallback PowerShell/zenity/osascript) UI chy localhost:8000 nên
// __TAURI__ không có, window.prompt vô hiu trong WebView2.
try {
const d = await window.SonicAPI.pickPluginDir();
if (d && typeof d.path === 'string' && d.path) { addDir(d.path); return; }
} catch (e) { /* fallthrough */ }
// 2) Tauri dialog trc tiếp (ch khi page đưc Tauri serve)
if (window.__TAURI__ && window.__TAURI__.core && window.__TAURI__.core.invoke) {
const sel = await window.__TAURI__.core.invoke('plugin:dialog|open', {
options: { directory: true, multiple: false }
});
if (typeof sel === 'string' && sel) {
if (!pmDirs.includes(sel)) setPmDirs(prev => [...prev, sel]);
}
return;
if (typeof sel === 'string' && sel) { addDir(sel); return; }
}
// Trinh duyet thu muc in-app (backend) hoat dong khi UI chay tren
// localhost:8000 (khong co __TAURI__, window.prompt vo hieu trong WebView2)
// 3) Trình duyt thư mc in-app (backend) fallback mi OS
await openPluginPicker();
return;
} catch (e) {
// Fallback cuoi: prompt nhap tay (browser thuan, khong phai WebView2)
// 4) Cui cùng: prompt nhp tay (browser thun, không phi WebView2)
try {
const manual = window.prompt('Nhập đường dẫn thư mục plugin (VST / SoundFont):');
if (manual && manual.trim()) {
const p = manual.trim();
if (!pmDirs.includes(p)) setPmDirs(prev => [...prev, p]);
}
if (manual && manual.trim()) addDir(manual.trim());
} catch (e2) {
showToast('Browse failed: ' + (e.message || e), 'error');
}
@@ -8902,7 +8904,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
keybedMouseDownRef.current = true;
try {
if (window.triggerMidiVuActivity) {
window.triggerMidiVuActivity(st.trackId, 100);
window.triggerMidiVuActivity((st.parent_tab_id && st.parent_tab_id.startsWith('session_') ? '_sess_' : '') + st.trackId, 100);
}
if (window.SonicSF) {
window.SonicSF.playNote(pitch, 100, 500, undefined, kbCtx.program, null, kbCtx.ch, kbCtx.synthEngine);
@@ -8915,7 +8917,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
if (keybedMouseDownRef.current) {
try {
if (window.triggerMidiVuActivity) {
window.triggerMidiVuActivity(st.trackId, 100);
window.triggerMidiVuActivity((st.parent_tab_id && st.parent_tab_id.startsWith('session_') ? '_sess_' : '') + st.trackId, 100);
}
if (window.SonicSF) {
window.SonicSF.playNote(pitch, 100, 200, undefined, kbCtx.program, null, kbCtx.ch, kbCtx.synthEngine);
@@ -26322,7 +26324,10 @@ STRICT CONSTRAINTS:
// Section item trên MAIN track: VU theo sub-nodes ca track này (key
// <trackId>_sub_*) âm section đi thng masterBus.input (không qua
// node chính) node chính không có tín hiu VU track đng yên.
if (audioPeak <= 0.001) {
// CH fallback cho canvas SECTION (_sess_) KHÔNG cho canvas MAIN:
// section-tab đang play chuyn sang MAIN SESSION VU track MAIN
// không đưc animate theo âm section (user bug).
if (audioPeak <= 0.001 && isSessVu) {
for (var sk in trackNodes) {
if (sk.indexOf(trackId + '_sub_') === 0) {
const sn = trackNodes[sk];