3#include <pulp/midi/detail/note_schedule.hpp>
45template <std::
size_t MaximumScheduledNotes = 128, std::
size_t MaximumHeldSources = 64>
48 using Block = note_schedule::Block;
57 if (
spec.gate_percent > 100 ||
spec.velocity_decay_percent > 100)
60 :
spec.milliseconds > 0;
74 ? note_schedule::samples_for_duration(block,
spec.interval)
75 : note_schedule::samples_for_milliseconds(block,
spec.milliseconds);
82 if (
spec.gate_percent == 0)
85 return std::max<std::int64_t>(1, (stride *
static_cast<std::int64_t
>(
spec.gate_percent)) /
91 std::int64_t attack_absolute, std::size_t index)
noexcept {
98 std::size_t index)
noexcept {
99 return note_schedule::decayed_velocity(velocity,
spec.velocity_decay_percent, index);
106 static constexpr std::optional<std::uint8_t>
108 const auto shifted =
static_cast<std::int32_t
>(note) +
109 static_cast<std::int32_t
>(
spec.transpose_semitones) *
110 static_cast<std::int32_t
>(index);
111 if (shifted < 0 || shifted > 127)
113 return static_cast<std::uint8_t
>(shifted);
119 constexpr bool valid() const noexcept {
128 const Block& block)
noexcept {
130 return {0, input.size(), 0,
false};
138 const std::int64_t block_end =
140 for (
const auto& event : input) {
141 const auto absolute =
143 emit_due_before(absolute, block, output, report);
144 const bool is_attack =
event.is_note_on() &&
event.velocity() != 0;
145 const bool is_release =
146 event.is_note_off() || (
event.is_note_on() && event.velocity() == 0);
149 if (
is_identity(spec_) || (!is_attack && !is_release))
152 schedule_echoes(event, absolute, block, report);
154 roll_back_armed(event, absolute, block, output, report);
156 emit_due_before(block_end, block, output, report);
171 report.
deferred += queue_.cancel_unstarted();
174 queue_.release_sounding_at(block.sample_start.value);
176 const bool drained = queue_.drain(
177 [&](
const note_schedule::ScheduledNote& slot,
bool attack, std::int64_t at) {
178 return emit_edge(slot, attack, at, block, output, report);
186 if (report.
complete && pending_spec_) {
187 spec_ = *pending_spec_;
189 pending_spec_.reset();
195 return flush(output, block);
199 const Block& block)
noexcept {
202 return {0, 0, 0,
false};
204 auto report =
flush(output, block);
205 if (report.complete) {
208 pending_spec_.reset();
210 pending_spec_ =
spec;
216 return queue_.empty();
220 return queue_.size();
224 return queue_.sounding();
231 std::int64_t attack = 0;
232 std::uint16_t group = 0;
236 Source* find_source(std::uint16_t group)
noexcept {
237 for (
auto& source : sources_)
238 if (source.armed && source.group == group)
243 Source* claim_source(std::uint16_t group)
noexcept {
244 if (
auto* existing = find_source(group); existing !=
nullptr)
246 for (
auto& source : sources_)
252 static constexpr std::uint16_t group_for(std::uint8_t channel, std::uint8_t note)
noexcept {
256 void schedule_echoes(
const MidiEvent& event, std::int64_t absolute,
const Block& block,
257 MidiUtilityProcessReport& report)
noexcept {
260 const auto group = group_for(event.channel(), event.note());
265 close_group(group, absolute);
266 auto* source = claim_source(group);
267 if (source ==
nullptr) {
271 report.complete =
false;
274 *source = {absolute,
group,
true};
275 for (std::size_t index = 1;
index <= spec_.repeats; ++
index) {
276 const auto pitch =
repeat_note(spec_, event.note(), index);
279 auto* slot = queue_.acquire();
280 if (slot ==
nullptr) {
284 report.complete =
false;
288 absolute, stride *
static_cast<std::int64_t
>(index));
289 slot->end = gate == 0 ? note_schedule::kArmedEnd
291 slot->channel =
event.channel();
295 slot->step =
static_cast<std::uint16_t
>(
index);
303 void roll_back_armed(
const MidiEvent& event, std::int64_t absolute,
const Block& block,
304 MidiBuffer& output, MidiUtilityProcessReport& report)
noexcept {
305 const auto group = group_for(event.channel(), event.note());
306 if (!close_group(group, absolute))
308 emit_due_before(absolute, block, output, report);
315 bool close_group(std::uint16_t group, std::int64_t absolute)
noexcept {
316 auto* source = find_source(group);
317 if (source ==
nullptr)
319 const auto held = std::max<std::int64_t>(1, absolute - source->attack);
320 queue_.visit([&](note_schedule::ScheduledNote& slot) {
321 if (slot.group != group || slot.end != note_schedule::kArmedEnd)
326 slot.end = slot.sounding ? std::max(end, absolute) :
end;
332 void emit_due_before(std::int64_t boundary,
const Block& block, MidiBuffer& output,
333 MidiUtilityProcessReport& report)
noexcept {
334 queue_.emit_due_before(
335 boundary, [&](
const note_schedule::ScheduledNote& slot,
bool attack, std::int64_t at) {
336 return emit_edge(slot, attack, at, block, output, report);
340 static bool emit_edge(
const note_schedule::ScheduledNote& slot,
bool attack, std::int64_t at,
341 const Block& block, MidiBuffer& output,
342 MidiUtilityProcessReport& report)
noexcept {
343 const auto offset = note_schedule::offset_in_block(block, at);
345 : MidiEvent::note_off(slot.channel, slot.note);
349 NoteDelaySpec spec_{};
351 std::optional<NoteDelaySpec> pending_spec_{};
352 note_schedule::ScheduleQueue<MaximumScheduledNotes> queue_{};
353 std::array<Source, MaximumHeldSources> sources_{};
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
constexpr NoteDelaySpec spec() const noexcept
Definition note_delay.hpp:123
constexpr NoteDelay(NoteDelaySpec spec={}) noexcept
Definition note_delay.hpp:116
static constexpr bool is_identity(NoteDelaySpec spec) noexcept
True when the kernel cannot change the stream, which is its declared bypass identity.
Definition note_delay.hpp:65
bool empty() const noexcept
Definition note_delay.hpp:215
MidiUtilityProcessReport replace_spec(NoteDelaySpec spec, MidiBuffer &output, const Block &block) noexcept
Definition note_delay.hpp:198
static std::int64_t repeat_stride(NoteDelaySpec spec, const Block &block) noexcept
Sample stride between consecutive repeats under whichever clock the spec selects.
Definition note_delay.hpp:72
static std::int64_t repeat_sample(NoteDelaySpec spec, const Block &block, std::int64_t attack_absolute, std::size_t index) noexcept
Absolute sample of repeat index (1-based; index 0 is the dry note).
Definition note_delay.hpp:90
constexpr bool valid() const noexcept
Definition note_delay.hpp:119
static constexpr std::uint8_t repeat_velocity(NoteDelaySpec spec, std::uint8_t velocity, std::size_t index) noexcept
Velocity of repeat index under the shipped decay law.
Definition note_delay.hpp:97
MidiUtilityProcessReport process(const MidiBuffer &input, MidiBuffer &output, const Block &block) noexcept
Definition note_delay.hpp:127
static constexpr std::optional< std::uint8_t > repeat_note(NoteDelaySpec spec, std::uint8_t note, std::size_t index) noexcept
Pitch of repeat index, or nothing when the cumulative transposition has walked the repeat off the MID...
Definition note_delay.hpp:107
MidiUtilityProcessReport reset(MidiBuffer &output, const Block &block) noexcept
Definition note_delay.hpp:194
std::size_t sounding() const noexcept
Definition note_delay.hpp:223
static constexpr bool valid_spec(NoteDelaySpec spec) noexcept
Definition note_delay.hpp:56
static constexpr MidiUtilityContract contract() noexcept
Definition note_delay.hpp:50
note_schedule::Block Block
Definition note_delay.hpp:48
MidiUtilityProcessReport flush(MidiBuffer &output, const Block &block) noexcept
Cancel unstarted echoes and release every echo the kernel still owns, including the ones whose end is...
Definition note_delay.hpp:164
static std::int64_t repeat_gate(NoteDelaySpec spec, const Block &block) noexcept
Length of an authorable echo gate.
Definition note_delay.hpp:81
std::size_t scheduled() const noexcept
Definition note_delay.hpp:219
@ group
Definition scene_node.hpp:92
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
@ FlushOnDiscontinuity
Definition utility_contract.hpp:33
@ DropUnstarted
Definition utility_contract.hpp:20
NoteDelaySync
Definition note_delay.hpp:13
@ Division
Definition note_delay.hpp:14
@ Milliseconds
Definition note_delay.hpp:15
@ ReleaseBeforeAttack
Definition utility_contract.hpp:27
@ index
Definition gpu_pass.hpp:140
@ attack
Definition envelope.hpp:58
constexpr std::int64_t kTicksPerQuarter
Definition tick.hpp:54
static MidiEvent note_on(uint8_t channel, uint8_t note, uint8_t velocity)
Create a Note On event.
Definition message.hpp:46
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
MIDI note delay / echo.
Definition note_delay.hpp:26
timebase::TickDuration interval
Definition note_delay.hpp:28
std::int8_t transpose_semitones
Definition note_delay.hpp:37
NoteDelaySync sync
Definition note_delay.hpp:27
std::int64_t milliseconds
Definition note_delay.hpp:29
std::uint8_t repeats
Definition note_delay.hpp:30
constexpr auto operator<=>(const NoteDelaySpec &) const =default
std::uint8_t gate_percent
Gate length for generated echoes as a percentage of their delay stride.
Definition note_delay.hpp:35
std::uint8_t velocity_decay_percent
Definition note_delay.hpp:36