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:
+10
-4
@@ -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)}")
|
||||
|
||||
Reference in New Issue
Block a user