feat: VST FX rack per-track + masterbus render via native_bridge
This commit is contained in:
@@ -5,6 +5,8 @@ import numpy as np
|
||||
import functools
|
||||
import threading
|
||||
from ctypes import c_char_p
|
||||
import subprocess
|
||||
|
||||
|
||||
# Native render (bridge/pyfluidsynth) không thread-safe: tuần tự hóa
|
||||
RENDER_LOCK = threading.RLock()
|
||||
@@ -280,6 +282,7 @@ class PluginManager:
|
||||
# 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()
|
||||
self._fx_scan_cache = None # cache for list_fx()
|
||||
|
||||
def _scan_plugins(self) -> dict:
|
||||
plugins = {}
|
||||
@@ -453,6 +456,52 @@ class PluginManager:
|
||||
"soundfonts": self._scan_soundfonts()
|
||||
}
|
||||
|
||||
def list_fx(self, refresh=False) -> list:
|
||||
"""VST3 effect plugins (is_fx) từ native_bridge --scan (SEH-safe).
|
||||
|
||||
Bridge tự phân loại FX/instrument theo VST3 subCategory; VST2 (.dll/.so)
|
||||
không introspect được nên bridge báo instrument — bỏ qua ở đây. Cache
|
||||
process-wide; refresh=True quét lại (Plugins Manager → Scan).
|
||||
"""
|
||||
if not refresh and self._fx_scan_cache is not None:
|
||||
return self._fx_scan_cache
|
||||
from app.core.native_render import find_bridge_exe
|
||||
exe = find_bridge_exe()
|
||||
results = []
|
||||
if exe:
|
||||
dirs = [d for d in [self.vst_dir] + self.extra_vst_dirs
|
||||
if d and os.path.isdir(d)]
|
||||
for d in dirs:
|
||||
try:
|
||||
r = subprocess.run([exe, "--scan", d], capture_output=True,
|
||||
text=True, timeout=300)
|
||||
except Exception:
|
||||
continue
|
||||
if r.returncode != 0:
|
||||
continue
|
||||
try:
|
||||
data = json.loads(r.stdout[r.stdout.find("{"):])
|
||||
except Exception:
|
||||
continue
|
||||
for p in data.get("plugins", []):
|
||||
if not p.get("is_fx"):
|
||||
continue
|
||||
path = p.get("path", "")
|
||||
if not path:
|
||||
continue
|
||||
name = p.get("name") or os.path.splitext(os.path.basename(path))[0]
|
||||
results.append({"id": name, "name": name, "path": path,
|
||||
"type": p.get("type", "VST3")})
|
||||
seen, uniq = set(), []
|
||||
for fx in results:
|
||||
if fx["path"] in seen:
|
||||
continue
|
||||
seen.add(fx["path"])
|
||||
uniq.append(fx)
|
||||
uniq.sort(key=lambda x: x["name"].lower())
|
||||
self._fx_scan_cache = uniq
|
||||
return uniq
|
||||
|
||||
@staticmethod
|
||||
def midi_events_to_messages(midi_events: list, bpm: float, sr: int, bank: int = None, program: int = None) -> list:
|
||||
beat_duration_sec = 60.0 / max(30.0, bpm)
|
||||
|
||||
Reference in New Issue
Block a user