Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
trigger_grid.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <array>
6#include <compare>
7#include <cstddef>
8#include <cstdint>
9#include <limits>
10#include <span>
11
12namespace pulp::timebase {
13
20 std::uint64_t numerator = 1;
21 std::uint64_t denominator = 1;
22 constexpr auto operator<=>(const TriggerProbability&) const = default;
23};
24
27 bool enabled = false;
28 std::uint8_t velocity = 100;
31 constexpr auto operator<=>(const TriggerCell&) const = default;
32};
33
36 std::size_t track = 0;
37 std::size_t step = 0;
38 std::uint8_t velocity = 0;
39 constexpr auto operator<=>(const TriggerEvent&) const = default;
40};
41
57
60 std::size_t event_count = 0;
61
62 constexpr explicit operator bool() const noexcept {
64 }
65};
66
75template <std::size_t MaxTracks = 16, std::size_t MaxSteps = 64> class TriggerGrid {
76 static_assert(MaxTracks > 0);
77 static_assert(MaxSteps > 0);
78 static_assert(MaxSteps <= static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max()));
79 static_assert(MaxTracks <= std::numeric_limits<std::size_t>::max() / MaxSteps);
80
81 public:
82 static constexpr std::size_t maximum_tracks = MaxTracks;
83 static constexpr std::size_t maximum_steps = MaxSteps;
84 static constexpr std::size_t maximum_events = MaxTracks * MaxSteps;
85
86 constexpr TriggerGridError configure(std::size_t track_count, std::size_t step_count,
87 TickDuration step_duration) noexcept {
88 if (track_count == 0 || step_count == 0)
90 if (track_count > MaxTracks || step_count > MaxSteps)
92 if (step_duration.value <= 0)
94
95 const auto last_step = step_count - 1;
96 const auto maximum_microtiming = (step_duration.value - 1) / 2;
97 const auto maximum_tick = std::numeric_limits<std::int64_t>::max();
98 if (last_step != 0 && step_duration.value > (maximum_tick - maximum_microtiming) /
99 static_cast<std::int64_t>(last_step))
101
102 track_count_ = track_count;
103 step_count_ = step_count;
104 step_duration_ = step_duration;
105 for (auto& cell : cells_)
106 cell = {};
108 }
109
110 constexpr std::size_t track_count() const noexcept {
111 return track_count_;
112 }
113 constexpr std::size_t step_count() const noexcept {
114 return step_count_;
115 }
116 constexpr std::size_t coordinate_count() const noexcept {
117 return track_count_ * step_count_;
118 }
119 constexpr TickDuration step_duration() const noexcept {
120 return step_duration_;
121 }
122
123 constexpr TickDuration minimum_microtiming() const noexcept {
124 return {-step_duration_.value / 2};
125 }
126 constexpr TickDuration maximum_microtiming() const noexcept {
127 return {(step_duration_.value - 1) / 2};
128 }
129
130 constexpr TriggerGridError set_cell(std::size_t track, std::size_t step,
131 TriggerCell cell) noexcept {
132 if (!contains(track, step))
134 if (cell.velocity == 0 || cell.velocity > 127)
136 if (cell.probability.denominator == 0 ||
137 cell.probability.numerator > cell.probability.denominator)
139 if (cell.microtiming < minimum_microtiming() || cell.microtiming > maximum_microtiming())
141 cells_[index_of(track, step)] = cell;
143 }
144
145 constexpr const TriggerCell* cell(std::size_t track, std::size_t step) const noexcept {
146 return contains(track, step) ? &cells_[index_of(track, step)] : nullptr;
147 }
148
149 constexpr void clear() noexcept {
150 for (auto& cell : cells_)
151 cell = {};
152 }
153
160 project_window(TickPosition cycle_start, TickPosition window_begin, TickPosition window_end,
161 std::span<const std::uint64_t> probability_draws,
162 std::span<TriggerEvent> output) const noexcept {
163 if (track_count_ == 0)
165 if (window_end < window_begin)
167 if (probability_draws.size() != coordinate_count())
169
170 std::size_t event_count = 0;
171 for (std::size_t step = 0; step < step_count_; ++step) {
172 for (std::size_t track = 0; track < track_count_; ++track) {
173 const auto coordinate = step * track_count_ + track;
174 const auto& authored = cells_[index_of(track, step)];
175 const auto position = position_at(cycle_start, step, authored.microtiming);
176 if (authored.enabled &&
177 selects(authored.probability, probability_draws[coordinate]) &&
178 position >= window_begin && position < window_end)
179 ++event_count;
180 }
181 }
182 if (event_count > output.size())
184
185 std::size_t output_index = 0;
186 for (std::size_t step = 0; step < step_count_; ++step) {
187 for (std::size_t track = 0; track < track_count_; ++track) {
188 const auto coordinate = step * track_count_ + track;
189 const auto& authored = cells_[index_of(track, step)];
190 const auto position = position_at(cycle_start, step, authored.microtiming);
191 if (authored.enabled &&
192 selects(authored.probability, probability_draws[coordinate]) &&
193 position >= window_begin && position < window_end) {
194 output[output_index++] = {position, track, step, authored.velocity};
195 }
196 }
197 }
198 return {TriggerGridError::None, event_count};
199 }
200
201 private:
202 constexpr bool contains(std::size_t track, std::size_t step) const noexcept {
203 return track < track_count_ && step < step_count_;
204 }
205
206 static constexpr bool selects(TriggerProbability probability, std::uint64_t draw) noexcept {
207 if (probability.numerator == 0)
208 return false;
209 if (probability.numerator == probability.denominator)
210 return true;
211 return detail::multiply_high(draw, probability.denominator) < probability.numerator;
212 }
213
214 constexpr std::size_t index_of(std::size_t track, std::size_t step) const noexcept {
215 return track * MaxSteps + step;
216 }
217
218 constexpr TickPosition position_at(TickPosition cycle_start, std::size_t step,
219 TickDuration microtiming) const noexcept {
220 // configure() proves this offset is representable. Combining it before
221 // the saturating add preserves event order near either signed tick rail.
222 const auto offset =
223 static_cast<std::int64_t>(step) * step_duration_.value + microtiming.value;
224 return {detail::saturating_add(cycle_start.value, offset)};
225 }
226
227 std::array<TriggerCell, maximum_events> cells_{};
228 std::size_t track_count_ = 0;
229 std::size_t step_count_ = 0;
230 TickDuration step_duration_{};
231};
232
233} // namespace pulp::timebase
Fixed-capacity authored trigger data with allocation-free tick projection.
Definition trigger_grid.hpp:75
constexpr TickDuration maximum_microtiming() const noexcept
Definition trigger_grid.hpp:126
static constexpr std::size_t maximum_tracks
Definition trigger_grid.hpp:82
constexpr std::size_t track_count() const noexcept
Definition trigger_grid.hpp:110
constexpr TickDuration minimum_microtiming() const noexcept
Definition trigger_grid.hpp:123
constexpr std::size_t step_count() const noexcept
Definition trigger_grid.hpp:113
constexpr std::size_t coordinate_count() const noexcept
Definition trigger_grid.hpp:116
constexpr const TriggerCell * cell(std::size_t track, std::size_t step) const noexcept
Definition trigger_grid.hpp:145
constexpr TriggerGridError configure(std::size_t track_count, std::size_t step_count, TickDuration step_duration) noexcept
Definition trigger_grid.hpp:86
constexpr void clear() noexcept
Definition trigger_grid.hpp:149
static constexpr std::size_t maximum_events
Definition trigger_grid.hpp:84
constexpr TickDuration step_duration() const noexcept
Definition trigger_grid.hpp:119
constexpr TriggerProjectionResult project_window(TickPosition cycle_start, TickPosition window_begin, TickPosition window_end, std::span< const std::uint64_t > probability_draws, std::span< TriggerEvent > output) const noexcept
Projects this cycle into [window_begin, window_end) without allocation.
Definition trigger_grid.hpp:160
constexpr TriggerGridError set_cell(std::size_t track, std::size_t step, TriggerCell cell) noexcept
Definition trigger_grid.hpp:130
static constexpr std::size_t maximum_steps
Definition trigger_grid.hpp:83
constexpr std::int64_t saturating_add(std::int64_t lhs, std::int64_t rhs) noexcept
Definition tick.hpp:11
constexpr std::uint64_t multiply_high(std::uint64_t lhs, std::uint64_t rhs) noexcept
Definition coordinate_random.hpp:31
Definition beat_division.hpp:11
TriggerGridError
Definition trigger_grid.hpp:42
@ PatternSpanOutOfRange
Definition trigger_grid.hpp:48
@ DrawCountMismatch
Definition trigger_grid.hpp:54
@ InvalidMicrotiming
Definition trigger_grid.hpp:52
@ InvalidDimensions
Definition trigger_grid.hpp:45
@ NotConfigured
Definition trigger_grid.hpp:44
@ IndexOutOfRange
Definition trigger_grid.hpp:49
@ None
Definition trigger_grid.hpp:43
@ InvalidWindow
Definition trigger_grid.hpp:53
@ InvalidProbability
Definition trigger_grid.hpp:51
@ InvalidStepDuration
Definition trigger_grid.hpp:47
@ InvalidVelocity
Definition trigger_grid.hpp:50
@ OutputTooSmall
Definition trigger_grid.hpp:55
@ CapacityExceeded
Definition trigger_grid.hpp:46
@ InvalidWindow
Definition ratchet.hpp:19
@ CapacityExceeded
Definition ratchet.hpp:17
@ None
Definition grid_projection.hpp:97
@ OutputTooSmall
Definition grid_projection.hpp:104
Definition tick.hpp:56
Definition tick.hpp:61
The authored data at one track and step coordinate.
Definition trigger_grid.hpp:26
bool enabled
Definition trigger_grid.hpp:27
std::uint8_t velocity
Definition trigger_grid.hpp:28
TriggerProbability probability
Definition trigger_grid.hpp:29
constexpr auto operator<=>(const TriggerCell &) const =default
TickDuration microtiming
Definition trigger_grid.hpp:30
Definition trigger_grid.hpp:34
std::size_t step
Definition trigger_grid.hpp:37
TickPosition position
Definition trigger_grid.hpp:35
std::size_t track
Definition trigger_grid.hpp:36
constexpr auto operator<=>(const TriggerEvent &) const =default
std::uint8_t velocity
Definition trigger_grid.hpp:38
An exact probability ratio for an authored trigger.
Definition trigger_grid.hpp:19
std::uint64_t denominator
Definition trigger_grid.hpp:21
std::uint64_t numerator
Definition trigger_grid.hpp:20
constexpr auto operator<=>(const TriggerProbability &) const =default
Definition trigger_grid.hpp:58
std::size_t event_count
Definition trigger_grid.hpp:60
TriggerGridError error
Definition trigger_grid.hpp:59