feat: VST FX rack per-track + masterbus render via native_bridge

This commit is contained in:
2026-08-17 21:31:57 +07:00
parent 8fbacd1d2b
commit b261eb0d7e
18 changed files with 1805 additions and 14 deletions
+49
View File
@@ -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)