Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pattern_development.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 <optional>
10#include <span>
11
12namespace pulp::music {
13
15using PatternEventId = std::uint64_t;
16
18enum class PatternEventRole : std::uint8_t {
20 anchor = 0,
27};
28
36 std::uint16_t accent = 1000;
39
40 constexpr auto operator<=>(const PatternEvent&) const = default;
41};
42
72
73namespace detail {
74
75constexpr bool valid_pattern_event_role(PatternEventRole role) noexcept {
76 return role == PatternEventRole::anchor || role == PatternEventRole::primary ||
78}
79
80constexpr bool event_less(const PatternEvent& lhs, const PatternEvent& rhs) noexcept {
81 return lhs.onset < rhs.onset || (lhs.onset == rhs.onset && lhs.id < rhs.id);
82}
83
84constexpr std::uint64_t event_priority(std::uint64_t seed, timebase::RandomCoordinate coordinate,
85 const PatternEvent& event) noexcept {
86 coordinate.tick = event.onset;
87 coordinate.stream ^= event.id;
88 return timebase::coordinate_random(seed, coordinate);
89}
90
91constexpr std::uint64_t event_threshold(std::uint64_t seed, timebase::RandomCoordinate coordinate,
92 const PatternEvent& event) noexcept {
93 return timebase::detail::multiply_high(event_priority(seed, coordinate, event), 1001u);
94}
95
96constexpr std::uint64_t positive_distance(std::int64_t low, std::int64_t high) noexcept {
97 if (low >= 0)
98 return static_cast<std::uint64_t>(high - low);
99 const auto low_magnitude = static_cast<std::uint64_t>(-(low + 1)) + 1u;
100 if (high < 0)
101 return low_magnitude - (static_cast<std::uint64_t>(-(high + 1)) + 1u);
102 return low_magnitude + static_cast<std::uint64_t>(high);
103}
104
105constexpr std::int64_t add_unsigned(std::int64_t value, std::uint64_t amount) noexcept {
106 if (value >= 0)
107 return value + static_cast<std::int64_t>(amount);
108 const auto magnitude = static_cast<std::uint64_t>(-(value + 1)) + 1u;
109 if (amount < magnitude) {
110 const auto remaining = magnitude - amount;
111 return -static_cast<std::int64_t>(remaining - 1u) - 1;
112 }
113 return static_cast<std::int64_t>(amount - magnitude);
114}
115
116constexpr std::int64_t subtract_unsigned(std::int64_t value, std::uint64_t amount) noexcept {
117 if (value <= 0) {
118 const auto magnitude = static_cast<std::uint64_t>(-(value + 1)) + 1u;
119 const auto result_magnitude = magnitude + amount;
120 return -static_cast<std::int64_t>(result_magnitude - 1u) - 1;
121 }
122 const auto positive = static_cast<std::uint64_t>(value);
123 if (amount <= positive)
124 return static_cast<std::int64_t>(positive - amount);
125 const auto magnitude = amount - positive;
126 return -static_cast<std::int64_t>(magnitude - 1u) - 1;
127}
128
129constexpr std::uint64_t scale_distance(std::uint64_t distance, std::uint16_t amount) noexcept {
130 return (distance / 1000u) * amount + ((distance % 1000u) * amount) / 1000u;
131}
132
133constexpr std::int64_t interpolate_integer(std::int64_t from, std::int64_t to,
134 std::uint16_t amount) noexcept {
135 if (amount == 0)
136 return from;
137 if (amount == 1000)
138 return to;
139 if (from < to)
140 return add_unsigned(from, scale_distance(positive_distance(from, to), amount));
141 return subtract_unsigned(from, scale_distance(positive_distance(to, from), amount));
142}
143
144} // namespace detail
145
150[[nodiscard]] constexpr PatternEventId
151make_pattern_event_id(std::uint64_t seed, timebase::RandomCoordinate coordinate) noexcept {
152 const auto value = timebase::coordinate_random(seed, coordinate);
153 return value == 0 ? PatternEventId{1} : value;
154}
155
157template <std::size_t MaxEvents = 64> class DevelopmentPattern {
158 public:
159 static_assert(MaxEvents > 0, "DevelopmentPattern capacity must be positive");
160 static_assert(MaxEvents <= 64, "Pattern development is bounded to 64 events");
162 static constexpr std::size_t capacity = MaxEvents;
163
165 [[nodiscard]] constexpr PatternDevelopmentError
166 assign(std::span<const PatternEvent> events) noexcept {
167 if (events.size() > MaxEvents)
169
170 DevelopmentPattern candidate;
171 for (const auto& event : events) {
172 const auto error = candidate.insert(event);
174 return error;
175 }
176 *this = candidate;
178 }
179
181 [[nodiscard]] constexpr PatternDevelopmentError insert(PatternEvent event) noexcept {
182 if (size_ == MaxEvents)
184 if (event.id == 0)
188 if (event.accent > 1000)
190 for (std::size_t index = 0; index < size_; ++index) {
191 if (events_[index].id == event.id)
193 if (events_[index].onset == event.onset)
195 }
196
197 std::size_t insertion = size_;
198 while (insertion > 0 && detail::event_less(event, events_[insertion - 1])) {
199 events_[insertion] = events_[insertion - 1];
200 --insertion;
201 }
202 events_[insertion] = event;
203 ++size_;
205 }
206
208 constexpr std::size_t size() const noexcept {
209 return size_;
210 }
211
212 constexpr bool empty() const noexcept {
213 return size_ == 0;
214 }
215
217 [[nodiscard]] constexpr std::optional<PatternEvent> event(std::size_t index) const noexcept {
218 if (index >= size_)
219 return std::nullopt;
220 return events_[index];
221 }
222
224 [[nodiscard]] constexpr std::optional<PatternEvent> find_id(PatternEventId id) const noexcept {
225 for (std::size_t index = 0; index < size_; ++index)
226 if (events_[index].id == id)
227 return events_[index];
228 return std::nullopt;
229 }
230
232 [[nodiscard]] constexpr std::optional<PatternEvent>
233 find_onset(timebase::TickPosition onset) const noexcept {
234 for (std::size_t index = 0; index < size_; ++index)
235 if (events_[index].onset == onset)
236 return events_[index];
237 return std::nullopt;
238 }
239
241 constexpr std::size_t anchor_count() const noexcept {
242 std::size_t result = 0;
243 for (std::size_t index = 0; index < size_; ++index)
244 result += events_[index].role == PatternEventRole::anchor;
245 return result;
246 }
247
248 constexpr auto operator<=>(const DevelopmentPattern&) const = default;
249
250 private:
251 std::array<PatternEvent, MaxEvents> events_{};
252 std::size_t size_ = 0;
253};
254
256template <std::size_t MaxEvents = 64> struct PatternDevelopmentResult {
261
263 constexpr explicit operator bool() const noexcept {
265 }
266};
267
279
285template <std::size_t MaxEvents = 64>
286[[nodiscard]] constexpr PatternDevelopmentResult<MaxEvents>
288 PatternSetOperation operation) noexcept {
290 if (operation != PatternSetOperation::set_union &&
292 operation != PatternSetOperation::difference &&
295 return result;
296 }
297
298 for (std::size_t index = 0; index < lhs.size(); ++index) {
299 const auto event = *lhs.event(index);
300 const auto other = rhs.find_onset(event.onset);
301 const bool include = operation == PatternSetOperation::set_union ||
302 (operation == PatternSetOperation::intersection && other) ||
303 (operation == PatternSetOperation::difference && !other) ||
304 (operation == PatternSetOperation::symmetric_difference && !other);
305 if (other && operation == PatternSetOperation::set_union && *other != event) {
307 result.pattern = {};
308 return result;
309 }
310 if (include) {
311 result.error = result.pattern.insert(event);
312 if (result.error != PatternDevelopmentError::none) {
313 result.pattern = {};
314 return result;
315 }
316 }
317 }
318 if (operation == PatternSetOperation::intersection ||
320 return result;
321 for (std::size_t index = 0; index < rhs.size(); ++index) {
322 const auto event = *rhs.event(index);
323 if (lhs.find_onset(event.onset))
324 continue;
325 result.error = result.pattern.insert(event);
326 if (result.error != PatternDevelopmentError::none) {
327 result.pattern = {};
328 return result;
329 }
330 }
331 return result;
332}
333
337 std::size_t target_onsets = 0;
339 std::uint64_t seed = 0;
342};
343
348template <std::size_t MaxEvents = 64>
349[[nodiscard]] constexpr PatternDevelopmentResult<MaxEvents>
351 DensitySelection selection) noexcept {
353 if (selection.target_onsets > source.size()) {
355 return result;
356 }
357 const auto anchors = source.anchor_count();
358 if (selection.target_onsets < anchors) {
360 return result;
361 }
362
363 const auto selected_nonanchors = selection.target_onsets - anchors;
364 for (std::size_t index = 0; index < source.size(); ++index) {
365 const auto event = *source.event(index);
366 bool include = event.role == PatternEventRole::anchor;
367 if (!include) {
368 const auto priority =
369 detail::event_priority(selection.seed, selection.coordinate, event);
370 std::size_t rank = 0;
371 for (std::size_t other_index = 0; other_index < source.size(); ++other_index) {
372 const auto other = *source.event(other_index);
373 if (other.role == PatternEventRole::anchor)
374 continue;
375 const auto other_priority =
376 detail::event_priority(selection.seed, selection.coordinate, other);
377 rank += other_priority < priority ||
378 (other_priority == priority && other.id < event.id);
379 }
380 include = rank < selected_nonanchors;
381 }
382 if (include) {
383 result.error = result.pattern.insert(event);
385 return result;
386 }
387 }
388 return result;
389}
390
404
409template <std::size_t MaxEvents = 64>
410[[nodiscard]] constexpr PatternDevelopmentResult<MaxEvents>
412 const DevelopmentPattern<MaxEvents>& candidates,
413 RegionalFillSelection selection) noexcept {
415 if (selection.begin >= selection.end) {
417 return result;
418 }
419
420 std::size_t region_events = 0;
421 std::size_t region_anchors = 0;
422 std::size_t outside_events = 0;
423 for (std::size_t index = 0; index < base.size(); ++index) {
424 const auto event = *base.event(index);
425 if (event.onset < selection.begin || event.onset >= selection.end) {
426 ++outside_events;
427 result.error = result.pattern.insert(event);
428 if (result.error != PatternDevelopmentError::none) {
429 result.pattern = {};
430 return result;
431 }
432 } else {
433 ++region_events;
434 region_anchors += event.role == PatternEventRole::anchor;
435 }
436 }
437 for (std::size_t index = 0; index < candidates.size(); ++index) {
438 const auto event = *candidates.event(index);
439 if (event.onset < selection.begin || event.onset >= selection.end)
440 continue;
441 const auto base_event = base.find_id(event.id);
442 if (base_event && *base_event != event) {
444 result.pattern = {};
445 return result;
446 }
447 const auto existing = base.find_onset(event.onset);
448 if (existing) {
449 if (*existing != event) {
451 result.pattern = {};
452 return result;
453 }
454 continue;
455 }
456 ++region_events;
457 }
458
459 if (selection.target_region_onsets > region_events) {
461 result.pattern = {};
462 return result;
463 }
464 if (selection.target_region_onsets < region_anchors) {
466 result.pattern = {};
467 return result;
468 }
469 if (outside_events + selection.target_region_onsets > MaxEvents) {
471 result.pattern = {};
472 return result;
473 }
474
475 const auto selected_nonanchors = selection.target_region_onsets - region_anchors;
476 const auto rank = [&](const PatternEvent& event) constexpr noexcept {
477 const auto priority = detail::event_priority(selection.seed, selection.coordinate, event);
478 std::size_t result_rank = 0;
479 const auto add_to_rank = [&](const PatternEvent& other) constexpr noexcept {
480 const auto other_priority =
481 detail::event_priority(selection.seed, selection.coordinate, other);
482 result_rank +=
483 other_priority < priority || (other_priority == priority && other.id < event.id);
484 };
485 for (std::size_t index = 0; index < base.size(); ++index) {
486 const auto other = *base.event(index);
487 if (other.onset >= selection.begin && other.onset < selection.end &&
488 other.role != PatternEventRole::anchor)
489 add_to_rank(other);
490 }
491 for (std::size_t index = 0; index < candidates.size(); ++index) {
492 const auto other = *candidates.event(index);
493 if (other.onset < selection.begin || other.onset >= selection.end ||
494 base.find_onset(other.onset))
495 continue;
496 add_to_rank(other);
497 }
498 return result_rank;
499 };
500
501 for (std::size_t index = 0; index < base.size(); ++index) {
502 const auto event = *base.event(index);
503 if (event.onset < selection.begin || event.onset >= selection.end)
504 continue;
505 if (event.role != PatternEventRole::anchor && rank(event) >= selected_nonanchors)
506 continue;
507 result.error = result.pattern.insert(event);
508 if (result.error != PatternDevelopmentError::none) {
509 result.pattern = {};
510 return result;
511 }
512 }
513 for (std::size_t index = 0; index < candidates.size(); ++index) {
514 const auto event = *candidates.event(index);
515 if (event.onset < selection.begin || event.onset >= selection.end ||
516 base.find_onset(event.onset) || rank(event) >= selected_nonanchors)
517 continue;
518 result.error = result.pattern.insert(event);
519 if (result.error != PatternDevelopmentError::none) {
520 result.pattern = {};
521 return result;
522 }
523 }
524 return result;
525}
526
530 std::uint16_t amount = 0;
532 std::uint64_t seed = 0;
535};
536
541template <std::size_t MaxEvents = 64>
542[[nodiscard]] constexpr PatternDevelopmentResult<MaxEvents>
544 PatternMorphSelection selection) noexcept {
546 if (selection.amount > 1000) {
548 return result;
549 }
550 if (selection.amount == 0) {
551 result.pattern = a;
552 return result;
553 }
554 if (selection.amount == 1000) {
555 result.pattern = b;
556 return result;
557 }
558
559 for (std::size_t index = 0; index < a.size(); ++index) {
560 auto event = *a.event(index);
561 const auto other = b.find_id(event.id);
562 bool include = true;
563 if (other) {
564 event.onset.value = detail::interpolate_integer(event.onset.value, other->onset.value,
565 selection.amount);
566 const auto accent_delta =
567 static_cast<std::int32_t>(other->accent) - static_cast<std::int32_t>(event.accent);
568 event.accent = static_cast<std::uint16_t>(static_cast<std::int32_t>(event.accent) +
569 accent_delta * selection.amount / 1000);
570 if (selection.amount >= 500)
571 event.role = other->role;
572 } else {
573 const auto threshold =
574 detail::event_threshold(selection.seed, selection.coordinate, event);
575 include = selection.amount <= threshold;
576 }
577 if (include) {
578 result.error = result.pattern.insert(event);
579 if (result.error != PatternDevelopmentError::none) {
580 result.pattern = {};
581 return result;
582 }
583 }
584 }
585 for (std::size_t index = 0; index < b.size(); ++index) {
586 const auto event = *b.event(index);
587 if (a.find_id(event.id))
588 continue;
589 const auto threshold = detail::event_threshold(selection.seed, selection.coordinate, event);
590 if (selection.amount <= threshold)
591 continue;
592 result.error = result.pattern.insert(event);
593 if (result.error != PatternDevelopmentError::none) {
594 result.pattern = {};
595 return result;
596 }
597 }
598 return result;
599}
600
601} // namespace pulp::music
Fixed-capacity, onset-ordered collection used by development operations.
Definition pattern_development.hpp:157
constexpr std::size_t size() const noexcept
Returns the number of stored events.
Definition pattern_development.hpp:208
constexpr auto operator<=>(const DevelopmentPattern &) const =default
constexpr std::optional< PatternEvent > find_onset(timebase::TickPosition onset) const noexcept
Finds an event at an exact timeline onset.
Definition pattern_development.hpp:233
constexpr std::optional< PatternEvent > find_id(PatternEventId id) const noexcept
Finds an event by stable identity.
Definition pattern_development.hpp:224
static constexpr std::size_t capacity
Maximum number of events the pattern can hold.
Definition pattern_development.hpp:162
constexpr PatternDevelopmentError assign(std::span< const PatternEvent > events) noexcept
Replaces the pattern with validated events, preserving the old value on error.
Definition pattern_development.hpp:166
constexpr std::optional< PatternEvent > event(std::size_t index) const noexcept
Returns the event at an onset-ordered index, or no value when out of range.
Definition pattern_development.hpp:217
constexpr PatternDevelopmentError insert(PatternEvent event) noexcept
Inserts one validated event in onset order.
Definition pattern_development.hpp:181
constexpr bool empty() const noexcept
Returns true when the pattern contains no events.
Definition pattern_development.hpp:212
constexpr std::size_t anchor_count() const noexcept
Returns the number of mandatory anchor events.
Definition pattern_development.hpp:241
Definition child_process_manager.hpp:18
constexpr std::uint64_t positive_distance(std::int64_t low, std::int64_t high) noexcept
Definition pattern_development.hpp:96
constexpr std::int64_t add_unsigned(std::int64_t value, std::uint64_t amount) noexcept
Definition pattern_development.hpp:105
constexpr bool event_less(const PatternEvent &lhs, const PatternEvent &rhs) noexcept
Definition pattern_development.hpp:80
constexpr std::uint64_t event_priority(std::uint64_t seed, timebase::RandomCoordinate coordinate, const PatternEvent &event) noexcept
Definition pattern_development.hpp:84
constexpr std::int64_t subtract_unsigned(std::int64_t value, std::uint64_t amount) noexcept
Definition pattern_development.hpp:116
constexpr std::uint64_t event_threshold(std::uint64_t seed, timebase::RandomCoordinate coordinate, const PatternEvent &event) noexcept
Definition pattern_development.hpp:91
constexpr bool valid_pattern_event_role(PatternEventRole role) noexcept
Definition pattern_development.hpp:75
constexpr std::uint64_t scale_distance(std::uint64_t distance, std::uint16_t amount) noexcept
Definition pattern_development.hpp:129
constexpr std::int64_t interpolate_integer(std::int64_t from, std::int64_t to, std::uint16_t amount) noexcept
Definition pattern_development.hpp:133
Definition chord.hpp:14
constexpr PatternEventId make_pattern_event_id(std::uint64_t seed, timebase::RandomCoordinate coordinate) noexcept
Produces a stable nonzero ID from a seed and musical coordinate.
Definition pattern_development.hpp:151
constexpr PatternDevelopmentResult< MaxEvents > apply_regional_fill(const DevelopmentPattern< MaxEvents > &base, const DevelopmentPattern< MaxEvents > &candidates, RegionalFillSelection selection) noexcept
Applies candidate events within a selected half-open region.
Definition pattern_development.hpp:411
PatternEventRole
Describes how an event participates in density, fill, and morph operations.
Definition pattern_development.hpp:18
@ primary
Ordinary event eligible for deterministic selection.
Definition pattern_development.hpp:22
@ ornament
Decorative event eligible for deterministic selection.
Definition pattern_development.hpp:24
@ anchor
Mandatory structural event that density operations always retain.
Definition pattern_development.hpp:20
@ fill
Event intended to supply regional variation.
Definition pattern_development.hpp:26
PatternSetOperation
Supported onset-keyed set operations.
Definition pattern_development.hpp:269
@ intersection
Include events found in both inputs.
Definition pattern_development.hpp:273
@ set_union
Include events found in either input.
Definition pattern_development.hpp:271
@ symmetric_difference
Include events found in exactly one input.
Definition pattern_development.hpp:277
@ difference
Include events found only in the left input.
Definition pattern_development.hpp:275
@ a
Definition pitch.hpp:29
@ b
Definition pitch.hpp:31
constexpr PatternDevelopmentResult< MaxEvents > select_pattern_density(const DevelopmentPattern< MaxEvents > &source, DensitySelection selection) noexcept
Selects an exact event count while retaining every anchor.
Definition pattern_development.hpp:350
std::uint64_t PatternEventId
Stable, nonzero identity for an event across pattern transformations.
Definition pattern_development.hpp:15
@ none
Definition markov.hpp:15
@ capacity_exceeded
Definition markov.hpp:17
PatternDevelopmentError
Result codes returned by bounded pattern-development operations.
Definition pattern_development.hpp:44
@ morph_amount_out_of_range
A morph amount exceeded 1000.
Definition pattern_development.hpp:70
@ accent_out_of_range
An event accent exceeded 1000.
Definition pattern_development.hpp:58
@ conflicting_event
Two inputs disagreed about the event at one identity or onset.
Definition pattern_development.hpp:62
@ none
The operation completed successfully.
Definition pattern_development.hpp:46
@ density_out_of_range
A requested density exceeded the available event count.
Definition pattern_development.hpp:64
@ zero_event_id
An event used the reserved zero identity.
Definition pattern_development.hpp:50
@ invalid_set_operation
A set operation was outside the declared enumeration.
Definition pattern_development.hpp:60
@ invalid_role
An event role was outside the declared enumeration.
Definition pattern_development.hpp:56
@ duplicate_onset
Two events occupied the same onset.
Definition pattern_development.hpp:54
@ duplicate_event_id
Two events used the same identity.
Definition pattern_development.hpp:52
@ capacity_exceeded
The result would exceed the pattern's fixed capacity.
Definition pattern_development.hpp:48
@ invalid_region
A regional operation used an empty or reversed half-open interval.
Definition pattern_development.hpp:68
@ density_below_anchor_count
A requested density could not retain every anchor.
Definition pattern_development.hpp:66
constexpr PatternDevelopmentResult< MaxEvents > morph_patterns(const DevelopmentPattern< MaxEvents > &a, const DevelopmentPattern< MaxEvents > &b, PatternMorphSelection selection) noexcept
Morphs two patterns using exact integer interpolation and stable thresholds.
Definition pattern_development.hpp:543
constexpr PatternDevelopmentResult< MaxEvents > pattern_set(const DevelopmentPattern< MaxEvents > &lhs, const DevelopmentPattern< MaxEvents > &rhs, PatternSetOperation operation) noexcept
Applies an onset-keyed set operation to two patterns.
Definition pattern_development.hpp:287
constexpr std::uint64_t multiply_high(std::uint64_t lhs, std::uint64_t rhs) noexcept
Definition coordinate_random.hpp:31
constexpr std::uint64_t coordinate_random(std::uint64_t seed, RandomCoordinate coordinate) noexcept
Definition coordinate_random.hpp:47
Parameters for deterministic density selection.
Definition pattern_development.hpp:335
std::size_t target_onsets
Exact number of output onsets, including mandatory anchors.
Definition pattern_development.hpp:337
timebase::RandomCoordinate coordinate
Coordinate namespace used to rank non-anchor events.
Definition pattern_development.hpp:341
std::uint64_t seed
Stable recipe seed.
Definition pattern_development.hpp:339
Pattern-development output paired with an explicit result code.
Definition pattern_development.hpp:256
PatternDevelopmentError error
Operation result code.
Definition pattern_development.hpp:260
DevelopmentPattern< MaxEvents > pattern
Result pattern, empty when the operation cannot produce a valid value.
Definition pattern_development.hpp:258
One uniquely identified onset in a development pattern.
Definition pattern_development.hpp:30
std::uint16_t accent
Relative emphasis in the inclusive range [0, 1000].
Definition pattern_development.hpp:36
timebase::TickPosition onset
Musical onset in timeline ticks.
Definition pattern_development.hpp:34
PatternEventRole role
Structural role used by development operations.
Definition pattern_development.hpp:38
constexpr auto operator<=>(const PatternEvent &) const =default
Parameters for deterministic interpolation between two patterns.
Definition pattern_development.hpp:528
std::uint64_t seed
Stable recipe seed for events unique to one input.
Definition pattern_development.hpp:532
timebase::RandomCoordinate coordinate
Coordinate namespace for unique-event thresholds.
Definition pattern_development.hpp:534
std::uint16_t amount
Interpolation amount in the inclusive range [0, 1000].
Definition pattern_development.hpp:530
Parameters for deterministic replacement within a half-open region.
Definition pattern_development.hpp:392
std::uint64_t seed
Stable recipe seed.
Definition pattern_development.hpp:400
timebase::RandomCoordinate coordinate
Coordinate namespace used to rank region events.
Definition pattern_development.hpp:402
timebase::TickPosition end
Exclusive end of the edited region.
Definition pattern_development.hpp:396
std::size_t target_region_onsets
Exact number of onsets to retain inside the region.
Definition pattern_development.hpp:398
timebase::TickPosition begin
Inclusive start of the edited region.
Definition pattern_development.hpp:394
Definition coordinate_random.hpp:14
Definition tick.hpp:61