Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
tempo_map.h
1// SPDX-FileCopyrightText: © 2025-2026 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <algorithm>
7#include <cassert>
8#include <cmath>
9#include <cstdint>
10#include <span>
11#include <stdexcept>
12#include <string_view>
13#include <vector>
14
15#include "utils/units.h"
16
17#include <nlohmann/json_fwd.hpp>
18
19namespace zrythm::dsp
20{
21
41template <units::tick_t::NTTP PPQ> class FixedPpqTempoMap
42{
43 friend class TempoMapWrapper;
44
45public:
47 enum class CurveType : std::uint8_t
48 {
51 };
52
55 {
56 units::tick_t tick;
57 double bpm{};
59
60 friend void to_json (nlohmann::json &j, const TempoEvent &e);
61 friend void from_json (const nlohmann::json &j, TempoEvent &e);
62 };
63
66 {
67 units::tick_t tick;
68 int numerator{};
70
71 constexpr auto quarters_per_bar () const
72 {
73 return (numerator * 4) / denominator;
74 }
75
76 constexpr auto ticks_per_bar () const
77 {
78 return units::ticks (quarters_per_bar () * FixedPpqTempoMap::get_ppq ());
79 }
80
81 constexpr auto ticks_per_beat () const
82 {
83 return ticks_per_bar () / numerator;
84 }
85
86 constexpr auto
87 is_different_time_signature (const TimeSignatureEvent &other) const
88 {
89 return numerator != other.numerator || denominator != other.denominator;
90 }
91
92 friend void to_json (nlohmann::json &j, const TimeSignatureEvent &e);
93 friend void from_json (const nlohmann::json &j, TimeSignatureEvent &e);
94 };
95
98 {
99 int bar{ 1 };
100 int beat{ 1 };
101 int sixteenth{ 1 };
102 int tick{};
103
104 friend bool
105 operator== (const MusicalPosition &lhs, const MusicalPosition &rhs) =
106 default;
107 };
108
113 explicit FixedPpqTempoMap (units::precise_sample_rate_t sampleRate)
114 : sample_rate_ (sampleRate)
115 {
116 }
117
119 void set_sample_rate (units::precise_sample_rate_t sampleRate)
120 {
121 sample_rate_ = sampleRate;
122 }
123
135 void add_tempo_event (units::tick_t tick, double bpm, CurveType curve)
136 {
137 if (bpm <= 0)
138 throw std::invalid_argument ("BPM must be positive");
139 if (tick < units::ticks (0))
140 throw std::invalid_argument ("Tick must be non-negative");
141
142 // Automatically add a default tempo event at tick 0
143 if (events_.empty () && tick != units::ticks (0))
144 {
145 events_.push_back (default_tempo_);
146 }
147
148 // Find and remove existing event at same tick
149 auto it = std::ranges::find (events_, tick, [] (const TempoEvent &e) {
150 return e.tick;
151 });
152 if (it != events_.end ())
153 {
154 events_.erase (it);
155 }
156
157 events_.push_back ({ tick, bpm, curve });
158 rebuild_cumulative_times ();
159 }
160
162 void remove_tempo_event (units::tick_t tick)
163 {
164 if (events_.size () > 1 && tick == units::ticks (0))
165 throw std::invalid_argument (
166 "Cannot remove first tempo event - remove other tempo event first");
167
168 auto it = std::ranges::find (events_, tick, [] (const TempoEvent &e) {
169 return e.tick;
170 });
171 if (it != events_.end ())
172 {
173 events_.erase (it);
174 rebuild_cumulative_times ();
175 }
176 }
177
192 void
193 add_time_signature_event (units::tick_t tick, int numerator, int denominator)
194 {
195 if (tick < units::ticks (0))
196 throw std::invalid_argument ("Tick must be non-negative");
197 if (numerator <= 0 || denominator <= 0)
198 throw std::invalid_argument ("Invalid time signature");
199 if (!events_.empty ())
200 throw std::logic_error (
201 "Time signature events must be added before tempo events");
202
203 // Automatically add a default time signature event at tick 0
204 if (time_sig_events_.empty () && tick != units::ticks (0))
205 {
206 time_sig_events_.push_back (default_time_sig_);
207 }
208
209 // Remove existing event at same tick
210 auto it = std::ranges::find (
211 time_sig_events_, tick,
212 [] (const TimeSignatureEvent &e) { return e.tick; });
213 if (it != time_sig_events_.end ())
214 {
215 time_sig_events_.erase (it);
216 }
217
218 time_sig_events_.push_back ({ tick, numerator, denominator });
219 std::ranges::sort (time_sig_events_, {}, &TimeSignatureEvent::tick);
220 }
221
223 void remove_time_signature_event (units::tick_t tick)
224 {
225 if (time_sig_events_.size () > 1 && tick == units::ticks (0))
226 throw std::invalid_argument (
227 "Cannot remove time signature event at tick 0 - remove other events first");
228
229 auto it = std::ranges::find (
230 time_sig_events_, tick,
231 [] (const TimeSignatureEvent &e) { return e.tick; });
232 if (it != time_sig_events_.end ())
233 {
234 time_sig_events_.erase (it);
235 }
236 }
237
239 auto
240 tick_to_seconds (units::precise_tick_t tick) const -> units::precise_second_t
241 {
242 const auto &[events, cumulative_seconds] = get_events_or_default ();
243
244 // Find the last event <= target tick
245 auto it = std::ranges::upper_bound (events, tick, {}, &TempoEvent::tick);
246
247 if (it == events.begin ())
248 {
249 return units::seconds (0.0);
250 }
251
252 size_t index = std::distance (events.begin (), it) - 1;
253 const auto &startEvent = events[index];
254 const units::tick_t segmentStart = startEvent.tick;
255 const auto ticksFromStart =
256 tick - static_cast<units::precise_tick_t> (segmentStart);
257 const auto baseSeconds = cumulative_seconds[index];
258
259 // Last event segment
260 if (index == events.size () - 1)
261 {
262 return baseSeconds
263 + units::seconds (
264 (ticksFromStart.in (units::ticks)
265 / static_cast<double> (get_ppq ()))
266 * (60.0 / startEvent.bpm));
267 }
268
269 const auto &endEvent = events[index + 1];
270 const units::tick_t segmentTicks = endEvent.tick - segmentStart;
271 const auto dSegmentTicks = static_cast<units::precise_tick_t> (segmentTicks);
272
273 // Constant tempo segment
274 if (startEvent.curve == CurveType::Constant)
275 {
276 return baseSeconds
277 + units::seconds (
278 (ticksFromStart.in (units::ticks)
279 / static_cast<double> (get_ppq ()))
280 * (60.0 / startEvent.bpm));
281 }
282 // Linear tempo ramp
283 else if (startEvent.curve == CurveType::Linear)
284 {
285 const double bpm0 = startEvent.bpm;
286 const double bpm1 = endEvent.bpm;
287
288 if (std::abs (bpm1 - bpm0) < 1e-5)
289 {
290 return baseSeconds
291 + units::seconds (
292 (ticksFromStart.in (units::ticks)
293 / static_cast<double> (get_ppq ()))
294 * (60.0 / bpm0));
295 }
296
297 const auto fraction = ticksFromStart / dSegmentTicks;
298 const double currentBpm = bpm0 + (fraction * (bpm1 - bpm0));
299
300 return baseSeconds
301 + units::seconds (
302 (60.0 * dSegmentTicks.in (units::ticks))
303 / (get_ppq () * (bpm1 - bpm0)) * std::log (currentBpm / bpm0));
304 }
305
306 return baseSeconds;
307 }
308
310 units::precise_sample_t tick_to_samples (units::precise_tick_t tick) const
311 {
312 return tick_to_seconds (tick) * sample_rate_;
313 }
314
315 units::sample_t tick_to_samples_rounded (units::precise_tick_t tick) const
316 {
317 return au::round_as<int64_t> (units::samples, tick_to_samples (tick));
318 }
319
321 units::precise_tick_t seconds_to_tick (units::precise_second_t seconds) const
322 {
323 if (seconds <= units::seconds (0.0))
324 return units::ticks (0.0);
325
326 const auto &[events, cumulative_seconds] = get_events_or_default ();
327
328 // Find the segment containing the time
329 auto it = std::ranges::upper_bound (cumulative_seconds, seconds);
330 const size_t index =
331 (it == cumulative_seconds.begin ())
332 ? 0
333 : std::distance (cumulative_seconds.begin (), it) - 1;
334
335 const auto baseSeconds = cumulative_seconds[index];
336 const auto timeInSegment = seconds - baseSeconds;
337 const TempoEvent &startEvent = events[index];
338
339 // Last segment
340 if (index == events.size () - 1)
341 {
342 const double beats =
343 timeInSegment.in (units::seconds) * (startEvent.bpm / 60.0);
344 return static_cast<units::precise_tick_t> (startEvent.tick)
345 + units::ticks (beats * get_ppq ());
346 }
347
348 const TempoEvent &endEvent = events[index + 1];
349 const units::tick_t segmentTicks = endEvent.tick - startEvent.tick;
350 const auto dSegmentTicks = static_cast<units::precise_tick_t> (segmentTicks);
351
352 // Constant tempo segment
353 if (startEvent.curve == CurveType::Constant)
354 {
355 const double beats =
356 timeInSegment.in (units::seconds) * (startEvent.bpm / 60.0);
357 return static_cast<units::precise_tick_t> (startEvent.tick)
358 + units::ticks (beats * get_ppq ());
359 }
360 // Linear tempo ramp
361 else if (startEvent.curve == CurveType::Linear)
362 {
363 const double bpm0 = startEvent.bpm;
364 const double bpm1 = endEvent.bpm;
365
366 if (std::abs (bpm1 - bpm0) < 1e-5)
367 {
368 const double beats =
369 timeInSegment.in (units::seconds) * (bpm0 / 60.0);
370 return static_cast<units::precise_tick_t> (startEvent.tick)
371 + units::ticks (beats * get_ppq ());
372 }
373
374 const auto A =
375 (get_ppq () * (bpm1 - bpm0))
376 / (60.0 * dSegmentTicks.in (units::ticks));
377 const double expVal = std::exp (A * timeInSegment.in (units::seconds));
378 const double f = (expVal - 1.0) * (bpm0 / (bpm1 - bpm0));
379
380 return static_cast<units::precise_tick_t> (startEvent.tick)
381 + f * dSegmentTicks;
382 }
383
384 return static_cast<units::precise_tick_t> (startEvent.tick);
385 }
386
388 units::precise_tick_t samples_to_tick (units::precise_sample_t samples) const
389 {
390 const auto seconds = samples / sample_rate_;
391 return seconds_to_tick (seconds);
392 }
393
399 TimeSignatureEvent time_signature_at_tick (units::tick_t tick) const
400 {
401 if (time_sig_events_.empty ())
402 return default_time_sig_;
403
404 // Find the last time signature change <= tick
405 auto it = std::ranges::upper_bound (
406 time_sig_events_, tick, {}, &TimeSignatureEvent::tick);
407 if (it == time_sig_events_.begin ())
408 {
409 // No event before tick - return default
410 return default_time_sig_;
411 }
412 --it;
413
414 return *it;
415 }
416
422 double tempo_at_tick (units::tick_t tick) const
423 {
424 if (events_.empty ())
425 return default_tempo_.bpm;
426
427 // Find the last tempo change <= tick
428 auto it = std::ranges::upper_bound (events_, tick, {}, &TempoEvent::tick);
429 if (it == events_.begin ())
430 {
431 // No event before tick - return default
432 return default_tempo_.bpm;
433 }
434 --it;
435
436 // If this is the last event or constant, return as-is
437 if (it == events_.end () - 1 || (it)->curve == CurveType::Constant)
438 {
439 return it->bpm;
440 }
441
442 // Handle linear ramp segment
443 const auto &startEvent = *it;
444 const auto &endEvent = *(it + 1);
445 const units::tick_t segmentTicks = endEvent.tick - startEvent.tick;
446 const double fraction =
447 static_cast<units::precise_tick_t> (tick - startEvent.tick)
448 / static_cast<units::precise_tick_t> (segmentTicks);
449 const double currentBpm =
450 startEvent.bpm + fraction * (endEvent.bpm - startEvent.bpm);
451
452 return currentBpm;
453 }
454
460 MusicalPosition tick_to_musical_position (units::tick_t tick) const
461 {
462 const auto &time_sig_events = get_time_signature_events_or_default ();
463
464 if (time_sig_events.empty ())
465 return { 1, 1, 1, 0 };
466
467 // Find the last time signature change <= tick
468 auto it = std::ranges::upper_bound (
469 time_sig_events, tick, {}, &TimeSignatureEvent::tick);
470 if (it == time_sig_events.begin ())
471 {
472 it = time_sig_events.end (); // No valid event
473 }
474 else
475 {
476 --it;
477 }
478
479 if (it == time_sig_events.end ())
480 {
481 return { 1, 1, 1, 0 };
482 }
483
484 const auto &sigEvent = *it;
485 const int numerator = sigEvent.numerator;
486 const int denominator = sigEvent.denominator;
487
488 // Calculate ticks per bar and beat
489 const double quarters_per_bar = numerator * (4.0 / denominator);
490 const int64_t ticks_per_bar =
491 static_cast<int64_t> (quarters_per_bar * get_ppq ());
492 const int64_t ticks_per_beat = ticks_per_bar / numerator;
493
494 // Calculate absolute bar number
495 int64_t cumulative_bars = 1;
496 // int64_t cumulative_ticks = 0;
497
498 // Calculate total bars from previous time signatures
499 for (auto prev = time_sig_events.begin (); prev != it; ++prev)
500 {
501 const int prev_numerator = prev->numerator;
502 const int prev_denominator = prev->denominator;
503 const double prev_quarters_per_bar =
504 prev_numerator * (4.0 / prev_denominator);
505 const int64_t prev_ticks_per_bar =
506 static_cast<int64_t> (prev_quarters_per_bar * get_ppq ());
507
508 // Ticks from this signature to next
509 auto next = std::next (prev);
510 const auto end_tick =
511 (next != time_sig_events.end ()) ? next->tick : sigEvent.tick;
512 const auto segment_ticks = end_tick - prev->tick;
513
514 cumulative_bars +=
515 (segment_ticks / prev_ticks_per_bar).in (units::ticks);
516 // cumulative_ticks += segment_ticks;
517 }
518
519 // Calculate bars since current signature
520 const auto ticks_since_sig = tick - sigEvent.tick;
521 const int64_t bars_since_sig =
522 ticks_since_sig.in (units::ticks) / ticks_per_bar;
523 const auto bar = cumulative_bars + bars_since_sig;
524
525 // Calculate position within current bar
526 const int64_t ticks_in_bar =
527 ticks_since_sig.in (units::ticks) % ticks_per_bar;
528 const auto beat = 1 + (ticks_in_bar / ticks_per_beat);
529
530 // Calculate position within current beat
531 const int64_t ticks_in_beat = ticks_in_bar % ticks_per_beat;
532 const auto sixteenth =
533 1 + (ticks_in_beat / ticks_per_sixteenth_.in (units::ticks));
534 const auto tick_in_sixteenth =
535 (ticks_in_beat % ticks_per_sixteenth_.in (units::ticks));
536
537 return {
538 static_cast<int> (bar), static_cast<int> (beat),
539 static_cast<int> (sixteenth), static_cast<int> (tick_in_sixteenth)
540 };
541 }
542
543 MusicalPosition samples_to_musical_position (units::sample_t samples) const
544 {
545 // Note: we are using `floor()` because we never want the MusicalPosition to
546 // be after the given samples
547 const auto tick = au::floor_as<int64_t> (
548 units::ticks,
549 samples_to_tick (static_cast<units::precise_sample_t> (samples)));
550 return tick_to_musical_position (tick);
551 }
552
560 units::tick_t musical_position_to_tick (const MusicalPosition &pos) const
561 {
562 const auto &time_sig_events = get_time_signature_events_or_default ();
563
564 // Validate position
565 if (pos.bar < 1 || pos.beat < 1 || pos.sixteenth < 1 || pos.tick < 0)
566 {
567 throw std::invalid_argument ("Invalid musical position");
568 }
569
570 auto cumulative_ticks = units::ticks (0);
571 int current_bar = 1;
572
573 // Iterate through time signature changes
574 for (size_t i = 0; i < time_sig_events.size (); ++i)
575 {
576 const auto &event = time_sig_events[i];
577 const int numerator = event.numerator;
578 const int denominator = event.denominator;
579 const double quarters_per_bar = numerator * (4.0 / denominator);
580 const int64_t ticks_per_bar =
581 static_cast<int64_t> (quarters_per_bar * get_ppq ());
582
583 // Determine bars covered by this time signature
584 auto bars_in_this_sig = units::ticks (0);
585 if (i < time_sig_events.size () - 1)
586 {
587 const units::tick_t next_tick = time_sig_events[i + 1].tick;
588 bars_in_this_sig = (next_tick - event.tick) / ticks_per_bar;
589 }
590 else
591 {
592 bars_in_this_sig = units::ticks (pos.bar - current_bar + 1);
593 }
594
595 // Check if position falls in this time signature segment
596 if (pos.bar < current_bar + bars_in_this_sig.in (units::ticks))
597 {
598 const int bar_in_seg = pos.bar - current_bar;
599 const auto bar_ticks =
600 event.tick + units::ticks (bar_in_seg * ticks_per_bar);
601 const int64_t ticks_per_beat = ticks_per_bar / numerator;
602
603 // Add beat and sub-beat components
604 return bar_ticks
605 + units::ticks (
606 static_cast<int64_t> (pos.beat - 1) * ticks_per_beat)
607 + units::ticks (
608 static_cast<int64_t> (pos.sixteenth - 1)
609 * ticks_per_sixteenth_.in (units::ticks))
610 + units::ticks (pos.tick);
611 }
612
613 // Move to next time signature segment
614 cumulative_ticks += bars_in_this_sig * ticks_per_bar;
615 current_bar += bars_in_this_sig.in (units::ticks);
616 }
617
618 return cumulative_ticks;
619 }
620
622 static consteval int get_ppq () { return from_nttp (PPQ).in (units::ticks); }
623
625 double get_sample_rate () const
626 {
627 return sample_rate_.in (units::sample_rate);
628 }
629
630 void set_default_bpm (double bpm) { default_tempo_.bpm = bpm; }
631 void set_default_time_signature (int numerator, int denominator)
632 {
633 default_time_sig_.numerator = numerator;
634 default_time_sig_.denominator = denominator;
635 }
636
637private:
639 const std::vector<TempoEvent> &get_tempo_events () const { return events_; }
640
642 const std::vector<TimeSignatureEvent> &get_time_signature_events () const
643 {
644 return time_sig_events_;
645 }
646
648 void rebuild_cumulative_times ()
649 {
650 if (events_.empty ())
651 return;
652
653 // Sort events by tick
654 std::ranges::sort (events_, {}, &TempoEvent::tick);
655
656 cumulative_seconds_.resize (events_.size ());
657 cumulative_seconds_[0] = units::seconds (0.0);
658
659 // Compute cumulative time at each event point
660 for (size_t i = 0; i < events_.size () - 1; ++i)
661 {
662 const units::tick_t segmentTicks = events_[i + 1].tick - events_[i].tick;
663 cumulative_seconds_[i + 1] =
664 cumulative_seconds_[i]
665 + compute_segment_time (events_[i], events_[i + 1], segmentTicks);
666 }
667 }
668
670 units::precise_second_t compute_segment_time (
671 const TempoEvent &start,
672 const TempoEvent &end,
673 units::tick_t segmentTicks) const
674 {
675 if (start.curve == CurveType::Constant)
676 {
677 return units::seconds (
678 (static_cast<units::precise_tick_t> (segmentTicks).in (units::ticks)
679 / static_cast<double> (get_ppq ()))
680 * (60.0 / start.bpm));
681 }
682 if (start.curve == CurveType::Linear)
683 {
684 const double bpm0 = start.bpm;
685 const double bpm1 = end.bpm;
686
687 if (std::abs (bpm1 - bpm0) < 1e-5)
688 {
689 return units::seconds (
690 (static_cast<units::precise_tick_t> (segmentTicks).in (units::ticks)
691 / static_cast<double> (get_ppq ()))
692 * (60.0 / bpm0));
693 }
694
695 return units::seconds (
696 (60.0
697 * static_cast<units::precise_tick_t> (segmentTicks).in (units::ticks))
698 / (get_ppq () * (bpm1 - bpm0)) * std::log (bpm1 / bpm0));
699 }
700 return units::seconds (0.0);
701 }
702
703 auto get_events_or_default () const
704 {
705 if (events_.empty ())
706 {
707 return std::make_pair (
708 std::span{ &default_tempo_, 1 },
709 std::span{ &DEFAULT_CUMULATIVE_SECONDS, 1 });
710 }
711
712 return std::make_pair (
713 std::span{ events_.data (), events_.size () },
714 std::span{ cumulative_seconds_.data (), cumulative_seconds_.size () });
715 }
716 auto get_time_signature_events_or_default () const
717 {
718 if (time_sig_events_.empty ())
719 {
720 return std::span{ &default_time_sig_, 1 };
721 }
722
723 return std::span{ time_sig_events_.data (), time_sig_events_.size () };
724 }
725
727 void clear_tempo_events ()
728 {
729 events_.clear ();
730 cumulative_seconds_.clear ();
731 }
732
734 void clear_time_signature_events () { time_sig_events_.clear (); }
735
736 static constexpr std::string_view kTempoChangesKey = "tempoChanges";
737 static constexpr std::string_view kTimeSignaturesKey = "timeSignatures";
738 friend void to_json (nlohmann::json &j, const FixedPpqTempoMap &tempo_map);
739 friend void from_json (const nlohmann::json &j, FixedPpqTempoMap &tempo_map);
740
741private:
742 units::precise_sample_rate_t sample_rate_;
743 static constexpr auto ticks_per_sixteenth_ =
744 from_nttp (PPQ) / 4;
745
746 static constexpr auto DEFAULT_BPM_EVENT = TempoEvent{
747 units::ticks (0), 120.0, CurveType::Constant
748 };
749 static constexpr auto DEFAULT_TIME_SIG_EVENT =
750 TimeSignatureEvent{ units::ticks (0), 4, 4 };
751 static constexpr auto DEFAULT_CUMULATIVE_SECONDS = units::seconds (0.0);
752
753 // Default tempo and time signature to be used when no events are present
754 TempoEvent default_tempo_{ DEFAULT_BPM_EVENT };
755 TimeSignatureEvent default_time_sig_{ DEFAULT_TIME_SIG_EVENT };
756
757 std::vector<TempoEvent> events_;
758 std::vector<TimeSignatureEvent> time_sig_events_;
759 std::vector<units::precise_second_t>
760 cumulative_seconds_;
761};
762
766using TempoMap = FixedPpqTempoMap<units::PPQ>;
767
768extern template class FixedPpqTempoMap<units::PPQ>;
769}
Manages tempo and time signature events for a DAW timeline.
Definition tempo_map.h:42
void remove_tempo_event(units::tick_t tick)
Remove a tempo event at the specified tick.
Definition tempo_map.h:162
TimeSignatureEvent time_signature_at_tick(units::tick_t tick) const
Get the time signature event active at the given tick.
Definition tempo_map.h:399
void add_time_signature_event(units::tick_t tick, int numerator, int denominator)
Add a time signature event.
Definition tempo_map.h:193
CurveType
Tempo curve type (constant or linear ramp).
Definition tempo_map.h:48
void add_tempo_event(units::tick_t tick, double bpm, CurveType curve)
Add a tempo event.
Definition tempo_map.h:135
FixedPpqTempoMap(units::precise_sample_rate_t sampleRate)
Construct a new FixedPpqTempoMap object.
Definition tempo_map.h:113
auto tick_to_seconds(units::precise_tick_t tick) const -> units::precise_second_t
Convert fractional ticks to seconds.
Definition tempo_map.h:240
double tempo_at_tick(units::tick_t tick) const
Get the tempo event active at the given tick.
Definition tempo_map.h:422
units::precise_tick_t samples_to_tick(units::precise_sample_t samples) const
Convert samples to fractional ticks.
Definition tempo_map.h:388
MusicalPosition tick_to_musical_position(units::tick_t tick) const
Convert ticks to musical position (bar:beat:sixteenth:tick).
Definition tempo_map.h:460
void remove_time_signature_event(units::tick_t tick)
Remove a time signature event at the specified tick.
Definition tempo_map.h:223
units::precise_tick_t seconds_to_tick(units::precise_second_t seconds) const
Convert seconds to fractional ticks.
Definition tempo_map.h:321
static consteval int get_ppq()
Get pulses per quarter note.
Definition tempo_map.h:622
units::tick_t musical_position_to_tick(const MusicalPosition &pos) const
Convert musical position to ticks.
Definition tempo_map.h:560
units::precise_sample_t tick_to_samples(units::precise_tick_t tick) const
Convert fractional ticks to samples.
Definition tempo_map.h:310
double get_sample_rate() const
Get current sample rate.
Definition tempo_map.h:625
void set_sample_rate(units::precise_sample_rate_t sampleRate)
Set the sample rate.
Definition tempo_map.h:119
Musical position representation.
Definition tempo_map.h:98
int tick
Ticks in sixteenth (0-indexed).
Definition tempo_map.h:102
int sixteenth
Sixteenth in beat (1-indexed).
Definition tempo_map.h:101
CurveType curve
Curve type from this event to the next.
Definition tempo_map.h:58
units::tick_t tick
Position in ticks.
Definition tempo_map.h:56
Time signature event definition.
Definition tempo_map.h:66
units::tick_t tick
Position in ticks.
Definition tempo_map.h:67