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 <cassert>
9#include <cmath>
10#include <optional>
11#include <type_traits>
12
13#if defined(_LIBCPP_CONSTEXPR_SINCE_CXX23) || defined(_GLIBCXX23_CONSTEXPR)
14#define ossia_constexpr_msvc_workaround constexpr
15#else
16#define ossia_constexpr_msvc_workaround
17#endif
18namespace ossia
19{
20using quarter_note = double;
21
25{
26 ossia::time_value date{};
27 int64_t index{};
28
36 double position{};
37
38 friend bool
39 operator==(const quantification_point&, const quantification_point&) noexcept
40 = default;
41};
42using quantification_points = ossia::small_vector<quantification_point, 8>;
43
44struct token_request
45{
46 constexpr token_request() noexcept = default;
47 constexpr token_request(const token_request&) noexcept = default;
48 constexpr token_request(token_request&&) noexcept = default;
49 constexpr token_request& operator=(const token_request&) noexcept = default;
50 constexpr token_request& operator=(token_request&&) noexcept = default;
51
52 constexpr token_request(
53 ossia::time_value prev_d, ossia::time_value d, ossia::time_value parent_duration,
54 ossia::time_value off, double s, time_signature sig, double tempo) noexcept
55 : prev_date{prev_d}
56 , date{d}
57 , parent_duration{parent_duration}
58 , offset{off}
59 , speed{s}
60 , tempo{tempo}
61 , signature{sig}
62 {
63 if(offset.impl < 0)
64 {
65 offset.impl = 0;
66 }
67 }
68
69 [[nodiscard]] constexpr token_request add_offset(ossia::time_value t) const noexcept
70 {
71 token_request other = *this;
72 other.prev_date += t;
73 other.date += t;
74 return other;
75 }
76
77 template <typename Exec, typename Transport>
78 constexpr void loop(
79 ossia::time_value start_offset, ossia::time_value loop_duration, Exec f,
80 Transport transport) const noexcept
81 {
82 if(loop_duration.impl <= 0)
83 {
84 token_request other = add_offset(start_offset);
85 f(other);
86 return;
87 }
88
89 ossia::token_request other = *this;
90 ossia::time_value orig_from = other.prev_date;
91 ossia::time_value tick_amount = other.date - other.prev_date;
92
93 // The pieces of a looped tick must tile the parent's carried sample span
94 // the way the parent tiles the buffer. Cutting on the accumulated model
95 // amount with a single rounding keeps the cuts monotone, so consecutive
96 // pieces share their boundary sample by construction.
97 const int64_t total_amount
98 = tick_amount.impl >= 0 ? tick_amount.impl : -tick_amount.impl;
99 const bool has_span
100 = start_sample >= 0 && length_sample >= 0 && total_amount > 0;
101 int64_t consumed = 0;
102 const auto set_piece_span = [&](int64_t piece) constexpr {
103 if(has_span)
104 {
105 const int64_t s0 = start_sample
106 + (int64_t{length_sample} * consumed + total_amount / 2)
107 / total_amount;
108 const int64_t s1
109 = start_sample
110 + (int64_t{length_sample} * (consumed + piece) + total_amount / 2)
111 / total_amount;
112 other.start_sample = int32_t(s0);
113 other.length_sample = int32_t(s1 - s0);
114 }
115 consumed += piece;
116 };
117
118 if(tick_amount >= 0_tv)
119 {
120 // Forward playback
121 while(tick_amount > 0_tv)
122 {
123 const time_value cur_from{orig_from % loop_duration};
124 if(cur_from + tick_amount < loop_duration)
125 {
126 other.prev_date = cur_from + start_offset;
127 other.date = other.prev_date + tick_amount;
128 set_piece_span(tick_amount.impl);
129 f(other);
130 break;
131 }
132 else
133 {
134 auto this_tick = loop_duration - cur_from;
135
136 tick_amount -= this_tick;
137 orig_from += this_tick;
138 other.prev_date = cur_from + start_offset;
139 other.date = other.prev_date + this_tick;
140
141 set_piece_span(this_tick.impl);
142 f(other);
143
144 transport(start_offset);
145 other.offset += this_tick;
146 }
147 }
148 }
149 else
150 {
151 // Backward playback
152 const int64_t loop_dur = loop_duration.impl;
153 int64_t remaining = -tick_amount.impl;
154
155 while(remaining > 0)
156 {
157 int64_t cur_from = orig_from.impl % loop_dur;
158 if(cur_from < 0)
159 cur_from += loop_dur;
160
161 if(cur_from == 0)
162 {
163 transport(start_offset + loop_duration);
164 cur_from = loop_dur;
165 }
166
167 const int64_t this_tick = remaining < cur_from ? remaining : cur_from;
168
169 other.prev_date = time_value{cur_from} + start_offset;
170 other.date = other.prev_date - time_value{this_tick};
171
172 set_piece_span(this_tick);
173 f(other);
174
175 remaining -= this_tick;
176 orig_from -= time_value{this_tick};
177 other.offset += time_value{this_tick};
178 }
179 }
180 }
181
183 [[nodiscard]] constexpr time_value model_read_duration() const noexcept
184 {
185 return date - prev_date;
186 }
187
188 [[nodiscard]] static constexpr double abs_speed(double s) noexcept
189 {
190 return s < 0. ? -s : s;
191 }
192
196 [[nodiscard]] constexpr physical_time
197 start_date_to_physical(double ratio) const noexcept
198 // C++23: [[ expects: speed != 0. ]]
199 {
200 assert(speed != 0.);
201 return this->prev_date.impl * ratio / abs_speed(speed);
202 }
203
208 [[nodiscard]] constexpr physical_time
209 sample_at(ossia::time_value tick_position, double ratio) const noexcept
210 {
211 assert(speed != 0.);
212 return constexpr_floor(tick_position.impl * ratio / abs_speed(speed));
213 }
214
218 [[nodiscard]] constexpr physical_time physical_start(double ratio) const noexcept
219 // C++23: [[ expects: speed != 0. ]]
220 {
221 if(start_sample >= 0)
222 return start_sample;
223 return sample_at(this->offset, ratio);
224 }
225
229 [[nodiscard]] constexpr physical_time
230 physical_read_duration(double ratio) const noexcept
231 {
232 // A difference of one map over absolute model time, so consecutive ticks
233 // read consecutive samples with neither a gap nor an overlap.
234 const auto a = prev_date.impl < date.impl ? prev_date.impl : date.impl;
235 const auto b = prev_date.impl < date.impl ? date.impl : prev_date.impl;
236 return constexpr_floor(b * ratio) - constexpr_floor(a * ratio);
237 }
238
243 [[nodiscard]] constexpr physical_time
244 physical_write_duration(double ratio) const noexcept
245 // C++23: [[ expects: speed != 0. ]]
246 {
247 if(length_sample >= 0)
248 return length_sample;
249 return sample_at(this->offset + abs(date - prev_date), ratio)
250 - sample_at(this->offset, ratio);
251 }
252
254 [[nodiscard]] constexpr physical_time
255 safe_physical_write_duration(double ratio, int bufferSize) const noexcept
256 // C++23: [[ expects: speed != 0. ]]
257 {
258 return bufferSize - physical_start(ratio);
259 }
260
272 [[nodiscard]] constexpr physical_time
273 physical_position(double musical_position, double ratio) const noexcept
274 {
275 // A tick with no speed advances through no samples, so everything in it
276 // belongs to the first one. Reconstructing the span below would divide by
277 // that speed - a span the producer carried needs no such thing.
278 if(speed == 0. && length_sample < 0)
279 return 0;
280
281 const int64_t len = physical_write_duration(ratio);
282 if(len <= 0)
283 return 0;
284
285 const double musical_tick_duration = musical_end_position - musical_start_position;
286 if(musical_tick_duration == 0.)
287 return 0;
288
289 // Positive in both directions: rewinding, the distance to the position and
290 // the duration of the tick are both negative.
291 const double r
292 = (musical_position - musical_start_position) / musical_tick_duration;
293 const int64_t s = constexpr_floor(r * len);
294 return s < 0 ? int64_t(0) : (s >= len ? len - 1 : s);
295 }
296
298 [[nodiscard]] constexpr bool in_range(ossia::time_value global_time) const noexcept
299 {
300 return global_time.impl >= prev_date.impl && global_time.impl < date.impl;
301 }
302
305 [[nodiscard]] constexpr physical_time
306 to_physical_time_in_tick(ossia::time_value global_time, double ratio) const noexcept
307 {
308 // How far into the tick this date sits, counted forwards in both directions.
309 const int64_t in_tick
310 = speed < 0. ? (prev_date - global_time).impl : (global_time - prev_date).impl;
311
312 // Place it inside the span this tick was actually handed, so an event and
313 // the audio it belongs to cannot end up on different samples. Reconstructing
314 // the position from the model dates can miss the span by one.
315 if(start_sample >= 0 && length_sample >= 0)
316 {
317 const int64_t dt = abs(date - prev_date).impl;
318 if(dt <= 0)
319 return start_sample;
320 int64_t s = start_sample
321 + constexpr_floor(double(in_tick) / double(dt) * length_sample);
322 if(s < start_sample)
323 s = start_sample;
324 else if(s > int64_t(start_sample) + length_sample)
325 s = int64_t(start_sample) + length_sample;
326 return s;
327 }
328
329 assert(speed != 0.);
330 return sample_at(this->offset + ossia::time_value{in_tick}, ratio);
331 }
332
335 [[nodiscard]] constexpr physical_time
336 to_physical_time_in_tick(int64_t global_time, double ratio) const noexcept
337 {
338 return to_physical_time_in_tick(ossia::time_value{global_time}, ratio);
339 }
340
343 [[nodiscard]] constexpr time_value
344 from_physical_time_in_tick(ossia::physical_time phys_time, double ratio) const noexcept
345 {
346 assert(speed != 0.);
347 const double in_tick = phys_time - physical_start(ratio);
348 return time_value{constexpr_floor(in_tick * (speed / ratio) + prev_date.impl)};
349 }
350
353 [[nodiscard]] constexpr double position() const noexcept
354 {
355 return parent_duration.impl > 0 ? date.impl / double(parent_duration.impl) : 0.;
356 }
357
359 [[nodiscard]] constexpr bool forward() const noexcept { return date > prev_date; }
360
362 [[nodiscard]] constexpr bool paused() const noexcept { return date == prev_date; }
363
365 [[nodiscard]] constexpr bool backward() const noexcept { return date < prev_date; }
366
367
375 template <typename F>
376 void for_each_bar_segment(double lo, double hi, F&& fn) const noexcept
377 {
378 const bool valid_sig = signature.upper > 0 && signature.lower > 0;
379 const double quarters_in_bar
380 = valid_sig ? 4. * signature.upper / signature.lower : 4.;
381 if(!(quarters_in_bar > 0.))
382 return;
383
384 constexpr double eps = 1e-9;
385 const bool rewinding = date < prev_date;
386 const double near_bar = rewinding ? musical_end_last_bar : musical_start_last_bar;
387 const double far_bar = rewinding ? musical_start_last_bar : musical_end_last_bar;
388
389 // Up to the reported far bar, on the grid the near bar defines.
390 double b = near_bar;
391 for(int i = 0; i < 1024 && b < far_bar - eps; i++)
392 {
393 const double next = (b + quarters_in_bar < far_bar) ? b + quarters_in_bar : far_bar;
394 if(next > lo + eps && b < hi + eps)
395 fn(b, next);
396 b += quarters_in_bar;
397 }
398
399 // From the far bar on, on the grid it defines.
400 b = (far_bar > near_bar) ? far_bar : near_bar;
401 for(int i = 0; i < 1024 && b < hi + eps; i++)
402 {
403 fn(b, b + quarters_in_bar);
404 b += quarters_in_bar;
405 }
406 }
407
415 [[nodiscard]] std::optional<quantification_point>
416 get_quantification_point(double rate) const noexcept
417 {
418 if(prev_date == date)
419 return std::nullopt;
420
421 // Quantized triggers are not interactive while rewinding.
422 if(backward())
423 return std::nullopt;
424
425 const auto pts = get_quantification_dates(rate);
426 if(pts.empty())
427 return std::nullopt;
428 return pts[0];
429 }
430
437 [[nodiscard]] std::optional<time_value>
438 get_quantification_date(double rate) const noexcept
439 {
440 if(const auto pt = get_quantification_point(rate))
441 return pt->date;
442 return std::nullopt;
443 }
444
452 [[nodiscard]] quantification_points
453 get_quantification_dates(double rate) const noexcept
454 {
455 quantification_points res;
456
457 if(prev_date == date)
458 return res;
459
460 const double musical_tick_duration = musical_end_position - musical_start_position;
461 const bool rewinding = date < prev_date;
462
463 // A musical duration that disagrees with the direction of the tick did not
464 // come from it - subdividing it would place every point at prev_date.
465 if(rate <= 0. || musical_tick_duration == 0.
466 || (musical_tick_duration < 0.) != rewinding)
467 {
468 res.push_back({prev_date, 0, musical_start_position});
469 return res;
470 }
471
472 // A point falling exactly on the end of the tick belongs to the next one,
473 // so the interval is [start; end[ - which is also what makes the first
474 // element agree with get_quantification_date().
475 constexpr double eps = 1e-9;
476 const time_value tick_duration = date - prev_date;
477 const bool valid_sig = signature.upper > 0 && signature.lower > 0;
478 const double quarters_in_bar = valid_sig ? 4. * signature.upper / signature.lower : 4.;
479
480 // A point is kept if it lands inside the tick; false means we walked past
481 // the end and can stop.
482 const auto try_push = [&](double musical_position, int64_t index) {
483 // Scale the musical distance to this point by the tick, rather than
484 // scaling the tick by a normalised position. The two associate
485 // differently in floating point and the result is truncated to a whole
486 // flick, so the other order lands a flick short on dates that come out
487 // exact in this one.
488 const double scale = double(tick_duration.impl) / musical_tick_duration;
489 time_value d
490 = prev_date + (musical_position - musical_start_position) * scale;
491
492 // The tick owns [start; end[ in musical positions as well as in dates:
493 // a point sitting musically on the far end belongs to the next tick,
494 // even when truncating its date to a whole flick pulls it inside this
495 // one. Without this, the point fires here at the last flick AND in the
496 // next tick at its first one.
497 if(rewinding)
498 {
499 if(musical_position <= musical_end_position)
500 return false;
501 if(d > prev_date)
502 d = prev_date;
503 if(d <= date)
504 return false;
505 }
506 else
507 {
508 if(musical_position >= musical_end_position)
509 return false;
510 if(d < prev_date)
511 d = prev_date;
512 if(d >= date)
513 return false;
514 }
515
516 res.push_back({d, index, musical_position});
517 // A tick spanning this many points means the rate is nonsense: stop
518 // rather than fill memory.
519 return res.size() < 1024;
520 };
521
522 if(rate <= 1.)
523 {
524 // A bar or longer: the rate is a fraction of a bar, counted from the last
525 // signature change, and no bar line subdivides it.
526 const double unit = quarters_in_bar / rate;
527 if(!(unit > 0.))
528 return res;
529
530 const double origin = musical_start_last_signature;
531 const double start = (musical_start_position - origin) / unit;
532 const double end = (musical_end_position - origin) / unit;
533 const int64_t first = rewinding ? int64_t(std::floor(start + eps))
534 : int64_t(std::ceil(start - eps));
535
536 for(int64_t k = first; rewinding ? (k > end + eps) : (k < end - eps);
537 k += rewinding ? -1 : 1)
538 {
539 if(!try_push(k * unit + origin, k))
540 break;
541 }
542 return res;
543 }
544
545 // Shorter than a bar: a subdivision of the quarter note, counted from the
546 // bar it falls in. The grid restarts at every bar line, so a bar whose
547 // length is not a whole number of divisions (7/8 against a half-note grid)
548 // does not carry a stale phase into the next one, and the bar line itself
549 // is always a point.
550 const double unit = 4. / rate;
551 if(!(unit > 0.) || !(quarters_in_bar > 0.))
552 return res;
553
554 const double lo = rewinding ? musical_end_position : musical_start_position;
555 const double hi = rewinding ? musical_start_position : musical_end_position;
556
557 // Collect the segments first so a rewinding walk can take them in reverse:
558 // the points inside a segment are monotone in k, and so are the segments.
559 ossia::small_vector<std::pair<double, double>, 8> segments;
560 for_each_bar_segment(lo, hi, [&](double bar_line, double next_bar) {
561 if(segments.size() < 1024)
562 segments.push_back({bar_line, next_bar});
563 });
564
565 const auto walk_segment = [&](double bar_line, double next_bar) {
566 const int divs = int(std::ceil((next_bar - bar_line) / unit)) + 1;
567 if(!rewinding)
568 {
569 for(int64_t k = 0; k <= divs; k++)
570 {
571 const double p = bar_line + k * unit;
572 if(p >= next_bar - eps || p > hi + eps)
573 return true;
574 if(p < lo)
575 continue;
576 if(!try_push(p, k))
577 return false;
578 }
579 }
580 else
581 {
582 for(int64_t k = divs; k >= 0; k--)
583 {
584 const double p = bar_line + k * unit;
585 if(p >= next_bar - eps || p > hi + eps)
586 continue;
587 if(p < lo)
588 return true;
589 if(!try_push(p, k))
590 return false;
591 }
592 }
593 return true;
594 };
595
596 if(!rewinding)
597 {
598 for(const auto& [b, n] : segments)
599 if(!walk_segment(b, n))
600 break;
601 }
602 else
603 {
604 for(auto it = segments.rbegin(); it != segments.rend(); ++it)
605 if(!walk_segment(it->first, it->second))
606 break;
607 }
608 return res;
609 }
610
616 [[nodiscard]] ossia_constexpr_msvc_workaround std::optional<physical_time>
617 get_physical_quantification_date(double rate, double modelToSamples) const noexcept
618 {
619 if(auto pt = get_quantification_point(rate))
620 return physical_start(modelToSamples)
621 + physical_position(pt->position, modelToSamples);
622 return {};
623 }
624
625 template <typename Tick, typename Tock>
626 constexpr void
627 metronome(double modelToSamplesRatio, Tick tick, Tock tock) const noexcept
628 {
629 const double musical_tick_duration = musical_end_position - musical_start_position;
630 const bool rewinding = backward();
631
632 // A musical duration that disagrees with the direction of the tick did not
633 // come from it, and interpolating in it would land outside the buffer.
634 if(musical_tick_duration == 0. || (musical_tick_duration < 0.) != rewinding)
635 return;
636
637 // A tick shorter than a sample covers no whole sample, but the grid point
638 // in it still belongs to one: the sample its span starts on, which is
639 // offset 0. Placement depends on the length, emission must not.
640 const int64_t samples_tick_duration = physical_write_duration(modelToSamplesRatio);
641 if(samples_tick_duration < 0)
642 return;
643
644 // The same map the quantification points use, so a click and a quantized
645 // event on one bar line land on one sample.
646 const auto sample_of = [&](double musical_position) {
647 return physical_position(musical_position, modelToSamplesRatio);
648 };
649
650 const double quarters_in_bar = 4. * signature.upper / signature.lower;
651 if(!(quarters_in_bar > 0.))
652 return;
653
654 // Walk every grid point the tick covers rather than only the last one: in
655 // 7/8 the third beat and the following bar line are half a quarter apart,
656 // so a tick of a moderate length steps over both.
657 const double lo = rewinding ? musical_end_position : musical_start_position;
658 const double hi = rewinding ? musical_start_position : musical_end_position;
659 const double bar0 = rewinding ? musical_end_last_bar : musical_start_last_bar;
660
661 // A grid point sitting exactly on a tick boundary belongs to the tick that
662 // starts on it, where it is sample 0, not to the one that ends on it, where
663 // it would be the last sample and so a sample early. Half-open at the end
664 // the tick is heading towards, in both directions.
665 const auto emit = [&](double p, bool is_bar) {
666 if(rewinding ? (p > hi || p <= lo) : (p < lo || p >= hi))
667 return;
668 if(is_bar)
669 tick(sample_of(p));
670 else
671 tock(sample_of(p));
672 };
673
674 // The same bar segments the quantification grid uses, so a click and a
675 // quantized event at the same bar line land on the same sample.
676 double seg_lo[64]{};
677 double seg_hi[64]{};
678 int n_seg = 0;
679 for_each_bar_segment(lo, hi, [&](double bar_line, double next_bar) {
680 if(n_seg < 64)
681 {
682 seg_lo[n_seg] = bar_line;
683 seg_hi[n_seg] = next_bar;
684 n_seg++;
685 }
686 });
687
688 const auto walk_segment = [&](double bar_line, double next_bar) {
689 if(!rewinding)
690 {
691 emit(bar_line, true);
692 for(double q = bar_line + 1.; q < next_bar - 1e-9; q += 1.)
693 emit(q, false);
694 }
695 else
696 {
697 double last = bar_line;
698 for(double q = bar_line + 1.; q < next_bar - 1e-9; q += 1.)
699 last = q;
700 for(double q = last; q > bar_line + 1e-9; q -= 1.)
701 emit(q, false);
702 emit(bar_line, true);
703 }
704 };
705
706 if(!rewinding)
707 for(int i = 0; i < n_seg; i++)
708 walk_segment(seg_lo[i], seg_hi[i]);
709 else
710 for(int i = n_seg - 1; i >= 0; i--)
711 walk_segment(seg_lo[i], seg_hi[i]);
712 }
713
714 [[nodiscard]] constexpr bool unexpected_bar_change() const noexcept
715 {
716 double bar_difference = musical_end_last_bar - musical_start_last_bar;
717 if(bar_difference != 0.)
718 {
719 // If the difference is divisble by the signature,
720 // then the bar change is expected.
721 // e.g. start = 4 -> end = 8 ; signature = 4/4 : good
722 // e.g. start = 4 -> end = 8 ; signature = 6/8 : bad
723 // e.g. start = 4 -> end = 7 ; signature = 6/8 : good
724
725 if(bar_difference < 0.)
726 bar_difference = -bar_difference;
727
728 double quarters_sig = 4. * double(signature.upper) / signature.lower;
729 double div = bar_difference / quarters_sig;
730 bool unexpected = div - int64_t(div) > 0.000001;
731 return unexpected;
732 }
733 return false;
734 }
735
739 [[nodiscard]] constexpr double tick_fraction_at(time_value t) const noexcept
740 {
741 const double total = double(date.impl - prev_date.impl);
742 if(total == 0.)
743 return 0.;
744 double f = double(t.impl - prev_date.impl) / total;
745 if(f < 0.)
746 f = 0.;
747 else if(f > 1.)
748 f = 1.;
749 return f;
750 }
751
752 constexpr void set_end_time(time_value t) noexcept
753 // C++23: [[ expects: t <= this->date && t > this->prev_date ]]
754 {
755 if(length_sample > 0)
756 length_sample = int32_t(length_sample * tick_fraction_at(t) + 0.5);
757
758 const auto old_date = date;
759 date = t;
760
761 if(old_date.impl > 0)
762 {
763 double ratio = t.impl / double(old_date.impl);
764 musical_end_position *= ratio;
765 }
766
767 // TODO what if musical_end_position is now before musical_end_last_bar
768 }
769
770 constexpr void set_start_time(time_value t) noexcept
771 // C++23: [[ expects: t <= this->date && t > this->prev_date ]]
772 {
773 if(length_sample > 0)
774 {
775 const auto skipped = int32_t(length_sample * tick_fraction_at(t) + 0.5);
776 start_sample += skipped;
777 length_sample -= skipped;
778 }
779
780 const auto old_date = prev_date;
781 prev_date = t;
782
783 if(old_date.impl > 0)
784 {
785 double ratio = t.impl / double(old_date.impl);
786 musical_start_position *= ratio;
787 }
788
789 // TODO what if musical_start_position is now after end_position /
790 // end_last_bar ?
791 }
792
793 ossia::time_value prev_date{}; // Sample we are at
794 ossia::time_value date{}; // Sample we are finishing at
795 ossia::time_value parent_duration{}; // Duration of the parent item of the
796 // one being ticked
797
810 ossia::time_value offset{};
811
812 double speed{1.};
813 double tempo{ossia::root_tempo};
814 time_signature signature{}; // Time signature at start
815
827 int32_t start_sample{-1};
828 int32_t length_sample{-1};
829
830 bool start_discontinuous{};
831 bool end_discontinuous{};
832
833 ossia::quarter_note musical_start_last_signature{}; // Position of the last bar
834 // signature change in quarter
835 // notes (at prev_date)
836 ossia::quarter_note musical_start_last_bar{}; // Position of the last bar start in
837 // quarter notes (at prev_date)
838 ossia::quarter_note musical_start_position{}; // Current position in quarter notes
839 ossia::quarter_note musical_end_last_bar{}; // Position of the last bar start in
840 // quarter notes (at date)
841 ossia::quarter_note musical_end_position{}; // Current position in quarter notes
842};
843
844// Copied per node per tick on the audio thread: it has to stay a POD that
845// memcpys, and it should not grow carelessly.
846static_assert(std::is_trivially_copyable_v<token_request>);
847static_assert(std::is_standard_layout_v<token_request>);
848static_assert(sizeof(token_request) == 104);
849
850inline bool operator==(const token_request& lhs, const token_request& rhs)
851{
852 return lhs.prev_date == rhs.prev_date && lhs.date == rhs.date
853 && lhs.parent_duration == rhs.parent_duration && lhs.offset == rhs.offset
854 && lhs.speed == rhs.speed && lhs.tempo == rhs.tempo
855 && lhs.signature == rhs.signature
856 && lhs.musical_start_last_bar == rhs.musical_start_last_bar
857 && lhs.musical_start_position == rhs.musical_start_position
858 && lhs.musical_end_last_bar == rhs.musical_end_last_bar
859 && lhs.musical_end_position == rhs.musical_end_position
860 && lhs.start_discontinuous == rhs.start_discontinuous
861 && lhs.end_discontinuous == rhs.end_discontinuous;
862}
863
864inline bool operator!=(const token_request& lhs, const token_request& rhs)
865{
866 return !(lhs == rhs);
867}
868
869// To be used only for simple examples
870struct simple_token_request
871{
872 time_value prev_date{};
873 time_value date{};
874 time_value parent_duration{};
875 time_value offset{};
876
877 operator token_request() const noexcept
878 {
879 return ossia::token_request{prev_date, date, parent_duration, offset, 1.0,
880 {4, 4}, 120.};
881 }
882
883 friend bool operator==(const token_request& lhs, const simple_token_request& self)
884 {
885 return lhs.prev_date == self.prev_date && lhs.date == self.date
886 && lhs.offset == self.offset;
887 }
888 friend bool operator==(const simple_token_request& self, const token_request& rhs)
889 {
890 return rhs == self;
891 }
892 friend bool operator!=(const token_request& lhs, const simple_token_request& self)
893 {
894 return !(lhs == self);
895 }
896 friend bool operator!=(const simple_token_request& self, const token_request& rhs)
897 {
898 return !(rhs == self);
899 }
900};
901}
Definition git_info.h:7
Definition ossia/dataflow/token_request.hpp:25
double position
Definition ossia/dataflow/token_request.hpp:36
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30