feat: CC11 expression automation (spec midi_note_cc11_expression)

- MIDINote schema + expression_curve (position_ratio 0..1, value 0..127)
- Piano roll CC lane mode Expression (CC11): draw/move/del anchors
- Offline: _expression_events grid 64 samples -> job.events -> RenderJob.cpp sample-accurate CC
- Realtime: scheduleNoteExpressionCC11 grid 16ms via bridge pushEvent / SonicSF fallback, epoch-guarded, cleared on stop
- tests/test_cc11_expression.py 6/6 (unit interp/grid/clamp + integration energy follows curve)
- rebuild daw_vst_bridge (narrowing fix) + sync binaries/dist, babel rebuild + sync 5 copies
This commit is contained in:
2026-08-26 11:14:56 +07:00
parent c146d3eb14
commit 2c96a223af
11 changed files with 482 additions and 32 deletions
+4 -1
View File
@@ -680,7 +680,8 @@ class PythonRenderEngine:
"note": note["pitch"],
"start_beat": target_global_bar * time_sig_num,
"duration_beats": note["duration_beats"],
"velocity": int(note.get("velocity", 0.8) * 127)
"velocity": int(note.get("velocity", 0.8) * 127),
"expression_curve": note.get("expression_curve")
})
if midi_events:
@@ -708,6 +709,7 @@ class PythonRenderEngine:
"velocity": float(ev["velocity"]) / 127.0,
"start_beat": ev["start_beat"],
"duration_beats": ev["duration_beats"],
"expression_curve": ev.get("expression_curve"),
} for ev in midi_events]
# preset_data rỗng → fallback preset
# bridge đang giữ (bridge_state.json) —
@@ -779,6 +781,7 @@ class PythonRenderEngine:
"velocity": float(ev["velocity"]) / 127.0,
"start_beat": ev["start_beat"],
"duration_beats": ev["duration_beats"],
"expression_curve": ev.get("expression_curve"),
} for ev in midi_events],
bpm=bpm,
sample_rate=self.sample_rate,