FIX: 6 lỗi âm thanh/Carla/temp-save/Ctrl-S/FX Chain Carla bridge
- Soundfont preview: hủy note đang chờ load soundfont (stopNote/stopAll/panic) — hết âm loop không dừng với MIDI Keyboard; preview dùng channel riêng (applyAITrackInstrument) — hết sai instrument - Carla bridge: endpoint /carla-stop (all-notes-off OSC + terminate process) + stopBridge() khi track chuyển VSTi -> soundfont — hết âm play qua Carla cũ - MIDI items play qua Carla khi VSTi loaded: scheduleCarlaNote route vào startTrackPlayback + startLocalTrackPlayback + ghost notes - Tự động lưu temp khi tắt app: beforeunload/pagehide sendBeacon + autosave 30s + ghi storage/temp/autosave.json + khôi phục khi load lại - Ctrl-S: desktop -> save-to-disk (Documents/SonicForgeDAW/Projects); docker -> Cloud/local - FX Chain (Mastering + FX Rack): module Carla Bridge (VST FX) — load/openInCarla + stop/unload, pass-through trong graph
This commit is contained in:
+90
-2
@@ -1,7 +1,7 @@
|
||||
import os, sys, uuid, json, tempfile, subprocess, time as _time
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, BackgroundTasks
|
||||
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, BackgroundTasks, Header
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Any
|
||||
@@ -631,7 +631,8 @@ async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(ge
|
||||
cmd.append(carxs_path)
|
||||
try:
|
||||
cwd = os.path.dirname(carla) or None
|
||||
subprocess.Popen(cmd, cwd=cwd, close_fds=os.name != "nt")
|
||||
proc = subprocess.Popen(cmd, cwd=cwd, close_fds=os.name != "nt")
|
||||
_register_carla_process(proc)
|
||||
return {
|
||||
"success": True,
|
||||
"started": True,
|
||||
@@ -644,6 +645,93 @@ async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(ge
|
||||
raise HTTPException(status_code=500, detail=f"Không mở được Carla: {e}")
|
||||
|
||||
|
||||
# ── Carla bridge lifecycle ─────────────────────────────────────────────────
|
||||
# App spawn Carla (open_in_carla) và PHẢI có khả năng dừng nó khi track chuyển
|
||||
# sang instrument KHÔNG phải VST (soundfont/GM) — nếu không, Carla vẫn chạy và
|
||||
# MIDI vẫn vào VSTi cũ → âm sai instrument + âm kẹt không dừng được.
|
||||
_CARLA_PROCESSES = [] # list[subprocess.Popen]
|
||||
|
||||
|
||||
def _register_carla_process(proc):
|
||||
"""Lưu handle tiến trình Carla do app spawn (để carla-stop terminate được)."""
|
||||
global _CARLA_PROCESSES
|
||||
_prune_carla_processes()
|
||||
_CARLA_PROCESSES.append(proc)
|
||||
|
||||
|
||||
def _prune_carla_processes():
|
||||
"""Bỏ các handle đã thoát (poll() trả code) — tránh list rác."""
|
||||
global _CARLA_PROCESSES
|
||||
_CARLA_PROCESSES = [p for p in _CARLA_PROCESSES if p is not None and p.poll() is None]
|
||||
|
||||
|
||||
def _send_carla_all_notes_off() -> bool:
|
||||
"""Gửi note_off TẤT CẢ pitch (0-127) trên mọi channel (0-15) tới Carla.
|
||||
|
||||
Dừng mọi âm đang ngân trong Carla (kể cả sustain) — dùng ngay trước khi
|
||||
terminate để không còn tiếng kẹt khi bridge bị unload."""
|
||||
ok = False
|
||||
try:
|
||||
for ch in range(16):
|
||||
for note in range(128):
|
||||
if _send_carla_osc("note_off", note, 0, ch):
|
||||
ok = True
|
||||
except Exception:
|
||||
pass
|
||||
return ok
|
||||
|
||||
|
||||
@router.post("/carla-stop")
|
||||
async def carla_stop(authorization: Optional[str] = Header(None)):
|
||||
"""Unload Carla bridge: tắt mọi note đang ngân + terminate tiến trình Carla
|
||||
do app spawn. Gọi khi track chuyển từ VSTi sang instrument khác (soundfont)
|
||||
để âm KHÔNG còn play qua Carla bridge."""
|
||||
# Auth là optional (desktop app có thể chưa login) — chỉ cần decode nếu có
|
||||
try:
|
||||
if authorization and authorization.startswith("Bearer "):
|
||||
from app.core.auth import decode_token
|
||||
decode_token(authorization.split(" ")[1])
|
||||
except Exception:
|
||||
pass
|
||||
# 1. Tắt hết âm đang ngân trong Carla (trước khi giết tiến trình)
|
||||
try:
|
||||
_send_carla_all_notes_off()
|
||||
except Exception:
|
||||
pass
|
||||
# 2. Terminate tiến trình Carla đã spawn
|
||||
killed = 0
|
||||
_prune_carla_processes()
|
||||
for proc in list(_CARLA_PROCESSES):
|
||||
try:
|
||||
proc.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
# Chờ tiến trình thoát (tối đa ~2s) rồi kill mạnh nếu còn sống
|
||||
try:
|
||||
import time as _t
|
||||
deadline = _t.time() + 2.0
|
||||
for proc in list(_CARLA_PROCESSES):
|
||||
while proc.poll() is None and _t.time() < deadline:
|
||||
_t.sleep(0.05)
|
||||
if proc.poll() is None:
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
if proc.poll() is not None:
|
||||
killed += 1
|
||||
except Exception:
|
||||
pass
|
||||
_CARLA_PROCESSES.clear()
|
||||
return {
|
||||
"success": True,
|
||||
"stopped": True,
|
||||
"killed": killed,
|
||||
"all_notes_off": True,
|
||||
}
|
||||
|
||||
|
||||
|
||||
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).
|
||||
|
||||
Reference in New Issue
Block a user