Arpeggiator.hpp
1 #pragma once
2 #include <ossia/detail/flat_map.hpp>
3 #include <ossia/detail/small_vector.hpp>
4 
5 #include <halp/audio.hpp>
6 #include <halp/controls.hpp>
7 #include <halp/meta.hpp>
8 #include <halp/midi.hpp>
9 #include <libremidi/message.hpp>
10 namespace Nodes
11 {
12 template <typename T>
13 static void duplicate_vector(T& vec)
14 {
15  const int N = vec.size();
16  vec.reserve(N * 2);
17  for(int i = 0; i < N; i++)
18  vec.push_back(vec[i]);
19 }
20 namespace Arpeggiator
21 {
22 struct Arpeggios
23 {
24  halp_meta(name, "Arpeggios");
25  enum widget
26  {
27  combobox
28  };
29 
30  struct range
31  {
32  halp::combo_pair<int> values[5]{{"Forward", 0}, {"Backward", 1}, {"F->B", 2}, {"B->F", 3}, {"Chord", 4}};
33  int init{0};
34  };
35 
36  int value{};
37 };
38 
39 struct Node
40 {
41  halp_meta(name, "Arpeggiator")
42  halp_meta(c_name, "Arpeggiator")
43  halp_meta(category, "Midi")
44  halp_meta(author, "ossia score")
45  halp_meta(
46  manual_url,
47  "https://ossia.io/score-docs/processes/midi-utilities.html#arpeggiator")
48  halp_meta(description, "Arpeggiator")
49  halp_meta(uuid, "0b98c7cd-f831-468f-81e3-706d6a97d705")
50 
51  // FIXME "note" bus instead of midi bus ; the host handles passing all the non note messages
52  struct
53  {
54  halp::midi_bus<"in", libremidi::message> midi;
55  Arpeggios arpeggios;
56  halp::hslider_i32<"Octave", halp::irange{1, 7, 1}> octave;
57  halp::hslider_i32<"Quantification", halp::irange{1, 32, 8}> quantification;
58  } inputs;
59  struct
60  {
61  halp::midi_out_bus<"out", libremidi::message> midi;
62  } outputs;
63 
64  using byte = unsigned char;
65  using chord = ossia::small_vector<std::pair<byte, byte>, 5>;
66 
67  ossia::flat_map<byte, byte> notes;
68  ossia::small_vector<chord, 10> arpeggio;
69  std::array<int8_t, 128> in_flight{};
70 
71  float previous_octave{};
72  int previous_arpeggio{};
73  std::size_t index{};
74 
75  void update()
76  {
77  // Create the content of the arpeggio
78  switch(previous_arpeggio)
79  {
80  case 0:
81  arpeggiate(1);
82  break;
83  case 1:
84  arpeggiate(1);
85  std::reverse(arpeggio.begin(), arpeggio.end());
86  break;
87  case 2:
88  arpeggiate(2);
89  duplicate_vector(arpeggio);
90  std::reverse(arpeggio.begin() + notes.size(), arpeggio.end());
91  break;
92  case 3:
93  arpeggiate(2);
94  duplicate_vector(arpeggio);
95  std::reverse(arpeggio.begin(), arpeggio.begin() + notes.size());
96  break;
97  case 4:
98  arpeggio.clear();
99  arpeggio.resize(1);
100  for(std::pair note : notes)
101  {
102  arpeggio[0].push_back(note);
103  }
104  break;
105  }
106 
107  const std::size_t orig_size = arpeggio.size();
108 
109  // Create the octave duplicates
110  for(int i = 1; i < previous_octave; i++)
111  {
112  octavize(orig_size, i);
113  }
114  for(int i = 1; i < previous_octave; i++)
115  {
116  octavize(orig_size, -i);
117  }
118  }
119 
120  void arpeggiate(int size_mult)
121  {
122  arpeggio.clear();
123  arpeggio.reserve(notes.size() * size_mult);
124  for(std::pair note : notes)
125  {
126  arpeggio.push_back(chord{note});
127  }
128  }
129 
130  void octavize(std::size_t orig_size, int i)
131  {
132  for(std::size_t j = 0; j < orig_size; j++)
133  {
134  auto copy = arpeggio[j];
135  for(auto it = copy.begin(); it != copy.end();)
136  {
137  auto& note = *it;
138  int res = note.first + 12 * i;
139  if(res >= 0.f && res <= 127.f)
140  {
141  note.first = res;
142  ++it;
143  }
144  else
145  {
146  it = copy.erase(it);
147  }
148  }
149 
150  arpeggio.push_back(std::move(copy));
151  }
152  }
153 
154  using tick = halp::tick_musical;
155  void operator()(const halp::tick_musical& tk)
156  {
157  // Store the current chord in a buffer
158  auto& self = *this;
159  auto& midi = this->inputs.midi;
160  auto& out = this->outputs.midi;
161  const auto& msgs = midi;
162  const int octave = inputs.octave;
163  const int arpeggio = inputs.arpeggios.value;
164  self.previous_octave = octave;
165  self.previous_arpeggio = arpeggio;
166 
167  if(msgs.size() > 0)
168  {
169  // Update the "running" notes
170  for(auto& note : msgs)
171  {
172  if(note.get_message_type() == libremidi::message_type::NOTE_ON)
173  {
174  self.notes.insert({note.bytes[1], note.bytes[2]});
175  }
176  else if(note.get_message_type() == libremidi::message_type::NOTE_OFF)
177  {
178  self.notes.erase(note.bytes[1]);
179  }
180  }
181  }
182 
183  // Update the arpeggio itself
184  const bool mustUpdateArpeggio = msgs.size() > 0 || octave != self.previous_octave || arpeggio != self.previous_arpeggio;
185  if(mustUpdateArpeggio)
186  {
187  self.update();
188  }
189 
190  if(self.arpeggio.empty())
191  {
192  for(int k = 0; k < 128; k++)
193  {
194  while(self.in_flight[k] > 0)
195  {
196  out.note_off(1, k, 0).timestamp = 0;
197  self.in_flight[k]--;
198  }
199  }
200  return;
201  }
202 
203  if(self.index >= self.arpeggio.size())
204  self.index = 0;
205 
206  // Play the next note / chord if we're on a quantification marker
207  // FIXME use the one
208  for(auto [date, q] : tk.get_quantification_date_with_bars(inputs.quantification.value))
209  {
210  // SCORE_SOFT_ASSERT(date >= 0);
211  // SCORE_SOFT_ASSERT(date < tk.frames);
212 
213  if(date >= tk.frames)
214  return;
215 
216  // Finish previous notes
217  for(int k = 0; k < 128; k++)
218  {
219  while(self.in_flight[k] > 0)
220  {
221  out.note_off(1, k, 0).timestamp = date;
222  self.in_flight[k]--;
223  }
224  }
225 
226  // Start the next note in the chord
227  auto& chord = self.arpeggio[self.index];
228 
229  for(auto& note : chord)
230  {
231  self.in_flight[note.first]++;
232  out.note_on(1, note.first, note.second).timestamp = date;
233  }
234 
235  // New chord to stop
236  self.index = (self.index + 1) % (self.arpeggio.size());
237  }
238  }
239 };
240 }
241 }
Definition: Arpeggiator.hpp:31
Definition: Arpeggiator.hpp:23
Definition: Arpeggiator.hpp:40