Loading...
Searching...
No Matches
Arpeggiator_v2.hpp
1#pragma once
2#include <Advanced/MidiScaler/MidiCommon/EventQueue.hpp>
3#include <Advanced/MidiScaler/MidiCommon/Messages.hpp>
4#include <Advanced/MidiScaler/MidiCommon/Rhythm.hpp>
5#include <Fx/Arpeggiator.hpp>
6#include <Fx/NoteValueGrid.hpp>
7#include <halp/layout.hpp>
8
9#include <cmath>
10
11namespace Nodes::Arpeggiator::v2
12{
15{
16 halp_meta(name, "Arpeggios");
17 halp_meta(
18 description,
19 "Order of the steps. F->B and B->F play the top and bottom twice, Up/Down "
20 "and Down/Up once. Low Up and High Up alternate the lowest or highest note "
21 "with the others. Chord plays every note at each step: with Pressure, it "
22 "is a note repeat.");
23 enum widget
24 {
25 combobox
26 };
27
28 struct range
29 {
30 halp::combo_pair<int> values[13]{
31 {"Forward", 0}, {"Backward", 1}, {"F->B", 2}, {"B->F", 3},
32 {"Chord", 4}, {"Random", 5}, {"Up/Down", 6}, {"Down/Up", 7},
33 {"Converge", 8}, {"Diverge", 9}, {"Low Up", 10}, {"High Up", 11},
34 {"As played", 12}};
35 int init{0};
36 };
37
38 int value{};
39};
40
41enum RetriggerMode
42{
43 NoRetrigger,
44 RetriggerOnNote,
45 RetriggerOnBeat
46};
48{
49 halp_meta(name, "Retrigger");
50 halp_meta(
51 description, "Restart the pattern from its first step on each new key, or "
52 "on each beat");
53 enum widget
54 {
55 combobox
56 };
57 struct range
58 {
59 halp::combo_pair<int> values[3]{{"Off", 0}, {"Note", 1}, {"Beat", 2}};
60 int init{0};
61 };
62 int value{};
63};
64
65enum LatchMode
66{
67 NoLatch,
68 LatchToggle,
69 LatchMomentary
70};
71struct Latch
72{
73 halp_meta(name, "Latch");
74 halp_meta(
75 description, "Toggle: released notes keep playing until a new chord. "
76 "Momentary: released notes keep playing while Hold is on.");
77 enum widget
78 {
79 combobox
80 };
81 struct range
82 {
83 halp::combo_pair<int> values[3]{{"Off", 0}, {"Toggle", 1}, {"Momentary", 2}};
84 int init{0};
85 };
86 int value{};
87};
88
89enum PressureMode
90{
91 NoPressure,
92 ChannelPressure,
93 PolyPressure,
94 AnyPressure
95};
97{
98 halp_meta(name, "Pressure");
99 halp_meta(
100 description, "Aftertouch sets the velocity of the following steps: channel "
101 "pressure for every note, poly pressure for its own key");
102 enum widget
103 {
104 combobox
105 };
106 struct range
107 {
108 halp::combo_pair<int> values[4]{
109 {"Off", 0}, {"Channel", 1}, {"Poly", 2}, {"Both", 3}};
110 int init{0};
111 };
112 int value{};
113};
114
115struct Node
116{
117 halp_meta(name, "Arpeggiator")
118 halp_meta(c_name, "Arpeggiator")
119 halp_meta(category, "Midi/Generators")
120 halp_meta(author, "ossia score")
121 halp_meta(
122 manual_url,
123 "https://ossia.io/score-docs/processes/midi-utilities.html#arpeggiator")
124 halp_meta(
125 description,
126 "Arpeggiator. For a note repeat, choose the Chord pattern, a Rate and a "
127 "Pressure mode: the held notes repeat at the rate, at the velocity of the "
128 "key pressure.")
129 halp_meta(uuid, "4df48c1c-f5f2-4ab1-9f37-3b0adba139ae")
130 halp_flag(process_exec);
131 // MIDI packets the output keeps for all the ticks of one buffer
132 halp_meta(local_midi_tick_batch_reserve, 4096)
133
134 using byte = unsigned char;
135
136 struct ins
137 {
138 halp::midi_bus<"in", libremidi::message> midi;
139 Arpeggios arpeggios;
140 halp::hslider_i32<"Octave", halp::irange{1, 7, 1}> octave;
141 OctaveMode octave_mode;
142 halp::hslider_i32<"Repeat", halp::irange{1, 8, 1}> repeat;
146 halp::time_chooser<"Rate", halp::range{0.01, 4., 0.125}, halp::bar_sync::pulse>
148
149 struct : halp::knob_f32<"Gate", halp::range{0.01, 2., 1.}>
150 {
151 halp_meta(
152 description, "Length of a note as a proportion of a step. 1 lasts until "
153 "the next step, above overlaps it.")
154 } gate;
155 struct : halp::knob_f32<"Swing", halp::range{0., 1., 0.}>
156 {
157 halp_meta(
158 description, "Delays every second step: 0.5 puts it on the triplet, 1 at "
159 "5/6 of the pair")
160 } swing;
161 struct : halp::spinbox_i32<"Accent every", halp::irange{0, 16, 0}>
162 {
163 halp_meta(description, "Every Nth step, from the first, plays at the accent velocity. 0: no accent.")
164 } accent_every;
165 struct : halp::hslider_i32<"Accent vel.", halp::irange{1, 127, 127}>
166 {
167 halp_meta(description, "Velocity of the accented steps")
168 } accent_velocity;
169 struct : halp::knob_f32<"Vel. decay", halp::range{0., 1., 0.}>
170 {
171 halp_meta(
172 description, "At each step since the chord started, the velocity moves "
173 "this proportion of the way to the decay target")
174 } decay;
175 struct : halp::hslider_i32<"Decay target", halp::irange{1, 127, 1}>
176 {
177 halp_meta(description, "Velocity the decay tends to")
178 } decay_target;
179 Retrigger retrigger;
180 Latch latch;
181 struct : halp::toggle<"Latch hold">
182 {
183 halp_meta(description, "With the Momentary latch: released notes keep playing while on")
184 } hold;
185 Pressure pressure;
186 struct : halp::spinbox_i32<"Seed", halp::irange{0, 10000, 0}>
187 {
188 halp_meta(description, "Random pattern: 0 runs freely, any other value repeats the same sequence after each stop")
189 } seed;
190 } inputs;
191 struct
192 {
193 halp::midi_out_bus<"out", libremidi::message> midi;
194 } outputs;
195
196 struct ui
197 {
198 halp_meta(name, "Arpeggiator")
199 halp_meta(layout, halp::layouts::hbox)
200 halp_meta(background, halp::colors::background_dark)
201
202 struct
203 {
204 halp_meta(name, "Pattern")
205 halp_meta(layout, halp::layouts::section)
206 halp_meta(background, halp::colors::background_mid)
207 halp::item<&ins::arpeggios> arpeggios;
208 struct
209 {
210 halp_meta(layout, halp::layouts::hbox)
211 halp::item<&ins::rate> rate;
212 halp::item<&ins::gate> gate;
213 halp::item<&ins::swing> swing;
214 } timing;
215 struct
216 {
217 halp_meta(layout, halp::layouts::hbox)
218 halp::item<&ins::octave> octave;
219 halp::item<&ins::octave_mode> octave_mode;
220 } octaves;
221 } pattern;
222
223 struct
224 {
225 halp_meta(name, "Settings")
226 halp_meta(layout, halp::layouts::tabs)
227 halp_meta(background, halp::colors::background_darker)
228
229 struct
230 {
231 halp_meta(name, "Steps")
232 halp_meta(layout, halp::layouts::vbox)
233 halp::item<&ins::repeat> repeat;
234 halp::item<&ins::retrigger> retrigger;
235 halp::item<&ins::seed> seed;
236 } steps;
237
238 struct
239 {
240 halp_meta(name, "Velocity")
241 halp_meta(layout, halp::layouts::grid)
242 halp_meta(columns, 2)
243 halp::item<&ins::accent_every> accent_every;
244 halp::item<&ins::accent_velocity> accent_velocity;
245 halp::item<&ins::decay> decay;
246 halp::item<&ins::decay_target> decay_target;
247 halp::item<&ins::pressure> pressure;
248 } velocity;
249
250 struct
251 {
252 halp_meta(name, "Latch")
253 halp_meta(layout, halp::layouts::vbox)
254 halp::item<&ins::latch> latch;
255 struct
256 {
257 halp_meta(layout, halp::layouts::vbox)
258 static constexpr auto condition()
259 {
260 return halp::enabled_when<&ins::latch, int(LatchMomentary)>{};
261 }
262 halp::item<&ins::hold> hold;
263 } hold;
264 } latch;
265 } settings;
266 };
267
270
271 Node()
272 {
273 std::random_device d{};
274 m_free_seed = (uint64_t(d()) << 32) ^ d();
275 }
276
277 halp::setup setup;
278 void prepare(halp::setup s) noexcept { setup = s; }
279
280 void stop() noexcept { m_stop = true; }
281
284 double grid_rate(const halp::tick_musical& tk) const noexcept
285 {
286 return Nodes::grid_rate(inputs.rate.value, tk.tempo, inputs.rate.sync);
287 }
288
289 using tick = halp::tick_musical;
290 void operator()(const halp::tick_musical& tk) noexcept
291 {
292 const int frames = std::max(0, tk.frames);
293 m_frames = frames;
294
295 bool dirty = false;
296 if(m_stop)
297 {
298 m_stop = false;
299 reset();
300 dirty = true;
301 }
302
303 if(inputs.seed.value != m_seed)
304 {
305 m_seed = inputs.seed.value;
306 reseed();
307 }
308
309 // Leaving a latch: what is not held any more stops
310 const bool sustaining = this->sustaining();
311 if(m_sustaining && !sustaining)
312 {
313 for(int p = 0; p < 128; p++)
314 if(m_held[p] == 0 && engine.notes.find(byte(p)) != engine.notes.end())
315 {
316 forget(p);
317 dirty = true;
318 }
319 }
320 m_sustaining = sustaining;
321
322 const int mode = std::clamp(inputs.arpeggios.value, 0, 12);
323 const int octave = std::clamp(int(inputs.octave), 1, Engine::max_octave);
324 const int octave_mode = inputs.octave_mode.value;
325 const int repeat = std::clamp(int(inputs.repeat), 1, Engine::max_repeat);
326 dirty |= octave != engine.previous_octave
327 || octave_mode != engine.previous_octave_mode
328 || repeat != engine.previous_repeat || mode != engine.previous_arpeggio;
329 engine.previous_octave = octave;
330 engine.previous_octave_mode = octave_mode;
331 engine.previous_repeat = repeat;
332 engine.previous_arpeggio = mode;
333 const auto refresh = [&] {
334 if(dirty)
335 {
336 engine.update();
337 dirty = false;
338 }
339 };
340 refresh();
341 if(engine.arpeggio.empty())
342 silence(0);
343
344 // The input and the steps are taken in time order: a message at a frame
345 // changes the steps from that frame on.
346 std::size_t next_input = 0;
347 const auto take_inputs_until = [&](int64_t until) {
348 for(; next_input < inputs.midi.size(); next_input++)
349 {
350 const auto& m = inputs.midi.midi_messages[next_input];
351 const int64_t ts = frames > 0 ? std::clamp<int64_t>(m.timestamp, 0, frames - 1) : 0;
352 if(ts > until)
353 break;
354 // A swung step due before the message plays first
355 if(m_pending.valid && m_pending.at <= m_now + ts)
356 fire_pending();
357 if(handle(m, ts))
358 {
359 dirty = true;
360 if(engine.notes.empty())
361 {
362 refresh();
363 if(engine.arpeggio.empty())
364 silence(ts);
365 }
366 }
367 }
368 };
369
370 const double rate = grid_rate(tk);
371 const double dq = tk.end_position_in_quarters - tk.start_position_in_quarters;
372 const bool musical = dq > 0. && frames > 0;
373 const double sr = setup.rate > 0. ? setup.rate : 48000.;
374 const double frames_per_quarter
375 = musical ? frames / dq : (tk.tempo > 0. ? 60. / tk.tempo * sr : 0.5 * sr);
376 // The grid's unit, as get_quantification_date counts it: a subdivision of
377 // the whole note, or for a bar or longer, a number of bars.
378 const bool valid_sig = tk.signature.num > 0 && tk.signature.denom > 0;
379 const double quarters_in_bar
380 = valid_sig ? 4. * tk.signature.num / tk.signature.denom : 4.;
381 const double quarters_per_step = rate > 1. ? 4. / rate : quarters_in_bar / rate;
382 m_step_frames = rate > 0. ? frames_per_quarter * quarters_per_step
383 : double(std::max(1, frames));
384
385 if(frames > 0)
386 {
387 const bool bar_restart = !inputs.rate.sync || inputs.rate.bar_sync;
388 for_each_step(tk, rate, quarters_in_bar, bar_restart, [&](int date, int64_t grid_point) {
389 take_inputs_until(date);
390 refresh();
391 if(engine.arpeggio.empty())
392 return;
393 if(engine.index >= engine.arpeggio.size())
394 engine.index = 0;
395
396 const int64_t at = m_now + date;
397 if(m_pending.valid && m_pending.at <= at)
398 fire_pending();
399
400 // Where the step falls on the grid: for the swing pairs, counted from
401 // the bar line as the grid is, and for the beats
402 int64_t grid_index = m_free_grid++;
403 bool on_beat = false;
404 if(musical && rate > 0.)
405 {
406 const double q = tk.start_position_in_quarters + date * dq / frames;
407 grid_index = grid_point;
408 on_beat = std::abs(q - std::round(q)) <= 0.5 * dq / frames + 1e-9;
409 }
410 if(inputs.retrigger.value == RetriggerOnBeat && on_beat)
411 restart();
412
413 std::size_t play_index;
414 if(mode == 5) // Random
415 play_index = m_rng() % engine.arpeggio.size();
416 else
417 {
418 play_index = engine.index;
419 engine.index = (engine.index + 1) % engine.arpeggio.size();
420 }
421
422 // The chosen notes and their velocities, kept until the swung date
423 Step s;
424 s.valid = true;
425 const int64_t delay = std::llround(
426 mtk::swing_delay(grid_index, double(inputs.swing.value)) * m_step_frames);
427 s.at = at + std::max<int64_t>(0, delay);
428 for(auto [pitch, vel] : engine.step(play_index))
429 {
430 if(s.count == 128)
431 break;
432 s.notes[s.count++] = {pitch, byte(velocity(vel, m_step))};
433 }
434 m_step++;
435
436 if(m_pending.valid)
437 {
438 // Swung past this step (the rate changed): it plays now
439 m_pending.at = std::min(m_pending.at, at);
440 fire_pending();
441 }
442 m_pending = s;
443 if(s.at < m_now + frames)
444 fire_pending();
445 });
446 }
447 take_inputs_until(frames);
448 refresh();
449 if(m_pending.valid && m_pending.at < m_now + frames)
450 fire_pending();
451 finish(frames);
452 }
453
454private:
463 template <typename F>
464 static void for_each_step(
465 const halp::tick_musical& tk, double rate, double quarters_in_bar,
466 bool bar_restart, F&& f) noexcept
467 {
468 const double start = tk.start_position_in_quarters;
469 const double end = tk.end_position_in_quarters;
470 const double unit = rate > 1. ? 4. / rate : 0.;
471 if(bar_restart || !(end > start) || !(unit > 0.) || !(quarters_in_bar > 0.))
472 {
473 for(auto [date, grid_point] : tk.get_quantification_date(rate))
474 {
475 if(date >= tk.frames)
476 break;
477 f(date, grid_point);
478 }
479 return;
480 }
481
482 for_each_pulse_point(
483 start, end, tk.last_signature_change, unit, quarters_in_bar,
484 [&](double p, int64_t index) {
485 const int64_t frame = std::clamp<int64_t>(
486 halp::musical_to_frame(p, start, end, tk.frames), 0, tk.frames - 1);
487 f(int(frame), index);
488 });
489 }
490
491 struct Step
492 {
493 bool valid{};
494 int64_t at{};
495 int count{};
496 std::array<std::pair<byte, byte>, 128> notes{};
497 };
498
499 bool sustaining() const noexcept
500 {
501 return inputs.latch.value == LatchToggle
502 || (inputs.latch.value == LatchMomentary && inputs.hold.value);
503 }
504
505 void reseed() noexcept
506 {
507 if(m_seed != 0)
508 m_rng.seed(uint64_t(m_seed), 0x5851f42d4c957f2dULL);
509 else
510 m_rng.seed(m_free_seed, 0x14057b7ef767814fULL);
511 }
512
513 void restart() noexcept
514 {
515 engine.index = 0;
516 m_step = 0;
517 }
518
521 int velocity(int key_velocity, int64_t step) const noexcept
522 {
523 double v = m_channel_pressure >= 0 ? m_channel_pressure : key_velocity;
524 if(const double decay = std::clamp(double(inputs.decay.value), 0., 1.); decay > 0.)
525 {
526 const double target = inputs.decay_target.value;
527 v = target + (v - target) * std::pow(1. - decay, double(step));
528 }
529 if(inputs.accent_every.value > 0 && step % inputs.accent_every.value == 0)
530 v = inputs.accent_velocity.value;
531 return std::clamp(int(std::lround(v)), 1, 127);
532 }
533
534 void add(int p, int v) noexcept
535 {
536 auto [it, inserted] = engine.notes.insert({byte(p), byte(v)});
537 if(!inserted)
538 it->second = byte(v);
539 else if(engine.order_count < 128)
540 engine.order[engine.order_count++] = byte(p);
541 }
542
543 void forget(int p) noexcept
544 {
545 engine.notes.erase(byte(p));
546 int w = 0;
547 for(int r = 0; r < engine.order_count; r++)
548 if(engine.order[r] != p)
549 engine.order[w++] = engine.order[r];
550 engine.order_count = w;
551 }
552
553 void forget_all() noexcept
554 {
555 engine.notes.clear();
556 engine.order_count = 0;
557 m_held.fill(0);
558 m_held_count = 0;
559 }
560
562 bool handle(const libremidi::message& m, int64_t ts) noexcept
563 {
564 switch(mtk::classify(m))
565 {
566 case mtk::midi_kind::note_on: {
567 const int p = m.bytes[1], v = m.bytes[2];
568 if(m_held_count == 0 && inputs.latch.value == LatchToggle)
569 forget_all();
570 if(engine.notes.empty())
571 {
572 m_step = 0;
573 m_channel_pressure = -1;
574 }
575 if(m_held[p] == 0)
576 {
577 m_held_count++;
578 if(inputs.retrigger.value == RetriggerOnNote)
579 restart();
580 }
581 m_held[p] = byte(v);
582 add(p, v);
583 return true;
584 }
585 case mtk::midi_kind::note_off: {
586 const int p = m.bytes[1];
587 if(m_held[p] != 0)
588 {
589 m_held[p] = 0;
590 m_held_count--;
591 }
592 if(!sustaining())
593 forget(p);
594 return true;
595 }
596 case mtk::midi_kind::channel_pressure:
597 if(inputs.pressure.value == ChannelPressure || inputs.pressure.value == AnyPressure)
598 {
599 m_channel_pressure = std::max(1, int(m.bytes[1]));
600 return false;
601 }
602 break;
603 case mtk::midi_kind::poly_pressure:
604 if(inputs.pressure.value == PolyPressure || inputs.pressure.value == AnyPressure)
605 {
606 if(auto it = engine.notes.find(m.bytes[1]); it != engine.notes.end())
607 {
608 // The most recent kind of pressure wins: per-key values take over
609 // again from an earlier channel pressure.
610 m_channel_pressure = -1;
611 it->second = byte(std::max(1, int(m.bytes[2])));
612 return true;
613 }
614 return false;
615 }
616 break;
617 case mtk::midi_kind::control_change:
618 if(mtk::is_all_notes_off(m))
619 {
620 end_all(ts);
621 m_pending.valid = false;
622 forget_all();
623 restart();
624 m_channel_pressure = -1;
625 auto out = m;
626 out.timestamp = ts;
627 outputs.midi.push_back(out);
628 return true;
629 }
630 break;
631 default:
632 break;
633 }
634 auto out = m;
635 out.timestamp = ts;
636 outputs.midi.push_back(out);
637 return false;
638 }
639
640 void send_off(int p, int64_t ts) noexcept
641 {
642 outputs.midi.note_off(1, byte(p), 0).timestamp = ts;
643 }
644
646 void end_all(int64_t ts) noexcept
647 {
648 m_floor = std::max(m_floor, ts);
649 for(int p = 0; p < 128; p++)
650 if(m_sounding[p])
651 {
652 m_sounding[p] = 0;
653 send_off(p, ts);
654 }
655 m_offs.clear();
656 }
657
659 void release_until(int64_t ts) noexcept
660 {
661 m_offs.release(m_now, int(ts), [this](libremidi::message&& msg) {
662 const int p = msg.bytes[1];
663 if(m_sounding[p])
664 {
665 m_sounding[p] = 0;
666 outputs.midi.push_back(std::move(msg));
667 }
668 });
669 }
670
671 void fire_pending() noexcept
672 {
673 Step& s = m_pending;
674 s.valid = false;
675 // Never before what was already sent in this tick: the output stays in
676 // time order for each pitch
677 const int64_t ts = std::clamp<int64_t>(
678 std::max(s.at - m_now, m_floor), 0, std::max(0, m_frames - 1));
679 m_floor = ts;
680 release_until(ts);
681
682 // Within half a percent of 1 the knob means "until the next step"
683 const double gate = std::clamp(double(inputs.gate.value), 0.01, 2.);
684 const bool legato = std::abs(gate - 1.) < 0.005;
685 if(gate < 1. || legato)
686 {
687 // The previous step ends where this one starts, at the latest
688 end_all(ts);
689 }
690 else
691 {
692 // Overlapping steps: a pitch played again ends first
693 for(int i = 0; i < s.count; i++)
694 {
695 const int p = s.notes[i].first;
696 if(m_sounding[p])
697 {
698 m_sounding[p] = 0;
699 send_off(p, ts);
700 m_offs.remove_if([p](const auto& e) { return e.msg.bytes[1] == p; });
701 }
702 }
703 }
704
705 const int64_t length
706 = std::max<int64_t>(1, std::llround(m_step_frames * gate));
707 for(int i = 0; i < s.count; i++)
708 {
709 const auto [p, v] = s.notes[i];
710 if(m_sounding[p])
711 continue; // twice in the same step
712 m_sounding[p] = 1;
713 outputs.midi.note_on(1, p, v).timestamp = ts;
714 // At 100 the note lasts until the next step, wherever it falls
715 if(!legato)
716 m_offs.schedule(
717 m_now + ts + length, libremidi::channel_events::note_off(1, p, 0));
718 }
719 }
720
722 void silence(int64_t ts) noexcept
723 {
724 end_all(ts);
725 m_pending.valid = false;
726 m_step = 0;
727 m_channel_pressure = -1;
728 }
729
730 void reset() noexcept
731 {
732 end_all(0);
733 m_pending.valid = false;
734 forget_all();
735 restart();
736 m_channel_pressure = -1;
737 m_sustaining = false;
738 reseed();
739 }
740
742 void finish(int frames) noexcept
743 {
744 release_until(frames);
745 // Stable insertion sort: same-frame messages keep the order they were
746 // made in, and nothing is allocated.
747 auto& msgs = outputs.midi.midi_messages;
748 for(std::size_t i = 1; i < msgs.size(); i++)
749 {
750 if(!(msgs[i].timestamp < msgs[i - 1].timestamp))
751 continue;
752 auto m = std::move(msgs[i]);
753 std::size_t j = i;
754 for(; j > 0 && m.timestamp < msgs[j - 1].timestamp; j--)
755 msgs[j] = std::move(msgs[j - 1]);
756 msgs[j] = std::move(m);
757 }
758 m_now += frames;
759 m_floor = 0;
760 }
761
762 mtk::event_queue<512> m_offs;
763 Step m_pending;
764 std::array<byte, 128> m_held{};
765 int m_held_count{};
766 std::array<byte, 128> m_sounding{};
767 int64_t m_now{};
768 int64_t m_floor{};
769 int64_t m_step{};
770 int64_t m_free_grid{};
771 double m_step_frames{1.};
772 int m_frames{};
773 int m_channel_pressure{-1};
774 int m_seed{-1};
775 uint64_t m_free_seed{};
776 rnd::pcg m_rng;
777 bool m_sustaining{};
778 bool m_stop{};
779};
780}
Link of score with the OSSIA API execution engine.
Definition CurveConversion.hpp:8
std::array< byte, 128 > order
The held notes in the order they came, for "As played".
Definition Arpeggiator.hpp:78
Definition Arpeggiator.hpp:46
Definition Arpeggiator_v2.hpp:29
The first six keep the names and indices of the old arpeggiator's.
Definition Arpeggiator_v2.hpp:15
Definition Arpeggiator_v2.hpp:82
Definition Arpeggiator_v2.hpp:72
Definition Arpeggiator_v2.hpp:137
halp::time_chooser<"Rate", halp::range{0.01, 4., 0.125}, halp::bar_sync::pulse > rate
Definition Arpeggiator_v2.hpp:147
Definition Arpeggiator_v2.hpp:197
Definition Arpeggiator_v2.hpp:116
Engine engine
notes is what plays: the held keys, and with a latch, the released ones.
Definition Arpeggiator_v2.hpp:269
double grid_rate(const halp::tick_musical &tk) const noexcept
Definition Arpeggiator_v2.hpp:284
Definition Arpeggiator_v2.hpp:107
Definition Arpeggiator_v2.hpp:97
Definition Arpeggiator_v2.hpp:58
Definition Arpeggiator_v2.hpp:48