Chord.hpp
1 #pragma once
2 
3 #include <Engine/Node/SimpleApi.hpp>
4 
5 #include <deque>
6 namespace Nodes
7 {
8 namespace Chord
9 {
10 struct Node
11 {
13  {
14  static const constexpr auto prettyName = "Chord";
15  static const constexpr auto objectKey = "Chord";
16  static const constexpr auto category = "Midi";
17  static const constexpr auto author = "ossia score";
18  static const constexpr auto tags = std::array<const char*, 0>{};
19  static const constexpr auto kind = Process::ProcessCategory::Mapping;
20  static const constexpr auto description = "Generate a chord from a single note";
21  static const uuid_constexpr auto uuid
22  = make_uuid("F0904279-EA26-48DB-B0DF-F68FE3091DA1");
23 
24  static const constexpr midi_in midi_ins[]{"in"};
25  static const constexpr midi_out midi_outs[]{"out"};
26  static const constexpr auto controls = tuplet::make_tuple(
27  Control::IntSlider{"Num. Notes", 1, 5, 3},
28  Control::make_enum(
29  "Chord", 0U, ossia::make_array("Maj", "Min", "Sus2", "Sus4", "Dim", "Aug")));
30  };
31 
32  struct State
33  {
34  struct chord
35  {
36  std::string ch{};
37  int notes{};
38  };
39  ossia::flat_map<uint8_t, std::vector<chord>> chords;
40  };
41 
42  enum Chord
43  {
44  I,
45  II,
46  III,
47  IV,
48  V,
49  VI,
50  VII
51  };
52 
53  using control_policy = ossia::safe_nodes::default_tick_controls;
54  // C C# D D# E F F# G G# A A# B
55  // 1 . . . 1 . . 1 . 1 . .
56  static const constexpr std::array<int, 5> major7{0, 4, 7, 11, 12};
57  static const constexpr std::array<int, 5> minor7{0, 3, 7, 10, 12};
58  static const constexpr std::array<int, 5> sus2{0, 2, 7, 9, 12};
59  static const constexpr std::array<int, 5> sus4{0, 5, 7, 9, 12};
60  static const constexpr std::array<int, 5> dim{0, 3, 6, 9, 12};
61  static const constexpr std::array<int, 5> aug{0, 4, 8, 10, 12};
62 
63  template <typename T>
64  static void startChord(
65  const T& chord, const libremidi::message& m, const std::size_t num,
66  ossia::midi_port& op)
67  {
68  for(std::size_t i = 0; i < std::min(num, chord.size()); i++)
69  {
70  auto new_note = m.bytes[1] + chord[i];
71  if(new_note > 127)
72  break;
73 
74  auto non
75  = libremidi::channel_events::note_on(m.get_channel(), new_note, m.bytes[2]);
76  non.timestamp = m.timestamp;
77  op.messages.push_back(non);
78  }
79  }
80 
81  template <typename T>
82  static void stopChord(
83  const T& chord, const libremidi::message& m, const std::size_t num,
84  ossia::midi_port& op)
85  {
86  for(std::size_t i = 0; i < std::min(num, chord.size()); i++)
87  {
88  auto new_note = m.bytes[1] + chord[i];
89  if(new_note > 127)
90  break;
91 
92  auto noff
93  = libremidi::channel_events::note_off(m.get_channel(), new_note, m.bytes[2]);
94  noff.timestamp = m.timestamp;
95  op.messages.push_back(noff);
96  }
97  }
98 
99  template <typename F>
100  static void dispatchChord(
101  std::string_view chord, const libremidi::message& m, int num, ossia::midi_port& op,
102  F&& f)
103  {
104  static const ossia::string_view_map<std::array<int, 5>> chords{
105  {"Maj", major7}, {"Min", minor7}, {"Sus2", sus2},
106  {"Sus4", sus4}, {"Dim", dim}, {"Aug", aug}};
107  auto it = chords.find(chord);
108  if(it != chords.end())
109  f(it->second, m, num, op);
110  }
111  static void
112  run(const ossia::midi_port& ip, const ossia::timed_vec<int>& num,
113  const ossia::timed_vec<std::string>& chord, ossia::midi_port& op,
114  ossia::token_request tk, ossia::exec_state_facade st, State& self)
115  {
116  for(const libremidi::message& m : ip.messages)
117  {
118  auto lastNum = num.rbegin()->second;
119  const auto& lastCh = chord.rbegin()->second;
120 
121  if(m.get_message_type() == libremidi::message_type::NOTE_ON)
122  {
123  auto cur = m.bytes[1];
124  self.chords[cur].push_back({lastCh, lastNum});
125  dispatchChord(
126  lastCh, m, lastNum, op, [](auto&&... args) { startChord(args...); });
127  }
128  else if(m.get_message_type() == libremidi::message_type::NOTE_OFF)
129  {
130  auto it = self.chords.find(m.bytes[1]);
131  if(it != self.chords.end())
132  {
133  for(const State::chord& chord : it->second)
134  {
135  dispatchChord(chord.ch, m, chord.notes, op, [](auto&&... args) {
136  stopChord(args...);
137  });
138  }
139  const_cast<std::vector<State::chord>&>(it->second).clear();
140  }
141  }
142  else
143  {
144  // just forward
145  op.messages.push_back(m);
146  }
147  }
148  }
149 };
150 }
151 }
Utilities for OSSIA data structures.
Definition: DeviceInterface.hpp:33
Definition: score-lib-process/Control/Widgets.hpp:178
Definition: SimpleApi.hpp:32
Definition: Chord.hpp:13
Definition: Chord.hpp:35
Definition: Chord.hpp:11