fix: 3 bug standalone — soundfont preview/play, Carla bridge play item, realtime sync LAN/Tauri

Bug1: dispatcher CREATE_MIDI_ITEM track_id number vs state string -> String(); play+preview native soundfont verified.
Bug2: Carla play item — chờ OSC ready (poll carla-status, queue nốt khi cold start), chống spawn trùng (carla-status dò port bind, open-in-carla skip khi đã chạy).
Bug3: realtime sync — GET /temp/revision (updated_at+client_id), poll 3s, apply qua deserializeProjectFromSchema; hash-skip autosave + client_id chống ping-pong.
This commit is contained in:
2026-08-10 22:10:12 +07:00
parent d31e226f00
commit aa1cd8d5de
7 changed files with 238 additions and 32 deletions
+42 -4
View File
@@ -563,6 +563,25 @@ def _carla_osc_port() -> int:
return 22752
def _carla_osc_ready() -> bool:
"""Carla OSC engine đang lắng nghe cổng UDP chưa — thử bind socket vào đúng
cổng. Bind THÀNH CÔNG → cổng trống (Carla chưa mở / OSC chưa sẵn sàng);
EADDRINUSE → Carla đang giữ cổng (kể cả mở thủ công ngoài app, hoặc app
spawn đang boot tới giai đoạn OSC). KHÔNG set SO_REUSEADDR (Linux/Win cho
phép 2 UDP socket cùng port → mất tác dụng phát hiện)."""
try:
import socket
port = _carla_osc_port()
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("127.0.0.1", port))
s.close()
return False
except OSError:
return True
except Exception:
return False
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).
@@ -626,6 +645,20 @@ async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(ge
carxs_path = ""
if plugin_path:
carxs_path = _write_carla_project(req.plugin_name or os.path.basename(plugin_path), plugin_path)
# ⚠️ FIX (Bug 2): Carla ĐÃ chạy (mở thủ công / app spawn từ trước, hoặc
# đang boot) → KHÔNG spawn bản 2 (trùng OSC port 22752 + audio → bridge
# hỏng; Carla là single-instance). Trả already_running — frontend poll
# carla-status chờ OSC ready rồi mới gửi note.
if _carla_osc_ready() or len(_CARLA_PROCESSES) > 0:
return {
"success": True,
"started": False,
"already_running": True,
"carla_path": carla,
"plugin_path": plugin_path,
"project_file": carxs_path,
"cmd": [],
}
cmd = [carla]
if carxs_path:
cmd.append(carxs_path)
@@ -683,15 +716,18 @@ def _send_carla_all_notes_off() -> bool:
@router.get("/carla-status")
async def carla_status():
"""Kiểm tra Carla bridge còn sống không (do app spawn) + cổng OSC đang dùng.
"""Kiểm tra Carla bridge còn sống không (OSC engine đang nghe cổng UDP —
phát hiện CẢ Carla mở thủ công ngoài app, không chỉ app-spawn; chính xác
hơn _CARLA_PROCESSES vì OSC bind khi Carla boot xong engine).
Frontend dùng để quyết định: route MIDI item EXCLUSIVE qua Carla (chỉ khi
Carla đang chạy) hay fallback FluidSynth (luôn có âm) — tránh câm toàn
phần khi Carla bị đóng."""
phần khi Carla bị đóng. running=OSC ready → gửi note là tới được Carla."""
_prune_carla_processes()
return {
"success": True,
"running": len(_CARLA_PROCESSES) > 0,
"running": _carla_osc_ready(),
"app_spawned": len(_CARLA_PROCESSES) > 0,
"osc_port": _carla_osc_port(),
}
@@ -1118,7 +1154,9 @@ def find_carla_local() -> str:
def _carla_bridge_running() -> bool:
try:
_prune_carla_processes()
return len(_CARLA_PROCESSES) > 0
# OSC ready (mở thủ công / app spawn đã boot) HOẶC tiến trình app-spawn
# còn sống (đang boot) — preview chấp nhận gửi sớm một chút.
return _carla_osc_ready() or len(_CARLA_PROCESSES) > 0
except Exception:
return False