Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pattern.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <pulp/music/detail/random_range.hpp>
4
5#include <array>
6#include <compare>
7#include <cstddef>
8#include <cstdint>
9#include <optional>
10#include <span>
11
12namespace pulp::music {
13
23
24template <std::size_t MaxSteps = 64>
26 public:
27 static_assert(MaxSteps > 0, "BinaryPattern capacity must be positive");
28 static constexpr std::size_t capacity = MaxSteps;
29
30 [[nodiscard]] constexpr PatternError resize(std::size_t size,
31 bool fill = false) noexcept {
32 if (size > MaxSteps)
34 values_.fill(0);
35 size_ = size;
36 for (std::size_t index = 0; index < size_; ++index)
37 values_[index] = static_cast<std::uint8_t>(fill);
38 return PatternError::none;
39 }
40
41 [[nodiscard]] constexpr PatternError
42 assign(std::span<const std::uint8_t> values) noexcept {
43 if (values.size() > MaxSteps)
45 values_.fill(0);
46 size_ = values.size();
47 for (std::size_t index = 0; index < size_; ++index)
48 values_[index] = static_cast<std::uint8_t>(values[index] != 0);
49 return PatternError::none;
50 }
51
52 constexpr std::size_t size() const noexcept { return size_; }
53 constexpr bool empty() const noexcept { return size_ == 0; }
54
55 [[nodiscard]] constexpr std::optional<bool> at(std::size_t index) const noexcept {
56 if (index >= size_)
57 return std::nullopt;
58 return values_[index] != 0;
59 }
60
61 [[nodiscard]] constexpr bool set(std::size_t index, bool value) noexcept {
62 if (index >= size_)
63 return false;
64 values_[index] = static_cast<std::uint8_t>(value);
65 return true;
66 }
67
68 constexpr std::size_t onset_count() const noexcept {
69 std::size_t result = 0;
70 for (std::size_t index = 0; index < size_; ++index)
71 result += values_[index] != 0;
72 return result;
73 }
74
75 constexpr auto operator<=>(const BinaryPattern&) const = default;
76
77 private:
78 std::array<std::uint8_t, MaxSteps> values_{};
79 std::size_t size_ = 0;
80};
81
82template <std::size_t MaxSteps = 64>
86 constexpr explicit operator bool() const noexcept {
87 return error == PatternError::none;
88 }
89};
90
91// Distributes pulses by the accumulator form of Euclid's algorithm. The
92// canonical rotation starts with an onset. Positive rotation delays every onset
93// by that many steps; negative rotation advances it. Zero pulses produce a
94// silent pattern.
95template <std::size_t MaxSteps = 64>
96[[nodiscard]] constexpr PatternResult<MaxSteps>
97euclidean_pattern(std::size_t steps, std::size_t pulses,
98 std::int64_t rotation = 0) noexcept {
100 if (steps == 0) {
102 return result;
103 }
104 if (steps > MaxSteps) {
106 return result;
107 }
108 if (pulses > steps) {
110 return result;
111 }
112 (void)result.pattern.resize(steps);
113 if (pulses == 0)
114 return result;
115
116 std::size_t bucket = steps - pulses;
117 for (std::size_t step = 0; step < steps; ++step) {
118 bucket += pulses;
119 if (bucket >= steps) {
120 bucket -= steps;
121 (void)result.pattern.set(step, true);
122 }
123 }
124 const auto signed_steps = static_cast<std::int64_t>(steps);
125 auto normalized_rotation = rotation % signed_steps;
126 if (normalized_rotation < 0)
127 normalized_rotation += signed_steps;
128 if (normalized_rotation != 0) {
129 const auto shift = static_cast<std::size_t>(normalized_rotation);
130 const auto canonical = result.pattern;
131 for (std::size_t destination = 0; destination < steps; ++destination) {
132 const auto source = (destination + steps - shift) % steps;
133 (void)result.pattern.set(destination, *canonical.at(source));
134 }
135 }
136 return result;
137}
138
139inline constexpr std::uint16_t euclidean_pattern_recipe_version = 1;
140
141// These named scalar fields form the persistence contract. Object bytes are not
142// a wire format: integrations serialize the fields individually and reject
143// versions they do not understand.
146 std::uint16_t steps = 16;
147 std::uint16_t pulses = 4;
148 std::int64_t rotation = 0;
149
150 constexpr auto operator<=>(const EuclideanPatternRecipe&) const = default;
151};
152
153template <std::size_t MaxSteps = 64>
154[[nodiscard]] constexpr PatternResult<MaxSteps>
156 if (recipe.version != euclidean_pattern_recipe_version) {
159 return result;
160 }
161 return euclidean_pattern<MaxSteps>(recipe.steps, recipe.pulses, recipe.rotation);
162}
163
164enum class PatternWalkerMode : std::uint8_t {
169};
170
171template <std::size_t MaxSteps = 64>
173 public:
174 static_assert(MaxSteps > 0, "PatternWalker capacity must be positive");
175
176 [[nodiscard]] constexpr PatternError
177 configure(std::size_t length, PatternWalkerMode mode) noexcept {
178 if (length == 0) {
179 length_ = 0;
181 }
182 if (length > MaxSteps) {
183 length_ = 0;
185 }
189 length_ = 0;
191 }
192 length_ = length;
193 mode_ = mode;
194 reset();
195 return PatternError::none;
196 }
197
198 constexpr void reset() noexcept {
199 cursor_ = mode_ == PatternWalkerMode::reverse && length_ > 0 ? length_ - 1 : 0;
200 direction_ = 1;
201 }
202
203 [[nodiscard]] constexpr std::optional<std::size_t>
204 next() noexcept {
205 if (length_ == 0 || mode_ == PatternWalkerMode::random)
206 return std::nullopt;
207 return next_deterministic();
208 }
209
210 [[nodiscard]] constexpr std::optional<std::size_t>
211 next(std::uint64_t random_word) noexcept {
212 if (length_ == 0)
213 return std::nullopt;
214 if (mode_ == PatternWalkerMode::random)
215 return static_cast<std::size_t>(
216 detail::reduce_random_word(random_word, length_));
217 return next_deterministic();
218 }
219
220 constexpr std::size_t length() const noexcept { return length_; }
221 constexpr PatternWalkerMode mode() const noexcept { return mode_; }
222
223 private:
224 constexpr std::size_t next_deterministic() noexcept {
225 const auto result = cursor_;
226 switch (mode_) {
228 cursor_ = (cursor_ + 1) % length_;
229 break;
231 cursor_ = cursor_ == 0 ? length_ - 1 : cursor_ - 1;
232 break;
234 advance_ping_pong();
235 break;
237 break;
238 }
239 return result;
240 }
241
242 constexpr void advance_ping_pong() noexcept {
243 if (length_ == 1)
244 return;
245 if (direction_ > 0 && cursor_ + 1 == length_) {
246 direction_ = -1;
247 --cursor_;
248 } else if (direction_ < 0 && cursor_ == 0) {
249 direction_ = 1;
250 ++cursor_;
251 } else if (direction_ > 0) {
252 ++cursor_;
253 } else {
254 --cursor_;
255 }
256 }
257
258 std::size_t length_ = 0;
259 std::size_t cursor_ = 0;
260 int direction_ = 1;
262};
263
264enum class CellularBoundary : std::uint8_t {
267};
268
269template <std::size_t MaxSteps = 64>
270[[nodiscard]] constexpr PatternResult<MaxSteps>
271cellular_evolve(const BinaryPattern<MaxSteps>& input, std::uint8_t rule,
272 CellularBoundary boundary = CellularBoundary::wrap) noexcept {
274 if (input.empty()) {
276 return result;
277 }
278 if (boundary != CellularBoundary::fixed_off && boundary != CellularBoundary::wrap) {
280 return result;
281 }
282 (void)result.pattern.resize(input.size());
283 for (std::size_t index = 0; index < input.size(); ++index) {
284 const bool center = *input.at(index);
285 const bool left = index > 0 ? *input.at(index - 1)
286 : boundary == CellularBoundary::wrap
287 ? *input.at(input.size() - 1)
288 : false;
289 const bool right = index + 1 < input.size() ? *input.at(index + 1)
290 : boundary == CellularBoundary::wrap
291 ? *input.at(0)
292 : false;
293 const auto neighborhood = static_cast<unsigned>((left ? 4u : 0u)
294 | (center ? 2u : 0u)
295 | (right ? 1u : 0u));
296 (void)result.pattern.set(index, ((rule >> neighborhood) & 1u) != 0);
297 }
298 return result;
299}
300
302 std::uint32_t numerator = 0;
303 std::uint32_t denominator = 1;
304};
305
306template <std::size_t MaxSteps = 64>
310 bool output = false;
311 bool mutated = false;
312 constexpr explicit operator bool() const noexcept {
313 return error == PatternError::none;
314 }
315};
316
317// Rotates the final bit back to the first cell and optionally flips that copied
318// bit according to the caller-supplied deterministic random word.
319template <std::size_t MaxSteps = 64>
320[[nodiscard]] constexpr ShiftRegisterResult<MaxSteps>
322 std::uint64_t random_word) noexcept {
324 result.pattern = input;
325 if (input.empty()) {
327 return result;
328 }
329 if (chance.denominator == 0 || chance.numerator > chance.denominator) {
331 return result;
332 }
333
334 result.output = *input.at(input.size() - 1);
335 if (chance.numerator != 0) {
336 result.mutated = chance.numerator == chance.denominator
337 || detail::reduce_random_word(random_word, chance.denominator)
338 < chance.numerator;
339 }
340 for (std::size_t index = input.size() - 1; index > 0; --index)
341 (void)result.pattern.set(index, *input.at(index - 1));
342 (void)result.pattern.set(0, result.output != result.mutated);
343 return result;
344}
345
346} // namespace pulp::music
Definition pattern.hpp:25
constexpr PatternError assign(std::span< const std::uint8_t > values) noexcept
Definition pattern.hpp:42
constexpr auto operator<=>(const BinaryPattern &) const =default
constexpr std::optional< bool > at(std::size_t index) const noexcept
Definition pattern.hpp:55
static constexpr std::size_t capacity
Definition pattern.hpp:28
constexpr bool set(std::size_t index, bool value) noexcept
Definition pattern.hpp:61
constexpr std::size_t size() const noexcept
Definition pattern.hpp:52
constexpr PatternError resize(std::size_t size, bool fill=false) noexcept
Definition pattern.hpp:30
constexpr bool empty() const noexcept
Definition pattern.hpp:53
constexpr std::size_t onset_count() const noexcept
Definition pattern.hpp:68
Definition pattern.hpp:172
constexpr std::optional< std::size_t > next() noexcept
Definition pattern.hpp:204
constexpr std::optional< std::size_t > next(std::uint64_t random_word) noexcept
Definition pattern.hpp:211
constexpr PatternWalkerMode mode() const noexcept
Definition pattern.hpp:221
constexpr PatternError configure(std::size_t length, PatternWalkerMode mode) noexcept
Definition pattern.hpp:177
constexpr void reset() noexcept
Definition pattern.hpp:198
constexpr std::size_t length() const noexcept
Definition pattern.hpp:220
Definition chord.hpp:14
constexpr PatternResult< MaxSteps > euclidean_pattern(std::size_t steps, std::size_t pulses, std::int64_t rotation=0) noexcept
Definition pattern.hpp:97
constexpr PatternResult< MaxSteps > cellular_evolve(const BinaryPattern< MaxSteps > &input, std::uint8_t rule, CellularBoundary boundary=CellularBoundary::wrap) noexcept
Definition pattern.hpp:271
PatternWalkerMode
Definition pattern.hpp:164
@ reverse
Definition pattern.hpp:166
@ random
Definition pattern.hpp:168
@ forward
Definition pattern.hpp:165
@ ping_pong
Definition pattern.hpp:167
@ fill
Event intended to supply regional variation.
Definition pattern_development.hpp:26
constexpr ShiftRegisterResult< MaxSteps > looping_shift_register(const BinaryPattern< MaxSteps > &input, MutationChance chance, std::uint64_t random_word) noexcept
Definition pattern.hpp:321
PatternError
Definition pattern.hpp:14
@ unsupported_recipe_version
Definition pattern.hpp:21
@ none
Definition pattern.hpp:15
@ invalid_mode
Definition pattern.hpp:20
@ capacity_exceeded
Definition pattern.hpp:17
@ pulses_exceed_steps
Definition pattern.hpp:18
@ invalid_probability
Definition pattern.hpp:19
@ empty_pattern
Definition pattern.hpp:16
@ none
Definition markov.hpp:15
@ capacity_exceeded
Definition markov.hpp:17
constexpr PatternResult< MaxSteps > materialize_pattern(EuclideanPatternRecipe recipe) noexcept
Definition pattern.hpp:155
constexpr std::uint16_t euclidean_pattern_recipe_version
Definition pattern.hpp:139
CellularBoundary
Definition pattern.hpp:264
@ fixed_off
Definition pattern.hpp:265
@ wrap
Definition pattern.hpp:266
Definition pattern.hpp:144
std::uint16_t version
Definition pattern.hpp:145
constexpr auto operator<=>(const EuclideanPatternRecipe &) const =default
std::int64_t rotation
Definition pattern.hpp:148
std::uint16_t steps
Definition pattern.hpp:146
std::uint16_t pulses
Definition pattern.hpp:147
Definition pattern.hpp:301
std::uint32_t denominator
Definition pattern.hpp:303
std::uint32_t numerator
Definition pattern.hpp:302
Definition pattern.hpp:83
PatternError error
Definition pattern.hpp:85
BinaryPattern< MaxSteps > pattern
Definition pattern.hpp:84
Definition pattern.hpp:307
PatternError error
Definition pattern.hpp:309
bool mutated
Definition pattern.hpp:311
BinaryPattern< MaxSteps > pattern
Definition pattern.hpp:308
bool output
Definition pattern.hpp:310