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/editor/scenario/time_signature.hpp>
6
7#include <cassert>
8#include <optional>
9
10#if defined(_LIBCPP_CONSTEXPR_SINCE_CXX23) || defined(_GLIBCXX23_CONSTEXPR)
11#define ossia_constexpr_msvc_workaround constexpr
12#else
13#define ossia_constexpr_msvc_workaround
14#endif
15namespace ossia
16{
17using quarter_note = double;
18
19struct token_request
20{
21 constexpr token_request() noexcept = default;
22 constexpr token_request(const token_request&) noexcept = default;
23 constexpr token_request(token_request&&) noexcept = default;
24 constexpr token_request& operator=(const token_request&) noexcept = default;
25 constexpr token_request& operator=(token_request&&) noexcept = default;
26
27 constexpr token_request(
28 ossia::time_value prev_d, ossia::time_value d, ossia::time_value parent_duration,
29 ossia::time_value off, double s, time_signature sig, double tempo) noexcept
30 : prev_date{prev_d}
31 , date{d}
32 , parent_duration{parent_duration}
33 , offset{off}
34 , speed{s}
35 , tempo{tempo}
36 , signature{sig}
37 {
38 if(offset.impl < 0)
39 {
40 offset.impl = 0;
41 }
42 }
43
44 [[nodiscard]] constexpr token_request add_offset(ossia::time_value t) const noexcept
45 {
46 token_request other = *this;
47 other.prev_date += t;
48 other.date += t;
49 return other;
50 }
51
52 template <typename Exec, typename Transport>
53 constexpr void loop(
54 ossia::time_value start_offset, ossia::time_value loop_duration, Exec f,
55 Transport transport) const noexcept
56 {
57 ossia::token_request other = *this;
58 ossia::time_value orig_from = other.prev_date;
59 ossia::time_value tick_amount = other.date - other.prev_date;
60
61 if(tick_amount >= 0_tv)
62 {
63 // Forward playback
64 while(tick_amount > 0_tv)
65 {
66 const time_value cur_from{orig_from % loop_duration};
67 if(cur_from + tick_amount < loop_duration)
68 {
69 other.prev_date = cur_from + start_offset;
70 other.date = other.prev_date + tick_amount;
71 f(other);
72 break;
73 }
74 else
75 {
76 auto this_tick = loop_duration - cur_from;
77
78 tick_amount -= this_tick;
79 orig_from += this_tick;
80 other.prev_date = cur_from + start_offset;
81 other.date = other.prev_date + this_tick;
82
83 f(other);
84
85 transport(start_offset);
86 other.offset += this_tick;
87 }
88 }
89 }
90 else
91 {
92 // Backward playback (tick_amount < 0)
93 while(tick_amount < 0_tv)
94 {
95 time_value cur_from{orig_from % loop_duration};
96 // Handle negative modulo: ensure cur_from is in [0, loop_duration)
97 if(cur_from.impl < 0)
98 cur_from.impl += loop_duration.impl;
99
100 if(cur_from + tick_amount >= 0_tv)
101 {
102 // The backward tick fits within the current loop iteration
103 other.prev_date = cur_from + start_offset;
104 other.date = other.prev_date + tick_amount;
105 f(other);
106 break;
107 }
108 else
109 {
110 // Go back to the start of the loop, then wrap around
111 auto this_tick = -cur_from; // negative: go from cur_from back to 0
112 if(this_tick == 0_tv)
113 this_tick = -loop_duration; // already at 0, go back a full loop
114
115 tick_amount -= this_tick; // tick_amount becomes less negative
116 orig_from += this_tick;
117 other.prev_date = cur_from + start_offset;
118 other.date = other.prev_date + this_tick;
119
120 f(other);
121
122 // Wrap to end of loop
123 transport(start_offset + loop_duration);
124 other.offset -= this_tick; // this_tick is negative, so offset increases
125 }
126 }
127 }
128 }
129
131 [[nodiscard]] constexpr time_value model_read_duration() const noexcept
132 {
133 return date - prev_date;
134 }
135
139 [[nodiscard]] constexpr physical_time
140 start_date_to_physical(double ratio) const noexcept
141 // C++23: [[ expects: speed != 0. ]]
142 {
143 assert(speed != 0.);
144 return this->prev_date.impl * ratio / speed;
145 }
146
148 [[nodiscard]] constexpr physical_time physical_start(double ratio) const noexcept
149 // C++23: [[ expects: speed != 0. ]]
150 {
151 assert(speed != 0.);
152 return this->offset.impl * ratio / speed;
153 }
154
158 [[nodiscard]] constexpr physical_time
159 physical_read_duration(double ratio) const noexcept
160 {
161 return constexpr_ceil(abs(date - prev_date).impl * ratio);
162 }
163
166 [[nodiscard]] constexpr physical_time
167 physical_write_duration(double ratio) const noexcept
168 // C++23: [[ expects: speed != 0. ]]
169 {
170 assert(speed != 0.);
171 return constexpr_ceil(abs(date - prev_date).impl * ratio / speed);
172 }
173
175 [[nodiscard]] constexpr physical_time
176 safe_physical_write_duration(double ratio, int bufferSize) const noexcept
177 // C++23: [[ expects: speed != 0. ]]
178 {
179 assert(speed != 0.);
180 return constexpr_floor(bufferSize - offset.impl * ratio / speed);
181 }
182
184 [[nodiscard]] constexpr bool in_range(ossia::time_value global_time) const noexcept
185 {
186 return global_time.impl >= prev_date.impl && global_time.impl < date.impl;
187 }
188
191 [[nodiscard]] constexpr physical_time
192 to_physical_time_in_tick(ossia::time_value global_time, double ratio) const noexcept
193 {
194 return (global_time - prev_date + offset).impl * ratio / speed;
195 }
196
199 [[nodiscard]] constexpr physical_time
200 to_physical_time_in_tick(int64_t global_time, double ratio) const noexcept
201 {
202 return to_physical_time_in_tick(ossia::time_value{global_time}, ratio);
203 }
204
207 [[nodiscard]] constexpr time_value
208 from_physical_time_in_tick(ossia::physical_time phys_time, double ratio) const noexcept
209 {
210 return time_value{
211 constexpr_floor(phys_time * (speed / ratio) + prev_date.impl - offset.impl)};
212 }
213
216 [[nodiscard]] constexpr double position() const noexcept
217 {
218 return parent_duration.impl > 0 ? date.impl / double(parent_duration.impl) : 0.;
219 }
220
222 [[nodiscard]] constexpr bool forward() const noexcept { return date > prev_date; }
223
225 [[nodiscard]] constexpr bool paused() const noexcept { return date == prev_date; }
226
228 [[nodiscard]] constexpr bool backward() const noexcept { return date < prev_date; }
229
230 [[nodiscard]] ossia_constexpr_msvc_workaround std::optional<time_value>
231 get_quantification_date_for_bars_or_longer(double rate) const noexcept
232 {
233 std::optional<time_value> quantification_date;
234 const double bars_per_quantization = 1.0 / rate;
235
236 // Convert positions to bar numbers from the last signature
237 const double start_bar_position
238 = (musical_start_position - musical_start_last_signature)
239 / (4.0 * signature.upper / signature.lower);
240 const double end_bar_position = (musical_end_position - musical_start_last_signature)
241 / (4.0 * signature.upper / signature.lower);
242
243 // Check if we're exactly on a quantization point at the start
244 const double start_remainder = std::fmod(start_bar_position, bars_per_quantization);
245 if(std::abs(start_remainder) < 0.0001 && musical_start_position >= 0)
246 {
247 quantification_date = prev_date;
248 }
249 else
250 {
251 // Find the next quantization bar after start
252 const double start_quant_bar
253 = std::floor(start_bar_position / bars_per_quantization);
254 const double next_quant_bar_number = (start_quant_bar + 1) * bars_per_quantization;
255
256 // Check if this quantization point falls within our tick (but NOT at the end)
257 if(next_quant_bar_number > start_bar_position
258 && next_quant_bar_number < end_bar_position)
259 {
260 // Calculate the musical position of this quantization point
261 const double quant_musical_position
262 = musical_start_last_signature
263 + next_quant_bar_number * (4.0 * signature.upper / signature.lower);
264
265 // Map this to a time value
266 const double musical_tick_duration
267 = musical_end_position - musical_start_position;
268 const double ratio
269 = (quant_musical_position - musical_start_position) / musical_tick_duration;
270 const time_value dt = date - prev_date;
271
272 time_value potential_date = prev_date + dt * ratio;
273
274 // Extra safety check: ensure we're not at the boundary
275 if(potential_date < date)
276 {
277 quantification_date = potential_date;
278 }
279 else
280 {
281 return std::nullopt;
282 }
283 }
284 }
285 return quantification_date;
286 }
287
288 [[nodiscard]] ossia_constexpr_msvc_workaround std::optional<time_value>
289 get_quantification_date_for_shorter_than_bars(double rate) const noexcept
290 {
291 // Quantize relative to quarter divisions
292 // TODO ! if there is a bar change,
293 // and no prior quantization date before that, we have to quantize to the
294 // bar change
295 const double start_quarter = (musical_start_position - musical_start_last_bar);
296 const double end_quarter = (musical_end_position - musical_start_last_bar);
297
298 // duration of what we quantify in terms of quarters
299 const double musical_quant_dur = rate / 4.;
300 const double start_quant = std::floor(start_quarter * musical_quant_dur);
301 const double end_quant = std::floor(end_quarter * musical_quant_dur);
302
303 if(start_quant != end_quant)
304 {
305 if(end_quant == end_quarter * musical_quant_dur)
306 {
307 // We want quantization on start, not on end
308 return std::nullopt;
309 }
310 // Date to quantify is the next one :
311 const double musical_tick_duration = musical_end_position - musical_start_position;
312 const double quantified_duration
313 = (musical_start_last_bar + (start_quant + 1) * 4. / rate)
314 - musical_start_position;
315 const double ratio = (date - prev_date).impl / musical_tick_duration;
316
317 return prev_date + quantified_duration * ratio;
318 }
319 else if(start_quant == start_quarter * musical_quant_dur)
320 {
321 // We start on a signature change
322 return prev_date;
323 }
324 else
325 {
326 return std::nullopt;
327 }
328 }
329
333 [[nodiscard]] ossia_constexpr_msvc_workaround std::optional<time_value>
334 get_quantification_date(double rate) const noexcept
335 {
336 if(prev_date == date)
337 return std::nullopt;
338
339 if(rate <= 0.)
340 return prev_date;
341
342 const double musical_tick_duration = musical_end_position - musical_start_position;
343 if(musical_tick_duration <= 0.)
344 return prev_date;
345
346 if(rate <= 1.)
347 {
348 return get_quantification_date_for_bars_or_longer(rate);
349 }
350 else
351 {
352 return get_quantification_date_for_shorter_than_bars(rate);
353 }
354 }
355
357 [[nodiscard]] ossia_constexpr_msvc_workaround std::optional<physical_time>
358 get_physical_quantification_date(double rate, double modelToSamples) const noexcept
359 {
360 if(auto d = get_quantification_date(rate))
361 return to_physical_time_in_tick(*d, modelToSamples);
362 return {};
363 }
364
365 template <typename Tick, typename Tock>
366 constexpr void
367 metronome(double modelToSamplesRatio, Tick tick, Tock tock) const noexcept
368 {
369 if((musical_end_last_bar != musical_start_last_bar) || musical_start_position == 0.)
370 {
371 // There is a bar change in this tick, start the up tick
372 const double musical_tick_duration = musical_end_position - musical_start_position;
373 if(musical_tick_duration != 0)
374 {
375 const double musical_bar_start = musical_end_last_bar - musical_start_position;
376 const int64_t samples_tick_duration
377 = physical_write_duration(modelToSamplesRatio);
378 if(samples_tick_duration > 0)
379 {
380 const double ratio = musical_bar_start / musical_tick_duration;
381 const int64_t hi_start_sample = samples_tick_duration * ratio;
382 tick(hi_start_sample);
383 }
384 }
385 }
386 else
387 {
388 const int64_t start_quarter
389 = std::floor(musical_start_position - musical_start_last_bar);
390 const int64_t end_quarter
391 = std::floor(musical_end_position - musical_start_last_bar);
392 if(start_quarter != end_quarter)
393 {
394 // There is a quarter change in this tick, start the down tick
395 // start_position is prev_date
396 // end_position is date
397 const double musical_tick_duration
398 = musical_end_position - musical_start_position;
399 if(musical_tick_duration != 0)
400 {
401 const double musical_bar_start
402 = (end_quarter + musical_start_last_bar) - musical_start_position;
403 const int64_t samples_tick_duration
404 = physical_write_duration(modelToSamplesRatio);
405 if(samples_tick_duration > 0)
406 {
407 const double ratio = musical_bar_start / musical_tick_duration;
408 const int64_t lo_start_sample = samples_tick_duration * ratio;
409 tock(lo_start_sample);
410 }
411 }
412 }
413 }
414 }
415
416 [[nodiscard]] constexpr bool unexpected_bar_change() const noexcept
417 {
418 double bar_difference = musical_end_last_bar - musical_start_last_bar;
419 if(bar_difference != 0.)
420 {
421 // If the difference is divisble by the signature,
422 // then the bar change is expected.
423 // e.g. start = 4 -> end = 8 ; signature = 4/4 : good
424 // e.g. start = 4 -> end = 8 ; signature = 6/8 : bad
425 // e.g. start = 4 -> end = 7 ; signature = 6/8 : good
426
427 double quarters_sig = 4. * double(signature.upper) / signature.lower;
428 double div = bar_difference / quarters_sig;
429 bool unexpected = div - int64_t(div) > 0.000001;
430 return unexpected;
431 }
432 return false;
433 }
434
435 constexpr void set_end_time(time_value t) noexcept
436 // C++23: [[ expects: t <= this->date && t > this->prev_date ]]
437 {
438 const auto old_date = date;
439 date = t;
440
441 if(old_date.impl > 0)
442 {
443 double ratio = t.impl / double(old_date.impl);
444 musical_end_position *= ratio;
445 }
446
447 // TODO what if musical_end_position is now before musical_end_last_bar
448 }
449
450 constexpr void set_start_time(time_value t) noexcept
451 // C++23: [[ expects: t <= this->date && t > this->prev_date ]]
452 {
453 const auto old_date = prev_date;
454 prev_date = t;
455
456 if(old_date.impl > 0)
457 {
458 double ratio = t.impl / double(old_date.impl);
459 musical_start_position *= ratio;
460 }
461
462 // TODO what if musical_start_position is now after end_position /
463 // end_last_bar ?
464 }
465
466 ossia::time_value prev_date{}; // Sample we are at
467 ossia::time_value date{}; // Sample we are finishing at
468 ossia::time_value parent_duration{}; // Duration of the parent item of the
469 // one being ticked
470
483 ossia::time_value offset{};
484
485 double speed{1.};
486 double tempo{ossia::root_tempo};
487 time_signature signature{}; // Time signature at start
488
489 ossia::quarter_note musical_start_last_signature{}; // Position of the last bar
490 // signature change in quarter
491 // notes (at prev_date)
492 ossia::quarter_note musical_start_last_bar{}; // Position of the last bar start in
493 // quarter notes (at prev_date)
494 ossia::quarter_note musical_start_position{}; // Current position in quarter notes
495 ossia::quarter_note musical_end_last_bar{}; // Position of the last bar start in
496 // quarter notes (at date)
497 ossia::quarter_note musical_end_position{}; // Current position in quarter notes
498 bool start_discontinuous{};
499 bool end_discontinuous{};
500};
501
502inline bool operator==(const token_request& lhs, const token_request& rhs)
503{
504 return lhs.prev_date == rhs.prev_date && lhs.date == rhs.date
505 && lhs.parent_duration == rhs.parent_duration && lhs.offset == rhs.offset
506 && lhs.speed == rhs.speed && lhs.tempo == rhs.tempo
507 && lhs.signature == rhs.signature
508 && lhs.musical_start_last_bar == rhs.musical_start_last_bar
509 && lhs.musical_start_position == rhs.musical_start_position
510 && lhs.musical_end_last_bar == rhs.musical_end_last_bar
511 && lhs.musical_end_position == rhs.musical_end_position
512 && lhs.start_discontinuous == rhs.start_discontinuous
513 && lhs.end_discontinuous == rhs.end_discontinuous;
514}
515
516inline bool operator!=(const token_request& lhs, const token_request& rhs)
517{
518 return !(lhs == rhs);
519}
520
521// To be used only for simple examples
522struct simple_token_request
523{
524 time_value prev_date{};
525 time_value date{};
526 time_value parent_duration{};
527 time_value offset{};
528
529 operator token_request() const noexcept
530 {
531 return ossia::token_request{prev_date, date, parent_duration, offset, 1.0,
532 {4, 4}, 120.};
533 }
534
535 friend bool operator==(const token_request& lhs, const simple_token_request& self)
536 {
537 return lhs.prev_date == self.prev_date && lhs.date == self.date
538 && lhs.offset == self.offset;
539 }
540 friend bool operator==(const simple_token_request& self, const token_request& rhs)
541 {
542 return rhs == self;
543 }
544 friend bool operator!=(const token_request& lhs, const simple_token_request& self)
545 {
546 return !(lhs == self);
547 }
548 friend bool operator!=(const simple_token_request& self, const token_request& rhs)
549 {
550 return !(rhs == self);
551 }
552};
553}
Definition git_info.h:7
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30