FIX: Carla sử dụng bridge có midikeyboard
This commit is contained in:
+84
-12
@@ -472,11 +472,15 @@ class PreviewRequest(BaseModel):
|
||||
|
||||
@router.post("/open-in-carla")
|
||||
async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(get_current_user)):
|
||||
"""Mở Carla (native GUI host) trên máy hiện tại — Windows desktop mode.
|
||||
"""Mở Carla với VSTi đã chọn — TỰ ĐỘNG load plugin (native GUI + keyboard).
|
||||
|
||||
Carla là app ngoài do user tự cài (GPL-2.0+ → không bundle/nhúng). App chỉ
|
||||
spawn tiến trình; user chỉnh preset trong GUI rồi Save → .vstpreset → upload
|
||||
vào thư viện preset → gán vào track → render engine tải preset tương ứng."""
|
||||
Cơ chế: sinh file project .carxs (định dạng XML chính thức của Carla —
|
||||
`carla.exe [FILE]` nhận project file) chứa node <Plugin><Info><Type>VST3
|
||||
</Type><Binary>...</Binary></Info> → Carla mở lên là plugin đã load sẵn,
|
||||
kèm on-screen MIDI keyboard (PixmapKeyboard) để preview realtime.
|
||||
|
||||
Carla là app ngoài do user tự giải nén (GPL-2.0+ → không bundle/nhúng);
|
||||
app chỉ spawn tiến trình + trao đổi file preset."""
|
||||
enforce_password_changed(current_user)
|
||||
from app.core.runtime import find_carla
|
||||
carla = find_carla()
|
||||
@@ -497,15 +501,12 @@ async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(ge
|
||||
plugin_path = plugins[req.plugin_name]
|
||||
except Exception:
|
||||
plugin_path = ""
|
||||
cmd = [carla]
|
||||
carxs_path = ""
|
||||
if plugin_path:
|
||||
# carla-single: mở thẳng 1 plugin thành app standalone có native GUI
|
||||
single = os.path.join(
|
||||
os.path.dirname(carla),
|
||||
"carla-single" + (".exe" if os.name == "nt" else ""),
|
||||
)
|
||||
if os.path.isfile(single):
|
||||
cmd = [single, plugin_path]
|
||||
carxs_path = _write_carla_project(req.plugin_name or os.path.basename(plugin_path), plugin_path)
|
||||
cmd = [carla]
|
||||
if carxs_path:
|
||||
cmd.append(carxs_path)
|
||||
try:
|
||||
cwd = os.path.dirname(carla) or None
|
||||
subprocess.Popen(cmd, cwd=cwd, close_fds=os.name != "nt")
|
||||
@@ -514,12 +515,83 @@ async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(ge
|
||||
"started": True,
|
||||
"carla_path": carla,
|
||||
"plugin_path": plugin_path,
|
||||
"project_file": carxs_path,
|
||||
"cmd": cmd,
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Không mở được Carla: {e}")
|
||||
|
||||
|
||||
def _write_carla_project(plugin_name: str, plugin_path: str) -> str:
|
||||
"""Sinh file project .carxs cho Carla load sẵn VSTi (chỉ VST3 — VST2 cần
|
||||
uniqueID không đoán được → mở Carla trống để user tự Add Plugin).
|
||||
|
||||
Định dạng theo source Carla (CarlaEngine::saveProjectInternal +
|
||||
CarlaStateSave::dumpToMemoryStream): root <CARLA-PROJECT VERSION='2.5'>,
|
||||
<Plugin><Info><Type>VST3</Type><Binary>path</Binary><Label>..</Label>
|
||||
</Info><Data><Active>Yes</Active><ControlChannel>1</ControlChannel>
|
||||
<Options>0x0</Options></Data></Plugin>."""
|
||||
if not plugin_path or not os.path.exists(plugin_path):
|
||||
return ""
|
||||
low = plugin_path.lower()
|
||||
is_vst3 = low.endswith(".vst3") or low.endswith(".vst3/") or "\\" in plugin_path and plugin_path.rstrip("\\/").lower().endswith(".vst3")
|
||||
if not is_vst3:
|
||||
# VST2 (.dll/.so ngoài .vst3): không auto-load được tin cậy → Carla trống
|
||||
return ""
|
||||
from xml.sax.saxutils import escape
|
||||
name = escape(plugin_name or os.path.splitext(os.path.basename(plugin_path))[0])
|
||||
binary = escape(plugin_path)
|
||||
xml = (
|
||||
"<?xml version='1.0' encoding='UTF-8'?>\n"
|
||||
"<!DOCTYPE CARLA-PROJECT>\n"
|
||||
f"<CARLA-PROJECT VERSION='2.5'>\n"
|
||||
" <EngineSettings>\n"
|
||||
" <ForceStereo>false</ForceStereo>\n"
|
||||
" <PreferPluginBridges>false</PreferPluginBridges>\n"
|
||||
" <PreferUiBridges>false</PreferUiBridges>\n"
|
||||
" <UIsAlwaysOnTop>false</UIsAlwaysOnTop>\n"
|
||||
" <MaxParameters>100</MaxParameters>\n"
|
||||
" <UIBridgesTimeout>10000</UIBridgesTimeout>\n"
|
||||
" </EngineSettings>\n"
|
||||
" <Plugin>\n"
|
||||
" <Info>\n"
|
||||
f" <Type>VST3</Type>\n"
|
||||
f" <Name>{name}</Name>\n"
|
||||
f" <Binary>{binary}</Binary>\n"
|
||||
f" <Label>{name}</Label>\n"
|
||||
" </Info>\n"
|
||||
" <Data>\n"
|
||||
" <Active>Yes</Active>\n"
|
||||
" <ControlChannel>1</ControlChannel>\n"
|
||||
" <Options>0x0</Options>\n"
|
||||
" </Data>\n"
|
||||
" </Plugin>\n"
|
||||
"</CARLA-PROJECT>\n"
|
||||
)
|
||||
try:
|
||||
proj_dir = os.path.join(settings.STORAGE_DIR, "carla_projects")
|
||||
os.makedirs(proj_dir, exist_ok=True)
|
||||
# Dọn project cũ (quá 1 ngày) — tránh rác
|
||||
try:
|
||||
now = _time.time()
|
||||
for f in os.listdir(proj_dir):
|
||||
fp = os.path.join(proj_dir, f)
|
||||
try:
|
||||
if os.path.isfile(fp) and now - os.path.getmtime(fp) > 86400:
|
||||
os.remove(fp)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
safe = "".join(c for c in plugin_name if c.isalnum() or c in " _-")[:40].strip() or "plugin"
|
||||
carxs = os.path.join(proj_dir, f"{safe}_{uuid.uuid4().hex[:8]}.carxs")
|
||||
with open(carxs, "w", encoding="utf-8") as fh:
|
||||
fh.write(xml)
|
||||
return carxs
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
@router.post("/preview")
|
||||
async def preview_instrument(req: PreviewRequest):
|
||||
"""Quick-render preview VSTi (âm thật, cùng code path với export)."""
|
||||
|
||||
Reference in New Issue
Block a user