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