OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
ossia/dataflow/token_request.hpp
1#pragma once
2#include <ossia/detail/flicks.hpp>
4#include <ossia/detail/small_vector.hpp>
5#include <ossia/editor/scenario/time_signature.hpp>
7
8#include <algorithm>
9#include <cassert>
10#include <cmath>
11#include <limits>
12#include <optional>
13#include <type_traits>
14
15#if defined(_LIBCPP_CONSTEXPR_SINCE_CXX23) || defined(_GLIBCXX23_CONSTEXPR)
16#define ossia_constexpr_msvc_workaround constexpr
17#else
18#define ossia_constexpr_msvc_workaround
19#endif
20namespace ossia
21{
25[[nodiscard]] constexpr int64_t musical_to_frame(
26 double position, double start, double end, int64_t frames) noexcept
27{
28 const auto abs = [](double x) { return x < 0. ? -x : x; };
29 const double duration = end - start;
30 const double r = (position - start) / duration;
31 // The positions are exact only to the few ulps of their magnitude the
32 // arithmetic that made them rounds off: a point that close below a sample
33 // boundary is on it, not one sample early.
34 const double tolerance = 16. * std::numeric_limits<double>::epsilon()
35 * std::max({abs(position), abs(start), abs(end)})
36 / abs(duration);
37 return constexpr_floor((r + tolerance) * double(frames));
38}
39
40using quarter_note = double;
41
45{
46 ossia::time_value date{};
47 int64_t index{};
48
56 double position{};
57
58 friend bool
59 operator==(const quantification_point&, const quantification_point&) noexcept
60 = default;
61};
62using quantification_points = ossia::small_vector<quantification_point, 8>;
63
64struct token_request
65{
66 constexpr token_request() noexcept = default;
67 constexpr token_request(const token_request&) noexcept = default;
68 constexpr token_request(token_request&&) noexcept = default;
69 constexpr token_request& operator=(const token_request&) noexcept = default;
70 constexpr token_request& operator=(token_request&&) noexcept = default;
71
72 constexpr token_request(
73 ossia::time_value prev_d, ossia::time_value d, ossia::time_value parent_duration,
74 ossia::time_value off, double s, time_signature sig, double tempo) noexcept
75 : prev_date{prev_d}
76 , date{d}
77 , parent_duration{parent_duration}
78 , offset{off}
79 , speed{s}
80 , tempo{tempo}
81 , signature{sig}
82 {
83 if(offset.impl < 0)
84 {
85 offset.impl = 0;
86 }
87 }
88
89 [[nodiscard]] constexpr token_request add_offset(ossia::time_value t) const noexcept
90 {
91 token_request other = *this;
92 other.prev_date += t;
93 other.date += t;
94 return other;
95 }
96
97 template <typename Exec, typename Transport>
98 constexpr void loop(
99 ossia::time_value start_offset, ossia::time_value loop_duration, Exec f,
100 Transport transport) const noexcept
101 {
102 if(loop_duration.impl <= 0)
103 {
104 token_request other = add_offset(start_offset);
105 f(other);
106 return;
107 }
108
109 ossia::token_request other = *this;
110 ossia::time_value orig_from = other.prev_date;
111 ossia::time_value tick_amount = other.date - other.prev_date;
112
113 // A tick that does not advance (speed 0) still runs the process once, at
114 // its position in the loop.
115 if(tick_amount.impl == 0)
116 {
117 other.prev_date = time_value{orig_from.impl % loop_duration.impl} + start_offset;
118 other.date = other.prev_date;
119 f(other);
120 return;
121 }
122
123 // The pieces of a looped tick must tile the parent's carried sample span
124 // the way the parent tiles the buffer. Cutting on the accumulated model
125 // amount with a single rounding keeps the cuts monotone, so consecutive
126 // pieces share their boundary sample by construction.
127 const int64_t total_amount
128 = tick_amount.impl >= 0 ? tick_amount.impl : -tick_amount.impl;
129 const bool has_span
130 = start_sample >= 0 && length_sample >= 0 && total_amount > 0;
131 int64_t consumed = 0;
132 const auto set_piece_span = [&](int64_t piece) constexpr {
133 if(has_span)
134 {
135 const int64_t s0 = start_sample
136 + (int64_t{length_sample} * consumed + total_amount / 2)
137 / total_amount;
138 const int64_t s1
139 = start_sample
140 + (int64_t{length_sample} * (consumed + piece) + total_amount / 2)
141 / total_amount;
142 other.start_sample = int32_t(s0);
143 other.length_sample = int32_t(s1 - s0);
144 }
145 consumed += piece;
146 };
147
148 if(tick_amount >= 0_tv)
149 {
150 // Forward playback
151 while(tick_amount > 0_tv)
152 {
153 const time_value cur_from{orig_from % loop_duration};
154 if(cur_from + tick_amount < loop_duration)
155 {
156 other.prev_date = cur_from + start_offset;
157 other.date = other.prev_date + tick_amount;
158 set_piece_span(tick_amount.impl);
159 f(other);
160 break;
161 }
162 else
163 {
164 auto this_tick = loop_duration - cur_from;
165
166 tick_amount -= this_tick;
167 orig_from += this_tick;
168 other.prev_date = cur_from + start_offset;
169 other.date = other.prev_date + this_tick;
170
171 set_piece_span(this_tick.impl);
172 f(other);
173
174 transport(start_offset);
175 other.offset += this_tick;
176 }
177 }
178 }
179 else
180 {
181 // Backward playback
182 const int64_t loop_dur = loop_duration.impl;
183 int64_t remaining = -tick_amount.impl;
184
185 while(remaining > 0)
186 {
187 int64_t cur_from = orig_from.impl % loop_dur;
188 if(cur_from < 0)
189 cur_from += loop_dur;
190
191 if(cur_from == 0)
192 {
193 transport(start_offset + loop_duration);
194 cur_from = loop_dur;
195 }
196
197 const int64_t this_tick = remaining < cur_from ? remaining : cur_from;
198
199 other.prev_date = time_value{cur_from} + start_offset;
200 other.date = other.prev_date - time_value{this_tick};
201
202 set_piece_span(this_tick);
203 f(other);
204
205 remaining -= this_tick;
206 orig_from -= time_value{this_tick};
207 other.offset += time_value{this_tick};
208 }
209 }
210 }
211
213 [[nodiscard]] constexpr time_value model_read_duration() const noexcept
214 {
215 return date - prev_date;
216 }
217
218 [[nodiscard]] static constexpr double abs_speed(double s) noexcept
219 {
220 return s < 0. ? -s : s;
221 }
222
226 [[nodiscard]] constexpr physical_time
227 start_date_to_physical(double ratio) const noexcept
228 // C++23: [[ expects: speed != 0. ]]
229 {
230 assert(speed != 0.);
231 return this->prev_date.impl * ratio / abs_speed(speed);
232 }
233
238 [[nodiscard]] constexpr physical_time
239 sample_at(ossia::time_value tick_position, double ratio) const noexcept
240 {
241 assert(speed != 0.);
242 return constexpr_floor(tick_position.impl * ratio / abs_speed(speed));
243 }
244
248 [[nodiscard]] constexpr physical_time physical_start(double ratio) const noexcept
249 // C++23: [[ expects: speed != 0. ]]
250 {
251 if(start_sample >= 0)
252 return start_sample;
253 return sample_at(this->offset, ratio);
254 }
255
259 [[nodiscard]] constexpr physical_time
260 physical_read_duration(double ratio) const noexcept
261 {
262 // A difference of one map over absolute model time, so consecutive ticks
263 // read consecutive samples with neither a gap nor an overlap.
264 const auto a = prev_date.impl < date.impl ? prev_date.impl : date.impl;
265 const auto b = prev_date.impl < date.impl ? date.impl : prev_date.impl;
266 return constexpr_floor(b * ratio) - constexpr_floor(a * ratio);
267 }
268
273 [[nodiscard]] constexpr physical_time
274 physical_write_duration(double ratio) const noexcept
275 // C++23: [[ expects: speed != 0. ]]
276 {
277 if(length_sample >= 0)
278 return length_sample;
279 return sample_at(this->offset + abs(date - prev_date), ratio)
280 - sample_at(this->offset, ratio);
281 }
282
284 [[nodiscard]] constexpr physical_time
285 safe_physical_write_duration(double ratio, int bufferSize) const noexcept
286 // C++23: [[ expects: speed != 0. ]]
287 {
288 return bufferSize - physical_start(ratio);
289 }
290
302 [[nodiscard]] constexpr physical_time
303 physical_position(double musical_position, double ratio) const noexcept
304 {
305 // A tick with no speed advances through no samples, so everything in it
306 // belongs to the first one. Reconstructing the span below would divide by
307 // that speed - a span the producer carried needs no such thing.
308 if(speed == 0. && length_sample < 0)
309 return 0;
310
311 const int64_t len = physical_write_duration(ratio);
312 if(len <= 0)
313 return 0;
314
315 const double musical_tick_duration = musical_end_position - musical_start_position;
316 if(musical_tick_duration == 0.)
317 return 0;
318
319 // Positive in both directions: rewinding, the distance to the position and
320 // the duration of the tick are both negative.
321 const int64_t s = musical_to_frame(
322 musical_position, musical_start_position, musical_end_position, len);
323 return s < 0 ? int64_t(0) : (s >= len ? len - 1 : s);
324 }
325
327 [[nodiscard]] constexpr bool in_range(ossia::time_value global_time) const noexcept
328 {
329 return global_time.impl >= prev_date.impl && global_time.impl < date.impl;
330 }
331
334 [[nodiscard]] constexpr physical_time
335 to_physical_time_in_tick(ossia::time_value global_time, double ratio) const noexcept
336 {
337 // How far into the tick this date sits, counted forwards in both directions.
338 const int64_t in_tick
339 = speed < 0. ? (prev_date - global_time).impl : (global_time - prev_date).impl;
340
341 // Place it inside the span this tick was actually handed, so an event and
342 // the audio it belongs to cannot end up on different samples. Reconstructing
343 // the position from the model dates can miss the span by one.
344 if(start_sample >= 0 && length_sample >= 0)
345 {
346 const int64_t dt = abs(date - prev_date).impl;
347 if(dt <= 0)
348 return start_sample;
349 int64_t s = start_sample
350 + constexpr_floor(double(in_tick) / double(dt) * length_sample);
351 if(s < start_sample)
352 s = start_sample;
353 else if(s > int64_t(start_sample) + length_sample)
354 s = int64_t(start_sample) + length_sample;
355 return s;
356 }
357
358 assert(speed != 0.);
359 return sample_at(this->offset + ossia::time_value{in_tick}, ratio);
360 }
361
364 [[nodiscard]] constexpr physical_time
365 to_physical_time_in_tick(int64_t global_time, double ratio) const noexcept
366 {
367 return to_physical_time_in_tick(ossia::time_value{global_time}, ratio);
368 }
369
372 [[nodiscard]] constexpr time_value
373 from_physical_time_in_tick(ossia::physical_time phys_time, double ratio) const noexcept
374 {
375 assert(speed != 0.);
376 const double in_tick = phys_time - physical_start(ratio);
377 return time_value{constexpr_floor(in_tick * (speed / ratio) + prev_date.impl)};
378 }
379
382 [[nodiscard]] constexpr double position() const noexcept
383 {
384 return parent_duration.impl > 0 ? date.impl / double(parent_duration.impl) : 0.;
385 }
386
388 [[nodiscard]] constexpr bool forward() const noexcept { return date > prev_date; }
389
391 [[nodiscard]] constexpr bool paused() const noexcept { return date == prev_date; }
392
394 [[nodiscard]] constexpr bool backward() const noexcept { return date < prev_date; }
395
396
404 template <typename F>
405 void for_each_bar_segment(double lo, double hi, F&& fn) const noexcept
406 {
407 const bool valid_sig = signature.upper > 0 && signature.lower > 0;
408 const double quarters_in_bar
409 = valid_sig ? 4. * signature.upper / signature.lower : 4.;
410 if(!(quarters_in_bar > 0.))
411 return;
412
413 constexpr double eps = 1e-9;
414 const bool rewinding = date < prev_date;
415 const double near_bar = rewinding ? musical_end_last_bar : musical_start_last_bar;
416 const double far_bar = rewinding ? musical_start_last_bar : musical_end_last_bar;
417
418 // Up to the reported far bar, on the grid the near bar defines.
419 double b = near_bar;
420 for(int i = 0; i < 1024 && b < far_bar - eps; i++)
421 {
422 const double next = (b + quarters_in_bar < far_bar) ? b + quarters_in_bar : far_bar;
423 if(next > lo + eps && b < hi + eps)
424 fn(b, next);
425 b += quarters_in_bar;
426 }
427
428 // From the far bar on, on the grid it defines.
429 b = (far_bar > near_bar) ? far_bar : near_bar;
430 for(int i = 0; i < 1024 && b < hi + eps; i++)
431 {
432 fn(b, b + quarters_in_bar);
433 b += quarters_in_bar;
434 }
435 }
436
444 [[nodiscard]] std::optional<quantification_point>
445 get_quantification_point(double rate) const noexcept
446 {
447 if(prev_date == date)
448 return std::nullopt;
449
450 // Quantized triggers are not interactive while rewinding.
451 if(backward())
452 return std::nullopt;
453
454 const auto pts = get_quantification_dates(rate);
455 if(pts.empty())
456 return std::nullopt;
457 return pts[0];
458 }
459
466 [[nodiscard]] std::optional<time_value>
467 get_quantification_date(double rate) const noexcept
468 {
469 if(const auto pt = get_quantification_point(rate))
470 return pt->date;
471 return std::nullopt;
472 }
473
481 [[nodiscard]] quantification_points
482 get_quantification_dates(double rate) const noexcept
483 {
484 quantification_points res;
485 for_each_quantification_date(
486 rate, [&](const quantification_point& p) { res.push_back(p); });
487 return res;
488 }
489
494 template <typename F>
495 void for_each_quantification_date(double rate, F&& f) const noexcept
496 {
497 if(prev_date == date)
498 return;
499
500 const double musical_tick_duration = musical_end_position - musical_start_position;
501 const bool rewinding = date < prev_date;
502
503 // A musical duration that disagrees with the direction of the tick did not
504 // come from it - subdividing it would place every point at prev_date.
505 if(rate <= 0. || musical_tick_duration == 0.
506 || (musical_tick_duration < 0.) != rewinding)
507 {
508 f(quantification_point{prev_date, 0, musical_start_position});
509 return;
510 }
511
512 // A point falling exactly on the end of the tick belongs to the next one,
513 // so the interval is [start; end[ - which is also what makes the first
514 // element agree with get_quantification_date().
515 constexpr double eps = 1e-9;
516 const time_value tick_duration = date - prev_date;
517 const bool valid_sig = signature.upper > 0 && signature.lower > 0;
518 const double quarters_in_bar = valid_sig ? 4. * signature.upper / signature.lower : 4.;
519
520 // A point is kept if it lands inside the tick; false means we walked past
521 // the end and can stop.
522 int count = 0;
523 const auto try_push = [&](double musical_position, int64_t index) {
524 // Scale the musical distance to this point by the tick, rather than
525 // scaling the tick by a normalised position. The two associate
526 // differently in floating point and the result is truncated to a whole
527 // flick, so the other order lands a flick short on dates that come out
528 // exact in this one.
529 const double scale = double(tick_duration.impl) / musical_tick_duration;
530 time_value d
531 = prev_date + (musical_position - musical_start_position) * scale;
532
533 // The tick owns [start; end[ in musical positions as well as in dates:
534 // a point sitting musically on the far end belongs to the next tick,
535 // even when truncating its date to a whole flick pulls it inside this
536 // one. Without this, the point fires here at the last flick AND in the
537 // next tick at its first one.
538 if(rewinding)
539 {
540 if(musical_position <= musical_end_position)
541 return false;
542 if(d > prev_date)
543 d = prev_date;
544 if(d <= date)
545 return false;
546 }
547 else
548 {
549 if(musical_position >= musical_end_position)
550 return false;
551 if(d < prev_date)
552 d = prev_date;
553 if(d >= date)
554 return false;
555 }
556
557 f(quantification_point{d, index, musical_position});
558 // A tick spanning this many points means the rate is nonsense: stop
559 // rather than run forever.
560 return ++count < 1024;
561 };
562
563 if(rate <= 1.)
564 {
565 // A bar or longer: the rate is a fraction of a bar, counted from the last
566 // signature change, and no bar line subdivides it.
567 const double unit = quarters_in_bar / rate;
568 if(!(unit > 0.))
569 return;
570
571 const double origin = musical_start_last_signature;
572 const double start = (musical_start_position - origin) / unit;
573 const double end = (musical_end_position - origin) / unit;
574 const int64_t first = rewinding ? int64_t(std::floor(start + eps))
575 : int64_t(std::ceil(start - eps));
576
577 for(int64_t k = first; rewinding ? (k > end + eps) : (k < end - eps);
578 k += rewinding ? -1 : 1)
579 {
580 if(!try_push(k * unit + origin, k))
581 break;
582 }
583 return;
584 }
585
586 // Shorter than a bar: a subdivision of the quarter note, counted from the
587 // bar it falls in. The grid restarts at every bar line, so a bar whose
588 // length is not a whole number of divisions (7/8 against a half-note grid)
589 // does not carry a stale phase into the next one, and the bar line itself
590 // is always a point.
591 const double unit = 4. / rate;
592 if(!(unit > 0.) || !(quarters_in_bar > 0.))
593 return;
594
595 const double lo = rewinding ? musical_end_position : musical_start_position;
596 const double hi = rewinding ? musical_start_position : musical_end_position;
597
598 const auto walk_segment = [&](double bar_line, double next_bar) {
599 const int divs = int(std::ceil((next_bar - bar_line) / unit)) + 1;
600 if(!rewinding)
601 {
602 for(int64_t k = 0; k <= divs; k++)
603 {
604 const double p = bar_line + k * unit;
605 if(p >= next_bar - eps || p > hi + eps)
606 return true;
607 if(p < lo)
608 continue;
609 if(!try_push(p, k))
610 return false;
611 }
612 }
613 else
614 {
615 for(int64_t k = divs; k >= 0; k--)
616 {
617 const double p = bar_line + k * unit;
618 if(p >= next_bar - eps || p > hi + eps)
619 continue;
620 if(p < lo)
621 return true;
622 if(!try_push(p, k))
623 return false;
624 }
625 }
626 return true;
627 };
628
629 if(!rewinding)
630 {
631 bool walking = true;
632 for_each_bar_segment(lo, hi, [&](double bar_line, double next_bar) {
633 if(walking)
634 walking = walk_segment(bar_line, next_bar);
635 });
636 return;
637 }
638
639 // Rewinding takes the segments in reverse: the points inside a segment
640 // are monotone in k, and so are the segments. A tick crosses more than 8
641 // bar lines only at absurd speeds.
642 ossia::small_vector<std::pair<double, double>, 8> segments;
643 for_each_bar_segment(lo, hi, [&](double bar_line, double next_bar) {
644 if(segments.size() < 1024)
645 segments.push_back({bar_line, next_bar});
646 });
647 for(auto it = segments.rbegin(); it != segments.rend(); ++it)
648 if(!walk_segment(it->first, it->second))
649 break;
650 }
651
657 [[nodiscard]] ossia_constexpr_msvc_workaround std::optional<physical_time>
658 get_physical_quantification_date(double rate, double modelToSamples) const noexcept
659 {
660 if(auto pt = get_quantification_point(rate))
661 return physical_start(modelToSamples)
662 + physical_position(pt->position, modelToSamples);
663 return {};
664 }
665
666 template <typename Tick, typename Tock>
667 constexpr void
668 metronome(double modelToSamplesRatio, Tick tick, Tock tock) const noexcept
669 {
670 const double musical_tick_duration = musical_end_position - musical_start_position;
671 const bool rewinding = backward();
672
673 // A musical duration that disagrees with the direction of the tick did not
674 // come from it, and interpolating in it would land outside the buffer.
675 if(musical_tick_duration == 0. || (musical_tick_duration < 0.) != rewinding)
676 return;
677
678 // A tick shorter than a sample covers no whole sample, but the grid point
679 // in it still belongs to one: the sample its span starts on, which is
680 // offset 0. Placement depends on the length, emission must not.
681 const int64_t samples_tick_duration = physical_write_duration(modelToSamplesRatio);
682 if(samples_tick_duration < 0)
683 return;
684
685 // The same map the quantification points use, so a click and a quantized
686 // event on one bar line land on one sample.
687 const auto sample_of = [&](double musical_position) {
688 return physical_position(musical_position, modelToSamplesRatio);
689 };
690
691 const double quarters_in_bar = 4. * signature.upper / signature.lower;
692 if(!(quarters_in_bar > 0.))
693 return;
694
695 // Walk every grid point the tick covers rather than only the last one: in
696 // 7/8 the third beat and the following bar line are half a quarter apart,
697 // so a tick of a moderate length steps over both.
698 const double lo = rewinding ? musical_end_position : musical_start_position;
699 const double hi = rewinding ? musical_start_position : musical_end_position;
700 const double bar0 = rewinding ? musical_end_last_bar : musical_start_last_bar;
701
702 // A grid point sitting exactly on a tick boundary belongs to the tick that
703 // starts on it, where it is sample 0, not to the one that ends on it, where
704 // it would be the last sample and so a sample early. Half-open at the end
705 // the tick is heading towards, in both directions.
706 const auto emit = [&](double p, bool is_bar) {
707 if(rewinding ? (p > hi || p <= lo) : (p < lo || p >= hi))
708 return;
709 if(is_bar)
710 tick(sample_of(p));
711 else
712 tock(sample_of(p));
713 };
714
715 // The same bar segments the quantification grid uses, so a click and a
716 // quantized event at the same bar line land on the same sample.
717 double seg_lo[64]{};
718 double seg_hi[64]{};
719 int n_seg = 0;
720 for_each_bar_segment(lo, hi, [&](double bar_line, double next_bar) {
721 if(n_seg < 64)
722 {
723 seg_lo[n_seg] = bar_line;
724 seg_hi[n_seg] = next_bar;
725 n_seg++;
726 }
727 });
728
729 const auto walk_segment = [&](double bar_line, double next_bar) {
730 if(!rewinding)
731 {
732 emit(bar_line, true);
733 for(double q = bar_line + 1.; q < next_bar - 1e-9; q += 1.)
734 emit(q, false);
735 }
736 else
737 {
738 double last = bar_line;
739 for(double q = bar_line + 1.; q < next_bar - 1e-9; q += 1.)
740 last = q;
741 for(double q = last; q > bar_line + 1e-9; q -= 1.)
742 emit(q, false);
743 emit(bar_line, true);
744 }
745 };
746
747 if(!rewinding)
748 for(int i = 0; i < n_seg; i++)
749 walk_segment(seg_lo[i], seg_hi[i]);
750 else
751 for(int i = n_seg - 1; i >= 0; i--)
752 walk_segment(seg_lo[i], seg_hi[i]);
753 }
754
755 [[nodiscard]] constexpr bool unexpected_bar_change() const noexcept
756 {
757 double bar_difference = musical_end_last_bar - musical_start_last_bar;
758 if(bar_difference != 0.)
759 {
760 // If the difference is divisble by the signature,
761 // then the bar change is expected.
762 // e.g. start = 4 -> end = 8 ; signature = 4/4 : good
763 // e.g. start = 4 -> end = 8 ; signature = 6/8 : bad
764 // e.g. start = 4 -> end = 7 ; signature = 6/8 : good
765
766 if(bar_difference < 0.)
767 bar_difference = -bar_difference;
768
769 double quarters_sig = 4. * double(signature.upper) / signature.lower;
770 double div = bar_difference / quarters_sig;
771 bool unexpected = div - int64_t(div) > 0.000001;
772 return unexpected;
773 }
774 return false;
775 }
776
780 [[nodiscard]] constexpr double tick_fraction_at(time_value t) const noexcept
781 {
782 const double total = double(date.impl - prev_date.impl);
783 if(total == 0.)
784 return 0.;
785 double f = double(t.impl - prev_date.impl) / total;
786 if(f < 0.)
787 f = 0.;
788 else if(f > 1.)
789 f = 1.;
790 return f;
791 }
792
797 [[nodiscard]] constexpr double musical_position_at(time_value t) const noexcept
798 {
799 return musical_start_position
800 + (musical_end_position - musical_start_position) * tick_fraction_at(t);
801 }
802
803 constexpr void set_end_time(time_value t) noexcept
804 // C++23: [[ expects: t <= this->date && t > this->prev_date ]]
805 {
806 const double f = tick_fraction_at(t);
807 if(length_sample > 0)
808 length_sample = int32_t(length_sample * f + 0.5);
809
810 musical_end_position = musical_position_at(t);
811 date = t;
812
813 // TODO what if musical_end_position is now before musical_end_last_bar
814 }
815
816 constexpr void set_start_time(time_value t) noexcept
817 // C++23: [[ expects: t <= this->date && t > this->prev_date ]]
818 {
819 if(length_sample > 0)
820 {
821 const auto skipped = int32_t(length_sample * tick_fraction_at(t) + 0.5);
822 start_sample += skipped;
823 length_sample -= skipped;
824 }
825
826 musical_start_position = musical_position_at(t);
827 prev_date = t;
828
829 // TODO what if musical_start_position is now after end_position /
830 // end_last_bar ?
831 }
832
833 ossia::time_value prev_date{}; // Sample we are at
834 ossia::time_value date{}; // Sample we are finishing at
835 ossia::time_value parent_duration{}; // Duration of the parent item of the
836 // one being ticked
837
850 ossia::time_value offset{};
851
852 double speed{1.};
853 double tempo{ossia::root_tempo};
854 time_signature signature{}; // Time signature at start
855
867 int32_t start_sample{-1};
868 int32_t length_sample{-1};
869
870 bool start_discontinuous{};
871 bool end_discontinuous{};
872
873 ossia::quarter_note musical_start_last_signature{}; // Position of the last bar
874 // signature change in quarter
875 // notes (at prev_date)
876 ossia::quarter_note musical_start_last_bar{}; // Position of the last bar start in
877 // quarter notes (at prev_date)
878 ossia::quarter_note musical_start_position{}; // Current position in quarter notes
879 ossia::quarter_note musical_end_last_bar{}; // Position of the last bar start in
880 // quarter notes (at date)
881 ossia::quarter_note musical_end_position{}; // Current position in quarter notes
882};
883
884// Copied per node per tick on the audio thread: it has to stay a POD that
885// memcpys, and it should not grow carelessly.
886static_assert(std::is_trivially_copyable_v<token_request>);
887static_assert(std::is_standard_layout_v<token_request>);
888static_assert(sizeof(token_request) == 104);
889
890inline bool operator==(const token_request& lhs, const token_request& rhs)
891{
892 return lhs.prev_date == rhs.prev_date && lhs.date == rhs.date
893 && lhs.parent_duration == rhs.parent_duration && lhs.offset == rhs.offset
894 && lhs.speed == rhs.speed && lhs.tempo == rhs.tempo
895 && lhs.signature == rhs.signature
896 && lhs.musical_start_last_bar == rhs.musical_start_last_bar
897 && lhs.musical_start_position == rhs.musical_start_position
898 && lhs.musical_end_last_bar == rhs.musical_end_last_bar
899 && lhs.musical_end_position == rhs.musical_end_position
900 && lhs.start_discontinuous == rhs.start_discontinuous
901 && lhs.end_discontinuous == rhs.end_discontinuous;
902}
903
904inline bool operator!=(const token_request& lhs, const token_request& rhs)
905{
906 return !(lhs == rhs);
907}
908
909// To be used only for simple examples
910struct simple_token_request
911{
912 time_value prev_date{};
913 time_value date{};
914 time_value parent_duration{};
915 time_value offset{};
916
917 operator token_request() const noexcept
918 {
919 return ossia::token_request{prev_date, date, parent_duration, offset, 1.0,
920 {4, 4}, 120.};
921 }
922
923 friend bool operator==(const token_request& lhs, const simple_token_request& self)
924 {
925 return lhs.prev_date == self.prev_date && lhs.date == self.date
926 && lhs.offset == self.offset;
927 }
928 friend bool operator==(const simple_token_request& self, const token_request& rhs)
929 {
930 return rhs == self;
931 }
932 friend bool operator!=(const token_request& lhs, const simple_token_request& self)
933 {
934 return !(lhs == self);
935 }
936 friend bool operator!=(const simple_token_request& self, const token_request& rhs)
937 {
938 return !(rhs == self);
939 }
940};
941}
Definition git_info.h:7
@ off
No motion gate: nearest-neighbour within the cost ranking.
Definition point_tracker.hpp:68
constexpr int64_t musical_to_frame(double position, double start, double end, int64_t frames) noexcept
The frame, counted from the start of a span of frames frames covering the musical positions [start; e...
Definition ossia/dataflow/token_request.hpp:25
One quantification point inside a tick: when it happens, where it happens musically,...
Definition ossia/dataflow/token_request.hpp:45
double position
Where the point sits in musical time, in quarters.
Definition ossia/dataflow/token_request.hpp:56
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30