Remove Carla native VSTi bridge entirely
Drop Carla integration across frontend and backend: - plugins.py: remove /carla-midi, /carla-status, /carla-stop, /open-in-carla, /carla-play-notes endpoints and Carla process management - runtime.py: remove find_carla / _carla_* helpers, detect and capabilities - app.jsx: remove Carla bridge UI, SonicCarlaMidi routing, shouldRouteCarla guards, VST row Carla button and preset fallback - system.py: remove /carla-path - Keep audio path unchanged (tracks -> masterBus -> output -> analyser), directOut only for offline render Build frontend, tests pass (124 passed).
This commit is contained in:
+1
-226
@@ -1,7 +1,6 @@
|
||||
# SonicForge Runtime Profile — phát hiện môi trường chạy (desktop Windows /
|
||||
# docker headless) và khả năng của máy, để app TỰ CHỌN cách xử lý:
|
||||
# - Windows desktop (server + client cùng 1 máy): Carla local để preview +
|
||||
# chỉnh preset → native_bridge render (Hướng A)
|
||||
# - Windows desktop (server + client cùng 1 máy): native_bridge render VSTi
|
||||
# - Docker / Linux headless (server + browser UI): soundfont + VSTi mở được
|
||||
# từ storage mount; preset upload qua browser; không có GUI local.
|
||||
#
|
||||
@@ -10,8 +9,6 @@
|
||||
# nhẹ và hoạt động trong mọi tiến trình (web/worker/celery).
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import socket
|
||||
import functools
|
||||
|
||||
@@ -19,10 +16,6 @@ from app.config import settings
|
||||
|
||||
SF_RUNTIME_ENV = "SF_RUNTIME" # auto | desktop | headless
|
||||
SF_DOCKER_ENV = "SF_DOCKER" # 1 = chạy trong container Docker
|
||||
CARLA_CONFIG_FILE = "carla_path.json" # storage/carla_path.json — user định vị
|
||||
# Bản Carla portable (zip) có thể giải nén ở BẤT KỲ ĐÂU — PATH/Program Files
|
||||
# không đủ. User tự chọn thư mục chứa carla.exe qua Plugin Manager → lưu file
|
||||
# này (ưu tiên cao nhất khi detect), kèm tìm kiếm nông Downloads/Desktop.
|
||||
|
||||
|
||||
def _is_windows() -> bool:
|
||||
@@ -67,219 +60,6 @@ def _tauri_bridge_ready() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _carla_config_path() -> str:
|
||||
return os.path.join(settings.STORAGE_DIR, CARLA_CONFIG_FILE)
|
||||
|
||||
|
||||
def get_configured_carla() -> str:
|
||||
"""Đường dẫn carla.exe do USER tự định vị (lưu trong storage/carla_path.json).
|
||||
|
||||
File lưu đường dẫn tới carla.exe (đã resolve) hoặc thư mục chứa — nếu là
|
||||
thư mục, tìm carla.exe bên trong (độ sâu ≤ 2). Đây là cách bắt buộc có cho
|
||||
bản Carla portable (zip) giải nén ở vị trí bất kỳ, không cài đặt/PATH."""
|
||||
try:
|
||||
with open(_carla_config_path(), "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except Exception:
|
||||
return ""
|
||||
p = (data.get("carla_path") or "").strip()
|
||||
if not p:
|
||||
return ""
|
||||
if os.path.isfile(p) and os.path.basename(p).lower() in ("carla.exe", "carla-single.exe", "carla"):
|
||||
return p
|
||||
if os.path.isdir(p):
|
||||
try:
|
||||
for root, dirs, files in os.walk(p):
|
||||
depth = root[len(p):].count(os.sep)
|
||||
if depth >= 2:
|
||||
dirs[:] = []
|
||||
continue
|
||||
for f in files:
|
||||
if f.lower() == "carla.exe":
|
||||
return os.path.join(root, f)
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def save_carla_path(path: str) -> str:
|
||||
"""Lưu vị trí Carla do user chọn (Plugin Manager → Định vị Carla...).
|
||||
|
||||
Chấp nhận: đường dẫn tới carla.exe, hoặc thư mục chứa carla.exe (portable
|
||||
zip). Resolve về đường dẫn exe hợp lệ → ghi config → xóa cache detect.
|
||||
Trả đường dẫn exe, hoặc '' nếu không hợp lệ."""
|
||||
path = (path or "").strip().strip('"').strip()
|
||||
exe = ""
|
||||
if os.path.isfile(path) and os.path.basename(path).lower() in ("carla.exe", "carla-single.exe", "carla"):
|
||||
exe = path
|
||||
elif os.path.isdir(path):
|
||||
try:
|
||||
for root, dirs, files in os.walk(path):
|
||||
depth = root[len(path):].count(os.sep)
|
||||
if depth >= 2:
|
||||
dirs[:] = []
|
||||
continue
|
||||
for f in files:
|
||||
if f.lower() == "carla.exe":
|
||||
exe = os.path.join(root, f)
|
||||
break
|
||||
if exe:
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if not exe:
|
||||
return ""
|
||||
try:
|
||||
os.makedirs(settings.STORAGE_DIR, exist_ok=True)
|
||||
with open(_carla_config_path(), "w", encoding="utf-8") as f:
|
||||
json.dump({"carla_path": exe, "carla_exe": os.path.basename(exe)}, f, ensure_ascii=False, indent=2)
|
||||
except Exception:
|
||||
return ""
|
||||
# Xóa cache detect/find_carla để capabilities phản ánh ngay
|
||||
invalidate_carla_cache()
|
||||
return exe
|
||||
|
||||
|
||||
def _carla_from_registry() -> str:
|
||||
"""Windows: Carla cài qua installer có thể ghi registry (best-effort)."""
|
||||
if not _is_windows():
|
||||
return ""
|
||||
try:
|
||||
import winreg
|
||||
for hive, key in (
|
||||
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Carla"),
|
||||
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Carla"),
|
||||
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Carla"),
|
||||
):
|
||||
try:
|
||||
with winreg.OpenKey(hive, key) as k:
|
||||
val, _ = winreg.QueryValueEx(k, "InstallPath")
|
||||
p = os.path.join(str(val), "carla.exe")
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def _find_carla_exe_bounded(base: str, max_depth: int = 3) -> str:
|
||||
"""Quét nông tìm carla.exe (bản portable giải nén thường nằm Downloads/
|
||||
Desktop/Documents). Giới hạn độ sâu + bỏ qua thư mục lớn — KHÔNG quét
|
||||
toàn ổ đĩa."""
|
||||
if not base or not os.path.isdir(base):
|
||||
return ""
|
||||
skip = ("node_modules", ".cache", "AppData", ".git", "venv", ".venv",
|
||||
"__pycache__", "$RECYCLE.BIN", "Windows", "Program Files",
|
||||
"Program Files (x86)")
|
||||
try:
|
||||
for root, dirs, files in os.walk(base):
|
||||
depth = root[len(base):].count(os.sep) if root != base else 0
|
||||
if depth >= max_depth:
|
||||
dirs[:] = []
|
||||
continue
|
||||
for f in files:
|
||||
if f.lower() == "carla.exe":
|
||||
return os.path.join(root, f)
|
||||
dirs[:] = [d for d in dirs if d not in skip and not d.startswith(".")]
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def _carla_from_shallow_search() -> str:
|
||||
"""Windows: tìm carla.exe trong Downloads/Desktop/Documents/Home."""
|
||||
if not _is_windows():
|
||||
return ""
|
||||
home = os.environ.get("USERPROFILE") or os.path.expanduser("~")
|
||||
bases = []
|
||||
for sub in ("Downloads", "Desktop", "Documents"):
|
||||
bases.append(os.path.join(home, sub))
|
||||
bases.append(home)
|
||||
for base in bases:
|
||||
hit = _find_carla_exe_bounded(base)
|
||||
if hit:
|
||||
return hit
|
||||
return ""
|
||||
|
||||
|
||||
def _carla_candidates() -> list:
|
||||
"""Các vị trí Carla — theo thứ tự ưu tiên:
|
||||
1) user định vị (config file — bản portable bắt buộc dùng cách này)
|
||||
2) PATH (shutil.which)
|
||||
3) registry (Windows, nếu cài qua installer)
|
||||
4) thư mục cài đặt chuẩn (Program Files...)
|
||||
5) quét nông Downloads/Desktop/Documents (Windows) / AppImage (Linux)"""
|
||||
cands = []
|
||||
configured = get_configured_carla()
|
||||
if configured:
|
||||
cands.append(configured)
|
||||
try:
|
||||
which = shutil.which("carla")
|
||||
if which:
|
||||
cands.append(which)
|
||||
except Exception:
|
||||
pass
|
||||
reg = _carla_from_registry()
|
||||
if reg:
|
||||
cands.append(reg)
|
||||
try:
|
||||
if _is_windows():
|
||||
pf = os.environ.get("ProgramFiles", r"C:\Program Files")
|
||||
lpf = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")
|
||||
la = os.environ.get("LOCALAPPDATA", "")
|
||||
for base in (pf, lpf, la):
|
||||
for rel in ("Carla\\carla.exe", "Carla\\bin\\carla.exe", "Programs\\Carla\\carla.exe"):
|
||||
p = os.path.join(base, rel) if base else ""
|
||||
if p and os.path.isfile(p):
|
||||
cands.append(p)
|
||||
elif _is_linux():
|
||||
home = os.path.expanduser("~")
|
||||
for p in ("/usr/bin/carla", "/usr/local/bin/carla", "/opt/carla/carla"):
|
||||
if os.path.isfile(p):
|
||||
cands.append(p)
|
||||
try:
|
||||
for f in os.listdir(home):
|
||||
if f.lower().startswith("carla") and f.lower().endswith(".appimage"):
|
||||
cands.append(os.path.join(home, f))
|
||||
except Exception:
|
||||
pass
|
||||
elif _is_macos():
|
||||
for p in ("/Applications/Carla.app/Contents/MacOS/Carla", "/Applications/Carla.app/Contents/MacOS/carla"):
|
||||
if os.path.isfile(p):
|
||||
cands.append(p)
|
||||
except Exception:
|
||||
pass
|
||||
shallow = _carla_from_shallow_search()
|
||||
if shallow:
|
||||
cands.append(shallow)
|
||||
# Dedup giữ thứ tự
|
||||
seen, out = set(), []
|
||||
for c in cands:
|
||||
if c not in seen:
|
||||
seen.add(c)
|
||||
out.append(c)
|
||||
return out
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def find_carla() -> str:
|
||||
"""Đường dẫn Carla đầu tiên tìm thấy, hoặc '' nếu chưa có.
|
||||
|
||||
Lưu ý license: Carla GPL-2.0+ — app KHÔNG bundle/nhúng, chỉ spawn tiến
|
||||
trình ngoài (user tự cài/giải nén) → không dính copyleft."""
|
||||
for c in _carla_candidates():
|
||||
return c
|
||||
return ""
|
||||
|
||||
|
||||
def invalidate_carla_cache():
|
||||
"""Xóa cache detect/find_carla — gọi sau khi user định vị Carla mới."""
|
||||
find_carla.cache_clear()
|
||||
detect.cache_clear()
|
||||
|
||||
|
||||
def _detect_platform() -> str:
|
||||
if _is_windows():
|
||||
return "windows"
|
||||
@@ -355,7 +135,6 @@ def detect() -> dict:
|
||||
"""Detect 1 lần (cache toàn cục) — kết quả bất biến trong 1 tiến trình."""
|
||||
platform = _detect_platform()
|
||||
runtime = _detect_runtime()
|
||||
carla = find_carla() if runtime == "desktop" else ""
|
||||
docker = _in_docker()
|
||||
return {
|
||||
"platform": platform,
|
||||
@@ -364,8 +143,6 @@ def detect() -> dict:
|
||||
"environment": "docker" if docker else "standalone", # luồng âm instrument: docker→FluidSynthWASM, standalone→native OS
|
||||
"has_display": _has_display(),
|
||||
"tauri_bridge": _tauri_bridge_ready(),
|
||||
"carla_path": carla,
|
||||
"carla_local": bool(carla), # chỉ có ý nghĩa khi runtime=desktop
|
||||
"vst_render": _vst_render_available(),
|
||||
"default_vst_dirs": default_vst_dirs(),
|
||||
"default_soundfont_dirs": default_soundfont_dirs(),
|
||||
@@ -411,8 +188,6 @@ def capabilities() -> dict:
|
||||
"""Capabilities API — frontend gọi 1 lần lúc boot để bật/tắt tính năng."""
|
||||
d = detect()
|
||||
features = {
|
||||
"carla_local": d["carla_local"],
|
||||
"carla_path": d["carla_path"],
|
||||
"preset_upload": True, # cả 2 mode đều upload preset được
|
||||
"vst_render": d["vst_render"],
|
||||
"tauri_bridge": d["tauri_bridge"],
|
||||
|
||||
Reference in New Issue
Block a user