feat: VSTi autosample -> SF2/SF3 (tools/autosample_vsti.py) + bit_depth 16/24/32 render/export WAV (render_engine, plugins API, audio_editor, app.jsx encoders) + tests + task/walkthrough docs (53)

This commit is contained in:
2026-08-11 12:09:02 +07:00
parent f8cb2c6a5e
commit 7293d7ac7e
12 changed files with 2266 additions and 18 deletions
+10 -4
View File
@@ -490,6 +490,7 @@ async def download_soundfont_asset(sf_id: str):
class RenderRequest(BaseModel):
project_json: dict
output_filename: Optional[str] = "render_output.wav"
bit_depth: int = 16 # 16/24/32 — WAV PCM
class OpenInCarlaRequest(BaseModel):
@@ -933,6 +934,7 @@ class MidiRenderRequest(BaseModel):
preset_id: Optional[str] = None
preset_path: Optional[str] = None
preset_data: Optional[str] = None # base64 bytes .vstpreset (project nhúng)
bit_depth: int = 16 # 16/24/32 — WAV PCM
@router.post("/midi-render")
@@ -960,6 +962,7 @@ async def midi_render(req: MidiRenderRequest, current_user: dict = Depends(get_c
preset_id=req.preset_id,
preset_path=req.preset_path,
preset_data_b64=req.preset_data,
bit_depth=req.bit_depth,
)
return {
"success": True,
@@ -995,7 +998,7 @@ async def midi_render(req: MidiRenderRequest, current_user: dict = Depends(get_c
def _render_midi_notes_pedalboard(instrument_id: str, notes: list, bpm: float,
sample_rate: int, preset_id=None, preset_path=None,
preset_data_b64=None, soundfont_bank=None,
soundfont_program=None) -> tuple:
soundfont_program=None, bit_depth: int = 16) -> tuple:
"""Render MIDI notes qua pedalboard (VSTi + preset) → WAV trong PROCESSED_DIR.
Trả (out_path, duration_sec). Ném HTTPException khi plugin không load được."""
@@ -1041,7 +1044,8 @@ def _render_midi_notes_pedalboard(instrument_id: str, notes: list, bpm: float,
buf = vst(midi_messages, sample_rate=sample_rate,
duration=total_needed / float(sample_rate), num_channels=2)
out_path = os.path.join(settings.PROCESSED_DIR, f"preview_{uuid.uuid4().hex[:10]}.wav")
sf.write(out_path, buf.T, sample_rate)
subtype_map = {16: "PCM_16", 24: "PCM_24", 32: "PCM_32"}
sf.write(out_path, buf.T, sample_rate, subtype=subtype_map.get(int(bit_depth), "PCM_16"))
return out_path, buf.shape[1] / float(sample_rate)
@@ -1056,6 +1060,7 @@ class SoundfontRenderRequest(BaseModel):
notes: list = []
bpm: float = 120.0
sample_rate: int = 44100
bit_depth: int = 16 # 16/24/32 — WAV PCM
@router.post("/soundfont-render")
@@ -1091,7 +1096,8 @@ async def soundfont_render(req: SoundfontRenderRequest, current_user: dict = Dep
bank=req.bank, program=req.program,
sr=req.sample_rate, bpm=req.bpm,
)
sf.write(out_path, audio.T, req.sample_rate)
subtype_map = {16: "PCM_16", 24: "PCM_24", 32: "PCM_32"}
sf.write(out_path, audio.T, req.sample_rate, subtype=subtype_map.get(int(req.bit_depth), "PCM_16"))
return {
"success": True,
"file_id": os.path.basename(out_path),
@@ -1191,7 +1197,7 @@ async def render_project(
safe_name += ".wav"
output_path = os.path.join(settings.PROCESSED_DIR, safe_name)
try:
result_path = engine.render_project(req.project_json, output_path)
result_path = engine.render_project(req.project_json, output_path, bit_depth=req.bit_depth)
return {"url": f"/static/audio/processed/{os.path.basename(result_path)}", "path": result_path}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Render failed: {str(e)}")