feat: VSTi live playback 100% client-side (không Carla) — autosample SF2 backend (pedalboard) → FluidSynth WASM qua masterBus → mastering → main out; SonicVstiAutosample ensure+dedup+cooldown; runtime gating __enableCarlaLivePlayback; âm bắt qua ensureMidiCapture → clientSideExport
This commit is contained in:
@@ -1118,6 +1118,100 @@ async def soundfont_render(req: SoundfontRenderRequest, current_user: dict = Dep
|
||||
raise HTTPException(status_code=500, detail=f"Render soundfont thất bại: {e}")
|
||||
|
||||
|
||||
class AutosampleRequest(BaseModel):
|
||||
"""Auto-sample VSTi preset -> SF2 (client FluidSynth WASM live playback).
|
||||
|
||||
Server goi tools.autosample_vsti.autosample_sf2 (pedalboard render tung
|
||||
note voi DUNG plugin + preset nhu export) -> luu {uuid}.sf2 vao
|
||||
UPLOAD_SF_DIR + .meta -> client tai qua /soundfonts/download/{uuid}.
|
||||
SF2 only (SF3 can ffmpeg/libvorbis; client WASM khong decode SF3)."""
|
||||
instrument_id: str
|
||||
preset_id: Optional[str] = None
|
||||
preset_path: Optional[str] = None
|
||||
preset_data: Optional[str] = None # base64 bytes .vstpreset (project nhung)
|
||||
low: int = 36
|
||||
high: int = 96
|
||||
step: int = 2
|
||||
duration: float = 2.5
|
||||
release: float = 1.0
|
||||
velocity: int = 100
|
||||
sample_rate: int = 44100
|
||||
|
||||
@router.post("/autosample")
|
||||
async def autosample_vsti(req: AutosampleRequest, current_user: dict = Depends(get_current_user)):
|
||||
"""Auto-sample VSTi -> SF2 cho live playback client-side (FluidSynth WASM).
|
||||
|
||||
Tra sf_id (bare uuid) de client tai SF2 va phat qua SonicSF nhu soundfont
|
||||
thuong. Chi phi autosample mot lan -> cache o client (localStorage)."""
|
||||
enforce_password_changed(current_user)
|
||||
if not HAS_PEDALBOARD:
|
||||
raise HTTPException(status_code=501, detail="pedalboard khong kha dung tren may nay")
|
||||
|
||||
# tools/ nam ngoai package app - import voi fallback path
|
||||
try:
|
||||
from tools.autosample_vsti import autosample_sf2 as _autosample_sf2
|
||||
except ImportError:
|
||||
_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
if _root not in sys.path:
|
||||
sys.path.insert(0, _root)
|
||||
from tools.autosample_vsti import autosample_sf2 as _autosample_sf2
|
||||
|
||||
file_uuid = str(uuid.uuid4())
|
||||
dest_path = os.path.join(UPLOAD_SF_DIR, file_uuid + ".sf2")
|
||||
log_lines = []
|
||||
|
||||
def _log(msg):
|
||||
log_lines.append(msg)
|
||||
print(f"[autosample] {msg}")
|
||||
|
||||
try:
|
||||
result = _autosample_sf2(
|
||||
req.instrument_id, dest_path,
|
||||
preset_id=req.preset_id,
|
||||
preset_path=req.preset_path,
|
||||
preset_data_b64=req.preset_data,
|
||||
low=req.low, high=req.high, step=req.step,
|
||||
duration=req.duration, release=req.release,
|
||||
velocity=req.velocity, sample_rate=req.sample_rate,
|
||||
log=_log,
|
||||
)
|
||||
except FileNotFoundError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except RuntimeError as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
except Exception as e:
|
||||
try:
|
||||
if os.path.exists(dest_path):
|
||||
os.remove(dest_path)
|
||||
except Exception:
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail=f"Auto-sample that bai: {e}")
|
||||
|
||||
meta_path = os.path.join(UPLOAD_SF_DIR, file_uuid + ".meta")
|
||||
try:
|
||||
with open(meta_path, "w", encoding="utf-8") as f:
|
||||
json.dump({"original_name": f"autosample_{file_uuid[:8]}.sf2",
|
||||
"uuid": file_uuid, "file": file_uuid + ".sf2",
|
||||
"plugin_id": req.instrument_id, "kind": "autosampled"}, f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
get_scanner().scan_once()
|
||||
except Exception as e:
|
||||
print(f"[autosample] scan_once failed: {e}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"sf_id": file_uuid,
|
||||
"file": file_uuid + ".sf2",
|
||||
"name": f"autosample_{file_uuid[:8]}.sf2",
|
||||
"url": f"/api/v1/plugins/soundfonts/download/{file_uuid}",
|
||||
"size_bytes": result["size_bytes"],
|
||||
"note_count": result["note_count"],
|
||||
"plugin_id": req.instrument_id,
|
||||
}
|
||||
|
||||
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