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