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:
+43
-12
@@ -1,5 +1,6 @@
|
||||
# SonicForge Studio VST / VSTi Engine Service
|
||||
import os
|
||||
import json
|
||||
import numpy as np
|
||||
import functools
|
||||
from ctypes import c_char_p
|
||||
@@ -101,19 +102,38 @@ _PLUGIN_MANAGER_INSTANCE = None
|
||||
_PLUGIN_MANAGER_ARGS = None
|
||||
_SF_INSTRUMENTS_CACHE = {} # sf_id → list[presets]
|
||||
|
||||
def _load_user_plugin_dirs() -> list:
|
||||
"""Đọc plugin_dirs.json (Plugin Manager user chọn) — cùng file với
|
||||
plugins.py (STORAGE_DIR/plugin_dirs.json). Không import plugins.py để
|
||||
tránh vòng import (plugins.py import vst_engine)."""
|
||||
try:
|
||||
from app.config import settings as _st
|
||||
path = os.path.join(_st.STORAGE_DIR, "plugin_dirs.json")
|
||||
if os.path.exists(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return [d for d in (data.get("plugin_dirs") or []) if d]
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
|
||||
|
||||
def get_plugin_manager(vst_dir=None, sf_dir=None, upload_sf_dir=None) -> "PluginManager":
|
||||
"""Singleton: reuse PluginManager when args match, else create new.
|
||||
Default dirs từ settings (env/.env/docker-compose hoặc user override)."""
|
||||
Default dirs từ settings (env/.env/docker-compose hoặc user override).
|
||||
VST scan gộp thêm plugin_dirs user (Plugin Manager) — nút Synth phải liệt
|
||||
kê được VSTi đã scan và load_vst phải tìm thấy chúng khi render."""
|
||||
from app.config import settings as _st
|
||||
vst_dir = vst_dir or _st.VST_DIR
|
||||
sf_dir = sf_dir or _st.SOUNDFONT_DIR
|
||||
upload_sf_dir = upload_sf_dir or _st.STORAGE_DIR + "/soundfonts"
|
||||
extra = _load_user_plugin_dirs()
|
||||
global _PLUGIN_MANAGER_INSTANCE, _PLUGIN_MANAGER_ARGS
|
||||
args = (vst_dir, sf_dir, upload_sf_dir)
|
||||
args = (vst_dir, sf_dir, upload_sf_dir, tuple(extra))
|
||||
if _PLUGIN_MANAGER_INSTANCE is not None and _PLUGIN_MANAGER_ARGS == args:
|
||||
return _PLUGIN_MANAGER_INSTANCE
|
||||
_PLUGIN_MANAGER_ARGS = args
|
||||
_PLUGIN_MANAGER_INSTANCE = PluginManager(vst_dir, sf_dir, upload_sf_dir)
|
||||
_PLUGIN_MANAGER_INSTANCE = PluginManager(vst_dir, sf_dir, upload_sf_dir, extra_vst_dirs=extra)
|
||||
return _PLUGIN_MANAGER_INSTANCE
|
||||
|
||||
def load_soundfont_cached(path: str):
|
||||
@@ -162,23 +182,34 @@ def release_soundfont(path: str):
|
||||
_FLUID_CACHE[path] = (fl, ref - 1)
|
||||
|
||||
class PluginManager:
|
||||
def __init__(self, vst_dir=None, sf_dir=None, upload_sf_dir=None):
|
||||
def __init__(self, vst_dir=None, sf_dir=None, upload_sf_dir=None, extra_vst_dirs=None):
|
||||
from app.config import settings as _st
|
||||
self.vst_dir = vst_dir or _st.VST_DIR
|
||||
self.sf_dir = sf_dir or _st.SOUNDFONT_DIR
|
||||
self.upload_sf_dir = upload_sf_dir
|
||||
# Thư mục VST thêm (plugin_dirs user scan trong Plugin Manager) —
|
||||
# list_available()/load_vst phải thấy VSTi user đã scan (bug: nút
|
||||
# Synth chỉ quét vst_dir env mặc định /opt/daw_engine/vst3).
|
||||
self.extra_vst_dirs = [d for d in (extra_vst_dirs or []) if d]
|
||||
self._sf_scan_cache = None # cache for _scan_soundfonts()
|
||||
|
||||
def _scan_plugins(self) -> dict:
|
||||
plugins = {}
|
||||
if not os.path.isdir(self.vst_dir):
|
||||
return plugins
|
||||
for root, dirs, files in os.walk(self.vst_dir):
|
||||
for file in files:
|
||||
if file.endswith(".vst3") or file.endswith(".so"):
|
||||
plugin_path = os.path.join(root, file)
|
||||
plugin_name = os.path.splitext(file)[0]
|
||||
plugins[plugin_name] = plugin_path
|
||||
for scan_dir in [self.vst_dir] + self.extra_vst_dirs:
|
||||
if not scan_dir or not os.path.isdir(scan_dir):
|
||||
continue
|
||||
for root, dirs, files in os.walk(scan_dir):
|
||||
# Windows: VST3 là FOLDER tên X.vst3 (chứa X.vst3.dll bên trong)
|
||||
for d in list(dirs):
|
||||
if d.lower().endswith(".vst3"):
|
||||
plugins[os.path.splitext(d)[0]] = os.path.join(root, d)
|
||||
for file in files:
|
||||
low = file.lower()
|
||||
if low.endswith(".vst3") or low.endswith(".so") or low.endswith(".dll"):
|
||||
plugin_path = os.path.join(root, file)
|
||||
plugin_name = os.path.splitext(file)[0]
|
||||
if plugin_name not in plugins:
|
||||
plugins[plugin_name] = plugin_path
|
||||
return plugins
|
||||
|
||||
def _scan_soundfonts(self) -> list:
|
||||
|
||||
Reference in New Issue
Block a user