From 52c7d6ab77cf41e37e08621af8c2a2593d5e6bb3 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 1 Jan 2026 19:17:05 -0700 Subject: [PATCH] temporal: customize the implementation of get_tempo_and_meter for BBT_Time We must not walk past a MusicTimePoint if the reftime is a BBT_Argument. May try to fold this back into the fully-templated version in a subsequent commit. --- libs/temporal/tempo.cc | 106 +++++++++++++++++++++++++++++++++ libs/temporal/temporal/tempo.h | 34 ++--------- 2 files changed, 110 insertions(+), 30 deletions(-) diff --git a/libs/temporal/tempo.cc b/libs/temporal/tempo.cc index aabd480c84..34f950061a 100644 --- a/libs/temporal/tempo.cc +++ b/libs/temporal/tempo.cc @@ -2759,6 +2759,112 @@ TempoMap::_get_tempo_and_meter (typename const_traits_t::tempo_point_type & tp, return last_used; } +Points::const_iterator +TempoMap::get_tempo_and_meter_bbt (TempoPoint const *& t, MeterPoint const *& m, BBT_Argument const & bbt, bool can_match, bool ret_iterator_after_not_at) const +{ + TEMPO_MAP_ASSERT (!_tempos.empty()); + TEMPO_MAP_ASSERT (!_meters.empty()); + TEMPO_MAP_ASSERT (!_points.empty()); + + Points::const_iterator p; + Points::const_iterator last_used = _points.end(); + bool tempo_done = false; + bool meter_done = false; + + /* Walk the bartimes list and find the place to start looking for the + * relevant tempo & meter + */ + + if (_bartimes.empty() || bbt.reference() == 0) { + p = _points.begin(); + } else { + MusicTimes::const_iterator mtp; + + for (mtp = _bartimes.begin(); mtp != _bartimes.end() && mtp->sclock() < bbt.reference(); ++mtp); + + if (mtp != _bartimes.end()) { + p = _points.s_iterator_to (*(static_cast (&(*mtp)))); + } else { + p = _points.s_iterator_to (*(static_cast (&_bartimes.back()))); + } + } + + /* If the starting position is the beginning of the timeline (indicated + * by the default constructor value for the time_type (superclock_t, + * Beats, BBT_Time), then we are always allowed to use the tempo & + * meter at that position. + * + * Without this, it would be necessary to special case "can_match" in + * the caller if the start is "zero". Instead we do that here, since + * common cases (e.g. ::get_grid()) will use can_match = false, but may + * pass in a zero start point. + */ + + can_match = (can_match || bbt == BBT_Time()); + + /* Set return tempo and meter points by value using the starting tempo + * and meter passed in. + * + * Then advance through all points, resetting either tempo and/or meter + * until we find a point beyond (or equal to, if @p can_match is + * true) the @p arg (end time) + */ + + for (; p != _points.end(); ++p) { + + TempoPoint const * tpp; + MeterPoint const * mpp; + + if (dynamic_cast (&(*p)) != 0) { + if (p->sclock() != bbt.reference()) { + break; + } + } + + if (!tempo_done && (tpp = dynamic_cast (&(*p))) != 0) { + if ((can_match && ((*p).bbt() > bbt)) || (!can_match && ((*p).bbt() >= bbt))) { + tempo_done = true; + } else { + t = tpp; + last_used = p; + } + } + + if (!meter_done && (mpp = dynamic_cast (&(*p))) != 0) { + if ((can_match && ((*p).bbt() > bbt)) || (!can_match && ((*p).bbt() >= bbt))) { + meter_done = true; + } else { + m = mpp; + last_used = p; + } + } + + if (meter_done && tempo_done) { + break; + } + } + + if (!t || !m) { + return _points.end(); + } + + if (ret_iterator_after_not_at) { + + p = last_used; + + if (can_match) { + while ((p != _points.end()) && ((*p).bbt() <= bbt)) ++p; + } else { + while ((p != _points.end()) && ((*p).bbt() < bbt)) ++p; + } + + return p; + } + + return last_used; +} + + Points::const_iterator TempoMap::get_grid (TempoMapPoints& ret, superclock_t rstart, superclock_t end, uint32_t bar_mod, uint32_t beat_div) const { diff --git a/libs/temporal/temporal/tempo.h b/libs/temporal/temporal/tempo.h index 22164c32e1..85ff033693 100644 --- a/libs/temporal/temporal/tempo.h +++ b/libs/temporal/temporal/tempo.h @@ -1163,38 +1163,12 @@ class /*LIBTEMPORAL_API*/ TempoMap : public PBD::StatefulDestructible if (_tempos.size() == 1 && _meters.size() == 1) { t = &_tempos.front(); m = &_meters.front(); return _points.end(); } return _get_tempo_and_meter > (t, m, &Point::beats, b, _points.begin(), _points.end(), &_tempos.front(), &_meters.front(), can_match, ret_iterator_after_not_at); } + + Points::const_iterator get_tempo_and_meter_bbt (TempoPoint const *& t, MeterPoint const *& m, BBT_Argument const & bbt, bool can_match, bool ret_iterator_after_not_at) const; + Points::const_iterator get_tempo_and_meter (TempoPoint const *& t, MeterPoint const *& m, BBT_Argument const & bbt, bool can_match, bool ret_iterator_after_not_at) const { - if (_tempos.size() == 1 && _meters.size() == 1) { t = &_tempos.front(); m = &_meters.front(); return _points.end(); } - - /* Skip through the tempo map to find the tempo and meter in - * effect at the bbt's "reference" time, and use them as the - * starting point for the normal operation of - * _get_tempo_and_meter () - */ - - Tempos::const_iterator tp = _tempos.begin(); - Meters::const_iterator mp = _meters.begin(); - superclock_t ref = bbt.reference(); - - if (ref != 0) { - while (tp != _tempos.end()) { - Tempos::const_iterator nxt = tp; ++nxt; - if (nxt == _tempos.end() || nxt->sclock() > ref) { - break; - } - tp = nxt; - } - while (mp != _meters.end()) { - Meters::const_iterator nxt = mp; ++nxt; - if (nxt == _meters.end() || mp->sclock() > ref) { - break; - } - mp = nxt; - } - } - - return _get_tempo_and_meter > (t, m, &Point::bbt, bbt, _points.begin(), _points.end(), &(*tp), &(*mp), can_match, ret_iterator_after_not_at); + return get_tempo_and_meter_bbt (t, m, bbt, can_match, ret_iterator_after_not_at); } /* This is private, and should not be callable from outside the map