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>
10namespace Nodes
11{
12template <typename T>
13static 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}
20namespace Arpeggiator
21{
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
39struct 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