66template <std::
size_t MaximumClusterNotes = 16>
class Strum {
68 using Block = note_schedule::Block;
80 if (
spec.window_samples < 0 ||
spec.timing_jitter_samples < 0 ||
81 spec.velocity_jitter > 127 || !std::isfinite(
spec.shape_exponent) ||
82 spec.shape_exponent <= 0.0 ||
spec.velocity_tilt < -127 ||
spec.velocity_tilt > 127)
85 :
spec.spacing_milliseconds > 0;
91 ? note_schedule::samples_for_duration(block,
spec.spacing_interval)
92 : note_schedule::samples_for_milliseconds(block,
spec.spacing_milliseconds);
99 std::size_t count)
noexcept {
100 if (count <= 1 || index == 0)
102 const auto last =
static_cast<std::int64_t
>(count - 1);
103 const auto position =
static_cast<std::int64_t
>(index);
106 exponent = std::max(0.0, exponent);
107 return static_cast<std::int64_t
>(std::llround(
108 std::pow(
static_cast<double>(position) /
static_cast<double>(last), exponent) *
109 static_cast<double>(last)));
111 exponent = std::max(0.0, exponent);
113 static_cast<std::int64_t
>(std::llround(
114 std::pow(
static_cast<double>(last - position) /
static_cast<double>(last),
116 static_cast<double>(last)));
125 std::size_t count)
noexcept {
132 constexpr bool valid() const noexcept {
141 const Block& block)
noexcept {
143 return {0, input.size(), 0,
false};
151 const std::int64_t block_end =
153 for (
const auto& event : input) {
154 const auto absolute =
156 advance_to(absolute, block, output, report);
157 const bool is_attack =
event.is_note_on() &&
event.velocity() != 0;
158 const bool is_release =
159 event.is_note_off() || (
event.is_note_on() && event.velocity() == 0);
160 if (!is_attack && !is_release) {
165 admit(event, absolute, block, output, report);
170 force_out(event.channel(), event.note(), absolute, block, output, report);
173 advance_to(block_end, block, output, report);
188 close_cluster(block);
189 for (
auto& slot : cluster_) {
192 if (!emit_slot(slot, block.sample_start.value, block, output, report)) {
201 if (report.
complete && pending_spec_) {
202 spec_ = *pending_spec_;
204 pending_spec_.reset();
210 return flush(output, block);
214 const Block& block)
noexcept {
217 return {0, 0, 0,
false};
219 auto report =
flush(output, block);
220 if (report.complete) {
223 pending_spec_.reset();
225 pending_spec_ =
spec;
231 for (
const auto& slot : cluster_)
240 std::int64_t arrival = 0;
241 std::int64_t scheduled = 0;
242 std::uint8_t order = 0;
243 bool scheduled_yet =
false;
247 static std::uint64_t draw(std::uint64_t seed, std::int64_t coordinate, std::uint64_t lane,
248 std::uint64_t stream, std::uint64_t span)
noexcept {
251 const timebase::RandomCoordinate point{timebase::TickPosition{coordinate}, lane, 0, stream};
255 void admit(
const MidiEvent& event, std::int64_t absolute,
const Block& block,
256 MidiBuffer& output, MidiUtilityProcessReport& report)
noexcept {
257 if (open_ && absolute - cluster_start_ > spec_.window_samples)
258 close_cluster(block);
260 cluster_start_ = absolute;
263 Slot* slot = free_slot();
264 if (slot ==
nullptr) {
268 close_cluster(block);
269 flush_scheduled(std::numeric_limits<std::int64_t>::max(), block, output, report);
273 *slot = {event, absolute, 0, 0,
false,
true};
279 void close_cluster(
const Block& block)
noexcept {
281 std::array<Slot*, MaximumClusterNotes> members{};
282 std::size_t members_count = 0;
283 for (
auto& slot : cluster_)
284 if (slot.active && !slot.scheduled_yet && members_count < MaximumClusterNotes)
285 members[members_count++] = &slot;
286 if (members_count == 0)
288 order_members(members, members_count);
292 for (std::size_t index = 0;
index < members_count; ++
index) {
293 auto* slot = members[
index];
294 const auto step =
shaped_step(spec_.shape, spec_.shape_exponent, index, members_count);
296 if (spec_.timing_jitter_samples > 0) {
297 const auto lane =
static_cast<std::uint64_t
>(
300 at,
static_cast<std::int64_t
>(
302 static_cast<std::uint64_t
>(spec_.timing_jitter_samples) + 1)));
304 slot->scheduled = std::max(at, slot->arrival);
305 slot->order =
static_cast<std::uint8_t
>(
index);
306 slot->scheduled_yet =
true;
307 if (spec_.velocity_jitter != 0)
308 apply_velocity_jitter(*slot);
309 if (spec_.velocity_tilt != 0)
310 apply_velocity_tilt(*slot, index, members_count);
312 alternate_flip_ = !alternate_flip_;
315 void apply_velocity_jitter(Slot& slot)
noexcept {
316 const auto lane =
static_cast<std::uint64_t
>(
318 const auto span =
static_cast<std::uint64_t
>(spec_.velocity_jitter) * 2 + 1;
319 const auto value =
static_cast<std::int32_t
>(
321 const auto offset =
value -
static_cast<std::int32_t
>(spec_.velocity_jitter);
322 const auto velocity =
static_cast<std::uint8_t
>(
323 std::clamp(
static_cast<std::int32_t
>(slot.event.velocity()) + offset, 1, 127));
325 shaped.timestamp = slot.event.timestamp;
329 void apply_velocity_tilt(Slot& slot, std::size_t index, std::size_t count)
noexcept {
332 const auto numerator =
static_cast<std::int32_t
>(
index);
333 const auto denominator =
static_cast<std::int32_t
>(
count - 1);
334 const auto tilt =
static_cast<std::int32_t
>(spec_.velocity_tilt);
335 const auto offset = (tilt * numerator) / denominator;
336 const auto velocity =
static_cast<std::uint8_t
>(
337 std::clamp(
static_cast<std::int32_t
>(slot.event.velocity()) + offset, 1, 127));
339 shaped.timestamp = slot.event.timestamp;
351 template <
typename Less>
352 static void stable_order(std::array<Slot*, MaximumClusterNotes>& members, std::size_t count,
353 Less less)
noexcept {
354 for (std::size_t i = 1;
i <
count; ++
i) {
355 Slot*
const held = members[
i];
358 while (j > 0 &&
less(held, members[j - 1])) {
359 members[
j] = members[
j - 1];
366 void order_members(std::array<Slot*, MaximumClusterNotes>& members,
367 std::size_t count)
noexcept {
368 switch (effective_direction()) {
371 stable_order(members, count,
372 [](
const Slot* a,
const Slot* b) {
return a->arrival <
b->arrival; });
375 stable_order(members, count, [](
const Slot* a,
const Slot* b) {
376 return a->event.note() <
b->event.note();
380 stable_order(members, count, [](
const Slot* a,
const Slot* b) {
381 return a->event.note() >
b->event.note();
391 stable_order(members, count, [
this](
const Slot* a,
const Slot* b) {
392 return random_key(*a) < random_key(*b);
398 return spec_.direction;
402 std::uint64_t random_key(
const Slot& slot)
const noexcept {
405 const timebase::RandomCoordinate point{timebase::TickPosition{cluster_start_}, lane, 0,
410 void advance_to(std::int64_t boundary,
const Block& block, MidiBuffer& output,
411 MidiUtilityProcessReport& report)
noexcept {
412 if (open_ && boundary - cluster_start_ > spec_.window_samples)
413 close_cluster(block);
414 flush_scheduled(boundary, block, output, report);
417 void flush_scheduled(std::int64_t boundary,
const Block& block, MidiBuffer& output,
418 MidiUtilityProcessReport& report)
noexcept {
420 Slot* earliest =
nullptr;
421 for (
auto& slot : cluster_) {
422 if (!slot.active || !slot.scheduled_yet || slot.scheduled >= boundary)
424 if (earliest ==
nullptr || slot.scheduled < earliest->scheduled ||
425 (slot.scheduled == earliest->scheduled && slot.order < earliest->order))
428 if (earliest ==
nullptr)
430 if (!emit_slot(*earliest, earliest->scheduled, block, output, report))
435 void force_out(std::uint8_t channel, std::uint8_t note, std::int64_t before,
const Block& block,
436 MidiBuffer& output, MidiUtilityProcessReport& report)
noexcept {
437 for (
auto& slot : cluster_) {
438 if (!slot.active || slot.event.channel() != channel || slot.event.note() != note)
440 const auto at = slot.scheduled_yet ? std::min(slot.scheduled, before) : before;
441 emit_slot(slot, at, block, output, report);
445 bool emit_slot(Slot& slot, std::int64_t at,
const Block& block, MidiBuffer& output,
446 MidiUtilityProcessReport& report)
noexcept {
447 const auto offset = note_schedule::offset_in_block(block, at);
456 Slot* free_slot() noexcept {
457 for (
auto& slot : cluster_)
465 std::optional<StrumSpec> pending_spec_{};
466 std::array<Slot, MaximumClusterNotes> cluster_{};
467 std::size_t count_ = 0;
468 std::int64_t cluster_start_ = 0;
470 bool alternate_flip_ =
false;