Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
clip_launch.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <array>
8#include <compare>
9#include <cstddef>
10#include <cstdint>
11#include <initializer_list>
12#include <iterator>
13#include <memory>
14#include <span>
15#include <string>
16#include <vector>
17
24namespace pulp::timeline {
25
29
30namespace detail {
31class SlotListStore;
32class SlotListAccess;
33} // namespace detail
34
43
45 constexpr bool immediate() const noexcept {
46 return grid.value <= 0;
47 }
48 constexpr auto operator<=>(const LaunchQuantize&) const = default;
49};
50
55
59constexpr LaunchQuantize launch_every_quarters(std::int64_t count) noexcept {
61}
62
67constexpr LaunchQuantize launch_every_bars(std::int64_t count,
68 timebase::MeterSignature meter) noexcept {
69 const std::int64_t ticks_per_bar =
70 timebase::kTicksPerQuarter * meter.numerator * 4 / meter.denominator;
71 return {timebase::TickDuration{count * ticks_per_bar}, timebase::TickPosition{0}};
72}
73
75enum class FollowActionKind : std::uint8_t {
76 None, // keep sounding; the slot is left alone
77 Stop, // stop this slot
78 Again, // relaunch this slot
79 Previous, // launch the nearest non-empty slot before this one, wrapping
80 Next, // launch the nearest non-empty slot after this one, wrapping
81 First, // launch the first non-empty slot in the lane
82 Last, // launch the last non-empty slot in the lane
83 Any, // launch a non-empty slot chosen at random, this one included
84 Other, // launch a non-empty slot chosen at random, this one excluded
85 Jump, // launch the slot named by `target`
86};
87
95 std::uint16_t weight = 1;
96
97 constexpr auto operator<=>(const FollowAction&) const = default;
98};
99
106 static constexpr std::size_t kMaxChoices = 4;
107
108 std::array<FollowAction, kMaxChoices> choices{};
109 std::uint8_t choice_count = 0;
110 std::uint32_t repetitions = 1;
112
116 constexpr std::span<const FollowAction> active() const noexcept {
117 const std::size_t count = choice_count < kMaxChoices ? choice_count : kMaxChoices;
118 return {choices.data(), count};
119 }
120
122 constexpr bool enabled() const noexcept {
123 return !active().empty() && grid.value > 0 && repetitions > 0;
124 }
125
126 constexpr bool operator==(const FollowActionSet&) const = default;
127};
128
131 std::uint32_t repetitions = 1) noexcept {
132 FollowActionSet set;
133 set.choices[0] = FollowAction{kind, ItemId{}, 1};
134 set.choice_count = 1;
135 set.repetitions = repetitions;
136 set.grid = grid;
137 return set;
138}
139
145struct Slot {
150
152 constexpr bool empty() const noexcept {
153 return !clip_id.valid();
154 }
155 constexpr bool operator==(const Slot&) const = default;
156};
157
163class SlotList {
164 public:
166 class Iterator {
167 public:
169 using difference_type = std::ptrdiff_t;
170 using pointer = const Slot*;
171 using reference = const Slot&;
172 using iterator_category = std::forward_iterator_tag;
173
175 const Slot& operator*() const noexcept;
177 const Slot* operator->() const noexcept;
179 Iterator& operator++() noexcept;
181 Iterator operator++(int) noexcept {
182 auto copy = *this;
183 ++*this;
184 return copy;
185 }
186
187 bool operator==(const Iterator&) const noexcept = default;
188
189 private:
190 friend class SlotList;
191 Iterator(std::shared_ptr<const std::vector<Slot>> raw,
192 std::shared_ptr<const detail::SlotListStore> store, std::size_t raw_index,
193 ItemId current) noexcept
194 : raw_(std::move(raw)), store_(std::move(store)), raw_index_(raw_index),
195 current_(current) {}
196 std::shared_ptr<const std::vector<Slot>> raw_;
197 std::shared_ptr<const detail::SlotListStore> store_;
198 std::size_t raw_index_ = 0;
199 ItemId current_;
200 };
201
205 SlotList(std::vector<Slot> slots);
207 SlotList(std::initializer_list<Slot> slots);
208
210 std::size_t size() const noexcept;
212 bool empty() const noexcept {
213 return size() == 0;
214 }
215
216 const Slot& operator[](std::size_t index) const noexcept;
218 const Slot& front() const noexcept {
219 return (*this)[0];
220 }
221
222 const Slot& back() const noexcept {
223 return (*this)[size() - 1];
224 }
225
226 const Slot* find(ItemId id) const noexcept;
228 Iterator begin() const noexcept;
230 Iterator end() const noexcept;
232 bool shares_storage_with(const SlotList& other) const noexcept;
234 bool operator==(const SlotList& other) const noexcept;
235
236 private:
237 friend class detail::SlotListAccess;
238 explicit SlotList(std::shared_ptr<const detail::SlotListStore> store) noexcept
239 : store_(std::move(store)) {}
240 std::shared_ptr<const std::vector<Slot>> raw_;
241 std::shared_ptr<const detail::SlotListStore> store_;
242};
243
247struct Scene {
249 std::string name;
251
252 bool operator==(const Scene&) const = default;
253};
254
255// Follow-action resolution
256// ------------------------
257// Resolution is a pure function of the lane contents, the acting slot, and a
258// draw index — never of hidden mutable state — so an engine replaying the same
259// document with the same transport trace resolves the same successor every
260// time. Random kinds draw from a stateless hash of (seed, slot id, draw index):
261// two slots deciding in the same block cannot perturb each other's draw, and
262// evaluation order is therefore irrelevant to the result.
263
265constexpr std::uint64_t follow_action_mix(std::uint64_t value) noexcept {
266 value += 0x9E3779B97F4A7C15ULL;
267 value ^= value >> 30;
268 value *= 0xBF58476D1CE4E5B9ULL;
269 value ^= value >> 27;
270 value *= 0x94D049BB133111EBULL;
271 value ^= value >> 31;
272 return value;
273}
274
280 std::uint64_t seed = 0;
282 std::uint64_t draw_index = 0;
283
285 constexpr std::uint64_t value() const noexcept {
287 (draw_index + 0x9E3779B97F4A7C15ULL));
288 }
289};
290
295constexpr std::size_t select_follow_action(const FollowActionSet& set, FollowDraw draw) noexcept {
296 const auto candidates = set.active();
297 std::uint64_t total = 0;
298 for (const auto& candidate : candidates)
299 total += candidate.weight;
300 if (total == 0)
302 std::uint64_t pick = draw.value() % total;
303 for (std::size_t index = 0; index < candidates.size(); ++index) {
304 const std::uint64_t weight = candidates[index].weight;
305 if (pick < weight)
306 return index;
307 pick -= weight;
308 }
309 return FollowActionSet::kMaxChoices; // unreachable: pick < total
310}
311
313enum class FollowOutcome : std::uint8_t {
314 None, // leave the acting slot sounding
315 Stop, // stop the acting slot
316 Play, // launch `slot_index` (which may be the acting slot: "again")
317};
318
321 static constexpr std::size_t kNoSlot = static_cast<std::size_t>(-1);
322
324 std::size_t slot_index = kNoSlot;
326
327 constexpr auto operator<=>(const FollowResolution&) const = default;
328};
329
330namespace follow_detail {
331
332constexpr std::size_t count_non_empty(std::span<const Slot> lane) noexcept {
333 std::size_t count = 0;
334 for (const auto& slot : lane)
335 count += slot.empty() ? 0u : 1u;
336 return count;
337}
338
339// The n-th non-empty slot in `lane`, or kNoSlot when fewer exist.
340constexpr std::size_t nth_non_empty(std::span<const Slot> lane, std::size_t n) noexcept {
341 for (std::size_t index = 0; index < lane.size(); ++index) {
342 if (lane[index].empty())
343 continue;
344 if (n == 0)
345 return index;
346 --n;
347 }
349}
350
351// The nearest non-empty slot after (or before) `from`, wrapping around the lane
352// and stepping over empty slots. `from` itself is only the answer when it is
353// the sole non-empty slot.
354constexpr std::size_t step_non_empty(std::span<const Slot> lane, std::size_t from,
355 bool forward) noexcept {
356 const std::size_t size = lane.size();
357 if (size == 0)
359 std::size_t index = from;
360 for (std::size_t taken = 0; taken < size; ++taken) {
361 index = forward ? (index + 1) % size : (index + size - 1) % size;
362 if (!lane[index].empty())
363 return index;
364 }
366}
367
368// Launching an empty slot would sound nothing, so it resolves to None instead.
369constexpr FollowResolution play(std::span<const Slot> lane, std::size_t index) noexcept {
370 if (index == FollowResolution::kNoSlot || index >= lane.size() || lane[index].empty())
371 return {};
372 return {FollowOutcome::Play, index, lane[index].id};
373}
374
375} // namespace follow_detail
376
382constexpr FollowResolution resolve_follow_action(std::span<const Slot> lane, std::size_t self_index,
383 FollowDraw draw) noexcept {
384 if (self_index >= lane.size())
385 return {};
386 const FollowActionSet& set = lane[self_index].follow;
387 if (!set.enabled())
388 return {};
389 const auto candidates = set.active();
390 const std::size_t choice = select_follow_action(set, draw);
391 if (choice >= candidates.size())
392 return {};
393 const FollowAction& action = candidates[choice];
394
395 switch (action.kind) {
397 return {};
399 return {FollowOutcome::Stop, self_index, lane[self_index].id};
401 return follow_detail::play(lane, self_index);
403 return follow_detail::play(lane, follow_detail::step_non_empty(lane, self_index, false));
405 return follow_detail::play(lane, follow_detail::step_non_empty(lane, self_index, true));
409 const std::size_t count = follow_detail::count_non_empty(lane);
410 if (count == 0)
411 return {};
412 return follow_detail::play(lane, follow_detail::nth_non_empty(lane, count - 1));
413 }
415 const std::size_t count = follow_detail::count_non_empty(lane);
416 if (count == 0)
417 return {};
418 // A second mix of the same draw keeps the candidate choice and the slot
419 // choice independent without a second draw index.
420 const std::uint64_t roll = follow_action_mix(draw.value());
421 return follow_detail::play(lane, follow_detail::nth_non_empty(lane, roll % count));
422 }
424 const bool self_counts = !lane[self_index].empty();
425 const std::size_t count = follow_detail::count_non_empty(lane);
426 const std::size_t others = count - (self_counts ? 1u : 0u);
427 if (others == 0)
428 // Nothing else to reach for: the acting slot plays again, matching
429 // the single-clip lane case.
430 return self_counts ? follow_detail::play(lane, self_index) : FollowResolution{};
431 const std::uint64_t roll = follow_action_mix(draw.value());
432 std::size_t pick = static_cast<std::size_t>(roll % others);
433 for (std::size_t index = 0; index < lane.size(); ++index) {
434 if (index == self_index || lane[index].empty())
435 continue;
436 if (pick == 0)
437 return follow_detail::play(lane, index);
438 --pick;
439 }
440 return {};
441 }
443 if (!action.target.valid())
444 return {};
445 for (std::size_t index = 0; index < lane.size(); ++index) {
446 if (lane[index].id == action.target)
447 return follow_detail::play(lane, index);
448 }
449 return {};
450 }
451 return {};
452}
453
455
456} // namespace pulp::timeline
Forward iterator that retains raw or persistent slot storage.
Definition clip_launch.hpp:166
std::forward_iterator_tag iterator_category
Definition clip_launch.hpp:172
bool operator==(const Iterator &) const noexcept=default
Compares iterator position and retained storage identity.
std::ptrdiff_t difference_type
Definition clip_launch.hpp:169
const Slot * pointer
Definition clip_launch.hpp:170
friend class SlotList
Definition clip_launch.hpp:190
const Slot & reference
Definition clip_launch.hpp:171
Slot value_type
Definition clip_launch.hpp:168
const Slot & operator*() const noexcept
Borrows the current slot; the iterator's retained storage keeps it alive.
Immutable authored slot order with persistent structural sharing.
Definition clip_launch.hpp:163
const Slot & operator[](std::size_t index) const noexcept
Borrows the slot at index; index must be less than size().
SlotList(std::initializer_list< Slot > slots)
Copies slots in authored order.
SlotList(std::vector< Slot > slots)
Takes ownership of slots in authored order.
const Slot & back() const noexcept
Borrows the last slot; the list must be nonempty.
Definition clip_launch.hpp:222
bool empty() const noexcept
Returns whether the list contains no slots.
Definition clip_launch.hpp:212
std::size_t size() const noexcept
Returns the number of authored slots.
Iterator begin() const noexcept
Returns the first iterator in authored order.
bool shares_storage_with(const SlotList &other) const noexcept
Reports whether both snapshots share their complete backing store.
friend class detail::SlotListAccess
Definition clip_launch.hpp:237
const Slot & front() const noexcept
Borrows the first slot; the list must be nonempty.
Definition clip_launch.hpp:218
SlotList()
Constructs an empty list.
const Slot * find(ItemId id) const noexcept
Finds a slot by identity, or returns null.
Iterator end() const noexcept
Returns the end sentinel.
constexpr LaunchQuantize launch_every_quarters(std::int64_t count) noexcept
Returns a phase-zero grid spanning count quarter notes.
Definition clip_launch.hpp:59
constexpr std::size_t select_follow_action(const FollowActionSet &set, FollowDraw draw) noexcept
Selects one active candidate by relative weight.
Definition clip_launch.hpp:295
constexpr LaunchQuantize launch_every_bars(std::int64_t count, timebase::MeterSignature meter) noexcept
Returns a phase-zero grid spanning count bars under meter.
Definition clip_launch.hpp:67
constexpr FollowResolution resolve_follow_action(std::span< const Slot > lane, std::size_t self_index, FollowDraw draw) noexcept
Resolves the successor of self_index under that slot's follow-action set.
Definition clip_launch.hpp:382
FollowOutcome
Runtime request produced by resolving an authored follow action.
Definition clip_launch.hpp:313
constexpr FollowActionSet follow_action(FollowActionKind kind, timebase::TickDuration grid, std::uint32_t repetitions=1) noexcept
Builds a one-candidate follow set with unit weight.
Definition clip_launch.hpp:130
constexpr std::uint64_t follow_action_mix(std::uint64_t value) noexcept
Applies the deterministic SplitMix64 finalizer used by follow draws.
Definition clip_launch.hpp:265
FollowActionKind
Action applied when a slot's follow timer elapses.
Definition clip_launch.hpp:75
constexpr LaunchQuantize launch_immediate() noexcept
Returns an immediate, phase-zero launch policy.
Definition clip_launch.hpp:52
@ None
Definition clip.hpp:176
@ Stop
Definition clip_launch.hpp:315
@ None
Definition clip_launch.hpp:314
@ Play
Definition clip_launch.hpp:316
@ Jump
Definition clip_launch.hpp:85
@ Next
Definition clip_launch.hpp:80
@ Stop
Definition clip_launch.hpp:77
@ Again
Definition clip_launch.hpp:78
@ Other
Definition clip_launch.hpp:84
@ None
Definition clip_launch.hpp:76
@ First
Definition clip_launch.hpp:81
@ Last
Definition clip_launch.hpp:82
@ Previous
Definition clip_launch.hpp:79
@ Any
Definition clip_launch.hpp:83
constexpr std::int64_t kTicksPerQuarter
Definition tick.hpp:54
Definition clip_launch.hpp:30
Definition clip_launch.hpp:330
constexpr std::size_t count_non_empty(std::span< const Slot > lane) noexcept
Definition clip_launch.hpp:332
constexpr std::size_t step_non_empty(std::span< const Slot > lane, std::size_t from, bool forward) noexcept
Definition clip_launch.hpp:354
constexpr FollowResolution play(std::span< const Slot > lane, std::size_t index) noexcept
Definition clip_launch.hpp:369
constexpr std::size_t nth_non_empty(std::span< const Slot > lane, std::size_t n) noexcept
Definition clip_launch.hpp:340
Definition program_compiler.hpp:18
STL namespace.
Definition compiled_meter_map.hpp:14
Definition tick.hpp:56
Definition tick.hpp:61
Bounded weighted follow behavior attached to a slot.
Definition clip_launch.hpp:105
constexpr bool enabled() const noexcept
Returns whether this set has candidates, a positive period, and repetitions.
Definition clip_launch.hpp:122
constexpr bool operator==(const FollowActionSet &) const =default
constexpr std::span< const FollowAction > active() const noexcept
Returns the populated prefix of choices.
Definition clip_launch.hpp:116
timebase::TickDuration grid
Definition clip_launch.hpp:111
std::uint32_t repetitions
Definition clip_launch.hpp:110
static constexpr std::size_t kMaxChoices
Definition clip_launch.hpp:106
std::uint8_t choice_count
Definition clip_launch.hpp:109
std::array< FollowAction, kMaxChoices > choices
Definition clip_launch.hpp:108
One weighted follow-action candidate.
Definition clip_launch.hpp:92
constexpr auto operator<=>(const FollowAction &) const =default
ItemId target
Definition clip_launch.hpp:94
FollowActionKind kind
Definition clip_launch.hpp:93
std::uint16_t weight
Definition clip_launch.hpp:95
Stable identity of one reproducible follow-action decision.
Definition clip_launch.hpp:279
constexpr std::uint64_t value() const noexcept
Returns the deterministic 64-bit draw for this decision identity.
Definition clip_launch.hpp:285
std::uint64_t seed
Definition clip_launch.hpp:280
std::uint64_t draw_index
Definition clip_launch.hpp:282
ItemId slot
Definition clip_launch.hpp:281
Deterministic outcome and optional target slot of follow resolution.
Definition clip_launch.hpp:320
std::size_t slot_index
Definition clip_launch.hpp:324
static constexpr std::size_t kNoSlot
Definition clip_launch.hpp:321
FollowOutcome outcome
Definition clip_launch.hpp:323
ItemId slot_id
Definition clip_launch.hpp:325
constexpr auto operator<=>(const FollowResolution &) const =default
Nonzero monotonic identity for a Timeline-owned object.
Definition item_id.hpp:19
Musical-boundary quantization for one launch.
Definition clip_launch.hpp:40
constexpr auto operator<=>(const LaunchQuantize &) const =default
constexpr bool immediate() const noexcept
Returns whether launch occurs without boundary quantization.
Definition clip_launch.hpp:45
timebase::TickDuration grid
Definition clip_launch.hpp:41
timebase::TickPosition phase
Definition clip_launch.hpp:42
Authored grouping of slots presented as one launchable column.
Definition clip_launch.hpp:247
SlotList slots
Definition clip_launch.hpp:250
ItemId id
Definition clip_launch.hpp:248
std::string name
Definition clip_launch.hpp:249
bool operator==(const Scene &) const =default
One authored launch slot.
Definition clip_launch.hpp:145
constexpr bool empty() const noexcept
Returns whether this slot references no launchable clip.
Definition clip_launch.hpp:152
LaunchQuantize launch_quantize
Definition clip_launch.hpp:148
FollowActionSet follow
Definition clip_launch.hpp:149
ItemId id
Definition clip_launch.hpp:146
ItemId clip_id
Definition clip_launch.hpp:147
constexpr bool operator==(const Slot &) const =default