Loading...
Searching...
No Matches
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#include <rnd/random.hpp>
11
12namespace Nodes
13{
14template <typename T>
15static void duplicate_vector(T& vec)
16{
17 const int N = vec.size();
18 vec.reserve(N * 2);
19 for(int i = 0; i < N; i++)
20 vec.push_back(vec[i]);
21}
22namespace Arpeggiator
23{
25{
26 halp_meta(name, "Arpeggios");
27 enum widget
28 {
29 combobox
30 };
31
32 struct range
33 {
34 halp::combo_pair<int> values[6]{{"Forward", 0}, {"Backward", 1}, {"F->B", 2}, {"B->F", 3}, {"Chord", 4}, {"Random", 5}};
35 int init{0};
36 };
37
38 int value{};
39};
40
42{
43 halp_meta(name, "Octave mode");
44 enum widget
45 {
46 combobox
47 };
48
49 struct range
50 {
51 halp::combo_pair<int> values[3]{{"Both", 0}, {"Above", 1}, {"Below", 2}};
52 int init{0};
53 };
54
55 int value{};
56};
57
58struct Node
59{
60 halp_meta(name, "Arpeggiator")
61 halp_meta(c_name, "Arpeggiator")
62 halp_meta(category, "Midi")
63 halp_meta(author, "ossia score")
64 halp_meta(
65 manual_url,
66 "https://ossia.io/score-docs/processes/midi-utilities.html#arpeggiator")
67 halp_meta(description, "Arpeggiator")
68 halp_meta(uuid, "0b98c7cd-f831-468f-81e3-706d6a97d705")
69
70 // FIXME "note" bus instead of midi bus ; the host handles passing all the non note messages
71 struct
72 {
73 halp::midi_bus<"in", libremidi::message> midi;
74 Arpeggios arpeggios;
75 halp::hslider_i32<"Octave", halp::irange{1, 7, 1}> octave;
76 OctaveMode octave_mode;
77 halp::hslider_i32<"Repeat", halp::irange{1, 8, 1}> repeat;
78 halp::hslider_i32<"Quantification", halp::irange{1, 32, 8}> quantification;
79 } inputs;
80 struct
81 {
82 halp::midi_out_bus<"out", libremidi::message> midi;
83 } outputs;
84
85 using byte = unsigned char;
86 using chord = ossia::small_vector<std::pair<byte, byte>, 5>;
87
88 ossia::flat_map<byte, byte> notes;
89 ossia::small_vector<chord, 10> arpeggio;
90 std::array<int8_t, 128> in_flight{};
91
92 int previous_octave{};
93 int previous_octave_mode{};
94 int previous_repeat{};
95 int previous_arpeggio{};
96 std::size_t index{};
97 rnd::pcg rng{[] {
98 std::random_device d{};
99 rnd::pcg r(d);
100 return r;
101 }()};
102
103 void update()
104 {
105 // Create the content of the arpeggio
106 switch(previous_arpeggio)
107 {
108 case 0: // Forward
109 arpeggiate(1);
110 break;
111 case 1: // Backward
112 arpeggiate(1);
113 std::reverse(arpeggio.begin(), arpeggio.end());
114 break;
115 case 2: // F->B
116 arpeggiate(2);
117 duplicate_vector(arpeggio);
118 std::reverse(arpeggio.begin() + notes.size(), arpeggio.end());
119 break;
120 case 3: // B->F
121 arpeggiate(2);
122 duplicate_vector(arpeggio);
123 std::reverse(arpeggio.begin(), arpeggio.begin() + notes.size());
124 break;
125 case 4: // Chord - all notes play simultaneously, octaves expand the chord
126 {
127 arpeggio.clear();
128 arpeggio.resize(1);
129 for(std::pair note : notes)
130 {
131 arpeggio[0].push_back(note);
132 // Add octave duplicates directly into the chord
133 for(int i = 1; i < previous_octave; i++)
134 {
135 // 0 = Both, 1 = Above, 2 = Below
136 if(previous_octave_mode != 2) // Above or Both
137 {
138 int up = note.first + 12 * i;
139 if(up <= 127)
140 arpeggio[0].push_back({static_cast<byte>(up), note.second});
141 }
142 if(previous_octave_mode != 1) // Below or Both
143 {
144 int down = note.first - 12 * i;
145 if(down >= 0)
146 arpeggio[0].push_back({static_cast<byte>(down), note.second});
147 }
148 }
149 }
150 return; // Skip normal octavize and repeat for chord mode
151 }
152 case 5: // Random - note selection happens in operator()
153 arpeggiate(1);
154 break;
155 }
156
157 // Apply repeat: duplicate each step N times
158 if(previous_repeat > 1)
159 {
160 decltype(arpeggio) repeated;
161 repeated.reserve(arpeggio.size() * previous_repeat);
162 for(auto& c : arpeggio)
163 {
164 for(int r = 0; r < previous_repeat; r++)
165 repeated.push_back(c);
166 }
167 arpeggio = std::move(repeated);
168 }
169
170 const std::size_t orig_size = arpeggio.size();
171
172 // Create the octave duplicates based on octave mode
173 // 0 = Both, 1 = Above, 2 = Below
174 if(previous_octave_mode != 2) // Above or Both
175 {
176 for(int i = 1; i < previous_octave; i++)
177 octavize(orig_size, i);
178 }
179 if(previous_octave_mode != 1) // Below or Both
180 {
181 for(int i = 1; i < previous_octave; i++)
182 octavize(orig_size, -i);
183 }
184 }
185
186 void arpeggiate(int size_mult)
187 {
188 arpeggio.clear();
189 arpeggio.reserve(notes.size() * size_mult);
190 for(std::pair note : notes)
191 {
192 arpeggio.push_back(chord{note});
193 }
194 }
195
196 void octavize(std::size_t orig_size, int i)
197 {
198 for(std::size_t j = 0; j < orig_size; j++)
199 {
200 auto copy = arpeggio[j];
201 for(auto it = copy.begin(); it != copy.end();)
202 {
203 auto& note = *it;
204 int res = note.first + 12 * i;
205 if(res >= 0.f && res <= 127.f)
206 {
207 note.first = res;
208 ++it;
209 }
210 else
211 {
212 it = copy.erase(it);
213 }
214 }
215
216 arpeggio.push_back(std::move(copy));
217 }
218 }
219
220 using tick = halp::tick_musical;
221 void operator()(const halp::tick_musical& tk)
222 {
223 // Store the current chord in a buffer
224 auto& self = *this;
225 auto& midi = this->inputs.midi;
226 auto& out = this->outputs.midi;
227 const auto& msgs = midi;
228 const int octave = inputs.octave;
229 const int octave_mode = inputs.octave_mode.value;
230 const int repeat = inputs.repeat;
231 const int arpeggio_mode = inputs.arpeggios.value;
232
233 if(msgs.size() > 0)
234 {
235 // Update the "running" notes
236 for(auto& note : msgs)
237 {
238 if(note.get_message_type() == libremidi::message_type::NOTE_ON)
239 {
240 self.notes.insert({note.bytes[1], note.bytes[2]});
241 }
242 else if(note.get_message_type() == libremidi::message_type::NOTE_OFF)
243 {
244 self.notes.erase(note.bytes[1]);
245 }
246 }
247 }
248
249 // Update the arpeggio itself
250 const bool mustUpdateArpeggio = msgs.size() > 0 || octave != self.previous_octave
251 || octave_mode != self.previous_octave_mode
252 || repeat != self.previous_repeat
253 || arpeggio_mode != self.previous_arpeggio;
254 self.previous_octave = octave;
255 self.previous_octave_mode = octave_mode;
256 self.previous_repeat = repeat;
257 self.previous_arpeggio = arpeggio_mode;
258
259 if(mustUpdateArpeggio)
260 {
261 self.update();
262 }
263
264 if(self.arpeggio.empty())
265 {
266 for(int k = 0; k < 128; k++)
267 {
268 while(self.in_flight[k] > 0)
269 {
270 out.note_off(1, k, 0).timestamp = 0;
271 self.in_flight[k]--;
272 }
273 }
274 return;
275 }
276
277 if(self.index >= self.arpeggio.size())
278 self.index = 0;
279
280 // Play the next note / chord if we're on a quantification marker
281 for(auto [date, q] :
282 tk.get_quantification_date_with_bars(inputs.quantification.value))
283 {
284 if(date >= tk.frames)
285 return;
286
287 // Finish previous notes
288 for(int k = 0; k < 128; k++)
289 {
290 while(self.in_flight[k] > 0)
291 {
292 out.note_off(1, k, 0).timestamp = date;
293 self.in_flight[k]--;
294 }
295 }
296
297 // Select the next index: random for Random mode, sequential otherwise
298 std::size_t play_index;
299 if(arpeggio_mode == 5) // Random
300 {
301 std::uniform_int_distribution<std::size_t> dist(0, self.arpeggio.size() - 1);
302 play_index = dist(rng);
303 }
304 else
305 {
306 play_index = self.index;
307 self.index = (self.index + 1) % self.arpeggio.size();
308 }
309
310 // Start the next note in the chord
311 auto& chord = self.arpeggio[play_index];
312
313 for(auto& note : chord)
314 {
315 self.in_flight[note.first]++;
316 out.note_on(1, note.first, note.second).timestamp = date;
317 }
318 }
319 }
320};
321}
322}
Definition Arpeggiator.hpp:33
Definition Arpeggiator.hpp:25
Definition Arpeggiator.hpp:59
Definition Arpeggiator.hpp:50
Definition Arpeggiator.hpp:42