Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
note_utility_kernels.hpp
Go to the documentation of this file.
1#pragma once
2
4
5namespace pulp::midi {
6
8 std::int64_t length_samples = 1;
9};
10
11template <std::size_t MaximumActiveNotes = 128> class NoteLengthShaper {
12 public:
13 static_assert(MaximumActiveNotes < std::numeric_limits<std::uint32_t>::max());
19
20 static constexpr bool valid_spec(NoteLengthSpec spec) noexcept {
21 return spec.length_samples > 0;
22 }
23 explicit constexpr NoteLengthShaper(NoteLengthSpec spec = {}) noexcept
24 : spec_(spec), valid_(valid_spec(spec)) {}
25 constexpr bool valid() const noexcept {
26 return valid_;
27 }
28
30 timebase::SamplePosition block_start,
31 std::int32_t block_samples) noexcept {
32 if (utility_detail::blocks_alias(input, output))
33 return {0, input.size(), 0, false};
35 current_block_start_ = block_start.value;
36 const auto block_sample_count = std::max<std::int64_t>(0, block_samples);
38 if (!utility_detail::ready(output)) {
39 account_unprocessed_input(input);
40 return {0, input.size(), 0, false};
41 }
42 if (!valid_ || block_samples < 0) {
43 account_unprocessed_input(input);
44 return {0, input.size(), 0, false};
45 }
46 if (!drain_release_debt(output, report)) {
47 account_unprocessed_input(input);
48 return report;
49 }
50 emit_due(output, report, block_start);
51 for (const auto& event : input) {
52 const auto absolute =
53 utility_detail::saturating_sample_add(block_start.value, event.sample_offset);
54 emit_due(output, report, {absolute});
55 const bool is_attack = event.is_note_on() && event.velocity() != 0;
56 const bool is_release = event.is_note_off() ||
57 (event.is_note_on() && event.velocity() == 0);
58 const bool is_note = is_attack || is_release;
59 const int event_key =
60 is_note ? utility_detail::key_index(event.channel(), event.note()) : 0;
61 if (is_note && quarantined_[event_key]) {
62 ++report.dropped;
63 report.complete = false;
64 continue;
65 }
66 if (is_attack) {
67 if (!release_matching(event.channel(), event.note(), event.sample_offset, output,
68 report)) {
69 track_suppressed(event_key);
70 continue;
71 }
72 Slot* slot = free_slot();
73 if (slot == nullptr) {
74 const int key = utility_detail::key_index(event.channel(), event.note());
75 if (!can_track_fail_open(key)) {
76 track_overflow_suppressed(key);
77 ++report.dropped;
78 report.complete = false;
79 } else if (utility_detail::emit(output, event, report)) {
80 track_passthrough(key);
81 } else {
82 track_suppressed(key);
83 }
84 continue;
85 }
86 if (!utility_detail::emit(output, event, report)) {
87 track_suppressed(utility_detail::key_index(event.channel(), event.note()));
88 continue;
89 }
90 if (next_serial_ == std::numeric_limits<std::uint32_t>::max())
91 rebase_slot_serials();
92 *slot = {event.channel(),
93 event.note(),
94 event.velocity(),
95 utility_detail::saturating_sample_add(absolute, spec_.length_samples),
96 block_start.value,
97 event.sample_offset,
98 next_serial_++,
99 true};
100 } else if (is_release) {
101 const int key = event_key;
102 release_fail_open(key, event, output, report);
103 } else {
104 utility_detail::emit(output, event, report);
105 }
106 }
107 const auto last_sample = block_sample_count > 0 ? block_sample_count - 1 : 0;
108 emit_due(output, report,
109 {utility_detail::saturating_sample_add(block_start.value, last_sample)});
110 utility_detail::copy_sidecars(input, output, report);
111 output.sort();
112 return report;
113 }
114
118 if (!utility_detail::ready(output))
119 return {0, 0, 0, false};
120 if (!drain_release_debt(output, report))
121 return report;
122 for (auto& slot : slots_) {
123 if (!slot.active)
124 continue;
125 auto off = MidiEvent::note_off(slot.channel, slot.note);
126 if (!utility_detail::emit(output, off, report)) {
127 ++report.deferred;
128 return report;
129 }
130 slot = {};
131 }
132 for (int key = 0; key < 16 * 128; ++key) {
133 while (passthrough_depth_[key] != 0) {
134 if (!utility_detail::emit(output,
135 MidiEvent::note_off(static_cast<std::uint8_t>(key / 128),
136 static_cast<std::uint8_t>(key % 128)),
137 report)) {
138 ++report.deferred;
139 report.complete = false;
140 return report;
141 }
142 --passthrough_depth_[key];
143 }
144 }
145 suppressed_depth_.fill(0);
146 overflow_suppressed_.fill(0);
147 ownership_run_size_ = 0;
148 quarantined_.fill(false);
149 if (empty() && pending_spec_) {
150 spec_ = *pending_spec_;
151 valid_ = true;
152 pending_spec_.reset();
153 }
154 report.complete = empty();
155 return report;
156 }
157
159 return flush(output);
160 }
161
163 if (!valid_spec(spec)) {
165 return {0, 0, 0, false};
166 }
167 auto report = flush(output);
168 if (report.complete) {
169 spec_ = spec;
170 valid_ = true;
171 } else {
172 pending_spec_ = spec;
173 }
174 return report;
175 }
176
177 bool empty() const noexcept {
178 for (const auto& slot : slots_)
179 if (slot.active)
180 return false;
181 for (const auto debt : release_debt_)
182 if (debt != 0)
183 return false;
184 for (const auto depth : passthrough_depth_)
185 if (depth != 0)
186 return false;
187 for (const bool quarantined : quarantined_)
188 if (quarantined)
189 return false;
190 return true;
191 }
192
193 private:
194 struct Slot {
195 std::uint8_t channel = 0;
196 std::uint8_t note = 0;
197 std::uint8_t velocity = 0;
198 std::int64_t release_sample = 0;
199 std::int64_t activation_block_start = 0;
200 std::int32_t minimum_release_offset = 0;
201 std::uint32_t serial = 0;
202 bool active = false;
203 };
204
205 struct FailOpenOwnershipRun {
206 std::uint16_t key = 0;
207 std::uint32_t count = 0;
208 bool forwarded = false;
209 };
210 static constexpr std::size_t kMaxOwnershipRuns = 4096;
211
212 template <typename Counter> static bool increment_checked(Counter& value) noexcept {
213 if (value != std::numeric_limits<Counter>::max()) {
214 ++value;
215 return true;
216 }
217 return false;
218 }
219
220 void track_suppressed(int key) noexcept {
221 if (overflow_suppressed_[key] != 0 ||
222 ownership_run_size_ == ownership_runs_.size()) {
223 track_overflow_suppressed(key);
224 return;
225 }
226 if (!increment_checked(suppressed_depth_[key]) ||
227 !append_ownership_run(key, false))
228 quarantined_[key] = true;
229 }
230
231 void track_passthrough(int key) noexcept {
232 if (!increment_checked(passthrough_depth_[key]) ||
233 !append_ownership_run(key, true))
234 quarantined_[key] = true;
235 }
236
237 bool can_track_fail_open(int key) const noexcept {
238 if (overflow_suppressed_[key] != 0) return false;
239 if (passthrough_depth_[key] == std::numeric_limits<std::uint32_t>::max() ||
240 suppressed_depth_[key] == std::numeric_limits<std::uint32_t>::max())
241 return false;
242 return ownership_run_size_ < ownership_runs_.size();
243 }
244
245 void track_overflow_suppressed(int key) noexcept {
246 if (!increment_checked(overflow_suppressed_[key]))
247 quarantined_[key] = true;
248 }
249
250 bool append_ownership_run(int key, bool forwarded) noexcept {
251 for (std::size_t i = ownership_run_size_; i != 0; --i) {
252 auto& previous = ownership_runs_[i - 1];
253 if (previous.key != key) continue;
254 if (previous.forwarded == forwarded &&
255 previous.count != std::numeric_limits<std::uint32_t>::max()) {
256 ++previous.count;
257 return true;
258 }
259 break;
260 }
261 if (ownership_run_size_ == ownership_runs_.size()) return false;
262 ownership_runs_[ownership_run_size_++] = {
263 static_cast<std::uint16_t>(key), 1, forwarded};
264 return true;
265 }
266
267 void release_fail_open(int key, const MidiEvent& event, MidiBuffer& output,
268 MidiUtilityProcessReport& report) noexcept {
269 release_fail_open_impl(key, [&](bool forwarded) {
270 if (forwarded && !utility_detail::emit(output, event, report))
271 track_release_debt(key);
272 });
273 }
274
275 template <typename Release>
276 void release_fail_open_impl(int key, Release&& release) noexcept {
277 for (std::size_t i = 0; i < ownership_run_size_; ++i) {
278 auto& run = ownership_runs_[i];
279 if (run.key != key) continue;
280 if (run.forwarded) {
281 --passthrough_depth_[key];
282 } else {
283 --suppressed_depth_[key];
284 }
285 release(run.forwarded);
286 if (--run.count == 0) erase_ownership_run(i);
287 return;
288 }
289 if (overflow_suppressed_[key] != 0)
290 --overflow_suppressed_[key];
291 }
292
293 void erase_ownership_run(std::size_t index) noexcept {
294 for (std::size_t i = index + 1; i < ownership_run_size_; ++i)
295 ownership_runs_[i - 1] = ownership_runs_[i];
296 --ownership_run_size_;
297 }
298
299 void track_release_debt(int key) noexcept {
300 if (!increment_checked(release_debt_[key]))
301 quarantined_[key] = true;
302 }
303
304 Slot* free_slot() noexcept {
305 for (auto& slot : slots_)
306 if (!slot.active)
307 return &slot;
308 return nullptr;
309 }
310
311 void rebase_slot_serials() noexcept {
312 std::array<std::uint32_t, MaximumActiveNotes> rebased{};
313 std::uint32_t rank = 1;
314 for (;;) {
315 std::size_t oldest_index = MaximumActiveNotes;
316 for (std::size_t i = 0; i < MaximumActiveNotes; ++i) {
317 if (!slots_[i].active || rebased[i] != 0)
318 continue;
319 if (oldest_index == MaximumActiveNotes ||
320 slots_[i].serial < slots_[oldest_index].serial)
321 oldest_index = i;
322 }
323 if (oldest_index == MaximumActiveNotes)
324 break;
325 rebased[oldest_index] = rank++;
326 }
327 for (std::size_t i = 0; i < MaximumActiveNotes; ++i)
328 slots_[i].serial = rebased[i];
329 next_serial_ = rank;
330 }
331
332 bool release_matching(std::uint8_t channel, std::uint8_t note, std::int32_t offset,
333 MidiBuffer& output, MidiUtilityProcessReport& report) noexcept {
334 for (;;) {
335 Slot* oldest = nullptr;
336 for (auto& slot : slots_) {
337 if (slot.active && slot.channel == channel && slot.note == note &&
338 (oldest == nullptr || slot.serial < oldest->serial))
339 oldest = &slot;
340 }
341 if (oldest == nullptr)
342 return true;
343 auto off = utility_detail::at(MidiEvent::note_off(channel, note), offset);
344 if (!utility_detail::emit(output, off, report))
345 return false;
346 *oldest = {};
347 }
348 }
349
350 void emit_due(MidiBuffer& output, MidiUtilityProcessReport& report,
351 timebase::SamplePosition until) noexcept {
352 for (;;) {
353 Slot* due = nullptr;
354 for (auto& slot : slots_) {
355 if (!slot.active || slot.release_sample > until.value)
356 continue;
357 if (due == nullptr || slot.release_sample < due->release_sample ||
358 (slot.release_sample == due->release_sample && slot.serial < due->serial))
359 due = &slot;
360 }
361 if (due == nullptr)
362 break;
363 std::int32_t offset = release_offset(*due);
364 auto off = utility_detail::at(MidiEvent::note_off(due->channel, due->note), offset);
365 if (!utility_detail::emit(output, off, report)) {
366 ++report.deferred;
367 break;
368 }
369 *due = {};
370 }
371 if (empty() && pending_spec_) {
372 spec_ = *pending_spec_;
373 valid_ = true;
374 pending_spec_.reset();
375 }
376 }
377
378 std::int32_t release_offset(const Slot& slot) const noexcept {
379 std::uint64_t distance = 0;
380 if (slot.release_sample > current_block_start_) {
381 distance = static_cast<std::uint64_t>(slot.release_sample) -
382 static_cast<std::uint64_t>(current_block_start_);
383 }
384 auto offset = static_cast<std::int32_t>(std::min<std::uint64_t>(
385 distance, static_cast<std::uint64_t>(std::numeric_limits<std::int32_t>::max())));
386 if (slot.activation_block_start == current_block_start_)
387 offset = std::max(offset, slot.minimum_release_offset);
388 return offset;
389 }
390
391 bool drain_release_debt(MidiBuffer& output, MidiUtilityProcessReport& report) noexcept {
392 for (int key = 0; key < 16 * 128; ++key) {
393 while (release_debt_[key] != 0) {
394 if (!utility_detail::emit(output,
395 MidiEvent::note_off(static_cast<std::uint8_t>(key / 128),
396 static_cast<std::uint8_t>(key % 128)),
397 report)) {
398 ++report.deferred;
399 return false;
400 }
401 --release_debt_[key];
402 }
403 }
404 return true;
405 }
406
407 void account_unprocessed_input(const MidiBuffer& input) noexcept {
408 for (const auto& event : input) {
409 const bool is_attack = event.is_note_on() && event.velocity() != 0;
410 const bool is_release = event.is_note_off() ||
411 (event.is_note_on() && event.velocity() == 0);
412 if (!is_attack && !is_release)
413 continue;
414 const int key = utility_detail::key_index(event.channel(), event.note());
415 if (quarantined_[key])
416 continue;
417 if (is_attack) {
418 track_suppressed(key);
419 } else if (is_release) {
420 release_fail_open_impl(key, [&](bool forwarded) {
421 if (forwarded) track_release_debt(key);
422 });
423 }
424 }
425 }
426
427 NoteLengthSpec spec_{};
428 bool valid_ = true;
429 std::optional<NoteLengthSpec> pending_spec_;
430 std::array<Slot, MaximumActiveNotes> slots_{};
431 std::array<std::uint32_t, 16 * 128> passthrough_depth_{};
432 std::array<std::uint32_t, 16 * 128> suppressed_depth_{};
433 std::array<std::uint32_t, 16 * 128> release_debt_{};
434 std::array<std::uint32_t, 16 * 128> overflow_suppressed_{};
435 std::array<bool, 16 * 128> quarantined_{};
436 std::array<FailOpenOwnershipRun, kMaxOwnershipRuns> ownership_runs_{};
437 std::size_t ownership_run_size_ = 0;
438 std::uint32_t next_serial_ = 1;
439 std::int64_t current_block_start_ = 0;
440};
441
442enum class MonophonicPriority : std::uint8_t { Low, High, Last };
443
449
451 bool active = false;
452 bool legato = false;
453 bool glide_active = false;
454 std::uint8_t channel = 0;
455 std::uint8_t note = 0;
456 float current_note = 0.0f;
457 float target_note = 0.0f;
458};
459
460template <typename Serial>
461inline constexpr bool monophonic_serial_capacity_supported = std::is_unsigned_v<Serial> &&
462 std::numeric_limits<Serial>::max() >
463 static_cast<std::uint64_t>(16 *
464 128);
465
466template <typename Serial = std::uint32_t> class BasicMonophonicNoteSelector {
467 public:
469 "Serial must represent every held MIDI key plus the next-order sentinel");
475
476 explicit constexpr BasicMonophonicNoteSelector(MonophonicSpec spec = {}) noexcept
477 : spec_(sanitize(spec)) {}
478
479 MidiUtilityProcessReport process(const MidiBuffer& input, MidiBuffer& output) noexcept {
480 if (utility_detail::blocks_alias(input, output))
481 return {0, input.size(), 0, false};
484 if (!utility_detail::ready(output))
485 return {0, input.size(), 0, false};
486 reconcile(0, output, report);
487 for (const auto& event : input) {
488 const bool is_attack = event.is_note_on() && event.velocity() != 0;
489 const bool is_release = event.is_note_off() ||
490 (event.is_note_on() && event.velocity() == 0);
491 if (is_attack) {
492 const int key = utility_detail::key_index(event.channel(), event.note());
493 if (held_depth_[key] != std::numeric_limits<std::uint32_t>::max())
494 ++held_depth_[key];
495 velocity_[key] = event.velocity();
496 if (next_serial_ == std::numeric_limits<Serial>::max())
497 rebase_serials();
498 serial_[key] = next_serial_++;
499 reconcile(event.sample_offset, output, report);
500 } else if (is_release) {
501 const int key = utility_detail::key_index(event.channel(), event.note());
502 if (held_depth_[key] != 0)
503 --held_depth_[key];
504 reconcile(event.sample_offset, output, report);
505 } else {
506 utility_detail::emit(output, event, report);
507 }
508 }
509 utility_detail::copy_sidecars(input, output, report);
510 output.sort();
511 return report;
512 }
513
514 void advance_glide(std::uint32_t samples, double sample_rate) noexcept {
515 if (!pitch_.active || !pitch_.glide_active)
516 return;
517 if (spec_.glide_seconds <= 0.0 || sample_rate <= 0.0 || !std::isfinite(sample_rate)) {
518 pitch_.current_note = pitch_.target_note;
519 pitch_.glide_active = false;
520 return;
521 }
522 const double coefficient =
523 std::exp(-static_cast<double>(samples) / (sample_rate * spec_.glide_seconds));
524 pitch_.current_note = static_cast<float>(
525 pitch_.target_note + (pitch_.current_note - pitch_.target_note) * coefficient);
526 if (std::abs(pitch_.current_note - pitch_.target_note) < 1.0e-5f) {
527 pitch_.current_note = pitch_.target_note;
528 pitch_.glide_active = false;
529 }
530 }
531
535 if (!utility_detail::ready(output))
536 return {0, 0, 0, false};
537 if (pitch_.active) {
538 if (!utility_detail::emit(output, MidiEvent::note_off(pitch_.channel, pitch_.note),
539 report)) {
540 report.deferred = 1;
541 return report;
542 }
543 }
544 held_depth_.fill(0);
545 serial_.fill(0);
546 pitch_ = {};
547 if (pending_spec_) {
548 spec_ = *pending_spec_;
549 pending_spec_.reset();
550 }
551 return report;
552 }
553
555 return flush(output);
556 }
557
559 auto report = flush(output);
560 if (report.complete)
561 spec_ = sanitize(spec);
562 else
563 pending_spec_ = sanitize(spec);
564 return report;
565 }
566
567 const MonophonicPitchState& pitch_state() const noexcept {
568 return pitch_;
569 }
570
571 private:
572 static constexpr MonophonicSpec sanitize(MonophonicSpec spec) noexcept {
573 if (!(spec.glide_seconds >= 0.0) || !std::isfinite(spec.glide_seconds))
574 spec.glide_seconds = 0.0;
575 return spec;
576 }
577
578 int selected_key() const noexcept {
579 int selected = -1;
580 for (int key = 0; key < 16 * 128; ++key) {
581 if (held_depth_[key] == 0)
582 continue;
583 if (selected < 0) {
584 selected = key;
585 continue;
586 }
587 const int note = key % 128;
588 const int selected_note = selected % 128;
589 if ((spec_.priority == MonophonicPriority::Low && note < selected_note) ||
590 (spec_.priority == MonophonicPriority::High && note > selected_note) ||
591 (spec_.priority == MonophonicPriority::Last && serial_[key] > serial_[selected]))
592 selected = key;
593 }
594 return selected;
595 }
596
597 void rebase_serials() noexcept {
598 std::array<Serial, 16 * 128> rebased{};
599 Serial rank = 1;
600 for (;;) {
601 int oldest = -1;
602 for (int key = 0; key < 16 * 128; ++key) {
603 if (held_depth_[key] == 0 || rebased[key] != 0)
604 continue;
605 if (oldest < 0 || serial_[key] < serial_[oldest] ||
606 (serial_[key] == serial_[oldest] && key < oldest))
607 oldest = key;
608 }
609 if (oldest < 0)
610 break;
611 rebased[oldest] = rank++;
612 }
613 serial_ = rebased;
614 next_serial_ = rank;
615 }
616
617 void reconcile(std::int32_t offset, MidiBuffer& output,
618 MidiUtilityProcessReport& report) noexcept {
619 const int next = selected_key();
620 const int current =
621 pitch_.active ? utility_detail::key_index(pitch_.channel, pitch_.note) : -1;
622 if (next == current)
623 return;
624 const bool overlapping = pitch_.active && next >= 0;
625 const float previous_pitch = pitch_.current_note;
626 if (pitch_.active) {
628 output,
629 utility_detail::at(MidiEvent::note_off(pitch_.channel, pitch_.note), offset),
630 report))
631 return;
632 pitch_.active = false;
633 }
634 if (next < 0) {
635 pitch_ = {};
636 return;
637 }
638 const auto channel = static_cast<std::uint8_t>(next / 128);
639 const auto note = static_cast<std::uint8_t>(next % 128);
641 output,
642 utility_detail::at(MidiEvent::note_on(channel, note, velocity_[next]), offset),
643 report))
644 return;
645 pitch_.active = true;
646 pitch_.legato = overlapping && spec_.legato;
647 pitch_.channel = channel;
648 pitch_.note = note;
649 pitch_.target_note = static_cast<float>(note);
650 pitch_.current_note = pitch_.legato ? previous_pitch : pitch_.target_note;
651 pitch_.glide_active = pitch_.legato && spec_.glide_seconds > 0.0;
652 }
653
654 MonophonicSpec spec_{};
655 std::optional<MonophonicSpec> pending_spec_;
656 std::array<std::uint32_t, 16 * 128> held_depth_{};
657 std::array<std::uint8_t, 16 * 128> velocity_{};
658 std::array<Serial, 16 * 128> serial_{};
659 Serial next_serial_ = 1;
660 MonophonicPitchState pitch_{};
661};
662
664
665} // namespace pulp::midi
Definition note_utility_kernels.hpp:466
MidiUtilityProcessReport reset(MidiBuffer &output) noexcept
Definition note_utility_kernels.hpp:554
const MonophonicPitchState & pitch_state() const noexcept
Definition note_utility_kernels.hpp:567
MidiUtilityProcessReport replace_spec(MonophonicSpec spec, MidiBuffer &output) noexcept
Definition note_utility_kernels.hpp:558
static constexpr MidiUtilityContract contract() noexcept
Definition note_utility_kernels.hpp:470
MidiUtilityProcessReport process(const MidiBuffer &input, MidiBuffer &output) noexcept
Definition note_utility_kernels.hpp:479
constexpr BasicMonophonicNoteSelector(MonophonicSpec spec={}) noexcept
Definition note_utility_kernels.hpp:476
MidiUtilityProcessReport flush(MidiBuffer &output) noexcept
Definition note_utility_kernels.hpp:532
void advance_glide(std::uint32_t samples, double sample_rate) noexcept
Definition note_utility_kernels.hpp:514
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
MidiUtilityProcessReport replace_spec(NoteLengthSpec spec, MidiBuffer &output) noexcept
Definition note_utility_kernels.hpp:162
MidiUtilityProcessReport process(const MidiBuffer &input, MidiBuffer &output, timebase::SamplePosition block_start, std::int32_t block_samples) noexcept
Definition note_utility_kernels.hpp:29
constexpr NoteLengthShaper(NoteLengthSpec spec={}) noexcept
Definition note_utility_kernels.hpp:23
constexpr bool valid() const noexcept
Definition note_utility_kernels.hpp:25
bool empty() const noexcept
Definition note_utility_kernels.hpp:177
MidiUtilityProcessReport reset(MidiBuffer &output) noexcept
Definition note_utility_kernels.hpp:158
static constexpr MidiUtilityContract contract() noexcept
Definition note_utility_kernels.hpp:14
MidiUtilityProcessReport flush(MidiBuffer &output) noexcept
Definition note_utility_kernels.hpp:115
static constexpr bool valid_spec(NoteLengthSpec spec) noexcept
Definition note_utility_kernels.hpp:20
void run(LV2_Handle handle, uint32_t n_samples)
Definition lv2_entry.hpp:430
@ release
Release only: voices enter their normal release and tails ring out (MIDI CC 123 semantic).
Definition panic.hpp:39
MidiEvent at(MidiEvent event, std::int32_t sample_offset) noexcept
Definition utility_contract.hpp:140
void copy_sidecars(const MidiBuffer &input, MidiBuffer &output, MidiUtilityProcessReport &report) noexcept
Definition utility_contract.hpp:96
void clear_output(MidiBuffer &output) noexcept
Definition utility_contract.hpp:82
bool blocks_alias(const MidiBuffer &first, const MidiBuffer &second) noexcept
Definition utility_contract.hpp:92
constexpr std::int64_t saturating_sample_add(std::int64_t position, std::int64_t offset) noexcept
Definition utility_contract.hpp:59
bool ready(const MidiBuffer &output) noexcept
Definition utility_contract.hpp:86
bool emit(MidiBuffer &output, const MidiEvent &event, MidiUtilityProcessReport &report)
Definition utility_contract.hpp:67
int key_index(std::uint8_t channel, std::uint8_t note) noexcept
Definition utility_contract.hpp:55
Definition custom_node_events.hpp:26
constexpr bool monophonic_serial_capacity_supported
Definition note_utility_kernels.hpp:461
@ FlushOnDiscontinuity
Definition utility_contract.hpp:33
@ RetainReleaseDebt
Definition utility_contract.hpp:22
@ FailOpenBalanced
Definition utility_contract.hpp:21
MonophonicPriority
Definition note_utility_kernels.hpp:442
@ Low
Definition note_utility_kernels.hpp:442
@ High
Definition note_utility_kernels.hpp:442
@ Last
Definition note_utility_kernels.hpp:442
BasicMonophonicNoteSelector<> MonophonicNoteSelector
Definition note_utility_kernels.hpp:663
@ ReleaseBeforeAttack
Definition utility_contract.hpp:27
@ off
Definition nonlinear_shaping.hpp:24
@ oldest
Recycle the grain furthest through its window — the quietest one when durations match,...
Definition granular.hpp:123
@ i
Definition formant_filter_bank.hpp:20
static MidiEvent note_on(uint8_t channel, uint8_t note, uint8_t velocity)
Create a Note On event.
Definition message.hpp:46
static MidiEvent note_off(uint8_t channel, uint8_t note, uint8_t velocity=0)
Create a Note Off event.
Definition message.hpp:54
Definition utility_contract.hpp:36
Definition utility_contract.hpp:46
bool complete
Definition utility_contract.hpp:50
std::size_t dropped
Definition utility_contract.hpp:48
std::size_t deferred
Definition utility_contract.hpp:49
Definition note_utility_kernels.hpp:450
bool glide_active
Definition note_utility_kernels.hpp:453
float current_note
Definition note_utility_kernels.hpp:456
std::uint8_t note
Definition note_utility_kernels.hpp:455
std::uint8_t channel
Definition note_utility_kernels.hpp:454
float target_note
Definition note_utility_kernels.hpp:457
bool legato
Definition note_utility_kernels.hpp:452
bool active
Definition note_utility_kernels.hpp:451
Definition note_utility_kernels.hpp:444
MonophonicPriority priority
Definition note_utility_kernels.hpp:445
double glide_seconds
Definition note_utility_kernels.hpp:447
bool legato
Definition note_utility_kernels.hpp:446
Definition note_utility_kernels.hpp:7
std::int64_t length_samples
Definition note_utility_kernels.hpp:8
Definition rational_time.hpp:9