Chord.hpp
1 #pragma once
2 #include <ossia/detail/string_map.hpp>
3 
4 #include <halp/audio.hpp>
5 #include <halp/controls.hpp>
6 #include <halp/meta.hpp>
7 #include <halp/midi.hpp>
8 #include <libremidi/message.hpp>
9 
10 namespace Nodes
11 {
12 namespace Chord
13 {
14 struct Node
15 {
16  halp_meta(name, "Chord")
17  halp_meta(c_name, "Chord")
18  halp_meta(category, "Midi")
19  halp_meta(author, "ossia score")
20  halp_meta(manual_url, "https://ossia.io/score-docs/processes/midi-utilities.html#chord")
21  halp_meta(description, "Generate a chord from a single note")
22  halp_meta(uuid, "F0904279-EA26-48DB-B0DF-F68FE3091DA1");
23 
24  enum Chord
25  {
26  Maj,
27  Min,
28  Sus2,
29  Sus4,
30  Dim,
31  Aug
32  };
33  using midi_out = halp::midi_out_bus<"out", libremidi::message>;
34  struct
35  {
36  halp::midi_bus<"in", libremidi::message> midi;
37  halp::hslider_i32<"Num. Notes", halp::irange{1, 5, 3}> num;
38  halp::enum_t<Chord, "Chord"> chord;
39  } inputs;
40  struct
41  {
42  midi_out midi;
43  } outputs;
44  struct chord_type
45  {
46  std::string_view ch{};
47  int notes{};
48  };
49  ossia::flat_map<uint8_t, std::vector<chord_type>> chords;
50 
51  // C C# D D# E F F# G G# A A# B
52  // 1 . . . 1 . . 1 . 1 . .
53  static const constexpr std::array<int, 5> major7{0, 4, 7, 11, 12};
54  static const constexpr std::array<int, 5> minor7{0, 3, 7, 10, 12};
55  static const constexpr std::array<int, 5> sus2{0, 2, 7, 9, 12};
56  static const constexpr std::array<int, 5> sus4{0, 5, 7, 9, 12};
57  static const constexpr std::array<int, 5> dim{0, 3, 6, 9, 12};
58  static const constexpr std::array<int, 5> aug{0, 4, 8, 10, 12};
59 
60  template <typename T>
61  static void startChord(const T& chord, const libremidi::message& m, const std::size_t num, midi_out& op)
62  {
63  for(std::size_t i = 0; i < std::min(num, chord.size()); i++)
64  {
65  auto new_note = m.bytes[1] + chord[i];
66  if(new_note > 127)
67  break;
68 
69  op.note_on(m.get_channel(), new_note, m.bytes[2]).timestamp = m.timestamp;
70  }
71  }
72 
73  template <typename T>
74  static void stopChord(const T& chord, const libremidi::message& m, const std::size_t num, midi_out& op)
75  {
76  for(std::size_t i = 0; i < std::min(num, chord.size()); i++)
77  {
78  auto new_note = m.bytes[1] + chord[i];
79  if(new_note > 127)
80  break;
81 
82  op.note_off(m.get_channel(), new_note, m.bytes[2]).timestamp = m.timestamp;
83  }
84  }
85 
86  template <typename F>
87  static void dispatchChord(std::string_view chord, const libremidi::message& m, int num, midi_out& op, F&& f)
88  {
89  static const ossia::string_view_map<std::array<int, 5>> chords{{"Maj", major7}, {"Min", minor7}, {"Sus2", sus2}, {"Sus4", sus4}, {"Dim", dim}, {"Aug", aug}};
90  auto it = chords.find(chord);
91  if(it != chords.end())
92  f(it->second, m, num, op);
93  }
94 
95  // FIXME here we want precise ticking, e.g. call for each change in any parameter
96  using tick = halp::tick_musical;
97  void operator()(const halp::tick_musical& tk)
98  {
99  for(const libremidi::message& m : inputs.midi)
100  {
101  int lastNum = inputs.num;
102  std::string_view lastCh = magic_enum::enum_name<Chord>(inputs.chord);
103  if(m.get_message_type() == libremidi::message_type::NOTE_ON)
104  {
105  auto cur = m.bytes[1];
106  this->chords[cur].push_back({lastCh, lastNum});
107  dispatchChord(lastCh, m, lastNum, outputs.midi, [](auto&&... args) { startChord(args...); });
108  }
109  else if(m.get_message_type() == libremidi::message_type::NOTE_OFF)
110  {
111  auto it = this->chords.find(m.bytes[1]);
112  if(it != this->chords.end())
113  {
114  for(const auto& chord : it->second)
115  {
116  dispatchChord(chord.ch, m, chord.notes, outputs.midi, [](auto&&... args) { stopChord(args...); });
117  }
118  const_cast<std::vector<chord_type>&>(it->second).clear();
119  }
120  }
121  else
122  {
123  // just forward
124  outputs.midi.push_back(m);
125  }
126  }
127  }
128 };
129 }
130 }
Definition: Chord.hpp:45
Definition: Chord.hpp:15