OSSIA
Open Scenario System for Interactive Application
ossia/dataflow/token_request.hpp
1 #pragma once
2 #include <ossia/detail/flicks.hpp>
3 #include <ossia/detail/math.hpp>
4 #include <ossia/editor/scenario/time_signature.hpp>
6 
7 #include <cassert>
8 #include <optional>
9 
10 namespace ossia
11 {
12 using quarter_note = double;
13 
14 struct 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 
186  [[nodiscard]] constexpr std::optional<time_value>
187  get_quantification_date(double rate) const noexcept
188  {
189  std::optional<time_value> quantification_date;
190 
191  if(rate <= 0.)
192  return prev_date;
193 
194  const double musical_tick_duration = musical_end_position - musical_start_position;
195  if(musical_tick_duration <= 0.)
196  return prev_date;
197 
198  if(rate <= 1.)
199  {
200  // Quantize relative to bars
201  if(musical_end_last_bar != musical_start_last_bar)
202  {
203  // 4 if we're in 4/4 for instance
204  const double musical_bar_duration
205  = musical_end_last_bar - musical_start_last_bar;
206 
207  // rate = 0.5 -> 2 bars at 3/4 -> 6 quarter notes
208  const double quantif_duration = musical_bar_duration / rate;
209 
210  // we must be on quarter note 6, 12, 18, ... from the previous
211  // signature
212  const double rem = std::fmod(
213  musical_end_last_bar - musical_start_last_signature, quantif_duration);
214  if(rem < 0.0001)
215  {
216  // There is a bar change in this tick and it is when we are going to
217  // trigger
218  const double musical_bar_start = musical_end_last_bar - musical_start_position;
219 
220  const double ratio = musical_bar_start / musical_tick_duration;
221  const time_value dt = date - prev_date; // TODO should be tick_offset
222 
223  quantification_date = prev_date + dt * ratio;
224  }
225  }
226  }
227  else
228  {
229  // Quantize relative to quarter divisions
230  // TODO ! if there is a bar change,
231  // and no prior quantization date before that, we have to quantize to the
232  // bar change
233  const double start_quarter = (musical_start_position - musical_start_last_bar);
234  const double end_quarter = (musical_end_position - musical_start_last_bar);
235 
236  // duration of what we quantify in terms of quarters
237  const double musical_quant_dur = rate / 4.;
238  const double start_quant = std::floor(start_quarter * musical_quant_dur);
239  const double end_quant = std::floor(end_quarter * musical_quant_dur);
240 
241  if(start_quant != end_quant)
242  {
243  // Date to quantify is the next one :
244  const double musical_tick_duration
245  = musical_end_position - musical_start_position;
246  const double quantified_duration
247  = (musical_start_last_bar + (start_quant + 1) * 4. / rate)
248  - musical_start_position;
249  const double ratio = (date - prev_date).impl / musical_tick_duration;
250 
251  quantification_date = prev_date + quantified_duration * ratio;
252  }
253  else if(musical_start_position == 0. && musical_end_position > 0.)
254  {
255  // Special first bar case
256  return prev_date;
257  }
258  }
259 
260  return quantification_date;
261  }
262 
264  [[nodiscard]] constexpr std::optional<physical_time>
265  get_physical_quantification_date(double rate, double modelToSamples) const noexcept
266  {
267  if(auto d = get_quantification_date(rate))
268  return to_physical_time_in_tick(*d, modelToSamples);
269  return {};
270  }
271 
272  template <typename Tick, typename Tock>
273  constexpr void
274  metronome(double modelToSamplesRatio, Tick tick, Tock tock) const noexcept
275  {
276  if((musical_end_last_bar != musical_start_last_bar) || musical_start_position == 0.)
277  {
278  // There is a bar change in this tick, start the up tick
279  const double musical_tick_duration = musical_end_position - musical_start_position;
280  if(musical_tick_duration != 0)
281  {
282  const double musical_bar_start = musical_end_last_bar - musical_start_position;
283  const int64_t samples_tick_duration
284  = physical_write_duration(modelToSamplesRatio);
285  if(samples_tick_duration > 0)
286  {
287  const double ratio = musical_bar_start / musical_tick_duration;
288  const int64_t hi_start_sample = samples_tick_duration * ratio;
289  tick(hi_start_sample);
290  }
291  }
292  }
293  else
294  {
295  const int64_t start_quarter
296  = std::floor(musical_start_position - musical_start_last_bar);
297  const int64_t end_quarter
298  = std::floor(musical_end_position - musical_start_last_bar);
299  if(start_quarter != end_quarter)
300  {
301  // There is a quarter change in this tick, start the down tick
302  // start_position is prev_date
303  // end_position is date
304  const double musical_tick_duration
305  = musical_end_position - musical_start_position;
306  if(musical_tick_duration != 0)
307  {
308  const double musical_bar_start
309  = (end_quarter + musical_start_last_bar) - musical_start_position;
310  const int64_t samples_tick_duration
311  = physical_write_duration(modelToSamplesRatio);
312  if(samples_tick_duration > 0)
313  {
314  const double ratio = musical_bar_start / musical_tick_duration;
315  const int64_t lo_start_sample = samples_tick_duration * ratio;
316  tock(lo_start_sample);
317  }
318  }
319  }
320  }
321  }
322 
323  [[nodiscard]] constexpr bool unexpected_bar_change() const noexcept
324  {
325  double bar_difference = musical_end_last_bar - musical_start_last_bar;
326  if(bar_difference != 0.)
327  {
328  // If the difference is divisble by the signature,
329  // then the bar change is expected.
330  // e.g. start = 4 -> end = 8 ; signature = 4/4 : good
331  // e.g. start = 4 -> end = 8 ; signature = 6/8 : bad
332  // e.g. start = 4 -> end = 7 ; signature = 6/8 : good
333 
334  double quarters_sig = 4. * double(signature.upper) / signature.lower;
335  double div = bar_difference / quarters_sig;
336  bool unexpected = div - int64_t(div) > 0.000001;
337  return unexpected;
338  }
339  return false;
340  }
341 
342  constexpr void set_end_time(time_value t) noexcept
343  // C++23: [[ expects: t <= this->date && t > this->prev_date ]]
344  {
345  const auto old_date = date;
346  date = t;
347 
348  if(old_date.impl > 0)
349  {
350  double ratio = t.impl / double(old_date.impl);
351  musical_end_position *= ratio;
352  }
353 
354  // TODO what if musical_end_position is now before musical_end_last_bar
355  }
356 
357  constexpr void set_start_time(time_value t) noexcept
358  // C++23: [[ expects: t <= this->date && t > this->prev_date ]]
359  {
360  const auto old_date = prev_date;
361  prev_date = t;
362 
363  if(old_date.impl > 0)
364  {
365  double ratio = t.impl / double(old_date.impl);
366  musical_start_position *= ratio;
367  }
368 
369  // TODO what if musical_start_position is now after end_position /
370  // end_last_bar ?
371  }
372 
373  ossia::time_value prev_date{}; // Sample we are at
374  ossia::time_value date{}; // Sample we are finishing at
375  ossia::time_value parent_duration{}; // Duration of the parent item of the
376  // one being ticked
377 
390  ossia::time_value offset{};
391 
392  double speed{1.};
393  double tempo{ossia::root_tempo};
394  time_signature signature{}; // Time signature at start
395 
396  ossia::quarter_note musical_start_last_signature{}; // Position of the last bar
397  // signature change in quarter
398  // notes (at prev_date)
399  ossia::quarter_note musical_start_last_bar{}; // Position of the last bar start in
400  // quarter notes (at prev_date)
401  ossia::quarter_note musical_start_position{}; // Current position in quarter notes
402  ossia::quarter_note musical_end_last_bar{}; // Position of the last bar start in
403  // quarter notes (at date)
404  ossia::quarter_note musical_end_position{}; // Current position in quarter notes
405  bool start_discontinuous{};
406  bool end_discontinuous{};
407 };
408 
409 inline bool operator==(const token_request& lhs, const token_request& rhs)
410 {
411  return lhs.prev_date == rhs.prev_date && lhs.date == rhs.date
412  && lhs.parent_duration == rhs.parent_duration && lhs.offset == rhs.offset
413  && lhs.speed == rhs.speed && lhs.tempo == rhs.tempo
414  && lhs.signature == rhs.signature
415  && lhs.musical_start_last_bar == rhs.musical_start_last_bar
416  && lhs.musical_start_position == rhs.musical_start_position
417  && lhs.musical_end_last_bar == rhs.musical_end_last_bar
418  && lhs.musical_end_position == rhs.musical_end_position
419  && lhs.start_discontinuous == rhs.start_discontinuous
420  && lhs.end_discontinuous == rhs.end_discontinuous;
421 }
422 
423 inline bool operator!=(const token_request& lhs, const token_request& rhs)
424 {
425  return !(lhs == rhs);
426 }
427 
428 // To be used only for simple examples
429 struct simple_token_request
430 {
431  time_value prev_date{};
432  time_value date{};
433  time_value parent_duration{};
434  time_value offset{};
435 
436  operator token_request() const noexcept
437  {
438  return ossia::token_request{prev_date, date, parent_duration, offset, 1.0,
439  {4, 4}, 120.};
440  }
441 
442  friend bool operator==(const token_request& lhs, const simple_token_request& self)
443  {
444  return lhs.prev_date == self.prev_date && lhs.date == self.date
445  && lhs.offset == self.offset;
446  }
447  friend bool operator==(const simple_token_request& self, const token_request& rhs)
448  {
449  return rhs == self;
450  }
451  friend bool operator!=(const token_request& lhs, const simple_token_request& self)
452  {
453  return !(lhs == self);
454  }
455  friend bool operator!=(const simple_token_request& self, const token_request& rhs)
456  {
457  return !(rhs == self);
458  }
459 };
460 }
Definition: git_info.h:7
The time_value class.
Definition: ossia/editor/scenario/time_value.hpp:28