123 halp_meta(name,
"Chord")
124 halp_meta(c_name,
"Chord")
125 halp_meta(category,
"Midi/Pitch")
126 halp_meta(author,
"ossia score")
127 halp_meta(manual_url,
"https://ossia.io/score-docs/processes/midi-utilities.html#chord")
128 halp_meta(description,
"Generate a chord from a single note")
129 halp_meta(uuid,
"F0904279-EA26-48DB-B0DF-F68FE3091DA1");
130 halp_flag(process_exec);
132 halp_meta(local_midi_tick_batch_reserve, 4096)
144 T00, T01, T02, T03, T04, T05, T06, T07, T08, T09,
145 T10, T11, T12, T13, T14, T15, T16, T17, T18, T19,
146 T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
147 T30, T31, T32, T33, T34, T35, T36
151 static_assert(chord_count == T36 + 1);
153 enum PlayMode : uint8_t
160 enum StrumDirection : uint8_t
170 static constexpr int max_intervals = 8;
180 std::array<int8_t, max_voices> offsets{};
185 using midi_out = halp::midi_out_bus<
"out", libremidi::message>;
188 halp::midi_bus<
"in", libremidi::message> midi;
189 halp::hslider_i32<
"Num. Notes", halp::irange{1, 5, 3}> num;
190 struct : halp::enum_t<Chord,
"Chord">
198 std::string_view values[chord_count]
199 = {
"Maj",
"Min",
"Sus2",
"Sus4",
"Dim",
200 "Aug",
"maj triad",
"min triad",
"dim triad",
"aug triad",
201 "Power",
"sus2 triad",
"sus4 triad",
"maj6",
"Dom7",
202 "Dom7-5",
"Dom7+5",
"Dom7sus4",
"maj7",
"maj7+5",
203 "add4",
"add9",
"sus4add9",
"add11",
"maj6/9",
204 "Dom9",
"maj9",
"Dom9sus4",
"Dom7-9",
"Dom7+11",
205 "m6",
"m7",
"m7-5",
"m7+5",
"dim7",
206 "mM7",
"madd4",
"madd9",
"m69",
"m9",
207 "m7b9b5",
"m7add11",
"mM7add11"};
212 struct : halp::enum_t<PlayMode,
"Mode">
216 "Chord plays the chosen chord type, Harmonizer the intervals, Memory "
217 "the chord learned for each key")
220 std::string_view values[3] = {
"Chord",
"Harmonizer",
"Memory"};
221 PlayMode init = Table;
225 struct : halp::value_list<
"Intervals", int, max_intervals, 4, 7>
229 "Harmonizer: intervals in semitones from the played note, e.g. 4 7 or "
230 "-12 7. A repeated interval plays one note")
231 static constexpr std::array<halp::list_column, 1> columns()
233 return {halp::list_column{.name =
"Semitones", .min = -48, .max = 48, .init = 7}};
237 struct : halp::toggle<
"Play root", halp::toggle_setup{.init =
true}>
239 halp_meta(description,
"Harmonizer: also play the note itself")
242 struct : halp::spinbox_i32<
"Inversion", halp::irange{-8, 8, 0}>
246 "Each step moves the lowest note an octave up (negative: the highest "
247 "an octave down). Past the number of notes, the whole chord moves by "
251 struct : halp::value_list<
"Voice chance", float,
max_voices, 1.f>
255 "Chance of each voice from 0 to 1, from the root up, e.g. 1 0.5 0.5. "
256 "The last value applies to the remaining voices")
257 static constexpr std::array<halp::list_column, 1> columns()
259 return {halp::list_column{
260 .name =
"Chance", .min = 0., .max = 1., .init = 1., .integral =
false}};
264 struct : halp::value_list<
"Voice velocity", float,
max_voices, 1.f>
268 "Factor applied to the played velocity for each voice, from the root "
269 "up, e.g. 1 0.8 0.6. The last value applies to the remaining voices")
270 static constexpr std::array<halp::list_column, 1> columns()
272 return {halp::list_column{
273 .name =
"Velocity", .min = 0., .max = 4., .init = 1., .integral =
false}};
277 struct : halp::spinbox_i32<
"Seed", halp::irange{0, 10000, 0}>
281 "0 draws freely; any other value gives a reproducible sequence of "
282 "chances, restarted on stop")
285 struct : halp::time_chooser<
"Strum", halp::range{0., 0.5, 0.}>
287 halp_meta(description,
"Delay between successive notes of a chord")
290 struct : halp::enum_t<StrumDirection,
"Strum direction">
293 description,
"Order of the strum: low to high, high to low, or both in turn")
296 std::string_view values[3] = {
"Up",
"Down",
"Alternate"};
297 StrumDirection init = Up;
301 struct : halp::knob_f32<
"Tension", halp::range{-1., 1., 0.}>
305 "Above 0 the strum slows down across the chord, below 0 it speeds up")
308 struct : halp::knob_f32<
"Crescendo", halp::range{-1., 1., 0.}>
312 "Velocity change across the strum order: 1 doubles the last note, -1 "
316 struct : halp::toggle<
"Learn">
320 "Memory learning: play a chord, release it, then press the key that "
324 struct : halp::string_list<
"Memory">
328 "Learned chords, one row per key: the key, then semitones from the "
329 "key, e.g. C4: 0 4 7 or 62: 0 3 7")
333 std::function<void(
const memory_table&)> update_controller;
334 static halp::string_list_value controller_value(
const memory_table& t)
341 static ossia::value upgrade_value(
const ossia::value& old)
343 if(
auto text = old.target<std::string>())
347 static ossia::value migrate_value(
const ossia::value& v)
349 return upgrade_value(v);
353 struct : halp::toggle<
"MPE">
357 "Each voice on its own channel, with the pitch bend, pressure and "
358 "CC 74 of the note that started it")
361 struct : halp::channel_mask<
"MPE channels", 0xFFFE>
363 halp_meta(description,
"Channels the MPE voices use. None: 2 to 16")
374 halp_meta(name,
"Chord")
375 halp_meta(layout, halp::layouts::hbox)
376 halp_meta(background, halp::colors::background_dark)
380 halp_meta(name,
"Chord")
381 halp_meta(layout, halp::layouts::section)
382 halp_meta(background, halp::colors::background_mid)
383 halp::item<&ins::mode> mode;
386 halp_meta(layout, halp::layouts::vbox)
387 static constexpr auto condition()
389 return halp::enabled_when<&ins::mode, PlayMode::Table>{};
391 halp::item<&ins::chord> chord;
392 halp::item<&ins::num> num;
394 halp::item<&ins::inversion> inversion;
399 halp_meta(name,
"Strum")
400 halp_meta(layout, halp::layouts::section)
401 halp_meta(background, halp::colors::background_mid)
402 halp::item<&ins::strum> strum;
403 halp::item<&ins::strum_direction, halp::label_as(
"Direction")> strum_direction;
406 halp_meta(layout, halp::layouts::hbox)
407 halp::item<&ins::tension> tension;
408 halp::item<&ins::crescendo> crescendo;
414 halp_meta(name,
"Settings")
415 halp_meta(layout, halp::layouts::tabs)
416 halp_meta(background, halp::colors::background_darker)
420 halp_meta(name,
"Harmonizer")
421 halp_meta(layout, halp::layouts::vbox)
422 static constexpr auto condition()
424 return halp::enabled_when<&ins::mode, PlayMode::Harmonizer>{};
426 halp::item<&ins::intervals> intervals;
427 halp::item<&ins::root> root;
432 halp_meta(name,
"Voices")
433 halp_meta(layout, halp::layouts::hbox)
434 halp::item<&ins::chance> chance;
435 halp::item<&ins::velocity> velocity;
436 halp::item<&ins::seed> seed;
441 halp_meta(name,
"Memory")
442 halp_meta(layout, halp::layouts::vbox)
443 static constexpr auto condition()
445 return halp::enabled_when<&ins::mode, PlayMode::Memory>{};
447 halp::item<&ins::learn> learn;
448 halp::item<&ins::memory> memory;
453 halp_meta(name,
"MPE")
454 halp_meta(layout, halp::layouts::vbox)
455 halp::item<&ins::mpe> mpe;
456 halp::item<&ins::mpe_channels, halp::label_as(
"Channels")> mpe_channels;
463 static const constexpr std::array<int, 5> major7{0, 4, 7, 11, 12};
464 static const constexpr std::array<int, 5> minor7{0, 3, 7, 10, 12};
465 static const constexpr std::array<int, 5> sus2{0, 2, 7, 9, 12};
466 static const constexpr std::array<int, 5> sus4{0, 5, 7, 9, 12};
467 static const constexpr std::array<int, 5> dim{0, 3, 6, 9, 12};
468 static const constexpr std::array<int, 5> aug{0, 4, 8, 10, 12};
469 static constexpr std::array<const std::array<int, 5>*,
legacy_chord_count> legacy_chords{
470 &major7, &minor7, &sus2, &sus4, &dim, &aug};
474 std::random_device rd;
475 m_free_seed = (uint64_t(rd()) << 32) ^ uint64_t(rd());
479 void prepare(halp::setup info)
noexcept
485 void stop() noexcept { m_stop_requested =
true; }
487 using tick = halp::tick_musical;
488 void operator()(
const halp::tick_musical& tk)
noexcept
490 const int frames = tk.frames > 0 ? tk.frames : 0;
495 m_stop_requested =
false;
499 for(
const libremidi::message& m : inputs.midi)
502 = frames > 0 ? std::clamp<int64_t>(m.timestamp, 0, frames - 1) : 0;
503 release_queue(
int(ts));
506 release_queue(frames);
512 int voices(
int pitch, std::array<int, max_voices>& out)
noexcept
515 std::array<voice, max_voices> v{};
516 const int n = build_voices(pitch, v);
517 for(
int i = 0; i < n; i++)
518 out[i] = v[i].offset;
531 using tracker_type = mtk::note_tracker<128, max_voices>;
533 static uint32_t tag_of(
int channel,
int pitch)
noexcept
535 return uint32_t(((channel - 1) << 7) | pitch) + 1;
540 void update_parameters() noexcept
542 const int seed = inputs.seed;
549 m_intervals.
assign(inputs.intervals.value, -127, 127);
550 update_memory(inputs.memory.value);
552 const uint16_t mask = inputs.mpe_channels.mask();
553 m_alloc.set_channels(mask != 0 ? mask : uint16_t(0xFFFE));
555 if(!inputs.learn.value)
559 void reseed() noexcept
561 m_rng = m_seed != 0 ? uint64_t(m_seed) * UINT64_C(0x9E3779B97F4A7C15) : m_free_seed;
566 static float voice_factor(
const auto& list,
int i,
float max)
noexcept
570 const float v = list[std::min(std::size_t(i), list.size() - 1)];
571 return std::isfinite(v) ? std::clamp(v, 0.f, max) : 1.f;
575 double random01() noexcept
577 uint64_t z = (m_rng += UINT64_C(0x9E3779B97F4A7C15));
578 z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9);
579 z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB);
581 return double(z >> 11) * 0x1.0p-53;
589 void update_memory(
const halp::string_list_value& rows)
noexcept
594 bool same = m_memory_parsed;
595 for_each_memory_char(rows, [&](
char c) {
596 same = same && n < m_memory_size && m_memory_text[n] == c;
599 if(same && n == m_memory_size)
602 for_each_memory_char(rows, [&](
char c) { m_memory_text[m_memory_size++] = c; });
603 m_memory_parsed =
true;
604 parse_memory({m_memory_text.data(), m_memory_size}, m_memory);
609 for_each_memory_char(
const halp::string_list_value& rows,
auto&& f)
noexcept
612 for(
const auto& [
id, text] : rows)
632 std::size_t start = 0;
633 while(start <= s.size())
635 std::size_t end = start;
636 while(end < s.size() && s[end] !=
';' && s[end] !=
'\n')
638 parse_memory_entry(s.substr(start, end - start), table);
647 static constexpr std::string_view names[12]
648 = {
"C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
"A",
"A#",
"B"};
649 char*
const end = buf + 64;
651 const auto name = names[key % 12];
652 p = std::copy(name.begin(), name.end(), p);
653 p = std::to_chars(p, end, key / 12 - 1).ptr;
655 for(
int i = 0; i < c.count; i++)
658 p = std::to_chars(p, end,
int(c.offsets[i])).ptr;
660 return std::size_t(p - buf);
667 halp::string_list_value rows;
668 for(
int key = 0; key < 128; key++)
670 if(table[key].count <= 0)
683 halp::string_list_value rows;
684 std::size_t start = 0;
685 while(start <= s.size())
687 std::size_t end = start;
688 while(end < s.size() && s[end] !=
';' && s[end] !=
'\n')
690 auto entry = s.substr(start, end - start);
692 while(!entry.empty() && (entry.front() ==
' ' || entry.front() ==
'\t'))
693 entry.remove_prefix(1);
695 && (entry.back() ==
' ' || entry.back() ==
'\t' || entry.back() ==
'\r'))
696 entry.remove_suffix(1);
702 parse_memory_entry(entry, one);
703 auto it = std::find_if(one.begin(), one.end(), [](
auto& c) { return c.count > 0; });
712 rows.emplace_back(
id, std::string(entry));
718 static ossia::value rows_value(
const halp::string_list_value& rows)
720 std::vector<ossia::value> res;
721 res.reserve(rows.size());
722 for(
const auto& [
id, text] : rows)
723 res.emplace_back(
std::vector<ossia::value>{id, text});
728 static void parse_memory_entry(std::string_view e,
memory_table& table)
noexcept
730 const auto colon = e.find(
':');
731 if(colon == std::string_view::npos)
733 auto key_text = e.substr(0, colon);
734 while(!key_text.empty() && (key_text.front() ==
' ' || key_text.front() ==
'\t'))
735 key_text.remove_prefix(1);
736 while(!key_text.empty() && (key_text.back() ==
' ' || key_text.back() ==
'\t' || key_text.back() ==
'\r'))
737 key_text.remove_suffix(1);
742 if(key_text[0] >=
'0' && key_text[0] <=
'9')
744 auto [ptr, ec] = std::from_chars(key_text.data(), key_text.data() + key_text.size(), key);
745 if(ec != std::errc{} || ptr != key_text.data() + key_text.size())
748 else if(
auto n = mtk::parse_note_name(key_text); n && n->second == key_text.size())
752 if(key < 0 || key > 127)
755 int_list<max_voices> offsets;
756 offsets.parse(e.substr(colon + 1), -127, 127);
757 if(!offsets.valid || offsets.count == 0)
759 auto& c = table[key];
760 c.count = int8_t(offsets.count);
761 for(
int i = 0; i < offsets.count; i++)
762 c.offsets[i] = int8_t(offsets.values[i]);
767 void reset_learning() noexcept
770 m_learn_down_count = 0;
772 m_captured_count = 0;
773 m_trigger_down =
false;
778 void learn_note_on(
int pitch)
noexcept
780 if(m_learn_down[pitch])
782 if(m_learn_down_count == 0)
785 if(m_captured_count >= 2)
787 assign_learned(pitch);
788 m_captured_count = 0;
789 m_trigger_down =
true;
792 m_learn_down[pitch] =
true;
793 m_learn_down_count++;
794 if(!m_trigger_down && m_group_count <
max_voices)
795 m_group[m_group_count++] = pitch;
798 void learn_note_off(
int pitch)
noexcept
800 if(!m_learn_down[pitch])
802 m_learn_down[pitch] =
false;
803 if(--m_learn_down_count > 0)
805 if(!m_trigger_down && m_group_count >= 2)
807 m_captured = m_group;
808 m_captured_count = m_group_count;
809 std::sort(m_captured.begin(), m_captured.begin() + m_captured_count);
811 m_trigger_down =
false;
815 void assign_learned(
int key)
noexcept
817 auto& c = m_memory[key];
818 c.count = int8_t(m_captured_count);
819 for(
int i = 0; i < m_captured_count; i++)
820 c.offsets[i] = int8_t(m_captured[i] - key);
824 if(inputs.memory.update_controller)
825 inputs.memory.update_controller(m_memory);
830 int build_voices(
int pitch, std::array<voice, max_voices>& v)
noexcept
833 auto add = [&](
int offset) {
836 v[n].offset = offset;
842 if(inputs.learn.value)
848 switch(inputs.mode.value)
852 const int chord = std::clamp(
int(inputs.chord.value), 0, chord_count - 1);
853 const int num = std::clamp(
int(inputs.num.value), 1,
max_voices);
856 const auto& c = *legacy_chords[chord];
857 for(
int i = 0; i < std::min(num,
int(c.size())); i++)
863 for(
int i = 0; i < std::min(num,
int(notes.size())); i++)
869 if(inputs.root.value)
871 const auto& iv = m_intervals;
872 for(
int i = 0; i < std::min(iv.count, max_intervals); i++)
875 for(
int k = 0; k < n; k++)
876 seen |= v[k].offset == iv.values[i];
883 const auto& c = m_memory[pitch & 127];
885 for(
int i = 0; i < c.count; i++)
893 invert(v, n, inputs.inversion.value);
899 static void invert(std::array<voice, max_voices>& v,
int& n,
int steps)
noexcept
903 for(
int s = 0; s < steps; s++)
906 for(
int i = 1; i < n; i++)
907 if(v[i].offset < v[lo].offset)
911 for(
int s = 0; s > steps; s--)
914 for(
int i = 1; i < n; i++)
915 if(v[i].offset > v[hi].offset)
920 for(
int r = 0; r < n; r++)
923 for(
int k = 0; k < w; k++)
924 seen |= v[k].offset == v[r].offset;
933 void handle(
const libremidi::message& m, int64_t ts)
noexcept
935 const int ch = mtk::channel_of(m);
936 switch(mtk::classify(m))
938 case mtk::midi_kind::note_on:
939 note_on(ch, m.bytes[1] & 127, m.bytes[2] & 127, ts);
941 case mtk::midi_kind::note_off:
944 const int vel = (m.bytes[0] & 0xF0) == 0x80 ? (m.bytes[2] & 127) : 0;
945 note_off(ch, m.bytes[1] & 127, vel, ts);
949 case mtk::midi_kind::control_change:
950 if(mtk::is_all_notes_off(m))
952 release_channel(ch, ts);
955 if(m.size() >= 3 && m.bytes[1] == 74)
957 m_in_timbre[ch - 1] = int16_t(m.bytes[2] & 127);
958 if(forward_expression(m, ch, -1))
962 case mtk::midi_kind::pitch_bend:
964 m_in_bend[ch - 1] = int16_t((m.bytes[2] << 7) | m.bytes[1]);
965 if(forward_expression(m, ch, -1))
968 case mtk::midi_kind::channel_pressure:
970 m_in_pressure[ch - 1] = int16_t(m.bytes[1] & 127);
971 if(forward_expression(m, ch, -1))
974 case mtk::midi_kind::poly_pressure:
975 if(m.size() >= 3 && forward_expression(m, ch, m.bytes[1] & 127))
981 outputs.midi.push_back(m);
984 void note_on(
int ch,
int pitch,
int vel, int64_t ts)
noexcept
986 if(inputs.learn.value)
987 learn_note_on(pitch);
990 if(m_tracker.holds(ch, pitch))
991 end_input(ch, pitch, 0, ts);
993 if(m_tracker.size() == 128)
995 const auto& oldest = m_tracker[0];
996 cancel_pending(oldest.channel, oldest.pitch);
998 m_tracker.begin(ch, pitch, ts, [
this](libremidi::message&& m) { emit(std::move(m)); });
1000 std::array<voice, max_voices> v{};
1001 int n = build_voices(pitch, v);
1006 for(
int i = 0; i < n; i++)
1008 const double draw = random01();
1009 const float chance = voice_factor(inputs.chance.value, v[i].index, 1.f);
1010 const float gain = voice_factor(inputs.velocity.value, v[i].index, 4.f);
1011 const int out = pitch + v[i].offset;
1012 if(draw >= chance || out < 0 || out > 127)
1015 v[kept].offset = out;
1016 v[kept].velocity = int(std::lround(vel *
double(gain)));
1022 bool down = inputs.strum_direction.value == Down;
1023 if(inputs.strum_direction.value == Alternate)
1025 down = m_alternate_down;
1026 m_alternate_down = !m_alternate_down;
1029 for(
int i = 1; i < n; i++)
1031 const voice x = v[i];
1033 while(j > 0 && (down ? v[j - 1].offset < x.offset : v[j - 1].offset > x.offset))
1041 const double strum = std::max(0.,
double(inputs.strum.value)) * m_rate;
1042 const double tension = std::clamp(
double(inputs.tension.value), -1., 1.);
1043 const double crescendo = std::clamp(
double(inputs.crescendo.value), -1., 1.);
1044 const double exponent = std::exp2(2. * tension);
1045 for(
int k = 0; k < n; k++)
1047 const double x = n > 1 ? double(k) / double(n - 1) : 0.;
1048 const double total = strum * double(n - 1);
1049 v[k].delay = int64_t(std::llround(total * std::pow(x, exponent)));
1051 = std::clamp(
int(std::lround(v[k].velocity * (1. + crescendo * x))), 1, 127);
1054 auto* h = m_tracker.find(ch, pitch);
1057 for(
int k = 0; k < n; k++)
1061 start_voice(*h, ch, v[k].offset, v[k].velocity, ts);
1066 m_now + ts + v[k].delay,
1067 libremidi::channel_events::note_on(ch, v[k].offset, v[k].velocity),
1075 void note_off(
int ch,
int pitch,
int vel, int64_t ts)
noexcept
1077 if(inputs.learn.value)
1078 learn_note_off(pitch);
1079 end_input(ch, pitch, vel, ts);
1082 bool end_input(
int ch,
int pitch,
int vel, int64_t ts)
noexcept
1084 cancel_pending(ch, pitch);
1085 return m_tracker.end(
1086 ch, pitch, ts, vel, [
this](libremidi::message&& m) { emit(std::move(m)); });
1089 void cancel_pending(
int ch,
int pitch)
noexcept
1091 const uint32_t tag = tag_of(ch, pitch);
1092 m_queue.remove_if([tag](
const auto& e) {
return e.tag == tag; });
1095 void start_voice(tracker_type::held& h,
int in_ch,
int pitch,
int vel, int64_t ts)
noexcept
1097 if(h.count >=
max_voices || pitch < 0 || pitch > 127)
1099 auto send = [
this](libremidi::message&& m) { emit(std::move(m)); };
1100 if(!inputs.mpe.value)
1102 m_tracker.play(h, in_ch, pitch, vel, ts, send);
1106 const auto ch = m_alloc.allocate(m_now + ts, [
this] {
return random01(); });
1111 const int16_t bend = m_in_bend[in_ch - 1];
1112 if(m_out_bend[*ch - 1] != bend)
1114 auto b = libremidi::channel_events::pitch_bend(*ch,
int(bend));
1116 outputs.midi.push_back(std::move(b));
1117 m_out_bend[*ch - 1] = bend;
1119 if(
const int16_t p = m_in_pressure[in_ch - 1]; m_out_pressure[*ch - 1] != p)
1121 auto b = libremidi::channel_events::aftertouch(*ch, uint8_t(p));
1123 outputs.midi.push_back(std::move(b));
1124 m_out_pressure[*ch - 1] = p;
1126 if(
const int16_t t = m_in_timbre[in_ch - 1]; m_out_timbre[*ch - 1] != t)
1128 auto b = libremidi::channel_events::control_change(*ch, 74, uint8_t(t));
1130 outputs.midi.push_back(std::move(b));
1131 m_out_timbre[*ch - 1] = t;
1133 m_mpe_voice[*ch - 1][pitch] =
true;
1134 m_tracker.play(h, *ch, pitch, vel, ts, send);
1137 void emit(libremidi::message&& m)
noexcept
1139 if(mtk::is_note_off(m))
1141 const int ch = mtk::channel_of(m);
1142 auto& mpe = m_mpe_voice[ch - 1][m.bytes[1] & 127];
1146 m_alloc.release(ch, m_now + m.timestamp);
1149 outputs.midi.push_back(std::move(m));
1155 bool forward_expression(
const libremidi::message& m,
int ch,
int pitch)
noexcept
1157 if(!inputs.mpe.value)
1161 for(
int i = 0; i < m_tracker.size(); i++)
1163 const auto& h = m_tracker[i];
1164 if(h.channel != ch || (pitch >= 0 && h.pitch != pitch))
1166 for(
int k = 0; k < h.count; k++)
1168 const auto& o = h.outputs[k];
1173 mtk::set_channel(c, o.channel);
1174 c.bytes[1] = o.pitch;
1175 outputs.midi.push_back(std::move(c));
1178 if(sent & (1u << (o.channel - 1)))
1180 sent |= uint16_t(1u << (o.channel - 1));
1182 mtk::set_channel(c, o.channel);
1183 switch(mtk::classify(c))
1185 case mtk::midi_kind::pitch_bend:
1186 m_out_bend[o.channel - 1] = m_in_bend[ch - 1];
1188 case mtk::midi_kind::channel_pressure:
1189 m_out_pressure[o.channel - 1] = m_in_pressure[ch - 1];
1191 case mtk::midi_kind::control_change:
1192 m_out_timbre[o.channel - 1] = m_in_timbre[ch - 1];
1197 outputs.midi.push_back(std::move(c));
1205 void release_channel(
int ch, int64_t ts)
noexcept
1207 if(inputs.mpe.value)
1210 m_tracker.end_all(ts, [
this](libremidi::message&& m) { emit(std::move(m)); });
1213 std::array<uint8_t, 128> pitches{};
1215 for(
int i = 0; i < m_tracker.size() && n < 128; i++)
1216 if(m_tracker[i].channel == ch)
1217 pitches[n++] = m_tracker[i].pitch;
1218 for(
int i = 0; i < n; i++)
1219 end_input(ch, pitches[i], 0, ts);
1222 void release_queue(
int upto)
noexcept
1224 m_queue.release(m_now, upto, [
this](libremidi::message&& m, uint32_t tag) {
1227 const int ch = int((tag - 1) >> 7) + 1;
1228 const int pitch = int((tag - 1) & 127);
1229 if(
auto* h = m_tracker.find(ch, pitch))
1230 start_voice(*h, ch, m.bytes[1], m.bytes[2], m.timestamp);
1234 void panic(int64_t ts)
noexcept
1237 m_tracker.end_all(ts, [
this](libremidi::message&& m) { emit(std::move(m)); });
1240 m_alternate_down =
false;
1245 tracker_type m_tracker;
1246 mtk::event_queue<512> m_queue;
1247 mtk::channel_allocator m_alloc;
1248 int_list<max_intervals> m_intervals;
1251 std::array<char, max_memory_text> m_memory_text{};
1252 std::size_t m_memory_size{};
1253 bool m_memory_parsed{};
1255 std::array<bool, 128> m_learn_down{};
1256 int m_learn_down_count{};
1257 std::array<int, max_voices> m_group{};
1258 int m_group_count{};
1259 std::array<int, max_voices> m_captured{};
1260 int m_captured_count{};
1261 bool m_trigger_down{};
1263 std::array<std::array<bool, 128>, 16> m_mpe_voice{};
1264 std::array<int16_t, 16> m_in_bend{8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192,
1265 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192};
1266 std::array<int16_t, 16> m_out_bend{m_in_bend};
1269 std::array<int16_t, 16> m_in_pressure{};
1270 std::array<int16_t, 16> m_out_pressure{};
1271 std::array<int16_t, 16> m_in_timbre{64, 64, 64, 64, 64, 64, 64, 64,
1272 64, 64, 64, 64, 64, 64, 64, 64};
1273 std::array<int16_t, 16> m_out_timbre{m_in_timbre};
1275 double m_rate{48000.};
1278 uint64_t m_free_seed{};
1280 bool m_alternate_down{};
1281 bool m_stop_requested{};