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