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
+20
View File
@@ -332,6 +332,26 @@ int run_render_job(const std::string& jobPath, const std::string& outPath) {
evs.push_back({endSample, 1, pitch, 0});
if (endSample > totalNeeded) totalNeeded = endSample;
}
// Optional CC events from job.events (e.g. CC11 expression curves emitted
// by native_render._expression_events). kind: 2=CC (a=cc, b=value 0..127),
// 3=program change (a=program). Sample-accurate like note events.
const json_value_s* jev = memberValue(job, "events");
if (jev && jev->type == json_type_array) {
const json_array_s* evs_arr = static_cast<const json_array_s*>(jev->payload);
for (const json_array_element_s* el = evs_arr->start; el; el = el->next) {
const json_value_s* v = el->value;
if (!v || v->type != json_type_object) continue;
const json_object_s* n = static_cast<const json_object_s*>(v->payload);
double sampleD = 0.0;
memberNumber(n, "sample", sampleD);
const uint32_t kind = (uint32_t)(memberInt(n, "kind", 2) & 0x3);
const uint32_t a = (uint32_t)(memberInt(n, "a", 0) & 0x7F);
const uint32_t b = (uint32_t)(memberInt(n, "b", 0) & 0x7F);
const uint64_t sample = (uint64_t)std::llround(sampleD);
evs.push_back({sample, (uint8_t)kind, a, b});
if (sample + 1 > totalNeeded) totalNeeded = sample + 1;
}
}
std::stable_sort(evs.begin(), evs.end(),
[](const Ev& a, const Ev& b) { return a.sample < b.sample; });