Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
grid_projection.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <cmath>
8#include <cstddef>
9#include <cstdint>
10#include <limits>
11#include <span>
12
13namespace pulp::timebase {
14
15enum class GridAnchor {
18};
19
20// Stable affine session-clock anchor shared by every host-mapped range in a
21// continuous interval. `source_tick` is the normalized source coordinate at
22// absolute output `frame`; the positive slope is expressed in source ticks per
23// output frame. Establish a new anchor when that normalization epoch or slope
24// changes.
26 double source_tick = 0.0;
27 std::int64_t frame = 0;
28 double ticks_per_frame = 0.0;
29 constexpr auto operator<=>(const HostGridAnchor&) const = default;
30};
31
32// One already-resolved transport range. This mirrors the clock domains a
33// playback transport publishes without making timebase depend on playback:
34// document samples/ticks may wrap or seek, while monotonic ticks do neither.
54
55// Where a grid event lands when its true frame position falls BETWEEN two
56// samples. This is a genuine policy, not a correctness setting, and the two
57// answers differ measurably:
58//
59// Floor the event takes the sample whose interval contains it. Never
60// fires late; error is [0, 1) samples and always early, mean 0.5.
61// Nearest the event takes the closest sample. Error is <= 0.5 samples in
62// either direction, mean 0.25, unbiased.
63//
64// `Nearest` is strictly the more accurate placement against true event time;
65// `Floor` is the conventional slot assignment and the one Pulp has always used,
66// so it stays the default and nothing existing changes. A consumer whose own
67// clock rounds to nearest -- Forge's `tempo_clock_advance` uses `llround` --
68// selects `Nearest` so adopting this projection does not move its events.
69//
70// Measured over 15'838 on-grid events, projecting Forge's clock through this
71// code with `Floor` moved 27.8% of them one sample earlier; with `Nearest` the
72// divergence is 0.2%.
73enum class GridPlacement {
76};
77
84
93
94inline constexpr std::size_t kMaximumGridProjectionPoints = 65'536;
95
106
109 std::size_t count = 0;
110 std::size_t required = 0;
111
112 constexpr explicit operator bool() const noexcept {
114 }
115};
116
117namespace detail {
118
119constexpr bool checked_grid_add(std::int64_t lhs, std::int64_t rhs, std::int64_t& result) noexcept {
120 constexpr auto min = std::numeric_limits<std::int64_t>::min();
121 constexpr auto max = std::numeric_limits<std::int64_t>::max();
122 if ((rhs > 0 && lhs > max - rhs) || (rhs < 0 && lhs < min - rhs))
123 return false;
124 result = lhs + rhs;
125 return true;
126}
127
128constexpr bool checked_grid_subtract(std::int64_t lhs, std::int64_t rhs,
129 std::int64_t& result) noexcept {
130 constexpr auto min = std::numeric_limits<std::int64_t>::min();
131 constexpr auto max = std::numeric_limits<std::int64_t>::max();
132 if ((rhs > 0 && lhs < min + rhs) || (rhs < 0 && lhs > max + rhs))
133 return false;
134 result = lhs - rhs;
135 return true;
136}
137
138// Frames per tick-magnitude of forward error budget when deciding whether a
139// host-mapped frame delta is an integer that binary64 could not represent.
140//
141// `frame_delta` is (source_tick - anchor.source_tick) / ticks_per_frame, and
142// the subtraction is a cancellation between two tick values that are routinely
143// near 1e9. That is where the error comes from: the result keeps the absolute
144// error of the larger operands, not of the small difference, so a delta that
145// is exactly an integer arrives as that integer plus or minus several ulp of
146// the INPUTS. Measured on one host-mapped range: an exact 565416 arrived as
147// 565416.0000000024 while the same inputs in exact rational arithmetic give
148// 565415.9999999999 -- the same intended integer, landing on opposite sides.
149//
150// So the budget is a forward-error bound computed from the operands rather
151// than a fixed epsilon, which would be either too small for large ticks or too
152// loose for small ones. The multiplier is headroom over the ~2 ulp the three
153// operations (add, subtract, divide) can each contribute.
154inline constexpr double kGridFrameErrorUlps = 8.0;
155
156// Hard ceiling on that budget, in frames. The bound above is a faithful
157// forward-error estimate, but it grows with the operands and an estimate is not
158// a licence to snap: at ticks near 2^53 with one tick per frame it reaches 48
159// frames, which would round a genuinely early event forward -- the one thing
160// this must never do. Past this ceiling the delta carries more uncertainty than
161// an integer's worth of meaning, so the honest answer is to keep flooring
162// rather than guess which integer was intended. ~20 ns at 48 kHz: four orders
163// of magnitude above the ~1e-8 frames a real host clock produces, and five
164// below the 1.0 that separates a genuinely early event from an on-grid one.
165inline constexpr double kGridMaxSnapFrames = 1.0 / 1024.0;
166
167// The largest frame error the binary64 evaluation of `frame_delta` can carry
168// for these operands. Deliberately a function of the inputs: see above.
169inline double grid_frame_error_budget(double source_tick, double anchor_source_tick,
170 double ticks_per_frame, double frame_delta) noexcept {
171 const auto tick_magnitude = std::abs(source_tick) + std::abs(anchor_source_tick);
172 const auto scale = tick_magnitude / std::abs(ticks_per_frame) + std::abs(frame_delta);
173 const auto bound = kGridFrameErrorUlps * std::numeric_limits<double>::epsilon() * scale;
174 return bound < kGridMaxSnapFrames ? bound : kGridMaxSnapFrames;
175}
176
177// Floor `frame_delta`, first recovering an integer that representation error
178// moved off it in EITHER direction.
179//
180// A bare `std::floor` here is a correctness defect rather than a rounding
181// preference: an on-grid event whose delta lands a few ulp low floors to the
182// preceding frame and fires one sample early, and the bias is one-directional
183// because floor only ever truncates. Snapping to the nearest integer when the
184// delta is within the operands' own error budget removes the bias without
185// touching a genuinely fractional delta -- the budget is many orders of
186// magnitude below the 1.0 that separates a real early event from an on-grid
187// one, so a truly early event is never rounded forward.
188inline double grid_integral_frame_delta(double frame_delta, double budget,
189 GridPlacement placement = GridPlacement::Floor) noexcept {
190 const auto nearest = std::nearbyint(frame_delta);
191 // The epsilon recovery applies to BOTH placements. Under Nearest it is
192 // almost always redundant -- rounding to nearest already absorbs a delta a
193 // few ulp off an integer -- but not at a .5 boundary, where representation
194 // error decides which way the tie breaks. Recovering the integer first
195 // keeps that decision away from the error.
196 if (std::abs(frame_delta - nearest) <= budget)
197 return nearest;
198 // `std::round` is half-away-from-zero, matching `std::llround` in a consumer
199 // clock. `std::nearbyint` is half-to-EVEN under the default rounding mode, so
200 // it disagrees at exact .5 ties -- nearbyint(56.5) is 56, llround(56.5) is 57.
201 return placement == GridPlacement::Nearest ? std::round(frame_delta) : std::floor(frame_delta);
202}
203
204// Adds an already-integral binary64 delta without first narrowing it to int64.
205// An opposite-signed anchor can make a delta beyond the signed domain produce
206// a valid signed frame, so retain a uint64 magnitude through the cancellation.
207inline bool checked_grid_add_integral_delta(std::int64_t anchor, double integral_delta,
208 std::int64_t& result) noexcept {
209 constexpr auto sign_bit = std::uint64_t{1} << 63U;
210 constexpr auto signed_max =
211 static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max());
212 const auto unsigned_max_exclusive = std::ldexp(1.0, 64);
213 if (!std::isfinite(integral_delta))
214 return false;
215
216 const auto assign_negative = [&](std::uint64_t magnitude) noexcept {
217 if (magnitude > sign_bit)
218 return false;
219 result = magnitude == sign_bit ? std::numeric_limits<std::int64_t>::min()
220 : -static_cast<std::int64_t>(magnitude);
221 return true;
222 };
223 const auto anchor_magnitude = [&] {
224 return anchor < 0 ? static_cast<std::uint64_t>(-(anchor + 1)) + 1U
225 : static_cast<std::uint64_t>(anchor);
226 }();
227
228 if (integral_delta >= 0.0) {
229 if (!(integral_delta < unsigned_max_exclusive))
230 return false;
231 const auto magnitude = static_cast<std::uint64_t>(integral_delta);
232 if (anchor >= 0) {
233 if (magnitude > signed_max - anchor_magnitude)
234 return false;
235 result = anchor + static_cast<std::int64_t>(magnitude);
236 return true;
237 }
238 if (magnitude < anchor_magnitude)
239 return assign_negative(anchor_magnitude - magnitude);
240 const auto positive = magnitude - anchor_magnitude;
241 if (positive > signed_max)
242 return false;
243 result = static_cast<std::int64_t>(positive);
244 return true;
245 }
246
247 const auto magnitude_as_double = -integral_delta;
248 if (!(magnitude_as_double < unsigned_max_exclusive))
249 return false;
250 const auto magnitude = static_cast<std::uint64_t>(magnitude_as_double);
251 if (anchor <= 0) {
252 if (magnitude > sign_bit - anchor_magnitude)
253 return false;
254 return assign_negative(anchor_magnitude + magnitude);
255 }
256 if (magnitude <= anchor_magnitude) {
257 result = static_cast<std::int64_t>(anchor_magnitude - magnitude);
258 return true;
259 }
260 return assign_negative(magnitude - anchor_magnitude);
261}
262
263// Exact ceiling to a positive quantum. Avoid forming floor(value / quantum) *
264// quantum: for INT64_MIN that intermediate can lie below the signed domain even
265// when the requested ceiling is representable.
266constexpr bool ceil_grid(std::int64_t value, std::int64_t quantum, std::int64_t& result) noexcept {
267 if (quantum <= 0)
268 return false;
269 const auto remainder = value % quantum;
270 if (remainder == 0) {
271 result = value;
272 return true;
273 }
274 if (remainder < 0)
275 return checked_grid_subtract(value, remainder, result);
276 return checked_grid_add(value, quantum - remainder, result);
277}
278
280 if (range.timeline_tick_end < range.timeline_tick_start ||
281 range.monotonic_end < range.monotonic_start)
283 std::int64_t timeline_duration = 0;
284 std::int64_t monotonic_duration = 0;
285 if (!checked_grid_subtract(range.timeline_tick_end.value, range.timeline_tick_start.value,
286 timeline_duration) ||
287 !checked_grid_subtract(range.monotonic_end.position.value,
288 range.monotonic_start.position.value, monotonic_duration))
290 if (timeline_duration != monotonic_duration)
292 std::int64_t sample_end = 0;
293 if (!checked_grid_add(range.timeline_sample_start.value, range.frame_count, sample_end))
295 std::uint64_t frame_end = 0;
296 frame_end = static_cast<std::uint64_t>(range.frame_offset) + range.frame_count;
297 const auto frame_domain_end =
298 static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max()) + 1U;
299 if (frame_end > frame_domain_end)
301 if (range.host_beat_mapping) {
302 const auto tick_start = range.has_precise_host_ticks
303 ? range.host_tick_start
304 : static_cast<double>(range.timeline_tick_start.value);
305 const auto tick_end = range.has_precise_host_ticks
306 ? range.host_tick_end
307 : static_cast<double>(range.timeline_tick_end.value);
308 if (!std::isfinite(tick_start) || !std::isfinite(tick_end) ||
309 (range.frame_count != 0 && !(tick_start < tick_end)))
311 std::int64_t absolute_frame_end = 0;
312 if (!range.has_host_anchor || !std::isfinite(range.host_anchor.source_tick) ||
313 !std::isfinite(range.host_anchor.ticks_per_frame) ||
314 !(range.host_anchor.ticks_per_frame > 0.0) ||
315 !std::isfinite(range.document_to_source_tick_offset) ||
316 !checked_grid_add(range.absolute_frame_start, range.frame_count, absolute_frame_end))
318 }
320}
321
322inline bool
324 std::uint32_t& output_offset,
325 GridPlacement placement = GridPlacement::Floor) noexcept {
326 const auto tick_start = range.has_precise_host_ticks
327 ? range.host_tick_start
328 : static_cast<double>(range.timeline_tick_start.value);
329 const auto tick_end = range.has_precise_host_ticks
330 ? range.host_tick_end
331 : static_cast<double>(range.timeline_tick_end.value);
332 if (!range.host_beat_mapping || range.frame_count == 0 || !(tick_start < tick_end))
333 return false;
334 // The host clock contract is binary64. Keep every intermediate binary64 as
335 // well: `long double` is binary64 on Apple ARM but extended precision on
336 // x86_64, which can move a grid event by one frame at a floor boundary.
337 const auto document = static_cast<double>(document_tick.value);
338 if (document < tick_start || !(document < tick_end))
339 return false;
340 const auto source_tick = document + range.document_to_source_tick_offset;
341 const auto frame_delta =
342 (source_tick - range.host_anchor.source_tick) / range.host_anchor.ticks_per_frame;
343 const auto integral_delta = grid_integral_frame_delta(
344 frame_delta,
345 grid_frame_error_budget(source_tick, range.host_anchor.source_tick,
346 range.host_anchor.ticks_per_frame, frame_delta),
347 placement);
348 std::int64_t absolute_frame = 0;
349 if (!checked_grid_add_integral_delta(range.host_anchor.frame, integral_delta, absolute_frame))
350 return false;
351 std::int64_t absolute_frame_end = 0;
352 if (!checked_grid_add(range.absolute_frame_start, range.frame_count, absolute_frame_end))
353 return false;
354 // A loop split rounds its first range up to the next output frame. A source
355 // tick just after the wrap therefore belongs to the second range but may
356 // floor to the preceding frame on the stable clock; clamp it to the first
357 // frame that actually represents the post-wrap document interval.
358 if (absolute_frame < range.absolute_frame_start)
359 absolute_frame = range.absolute_frame_start;
360 if (absolute_frame >= absolute_frame_end)
361 absolute_frame = absolute_frame_end - 1;
362 const auto local = static_cast<std::uint64_t>(absolute_frame) -
363 static_cast<std::uint64_t>(range.absolute_frame_start);
364 const auto total = static_cast<std::uint64_t>(range.frame_offset) + local;
365 if (total > std::numeric_limits<std::uint32_t>::max())
366 return false;
367 output_offset = static_cast<std::uint32_t>(total);
368 return true;
369}
370
371inline std::uint64_t grid_candidate_count(std::int64_t start, std::int64_t end,
372 std::int64_t quantum, bool inclusive_end) noexcept {
373 if (inclusive_end ? start > end : start >= end)
374 return 0;
375 const auto last = inclusive_end ? end : end - 1;
376 std::int64_t first = 0;
377 if (!ceil_grid(start, quantum, first) || first > last)
378 return 0;
379 const auto distance = static_cast<std::uint64_t>(last) - static_cast<std::uint64_t>(first);
380 return distance / static_cast<std::uint64_t>(quantum) + 1U;
381}
382
384 const GridProjectionRange& range,
385 TickDuration grid, GridAnchor anchor,
386 std::uint64_t& candidates) noexcept {
387 candidates = 0;
388 const auto validation = validate_range(range);
389 if (validation != GridProjectionError::None)
390 return validation;
391 if (range.frame_count == 0)
393 // The rounded tick end is only a search bound. Document-clock membership
394 // is decided by the half-open sample interval below: on a sparse tick map a
395 // one-frame range may have equal rounded endpoints while still owning the
396 // grid tick at its first sample.
397 const auto inclusive_end = true;
398 const auto grid_count = grid_candidate_count(
399 range.timeline_tick_start.value, range.timeline_tick_end.value, grid.value, inclusive_end);
400 if (grid_count > kMaximumGridProjectionPoints)
402 if (anchor == GridAnchor::Timeline) {
403 candidates = grid_count;
405 }
406
407 const auto last_tick =
408 inclusive_end ? range.timeline_tick_end.value : range.timeline_tick_end.value - 1;
409 const auto first_bar = meter.tick_to_bar(range.timeline_tick_start).bar.value;
410 const auto last_bar = meter.tick_to_bar({last_tick}).bar.value;
411 if (last_bar < first_bar)
413 const auto bar_count =
414 static_cast<std::uint64_t>(last_bar) - static_cast<std::uint64_t>(first_bar) + 1U;
415 // Sum ceil(bar_length / grid) is bounded by the global grid count plus
416 // one reset opportunity per bar. Reject conservatively before walking bars.
417 if (bar_count > kMaximumGridProjectionPoints ||
418 grid_count > kMaximumGridProjectionPoints - bar_count)
420 candidates = grid_count + bar_count;
422}
423
424template <typename Emit>
426 const GridProjectionRange& range, TickDuration grid,
427 GridAnchor anchor, GridPlacement placement,
428 Emit&& emit) noexcept {
429 const auto validation = validate_range(range);
430 if (validation != GridProjectionError::None)
431 return validation;
432 if (range.frame_count == 0)
434
435 std::int64_t sample_end = 0;
436 (void)checked_grid_add(range.timeline_sample_start.value, range.frame_count, sample_end);
437
438 auto emit_timeline = [&](std::int64_t timeline_tick) noexcept {
439 std::uint32_t output_frame = 0;
440 if (range.host_beat_mapping) {
441 if (!host_mapped_grid_output_offset(range, {timeline_tick}, output_frame, placement))
443 } else {
444 if (timeline_tick < range.timeline_tick_start.value ||
445 timeline_tick > range.timeline_tick_end.value)
447 const auto sample = tempo.ticks_to_samples({timeline_tick});
448 if (sample.value < range.timeline_sample_start.value || sample.value >= sample_end)
450 const auto local_frame =
451 static_cast<std::uint64_t>(sample.value - range.timeline_sample_start.value);
452 const auto projected_frame =
453 static_cast<std::uint64_t>(range.frame_offset) + local_frame;
454 if (projected_frame > std::numeric_limits<std::uint32_t>::max())
456 output_frame = static_cast<std::uint32_t>(projected_frame);
457 }
458 std::int64_t local_tick = 0;
459 std::int64_t transport_tick = 0;
460 if (!checked_grid_subtract(timeline_tick, range.timeline_tick_start.value, local_tick) ||
461 !checked_grid_add(range.monotonic_start.position.value, local_tick, transport_tick))
463 return emit(GridProjectionPoint{output_frame,
464 {timeline_tick},
465 {{transport_tick}},
466 meter.tick_to_bar({timeline_tick}),
467 range.loop_pass_index});
468 };
469
470 if (anchor == GridAnchor::Timeline) {
471 std::int64_t candidate = 0;
472 if (!ceil_grid(range.timeline_tick_start.value, grid.value, candidate))
474 while (candidate <= range.timeline_tick_end.value) {
475 const auto error = emit_timeline(candidate);
476 if (error != GridProjectionError::None)
477 return error;
478 if (!checked_grid_add(candidate, grid.value, candidate))
479 break;
480 }
482 }
483
484 auto bar = meter.tick_to_bar(range.timeline_tick_start).bar;
485 while (true) {
486 const auto bar_start = meter.bar_to_tick(bar);
487 if (bar_start > range.timeline_tick_end)
488 break;
489 if (bar.value == std::numeric_limits<std::int64_t>::max())
491 const auto next_bar = meter.bar_to_tick({bar.value + 1});
492 if (next_bar <= bar_start)
494 std::int64_t local_begin = 0;
495 if (range.timeline_tick_start > bar_start &&
496 !checked_grid_subtract(range.timeline_tick_start.value, bar_start.value, local_begin))
498 std::int64_t local = 0;
499 if (!ceil_grid(local_begin, grid.value, local))
501 std::int64_t bar_duration = 0;
502 if (!checked_grid_subtract(next_bar.value, bar_start.value, bar_duration))
504 while (local < bar_duration) {
505 std::int64_t candidate = 0;
506 if (!checked_grid_add(bar_start.value, local, candidate))
508 if (candidate > range.timeline_tick_end.value)
509 break;
510 const auto error = emit_timeline(candidate);
511 if (error != GridProjectionError::None)
512 return error;
513 if (!checked_grid_add(local, grid.value, local))
515 }
516 bar.value += 1;
517 }
519}
520
521template <typename Emit>
523 std::span<const GridProjectionRange> ranges, TickDuration grid,
524 GridAnchor anchor, GridPlacement placement,
525 Emit&& emit) noexcept {
526 std::uint64_t previous_end = 0;
527 bool have_previous = false;
528 for (const auto& range : ranges) {
529 if (have_previous && range.frame_offset < previous_end)
531 const auto error = enumerate_range(tempo, meter, range, grid, anchor, placement, emit);
532 if (error != GridProjectionError::None)
533 return error;
534 previous_end = static_cast<std::uint64_t>(range.frame_offset) + range.frame_count;
535 have_previous = true;
536 }
538}
539
540} // namespace detail
541
542// Projects exact document grid points through transport-owned ranges. Loop
543// passes intentionally reuse the same document sample interval; seeks may pair
544// any timeline anchor with an independent monotonic anchor. With no mutable
545// phase, splitting a range at a callback boundary cannot change coordinates.
546// On capacity failure output is untouched.
548 const CompiledMeterMap& meter,
549 const GridProjectionRequest& request,
550 std::span<const GridProjectionRange> ranges,
551 std::span<GridProjectionPoint> output) noexcept {
552 const auto grid_result = division_ticks(request.division);
553 if (!grid_result)
555 if (request.anchor != GridAnchor::Timeline && request.anchor != GridAnchor::Bar)
557 if (!request.playing)
558 return {GridProjectionError::None, 0, 0};
559
560 if (ranges.size() > kMaximumGridProjectionPoints)
562 std::uint64_t candidate_total = 0;
563 for (const auto& range : ranges) {
564 std::uint64_t candidates = 0;
565 const auto bound = detail::preflight_candidate_bound(meter, range, grid_result.value(),
566 request.anchor, candidates);
567 if (bound != GridProjectionError::None)
568 return {bound, 0, 0};
569 if (candidates > kMaximumGridProjectionPoints - candidate_total)
571 candidate_total += candidates;
572 }
573
574 std::size_t required = 0;
575 const auto count_error =
576 detail::enumerate_ranges(tempo, meter, ranges, grid_result.value(), request.anchor,
577 request.placement, [&](const GridProjectionPoint&) noexcept {
578 if (required == kMaximumGridProjectionPoints)
579 return GridProjectionError::ProjectionLimitExceeded;
580 ++required;
581 return GridProjectionError::None;
582 });
583 if (count_error != GridProjectionError::None)
584 return {count_error, 0, required};
585 if (required > output.size())
586 return {GridProjectionError::OutputTooSmall, 0, required};
587
588 std::size_t count = 0;
589 const auto write_error =
590 detail::enumerate_ranges(tempo, meter, ranges, grid_result.value(), request.anchor,
591 request.placement, [&](const GridProjectionPoint& point) noexcept {
592 output[count++] = point;
593 return GridProjectionError::None;
594 });
595 if (write_error != GridProjectionError::None)
596 return {write_error, 0, required};
597 return {GridProjectionError::None, count, required};
598}
599
600} // namespace pulp::timebase
Definition compiled_meter_map.hpp:64
Definition compiled_tempo_map.hpp:91
constexpr double kGridMaxSnapFrames
Definition grid_projection.hpp:165
GridProjectionError preflight_candidate_bound(const CompiledMeterMap &meter, const GridProjectionRange &range, TickDuration grid, GridAnchor anchor, std::uint64_t &candidates) noexcept
Definition grid_projection.hpp:383
constexpr double kGridFrameErrorUlps
Definition grid_projection.hpp:154
constexpr bool checked_grid_add(std::int64_t lhs, std::int64_t rhs, std::int64_t &result) noexcept
Definition grid_projection.hpp:119
constexpr bool ceil_grid(std::int64_t value, std::int64_t quantum, std::int64_t &result) noexcept
Definition grid_projection.hpp:266
double grid_frame_error_budget(double source_tick, double anchor_source_tick, double ticks_per_frame, double frame_delta) noexcept
Definition grid_projection.hpp:169
GridProjectionError enumerate_ranges(const CompiledTempoMap &tempo, const CompiledMeterMap &meter, std::span< const GridProjectionRange > ranges, TickDuration grid, GridAnchor anchor, GridPlacement placement, Emit &&emit) noexcept
Definition grid_projection.hpp:522
bool checked_grid_add_integral_delta(std::int64_t anchor, double integral_delta, std::int64_t &result) noexcept
Definition grid_projection.hpp:207
constexpr bool checked_grid_subtract(std::int64_t lhs, std::int64_t rhs, std::int64_t &result) noexcept
Definition grid_projection.hpp:128
std::uint64_t grid_candidate_count(std::int64_t start, std::int64_t end, std::int64_t quantum, bool inclusive_end) noexcept
Definition grid_projection.hpp:371
GridProjectionError enumerate_range(const CompiledTempoMap &tempo, const CompiledMeterMap &meter, const GridProjectionRange &range, TickDuration grid, GridAnchor anchor, GridPlacement placement, Emit &&emit) noexcept
Definition grid_projection.hpp:425
double grid_integral_frame_delta(double frame_delta, double budget, GridPlacement placement=GridPlacement::Floor) noexcept
Definition grid_projection.hpp:188
GridProjectionError validate_range(const GridProjectionRange &range) noexcept
Definition grid_projection.hpp:279
bool host_mapped_grid_output_offset(const GridProjectionRange &range, TickPosition document_tick, std::uint32_t &output_offset, GridPlacement placement=GridPlacement::Floor) noexcept
Definition grid_projection.hpp:323
Definition beat_division.hpp:11
BeatDivision
Definition beat_division.hpp:15
@ Quarter
Definition beat_division.hpp:22
GridProjectionResult project_grid(const CompiledTempoMap &tempo, const CompiledMeterMap &meter, const GridProjectionRequest &request, std::span< const GridProjectionRange > ranges, std::span< GridProjectionPoint > output) noexcept
Definition grid_projection.hpp:547
GridAnchor
Definition grid_projection.hpp:15
@ Timeline
Definition grid_projection.hpp:16
@ Bar
Definition grid_projection.hpp:17
runtime::Result< TickDuration, BeatDivisionError > division_ticks(BeatDivision division) noexcept
Definition beat_division.hpp:88
GridPlacement
Definition grid_projection.hpp:73
@ Nearest
Definition grid_projection.hpp:75
@ Floor
Definition grid_projection.hpp:74
@ InvalidDivision
Definition beat_division.hpp:47
@ SampleRangeExceeded
Definition compiled_tempo_map.hpp:39
constexpr std::size_t kMaximumGridProjectionPoints
Definition grid_projection.hpp:94
GridProjectionError
Definition grid_projection.hpp:96
@ InvalidDivision
Definition grid_projection.hpp:98
@ None
Definition grid_projection.hpp:97
@ SampleRangeExceeded
Definition grid_projection.hpp:101
@ InvalidAnchor
Definition grid_projection.hpp:99
@ ProjectionLimitExceeded
Definition grid_projection.hpp:103
@ InvalidRange
Definition grid_projection.hpp:100
@ TickRangeExceeded
Definition grid_projection.hpp:102
@ OutputTooSmall
Definition grid_projection.hpp:104
Definition compiled_meter_map.hpp:31
Definition grid_projection.hpp:85
std::uint32_t frame_offset
Definition grid_projection.hpp:86
BarTickPosition bar_tick
Definition grid_projection.hpp:89
MonotonicBeat transport_tick
Definition grid_projection.hpp:88
constexpr auto operator<=>(const GridProjectionPoint &) const =default
std::uint64_t loop_pass_index
Definition grid_projection.hpp:90
TickPosition timeline_tick
Definition grid_projection.hpp:87
Definition grid_projection.hpp:35
bool has_precise_host_ticks
Definition grid_projection.hpp:47
TickPosition timeline_tick_start
Definition grid_projection.hpp:39
MonotonicBeat monotonic_end
Definition grid_projection.hpp:42
bool host_beat_mapping
Definition grid_projection.hpp:44
double host_tick_end
Definition grid_projection.hpp:46
constexpr auto operator<=>(const GridProjectionRange &) const =default
MonotonicBeat monotonic_start
Definition grid_projection.hpp:41
double host_tick_start
Definition grid_projection.hpp:45
std::uint32_t frame_offset
Definition grid_projection.hpp:36
std::uint32_t frame_count
Definition grid_projection.hpp:37
TickPosition timeline_tick_end
Definition grid_projection.hpp:40
HostGridAnchor host_anchor
Definition grid_projection.hpp:48
bool has_host_anchor
Definition grid_projection.hpp:51
double document_to_source_tick_offset
Definition grid_projection.hpp:50
SamplePosition timeline_sample_start
Definition grid_projection.hpp:38
std::int64_t absolute_frame_start
Definition grid_projection.hpp:49
std::uint64_t loop_pass_index
Definition grid_projection.hpp:43
Definition grid_projection.hpp:78
bool playing
Definition grid_projection.hpp:81
BeatDivision division
Definition grid_projection.hpp:79
GridAnchor anchor
Definition grid_projection.hpp:80
GridPlacement placement
Definition grid_projection.hpp:82
Definition grid_projection.hpp:107
std::size_t required
Definition grid_projection.hpp:110
std::size_t count
Definition grid_projection.hpp:109
GridProjectionError error
Definition grid_projection.hpp:108
Definition grid_projection.hpp:25
double ticks_per_frame
Definition grid_projection.hpp:28
constexpr auto operator<=>(const HostGridAnchor &) const =default
std::int64_t frame
Definition grid_projection.hpp:27
double source_tick
Definition grid_projection.hpp:26
Definition tick.hpp:71
Definition rational_time.hpp:9
Definition tick.hpp:56
Definition tick.hpp:61