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