135 static_assert(MaximumHeldNotes > 0);
136 static_assert(MaximumOctaves > 0);
137 static_assert(MaximumStepsPerBlock > 0);
138 static_assert(MaximumOwnershipRuns >= MaximumHeldNotes);
139 static_assert(MaximumHeldNotes <= std::numeric_limits<std::size_t>::max() / MaximumOctaves);
142 static_assert(MaximumStepsPerBlock <=
147 return {MaximumHeldNotes,
149 MaximumStepsPerBlock,
160 return spec.rate.value > 0 &&
spec.rate.value <= kMaximumRate &&
spec.octave_count > 0 &&
161 spec.octave_count <= MaximumOctaves &&
spec.gate.numerator > 0 &&
162 spec.gate.denominator > 0 &&
spec.gate.numerator <=
spec.gate.denominator &&
173 constexpr bool valid() const noexcept {
181 std::size_t count = 0;
182 for (
const auto& note : held_)
189 return physical_count_;
192 std::size_t count = 0;
193 for (
const auto& note : sounding_)
202 return {0, input.size(), 0,
false};
206 retain_unprocessed_input(input);
207 return {0, input.size(), 0,
false};
210 current_block_ = █
211 steps_this_block_ = 0;
212 const bool discontinuity = detects_discontinuity(block);
214 clock_initialized_ =
false;
215 if (!release_all(output, report, 0)) {
216 retain_unprocessed_input(input);
221 if (!drain_pending_spec(output, report)) {
222 retain_unprocessed_input(input);
227 const bool should_play =
230 if (!release_all(output, report, 0)) {
231 retain_unprocessed_input(input);
235 clock_initialized_ =
false;
236 }
else if (!clock_initialized_) {
237 align_clock(block.tick_start);
240 std::size_t input_index = 0;
241 while (input_index < input.size()) {
242 const auto offset = input[input_index].sample_offset;
243 if (offset < 0 || offset >= block.sample_count) {
246 retain_input_event(input[input_index]);
250 if (!render_until(offset,
252 should_play, output, report)) {
253 retain_unprocessed_input(input, input_index);
257 const auto group_end = equal_offset_end(input, input_index, offset);
258 for (; input_index < group_end; ++input_index) {
259 if (!consume_input_event(input[input_index], output, report)) {
260 retain_unprocessed_input(input, input_index + 1);
266 !render_until(offset,
true,
true, output, report)) {
267 retain_unprocessed_input(input, input_index);
273 if (!render_until(block.sample_count,
false, should_play, output, report)) {
291 release_all(output, report, 0);
292 clock_initialized_ =
false;
301 last_block_valid_ =
false;
302 pending_spec_.reset();
313 return {0, 0, 0,
false};
316 pending_spec_ =
spec;
319 if (!release_all(output, report, 0)) {
320 pending_spec_ =
spec;
330 std::uint8_t channel = 0;
331 std::uint8_t note = 0;
332 std::uint8_t velocity = 0;
333 std::uint64_t serial = 0;
334 bool physical =
false;
339 std::uint8_t channel = 0;
340 std::uint8_t note = 0;
341 std::uint8_t velocity = 0;
342 std::uint64_t serial = 0;
345 struct SoundingNote {
346 std::uint8_t channel = 0;
347 std::uint8_t note = 0;
348 std::int64_t release_sample = 0;
352 struct OwnershipRun {
353 std::uint16_t key = 0;
354 std::uint32_t count = 0;
355 bool accepted =
false;
358 static constexpr bool is_attack(
const MidiEvent& event)
noexcept {
359 return event.is_note_on() &&
event.velocity() != 0;
362 static constexpr bool is_release(
const MidiEvent& event)
noexcept {
363 return event.is_note_off() || (
event.is_note_on() && event.velocity() == 0);
366 static bool valid_block(
const ArpeggiatorBlock& block)
noexcept {
367 if (
block.sample_count < 0)
369 if (
block.tempo_map !=
nullptr)
370 return block.tempo_map->sample_rate().valid();
374 static bool ordered(
const MidiBuffer& input)
noexcept {
375 std::int32_t previous = std::numeric_limits<std::int32_t>::min();
376 for (
const auto& event : input) {
377 if (event.sample_offset < previous)
379 previous =
event.sample_offset;
384 static std::size_t equal_offset_end(
const MidiBuffer& input, std::size_t begin,
385 std::int32_t offset)
noexcept {
387 while (end < input.size() && input[end].sample_offset == offset)
392 bool detects_discontinuity(
const ArpeggiatorBlock& block)
const noexcept {
395 if (!last_block_valid_)
397 if (
block.sample_start.value != last_sample_end_ ||
block.playing != last_playing_)
399 return arpeggiator_detail::distance_exceeds(
block.tick_start.value, projected_tick_end_, 2);
402 void finish_block(
const ArpeggiatorBlock& block)
noexcept {
405 if (
block.tempo_map !=
nullptr) {
406 const auto anchor =
block.tempo_map->fractional_ticks_to_samples(
407 static_cast<long double>(
block.tick_start.value));
408 projected_tick_end_ =
409 arpeggiator_detail::saturating_round(
block.tempo_map->fractional_samples_to_ticks(
410 anchor +
static_cast<long double>(
block.sample_count)));
412 const auto ticks =
static_cast<long double>(
block.sample_count) *
413 static_cast<long double>(
block.tempo_bpm) *
415 (60.0L *
block.sample_rate.as_long_double());
417 block.tick_start.value, arpeggiator_detail::saturating_round(ticks));
419 last_playing_ =
block.playing;
420 last_block_valid_ =
true;
421 current_block_ =
nullptr;
424 void align_clock(timebase::TickPosition tick)
noexcept {
425 const auto step = spec_.rate.value;
426 const auto quotient = arpeggiator_detail::floor_div(tick.value, step);
427 auto index = quotient == std::numeric_limits<std::int64_t>::min() ? quotient : quotient - 1;
428 for (
int attempts = 0; attempts < 4; ++attempts, ++
index) {
429 const auto candidate = swung_boundary(index);
431 next_step_index_ =
index;
433 clock_initialized_ =
true;
437 next_step_index_ =
index;
438 next_step_tick_ = swung_boundary(index);
439 clock_initialized_ =
true;
442 timebase::TickPosition swung_boundary(std::int64_t index)
const noexcept {
443 const auto straight = timebase::TickPosition{
444 arpeggiator_detail::saturating_multiply(index, spec_.rate.value)};
449 void advance_clock() noexcept {
450 if (next_step_index_ != std::numeric_limits<std::int64_t>::max())
452 next_step_tick_ = swung_boundary(next_step_index_);
455 std::int64_t sample_for_tick(timebase::TickPosition tick)
const noexcept {
456 const auto&
block = *current_block_;
457 long double delta = 0.0L;
458 if (
block.tempo_map !=
nullptr) {
459 const auto event_sample =
460 block.tempo_map->fractional_ticks_to_samples(
static_cast<long double>(tick.value));
461 const auto anchor_sample =
block.tempo_map->fractional_ticks_to_samples(
462 static_cast<long double>(
block.tick_start.value));
463 delta = event_sample - anchor_sample;
465 const auto tick_delta = arpeggiator_detail::difference_as_long_double(
466 tick.value,
block.tick_start.value);
467 delta = tick_delta *
block.sample_rate.as_long_double() * 60.0L /
468 (
static_cast<long double>(
block.tempo_bpm) *
471 constexpr auto minimum =
static_cast<long double>(std::numeric_limits<std::int64_t>::min());
472 constexpr auto maximum =
static_cast<long double>(std::numeric_limits<std::int64_t>::max());
473 const auto absolute =
static_cast<long double>(
block.sample_start.value) + delta;
474 if (absolute <= minimum)
475 return std::numeric_limits<std::int64_t>::min();
476 if (absolute >= maximum)
477 return std::numeric_limits<std::int64_t>::max();
478 return static_cast<std::int64_t
>(std::llround(absolute));
481 bool render_until(std::int32_t offset,
bool include_step_at_offset,
bool playing,
482 MidiBuffer& output, MidiUtilityProcessReport& report)
noexcept {
483 const auto absolute_limit =
485 const bool limit_is_inside_block = offset < current_block_->sample_count;
487 spec_.gate.numerator == spec_.gate.denominator;
489 return release_due(absolute_limit, limit_is_inside_block, output, report);
491 while (clock_initialized_) {
492 const auto step_sample = sample_for_tick(next_step_tick_);
493 const bool due = include_step_at_offset ? step_sample <= absolute_limit
494 : step_sample < absolute_limit;
497 if (!release_due(step_sample, !full_gate_tie, output, report))
499 if (step_sample < current_block_->sample_start.value) {
501 report.complete =
false;
505 if (steps_this_block_ == MaximumStepsPerBlock) {
506 report.complete =
false;
508 const auto tick = tick_at_block_offset(offset);
512 const auto following_tick =
513 swung_boundary(next_step_index_ == std::numeric_limits<std::int64_t>::max()
515 : next_step_index_ + 1);
516 if (!fire_step(step_sample, next_step_tick_, following_tick, output, report))
521 const bool step_at_limit =
522 clock_initialized_ && sample_for_tick(next_step_tick_) == absolute_limit;
523 const bool include_releases_at_limit =
524 limit_is_inside_block && !(full_gate_tie && step_at_limit);
525 return release_due(absolute_limit, include_releases_at_limit, output, report);
528 timebase::TickPosition tick_at_block_offset(std::int32_t offset)
const noexcept {
529 const auto&
block = *current_block_;
530 if (
block.tempo_map !=
nullptr) {
531 const auto anchor =
block.tempo_map->fractional_ticks_to_samples(
532 static_cast<long double>(
block.tick_start.value));
533 const auto tick =
block.tempo_map->fractional_samples_to_ticks(
534 anchor +
static_cast<long double>(offset));
535 return {arpeggiator_detail::saturating_round(tick)};
537 const auto ticks =
static_cast<long double>(offset) *
538 static_cast<long double>(
block.tempo_bpm) *
540 (60.0L *
block.sample_rate.as_long_double());
542 arpeggiator_detail::saturating_round(ticks))};
545 bool consume_input_event(
const MidiEvent& event, MidiBuffer& output,
546 MidiUtilityProcessReport& report)
noexcept {
547 if (is_attack(event)) {
548 const bool starts_latched_phrase =
550 if (starts_latched_phrase) {
551 const bool releases_complete = release_all(output, report, event.sample_offset);
552 clear_latched_notes();
553 retain_attack(event, report);
554 return releases_complete;
556 retain_attack(event, report);
559 if (is_release(event)) {
560 consume_release(event.channel(), event.note());
562 return release_all(output, report, event.sample_offset);
568 void retain_input_event(
const MidiEvent& event)
noexcept {
569 if (is_attack(event)) {
570 const auto key =
static_cast<std::uint16_t
>(
572 record_ownership(key,
false);
573 }
else if (is_release(event)) {
574 consume_release(event.channel(), event.note());
578 void retain_attack(
const MidiEvent& event, MidiUtilityProcessReport& report)
noexcept {
581 const auto slot = free_held_slot();
582 const bool accepted = slot !=
nullptr && can_record_ownership(key,
true);
583 if (!record_ownership(key, accepted) || !accepted) {
585 report.complete =
false;
588 if (next_serial_ == std::numeric_limits<std::uint64_t>::max())
590 *slot = {
event.channel(),
event.note(),
event.velocity(), next_serial_++,
true,
true};
594 void retain_unprocessed_input(
const MidiBuffer& input, std::size_t begin = 0) noexcept {
595 for (std::size_t index = begin;
index < input.size(); ++
index)
596 retain_input_event(input[index]);
599 HeldNote* free_held_slot() noexcept {
600 for (
auto& note : held_)
606 bool can_record_ownership(std::uint16_t key,
bool accepted)
const noexcept {
607 if (overflow_suppressed_[key] != 0)
609 if (ownership_size_ < ownership_.size())
614 bool record_ownership(std::uint16_t key,
bool accepted)
noexcept {
615 if (overflow_suppressed_[key] != 0 || ownership_size_ == ownership_.size()) {
616 if (accepted || overflow_suppressed_[key] == std::numeric_limits<std::uint32_t>::max())
618 ++overflow_suppressed_[key];
621 for (std::size_t index = ownership_size_;
index != 0; --
index) {
622 auto& previous = ownership_[
index - 1];
623 if (previous.key != key)
625 if (previous.accepted == accepted &&
626 previous.count != std::numeric_limits<std::uint32_t>::max()) {
632 ownership_[ownership_size_++] = {key, 1,
accepted};
636 void consume_release(std::uint8_t channel, std::uint8_t note)
noexcept {
638 for (std::size_t index = 0;
index < ownership_size_; ++
index) {
643 if (--
run.count == 0)
644 erase_ownership(index);
646 release_oldest_physical(key);
649 if (overflow_suppressed_[key] != 0)
650 --overflow_suppressed_[key];
653 void release_oldest_physical(std::uint16_t key)
noexcept {
654 HeldNote*
oldest =
nullptr;
655 for (
auto& note : held_) {
656 if (!note.active || !note.physical ||
659 if (oldest ==
nullptr || note.serial <
oldest->serial)
662 if (oldest ==
nullptr)
670 void erase_ownership(std::size_t index)
noexcept {
671 for (std::size_t next = index + 1; next < ownership_size_; ++next)
672 ownership_[next - 1] = ownership_[next];
676 void clear_latched_notes() noexcept {
677 for (
auto& note : held_)
678 if (note.active && !note.physical)
682 void clear_held_state() noexcept {
685 overflow_suppressed_.fill(0);
690 void rebase_serials() noexcept {
691 std::array<std::size_t, MaximumHeldNotes> indices{};
692 std::size_t
size = 0;
693 for (std::size_t index = 0;
index < held_.size(); ++
index)
694 if (held_[index].active)
696 std::sort(indices.begin(), indices.begin() + size, [&](std::size_t lhs, std::size_t rhs) {
697 return held_[lhs].serial < held_[rhs].serial;
699 for (std::size_t serial = 0; serial <
size; ++serial)
700 held_[indices[serial]].serial = serial;
704 std::size_t build_pattern() noexcept {
705 std::array<std::size_t, MaximumHeldNotes> held_indices{};
706 std::size_t held_size = 0;
707 for (std::size_t index = 0;
index < held_.size(); ++
index)
708 if (held_[index].active)
709 held_indices[held_size++] =
index;
710 std::sort(held_indices.begin(), held_indices.begin() + held_size,
711 [&](std::size_t lhs, std::size_t rhs) {
712 return held_[lhs].serial < held_[rhs].serial;
714 std::size_t
size = 0;
715 for (std::size_t octave = 0;
octave < spec_.octave_count; ++
octave) {
716 for (std::size_t held_index = 0; held_index < held_size; ++held_index) {
717 const auto& held = held_[held_indices[held_index]];
718 const auto pitch =
static_cast<int>(held.note) +
722 const auto order_serial =
static_cast<std::uint64_t
>(
size);
723 pattern_[
size++] = {held.channel,
static_cast<std::uint8_t
>(pitch), held.velocity,
728 std::sort(pattern_.begin(), pattern_.begin() + size,
729 [](
const PatternNote& lhs,
const PatternNote& rhs) {
730 if (lhs.note != rhs.note)
731 return lhs.note < rhs.note;
732 if (lhs.channel != rhs.channel)
733 return lhs.channel < rhs.channel;
734 return lhs.serial < rhs.serial;
740 std::size_t selected_pattern_index(std::size_t size)
const noexcept {
742 auto result =
value %
static_cast<std::int64_t
>(
size);
744 result +=
static_cast<std::int64_t
>(
size);
745 return static_cast<std::size_t
>(result);
747 switch (spec_.order) {
753 const auto period =
size * 2 - 2;
754 auto position = next_step_index_ %
static_cast<std::int64_t
>(period);
756 position +=
static_cast<std::int64_t
>(period);
757 const auto index =
static_cast<std::size_t
>(position);
761 return static_cast<std::size_t
>(
762 arpeggiator_detail::mix(spec_.random_seed ^
763 static_cast<std::uint64_t
>(next_step_index_)) %
773 bool fire_step(std::int64_t step_sample, timebase::TickPosition step_tick,
774 timebase::TickPosition following_tick, MidiBuffer& output,
775 MidiUtilityProcessReport& report)
noexcept {
776 const auto pattern_size = build_pattern();
777 if (pattern_size == 0)
778 return release_all_at_sample(output, report, step_sample);
780 const auto unsigned_interval = following_tick.value <= step_tick.value
782 :
static_cast<std::uint64_t
>(following_tick.value) -
783 static_cast<std::uint64_t
>(step_tick.value);
784 const auto interval = std::max<std::int64_t>(
785 1, unsigned_interval >=
786 static_cast<std::uint64_t
>(std::numeric_limits<std::int64_t>::max())
787 ? std::numeric_limits<std::int64_t>::max()
788 :
static_cast<std::int64_t
>(unsigned_interval));
789 const auto gate_ticks = std::max<std::int64_t>(
790 1, arpeggiator_detail::saturating_round(
791 (
static_cast<long double>(interval) * spec_.gate.numerator) /
792 spec_.gate.denominator));
793 const auto release_tick = timebase::TickPosition{
795 const auto release_sample = sample_for_tick(release_tick);
799 for (std::size_t index = 0;
index < pattern_size; ++
index)
800 selected_[selected_count_++] = pattern_[index];
802 selected_[selected_count_++] = pattern_[selected_pattern_index(pattern_size)];
806 spec_.gate.numerator == spec_.gate.denominator;
807 if (!release_for_step(selected_, selected_count_, may_tie, step_sample, output, report))
810 const auto offset = block_offset(step_sample);
811 for (std::size_t index = 0;
index < selected_count_; ++
index) {
812 const auto& note = selected_[
index];
813 if (may_tie && sounding_contains(note.channel, note.note)) {
814 extend_sounding(note.channel, note.note, release_sample);
817 auto* slot = free_sounding_slot();
818 if (slot ==
nullptr) {
820 report.complete =
false;
824 attack.sample_offset = offset;
827 *slot = {note.channel, note.note, release_sample,
true};
832 bool release_for_step(
const std::array<PatternNote, kMaximumPatternNotes>& next,
833 std::size_t next_count,
bool may_tie, std::int64_t sample,
834 MidiBuffer& output, MidiUtilityProcessReport& report)
noexcept {
835 for (
auto& sounding : sounding_) {
836 if (!sounding.active)
840 for (std::size_t index = 0;
index < next_count; ++
index) {
841 if (next[index].channel == sounding.channel &&
842 next[index].note == sounding.note) {
850 if (!emit_release(sounding, sample, output, report))
856 bool release_due(std::int64_t sample,
bool include_at_sample, MidiBuffer& output,
857 MidiUtilityProcessReport& report)
noexcept {
858 for (
auto& sounding : sounding_) {
859 const bool due = sounding.release_sample < sample ||
860 (include_at_sample && sounding.release_sample == sample);
861 if (sounding.active && due &&
862 !emit_release(sounding, sounding.release_sample, output, report))
868 bool release_all(MidiBuffer& output, MidiUtilityProcessReport& report,
869 std::int32_t offset)
noexcept {
871 current_block_ !=
nullptr
874 return release_all_at_sample(output, report, sample);
877 bool release_all_at_sample(MidiBuffer& output, MidiUtilityProcessReport& report,
878 std::int64_t sample)
noexcept {
879 for (
auto& sounding : sounding_)
880 if (sounding.active && !emit_release(sounding, sample, output, report))
885 bool emit_release(SoundingNote& sounding, std::int64_t sample, MidiBuffer& output,
886 MidiUtilityProcessReport& report)
noexcept {
888 release.sample_offset = block_offset(sample);
890 sounding.release_sample = sample;
898 std::int32_t block_offset(std::int64_t sample)
const noexcept {
899 if (current_block_ ==
nullptr)
901 const auto start = current_block_->sample_start.value;
904 const auto maximum_offset = std::max<std::int32_t>(0, current_block_->sample_count - 1);
906 static_cast<std::uint64_t
>(sample) -
static_cast<std::uint64_t
>(start);
907 if (difference >=
static_cast<std::uint64_t
>(maximum_offset))
908 return maximum_offset;
912 SoundingNote* free_sounding_slot() noexcept {
913 for (
auto& note : sounding_)
919 bool sounding_contains(std::uint8_t channel, std::uint8_t note)
const noexcept {
920 for (
const auto& sounding : sounding_)
921 if (sounding.active && sounding.channel == channel && sounding.note == note)
926 void extend_sounding(std::uint8_t channel, std::uint8_t note,
927 std::int64_t release_sample)
noexcept {
928 for (
auto& sounding : sounding_)
929 if (sounding.active && sounding.channel == channel && sounding.note == note)
930 sounding.release_sample = std::max(sounding.release_sample, release_sample);
933 bool drain_pending_spec(MidiBuffer& output, MidiUtilityProcessReport& report)
noexcept {
936 if (!release_all(output, report, 0))
938 const auto replacement = *pending_spec_;
939 pending_spec_.reset();
940 apply_spec(replacement);
944 void apply_spec(
const ArpeggiatorSpec&
spec)
noexcept {
945 const bool disabling_latch = spec_.latch && !
spec.latch;
948 clock_initialized_ =
false;
950 clear_latched_notes();
953 ArpeggiatorSpec spec_{};
955 std::optional<ArpeggiatorSpec> pending_spec_;
956 std::array<HeldNote, MaximumHeldNotes> held_{};
957 std::array<PatternNote, kMaximumPatternNotes> pattern_{};
958 std::array<PatternNote, kMaximumPatternNotes> selected_{};
959 std::size_t selected_count_ = 0;
960 std::array<SoundingNote, kMaximumPatternNotes> sounding_{};
961 std::array<OwnershipRun, MaximumOwnershipRuns> ownership_{};
962 std::array<std::uint32_t, 16 * 128> overflow_suppressed_{};
963 std::size_t ownership_size_ = 0;
964 std::size_t physical_count_ = 0;
965 std::uint64_t next_serial_ = 0;
966 std::int64_t next_step_index_ = 0;
967 timebase::TickPosition next_step_tick_{};
968 bool clock_initialized_ =
false;
969 const ArpeggiatorBlock* current_block_ =
nullptr;
970 std::int64_t last_sample_end_ = 0;
971 std::int64_t projected_tick_end_ = 0;
972 bool last_playing_ =
false;
973 bool last_block_valid_ =
false;
974 std::size_t steps_this_block_ = 0;