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