FIX: sửa lỗi không load instrument của soundfont trên window

This commit is contained in:
2026-08-10 10:04:54 +07:00
parent 85ac80bb4f
commit 1db892bb99
8 changed files with 160 additions and 4 deletions
+82 -2
View File
@@ -164,7 +164,7 @@ def get_scanner():
@router.get("/available")
async def list_plugins(current_user: dict = Depends(get_current_user)):
d = _effective_dirs()
pm = PluginManager(vst_dir=d["vst_dir"], sf_dir=d["soundfont_dir"], upload_sf_dir=UPLOAD_SF_DIR)
pm = PluginManager(vst_dir=d["vst_dir"], sf_dir=d["soundfont_dir"], upload_sf_dir=UPLOAD_SF_DIR, extra_vst_dirs=d["plugin_dirs"])
avail = pm.list_available()
# Gộp VST từ plugin_dirs user đã scan — list_available() CHỈ quét vst_dir
# env (mặc định /opt/daw_engine/vst3) → Synth dropdown không thấy VSTi mà
@@ -369,7 +369,9 @@ async def soundfont_catalog(current_user: dict = Depends(get_current_user)):
@router.get("/soundfont-instruments/{sf_id}")
async def list_soundfont_instruments(sf_id: str, current_user: dict = Depends(get_current_user)):
pm = PluginManager(upload_sf_dir=UPLOAD_SF_DIR)
d = _effective_dirs()
# extra_vst_dirs = plugin_dirs user (chứa cả VST lẫn SoundFont) → tìm sf2 ở đó
pm = PluginManager(upload_sf_dir=UPLOAD_SF_DIR, extra_vst_dirs=d["plugin_dirs"])
presets = pm.list_soundfont_instruments(sf_id)
return {"presets": presets, "count": len(presets)}
@@ -512,6 +514,84 @@ class PreviewRequest(BaseModel):
preset_data: Optional[str] = None # base64 bytes .vstpreset (project nhúng)
class CarlaMidiRequest(BaseModel):
"""Gửi MIDI note từ track ARM → Carla (OSC /Carla/0/note_on|note_off).
Carla standalone bật OSC UDP mặc định cổng 22752 (source: CarlaEngineOsc,
CarlaEngineData oscPortUDP=22752; override: env CARLA_OSC_UDP_PORT của
Carla, hoặc SF_CARLA_OSC_PORT / osc_port trong carla_path.json của app).
Plugin đầu tiên trong project .carxs do app sinh có pluginId = 0."""
event: str # "note_on" | "note_off"
note: int
velocity: Optional[int] = 100
channel: Optional[int] = 0
@router.post("/carla-midi")
async def carla_midi(req: CarlaMidiRequest):
"""MIDI keyboard (piano roll / keybed) → Carla để preview VSTi realtime."""
if req.event not in ("note_on", "note_off"):
raise HTTPException(status_code=400, detail="event phải là note_on hoặc note_off")
ok = _send_carla_osc(req.event, req.note, req.velocity if req.event == "note_on" else 0, req.channel)
if not ok:
raise HTTPException(
status_code=502,
detail="Không gửi được OSC tới Carla — Carla đã mở chưa? (cổng OSC UDP mặc định 22752; "
"nếu đổi cổng trong Carla, đặt SF_CARLA_OSC_PORT hoặc osc_port trong carla_path.json)",
)
return {"success": True, "event": req.event, "note": req.note, "channel": req.channel}
def _carla_osc_port() -> int:
"""Cổng OSC UDP của Carla: SF_CARLA_OSC_PORT env → osc_port trong
storage/carla_path.json → mặc định 22752 (CarlaEngineData)."""
try:
p = os.environ.get("SF_CARLA_OSC_PORT")
if p:
return int(p)
except Exception:
pass
try:
from app.core.runtime import _carla_config_path
with open(_carla_config_path(), "r", encoding="utf-8") as f:
data = json.load(f)
p = data.get("osc_port")
if p:
return int(p)
except Exception:
pass
return 22752
def _send_carla_osc(event: str, note: int, velocity: int, channel: int) -> bool:
"""Gửi OSC UDP tới `/Carla/0/{event}` (plugin đầu tiên = plugin auto-load).
OSC message: path + typetag + int args, mỗi phần pad '\0' tới bội số 4.
Đã xác minh từ source Carla: handleMsgNoteOn/NoteOff nhận `iii`/`ii` và
tên client mặc định của app standalone là "Carla" (carla_host.py
fClientName = CARLA_CLIENT_NAME or "Carla")."""
try:
import socket
import struct
port = _carla_osc_port()
path = f"/Carla/0/{event}".encode("utf-8")
typetag = b",iii" if event == "note_on" else b",ii"
vals = [int(channel), int(note), int(velocity)] if event == "note_on" else [int(channel), int(note)]
def _pad(b: bytes) -> bytes:
rem = len(b) % 4
return b + b"\x00" * (4 - rem) if rem else b
msg = _pad(path) + _pad(typetag) + b"".join(struct.pack(">i", v) for v in vals)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0.5)
s.sendto(msg, ("127.0.0.1", port))
s.close()
return True
except Exception:
return False
@router.post("/open-in-carla")
async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(get_current_user)):
"""Mở Carla với VSTi đã chọn — TỰ ĐỘNG load plugin (native GUI + keyboard).