phase2: fix velocity double-scaling in bridge job, bank/program parity, golden RMS test

- render_offline: send velocity as float 0..1 (RenderJob.cpp scales *127);
  old int(0.8*127) was scaled again -> clamp 127, velocity ignored
- forward soundfont_bank/program into bridge job (presence-based, matches
  midi_events_to_messages)
- new tests: bridge branch dispatch (SF_RENDER_ENGINE=bridge -> 404),
  bridge vs pedalboard golden RMS comparison (plan section 6)
This commit is contained in:
2026-08-17 11:04:01 +07:00
parent 235dee103d
commit 3d963eb8dd
3 changed files with 46 additions and 3 deletions
+9 -2
View File
@@ -77,7 +77,8 @@ def _preset_b64(preset_id, preset_path, preset_data_b64) -> str:
def render_offline(instrument_id, notes, bpm, sample_rate, preset_id=None,
preset_path=None, preset_data_b64=None) -> tuple:
preset_path=None, preset_data_b64=None,
soundfont_bank=None, soundfont_program=None) -> tuple:
"""Render MIDI notes qua native_bridge --render → (out_path, duration_sec).
Giữ response shape của _render_midi_notes_pedalboard. Lỗi → HTTPException
@@ -103,7 +104,7 @@ def render_offline(instrument_id, notes, bpm, sample_rate, preset_id=None,
"bpm": float(bpm),
"notes": [{
"pitch": int(n.get("pitch", 60)),
"velocity": int(float(n.get("velocity", 0.8)) * 127),
"velocity": float(n.get("velocity", 0.8)), # bridge job: float 0..1 (RenderJob.cpp *127)
"start_beat": float(n.get("start_beat", 0)),
"duration_beats": float(n.get("duration_beats", 1)),
} for n in notes],
@@ -112,6 +113,12 @@ def render_offline(instrument_id, notes, bpm, sample_rate, preset_id=None,
preset = _preset_b64(preset_id, preset_path, preset_data_b64)
if preset:
job["preset"] = preset
# bank/program (giữ parity với midi_events_to_messages cũ: key có mặt
# kể cả giá trị 0 — bridge xử lý theo presence, không theo truthiness)
if soundfont_bank is not None:
job["soundfont_bank"] = int(soundfont_bank) & 0x7F
if soundfont_program is not None:
job["soundfont_program"] = int(soundfont_program) & 0x7F
os.makedirs(settings.PROCESSED_DIR, exist_ok=True)
out_path = os.path.join(settings.PROCESSED_DIR, f"preview_{uuid.uuid4().hex[:10]}.wav")
job_path = out_path + ".job.json"