connect solution into app: VSTi live native (vst3 attach/vst2 bridge) with WASM autosample fallback, VST GUI window (Bug 1), native-first track instrument

This commit is contained in:
2026-08-11 20:35:53 +07:00
parent 916ce1334b
commit 255e8586bc
13 changed files with 538 additions and 98 deletions
+53 -10
View File
@@ -30,8 +30,8 @@ _NATIVE_DIR = (
or os.path.join(settings.BASE_DIR, "native_host", "build", "Release")
)
_ERR = None
_LOCK = threading.Lock()
_ERR = None # khoi tao buffer sau khi dinh nghia _errbuf()
_LOCK = threading.RLock() # reentrant: _hidden_hwnd() goi trong ensure_*
def native_dir() -> str:
@@ -49,6 +49,39 @@ def _errbuf():
def _errval(buf) -> str:
return buf.value.decode(errors="replace") if buf and buf.value else ""
_ERR = _errbuf() # module-level err buffer (dung chung, 256 bytes)
def _hidden_hwnd():
"""Tao 1 hidden window dung lam parent cho VST3 editor headless (live).
SF_VST3_Attach yeu cau parent_hwnd != NULL; window 0-size khong hien thi."""
import ctypes as _ct
from ctypes import wintypes as _wt
if getattr(_hidden_hwnd, "_hwnd", 0):
return _hidden_hwnd._hwnd
with _LOCK:
if getattr(_hidden_hwnd, "_hwnd", 0):
return _hidden_hwnd._hwnd
_u = _ct.windll.user32
_k = _ct.windll.kernel32
_u.DefWindowProcW.argtypes = [_wt.HWND, _wt.UINT, _ct.c_void_p, _ct.c_void_p]
_u.DefWindowProcW.restype = _ct.c_long
_WNDPROC = _ct.WINFUNCTYPE(_ct.c_long, _wt.HWND, _wt.UINT, _ct.c_void_p, _ct.c_void_p) # WPARAM/LPARAM 64-bit
class _WC(_ct.Structure):
_fields_ = [("style", _ct.c_uint), ("lpfnWndProc", _WNDPROC), ("cbClsExtra", _ct.c_int),
("cbWndExtra", _ct.c_int), ("hInstance", _wt.HINSTANCE), ("hIcon", _wt.HICON),
("hCursor", _wt.HANDLE), ("hbrBackground", _wt.HBRUSH), ("lpszMenuName", _wt.LPCWSTR),
("lpszClassName", _wt.LPCWSTR)]
_wc = _WC()
_wc.lpfnWndProc = _WNDPROC(_u.DefWindowProcW)
_wc.lpszClassName = "SonicForgeNativeHidden"
_wc.hInstance = _k.GetModuleHandleW(None)
if _u.RegisterClassW(_ct.byref(_wc)) or _k.GetLastError() == 1410: # 1410 = class ton tai
_h = _u.CreateWindowExW(0, "SonicForgeNativeHidden", "sf", 0, 0, 0, 0, 0,
None, None, _wc.hInstance, None)
_hidden_hwnd._hwnd = _h or 0
return _hidden_hwnd._hwnd
def is_available() -> bool:
return os.path.isfile(_dll("sf_host_bridge.dll"))
@@ -133,11 +166,21 @@ class _Vst3Ctx:
self.handle = 0
def create(self, plugin_path):
# Attach can plugin_name = ten class VST3 (ClassInfo::name) + parent
# hwnd. Ten class thuong = ten file/folder .vst3; hidden window cho
# live headless. Neu ten class khong khop -> rc 0 -> caller fallback.
base = os.path.basename(plugin_path.rstrip("\\/"))
if base.lower().endswith(".vst3"):
plugin_name = base[:-5]
else:
plugin_name = os.path.splitext(base)[0]
w = ctypes.c_int32(0)
h = ctypes.c_int32(0)
self.handle = self.dll.SF_VST3_Load(plugin_path.encode("utf-8"), 0,
ctypes.byref(w), ctypes.byref(h),
_ERR, 256)
self.handle = self.dll.SF_VST3_Attach(plugin_path.encode("utf-8"),
plugin_name.encode("utf-8"),
_hidden_hwnd(),
ctypes.byref(w), ctypes.byref(h),
_ERR, 256)
return self.handle
def close(self):
@@ -509,10 +552,10 @@ class NativeAudioService:
raise RuntimeError(f"thiếu {_dll('vst3_host_bridge.dll')}")
dll = ctypes.WinDLL(_dll("vst3_host_bridge.dll"))
i32 = ctypes.c_int32
dll.SF_VST3_Load.argtypes = [ctypes.c_char_p, ctypes.c_void_p,
ctypes.POINTER(i32), ctypes.POINTER(i32),
ctypes.c_char_p, i32]
dll.SF_VST3_Load.restype = i32
dll.SF_VST3_Attach.argtypes = [ctypes.c_char_p, ctypes.c_char_p,
ctypes.c_void_p, ctypes.POINTER(i32),
ctypes.POINTER(i32), ctypes.c_char_p, i32]
dll.SF_VST3_Attach.restype = i32
dll.SF_VST3_SendNoteOn.argtypes = [i32, i32, i32, i32]
dll.SF_VST3_SendNoteOn.restype = i32
dll.SF_VST3_SendNoteOff.argtypes = [i32, i32, i32]
@@ -543,7 +586,7 @@ class NativeAudioService:
ctx = _Vst3Ctx(dll)
h = ctx.create(plugin_path)
if h <= 0:
raise RuntimeError(f"SF_VST3_Load fail: {_errval(_ERR)}")
raise RuntimeError(f"SF_VST3_Attach fail: {_errval(_ERR)}")
ctx.SetMasterGain(h, self._master_lin)
if live:
rc = ctx.AudioStart(h, self.sample_rate, self.block_size, _ERR, 256)