117 halp_meta(name,
"Arpeggiator")
118 halp_meta(c_name,
"Arpeggiator")
119 halp_meta(category,
"Midi/Generators")
120 halp_meta(author,
"ossia score")
123 "https://ossia.io/score-docs/processes/midi-utilities.html#arpeggiator")
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 "
129 halp_meta(uuid,
"4df48c1c-f5f2-4ab1-9f37-3b0adba139ae")
130 halp_flag(process_exec);
132 halp_meta(local_midi_tick_batch_reserve, 4096)
134 using byte =
unsigned char;
138 halp::midi_bus<
"in", libremidi::message> midi;
140 halp::hslider_i32<
"Octave", halp::irange{1, 7, 1}> octave;
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>
149 struct : halp::knob_f32<
"Gate", halp::range{0.01, 2., 1.}>
152 description,
"Length of a note as a proportion of a step. 1 lasts until "
153 "the next step, above overlaps it.")
155 struct : halp::knob_f32<
"Swing", halp::range{0., 1., 0.}>
158 description,
"Delays every second step: 0.5 puts it on the triplet, 1 at "
161 struct : halp::spinbox_i32<
"Accent every", halp::irange{0, 16, 0}>
163 halp_meta(description,
"Every Nth step, from the first, plays at the accent velocity. 0: no accent.")
165 struct : halp::hslider_i32<
"Accent vel.", halp::irange{1, 127, 127}>
167 halp_meta(description,
"Velocity of the accented steps")
169 struct : halp::knob_f32<
"Vel. decay", halp::range{0., 1., 0.}>
172 description,
"At each step since the chord started, the velocity moves "
173 "this proportion of the way to the decay target")
175 struct : halp::hslider_i32<
"Decay target", halp::irange{1, 127, 1}>
177 halp_meta(description,
"Velocity the decay tends to")
181 struct : halp::toggle<
"Latch hold">
183 halp_meta(description,
"With the Momentary latch: released notes keep playing while on")
186 struct : halp::spinbox_i32<
"Seed", halp::irange{0, 10000, 0}>
188 halp_meta(description,
"Random pattern: 0 runs freely, any other value repeats the same sequence after each stop")
193 halp::midi_out_bus<
"out", libremidi::message> midi;
198 halp_meta(name,
"Arpeggiator")
199 halp_meta(layout, halp::layouts::hbox)
200 halp_meta(background, halp::colors::background_dark)
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;
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;
217 halp_meta(layout, halp::layouts::hbox)
218 halp::item<&ins::octave> octave;
219 halp::item<&ins::octave_mode> octave_mode;
225 halp_meta(name,
"Settings")
226 halp_meta(layout, halp::layouts::tabs)
227 halp_meta(background, halp::colors::background_darker)
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;
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;
252 halp_meta(name,
"Latch")
253 halp_meta(layout, halp::layouts::vbox)
254 halp::item<&ins::latch> latch;
257 halp_meta(layout, halp::layouts::vbox)
258 static constexpr auto condition()
260 return halp::enabled_when<&ins::latch, int(LatchMomentary)>{};
262 halp::item<&ins::hold> hold;
273 std::random_device d{};
274 m_free_seed = (uint64_t(d()) << 32) ^ d();
278 void prepare(halp::setup s)
noexcept { setup = s; }
280 void stop() noexcept { m_stop =
true; }
284 double grid_rate(
const halp::tick_musical& tk)
const noexcept
286 return Nodes::grid_rate(inputs.
rate.value, tk.tempo, inputs.
rate.sync);
289 using tick = halp::tick_musical;
290 void operator()(
const halp::tick_musical& tk)
noexcept
292 const int frames = std::max(0, tk.frames);
303 if(inputs.seed.value != m_seed)
305 m_seed = inputs.seed.value;
310 const bool sustaining = this->sustaining();
311 if(m_sustaining && !sustaining)
313 for(
int p = 0; p < 128; p++)
314 if(m_held[p] == 0 &&
engine.notes.find(
byte(p)) !=
engine.notes.end())
320 m_sustaining = sustaining;
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 = [&] {
341 if(
engine.arpeggio.empty())
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++)
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;
355 if(m_pending.valid && m_pending.at <= m_now + ts)
363 if(
engine.arpeggio.empty())
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);
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));
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);
391 if(
engine.arpeggio.empty())
396 const int64_t at = m_now + date;
397 if(m_pending.valid && m_pending.at <= at)
402 int64_t grid_index = m_free_grid++;
403 bool on_beat =
false;
404 if(musical && rate > 0.)
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;
410 if(inputs.retrigger.value == RetriggerOnBeat && on_beat)
413 std::size_t play_index;
415 play_index = m_rng() %
engine.arpeggio.size();
418 play_index =
engine.index;
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))
432 s.notes[s.count++] = {pitch, byte(velocity(vel, m_step))};
439 m_pending.at = std::min(m_pending.at, at);
443 if(s.at < m_now + frames)
447 take_inputs_until(frames);
449 if(m_pending.valid && m_pending.at < m_now + frames)
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
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.))
473 for(
auto [date, grid_point] : tk.get_quantification_date(rate))
475 if(date >= tk.frames)
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);
496 std::array<std::pair<byte, byte>, 128> notes{};
499 bool sustaining() const noexcept
501 return inputs.latch.value == LatchToggle
502 || (inputs.latch.value == LatchMomentary && inputs.hold.value);
505 void reseed() noexcept
508 m_rng.seed(uint64_t(m_seed), 0x5851f42d4c957f2dULL);
510 m_rng.seed(m_free_seed, 0x14057b7ef767814fULL);
513 void restart() noexcept
521 int velocity(
int key_velocity, int64_t step)
const noexcept
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.)
526 const double target = inputs.decay_target.value;
527 v = target + (v - target) * std::pow(1. - decay,
double(step));
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);
534 void add(
int p,
int v)
noexcept
536 auto [it, inserted] =
engine.notes.insert({byte(p), byte(v)});
538 it->second = byte(v);
539 else if(
engine.order_count < 128)
543 void forget(
int p)
noexcept
545 engine.notes.erase(
byte(p));
547 for(
int r = 0; r <
engine.order_count; r++)
553 void forget_all() noexcept
562 bool handle(
const libremidi::message& m, int64_t ts)
noexcept
564 switch(mtk::classify(m))
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)
573 m_channel_pressure = -1;
578 if(inputs.retrigger.value == RetriggerOnNote)
585 case mtk::midi_kind::note_off: {
586 const int p = m.bytes[1];
596 case mtk::midi_kind::channel_pressure:
597 if(inputs.pressure.value == ChannelPressure || inputs.pressure.value == AnyPressure)
599 m_channel_pressure = std::max(1,
int(m.bytes[1]));
603 case mtk::midi_kind::poly_pressure:
604 if(inputs.pressure.value == PolyPressure || inputs.pressure.value == AnyPressure)
606 if(
auto it =
engine.notes.find(m.bytes[1]); it !=
engine.notes.end())
610 m_channel_pressure = -1;
611 it->second = byte(std::max(1,
int(m.bytes[2])));
617 case mtk::midi_kind::control_change:
618 if(mtk::is_all_notes_off(m))
621 m_pending.valid =
false;
624 m_channel_pressure = -1;
627 outputs.midi.push_back(out);
636 outputs.midi.push_back(out);
640 void send_off(
int p, int64_t ts)
noexcept
642 outputs.midi.note_off(1,
byte(p), 0).timestamp = ts;
646 void end_all(int64_t ts)
noexcept
648 m_floor = std::max(m_floor, ts);
649 for(
int p = 0; p < 128; p++)
659 void release_until(int64_t ts)
noexcept
661 m_offs.release(m_now,
int(ts), [
this](libremidi::message&& msg) {
662 const int p = msg.bytes[1];
666 outputs.midi.push_back(std::move(msg));
671 void fire_pending() noexcept
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));
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)
693 for(
int i = 0; i < s.count; i++)
695 const int p = s.notes[i].first;
700 m_offs.remove_if([p](
const auto& e) {
return e.msg.bytes[1] == p; });
706 = std::max<int64_t>(1, std::llround(m_step_frames * gate));
707 for(
int i = 0; i < s.count; i++)
709 const auto [p, v] = s.notes[i];
713 outputs.midi.note_on(1, p, v).timestamp = ts;
717 m_now + ts + length, libremidi::channel_events::note_off(1, p, 0));
722 void silence(int64_t ts)
noexcept
725 m_pending.valid =
false;
727 m_channel_pressure = -1;
730 void reset() noexcept
733 m_pending.valid =
false;
736 m_channel_pressure = -1;
737 m_sustaining =
false;
742 void finish(
int frames)
noexcept
744 release_until(frames);
747 auto& msgs = outputs.midi.midi_messages;
748 for(std::size_t i = 1; i < msgs.size(); i++)
750 if(!(msgs[i].timestamp < msgs[i - 1].timestamp))
752 auto m = std::move(msgs[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);
762 mtk::event_queue<512> m_offs;
764 std::array<byte, 128> m_held{};
766 std::array<byte, 128> m_sounding{};
770 int64_t m_free_grid{};
771 double m_step_frames{1.};
773 int m_channel_pressure{-1};
775 uint64_t m_free_seed{};