Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
rhythm_relationship.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <compare>
6#include <cstddef>
7#include <cstdint>
8#include <limits>
9
10namespace pulp::music {
11
12enum class RhythmRelationship : std::uint8_t {
16};
17
18enum class RhythmLengthMapping : std::uint8_t {
19 wrap = 0,
21};
22
27
28enum class RhythmDensityPolicy : std::uint8_t {
31};
32
44
46 std::uint64_t seed = 0;
47 std::int64_t cycle = 0;
48 std::uint32_t lane = 0;
49
50 constexpr auto operator<=>(const RhythmDrawCoordinate&) const = default;
51};
52
63
64template <std::size_t MaxSteps = 64> struct RhythmRelationshipResult {
67 constexpr explicit operator bool() const noexcept {
69 }
70};
71
72namespace detail {
73
74// SplitMix64's stateless finalizer turns the decision coordinate into a draw;
75// no mutable stream means lane evaluation order cannot change the result.
76constexpr std::uint64_t rhythm_mix(std::uint64_t value) noexcept {
77 value += 0x9E3779B97F4A7C15ull;
78 value = (value ^ (value >> 30u)) * 0xBF58476D1CE4E5B9ull;
79 value = (value ^ (value >> 27u)) * 0x94D049BB133111EBull;
80 return value ^ (value >> 31u);
81}
82
83constexpr std::uint64_t rhythm_draw(RhythmDrawCoordinate coordinate,
84 std::size_t target_step) noexcept {
85 auto value = rhythm_mix(coordinate.seed);
86 value = rhythm_mix(value ^ static_cast<std::uint64_t>(coordinate.cycle));
87 value = rhythm_mix(value ^ (static_cast<std::uint64_t>(coordinate.lane) << 32u));
88 return rhythm_mix(value ^ static_cast<std::uint64_t>(target_step));
89}
90
91constexpr bool valid_relationship(RhythmRelationship value) noexcept {
94}
95
96constexpr bool valid_length_mapping(RhythmLengthMapping value) noexcept {
98}
99
104
105constexpr bool valid_density_policy(RhythmDensityPolicy value) noexcept {
108}
109
110constexpr std::size_t delayed_step(std::size_t step, std::size_t length,
111 std::int64_t phase_steps) noexcept {
112 const auto magnitude = phase_steps >= 0
113 ? static_cast<std::uint64_t>(phase_steps)
114 : static_cast<std::uint64_t>(-(phase_steps + 1)) + 1u;
115 const auto phase = static_cast<std::size_t>(magnitude % length);
116 if (phase_steps >= 0)
117 return step >= phase ? step - phase : length - (phase - step);
118 return step < length - phase ? step + phase : step - (length - phase);
119}
120
121// Computes floor(multiplicand * multiplier / divisor) without forming the
122// potentially overflowing product. The mapping caller supplies
123// multiplicand < divisor, so every prefix quotient is representable.
124constexpr std::size_t multiply_divide_floor(std::size_t multiplicand,
125 std::size_t multiplier,
126 std::size_t divisor) noexcept {
127 std::size_t quotient = 0;
128 std::size_t remainder = 0;
129 auto bit = std::size_t{1} << (std::numeric_limits<std::size_t>::digits - 1);
130 for (; bit != 0; bit >>= 1u) {
131 quotient *= 2;
132 if (remainder >= divisor - remainder) {
133 remainder -= divisor - remainder;
134 ++quotient;
135 } else {
136 remainder += remainder;
137 }
138 if ((multiplier & bit) == 0)
139 continue;
140 if (remainder >= divisor - multiplicand) {
141 remainder -= divisor - multiplicand;
142 ++quotient;
143 } else {
144 remainder += multiplicand;
145 }
146 }
147 return quotient;
148}
149
150template <std::size_t MaxSteps>
151constexpr std::size_t mapped_source_step(const BinaryPattern<MaxSteps>& source,
152 const RhythmRelationshipConfig& config,
153 std::size_t target_step) noexcept {
154 const auto shifted = delayed_step(target_step, config.target_steps, config.phase_steps);
155 if (config.length_mapping == RhythmLengthMapping::wrap)
156 return shifted % source.size();
157 return multiply_divide_floor(shifted, source.size(), config.target_steps);
158}
159
160template <std::size_t MaxSteps>
162 const RhythmRelationshipConfig& config,
163 std::size_t target_step) noexcept {
164 const auto source_step = mapped_source_step(source, config, target_step);
165 const bool source_onset = *source.at(source_step);
166 bool candidate = false;
167 switch (config.relationship) {
169 candidate = source_onset;
170 break;
172 candidate = !source_onset;
173 break;
175 candidate = true;
176 break;
177 }
178 return candidate &&
179 (config.collision == RhythmCollisionPolicy::allow_source_overlap || !source_onset);
180}
181
182} // namespace detail
183
184// Derives a target lane without allocation or hidden state. Positive phase
185// delays the relationship on the target grid. Wrap mapping repeats source steps;
186// proportional mapping scales the source cycle once across the target length.
187template <std::size_t MaxSteps = 64>
188[[nodiscard]] constexpr RhythmRelationshipResult<MaxSteps>
190 const RhythmRelationshipConfig& config) noexcept {
192 if (source.empty()) {
194 return result;
195 }
196 if (config.target_steps == 0 || config.target_steps > MaxSteps) {
197 result.error = config.target_steps == 0 ? RhythmRelationshipError::empty_target
199 return result;
200 }
201 if (!detail::valid_relationship(config.relationship)) {
203 return result;
204 }
205 if (!detail::valid_length_mapping(config.length_mapping)) {
207 return result;
208 }
209 if (!detail::valid_collision_policy(config.collision)) {
211 return result;
212 }
213 if (!detail::valid_density_policy(config.density)) {
215 return result;
216 }
217
218 (void)result.pattern.resize(config.target_steps);
219 std::size_t candidate_count = 0;
220 for (std::size_t step = 0; step < config.target_steps; ++step)
221 candidate_count += detail::relationship_candidate(source, config, step);
222
223 if (config.density == RhythmDensityPolicy::exact_onsets &&
224 config.target_onsets > candidate_count) {
226 return result;
227 }
228
229 for (std::size_t step = 0; step < config.target_steps; ++step) {
230 if (!detail::relationship_candidate(source, config, step))
231 continue;
232 if (config.density == RhythmDensityPolicy::preserve_candidates) {
233 (void)result.pattern.set(step, true);
234 continue;
235 }
236
237 const auto priority = detail::rhythm_draw(config.draw, step);
238 std::size_t rank = 0;
239 for (std::size_t other = 0; other < config.target_steps; ++other) {
240 if (!detail::relationship_candidate(source, config, other))
241 continue;
242 const auto other_priority = detail::rhythm_draw(config.draw, other);
243 rank += other_priority < priority || (other_priority == priority && other < step);
244 }
245 if (rank < config.target_onsets)
246 (void)result.pattern.set(step, true);
247 }
248 return result;
249}
250
251} // namespace pulp::music
Definition pattern.hpp:25
constexpr std::size_t multiply_divide_floor(std::size_t multiplicand, std::size_t multiplier, std::size_t divisor) noexcept
Definition rhythm_relationship.hpp:124
constexpr bool valid_relationship(RhythmRelationship value) noexcept
Definition rhythm_relationship.hpp:91
constexpr bool valid_collision_policy(RhythmCollisionPolicy value) noexcept
Definition rhythm_relationship.hpp:100
constexpr bool valid_density_policy(RhythmDensityPolicy value) noexcept
Definition rhythm_relationship.hpp:105
constexpr std::size_t delayed_step(std::size_t step, std::size_t length, std::int64_t phase_steps) noexcept
Definition rhythm_relationship.hpp:110
constexpr bool relationship_candidate(const BinaryPattern< MaxSteps > &source, const RhythmRelationshipConfig &config, std::size_t target_step) noexcept
Definition rhythm_relationship.hpp:161
constexpr bool valid_length_mapping(RhythmLengthMapping value) noexcept
Definition rhythm_relationship.hpp:96
constexpr std::uint64_t rhythm_mix(std::uint64_t value) noexcept
Definition rhythm_relationship.hpp:76
constexpr std::uint64_t rhythm_draw(RhythmDrawCoordinate coordinate, std::size_t target_step) noexcept
Definition rhythm_relationship.hpp:83
constexpr std::size_t mapped_source_step(const BinaryPattern< MaxSteps > &source, const RhythmRelationshipConfig &config, std::size_t target_step) noexcept
Definition rhythm_relationship.hpp:151
Definition chord.hpp:14
constexpr RhythmRelationshipResult< MaxSteps > derive_rhythm_relationship(const BinaryPattern< MaxSteps > &source, const RhythmRelationshipConfig &config) noexcept
Definition rhythm_relationship.hpp:189
RhythmDensityPolicy
Definition rhythm_relationship.hpp:28
@ exact_onsets
Definition rhythm_relationship.hpp:30
@ preserve_candidates
Definition rhythm_relationship.hpp:29
@ none
Definition markov.hpp:15
@ capacity_exceeded
Definition markov.hpp:17
RhythmRelationshipError
Definition rhythm_relationship.hpp:33
@ invalid_collision_policy
Definition rhythm_relationship.hpp:40
@ none
Definition rhythm_relationship.hpp:34
@ invalid_relationship
Definition rhythm_relationship.hpp:38
@ empty_source
Definition rhythm_relationship.hpp:35
@ insufficient_candidates
Definition rhythm_relationship.hpp:42
@ empty_target
Definition rhythm_relationship.hpp:36
@ invalid_length_mapping
Definition rhythm_relationship.hpp:39
@ capacity_exceeded
Definition rhythm_relationship.hpp:37
@ invalid_density_policy
Definition rhythm_relationship.hpp:41
@ wrap
Definition pattern.hpp:266
RhythmCollisionPolicy
Definition rhythm_relationship.hpp:23
@ avoid_source_overlap
Definition rhythm_relationship.hpp:25
@ allow_source_overlap
Definition rhythm_relationship.hpp:24
RhythmLengthMapping
Definition rhythm_relationship.hpp:18
@ proportional
Definition rhythm_relationship.hpp:20
@ wrap
Definition rhythm_relationship.hpp:19
RhythmRelationship
Definition rhythm_relationship.hpp:12
@ complementary
Definition rhythm_relationship.hpp:14
@ independent
Definition rhythm_relationship.hpp:15
@ coincident
Definition rhythm_relationship.hpp:13
Definition rhythm_relationship.hpp:45
std::uint32_t lane
Definition rhythm_relationship.hpp:48
std::int64_t cycle
Definition rhythm_relationship.hpp:47
std::uint64_t seed
Definition rhythm_relationship.hpp:46
constexpr auto operator<=>(const RhythmDrawCoordinate &) const =default
Definition rhythm_relationship.hpp:53
RhythmLengthMapping length_mapping
Definition rhythm_relationship.hpp:56
RhythmDrawCoordinate draw
Definition rhythm_relationship.hpp:61
std::size_t target_steps
Definition rhythm_relationship.hpp:54
RhythmDensityPolicy density
Definition rhythm_relationship.hpp:59
std::size_t target_onsets
Definition rhythm_relationship.hpp:60
std::int64_t phase_steps
Definition rhythm_relationship.hpp:57
RhythmRelationship relationship
Definition rhythm_relationship.hpp:55
RhythmCollisionPolicy collision
Definition rhythm_relationship.hpp:58
Definition rhythm_relationship.hpp:64
BinaryPattern< MaxSteps > pattern
Definition rhythm_relationship.hpp:65
RhythmRelationshipError error
Definition rhythm_relationship.hpp:66