feat: VST FX rack per-track + masterbus render via native_bridge
This commit is contained in:
@@ -1002,6 +1002,82 @@ def _render_midi_notes_pedalboard(instrument_id: str, notes: list, bpm: float,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/fx")
|
||||
async def list_fx_plugins(current_user: dict = Depends(get_current_user),
|
||||
refresh: bool = Query(False)):
|
||||
"""List VST effect plugins (is_fx) — native_bridge --scan trên các dir.
|
||||
|
||||
Chỉ plugin VST3 effect (Ozone, Scaler Audio, ...); instrument/VST2 bị
|
||||
bridge loại. refresh=true → quét lại (Plugins Manager → Scan)."""
|
||||
from app.core.vst_engine import get_plugin_manager
|
||||
pm = get_plugin_manager()
|
||||
return {"success": True, "plugins": pm.list_fx(refresh=refresh)}
|
||||
|
||||
|
||||
class FxRenderRequest(BaseModel):
|
||||
"""Render WAV qua chuỗi FX (VST3 effect + builtin gain/normalize).
|
||||
|
||||
Dùng cho masterbus (render xong project → Ozone trên bus) và FX Rack
|
||||
per-track (preview offline qua bridge; realtime vẫn WebAudio)."""
|
||||
input_file_id: Optional[str] = None # file trong PROCESSED_DIR
|
||||
input_path: Optional[str] = None # path tuyệt đối (test)
|
||||
fx_chain: list = []
|
||||
sample_rate: int = 44100
|
||||
|
||||
|
||||
@router.post("/fx-render")
|
||||
async def fx_render(req: FxRenderRequest, current_user: dict = Depends(get_current_user)):
|
||||
"""Áp fx_chain lên WAV đầu vào → WAV mới trong PROCESSED_DIR."""
|
||||
enforce_password_changed(current_user)
|
||||
src = (req.input_path or "").strip()
|
||||
if not src:
|
||||
fid = (req.input_file_id or "").strip()
|
||||
if not fid:
|
||||
raise HTTPException(status_code=400,
|
||||
detail="Cần input_file_id hoặc input_path")
|
||||
src = os.path.join(settings.PROCESSED_DIR, os.path.basename(fid))
|
||||
if not os.path.isfile(src):
|
||||
raise HTTPException(status_code=404,
|
||||
detail=f"File đầu vào không tồn tại: {src}")
|
||||
user_id = current_user["user_id"]
|
||||
out_name = f"user_{user_id}_fx_{uuid.uuid4().hex[:10]}.wav"
|
||||
out_path = os.path.join(settings.PROCESSED_DIR, out_name)
|
||||
try:
|
||||
_render_fx_locked(src, req.fx_chain, req.sample_rate, out_path)
|
||||
duration = sf.info(out_path).frames / max(1, req.sample_rate)
|
||||
return {
|
||||
"success": True,
|
||||
"file_id": out_name,
|
||||
"url": f"/static/audio/processed/{out_name}",
|
||||
"path": out_path,
|
||||
"duration_sec": round(duration, 3),
|
||||
"render_mode": settings.RENDER_ENGINE,
|
||||
}
|
||||
except HTTPException:
|
||||
try:
|
||||
os.remove(out_path)
|
||||
except Exception:
|
||||
pass
|
||||
raise
|
||||
except Exception as e:
|
||||
try:
|
||||
os.remove(out_path)
|
||||
except Exception:
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail=f"FX render thất bại: {e}")
|
||||
|
||||
|
||||
@_render_locked
|
||||
def _render_fx_locked(input_path: str, fx_chain: list, sample_rate: int, out_path: str):
|
||||
"""FX render qua native_bridge --render-fx (không thread-safe → lock)."""
|
||||
if settings.RENDER_ENGINE != "bridge":
|
||||
raise HTTPException(status_code=501,
|
||||
detail="Chỉ hỗ trợ native_bridge render — set SF_RENDER_ENGINE=bridge")
|
||||
from app.core.native_render import render_fx_chain
|
||||
render_fx_chain(input_wav=input_path, fx_chain=fx_chain,
|
||||
sample_rate=sample_rate, out_path=out_path)
|
||||
|
||||
|
||||
class SoundfontRenderRequest(BaseModel):
|
||||
"""Render MIDI notes → WAV bằng soundfont THẬT qua native FluidSynth
|
||||
(pyfluidsynth) — luồng âm instrument cho môi trường STANDALONE (xử lí
|
||||
|
||||
Reference in New Issue
Block a user