Arpeggiator.hpp
1 #pragma once
2 #include <Engine/Node/SimpleApi.hpp>
3 
4 #include <ossia/detail/flat_map.hpp>
5 
6 namespace Nodes
7 {
8 template <typename T>
9 static void duplicate_vector(T& vec)
10 {
11  const int N = vec.size();
12  vec.reserve(N * 2);
13  for(int i = 0; i < N; i++)
14  vec.push_back(vec[i]);
15 }
16 namespace Arpeggiator
17 {
18 struct Node
19 {
21  {
22  static const constexpr auto prettyName = "Arpeggiator";
23  static const constexpr auto objectKey = "Arpeggiator";
24  static const constexpr auto category = "Midi";
25  static const constexpr auto author = "ossia score";
26  static const constexpr auto tags = std::array<const char*, 0>{};
27  static const constexpr auto kind = Process::ProcessCategory::MidiEffect;
28  static const constexpr auto description = "Arpeggiator";
29  static const uuid_constexpr auto uuid
30  = make_uuid("0b98c7cd-f831-468f-81e3-706d6a97d705");
31 
32  static const constexpr midi_in midi_ins[]{"in"};
33  static const constexpr midi_out midi_outs[]{"out"};
34  static const constexpr auto controls = tuplet::make_tuple(
35  Control::Widgets::ArpeggioChooser(), Control::IntSlider("Octave", 1, 7, 1),
36  Control::IntSlider("Quantification", 1, 32, 8));
37  };
38 
39  using byte = unsigned char;
40  using chord = ossia::small_vector<std::pair<byte, byte>, 5>;
41 
42  struct State
43  {
44  ossia::flat_map<byte, byte> notes;
45  ossia::small_vector<chord, 10> arpeggio;
46  std::array<int8_t, 128> in_flight{};
47 
48  float previous_octave{};
49  int previous_arpeggio{};
50  std::size_t index{};
51 
52  void update()
53  {
54  // Create the content of the arpeggio
55  switch(previous_arpeggio)
56  {
57  case 0:
58  arpeggiate(1);
59  break;
60  case 1:
61  arpeggiate(1);
62  std::reverse(arpeggio.begin(), arpeggio.end());
63  break;
64  case 2:
65  arpeggiate(2);
66  duplicate_vector(arpeggio);
67  std::reverse(arpeggio.begin() + notes.size(), arpeggio.end());
68  break;
69  case 3:
70  arpeggiate(2);
71  duplicate_vector(arpeggio);
72  std::reverse(arpeggio.begin(), arpeggio.begin() + notes.size());
73  break;
74  case 4:
75  arpeggio.clear();
76  arpeggio.resize(1);
77  for(std::pair note : notes)
78  {
79  arpeggio[0].push_back(note);
80  }
81  break;
82  }
83 
84  const std::size_t orig_size = arpeggio.size();
85 
86  // Create the octave duplicates
87  for(int i = 1; i < previous_octave; i++)
88  {
89  octavize(orig_size, i);
90  }
91  for(int i = 1; i < previous_octave; i++)
92  {
93  octavize(orig_size, -i);
94  }
95  }
96 
97  void arpeggiate(int size_mult)
98  {
99  arpeggio.clear();
100  arpeggio.reserve(notes.size() * size_mult);
101  for(std::pair note : notes)
102  {
103  arpeggio.push_back(chord{note});
104  }
105  }
106 
107  void octavize(std::size_t orig_size, int i)
108  {
109  for(std::size_t j = 0; j < orig_size; j++)
110  {
111  auto copy = arpeggio[j];
112  for(auto it = copy.begin(); it != copy.end();)
113  {
114  auto& note = *it;
115  int res = note.first + 12 * i;
116  if(res >= 0.f && res <= 127.f)
117  {
118  note.first = res;
119  ++it;
120  }
121  else
122  {
123  it = copy.erase(it);
124  }
125  }
126 
127  arpeggio.push_back(std::move(copy));
128  }
129  }
130  };
131 
132  using control_policy = ossia::safe_nodes::precise_tick;
133  static void
134  run(const ossia::midi_port& midi, int arpeggio, float octave, int quantif,
135  ossia::midi_port& out, ossia::token_request tk, ossia::exec_state_facade st,
136  State& self)
137  {
138  // Store the current chord in a buffer
139  auto msgs = midi.messages;
140  self.previous_octave = octave;
141  self.previous_arpeggio = arpeggio;
142 
143  if(msgs.size() > 0)
144  {
145  // Update the "running" notes
146  for(auto& note : msgs)
147  {
148  if(note.get_message_type() == libremidi::message_type::NOTE_ON)
149  {
150  self.notes.insert({note.bytes[1], note.bytes[2]});
151  }
152  else if(note.get_message_type() == libremidi::message_type::NOTE_OFF)
153  {
154  self.notes.erase(note.bytes[1]);
155  }
156  }
157  }
158 
159  // Update the arpeggio itself
160  const bool mustUpdateArpeggio = msgs.size() > 0 || octave != self.previous_octave
161  || arpeggio != self.previous_arpeggio;
162  if(mustUpdateArpeggio)
163  {
164  self.update();
165  }
166 
167  if(self.arpeggio.empty())
168  {
169  const auto [tick_start, d] = st.timings(tk);
170  for(int k = 0; k < 128; k++)
171  {
172  while(self.in_flight[k] > 0)
173  {
174  out.note_off(1, k, 0).timestamp = tick_start;
175  self.in_flight[k]--;
176  }
177  }
178  return;
179  }
180 
181  if(self.index >= self.arpeggio.size())
182  self.index = 0;
183 
184  // Play the next note / chord if we're on a quantification marker
185  if(auto date = tk.get_physical_quantification_date(quantif, st.modelToSamples()))
186  {
187  // Finish previous notes
188 
189  for(int k = 0; k < 128; k++)
190  {
191  while(self.in_flight[k] > 0)
192  {
193  out.note_off(1, k, 0).timestamp = *date;
194  self.in_flight[k]--;
195  }
196  }
197 
198  // Start the next note in the chord
199  auto& chord = self.arpeggio[self.index];
200 
201  for(auto& note : chord)
202  {
203  self.in_flight[note.first]++;
204  out.note_on(1, note.first, note.second).timestamp = *date;
205  }
206 
207  // New chord to stop
208  self.index = (self.index + 1) % (self.arpeggio.size());
209  }
210  }
211 };
212 }
213 }
Utilities for OSSIA data structures.
Definition: DeviceInterface.hpp:33
Definition: score-lib-process/Control/Widgets.hpp:178
Definition: SimpleApi.hpp:32
Definition: Arpeggiator.hpp:21
Definition: Arpeggiator.hpp:19