Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
note_repeat.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <pulp/midi/detail/note_schedule.hpp>
5
6#include <algorithm>
7#include <cstddef>
8#include <cstdint>
9#include <optional>
10
11namespace pulp::midi {
12
25 std::uint8_t count = 1;
26 std::uint8_t gate_percent = 50;
27 std::uint8_t probability_percent = 100;
28 std::uint8_t velocity_decay_percent = 100;
29 std::uint64_t seed = 0;
30 constexpr auto operator<=>(const NoteRepeatSpec&) const = default;
31};
32
33template <std::size_t MaximumScheduledNotes = 128> class NoteRepeat {
34 public:
35 using Block = note_schedule::Block;
36
42
43 static constexpr bool valid_spec(NoteRepeatSpec spec) noexcept {
44 return spec.interval.value > 0 && spec.count >= 1 && spec.gate_percent >= 1 &&
45 spec.gate_percent <= 100 && spec.probability_percent <= 100 &&
46 spec.velocity_decay_percent <= 100;
47 }
48
51 static constexpr bool is_identity(NoteRepeatSpec spec) noexcept {
52 return valid_spec(spec) && spec.count <= 1;
53 }
54
58 static std::int64_t hit_stride(NoteRepeatSpec spec, const Block& block) noexcept {
59 return note_schedule::samples_for_duration(block, spec.interval);
60 }
61
63 static std::int64_t hit_sample(NoteRepeatSpec spec, const Block& block,
64 std::int64_t attack_absolute, std::size_t index) noexcept {
66 attack_absolute, hit_stride(spec, block) * static_cast<std::int64_t>(index));
67 }
68
70 static constexpr std::uint8_t hit_velocity(NoteRepeatSpec spec, std::uint8_t velocity,
71 std::size_t index) noexcept {
72 return note_schedule::decayed_velocity(velocity, spec.velocity_decay_percent, index);
73 }
74
78 static bool hit_sounds(NoteRepeatSpec spec, std::uint8_t channel, std::uint8_t note,
79 std::int64_t attack_absolute, std::size_t index) noexcept {
80 if (spec.probability_percent >= 100)
81 return true;
82 if (spec.probability_percent == 0)
83 return false;
84 const timebase::RandomCoordinate coordinate{
85 timebase::TickPosition{attack_absolute},
86 static_cast<std::uint64_t>(utility_detail::key_index(channel, note)),
87 static_cast<std::uint64_t>(index), kProbabilityStream};
88 const auto chance =
89 timebase::coordinate_chance(spec.seed, coordinate, spec.probability_percent, 100);
90 return chance.has_value() && chance.value();
91 }
92
93 explicit constexpr NoteRepeat(NoteRepeatSpec spec = {}) noexcept
94 : spec_(spec), valid_(valid_spec(spec)) {}
95
96 constexpr bool valid() const noexcept {
97 return valid_;
98 }
99
100 constexpr NoteRepeatSpec spec() const noexcept {
101 return spec_;
102 }
103
105 const Block& block) noexcept {
106 if (utility_detail::blocks_alias(input, output))
107 return {0, input.size(), 0, false};
110 if (!utility_detail::ready(output) || !valid_ || !note_schedule::valid_block(block)) {
111 report.dropped = input.size();
112 report.complete = false;
113 return report;
114 }
115 const std::int64_t block_end =
116 utility_detail::saturating_sample_add(block.sample_start.value, block.sample_count);
117 for (const auto& event : input) {
118 const auto absolute =
119 utility_detail::saturating_sample_add(block.sample_start.value, event.sample_offset);
120 emit_due_before(absolute, block, output, report);
121 const bool is_attack = event.is_note_on() && event.velocity() != 0;
122 const bool is_release =
123 event.is_note_off() || (event.is_note_on() && event.velocity() == 0);
124 if (is_identity(spec_) || (!is_attack && !is_release)) {
125 utility_detail::emit(output, event, report);
126 continue;
127 }
128 if (is_attack) {
129 schedule_series(event, absolute, block, report);
130 continue;
131 }
132 // Letting go stops the hits that have not started. A hit already
133 // sounding keeps its gate, so no note the kernel owns is truncated
134 // and no note-off is orphaned.
135 report.deferred += queue_.cancel_unstarted_key(event.channel(), event.note());
136 }
137 emit_due_before(block_end, block, output, report);
138 utility_detail::copy_sidecars(input, output, report);
139 output.sort();
140 return report;
141 }
142
145 MidiUtilityProcessReport flush(MidiBuffer& output, const Block& block) noexcept {
148 if (!utility_detail::ready(output)) {
149 report.complete = false;
150 return report;
151 }
152 report.deferred += queue_.cancel_unstarted();
153 queue_.release_sounding_at(block.sample_start.value);
154 const bool drained =
155 queue_.drain([&](const note_schedule::ScheduledNote& slot, bool attack, std::int64_t at) {
156 return emit_edge(slot, attack, at, block, output, report);
157 });
158 if (!drained) {
159 ++report.deferred;
160 report.complete = false;
161 return report;
162 }
163 report.complete = empty();
164 if (report.complete && pending_spec_) {
165 spec_ = *pending_spec_;
166 valid_ = true;
167 pending_spec_.reset();
168 }
169 return report;
170 }
171
172 MidiUtilityProcessReport reset(MidiBuffer& output, const Block& block) noexcept {
173 return flush(output, block);
174 }
175
177 const Block& block) noexcept {
178 if (!valid_spec(spec)) {
180 return {0, 0, 0, false};
181 }
182 auto report = flush(output, block);
183 if (report.complete) {
184 spec_ = spec;
185 valid_ = true;
186 pending_spec_.reset();
187 } else {
188 pending_spec_ = spec;
189 }
190 return report;
191 }
192
193 bool empty() const noexcept {
194 return queue_.empty();
195 }
196
197 std::size_t scheduled() const noexcept {
198 return queue_.size();
199 }
200
201 std::size_t sounding() const noexcept {
202 return queue_.sounding();
203 }
204
205 private:
206 static constexpr std::uint64_t kProbabilityStream = 3;
207
208 void schedule_series(const MidiEvent& event, std::int64_t absolute, const Block& block,
209 MidiUtilityProcessReport& report) noexcept {
210 const auto stride = hit_stride(spec_, block);
211 const auto gate =
212 std::max<std::int64_t>(1, stride * static_cast<std::int64_t>(spec_.gate_percent) / 100);
213 for (std::size_t index = 0; index < spec_.count; ++index) {
214 if (!hit_sounds(spec_, event.channel(), event.note(), absolute, index))
215 continue;
216 auto* slot = queue_.acquire();
217 if (slot == nullptr) {
218 // DropUnstarted: a hit that cannot be scheduled never sounds, so
219 // it can never leave an unbalanced note behind.
220 ++report.dropped;
221 report.complete = false;
222 return;
223 }
225 absolute, stride * static_cast<std::int64_t>(index));
226 slot->start = at;
227 slot->end = utility_detail::saturating_sample_add(at, gate);
228 slot->channel = event.channel();
229 slot->note = event.note();
230 slot->velocity = hit_velocity(spec_, event.velocity(), index);
231 }
232 }
233
234 void emit_due_before(std::int64_t boundary, const Block& block, MidiBuffer& output,
235 MidiUtilityProcessReport& report) noexcept {
236 queue_.emit_due_before(
237 boundary, [&](const note_schedule::ScheduledNote& slot, bool attack, std::int64_t at) {
238 return emit_edge(slot, attack, at, block, output, report);
239 });
240 }
241
242 static bool emit_edge(const note_schedule::ScheduledNote& slot, bool attack, std::int64_t at,
243 const Block& block, MidiBuffer& output,
244 MidiUtilityProcessReport& report) noexcept {
245 const auto offset = note_schedule::offset_in_block(block, at);
246 const auto event = attack ? MidiEvent::note_on(slot.channel, slot.note, slot.velocity)
247 : MidiEvent::note_off(slot.channel, slot.note);
248 return utility_detail::emit(output, utility_detail::at(event, offset), report);
249 }
250
251 NoteRepeatSpec spec_{};
252 bool valid_ = true;
253 std::optional<NoteRepeatSpec> pending_spec_{};
254 note_schedule::ScheduleQueue<MaximumScheduledNotes> queue_{};
255};
256
257} // namespace pulp::midi
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
static constexpr bool is_identity(NoteRepeatSpec spec) noexcept
True when the kernel cannot change the stream, which is its declared bypass identity.
Definition note_repeat.hpp:51
constexpr bool valid() const noexcept
Definition note_repeat.hpp:96
MidiUtilityProcessReport replace_spec(NoteRepeatSpec spec, MidiBuffer &output, const Block &block) noexcept
Definition note_repeat.hpp:176
MidiUtilityProcessReport process(const MidiBuffer &input, MidiBuffer &output, const Block &block) noexcept
Definition note_repeat.hpp:104
std::size_t scheduled() const noexcept
Definition note_repeat.hpp:197
static constexpr std::uint8_t hit_velocity(NoteRepeatSpec spec, std::uint8_t velocity, std::size_t index) noexcept
Velocity of hit index under the shipped decay law.
Definition note_repeat.hpp:70
static constexpr MidiUtilityContract contract() noexcept
Definition note_repeat.hpp:37
std::size_t sounding() const noexcept
Definition note_repeat.hpp:201
note_schedule::Block Block
Definition note_repeat.hpp:35
bool empty() const noexcept
Definition note_repeat.hpp:193
MidiUtilityProcessReport reset(MidiBuffer &output, const Block &block) noexcept
Definition note_repeat.hpp:172
constexpr NoteRepeat(NoteRepeatSpec spec={}) noexcept
Definition note_repeat.hpp:93
static constexpr bool valid_spec(NoteRepeatSpec spec) noexcept
Definition note_repeat.hpp:43
static bool hit_sounds(NoteRepeatSpec spec, std::uint8_t channel, std::uint8_t note, std::int64_t attack_absolute, std::size_t index) noexcept
Whether hit index of the attack at attack_absolute survives its probability draw.
Definition note_repeat.hpp:78
static std::int64_t hit_stride(NoteRepeatSpec spec, const Block &block) noexcept
Sample stride between consecutive hits, derived from division arithmetic alone.
Definition note_repeat.hpp:58
MidiUtilityProcessReport flush(MidiBuffer &output, const Block &block) noexcept
Cancel unstarted hits and release every note the kernel still owns.
Definition note_repeat.hpp:145
constexpr NoteRepeatSpec spec() const noexcept
Definition note_repeat.hpp:100
static std::int64_t hit_sample(NoteRepeatSpec spec, const Block &block, std::int64_t attack_absolute, std::size_t index) noexcept
Absolute sample of hit index for an attack at attack_absolute.
Definition note_repeat.hpp:63
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
@ ReleaseBeforeAttack
Definition utility_contract.hpp:27
@ attack
Definition envelope.hpp:58
constexpr std::int64_t kTicksPerQuarter
Definition tick.hpp:54
runtime::Result< bool, ProbabilityError > coordinate_chance(std::uint64_t seed, RandomCoordinate coordinate, std::uint64_t numerator, std::uint64_t denominator) noexcept
Definition coordinate_random.hpp:63
Timestamped MIDI event combining a choc::midi::ShortMessage with timing info.
Definition message.hpp:32
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
Clock-divided note repeat / retrigger.
Definition note_repeat.hpp:23
std::uint8_t count
Definition note_repeat.hpp:25
std::uint8_t probability_percent
Definition note_repeat.hpp:27
std::uint64_t seed
Definition note_repeat.hpp:29
timebase::TickDuration interval
Definition note_repeat.hpp:24
constexpr auto operator<=>(const NoteRepeatSpec &) const =default
std::uint8_t velocity_decay_percent
Definition note_repeat.hpp:28
std::uint8_t gate_percent
Definition note_repeat.hpp:26
Definition coordinate_random.hpp:14
Definition tick.hpp:56
Definition tick.hpp:61