Loading...
Searching...
No Matches
VelToNote.hpp
1#pragma once
2#include <Fx/Types.hpp>
3
4#include <ossia/dataflow/value_port.hpp>
5#include <ossia/detail/math.hpp>
6
7#include <halp/controls.hpp>
8#include <halp/meta.hpp>
9#include <halp/midi.hpp>
10#include <libremidi/message.hpp>
11#include <rnd/random.hpp>
12
13#include <iostream>
14inline std::ostream&
15operator<<(std::ostream& s, decltype(halp::tick_musical::signature) sig)
16{
17 return s << sig.num << " / " << sig.denom;
18}
19namespace Nodes
20{
21namespace PulseToNote
22{
23struct Node
24{
25 halp_meta(name, "Pulse to Midi")
26 halp_meta(c_name, "VelToNote")
27 halp_meta(category, "Midi")
28 halp_meta(author, "ossia score")
29 halp_meta(manual_url, "https://ossia.io/score-docs/processes/midi-utilities.html#pulse-to-note")
30 halp_meta(
31 description,
32 "Converts a message into MIDI.\n"
33 "If the input is an impulse, the output will be the default pitch "
34 "at the default velocity.\n"
35 "If the input is a single integer in [0; 127], the output will be "
36 "the relevant note at the default velocity"
37 "If the input is an array of two values between [0; 127], the "
38 "output will be the relevant note.")
39
40 halp_meta(uuid, "2c6493c3-5449-4e52-ae04-9aee3be5fb6a")
41
42 struct
43 {
44 // FIXME is_event
45 // FIXME all incorrect when token_request smaller than tick
46 // Implement polyphonic behaviour at the avendish level?
47 ossia_port<"in", ossia::value_port> port{};
48 quant_selector<"Start quant."> start_quant;
49 halp::hslider_f32<"Tightness", halp::range{0.f, 1.f, 0.8f}> tightness;
50 quant_selector<"End quant."> end_quant;
51 midi_spinbox<"Default pitch"> basenote;
52 midi_spinbox<"Default vel."> basevel;
53 octave_slider<"Pitch shift", -5, 5> shift_note;
54 octave_slider<"Pitch random", 0, 2> note_random;
55 octave_slider<"Vel. random", 0, 2> vel_random;
56 midi_channel<"Channel"> channel;
57
58 } inputs;
59 struct
60 {
61 midi_out midi;
62 } outputs;
63
64 struct Note
65 {
66 uint8_t pitch{};
67 uint8_t vel{};
68 uint8_t chan{};
69 };
70
71 struct NoteIn
72 {
73 Note note{};
74 int64_t date{};
75 bool fresh{};
76 };
77 std::vector<NoteIn> to_start;
78 std::vector<NoteIn> running_notes;
79
81 {
82 Node& st;
83 uint8_t base_note{};
84 uint8_t base_vel{};
85
86 Note operator()() { return {base_note, base_vel}; }
87 Note operator()(ossia::impulse) { return {base_note, base_vel}; }
88 template <typename T>
89 Note operator()(const T&)
90 {
91 return {base_note, base_vel};
92 }
93 Note operator()(float note) { return {(uint8_t)note, base_vel}; }
94 Note operator()(char note) { return {(uint8_t)note, base_vel}; }
95 Note operator()(int note) { return {(uint8_t)note, base_vel}; }
96 Note operator()(int note, int vel) { return {(uint8_t)note, (uint8_t)vel}; }
97 Note operator()(int note, float vel) { return {(uint8_t)note, (uint8_t)(vel * 127.f)}; }
98 Note operator()(const std::vector<ossia::value>& v)
99 {
100 switch(v.size())
101 {
102 case 0:
103 return operator()();
104 case 1:
105 return operator()(ossia::convert<int>(v[0]));
106 case 2: {
107 int note = ossia::convert<int>(v[0]);
108 switch(v[1].get_type())
109 {
110 case ossia::val_type::FLOAT:
111 return operator()(note, *v[1].v.target<float>());
112 case ossia::val_type::INT:
113 return operator()(note, *v[1].v.target<int>());
114 default:
115 return operator()(note, ossia::convert<int>(v[1]));
116 }
117 }
118 default:
119 return operator()(ossia::convert<int>(v[0]), ossia::convert<int>(v[1]));
120 }
121 }
122 template <std::size_t N>
123 Note operator()(const std::array<float, N>& v)
124 {
125 static_assert(N >= 2);
126 return operator()(v[0], v[1]);
127 }
128 };
129
130 static constexpr uint8_t midi_clamp(int num) { return (uint8_t)ossia::clamp(num, 0, 127); }
131
132 using tick = halp::tick_flicks;
133 void operator()(const tick& tk)
134 {
135 // TODO : when arrays like [ 1, 25, 12, 37, 10, 40 ] are received
136 // send relevant chords
137
138 // When a message is received, we have three cases :
139 // 1. Just an impulse: use base note & base vel
140 // 2. Just an int: it's the velocity, use base note
141 // 3. A tuple [int, int]: it's note and velocity
142
143 // Then once we have a pair [int, int] we randomize and we output a note
144 // on.
145
146 // At the end, scan running_notes: if any is going to end in this buffer,
147 // end it too.
148
149 // FIXME next version choose between these behaviours
150
151#define STOP_AT_NEXT_SAMPLE_FOR_ZERO_END_QUANT 1
152
153 const auto& start = inputs.start_quant.value;
154 // TODO double precision = tightness.rbegin()->second;
155 const auto& end = inputs.end_quant.value;
156 const auto& shiftnote = inputs.shift_note.value;
157 const auto& base_note = midi_clamp(inputs.basenote.value);
158 const auto& base_vel = midi_clamp(inputs.basevel.value);
159 const auto& rand_note = inputs.note_random.value;
160 const auto& rand_vel = inputs.vel_random.value;
161 const auto& chan = inputs.channel.value;
162
163 for(auto& in : inputs.port.value->get_data())
164 {
165 auto note = in.value.apply(val_visitor{*this, base_note, base_vel});
166
167 if(rand_note != 0)
168 note.pitch = std::clamp(
169 (int)note.pitch + (int)rnd::rand(-rand_note, rand_note), 0, 127);
170
171 if(rand_vel != 0)
172 note.vel
173 = std::clamp((int)note.vel + (int)rnd::rand(-rand_vel, rand_vel), 0, 127);
174
175 note.pitch = ossia::clamp((int)note.pitch + shiftnote, 0, 127);
176 note.vel = ossia::clamp((int)note.vel, 0, 127);
177
178 if(note.vel != 0.)
179 {
180 if(start == 0.f) // No quantification, start directly
181 {
182 outputs.midi.note_on(chan, note.pitch, note.vel).timestamp = in.timestamp;
183 if(end > 0.f)
184 {
185 this->running_notes.push_back(
186 {note, tk.position_in_frames + in.timestamp, true});
187 }
188 else if(end == 0.f)
189 {
190 // Stop at the next sample
191#if STOP_AT_NEXT_SAMPLE_FOR_ZERO_END_QUANT
192 outputs.midi.note_off(chan, note.pitch, note.vel).timestamp = in.timestamp;
193#endif
194 }
195 else
196 {
197 // else do nothing and just wait for a note off
198 }
199 }
200 else
201 {
202 // Find next time that matches the requested quantification
203 this->to_start.push_back({note, {}});
204 }
205 }
206 else
207 {
208 // Just stop
209 outputs.midi.note_off(chan, note.pitch, note.vel).timestamp = in.timestamp;
210 }
211 }
212 std::optional<int> start_quant_date{};
213
214 if(!to_start.empty())
215 {
216 if(start != 0.f)
217 {
218 for(auto [date, q] : tk.get_quantification_date(4. * start))
219 {
220 start_all_notes(tk.position_in_frames + date, date, chan, end);
221 start_quant_date = date;
222 break;
223 }
224 }
225 else
226 {
227 start_all_notes(tk.position_in_frames, 0, chan, end);
228 start_quant_date = 0;
229 }
230 }
231
232 if(end != 0.f)
233 {
234 for(auto [date, q] : tk.get_quantification_date(4. * end))
235 {
236 if(!start_quant_date || date > start_quant_date)
237 {
238 stop_notes(date, chan);
239 }
240 }
241 }
242 else
243 {
244 // FIXME this means that the notes cannot run ? instead we should just rely on note off maybe? but it's an impulse...
245 // we should do this for "impulses" and individual ints, but let the normal note off
246#if STOP_AT_NEXT_SAMPLE_FOR_ZERO_END_QUANT
247 stop_notes(0, chan);
248#endif
249 }
250 }
251
252 void start_all_notes(
253 ossia::physical_time abs_date, ossia::physical_time date_phys, int chan,
254 float endq) noexcept
255 {
256 for(auto& note : this->to_start)
257 {
258 outputs.midi.note_on(chan, note.note.pitch, note.note.vel).timestamp = date_phys;
259
260 if(endq > 0.f)
261 {
262 this->running_notes.push_back({{note.note}, abs_date});
263 }
264 else if(endq == 0.f)
265 {
266 // Stop at the next sample
267
268#if STOP_AT_NEXT_SAMPLE_FOR_ZERO_END_QUANT
269 outputs.midi.note_off(chan, note.note.pitch, note.note.vel).timestamp
270 = date_phys;
271#endif
272 }
273 }
274 this->to_start.clear();
275 }
276
277 void stop_notes(ossia::physical_time date_phys, int chan)
278 {
279 for(auto it = this->running_notes.begin(); it != this->running_notes.end();)
280 {
281 auto& note = *it;
282 if(!note.fresh)
283 {
284 it = this->running_notes.erase(it);
285 outputs.midi.note_off(chan, note.note.pitch, note.note.vel).timestamp
286 = date_phys;
287 }
288 else
289 {
290 note.fresh = false;
291 ++it;
292 }
293 }
294 /*
295 for(auto it = this->running_notes.begin(); it != this->running_notes.end();)
296 {
297 auto& note = *it;
298 // note.date is the date at which the note was started.
299
300 if(note.date.impl > tk.date.impl)
301 {
302 // Note was started "in the future", stop it right now as it means we went back in time
303 p2.note_off(chan, note.note.pitch, note.note.vel).timestamp
304 = tk.to_physical_time_in_tick(tk.prev_date, sampleRatio);
305 it = this->running_notes.erase(it);
306 }
307 else if(note.date.impl < tk.prev_date.impl)
308 {
309 // if we're
310 it = this->running_notes.erase(it);
311 }
312 else
313 {
314 ++it;
315 }
316
317 if(note.date > tk.prev_date && note.date.impl < tk.date.impl)
318 {
319 p2.note_off(chan, note.note.pitch, note.note.vel).timestamp
320 = (note.date - tk.prev_date).impl * st.modelToSamples();
321
322 it = this->running_notes.erase(it);
323 }
324 else
325 {
326 ++it;
327 }
328 }
329*/
330 }
331};
332}
333}
Definition VelToNote.hpp:65
Definition VelToNote.hpp:72
Definition VelToNote.hpp:81
Definition VelToNote.hpp:24
Definition Types.hpp:49
Definition Types.hpp:40