From 696e05c3e50f29658382652fcf2eb4a15051bae2 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 21 Jun 2022 12:26:09 -0600 Subject: [PATCH] convert internal implementation of Temporal::Beats to just use a 64 bit tick value This expands significantly the maximum number of Beats that can be represented, which is a good thing in itself. It slightly speeds up some Beats::operator methods, and slightly slows down ::get_beats() and ::get_ticks(). One minor change in an API user was required, and several tweaks to the unit tests due to the macros being used by cppunit creating possible type confusion. Units test pass --- libs/temporal/tempo.cc | 2 +- libs/temporal/temporal/beats.h | 111 +++++++++------------------------ libs/temporal/test/BeatTest.cc | 34 +++++----- 3 files changed, 46 insertions(+), 101 deletions(-) diff --git a/libs/temporal/tempo.cc b/libs/temporal/tempo.cc index ec6b3eca2a..66a49853b2 100644 --- a/libs/temporal/tempo.cc +++ b/libs/temporal/tempo.cc @@ -662,7 +662,7 @@ TempoMetric::bbt_at (timepos_t const & pos) const the current meter, which we'll call "grid" */ - const int64_t note_value_count = int_div_round (dq.get_beats() * _meter->note_value(), 4); + const int64_t note_value_count = int_div_round (dq.get_beats() * _meter->note_value(), int64_t (4)); /* now construct a BBT_Offset using the count in grid units */ diff --git a/libs/temporal/temporal/beats.h b/libs/temporal/temporal/beats.h index 9eee691e07..030be30a65 100644 --- a/libs/temporal/temporal/beats.h +++ b/libs/temporal/temporal/beats.h @@ -66,45 +66,11 @@ class /*LIBTEMPORAL_API*/ Beats { public: LIBTEMPORAL_API static const int32_t PPQN = Temporal::ticks_per_beat; - Beats() : _beats(0), _ticks(0) {} - Beats(const Beats& other) : _beats(other._beats), _ticks(other._ticks) {} - - /** Normalize so ticks is within PPQN. */ - void normalize() { - // First, fix negative ticks with positive beats - while (_beats > 0 && _ticks < 0) { - --_beats; - _ticks += PPQN; - } - - // Now fix positive ticks with negative beats - while (_beats < 0 && _ticks > 0) { - ++_beats; - _ticks -= PPQN; - } - - assert ((_beats < 0 && _ticks <= 0) || (_beats > 0 && _ticks >= 0) || _beats == 0); - - // Work with positive beats and ticks to normalize - const int32_t sign = _beats < 0 ? -1 : _ticks < 0 ? -1 : 1; - int32_t beats = ::abs(_beats); - int32_t ticks = ::abs(_ticks); - - // Fix ticks greater than 1 beat - while (ticks >= PPQN) { - ++beats; - ticks -= PPQN; - } - - // Set fields with appropriate sign - _beats = sign * beats; - _ticks = sign * ticks; - } + Beats() : _ticks(0) {} + Beats(const Beats& other) : _ticks(other._ticks) {} /** Create from a precise beats:ticks pair. */ - explicit Beats(int32_t b, int32_t t) : _beats(b), _ticks(t) { - normalize(); - } + explicit Beats(int32_t b, int32_t t) : _ticks ((b*PPQN) + t) {} /** Create from a real number of beats. */ static Beats from_double (double beats) { @@ -120,8 +86,7 @@ public: /** Create from ticks at the standard PPQN. */ static Beats ticks(int64_t ticks) { - assert (ticks/PPQN < std::numeric_limits::max()); - return Beats (ticks / PPQN, ticks % PPQN); + return Beats (0, ticks); } /** Create from ticks at a given rate. @@ -135,25 +100,18 @@ public: return Beats(ticks / ppqn, (ticks % ppqn) * PPQN / ppqn); } - static int64_t make_ticks (Beats const & b) { return b.get_beats() * ticks_per_beat + b.get_ticks(); } + int64_t to_ticks () const { return _ticks; } + int64_t to_ticks (uint32_t ppqn) const { return (_ticks * ppqn) / PPQN; } - int64_t to_ticks() const { return (int64_t)_beats * PPQN + _ticks; } - int64_t to_ticks(uint32_t ppqn) const { return (int64_t)_beats * ppqn + (_ticks * ppqn / PPQN); } - - int32_t get_beats() const { return _beats; } - int32_t get_ticks() const { return _ticks; } + int64_t get_beats () const { return _ticks / PPQN; } + int32_t get_ticks () const { return _ticks % PPQN; } Beats& operator=(double time) { - double whole; - const double frac = modf(time, &whole); - - _beats = whole; - _ticks = frac * PPQN; + *this = from_double (time); return *this; } Beats& operator=(const Beats& other) { - _beats = other._beats; _ticks = other._ticks; return *this; } @@ -170,32 +128,32 @@ public: } Beats round_to_beat() const { - return (_ticks >= (PPQN/2)) ? Beats (_beats + 1, 0) : Beats (_beats, 0); + return (get_ticks() >= (PPQN/2)) ? Beats (get_beats() + 1, 0) : Beats (get_beats(), 0); } Beats round_up_to_beat() const { - return (_ticks == 0) ? *this : Beats(_beats + 1, 0); + return (get_ticks() == 0) ? *this : Beats(get_beats() + 1, 0); } Beats round_down_to_beat() const { - return Beats(_beats, 0); + return Beats(get_beats(), 0); } Beats prev_beat() const { /* always moves backwards even if currently on beat */ - return Beats (_beats-1, 0); + return Beats (get_beats()-1, 0); } Beats next_beat() const { /* always moves forwards even if currently on beat */ - return Beats (_beats+1, 0); + return Beats (get_beats()+1, 0); } LIBTEMPORAL_API Beats round_to_subdivision (int subdivision, RoundMode dir) const; Beats abs () const { - return Beats (::abs (_beats), ::abs (_ticks)); + return ticks (::abs (_ticks)); } Beats diff (Beats const & other) const { @@ -206,11 +164,11 @@ public: } inline bool operator==(const Beats& b) const { - return _beats == b._beats && _ticks == b._ticks; + return _ticks == b._ticks; } inline bool operator==(int beats) const { - return _beats == beats; + return get_beats() == beats; } inline bool operator!=(const Beats& b) const { @@ -218,38 +176,31 @@ public: } inline bool operator<(const Beats& b) const { - return _beats < b._beats || (_beats == b._beats && _ticks < b._ticks); + return _ticks < b._ticks; } inline bool operator<=(const Beats& b) const { - return _beats < b._beats || (_beats == b._beats && _ticks <= b._ticks); + return _ticks <= b._ticks; } inline bool operator>(const Beats& b) const { - return _beats > b._beats || (_beats == b._beats && _ticks > b._ticks); + return _ticks > b._ticks; } inline bool operator>=(const Beats& b) const { - return _beats > b._beats || (_beats == b._beats && _ticks >= b._ticks); + return _ticks >= b._ticks; } Beats operator+(const Beats& b) const { - return Beats(_beats + b._beats, _ticks + b._ticks); + return ticks (_ticks + b._ticks); } Beats operator-(const Beats& b) const { - return Beats(_beats - b._beats, _ticks - b._ticks); + return ticks (_ticks - b._ticks); } Beats operator-() const { - /* must avoid normalization here, which will convert a negative - value into a valid beat position before zero, which is not - we want here. - */ - Beats b (_beats, _ticks); - b._beats = -b._beats; - b._ticks = -b._ticks; - return b; + return ticks (-_ticks); } Beats operator*(int32_t factor) const {return ticks (to_ticks() * factor); } @@ -261,7 +212,6 @@ public: Beats operator%= (Beats const & b) { const Beats B (Beats::ticks (to_ticks() % b.to_ticks())); - _beats = B._beats; _ticks = B._ticks; return *this; } @@ -275,27 +225,22 @@ public: } Beats& operator+=(const Beats& b) { - _beats += b._beats; _ticks += b._ticks; - normalize(); return *this; } Beats& operator-=(const Beats& b) { - _beats -= b._beats; _ticks -= b._ticks; - normalize(); return *this; } - bool operator!() const { return _beats == 0 && _ticks == 0; } - explicit operator bool () const { return _beats != 0 || _ticks != 0; } + bool operator!() const { return _ticks == 0; } + explicit operator bool () const { return _ticks != 0; } static Beats one_tick() { return Beats(0, 1); } protected: - int32_t _beats; - int32_t _ticks; + int64_t _ticks; }; @@ -307,7 +252,7 @@ class DoubleableBeats : public Beats { public: DoubleableBeats (Beats const & b) : Beats (b) {} - double to_double() const { return (double)_beats + (_ticks / (double)PPQN); } + double to_double() const { return (double)get_beats() + (get_ticks() / (double)PPQN); } }; diff --git a/libs/temporal/test/BeatTest.cc b/libs/temporal/test/BeatTest.cc index f67b704b3a..30e716443a 100644 --- a/libs/temporal/test/BeatTest.cc +++ b/libs/temporal/test/BeatTest.cc @@ -14,21 +14,21 @@ void BeatsTest::createTest() { const Beats a(1, 2); - CPPUNIT_ASSERT_EQUAL(1, a.get_beats()); + CPPUNIT_ASSERT_EQUAL(int64_t(1), a.get_beats()); CPPUNIT_ASSERT_EQUAL(2, a.get_ticks()); CPPUNIT_ASSERT_DOUBLES_EQUAL(1 + 2 / (double)Beats::PPQN, DoubleableBeats(a).to_double(), delta); const Beats b = Beats::from_double (1.5); - CPPUNIT_ASSERT_EQUAL(1, b.get_beats()); + CPPUNIT_ASSERT_EQUAL(int64_t(1), b.get_beats()); CPPUNIT_ASSERT_EQUAL(Beats::PPQN / 2, b.get_ticks()); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, DoubleableBeats (b).to_double(), delta); const Beats c = Beats::beats(6); - CPPUNIT_ASSERT_EQUAL(6, c.get_beats()); + CPPUNIT_ASSERT_EQUAL(int64_t(6), c.get_beats()); CPPUNIT_ASSERT_EQUAL(0, c.get_ticks()); const Beats d = Beats::ticks(7); - CPPUNIT_ASSERT_EQUAL(0, d.get_beats()); + CPPUNIT_ASSERT_EQUAL(int64_t(0), d.get_beats()); CPPUNIT_ASSERT_EQUAL(7, d.get_ticks()); Beats e(8, 9); @@ -50,7 +50,7 @@ BeatsTest::addTest() // Positive + positive const Beats c = a + b; - CPPUNIT_ASSERT_EQUAL(4, c.get_beats()); + CPPUNIT_ASSERT_EQUAL(int64_t(4), c.get_beats()); CPPUNIT_ASSERT_EQUAL(6, c.get_ticks()); const Beats n1 = Beats::from_double (-12.34); @@ -62,19 +62,19 @@ BeatsTest::addTest() // Positive + negative const Beats p1 = Beats::from_double (1.0); const Beats p_n = p1 + n1; - CPPUNIT_ASSERT_EQUAL(-11, p_n.get_beats()); + CPPUNIT_ASSERT_EQUAL(-int64_t(11), p_n.get_beats()); CPPUNIT_ASSERT_EQUAL((int32_t) rint (Beats::PPQN * -0.34), p_n.get_ticks()); CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, DoubleableBeats (p_n).to_double(), delta); // Negative + positive const Beats n_p = n1 + p1; - CPPUNIT_ASSERT_EQUAL(-11, n_p.get_beats()); + CPPUNIT_ASSERT_EQUAL(-int64_t(11), n_p.get_beats()); CPPUNIT_ASSERT_EQUAL((int32_t) rint (Beats::PPQN * -0.34), n_p.get_ticks()); CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, DoubleableBeats (n_p).to_double(), delta); // Negative + negative const Beats sum = n1 + n2; - CPPUNIT_ASSERT_EQUAL(-69, sum.get_beats()); + CPPUNIT_ASSERT_EQUAL(-int64_t(69), sum.get_beats()); //CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.12), n_p.get_ticks()); CPPUNIT_ASSERT_DOUBLES_EQUAL(-69.12, DoubleableBeats (sum).to_double(), delta); } @@ -87,7 +87,7 @@ BeatsTest::subtractTest() // Positive - positive const Beats c = b - a; - CPPUNIT_ASSERT_EQUAL(2, c.get_beats()); + CPPUNIT_ASSERT_EQUAL(int64_t(2), c.get_beats()); CPPUNIT_ASSERT_EQUAL(2, c.get_ticks()); const Beats n1 = Beats::from_double (-12.34); @@ -99,19 +99,19 @@ BeatsTest::subtractTest() // Positive - negative const Beats p1= Beats::from_double (1.0); const Beats p_n = p1 - n1; - CPPUNIT_ASSERT_EQUAL(13, p_n.get_beats()); + CPPUNIT_ASSERT_EQUAL(int64_t(13), p_n.get_beats()); CPPUNIT_ASSERT_EQUAL((int32_t) rint (Beats::PPQN * 0.34), p_n.get_ticks()); CPPUNIT_ASSERT_DOUBLES_EQUAL(13.34, DoubleableBeats (p_n).to_double(), delta); // Negative - positive const Beats n_p = n1 - p1; - CPPUNIT_ASSERT_EQUAL(-13, n_p.get_beats()); + CPPUNIT_ASSERT_EQUAL(-int64_t(13), n_p.get_beats()); CPPUNIT_ASSERT_EQUAL((int32_t) rint (Beats::PPQN * -0.34), n_p.get_ticks()); CPPUNIT_ASSERT_DOUBLES_EQUAL(-13.34, DoubleableBeats (n_p).to_double(), delta); // Negative - negative const Beats diff = n1 - n2; - CPPUNIT_ASSERT_EQUAL(44, diff.get_beats()); + CPPUNIT_ASSERT_EQUAL(int64_t(44), diff.get_beats()); CPPUNIT_ASSERT_EQUAL((int32_t)lrint(Beats::PPQN * 0.44), diff.get_ticks()); CPPUNIT_ASSERT_DOUBLES_EQUAL(44.44, DoubleableBeats (diff).to_double(), delta); } @@ -131,12 +131,12 @@ BeatsTest::roundTest() // Round a up const Beats au = a.round_up_to_beat(); - CPPUNIT_ASSERT_EQUAL(au.get_beats(), 2); + CPPUNIT_ASSERT_EQUAL(au.get_beats(), int64_t(2)); CPPUNIT_ASSERT_EQUAL(au.get_ticks(), 0); // Round a down const Beats ad = a.round_down_to_beat(); - CPPUNIT_ASSERT_EQUAL(ad.get_beats(), 1); + CPPUNIT_ASSERT_EQUAL(ad.get_beats(), int64_t (1)); CPPUNIT_ASSERT_EQUAL(ad.get_ticks(), 0); // Round result down again @@ -149,7 +149,7 @@ BeatsTest::roundTest() // Snap to 1.5 const Beats snapped = a.round_to_multiple (Beats::from_double (1.5)); - CPPUNIT_ASSERT_EQUAL(snapped.get_beats(), 1); + CPPUNIT_ASSERT_EQUAL(snapped.get_beats(), int64_t (1)); CPPUNIT_ASSERT_EQUAL(snapped.get_ticks(), Beats::PPQN / 2); } @@ -157,12 +157,12 @@ void BeatsTest::convertTest() { const Beats a = Beats::ticks_at_rate(72000, 48000); - CPPUNIT_ASSERT_DOUBLES_EQUAL(1, a.get_beats(), delta); + CPPUNIT_ASSERT_DOUBLES_EQUAL(int64_t (1), a.get_beats(), delta); CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN / 2, a.get_ticks(), delta); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, DoubleableBeats(a).to_double(), delta); const Beats b = Beats::ticks_at_rate(8, 48000); - CPPUNIT_ASSERT_DOUBLES_EQUAL(0, b.get_beats(), delta); + CPPUNIT_ASSERT_DOUBLES_EQUAL(int64_t (0), b.get_beats(), delta); CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN * 8 / 48000, b.get_ticks(), delta); CPPUNIT_ASSERT_DOUBLES_EQUAL((8 / 48000.0), DoubleableBeats (b).to_double(), delta);