231 halp_meta(name,
"Entity To MIDI")
232 halp_meta(c_name,
"avnd_entity_to_midi")
233 halp_meta(category,
"Midi")
234 halp_meta(author,
"ossia team")
237 "Turn tracked entities (people, blobs, hands) into MIDI: each entity "
238 "becomes one held MPE note whose pitch bend, pressure and timbre follow "
239 "its motion for its whole lifetime. No note spam, no stuck notes.")
240 halp_meta(uuid,
"e977241d-439f-4316-9de8-e316a1651b2e")
241 halp_flag(process_exec);
250 struct : halp::val_port<
"Entities", std::vector<ossia::value>>
254 "One element per entity, in the same formats Point Tracker accepts on "
255 "its Points inlet: full track records (as emitted by Point Tracker's "
256 "Tracks output), {position, id, confidence, ...} maps, vec2f / vec3f / "
257 "vec4f, sub-lists [x, y(, z)(, confidence)], or one flat list of "
258 "numbers. Records carrying an id keep it; bare positions are "
259 "identified by their index in the list, so such a source must emit "
260 "entities in a stable order - otherwise cable Point Tracker in "
261 "between, which is what assigns persistent ids.")
262 void update(
auto& self) { self.tracks_dirty =
true; }
265 struct : halp::combobox_t<
"Coordinates", E2MCoords>
269 "How to read bare vectors and flat number lists. 2D: (x, y), a third "
270 "component is confidence, flat lists stride by 2. 3D: (x, y, z), a "
271 "fourth component is confidence, flat lists stride by 3. Full records "
272 "are unaffected - they name their fields.")
275 std::string_view values[2]{
"2D",
"3D"};
276 E2MCoords init{E2MCoords::TwoD};
281 struct : halp::combobox_t<
"Output Mode", E2MOutputMode>
285 "MPE gives every entity its own channel with per-note pitch bend, "
286 "pressure and CC74 - use it with any MPE synth. Channel per entity "
287 "is the same channel pinning without the MPE handshake, for "
288 "hardware or DAW routing. Single channel puts every note on one "
289 "channel: per-note bend is impossible there, so pitch is latched "
290 "and pressure goes out as polyphonic aftertouch.")
293 std::string_view values[3]{
"MPE",
"Channel per entity",
"Single channel"};
294 E2MOutputMode init{E2MOutputMode::MPE};
298 struct : halp::combobox_t<
"MPE Zone", E2MZone>
302 "Lower zone: channel 1 is the master, notes use channels 2 and up. "
303 "Upper zone: channel 16 is the master, notes go down from 15. Use "
304 "Upper only to share the port with another Lower-zone device.")
307 std::string_view values[2]{
"Lower",
"Upper"};
308 E2MZone init{E2MZone::Lower};
312 struct : halp::spinbox_i32<
"Member Channels", halp::irange{1, 15, 15}>
316 "How many note channels the MPE zone uses (sent in the MPE "
317 "configuration). Also the channel count in Channel-per-entity mode. "
318 "Lower it to leave channels free for other gear on the same port.")
321 struct : halp::spinbox_i32<
"Bend Range", halp::irange{1, 96, 48}>
325 "Per-note pitch bend range in semitones, sent to the receiver at "
326 "start. 48 is the MPE default and gives eight octaves of glide, so "
327 "a moving entity never needs a retrigger. Must match the receiver "
328 "in non-MPE modes - set its bend range to the same value.")
331 struct : halp::toggle<
"Send MPE Config", halp::default_on_toggle>
335 "On start, send the MPE configuration (RPN 6) and per-channel bend "
336 "range (RPN 0) so the receiver sets itself up. Turn off if the "
337 "synth is already configured or mangles RPNs.")
340 struct : halp::spinbox_i32<
"Channel", halp::irange{1, 16, 1}>
344 "The MIDI channel used in Single-channel mode. Ignored in the "
348 struct : halp::combobox_t<
"Channel Reuse", E2MChannelReuse>
352 "What happens when a note ends and its channel could serve a new "
353 "entity. Least recently used waits for release tails to fade "
354 "before a channel is reused, so a new note's bend reset does not "
355 "detune a still-ringing release. Immediate reuses right away.")
358 std::string_view values[2]{
"Least recently used",
"Immediate"};
359 E2MChannelReuse init{E2MChannelReuse::LRU};
363 struct : halp::spinbox_f32<
"Release Reserve", halp::range{0., 5000., 500.}>
367 "Milliseconds a freed channel stays reserved for the previous "
368 "note's release tail. Raise it for long synth releases; 0 to "
369 "disable the reservation entirely.")
373 struct : halp::combobox_t<
"Note Model", E2MNoteModel>
377 "Sustained: the note IS the entity - one note-on when it appears, "
378 "one note-off when it leaves, expression in between. Triggered: "
379 "notes are short events fired when an entity appears (and "
380 "re-fired on pitch change in Retrigger tracking), for percussive "
381 "material. The two have different failure profiles on purpose.")
384 std::string_view values[2]{
"Sustained",
"Triggered"};
385 E2MNoteModel init{E2MNoteModel::Sustained};
389 struct : halp::combobox_t<
"Trigger On", E2MTriggerOn>
393 "Confirmed waits until the tracker has real evidence (~100 ms) - "
394 "no ghost notes from noise. First detection fires on the very "
395 "first sighting for the lowest possible onset latency, at the "
396 "price of occasional false starts.")
399 std::string_view values[2]{
"Confirmed",
"First detection"};
400 E2MTriggerOn init{E2MTriggerOn::Confirmed};
404 struct : halp::spinbox_i32<
"Max Voices", halp::irange{1, 16, 8}>
408 "How many entities sound at once. Fewer audible voices than "
409 "trackable entities keeps the result legible - the audience can "
410 "follow one person. Kept below the MPE member count so channel "
411 "sharing (which fuses two people into one voice) never happens.")
414 struct : halp::combobox_t<
"Steal Policy", E2MPriority>
418 "Who loses their voice when a new entity arrives and all voices "
419 "are busy. Confidence x Age protects long-standing, well-tracked "
420 "entities - a newcomer is the least musically committed. The "
421 "others name the victim directly. A confirmed entity is never "
422 "stolen for a provisional one.")
425 std::string_view values[5]{
426 "Confidence x Age",
"Steal oldest",
"Steal newest",
"Steal slowest",
427 "Steal least confident"};
428 E2MPriority init{E2MPriority::ConfidenceAge};
432 struct : halp::toggle<
"Allow Stealing", halp::default_on_toggle>
436 "Off: when every voice is busy, new entities are denied (and "
437 "counted on the Denied output) instead of interrupting a playing "
441 struct : halp::knob_f32<
"Steal Margin", halp::range{0., 1., 0.1}>
445 "A newcomer must beat the weakest voice's priority by this much "
446 "to steal it. Without the margin, two near-equal entities "
447 "ping-pong one voice into a stutter.")
450 struct : halp::spinbox_f32<
"Lost Grace", halp::range{0., 5000., 250.}>
454 "Milliseconds a note is held after its entity vanishes from the "
455 "tracker. If the entity comes back within the grace it resumes "
456 "the same note on the same channel - no retrigger, no channel "
457 "jump. Raise it for flickery detectors.")
460 struct : halp::spinbox_f32<
"Min Note", halp::range{0., 2000., 80.}>
464 "Shortest note the object will ever emit, in ms. An entity that "
465 "blinks out immediately still produces an audible note instead "
466 "of an unmusical click.")
469 struct : halp::spinbox_f32<
"Max Note", halp::range{0., 60000., 0.}>
473 "Hard ceiling on note length in ms; the note is released even if "
474 "the entity stays. 0 = unlimited (the note lives as long as the "
478 struct : halp::spinbox_f32<
"Trigger Length", halp::range{10., 5000., 200.}>
482 "Note duration in Triggered mode, in ms. Ignored in Sustained "
483 "mode, where the entity itself decides.")
486 struct : halp::spinbox_f32<
"Retrigger Lockout", halp::range{0., 2000., 120.}>
490 "Minimum ms between two note-ons for the same entity. The "
491 "machine-gun brake for Triggered mode and for entities that "
492 "flicker in and out beyond the lost grace.")
495 struct : halp::spinbox_f32<
"Watchdog", halp::range{0., 10000., 1000.}>
499 "Independent safety net: if no tracking data at all arrives for a "
500 "held note for this many ms (tracker crashed, cable unplugged), "
501 "the note is released anyway. 0 disables it. This is deliberately "
502 "separate from the tracker's own lifecycle.")
505 struct : halp::combobox_t<
"While Coasting", E2MCoast>
509 "What expression does while the tracker is coasting (predicting "
510 "through an occlusion). Freeze holds the last real values - "
511 "nothing moves that no one moved. Follow trusts the prediction. "
512 "Fade lets pressure sink towards silence until the entity is "
516 std::string_view values[3]{
"Freeze",
"Follow",
"Fade"};
517 E2MCoast init{E2MCoast::Freeze};
522 struct : halp::combobox_t<
"Pitch Axis", E2MAxis>
526 "Which movement axis drives pitch. Vertical (Y) reads as "
527 "high-note/high-position without explanation - the most legible "
528 "default for an audience.")
531 std::string_view values[3]{
"X",
"Y",
"Z"};
532 E2MAxis init{E2MAxis::Y};
536 struct : halp::toggle<
"Invert Axis", halp::default_on_toggle>
540 "Flip the axis. On by default because camera coordinates grow "
541 "downwards: inverted, standing tall plays high.")
544 struct : halp::spinbox_f32<
"Position Min", halp::range{-1000., 1000., 0.}>
548 "Axis value that maps to the lowest pitch. The computer-vision "
549 "chain normalises positions to 0..1, hence the default.")
552 struct : halp::spinbox_f32<
"Position Max", halp::range{-1000., 1000., 1.}>
556 "Axis value that maps to the highest pitch. Narrow the min/max "
557 "window to make a small stage area cover the whole range.")
560 struct : halp::spinbox_i32<
"Lowest Pitch", halp::irange{0, 127, 48}>
562 halp_meta(description,
"Bottom of the pitch range (MIDI note, 48 = C3).")
565 struct : halp::spinbox_i32<
"Highest Pitch", halp::irange{0, 127, 84}>
567 halp_meta(description,
"Top of the pitch range (MIDI note, 84 = C6).")
570 struct : halp::combobox_t<
"Pitch Tracking", E2MPitchTracking>
574 "Continuous bend: the note number is set once and per-note pitch "
575 "bend follows the entity - glissando or, with a scale, glides "
576 "that land exactly on scale notes. Latched: pitch is fixed at "
577 "note-on and never moves. Retrigger fires a new note on every "
578 "scale-step change and is only meaningful in Triggered mode.")
581 std::string_view values[3]{
"Continuous bend",
"Latched",
"Retrigger"};
582 E2MPitchTracking init{E2MPitchTracking::ContinuousBend};
586 struct : halp::spinbox_f32<
"Glide", halp::range{0., 2000., 60.}>
590 "Slew time in ms of the pitch bend towards its target. Short = "
591 "tight tracking; long = portamento. Per the MPE spec the slew "
592 "stops the instant the note is released.")
595 struct : halp::combobox_t<
"Scale", E2MScale>
599 "Snap pitch to a scale. None is a theremin: raw continuous "
600 "pitch. With a scale, position picks the nearest scale note and "
601 "the bend glides between them - continuous and quantised pitch "
602 "are the same mechanism. Pentatonic scales make any combination "
603 "of entities consonant, which is why installations use them.")
606 std::string_view values[13]{
607 "None",
"Chromatic",
"Major",
"Natural minor",
"Harmonic minor",
608 "Major pentatonic",
"Minor pentatonic",
"Blues",
"Dorian",
"Phrygian",
609 "Lydian",
"Mixolydian",
"Whole tone"};
610 E2MScale init{E2MScale::MinorPentatonic};
614 struct : halp::combobox_t<
"Root", E2MRoot>
616 halp_meta(description,
"Root note of the scale.")
619 std::string_view values[12]{
620 "C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
"A",
"A#",
"B"};
621 E2MRoot init{E2MRoot::C};
625 struct : halp::knob_f32<
"Snap Hysteresis", halp::range{0., 0.5, 0.15}>
629 "Fraction of a scale step an entity must travel past the "
630 "boundary before the quantiser switches notes. Stops a body "
631 "hovering on a boundary from machine-gunning between two "
632 "pitches - the hysteresis lives here at the quantiser, where the "
633 "chatter actually happens.")
637 struct : halp::combobox_t<
"Velocity From", E2MVelSource>
641 "What sets note velocity. Entry speed: how fast the entity was "
642 "moving when it appeared - a run onto the stage hits hard, a "
643 "drift in whispers. Fixed: constant. Confidence: the tracker's "
644 "certainty about the entity.")
647 std::string_view values[3]{
"Entry speed",
"Fixed",
"Confidence"};
648 E2MVelSource init{E2MVelSource::EntrySpeed};
652 struct : halp::spinbox_i32<
"Fixed Velocity", halp::irange{1, 127, 100}>
654 halp_meta(description,
"Velocity used when Velocity From is Fixed.")
657 struct : halp::spinbox_i32<
"Velocity Min", halp::irange{1, 127, 40}>
661 "Softest velocity. 40 keeps even the gentlest entrance audible.")
664 struct : halp::spinbox_i32<
"Velocity Max", halp::irange{1, 127, 110}>
666 halp_meta(description,
"Hardest velocity, reached at Speed Reference.")
669 struct : halp::spinbox_f32<
"Speed Reference", halp::range{0.01, 100., 2.}>
673 "Speed (in tracker coordinate units/s) that maps to maximum "
674 "velocity and full Speed expression. In normalised camera space, "
675 "2.0 means crossing the whole frame in half a second.")
678 struct : halp::spinbox_f32<
"Speed Window", halp::range{0., 500., 100.}>
682 "Entry-speed velocity uses the PEAK speed over this many ms "
683 "before the note starts. Measuring at the exact instant of "
684 "confirmation catches the tracker's smoother still converging "
685 "and every note comes out the same - the peak over a short "
686 "window is the real gesture.")
690 struct : halp::combobox_t<
"Pressure From", E2MSource>
694 "What drives per-note pressure (the loudness/intensity dimension "
695 "on MPE synths). Speed: moving fast presses hard - stillness is "
696 "silence, motion is sound. In Single-channel mode this goes out "
697 "as polyphonic aftertouch.")
701 std::string_view values[27]{
705 "Velocity X",
"Velocity Y",
706 "Velocity Z",
"|Velocity X|",
707 "|Velocity Y|",
"|Velocity Z|",
708 "Acceleration",
"Accel X",
709 "Accel Y",
"Accel Z",
711 "Curvature",
"Heading sin",
712 "Heading cos",
"Agitation",
713 "Nearest neighbour",
"Distance to centroid",
717 E2MSource init{E2MSource::Speed};
721 struct : halp::combobox_t<
"Timbre From", E2MSource>
725 "What drives the timbre control (CC74, the brightness dimension "
726 "on MPE synths). Defaults to X so left-right position colours "
727 "the sound while height plays pitch.")
731 std::string_view values[27]{
735 "Velocity X",
"Velocity Y",
736 "Velocity Z",
"|Velocity X|",
737 "|Velocity Y|",
"|Velocity Z|",
738 "Acceleration",
"Accel X",
739 "Accel Y",
"Accel Z",
741 "Curvature",
"Heading sin",
742 "Heading cos",
"Agitation",
743 "Nearest neighbour",
"Distance to centroid",
747 E2MSource init{E2MSource::X};
751 struct : halp::spinbox_i32<
"Timbre CC", halp::irange{0, 127, 74}>
755 "Controller number for the timbre dimension. 74 is the MPE "
756 "standard; change it only for non-MPE receivers.")
759 struct : halp::spinbox_f32<
"Rise", halp::range{0., 2000., 20.}>
763 "Smoothing time when expression values increase. Fast attack "
764 "with slow release is the difference between responsive and "
765 "twitchy - a symmetric filter cannot give you both.")
768 struct : halp::spinbox_f32<
"Fall", halp::range{0., 2000., 80.}>
772 "Smoothing time when expression values decrease. Longer than "
773 "Rise so gestures speak instantly and decay musically.")
776 struct : halp::spinbox_f32<
"Expression Rate", halp::range{1., 200., 50.}>
780 "Updates per second for each expression dimension of each note. "
781 "50 Hz is imperceptible from continuous; lower it for DIN ports "
782 "or receivers that choke on dense CC streams.")
785 struct : halp::knob_f32<
"Deadband", halp::range{0., 0.1, 0.005}>
789 "Suppress expression changes smaller than this fraction of full "
790 "scale. Kills the byte stream of an entity standing still "
791 "without affecting real motion.")
795 struct : halp::combobox_t<
"Beat Quantize", E2MQuantTargets>
799 "Snap notes to the musical grid of the score (which a Beat "
800 "Tracker can be driving live from a drummer). Onsets delays each "
801 "note-on to the nearest grid point - at most half a division. "
802 "Adding offsets snaps releases too. A note-off is never allowed "
803 "to land before its note-on.")
806 std::string_view values[3]{
"Off",
"Onsets",
"Onsets + offsets"};
807 E2MQuantTargets init{E2MQuantTargets::Off};
811 struct : halp::combobox_t<
"Grid", E2MGrid>
815 "Grid division notes snap to. Finer grids mean less hold "
816 "latency: at 120 BPM, 1/8 holds at most 125 ms, 1/16 at most "
820 std::string_view values[7]{
"1/1",
"1/2",
"1/4",
"1/8",
821 "1/16",
"1/8T",
"1/16T"};
822 E2MGrid init{E2MGrid::Eighth};
826 struct : halp::knob_f32<
"Quantize Strength", halp::range{0., 1., 1.}>
830 "Blend between the gesture's own timing (0) and the grid (1). "
831 "Partial values keep the human feel while tightening it.")
834 struct : halp::spinbox_f32<
"Max Hold", halp::range{0., 2000., 250.}>
838 "Longest a note will wait for its grid point, in ms. If the "
839 "transport stalls or the beat tracker drops out, the note plays "
840 "anyway instead of being stranded forever.")
844 struct : halp::impulse_button<
"Panic">
848 "Release every held note now: per-voice note-offs, then All "
849 "Sound Off / All Notes Off / Reset Controllers on every touched "
850 "channel, then bend/pressure/timbre reset - messages spaced so a "
851 "DIN receiver is not overrun at the exact moment it must work.")
852 void update(
auto& self) { self.panic_requested =
true; }
855 struct : halp::toggle<
"Panic On Stop", halp::default_on_toggle>
859 "Run the panic routine when the transport stops or jumps, so "
860 "stopping the score never leaves a synth droning. Leave on "
861 "unless you are debugging the routine itself.")
864 struct : halp::spinbox_f32<
"Panic Spacing", halp::range{0., 20., 1.}>
868 "Milliseconds between consecutive panic and configuration "
869 "messages. 1 ms keeps a DIN receiver from being overrun by the "
870 "burst; 0 sends everything back-to-back.")
880 struct : halp::spinbox_f32<
"Acceleration Ref", halp::range{0.01, 200., 8.}>
884 "Acceleration, in coordinate units per second squared, that maps to "
885 "the top of the range. Much larger than the speed reference: a "
886 "gesture that reverses within a tenth of a second is already tens of "
887 "units per second squared.")
890 struct : halp::spinbox_f32<
"Jerk Ref", halp::range{0.1, 5000., 200.}>
894 "Jerk (units per second cubed) that maps to the top of the range. "
895 "Jerk is the sharpest onset cue there is, and also the noisiest "
896 "descriptor - raise Descriptor Smoothing with it.")
899 struct : halp::spinbox_f32<
"Turn Rate Ref", halp::range{0.1, 50., 6.28}>
903 "Turn rate, in radians per second, that maps to the top of the "
904 "range. The default is one full turn per second.")
907 struct : halp::spinbox_f32<
"Neighbour Range", halp::range{0.001, 100., 0.25}>
911 "Radius, in coordinate units, within which another entity counts as "
912 "a neighbour. Sets the scale of both Nearest Neighbour and Density: "
913 "roughly how close two people have to be to read as together.")
916 struct : halp::spinbox_f32<
"Age Ref", halp::range{0.1, 600., 10.}>
920 "How long an entity must have been present, in seconds, for Age to "
921 "reach the top of the range - the length of the arc a mapping from "
925 struct : halp::spinbox_f32<
"Descriptor Smoothing", halp::range{0., 2000., 80.}>
929 "Time constant, in milliseconds, applied to the derivative-based "
930 "descriptors (acceleration, jerk, turn rate, agitation). Each "
931 "derivative multiplies the detector's position noise by about 1/dt, "
932 "so acceleration carries it squared and jerk cubed: without "
933 "smoothing they are noise. Raise it until the mapping is playable.")
939 struct : halp::midi_out_bus<
"MIDI">
943 "The MIDI stream: notes, per-note expression, MPE configuration "
944 "and panic messages, sample-accurately timestamped. Cable it to "
945 "a MIDI device or another process.")
946 ossia::net::node_base* ossia_node{};
951 halp_meta(name,
"Active Voices")
954 "Number of notes currently sounding (including notes held "
955 "through a tracking dropout).")
961 halp_meta(name,
"Denied")
964 "Running count of entities that wanted a voice and were refused "
965 "(voices full and stealing off or not worth it). If this climbs "
966 "during a show, raise Max Voices or loosen the steal policy - "
967 "silent drops are how installations get debugged at 2 am.")
973 using tick = halp::tick_musical;
977 static constexpr int k_max_voices = 16;
979 enum class vstate : uint8_t
991 vstate st = vstate::off;
992 bool on_wire =
false;
993 bool entity_confirmed =
false;
994 bool missing =
false;
995 bool coasting =
false;
996 bool off_scheduled =
false;
1001 bool note_suppressed =
false;
1002 double missing_since = 0.;
1003 double last_seen = 0.;
1004 double on_time = 0.;
1005 double on_raw_t = 0.;
1006 double on_target_q = -1.;
1007 double off_raw_t = 0.;
1008 double off_target_q = -1.;
1009 float priority = 0.f;
1011 float pitch_target = 60.f;
1012 float quant_prev = -1.f;
1013 float bend_semis = 0.f;
1014 float pressure_v = 0.f;
1015 float timbre_v = 0.f;
1016 float pressure_t = 0.f;
1017 float timbre_t = 0.f;
1019 int last_pressure = -1;
1020 int last_timbre = -1;
1021 double expr_acc = 1e9;
1022 uint8_t velocity = 100;
1027 bool in_use =
false;
1028 double freed_at = -1e18;
1048 static constexpr int N = 24;
1053 void push(
double time,
float speed)
noexcept
1057 head = (head + 1) % N;
1060 float peak_since(
double t0)
const noexcept
1063 for(
int i = 0; i < N; i++)
1064 if(t[i] >= t0 && s[i] > m)
1075 double first_seen = 0.;
1080 decltype(track_record::position) pos{};
1089 decltype(track_record::velocity)
vel{};
1090 decltype(track_record::velocity)
accel{};
1092 bool has_heading =
false;
1094 ossia::one_pole_filter<float> f_accel, f_jerk, f_turn, f_agitation;
1095 ossia::one_pole_filter<float> f_ax, f_ay, f_az;
1098 ossia::one_pole_filter<float>
f_vx, f_vy, f_vz;
1100 ossia::flat_map<int, synth_state> m_synth;
1101 std::vector<track_record> m_recs;
1105 ossia::flat_map<int, entity_descriptors>
m_desc;
1107 bool tracks_dirty =
false;
1108 bool panic_requested =
false;
1112 std::vector<std::array<uint8_t, 3>> last_direct_panic;
1116 ossia::exec_state_facade ossia_state;
1117 std::atomic<ossia::net::midi::midi_protocol*> midi_out{};
1119 void prepare(halp::setup s)
1133 if(inputs.panic_on_stop)
1141 if(inputs.panic_on_stop)
1146 void resume() { m_do_config =
true; }
1148 void transport(
auto flicks)
1152 if(inputs.panic_on_stop)
1153 panic_requested =
true;
1156 void operator()(
const halp::tick_musical& tk)
1158 const int frames = std::max(tk.frames, 1);
1159 const double dt = frames / m_rate;
1160 const double now = m_now;
1161 const double t_end = now + dt;
1168 const uint64_t sig = config_signature();
1169 if(sig != m_config_sig)
1171 if(m_config_sig != 0 && held_count() > 0)
1179 m_do_config =
false;
1180 if(inputs.send_config && inputs.output_mode.value == E2MOutputMode::MPE)
1186 panic_requested =
false;
1192 tracks_dirty =
false;
1193 parse_entities(now);
1199 emit_pending_notes(now, dt, frames, tk);
1200 update_expression(now, dt, frames);
1201 flush_timed(now, dt, frames);
1205 std::stable_sort(m_msgs.begin(), m_msgs.end(), [](
const out_msg& a,
const out_msg& b) {
1206 return a.ts != b.ts ? a.ts < b.ts : a.prio < b.prio;
1208 for(
const auto& m : m_msgs)
1210 auto& msg = outputs.midi.midi_messages.emplace_back();
1211 msg.bytes.assign(m.bytes, m.bytes + m.n);
1212 msg.timestamp = std::clamp<int64_t>(m.ts, 0, frames - 1);
1220 for(
const auto& m : m_msgs)
1222 const uint8_t st = m.bytes[0] & 0xF0;
1223 const int ch = m.bytes[0] & 0x0F;
1224 if(st == 0x90 && m.bytes[2] > 0)
1226 if(m_wire[ch].test(m.bytes[1]))
1227 m_wire_err =
"note-on while the (channel, pitch) is still held on the wire";
1228 m_wire[ch].set(m.bytes[1]);
1230 else if(st == 0x80 || (st == 0x90 && m.bytes[2] == 0))
1232 if(!m_wire[ch].test(m.bytes[1]))
1233 m_wire_err =
"note-off for a (channel, pitch) not held on the wire";
1234 m_wire[ch].reset(m.bytes[1]);
1238 outputs.active.value = held_count();
1239 outputs.denied.value = m_denied;
1246 bool check_invariants(std::string* err =
nullptr)
const
1250 if(!m_wire_err.empty())
1259 for(
const auto& v : m_voices)
1263 if(v.st == vstate::off)
1266 *err =
"note on wire for a dead voice";
1270 if(on_wire != m_outstanding)
1273 *err =
"outstanding note count mismatch";
1277 for(
int i = 0; i < k_max_voices; i++)
1278 for(
int j = i + 1; j < k_max_voices; j++)
1279 if(m_voices[i].on_wire && m_voices[j].on_wire
1280 && m_voices[i].channel == m_voices[j].channel
1281 && m_voices[i].note == m_voices[j].note)
1284 *err =
"duplicate (channel, pitch) on wire";
1290 int held_count() const noexcept
1293 for(
const auto& v : m_voices)
1299 int outstanding() const noexcept {
return m_outstanding; }
1309 const ossia::flat_map<int, entity_descriptors>&
descriptors() const noexcept
1316 bool is_mpe() const noexcept
1318 return inputs.output_mode.value == E2MOutputMode::MPE;
1320 bool is_single() const noexcept
1322 return inputs.output_mode.value == E2MOutputMode::SingleChannel;
1325 int master_channel() const noexcept
1327 return inputs.mpe_zone.value == E2MZone::Lower ? 0 : 15;
1330 int member_count() const noexcept
1334 int n = std::clamp(inputs.member_channels.value, 1, is_mpe() ? 15 : 16);
1339 int member_channel(
int i)
const noexcept
1342 return std::clamp(inputs.single_channel.value - 1, 0, 15);
1344 return inputs.mpe_zone.value == E2MZone::Lower ? 1 + i : 14 - i;
1348 int alloc_channel(
double now)
noexcept
1351 return member_channel(0);
1353 const int n = member_count();
1354 const bool lru = inputs.channel_reuse.value == E2MChannelReuse::LRU;
1355 const double reserve = inputs.release_reserve.value * 1e-3;
1358 double best_score = 0.;
1359 bool best_ok =
false;
1360 for(
int i = 0; i < n; i++)
1362 const int c = member_channel(i);
1363 const auto& ci = m_chans[c];
1366 const double idle = now - ci.freed_at;
1367 const bool ok = !lru || idle >= reserve;
1370 if(best < 0 || (ok && !best_ok) || (ok == best_ok && idle > best_score))
1378 m_chans[best].in_use =
true;
1382 void free_channel(
int c,
double t)
noexcept
1384 if(c >= 0 && c < 16)
1386 m_chans[c].in_use =
false;
1387 m_chans[c].freed_at = t;
1388 m_touched[c] =
true;
1393 static float axis_of(
const track_record& r, E2MAxis a)
noexcept
1398 return r.position.x;
1400 return r.position.y;
1402 return r.position.z;
1407 float norm_axis(
const track_record& r, E2MAxis a,
bool invert)
const noexcept
1409 const float lo = inputs.in_lo.value, hi = inputs.in_hi.value;
1410 float n = hi != lo ? (axis_of(r, a) - lo) / (hi - lo) : 0.f;
1411 n = std::clamp(n, 0.f, 1.f);
1412 return invert ? 1.f - n : n;
1415 float speed_of(
const track_record& r)
const noexcept
1417 const float vx = r.velocity.x, vy = r.velocity.y, vz = r.velocity.z;
1418 return std::sqrt(vx * vx + vy * vy + vz * vz);
1421 float norm_speed(
const track_record& r)
const noexcept
1423 const float ref = std::max(inputs.speed_ref.value, 1e-3f);
1424 return std::clamp(speed_of(r) / ref, 0.f, 1.f);
1427 float raw_pitch(
const track_record& r)
const noexcept
1429 const float n = norm_axis(r, inputs.pitch_axis.value, inputs.pitch_invert.value);
1430 const float lo = float(inputs.pitch_lo.value);
1431 const float hi = float(inputs.pitch_hi.value);
1432 return lo + n * (hi - lo);
1435 static const uint16_t* scale_mask(E2MScale s)
noexcept
1438 static constexpr uint16_t masks[13] = {
1453 return &masks[int(s)];
1456 bool in_scale(
int note)
const noexcept
1458 const auto s = inputs.scale.value;
1459 if(s == E2MScale::None || s == E2MScale::Chromatic)
1461 const int root = int(inputs.root.value);
1462 const int pc = ((note - root) % 12 + 12) % 12;
1463 return (*scale_mask(s) >> pc) & 1;
1466 float nearest_scale_note(
float raw)
const noexcept
1468 const auto s = inputs.scale.value;
1469 if(s == E2MScale::None)
1471 if(s == E2MScale::Chromatic)
1472 return std::round(raw);
1473 float best = std::round(raw);
1474 float best_d = 1e9f;
1475 const int c = int(std::floor(raw));
1476 for(
int k = c - 12; k <= c + 13; k++)
1478 if(k < 0 || k > 127 || !in_scale(k))
1480 const float d = std::abs(raw -
float(k));
1493 float quantize_pitch(
float raw, voice& v)
const noexcept
1495 if(inputs.scale.value == E2MScale::None)
1497 v.quant_prev = -1.f;
1500 const float cand = nearest_scale_note(raw);
1501 if(v.quant_prev < 0.f)
1503 v.quant_prev = cand;
1506 if(cand != v.quant_prev)
1508 const float step = std::abs(cand - v.quant_prev);
1509 const float mid = 0.5f * (cand + v.quant_prev);
1510 const float h = inputs.quant_hyst.value * step;
1511 if((cand > v.quant_prev && raw > mid + h) || (cand < v.quant_prev && raw < mid - h))
1512 v.quant_prev = cand;
1514 return v.quant_prev;
1526 static constexpr std::size_t max_pairwise = 64;
1530 void analyse(
double now)
1536 const float in_lo = inputs.in_lo.value, in_hi = inputs.in_hi.value;
1537 const float half = std::max(0.5f * (in_hi - in_lo), 1e-6f);
1540 const float centre = 0.5f * (in_lo + in_hi);
1541 const bool is_3d = inputs.coords.value == E2MCoords::ThreeD;
1543 const float sref = std::max(inputs.speed_ref.value, 1e-6f);
1544 const float aref = std::max(inputs.accel_ref.value, 1e-6f);
1545 const float jref = std::max(inputs.jerk_ref.value, 1e-6f);
1546 const float tref = std::max(inputs.turn_ref.value, 1e-6f);
1547 const float nrange = std::max(inputs.neighbour_range.value, 1e-6f);
1548 const float tau = std::max(inputs.desc_smooth.value * 1e-3f, 1e-4f);
1551 float gx = 0.f, gy = 0.f, gz = 0.f;
1552 for(
const auto& r : m_recs)
1558 const float inv_n = 1.f / float(m_recs.size());
1563 const bool pairwise = m_recs.size() <= max_pairwise;
1565 for(std::size_t i = 0; i < m_recs.size(); i++)
1567 const auto& r = m_recs[i];
1568 entity_descriptors d;
1570 auto st_it = m_synth.find(r.id);
1571 const double dt = (st_it != m_synth.end()) ? st_it->second.last_dt : 0.;
1572 const float fdt = float(dt > 1e-6 ? dt : 0.);
1573 const float alpha = fdt > 0.f ? ossia::lag_alpha(tau, fdt) : 1.f;
1576 d.x = norm_axis(r, E2MAxis::X,
false);
1577 d.y = norm_axis(r, E2MAxis::Y,
false);
1578 d.z = norm_axis(r, E2MAxis::Z,
false);
1583 const float dx = r.position.x - centre, dy = r.position.y - centre;
1584 float r2 = dx * dx + dy * dy;
1587 const float dz = r.position.z - centre;
1590 d.radius = std::clamp(std::sqrt(r2) / half, 0.f, 1.f);
1592 d.confidence = std::clamp(r.confidence, 0.f, 1.f);
1593 d.age = std::clamp(r.age / std::max(inputs.age_ref.value, 1e-6f), 0.f, 1.f);
1596 const float vx = r.velocity.x, vy = r.velocity.y, vz = r.velocity.z;
1597 const float sp = std::sqrt(vx * vx + vy * vy + vz * vz);
1598 d.speed = std::clamp(sp / sref, 0.f, 1.f);
1599 d.vx = bipolar(vx / sref);
1600 d.vy = bipolar(vy / sref);
1601 d.vz = bipolar(vz / sref);
1602 d.sx = std::clamp(std::abs(vx) / sref, 0.f, 1.f);
1603 d.sy = std::clamp(std::abs(vy) / sref, 0.f, 1.f);
1604 d.sz = std::clamp(std::abs(vz) / sref, 0.f, 1.f);
1607 if(st_it != m_synth.end() && fdt > 0.f)
1609 auto& st = st_it->second;
1611 const float ax = (vx - st.vel.x) / fdt;
1612 const float ay = (vy - st.vel.y) / fdt;
1613 const float az = (vz - st.vel.z) / fdt;
1614 const float amag = std::sqrt(ax * ax + ay * ay + az * az);
1616 d.accel = std::clamp(st.f_accel(amag, alpha) / aref, 0.f, 1.f);
1617 d.ax = bipolar(st.f_ax(ax, alpha) / aref);
1618 d.ay = bipolar(st.f_ay(ay, alpha) / aref);
1619 d.az = bipolar(st.f_az(az, alpha) / aref);
1621 const float jx = (ax - st.accel.x) / fdt;
1622 const float jy = (ay - st.accel.y) / fdt;
1623 const float jz = (az - st.accel.z) / fdt;
1624 const float jmag = std::sqrt(jx * jx + jy * jy + jz * jz);
1625 d.jerk = std::clamp(st.f_jerk(jmag, alpha) / jref, 0.f, 1.f);
1631 const float raw = std::atan2(vy, vx);
1632 const float unwrapped
1633 = st.has_heading ? ossia::unwrap_angle(raw, st.heading) : raw;
1634 const float rate = st.has_heading ? (unwrapped - st.heading) / fdt : 0.f;
1636 d.turn_rate = std::clamp(std::abs(st.f_turn(rate, alpha)) / tref, 0.f, 1.f);
1639 d.curvature = std::clamp(std::abs(rate) / (sp * tref) * sref, 0.f, 1.f);
1640 d.heading_sin = bipolar(std::sin(raw));
1641 d.heading_cos = bipolar(std::cos(raw));
1643 st.heading = unwrapped;
1644 st.has_heading =
true;
1648 d.heading_sin = bipolar(std::sin(st.heading));
1649 d.heading_cos = bipolar(std::cos(st.heading));
1654 const float mvx = st.f_vx(vx, alpha), mvy = st.f_vy(vy, alpha),
1655 mvz = st.f_vz(vz, alpha);
1656 const float ddx = vx - mvx, ddy = vy - mvy, ddz = vz - mvz;
1657 const float dev = std::sqrt(ddx * ddx + ddy * ddy + ddz * ddz);
1658 d.agitation = std::clamp(st.f_agitation(dev, alpha) / sref, 0.f, 1.f);
1660 st.vel = r.velocity;
1661 st.accel = {ax, ay, az};
1666 const float dx = r.position.x - gx, dy = r.position.y - gy,
1667 dz = r.position.z - gz;
1669 = std::clamp(std::sqrt(dx * dx + dy * dy + dz * dz) / half, 0.f, 1.f);
1672 if(pairwise && m_recs.size() > 1)
1674 float best = std::numeric_limits<float>::max();
1676 for(std::size_t j = 0; j < m_recs.size(); j++)
1680 const auto& o = m_recs[j];
1681 const float dx = r.position.x - o.position.x;
1682 const float dy = r.position.y - o.position.y;
1683 const float dz = r.position.z - o.position.z;
1684 const float dist = std::sqrt(dx * dx + dy * dy + dz * dz);
1685 best = std::min(best, dist);
1689 d.nearest = std::clamp(best / nrange, 0.f, 1.f);
1692 d.density = std::clamp(
float(within) /
float(m_recs.size() - 1), 0.f, 1.f);
1702 static float bipolar(
float v)
noexcept
1704 return std::clamp(0.5f + 0.5f * v, 0.f, 1.f);
1707 float source_value(
const track_record& r, E2MSource s)
const noexcept
1711 case E2MSource::None:
1714 return norm_axis(r, E2MAxis::X,
false);
1716 return norm_axis(r, E2MAxis::Y,
false);
1718 return norm_axis(r, E2MAxis::Z,
false);
1719 case E2MSource::Confidence:
1720 return std::clamp(r.confidence, 0.f, 1.f);
1725 const auto it =
m_desc.find(r.id);
1728 const entity_descriptors& d = it->second;
1732 case E2MSource::Radius:
1734 case E2MSource::Speed:
1736 case E2MSource::VelocityX:
1738 case E2MSource::VelocityY:
1740 case E2MSource::VelocityZ:
1742 case E2MSource::SpeedX:
1744 case E2MSource::SpeedY:
1746 case E2MSource::SpeedZ:
1748 case E2MSource::Acceleration:
1750 case E2MSource::AccelX:
1752 case E2MSource::AccelY:
1754 case E2MSource::AccelZ:
1756 case E2MSource::Jerk:
1758 case E2MSource::TurnRate:
1760 case E2MSource::Curvature:
1762 case E2MSource::HeadingSin:
1763 return d.heading_sin;
1764 case E2MSource::HeadingCos:
1765 return d.heading_cos;
1766 case E2MSource::Agitation:
1768 case E2MSource::NearestNeighbour:
1770 case E2MSource::CentroidDistance:
1771 return d.centroid_dist;
1772 case E2MSource::Density:
1774 case E2MSource::Age:
1781 float entity_priority(
const track_record& r)
const noexcept
1783 switch(inputs.priority.value)
1785 case E2MPriority::ConfidenceAge:
1786 return std::clamp(r.confidence, 0.f, 1.f)
1787 * std::clamp(r.age / 1.f, 0.f, 1.f);
1788 case E2MPriority::StealOldest:
1790 case E2MPriority::StealNewest:
1792 case E2MPriority::StealSlowest:
1794 case E2MPriority::StealLeastConfident:
1795 return r.confidence;
1801 voice* find_voice(int32_t
id)
noexcept
1803 for(
auto& v : m_voices)
1804 if(v.st != vstate::off && v.id == id)
1809 voice* free_voice() noexcept
1812 voice* free =
nullptr;
1813 for(
auto& v : m_voices)
1815 if(v.st == vstate::off)
1823 return used < std::min(inputs.max_voices.value, k_max_voices) ? free :
nullptr;
1826 static bool number_like(
const ossia::value& v)
noexcept
1828 const auto t = v.get_type();
1829 return t == ossia::val_type::FLOAT || t == ossia::val_type::INT
1830 || t == ossia::val_type::BOOL;
1832 static float to_float(
const ossia::value& v)
noexcept
1834 return ossia::convert<float>(v);
1839 bool third_is_confidence() const noexcept
1841 return inputs.coords.value == E2MCoords::TwoD;
1845 bool parse_entity(
const ossia::value& v,
bool third_conf, track_record& out)
noexcept
1847 switch(v.get_type())
1849 case ossia::val_type::VEC2F: {
1850 const auto& a = *v.target<ossia::vec2f>();
1851 out.position = {a[0], a[1], 0.f};
1854 case ossia::val_type::VEC3F: {
1855 const auto& a = *v.target<ossia::vec3f>();
1858 out.position = {a[0], a[1], 0.f};
1859 out.confidence = a[2];
1862 out.position = {a[0], a[1], a[2]};
1865 case ossia::val_type::VEC4F: {
1866 const auto& a = *v.target<ossia::vec4f>();
1867 out.position = {a[0], a[1], a[2]};
1868 out.confidence = a[3];
1871 case ossia::val_type::LIST: {
1872 const auto& l = *v.target<std::vector<ossia::value>>();
1873 if(l.size() < 2 || !number_like(l[0]) || !number_like(l[1]))
1875 out.position = {to_float(l[0]), to_float(l[1]), 0.f};
1876 if(l.size() >= 3 && number_like(l[2]))
1879 out.confidence = to_float(l[2]);
1881 out.position.z = to_float(l[2]);
1883 if(l.size() >= 4 && number_like(l[3]))
1884 out.confidence = to_float(l[3]);
1887 case ossia::val_type::MAP: {
1888 const auto& m = *v.target<ossia::value_map_type>();
1889 bool has_pos =
false;
1890 for(
const auto& [k, val] : m)
1892 if(k ==
"position" || k ==
"pos" || k ==
"centroid" || k ==
"point")
1895 if(parse_entity(val, third_conf, sub))
1897 out.position = sub.position;
1901 else if(k ==
"position_raw")
1904 if(parse_entity(val, third_conf, sub))
1905 out.position_raw = sub.position;
1907 else if(k ==
"velocity" || k ==
"vel")
1910 if(parse_entity(val,
false, sub))
1911 out.velocity = sub.position;
1915 if(number_like(val))
1916 out.id = int(to_float(val));
1918 else if(k ==
"slot")
1920 if(number_like(val))
1921 out.slot = int(to_float(val));
1923 else if(k ==
"confidence" || k ==
"conf" || k ==
"score")
1925 if(number_like(val))
1926 out.confidence = to_float(val);
1930 if(number_like(val))
1931 out.age = to_float(val);
1933 else if(k ==
"time_since_seen")
1935 if(number_like(val))
1936 out.time_since_seen = to_float(val);
1938 else if(k ==
"creation_time")
1940 if(number_like(val))
1941 out.creation_time = to_float(val);
1943 else if(k ==
"state")
1945 if(
auto st = val.target<std::string>())
1948 else if(k ==
"provisional")
1950 if(number_like(val))
1951 out.provisional = to_float(val) != 0.f;
1953 else if(k ==
"reacquired")
1955 if(number_like(val))
1956 out.reacquired = to_float(val) != 0.f;
1970 void parse_entities(
double now)
1973 const auto& in = inputs.tracks.value;
1980 const bool third_conf = third_is_confidence();
1984 if(number_like(in[0]))
1986 const std::size_t stride = inputs.coords.value == E2MCoords::ThreeD ? 3 : 2;
1987 for(std::size_t i = 0; i + stride <= in.size(); i += stride)
1991 = {to_float(in[i]), to_float(in[i + 1]),
1992 stride == 3 ? to_float(in[i + 2]) : 0.f};
1994 m_recs.push_back(std::move(r));
1995 if(m_recs.size() >= 512)
2001 for(
const auto& v : in)
2005 if(parse_entity(v, third_conf, r))
2006 m_recs.push_back(std::move(r));
2007 if(m_recs.size() >= 512)
2015 ossia::flat_map<int, synth_state> next;
2016 for(std::size_t i = 0; i < m_recs.size(); i++)
2018 auto& r = m_recs[i];
2024 r.state = r.provisional ?
"provisional" :
"confirmed";
2025 if(!std::isfinite(r.position_raw.x))
2026 r.position_raw = r.position;
2028 auto& st = next[r.id];
2029 if(
auto it = m_synth.find(r.id); it != m_synth.end())
2031 const auto& prev = it->second;
2032 const double d = now - prev.time;
2036 if(d > 1e-6 && r.velocity.x == 0.f && r.velocity.y == 0.f
2037 && r.velocity.z == 0.f)
2040 = {float((r.position.x - prev.pos.x) / d),
2041 float((r.position.y - prev.pos.y) / d),
2042 float((r.position.z - prev.pos.z) / d)};
2048 const double since = now - prev.time;
2054 st.first_seen = now;
2060 st.pos = r.position;
2062 r.age = float(now - st.first_seen);
2064 m_synth = std::move(next);
2067 void ingest(
double now)
2069 const auto& recs = m_recs;
2070 const bool triggered = inputs.note_model.value == E2MNoteModel::Triggered;
2073 for(
const auto& r : recs)
2077 m_present.push_back(r.id);
2081 auto& h = m_hist[r.id];
2082 h.push(now, speed_of(r));
2084 voice* v = find_voice(r.id);
2086 = inputs.trigger_on.value == E2MTriggerOn::FirstDetection || !r.provisional;
2090 refresh_voice(*v, r, now);
2095 try_start_triggered(r, now);
2097 try_start_sustained(r, now);
2102 for(
auto& v : m_voices)
2104 if(v.st == vstate::off)
2106 bool present =
false;
2107 for(
auto id : m_present)
2113 if(!present && !v.missing)
2116 v.missing_since = now;
2121 if(m_hist.size() > 128)
2122 for(
auto it = m_hist.begin(); it != m_hist.end();)
2123 it = (now - it->second.last_t > 2.) ? m_hist.erase(it) : std::next(it);
2126 void refresh_voice(voice& v,
const track_record& r,
double now)
2130 v.coasting = r.state ==
"coasting";
2131 v.entity_confirmed = v.entity_confirmed || !r.provisional;
2132 v.priority = entity_priority(r);
2134 const bool freeze = v.coasting && inputs.coast.value == E2MCoast::Freeze;
2137 const float rp = raw_pitch(r);
2138 v.pitch_target = quantize_pitch(rp, v);
2139 v.pressure_t = source_value(r, inputs.pressure_src.value);
2140 v.timbre_t = source_value(r, inputs.timbre_src.value);
2142 if(v.coasting && inputs.coast.value == E2MCoast::Fade)
2152 if(inputs.note_model.value == E2MNoteModel::Triggered
2153 && inputs.pitch_tracking.value == E2MPitchTracking::Retrigger
2154 && v.st == vstate::sounding && !v.retrig_pending)
2156 const int new_note = pitch_to_note(v.pitch_target);
2157 if(new_note != v.note && can_retrigger(v.id, now)
2158 && now - v.on_time >= inputs.min_note.value * 1e-3)
2163 v.off_raw_t = std::min(v.off_raw_t, now);
2164 v.off_target_q = -1.;
2165 v.retrig_pending =
true;
2167 m_retrig_ids.push_back(v.id);
2172 static int pitch_to_note(
float target)
noexcept
2174 return std::clamp(
int(std::lround(target)), 0, 127);
2177 bool can_retrigger(int32_t
id,
double now)
const noexcept
2179 auto it = m_last_on.find(
id);
2180 return it == m_last_on.end()
2181 || now - it->second >= inputs.retrig_lockout.value * 1e-3;
2185 voice* obtain_voice(
const track_record& r,
double now)
2187 if(
auto* v = free_voice())
2190 if(!inputs.allow_steal)
2197 voice* victim =
nullptr;
2198 for(
auto& v : m_voices)
2200 if(v.st == vstate::off)
2202 if(r.provisional && v.entity_confirmed)
2204 if(!victim || v.priority < victim->priority)
2207 const float p_new = entity_priority(r);
2208 if(!victim || p_new <= victim->priority + inputs.steal_margin.value)
2216 kill_voice(*victim, now);
2220 void init_voice(voice& v,
const track_record& r,
double now)
2224 v.st = vstate::pending_on;
2225 v.entity_confirmed = !r.provisional;
2227 v.priority = entity_priority(r);
2229 v.pitch_target = quantize_pitch(raw_pitch(r), v);
2230 v.pressure_t = source_value(r, inputs.pressure_src.value);
2231 v.timbre_t = source_value(r, inputs.timbre_src.value);
2232 v.pressure_v = v.pressure_t;
2233 v.timbre_v = v.timbre_t;
2234 v.velocity = compute_velocity(r, now);
2236 if(inputs.quant_mode.value != E2MQuantTargets::Off)
2237 v.on_target_q = -2.;
2239 v.on_target_q = -1.;
2242 void try_start_sustained(
const track_record& r,
double now)
2247 if(!m_timed.empty())
2249 if(!can_retrigger(r.id, now))
2251 voice* v = obtain_voice(r, now);
2254 init_voice(*v, r, now);
2255 const int c = alloc_channel(now);
2259 v->st = vstate::off;
2263 v->channel = int8_t(c);
2266 void try_start_triggered(
const track_record& r,
double now)
2268 if(!m_timed.empty())
2270 if(!can_retrigger(r.id, now))
2272 voice* v = obtain_voice(r, now);
2275 init_voice(*v, r, now);
2276 const int c = alloc_channel(now);
2279 v->st = vstate::off;
2283 v->channel = int8_t(c);
2285 v->off_scheduled =
true;
2286 v->off_raw_t = now + inputs.trigger_duration.value * 1e-3;
2287 v->off_target_q = -1.;
2290 uint8_t compute_velocity(
const track_record& r,
double now)
const
2292 const int lo = std::min(inputs.vel_lo.value, inputs.vel_hi.value);
2293 const int hi = std::max(inputs.vel_lo.value, inputs.vel_hi.value);
2294 switch(inputs.vel_source.value)
2296 case E2MVelSource::Fixed:
2297 return uint8_t(std::clamp(inputs.vel_fixed.value, 1, 127));
2298 case E2MVelSource::Confidence: {
2299 const float n = std::clamp(r.confidence, 0.f, 1.f);
2300 return uint8_t(std::clamp(
int(std::lround(lo + n * (hi - lo))), 1, 127));
2302 case E2MVelSource::EntrySpeed: {
2308 auto it = m_hist.find(r.id);
2309 if(it != m_hist.end())
2310 peak = it->second.peak_since(now - inputs.preroll.value * 1e-3);
2313 const float ref = std::max(inputs.speed_ref.value, 1e-3f);
2314 const float n = std::clamp(peak / ref, 0.f, 1.f);
2315 return uint8_t(std::clamp(
int(std::lround(lo + n * (hi - lo))), 1, 127));
2322 void lifecycle(
double now,
const halp::tick_musical& tk)
2324 const bool sustained = inputs.note_model.value == E2MNoteModel::Sustained;
2325 const double grace = inputs.lost_grace.value * 1e-3;
2326 const double wd = inputs.watchdog.value * 1e-3;
2327 const double maxn = inputs.max_note.value * 1e-3;
2329 for(
auto& v : m_voices)
2331 if(v.st == vstate::off)
2335 if(sustained && v.missing && !v.off_scheduled && now - v.missing_since >= grace)
2336 schedule_off(v, now,
true);
2339 if(v.st == vstate::pending_on && v.missing && now - v.missing_since >= grace)
2341 free_channel(v.channel, now);
2348 if(wd > 0. && v.st == vstate::sounding && !v.off_scheduled
2349 && now - v.last_seen >= wd)
2350 schedule_off(v, now,
false);
2353 if(maxn > 0. && v.st == vstate::sounding && !v.off_scheduled
2354 && now - v.on_time >= maxn)
2355 schedule_off(v, now,
true);
2359 void schedule_off(voice& v,
double now,
bool allow_quantise)
2361 if(v.st == vstate::pending_on)
2364 free_channel(v.channel, now);
2370 v.off_scheduled =
true;
2374 && inputs.quant_mode.value == E2MQuantTargets::OnsetsAndOffsets)
2380 void kill_voice(voice& v,
double now)
2383 push_note_off(v, 0);
2384 free_channel(v.channel, now);
2389 double grid_quarters() const noexcept
2391 switch(inputs.grid.value)
2393 case E2MGrid::Whole:
2397 case E2MGrid::Quarter:
2399 case E2MGrid::Eighth:
2401 case E2MGrid::Sixteenth:
2403 case E2MGrid::EighthTriplet:
2405 case E2MGrid::SixteenthTriplet:
2413 double quantise_target(
double raw_q)
const noexcept
2415 const double g = grid_quarters();
2416 const double grid_q = std::round(raw_q / g) * g;
2417 return raw_q + inputs.strength.value * (grid_q - raw_q);
2423 quarters_to_frame(
double target_q,
const halp::tick_musical& tk)
noexcept
2425 const double a = tk.start_position_in_quarters;
2426 const double b = tk.end_position_in_quarters;
2433 return int64_t((target_q - a) / (b - a) * std::max(tk.frames, 1));
2436 void emit_pending_notes(
double now,
double dt,
int frames,
const halp::tick_musical& tk)
2438 const double max_hold = inputs.max_hold.value * 1e-3;
2439 const double min_note = inputs.min_note.value * 1e-3;
2441 for(
auto& v : m_voices)
2443 if(v.st == vstate::pending_on)
2446 if(v.on_target_q == -2.)
2447 v.on_target_q = quantise_target(tk.start_position_in_quarters);
2451 if(v.on_target_q < 0.)
2458 const int64_t f = quarters_to_frame(v.on_target_q, tk);
2464 else if(now - v.on_raw_t >= max_hold)
2472 emit_note_on(v, now, off, frames);
2475 if(v.st == vstate::sounding && v.off_scheduled)
2478 if(v.off_target_q == -2.)
2479 v.off_target_q = quantise_target(tk.start_position_in_quarters);
2483 const double earliest = v.on_time + min_note;
2487 if(v.off_target_q >= 0.)
2489 const int64_t f = quarters_to_frame(v.off_target_q, tk);
2495 else if(now - v.off_raw_t >= max_hold)
2501 else if(v.off_raw_t <= now + dt)
2504 off = std::clamp<int64_t>(
2505 int64_t((v.off_raw_t - now) / dt * frames), 0, frames - 1);
2510 const double t_emit = now + double(off) / m_rate;
2511 if(t_emit < earliest)
2514 if(earliest <= now + dt)
2517 off = std::clamp<int64_t>(
2518 int64_t((earliest - now) / dt * frames), 0, frames - 1);
2524 emit_note_off(v, off, now +
double(off) / m_rate);
2531 if(!m_retrig_ids.empty())
2533 for(
auto id : m_retrig_ids)
2535 for(
const auto& r : m_recs)
2538 try_start_triggered(r, now);
2542 m_retrig_ids.clear();
2546 void emit_note_on(voice& v,
double tick_start, int64_t frame_off,
int frames)
2548 const int note = pitch_to_note(v.pitch_target);
2554 for(
const auto& o : m_voices)
2555 if(&o != &v && o.on_wire && o.channel == v.channel && o.note == note)
2559 if(!v.note_suppressed)
2561 v.note_suppressed =
true;
2574 for(
const auto& m : m_msgs)
2575 if(m.n == 3 && (m.bytes[0] & 0xF0) == 0x80
2576 && (m.bytes[0] & 0x0F) == (uint8_t(v.channel) & 0x0F)
2577 && m.bytes[1] == uint8_t(note) && m.ts > frame_off)
2579 frame_off = std::clamp<int64_t>(frame_off, 0, frames - 1);
2580 const double t = tick_start + double(frame_off) / m_rate;
2582 v.note = uint8_t(note);
2583 v.note_suppressed =
false;
2584 v.bend_semis = v.pitch_target - float(v.note);
2586 const uint8_t ch = uint8_t(v.channel);
2587 const bool member_dims = !is_single();
2592 if(inputs.pitch_tracking.value == E2MPitchTracking::ContinuousBend)
2594 push_bend(v, frame_off, 1);
2599 push_bend_value(ch, 8192, frame_off, 1);
2602 if(inputs.pressure_src.value != E2MSource::None)
2603 push_pressure(v, frame_off, 1);
2604 if(inputs.timbre_src.value != E2MSource::None)
2605 push_timbre(v, frame_off, 1);
2608 push(frame_off, 1, uint8_t(0x90 | ch), v.note, v.velocity);
2611 v.st = vstate::sounding;
2614 m_touched[ch] =
true;
2615 m_last_on[v.id] = t;
2619 void emit_note_off(voice& v, int64_t frame_off,
double t)
2623 push(frame_off, 0, uint8_t(0x80 | uint8_t(v.channel)), v.note, 64);
2627 free_channel(v.channel, t);
2631 void push_note_off(voice& v, int64_t frame_off)
2635 push(frame_off, 0, uint8_t(0x80 | uint8_t(v.channel)), v.note, 64);
2641 void prune_last_on(
double now)
2643 if(m_last_on.size() > 256)
2644 for(
auto it = m_last_on.begin(); it != m_last_on.end();)
2645 it = (now - it->second > 10.) ? m_last_on.erase(it) : std::next(it);
2649 void update_expression(
double now,
double dt,
int frames)
2651 const bool single = is_single();
2652 const double period = 1. / std::max(inputs.expr_rate.value, 1.f);
2653 const float db = inputs.deadband.value;
2654 const double tau_rise = std::max(inputs.rise.value, 0.f) * 1e-3;
2655 const double tau_fall = std::max(inputs.fall.value, 0.f) * 1e-3;
2656 const double tau_glide = std::max(inputs.glide.value, 0.f) * 1e-3;
2658 = inputs.pitch_tracking.value == E2MPitchTracking::ContinuousBend && !single;
2661 const int n = k_max_voices;
2662 const int start = m_rr++ % n;
2663 for(
int k = 0; k < n; k++)
2665 voice& v = m_voices[(start + k) % n];
2666 if(v.st != vstate::sounding || !v.on_wire)
2670 const float bend_target = cont ? (v.pitch_target - float(v.note)) : 0.f;
2672 v.bend_semis = bend_target;
2674 v.bend_semis += (bend_target - v.bend_semis)
2675 *
float(ossia::lag_alpha(tau_glide, dt));
2676 smooth_asym(v.pressure_v, v.pressure_t, tau_rise, tau_fall, dt);
2677 smooth_asym(v.timbre_v, v.timbre_t, tau_rise, tau_fall, dt);
2680 if(v.expr_acc < period)
2687 const int bend = bend_to_14bit(v);
2688 if(v.last_bend < 0 || std::abs(bend - v.last_bend) >=
int(db * 16384.f))
2689 if(bend != v.last_bend)
2691 push_bend_value(uint8_t(v.channel), bend, 0, 2);
2695 if(inputs.pressure_src.value != E2MSource::None)
2697 const int p = std::clamp(
int(std::lround(v.pressure_v * 127.f)), 0, 127);
2698 if(v.last_pressure < 0
2699 || std::abs(p - v.last_pressure) >= std::max(1,
int(db * 127.f)))
2700 if(p != v.last_pressure)
2703 push(0, 2, uint8_t(0xA0 | uint8_t(v.channel)), v.note, uint8_t(p));
2705 push2(0, 2, uint8_t(0xD0 | uint8_t(v.channel)), uint8_t(p));
2706 v.last_pressure = p;
2709 if(!single && inputs.timbre_src.value != E2MSource::None)
2711 const int t = std::clamp(
int(std::lround(v.timbre_v * 127.f)), 0, 127);
2712 if(v.last_timbre < 0
2713 || std::abs(t - v.last_timbre) >= std::max(1,
int(db * 127.f)))
2714 if(t != v.last_timbre)
2717 0, 2, uint8_t(0xB0 | uint8_t(v.channel)),
2718 uint8_t(std::clamp(inputs.timbre_cc.value, 0, 127)), uint8_t(t));
2726 smooth_asym(
float& state,
float target,
double tau_rise,
double tau_fall,
double dt)
2728 const double tau = target > state ? tau_rise : tau_fall;
2732 state += (target - state) *
float(ossia::lag_alpha(tau, dt));
2735 int bend_to_14bit(
const voice& v)
const noexcept
2737 const float range = float(std::max(inputs.bend_range.value, 1));
2738 const float n = std::clamp(v.bend_semis /
range, -1.f, 1.f);
2739 return std::clamp(8192 +
int(std::lround(n * 8191.f)), 0, 16383);
2743 void push(int64_t ts, uint8_t prio, uint8_t b0, uint8_t b1, uint8_t b2)
2745 m_msgs.push_back({ts, prio, 3, {b0, b1, b2}});
2747 void push2(int64_t ts, uint8_t prio, uint8_t b0, uint8_t b1)
2749 m_msgs.push_back({ts, prio, 2, {b0, b1, 0}});
2752 void push_bend(voice& v, int64_t ts, uint8_t prio)
2754 const int bend = bend_to_14bit(v);
2755 push_bend_value(uint8_t(v.channel), bend, ts, prio);
2758 void push_bend_value(uint8_t ch,
int bend, int64_t ts, uint8_t prio)
2760 push(ts, prio, uint8_t(0xE0 | ch), uint8_t(bend & 0x7F), uint8_t((bend >> 7) & 0x7F));
2762 void push_pressure(voice& v, int64_t ts, uint8_t prio)
2764 const int p = std::clamp(
int(std::lround(v.pressure_v * 127.f)), 0, 127);
2765 push2(ts, prio, uint8_t(0xD0 | uint8_t(v.channel)), uint8_t(p));
2766 v.last_pressure = p;
2768 void push_timbre(voice& v, int64_t ts, uint8_t prio)
2770 const int t = std::clamp(
int(std::lround(v.timbre_v * 127.f)), 0, 127);
2772 ts, prio, uint8_t(0xB0 | uint8_t(v.channel)),
2773 uint8_t(std::clamp(inputs.timbre_cc.value, 0, 127)), uint8_t(t));
2779 void queue_timed(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t n = 3)
2781 m_timed_tail = std::max(m_timed_tail, m_now);
2782 m_timed.push_back({m_timed_tail, n, {b0, b1, b2}});
2783 m_timed_tail += std::max(inputs.spacing.value, 0.f) * 1e-3;
2786 void flush_timed(
double now,
double dt,
int frames)
2790 std::size_t taken = 0;
2791 for(
const auto& m : m_timed)
2795 const int64_t off = std::clamp<int64_t>(
2796 int64_t((m.t - now) / dt * frames), 0, frames - 1);
2799 m_msgs.push_back({off, 0, m.n, {m.bytes[0], m.bytes[1], m.bytes[2]}});
2803 m_timed.erase(m_timed.begin(), m_timed.begin() + taken);
2806 void queue_rpn(uint8_t ch, uint8_t rpn_msb, uint8_t rpn_lsb, uint8_t value)
2808 queue_timed(uint8_t(0xB0 | ch), 101, rpn_msb);
2809 queue_timed(uint8_t(0xB0 | ch), 100, rpn_lsb);
2810 queue_timed(uint8_t(0xB0 | ch), 6, value);
2811 queue_timed(uint8_t(0xB0 | ch), 101, 127);
2812 queue_timed(uint8_t(0xB0 | ch), 100, 127);
2815 void queue_mpe_config()
2817 const uint8_t master = uint8_t(master_channel());
2818 const uint8_t members = uint8_t(member_count());
2820 queue_rpn(master, 0, 6, members);
2822 const uint8_t
range = uint8_t(std::clamp(inputs.bend_range.value, 1, 96));
2823 for(
int i = 0; i < members; i++)
2824 queue_rpn(uint8_t(member_channel(i)), 0, 0,
range);
2830 const double now = m_now;
2832 for(
auto& v : m_voices)
2834 if(v.st == vstate::off)
2838 queue_timed(uint8_t(0x80 | uint8_t(v.channel)), v.note, 64);
2842 free_channel(v.channel, now);
2847 for(
int c = 0; c < 16; c++)
2851 queue_timed(uint8_t(0xB0 | c), 120, 0);
2852 queue_timed(uint8_t(0xB0 | c), 123, 0);
2853 queue_timed(uint8_t(0xB0 | c), 121, 0);
2854 queue_timed(uint8_t(0xE0 | c), 0x00, 0x40);
2855 queue_timed(uint8_t(0xD0 | c), 0, 0, 2);
2856 queue_timed(uint8_t(0xB0 | c), 74, 64);
2867 last_direct_panic.clear();
2868 auto* proto = midi_out.load();
2869 for(
auto& v : m_voices)
2871 if(v.st == vstate::off)
2875 send_direct(proto, uint8_t(0x80 | uint8_t(v.channel)), v.note, 64);
2877 m_wire[uint8_t(v.channel) & 0x0F].reset(v.note);
2882 for(
int c = 0; c < 16; c++)
2886 send_direct(proto, uint8_t(0xB0 | c), 120, 0);
2887 send_direct(proto, uint8_t(0xB0 | c), 123, 0);
2888 send_direct(proto, uint8_t(0xB0 | c), 121, 0);
2889 send_direct(proto, uint8_t(0xE0 | c), 0x00, 0x40);
2894 ossia::net::midi::midi_protocol* proto, uint8_t b0, uint8_t b1, uint8_t b2)
2896 if(last_direct_panic.size() < 4096)
2897 last_direct_panic.push_back({b0, b1, b2});
2899 proto->push_value(libremidi::message{libremidi::midi_bytes{b0, b1, b2}, 0});
2902 void resolve_protocol()
2904 if(outputs.midi.ossia_node)
2906 auto& proto = outputs.midi.ossia_node->get_device().get_protocol();
2907 if(
auto mp =
dynamic_cast<ossia::net::midi::midi_protocol*
>(&proto))
2914 for(
auto& v : m_voices)
2920 for(
auto& c : m_chans)
2924 m_retrig_ids.clear();
2927 for(
auto& w : m_wire)
2931 uint64_t config_signature() const noexcept
2933 return uint64_t(inputs.output_mode.value)
2934 | (uint64_t(inputs.mpe_zone.value) << 4)
2935 | (uint64_t(std::clamp(inputs.member_channels.value, 1, 16)) << 8)
2936 | (uint64_t(std::clamp(inputs.bend_range.value, 1, 96)) << 16)
2937 | (uint64_t(std::clamp(inputs.single_channel.value, 1, 16)) << 24)
2938 | (uint64_t(1) << 32);
2942 std::array<voice, k_max_voices> m_voices{};
2943 std::array<chan_info, 16> m_chans{};
2944 std::array<bool, 16> m_touched{};
2948 std::array<std::bitset<128>, 16> m_wire{};
2949 std::string m_wire_err;
2950 std::vector<out_msg> m_msgs;
2951 std::vector<timed_msg> m_timed;
2952 std::vector<int32_t> m_present;
2953 std::vector<int32_t> m_retrig_ids;
2954 ossia::flat_map<int32_t, speed_hist> m_hist;
2955 ossia::flat_map<int32_t, double> m_last_on;
2957 double m_rate = 48000.;
2959 double m_timed_tail = 0.;
2961 int m_outstanding = 0;
2963 uint64_t m_config_sig = 0;
2964 bool m_do_config =
false;
2965 bool m_started =
false;
2971 halp_meta(name,
"Entity To MIDI")
2972 halp_meta(layout, halp::layouts::tabs)
2973 halp_meta(background, halp::colors::background_mid)
2977 halp_meta(name,
"Output")
2978 halp_meta(layout, halp::layouts::hbox)
2982 halp_meta(name,
"Mode")
2983 halp_meta(layout, halp::layouts::vbox)
2984 halp::item<&ins::output_mode> output_mode;
2985 halp::item<&ins::single_channel> single_channel;
2988 halp::spacing sp1{.width = 12, .height = 1};
2992 halp_meta(name,
"MPE")
2993 halp_meta(layout, halp::layouts::vbox)
2994 halp::item<&ins::mpe_zone> mpe_zone;
2995 halp::item<&ins::member_channels> member_channels;
2996 halp::item<&ins::bend_range> bend_range;
2997 halp::item<&ins::send_config> send_config;
3000 halp::spacing sp2{.width = 12, .height = 1};
3004 halp_meta(name,
"Channels")
3005 halp_meta(layout, halp::layouts::vbox)
3006 halp::item<&ins::channel_reuse> channel_reuse;
3007 halp::item<&ins::release_reserve> release_reserve;
3013 halp_meta(name,
"Voice")
3014 halp_meta(layout, halp::layouts::hbox)
3018 halp_meta(name,
"Model")
3019 halp_meta(layout, halp::layouts::vbox)
3020 halp::item<&ins::note_model> note_model;
3021 halp::item<&ins::trigger_on> trigger_on;
3022 halp::item<&ins::trigger_duration> trigger_duration;
3023 halp::item<&ins::coast> coast;
3026 halp::spacing sp1{.width = 12, .height = 1};
3030 halp_meta(name,
"Allocation")
3031 halp_meta(layout, halp::layouts::vbox)
3032 halp::item<&ins::max_voices> max_voices;
3033 halp::item<&ins::priority> priority;
3034 halp::item<&ins::allow_steal> allow_steal;
3035 halp::item<&ins::steal_margin> steal_margin;
3038 halp::spacing sp2{.width = 12, .height = 1};
3042 halp_meta(name,
"Timing")
3043 halp_meta(layout, halp::layouts::vbox)
3044 halp::item<&ins::lost_grace> lost_grace;
3045 halp::item<&ins::min_note> min_note;
3046 halp::item<&ins::max_note> max_note;
3047 halp::item<&ins::retrig_lockout> retrig_lockout;
3048 halp::item<&ins::watchdog> watchdog;
3054 halp_meta(name,
"Pitch")
3055 halp_meta(layout, halp::layouts::hbox)
3059 halp_meta(name,
"Source")
3060 halp_meta(layout, halp::layouts::vbox)
3061 halp::item<&ins::coords> coords;
3062 halp::item<&ins::pitch_axis> pitch_axis;
3063 halp::item<&ins::pitch_invert> pitch_invert;
3064 halp::item<&ins::in_lo> in_lo;
3065 halp::item<&ins::in_hi> in_hi;
3068 halp::spacing sp1{.width = 12, .height = 1};
3072 halp_meta(name,
"Range")
3073 halp_meta(layout, halp::layouts::vbox)
3074 halp::item<&ins::pitch_lo> pitch_lo;
3075 halp::item<&ins::pitch_hi> pitch_hi;
3076 halp::item<&ins::pitch_tracking> pitch_tracking;
3077 halp::item<&ins::glide> glide;
3080 halp::spacing sp2{.width = 12, .height = 1};
3084 halp_meta(name,
"Scale")
3085 halp_meta(layout, halp::layouts::vbox)
3088 halp::item<&ins::scale> scale_sel;
3089 halp::item<&ins::root> root;
3090 halp::item<&ins::quant_hyst> quant_hyst;
3096 halp_meta(name,
"Expression")
3097 halp_meta(layout, halp::layouts::hbox)
3101 halp_meta(name,
"Velocity")
3102 halp_meta(layout, halp::layouts::vbox)
3103 halp::item<&ins::vel_source> vel_source;
3104 halp::item<&ins::vel_fixed> vel_fixed;
3105 halp::item<&ins::vel_lo> vel_lo;
3106 halp::item<&ins::vel_hi> vel_hi;
3107 halp::item<&ins::preroll> preroll;
3110 halp::spacing sp1{.width = 12, .height = 1};
3114 halp_meta(name,
"Dimensions")
3115 halp_meta(layout, halp::layouts::vbox)
3116 halp::item<&ins::pressure_src> pressure_src;
3117 halp::item<&ins::timbre_src> timbre_src;
3118 halp::item<&ins::timbre_cc> timbre_cc;
3121 halp::spacing sp_an{.width = 12, .height = 1};
3128 halp_meta(name,
"Analysis range")
3129 halp_meta(layout, halp::layouts::vbox)
3130 halp::item<&ins::speed_ref> speed_ref;
3131 halp::item<&ins::accel_ref> accel_ref;
3132 halp::item<&ins::jerk_ref> jerk_ref;
3133 halp::item<&ins::turn_ref> turn_ref;
3134 halp::item<&ins::neighbour_range> neighbour_range;
3135 halp::item<&ins::age_ref> age_ref;
3136 halp::item<&ins::desc_smooth> desc_smooth;
3139 halp::spacing sp2{.width = 12, .height = 1};
3143 halp_meta(name,
"Smoothing")
3144 halp_meta(layout, halp::layouts::vbox)
3145 halp::item<&ins::rise> rise;
3146 halp::item<&ins::fall> fall;
3147 halp::item<&ins::expr_rate> expr_rate;
3148 halp::item<&ins::deadband> deadband;
3154 halp_meta(name,
"Rhythm")
3155 halp_meta(layout, halp::layouts::hbox)
3159 halp_meta(name,
"Quantize")
3160 halp_meta(layout, halp::layouts::vbox)
3161 halp::item<&ins::quant_mode> quant_mode;
3162 halp::item<&ins::grid> grid;
3165 halp::spacing sp1{.width = 12, .height = 1};
3169 halp_meta(name,
"Feel")
3170 halp_meta(layout, halp::layouts::vbox)
3171 halp::item<&ins::strength> strength;
3172 halp::item<&ins::max_hold> max_hold;
3178 halp_meta(name,
"Safety")
3179 halp_meta(layout, halp::layouts::hbox)
3183 halp_meta(name,
"Panic")
3184 halp_meta(layout, halp::layouts::vbox)
3185 halp::item<&ins::panic> panic;
3186 halp::item<&ins::panic_on_stop> panic_on_stop;
3187 halp::item<&ins::spacing> spacing;