feat: tách luồng âm instrument theo môi trường — docker dùng FluidSynthWASM, standalone dùng native pyfluidsynth (+Carla VSTi)
- runtime: detect()/capabilities() trả environment (docker|standalone) - vst_engine: render_soundfont_midi_to_audio (pyfluidsynth native, event-stream, release tail) - plugins: POST /soundfont-render — render MIDI notes -> WAV (auth, fallback default SF) - frontend: SonicRuntime.environment + dataset.environment; app.jsx route preview/transport: standalone+SF -> soundfont-render WAV (playNativeSfNote/scheduleNativeSfItem), docker giữ WASM, VSTi standalone giữ Carla bridge
This commit is contained in:
+69
-2
@@ -6,8 +6,8 @@ from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Any
|
||||
from app.config import settings
|
||||
from app.core.vst_engine import PluginManager, HAS_PEDALBOARD, HAS_PYFLUIDSYNTH, apply_preset_to_plugin
|
||||
from app.core.render_engine import PythonRenderEngine
|
||||
from app.core.vst_engine import PluginManager, HAS_PEDALBOARD, HAS_PYFLUIDSYNTH, apply_preset_to_plugin, render_soundfont_midi_to_audio
|
||||
from app.core.render_engine import PythonRenderEngine, _find_sf2_path, _find_default_sf2
|
||||
from app.core.soundfont_inspector import SoundFontInspector
|
||||
from app.core.soundfont_converter import SoundFontConverter
|
||||
from app.core.soundfont_scanner import SoundFontAutoScanner
|
||||
@@ -992,6 +992,73 @@ def _render_midi_notes_pedalboard(instrument_id: str, notes: list, bpm: float,
|
||||
return out_path, buf.shape[1] / float(sample_rate)
|
||||
|
||||
|
||||
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í
|
||||
trực tiếp trên OS; docker dùng FluidSynthWASM ở client)."""
|
||||
soundfont_id: Optional[str] = None
|
||||
soundfont_path: Optional[str] = None
|
||||
bank: int = 0
|
||||
program: int = 0
|
||||
notes: list = []
|
||||
bpm: float = 120.0
|
||||
sample_rate: int = 44100
|
||||
|
||||
|
||||
@router.post("/soundfont-render")
|
||||
async def soundfont_render(req: SoundfontRenderRequest, current_user: dict = Depends(get_current_user)):
|
||||
"""Preview/play MIDI notes bằng âm soundfont thật — native FluidSynth trên OS.
|
||||
|
||||
Khác FluidSynthWASM (docker): âm render server-side bằng pyfluidsynth, trả
|
||||
WAV để client play. Dùng cho: preview/keyboard, play MIDI item khi chạy
|
||||
transport ở môi trường standalone."""
|
||||
enforce_password_changed(current_user)
|
||||
if not req.notes:
|
||||
raise HTTPException(status_code=400, detail="Chưa có nốt nhạc để render")
|
||||
if not HAS_PYFLUIDSYNTH:
|
||||
raise HTTPException(status_code=501, detail="pyfluidsynth không khả dụng — không render được soundfont native")
|
||||
# Resolve path: soundfont_path → soundfont_id (upload/user dirs) → default
|
||||
sf_path = (req.soundfont_path or "").strip()
|
||||
if not sf_path and req.soundfont_id:
|
||||
sf_path = _find_sf2_path(req.soundfont_id)
|
||||
if not sf_path or not os.path.exists(sf_path):
|
||||
sf_path = _find_default_sf2()
|
||||
if not sf_path or not os.path.exists(sf_path):
|
||||
raise HTTPException(status_code=404, detail="SoundFont không tìm thấy — hãy thêm soundfont vào Plugin Manager")
|
||||
midi_events = [{
|
||||
"note": int(n.get("pitch", 60)),
|
||||
"start_beat": float(n.get("start_beat", 0)),
|
||||
"duration_beats": float(n.get("duration_beats", 1)),
|
||||
"velocity": int(float(n.get("velocity", 0.8)) * 127),
|
||||
} for n in req.notes]
|
||||
out_path = os.path.join(settings.PROCESSED_DIR, f"sf_{uuid.uuid4().hex[:10]}.wav")
|
||||
try:
|
||||
audio = render_soundfont_midi_to_audio(
|
||||
midi_events, sf_path,
|
||||
bank=req.bank, program=req.program,
|
||||
sr=req.sample_rate, bpm=req.bpm,
|
||||
)
|
||||
sf.write(out_path, audio.T, req.sample_rate)
|
||||
return {
|
||||
"success": True,
|
||||
"file_id": os.path.basename(out_path),
|
||||
"url": f"/static/audio/processed/{os.path.basename(out_path)}",
|
||||
"path": out_path,
|
||||
"duration_sec": round(audio.shape[1] / float(req.sample_rate), 3),
|
||||
"render_mode": "native-fluidsynth",
|
||||
"soundfont": os.path.basename(sf_path),
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
try:
|
||||
if os.path.exists(out_path):
|
||||
os.remove(out_path)
|
||||
except Exception:
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail=f"Render soundfont thất bại: {e}")
|
||||
|
||||
|
||||
class CarlaPlayNotesRequest(BaseModel):
|
||||
"""Phát dãy MIDI notes qua Carla bridge (OSC, realtime) — preview khi
|
||||
pedalboard không render được plugin (VD VST2). Cần Carla đang chạy với
|
||||
|
||||
Reference in New Issue
Block a user