/* Copyright (C) 2017 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include "pbd/compose.h" #include "pbd/convert.h" #include "pbd/enumwriter.h" #include "pbd/error.h" #include "pbd/integer_division.h" #include "pbd/failed_constructor.h" #include "pbd/stacktrace.h" #include "pbd/string_convert.h" #include "temporal/debug.h" #include "temporal/scope.h" #include "temporal/tempo.h" #include "temporal/types_convert.h" #include "pbd/i18n.h" using namespace PBD; using namespace Temporal; using std::cerr; using std::cout; using std::endl; using Temporal::superclock_t; std::string Tempo::xml_node_name = X_("Tempo"); std::string Meter::xml_node_name = X_("Meter"); SerializedRCUManager TempoMap::_map_mgr (0); thread_local TempoMap::SharedPtr TempoMap::_tempo_map_p; PBD::Signal TempoMap::MapChanged; #ifndef NDEBUG #define TEMPO_MAP_ASSERT(expr) TempoMap::map_assert(expr, #expr, __FILE__, __LINE__) #else #define TEMPO_MAP_ASSERT(expr) #endif void Point::add_state (XMLNode & node) const { node.set_property (X_("sclock"), _sclock); node.set_property (X_("quarters"), _quarters); node.set_property (X_("bbt"), _bbt); } Point::Point (TempoMap const & map, XMLNode const & node) : MapOwned (map) { if (!node.get_property (X_("sclock"), _sclock)) { throw failed_constructor(); } if (!node.get_property (X_("quarters"), _quarters)) { throw failed_constructor(); } if (!node.get_property (X_("bbt"), _bbt)) { throw failed_constructor(); } } #if 0 samplepos_t Point::sample() const { return superclock_to_samples (_sclock, _map->sample_rate()); } #endif Tempo::Tempo (XMLNode const & node) { TEMPO_MAP_ASSERT (node.name() == xml_node_name); node.get_property (X_("npm"), _npm); node.get_property (X_("enpm"), _enpm); _superclocks_per_note_type = double_npm_to_scpn (_npm); _end_superclocks_per_note_type = double_npm_to_scpn (_enpm); if (!node.get_property (X_("note-type"), _note_type)) { throw failed_constructor (); } if (!node.get_property (X_("locked-to-meter"), _locked_to_meter)) { _locked_to_meter = true; } /* older versions used "clamped" as the property name here */ if (!node.get_property (X_("continuing"), _continuing) && !node.get_property (X_("clamped"), _continuing)) { _continuing = false; } } void Tempo::set_note_types_per_minute (double npm) { _npm = npm; _superclocks_per_note_type = double_npm_to_scpn (_npm); } void Tempo::set_end_npm (double npm) { _enpm = npm; _end_superclocks_per_note_type = double_npm_to_scpn (_enpm); } void Tempo::set_continuing (bool yn) { _continuing = yn; } XMLNode& Tempo::get_state () const { XMLNode* node = new XMLNode (xml_node_name); node->set_property (X_("npm"), note_types_per_minute()); node->set_property (X_("enpm"), end_note_types_per_minute()); node->set_property (X_("note-type"), note_type()); node->set_property (X_("type"), type()); node->set_property (X_("locked-to-meter"), _locked_to_meter); node->set_property (X_("continuing"), _continuing); /* We don't have an _active property any more, but earlier versions of Ardour will crash during session loading if this property is not provided. For the 7.5 - 8.0 transition, there was theoretically no file format change, so leave this in place till at least the next format version change. */ node->set_property (X_("active"), true); return *node; } int Tempo::set_state (XMLNode const & node, int /*version*/) { if (node.name() != xml_node_name) { return -1; } node.get_property (X_("npm"), _npm); node.get_property (X_("enpm"), _enpm); _superclocks_per_note_type = double_npm_to_scpn (_npm); _end_superclocks_per_note_type = double_npm_to_scpn (_enpm); node.get_property (X_("note-type"), _note_type); if (!node.get_property (X_("locked-to-meter"), _locked_to_meter)) { _locked_to_meter = true; } /* older versions used "clamped" as the property name here */ if (!node.get_property (X_("continuing"), _continuing) && !node.get_property (X_("continuing"), _continuing)) { _continuing = false; } return 0; } Meter::Meter (XMLNode const & node) { TEMPO_MAP_ASSERT (node.name() == xml_node_name); if (!node.get_property (X_("note-value"), _note_value)) { throw failed_constructor (); } if (!node.get_property (X_("divisions-per-bar"), _divisions_per_bar)) { throw failed_constructor (); } } XMLNode& Meter::get_state () const { XMLNode* node = new XMLNode (xml_node_name); node->set_property (X_("note-value"), note_value()); node->set_property (X_("divisions-per-bar"), divisions_per_bar()); return *node; } int Meter::set_state (XMLNode const & node, int /* version */) { if (node.name() != xml_node_name) { return -1; } node.get_property (X_("note-value"), _note_value); node.get_property (X_("divisions-per-bar"), _divisions_per_bar); return 0; } Temporal::BBT_Time Meter::bbt_add (Temporal::BBT_Time const & bbt, Temporal::BBT_Offset const & add) const { int32_t bars = bbt.bars; int32_t beats = bbt.beats; int32_t ticks = bbt.ticks; if ((bars ^ add.bars) < 0) { /* signed-ness varies */ if (abs(add.bars) >= abs(bars)) { /* addition will change which side of "zero" the answer is on; adjust bbt.bars towards zero to deal with "unusual" BBT math */ if (bars < 0) { bars++; } else { bars--; } } } if ((beats ^ add.beats) < 0) { /* signed-ness varies */ if (abs (add.beats) >= abs (beats)) { /* adjust bbt.beats towards zero to deal with "unusual" BBT math */ if (beats < 0) { beats++; } else { beats--; } } } Temporal::BBT_Offset r (bars + add.bars, beats + add.beats, ticks + add.ticks); /* ticks-per-bar-division; PPQN is ticks-per-quarter note */ const int32_t tpg = ticks_per_grid (); if (r.ticks >= tpg) { /* ticks per bar */ const int32_t tpB = tpg * _divisions_per_bar; if (r.ticks >= tpB) { r.bars += r.ticks / tpB; r.ticks %= tpB; } if (r.ticks >= tpg) { r.beats += r.ticks / tpg; r.ticks %= tpg; } } if (r.beats > _divisions_per_bar) { /* adjust to zero-based math, since that's what C++ operators expect */ r.beats -= 1; r.bars += r.beats / _divisions_per_bar; r.beats %= _divisions_per_bar; /* adjust back */ r.beats += 1; } if (r.bars == 0) { r.bars = 1; } return Temporal::BBT_Time (r.bars, r.beats, r.ticks); } Temporal::BBT_Time Meter::bbt_subtract (Temporal::BBT_Time const & bbt, Temporal::BBT_Offset const & sub) const { int32_t bars = bbt.bars; int32_t beats = bbt.beats; int32_t ticks = bbt.ticks; if ((bars ^ sub.bars) < 0) { /* signed-ness varies */ if (abs (sub.bars) >= abs (bars)) { /* adjust bbt.bars towards zero to deal with "unusual" BBT math */ if (bars < 0) { bars++; } else { bars--; } } } if ((beats ^ sub.beats) < 0) { /* signed-ness varies */ if (abs (sub.beats) >= abs (beats)) { /* adjust bbt.beats towards zero to deal with "unusual" BBT math */ if (beats < 0) { beats++; } else { beats--; } } } Temporal::BBT_Offset r (bars - sub.bars, beats - sub.beats, ticks - sub.ticks); /* ticks-per-bar-division; PPQN is ticks-per-quarter note */ const int32_t tpg = ticks_per_grid (); if (r.ticks < 0) { r.beats += floor ((double) r.ticks / tpg); r.ticks = tpg + (r.ticks % Temporal::Beats::PPQN); } if (r.beats <= 0) { r.bars += floor ((r.beats - 1.0) / _divisions_per_bar); r.beats = _divisions_per_bar + (r.beats % _divisions_per_bar); } if (r.bars <= 0) { r.bars -= 1; } return Temporal::BBT_Time (r.bars, r.beats, r.ticks); } /* Compute BBT_offset between two BBT_Times (assumed to have the same reference * point), The Meter object this is called upon MUST cover both BBT_Times, * otherwise the return value is undefined. */ Temporal::BBT_Offset Meter::bbt_delta (BBT_Time const & later, BBT_Time const & earlier) const { if (later == earlier) { return BBT_Offset(); } assert (later > earlier); BBT_Offset d; BBT_Time a (earlier); BBT_Time b (later); if (a.ticks > b.ticks) { d.ticks = b.ticks + (ticks_per_grid() - a.ticks); if (a.beats == _divisions_per_bar) { a.beats = 1; a.bars++; } else { a.beats++; } } else { d.ticks = b.ticks - a.ticks; } if (a.beats > b.beats) { d.beats = b.beats + (_divisions_per_bar - a.beats); a.bars++; } else { d.beats = b.beats - a.beats; } d.bars = b.bars - a.bars; return d; } Temporal::Beats Meter::round_to_beat (Temporal::Beats const & b) const { return b.round_to_multiple (Beats::ticks (ticks_per_grid())); } Temporal::BBT_Time Meter::round_to_beat (Temporal::BBT_Time const & bbt) const { if (bbt.ticks == 0) { return bbt; } BBT_Time b (bbt); if (bbt.ticks >= ticks_per_grid()) { /* Round up */ b.ticks = 0; b.beats += 1; if (b.beats > _divisions_per_bar) { b.beats = 0; b.bars++; } } else { /* Round down */ b.ticks = 0; } return b; } Temporal::BBT_Time Meter::round_to_bar (Temporal::BBT_Time const & bbt) const { Beats b (bbt.beats, bbt.ticks); Beats half (Beats::ticks (Beats::PPQN + ((_divisions_per_bar * Beats::PPQN / 2)))); if (b >= half) { return BBT_Time (bbt.bars+1, 1, 0); } return BBT_Time (bbt.bars, 1, 0); } Temporal::BBT_Time Meter::round_up_to_bar (Temporal::BBT_Time const & bbt) const { if (bbt.beats == 1 && bbt.ticks == 0) { /* on bar, do not round up */ return bbt; } return BBT_Time (bbt.bars+1, 1, 0); } Temporal::BBT_Time Meter::round_up_to_beat_div (Temporal::BBT_Time const & bbt, int beat_div) const { const int32_t div_ticks = ticks_per_grid() / beat_div; int32_t rounded_up = bbt.ticks + div_ticks - 1; rounded_up -= rounded_up % div_ticks; if (rounded_up == ticks_per_grid()) { /* round up next beat count, which may be in next bar */ if (bbt.beats + 1 > _divisions_per_bar) { return BBT_Time (bbt.bars+1, 1, 0); } else { return BBT_Time (bbt.bars, bbt.beats+1, 0); } } return BBT_Time (bbt.bars, bbt.beats, rounded_up); } Temporal::Beats Meter::to_quarters (Temporal::BBT_Offset const & offset) const { int64_t ticks = (Beats::PPQN * offset.bars * _divisions_per_bar * 4) / _note_value; ticks += (Beats::PPQN * offset.beats * 4) / _note_value; /* "parts per bar division" */ const int tpg = ticks_per_grid (); if (offset.ticks > tpg) { ticks += Beats::PPQN * offset.ticks / tpg; ticks += offset.ticks % tpg; } else { ticks += offset.ticks; } return Beats (ticks/Beats::PPQN, ticks%Beats::PPQN); } int TempoPoint::set_state (XMLNode const & node, int version) { int ret; if ((ret = Tempo::set_state (node, version)) == 0) { if (node.get_property (X_("omega"), _omega)) { if (node.get_property (X_("omega"), _omega)) { /* Older versions only defined a single omega value */ /* ???? */ } } } return ret; } XMLNode& TempoPoint::get_state () const { XMLNode& base (Tempo::get_state()); Point::add_state (base); base.set_property (X_("omega"), _omega); return base; } TempoPoint::TempoPoint (TempoMap const & map, XMLNode const & node) : Point (map, node) , Tempo (node) , _omega (0.) { if (node.get_property (X_("omega"), _omega)) { /* Older versions only defined a single omega value */ if (node.get_property (X_("omega"), _omega)) { /* ???? */ } } } void TempoPoint::set_omega (double ob) { _omega = ob; } /* To understand the math(s) behind ramping, see the file doc/tempo.{pdf,tex} */ void TempoPoint::compute_omega_from_next_tempo (TempoPoint const & next) { compute_omega_from_distance_and_next_tempo (next.beats() - beats(), next); } void TempoPoint::compute_omega_from_distance_and_next_tempo (Beats const & quarter_duration, TempoPoint const & next) { superclock_t end_scpqn; if (!_continuing) { /* tempo is defined by our own start and end */ end_scpqn = end_superclocks_per_quarter_note(); } else { /* tempo is defined by our own start the start of the next tempo */ end_scpqn = next.superclocks_per_quarter_note (); } if (superclocks_per_quarter_note () == end_scpqn) { _omega = 0.0; return; } compute_omega_from_quarter_duration (quarter_duration, end_scpqn); } void TempoPoint::compute_omega_from_quarter_duration (Beats const & quarter_duration, superclock_t end_scpqn) { const double old = _omega; if (!std::isfinite (_omega = ((1.0/end_scpqn) - (1.0/superclocks_per_quarter_note())) / DoubleableBeats (quarter_duration).to_double())) { DEBUG_TRACE (DEBUG::TemporalMap, "quarter-computed omega out of bounds\n"); _omega = old; } DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("quarter-computed omega from qtr duration = %1 dur was %2 start speed %3 end speed [%4]\n", _omega, quarter_duration.str(), superclocks_per_quarter_note(), end_scpqn)); } superclock_t TempoPoint::superclock_at (Temporal::Beats const & qn) const { if (qn == _quarters) { return _sclock; } if (qn < Beats()) { /* negative */ TEMPO_MAP_ASSERT (_quarters == Beats()); } else { /* positive */ if (qn < _quarters) { std::cerr << "\n\nLOGIC FAIL sc @ " << qn << " using " << *this << std::endl; PBD::stacktrace (std::cerr, 19); } TEMPO_MAP_ASSERT (qn >= _quarters); } superclock_t r; if (!actually_ramped()) { /* not ramped, use linear */ const Beats delta = qn - _quarters; const superclock_t spqn = superclocks_per_quarter_note (); r = _sclock + (spqn * delta.get_beats()) + muldiv_round (spqn, delta.get_ticks(), superclock_t (Temporal::ticks_per_beat)); } else { const double log_expr = superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double(); // std::cerr << "logexpr " << log_expr << " from " << superclocks_per_quarter_note() << " * " << _omega << " * " << (qn - _quarters) << std::endl; if (log_expr < -1.) { /* The overwhelmingly likely reason for arriving here * is using the wrong TempoMetric to compute * superclocks at a BBT time. The omega value is only * valid for the range over which a ramp was computed, * so if the TempoPoint is "too early" and being used * to compute superclocks_at() a BBT time that is * further away than the ramp was long, we will end up * here, despite the math being correct. * * So before revisiting all the math here (which has * been checked many times), go back and investigate * the TempoMetric being used, and how it was arrived * at. */ std::cerr << "CASE 1: " << *this << endl << " scpqn = " << superclocks_per_quarter_note() << std::endl; std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log (" << log_expr << ") " << " omega = " << _omega << std::endl; abort (); } else { r = _sclock + llrint (log1p (log_expr) / _omega); // std::cerr << "r = " << _sclock << " + " << log1p (log_expr) / _omega << " => " << r << std::endl; if (r < 0) { std::cerr << "CASE 2: scpqn = " << superclocks_per_quarter_note() << std::endl; std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log1p (" << superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double() << " = " << log1p (superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double()) << " => " << r << std::endl; _map->dump (std::cerr); abort (); } } } return r; } superclock_t TempoPoint::superclocks_per_note_type_at (timepos_t const &pos) const { if (!actually_ramped()) { return _superclocks_per_note_type; } return _superclocks_per_note_type * exp (-_omega * (pos.superclocks() - sclock())); } Temporal::Beats TempoPoint::quarters_at_superclock (superclock_t sc) const { /* catch a special case. The maximum superclock_t value cannot be converted into a 64 bit tick value for common tempos. Obviously, values less than this can also cause overflow, but are unlikely to be encountered. A longer term/big picture solution for this is likely required in order to deal with longer sessions. Still, even at 300bpm, a 32 bit integer should cover 165 days. The problem is that a 62 bit (int62_t) superclock counter can cover 105064 days, so the theoretical potential for errors here is real. */ if (sc >= int62_t::max) { return std::numeric_limits::max(); } if (!actually_ramped()) { /* The simple expression of the math we're computing here is * (if we could use floating point): * * double sc_delta = sc - _sclock; // how far in superclocks from this point's superclock position? * double seconds = sc_delta / superclock_ticks_per_second; // how long is that in seconds? * double note_types_per_second = npm / 60.; // how many note types in 1 second ? * double quarter_delta = note_types_per_second * (4. / * _note_type); // how many quarters is that? * return _quarters + Beats (floor (quarter_delta), fmod (quarter_delta, 1.)); // add to this point's position in quarters * * But we can't use doubles, so this gets quite a bit more complex. */ // TEMPO_MAP_ASSERT (sc >= _sclock); superclock_t sc_delta = sc - _sclock; /* convert sc into superbeats, given that sc represents some number of seconds */ const superclock_t whole_seconds = sc_delta / superclock_ticks_per_second(); const superclock_t remainder = sc_delta - (whole_seconds * superclock_ticks_per_second()); /* big number to allow most (fractional) BPMs to be represented as an integer "super note type per second" * * It is not required that big_numerator equal superclock_ticks_per_second but since the values in both cases have similar * desired properties (many, many factors), it doesn't hurt to use the same number. */ const superclock_t big_numerator = 508032000; // 2^10 * 3^4 * 5^3 * 7^2 uint64_t _super_note_type_per_second = (uint64_t) llround (_npm * big_numerator / 60); const int64_t supernotes = ((_super_note_type_per_second) * whole_seconds) + muldiv_round (superclock_t (_super_note_type_per_second), remainder, superclock_ticks_per_second()); const int64_t superbeats = muldiv_round (supernotes, 4, (superclock_t) _note_type); /* convert superbeats to beats:ticks */ int32_t b = superbeats / big_numerator; int64_t remain = superbeats - (b * big_numerator); int32_t t = PBD::muldiv_round (Temporal::ticks_per_beat, remain, big_numerator); const Beats ret = _quarters + Beats (b, t); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("%8 => \nsc %1 delta %9 = %2 secs rem = %3 rem snotes %4 sbeats = %5 => %6 : %7 + %10 = %11\n", sc, whole_seconds, remainder, supernotes, superbeats, b , t, *this, sc_delta, _quarters, ret)); /* positive superclock can never generate negative beats unless * it is too large. If that happens, handle it the same way as * the opening special case in this method. */ if (sc >= 0 && ret < Beats()) { return std::numeric_limits::max(); } return ret; } const double b = (exp (_omega * (sc - _sclock)) - 1) / (superclocks_per_quarter_note() * _omega); return _quarters + Beats::from_double (b); } MeterPoint::MeterPoint (TempoMap const & map, XMLNode const & node) : Point (map, node) , Meter (node) { } /* Given a time in BBT_Time, compute the equivalent Beat Time. * * Computation assumes that the Meter is in effect at the time specified as * BBT_Time (i.e. there is no other MeterPoint between this one and the specified * time. */ Temporal::Beats MeterPoint::quarters_at (Temporal::BBT_Time const & bbt) const { DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("%1 computing quarters for bbt %2\n", *this, bbt)); BBT_Offset offset (bbt_delta (bbt, _bbt)); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("getting quarters at %1 from %2, offset is %3 as quarters %4 so %5\n", bbt, *this, offset, to_quarters (offset), _quarters + to_quarters (offset))); return _quarters + to_quarters (offset); } /* Given a time in Beats, compute the equivalent BBT Time. * * Computation assumes that the Meter is in effect at the time specified in * Beats (i.e. there is no other MeterPoint between this one and the specified * time. */ Temporal::BBT_Time MeterPoint::bbt_at (Temporal::Beats const & qn) const { return bbt_add (_bbt, Temporal::BBT_Offset (0, 0, (qn - _quarters).to_ticks())); } XMLNode& MeterPoint::get_state () const { XMLNode& base (Meter::get_state()); Point::add_state (base); return base; } TempoMetric::TempoMetric (TempoPoint const & t, MeterPoint const & m) : _tempo (&t) , _meter (&m) { _reftime = _tempo->map().reftime (t, m); } superclock_t TempoMap::reftime (TempoPoint const & t, MeterPoint const & m) const { Points::const_iterator pi; if (m.sclock() < t.sclock()) { pi = _points.s_iterator_to (*(static_cast (&m))); } else { pi = _points.s_iterator_to (*(static_cast (&t))); } /* Walk backwards through points to find a BBT markers, or the start */ while (pi != _points.begin()) { if (dynamic_cast (&*pi)) { break; } --pi; } return pi->sclock(); } Temporal::BBT_Argument TempoMetric::bbt_at (timepos_t const & pos) const { if (pos.is_beats()) { return bbt_at (pos.beats()); } return bbt_at_superclock (pos.superclocks()); } Temporal::BBT_Argument TempoMetric::bbt_at_superclock (superclock_t sc) const { /* Use the later of the tempo or meter as the reference point to * compute the BBT distance. All map points are fully defined by all 3 * time types, but we need the latest one to avoid incorrect * computations of quarter duration. */ const Point* reference_point; if (_tempo->beats() < _meter->beats()) { reference_point = _meter; } else { reference_point = _tempo; } const Beats dq = _tempo->quarters_at_superclock (sc) - reference_point->beats(); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("qn @ %1 = %2, meter @ %3 , delta %4 from ref %5\n", sc, _tempo->quarters_at_superclock (sc), reference_point->beats(), dq, *reference_point)); /* dq is delta in quarters (beats). Convert to delta in note types of the current meter, which we'll call "grid" */ const int64_t note_value_count = muldiv_round (dq.get_beats(), _meter->note_value(), int64_t (4)); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("which converts to %1 note value count\n", note_value_count)); /* now construct a BBT_Offset using the count in grid units */ const BBT_Offset bbt_offset (0, note_value_count, dq.get_ticks()); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("BBT offset from %1 = %2, so final BBT = %3\n", reference_point->bbt(), bbt_offset, _meter->bbt_add (reference_point->bbt(), bbt_offset))); superclock_t ref (std::min (_meter->sclock(), _tempo->sclock())); return BBT_Argument (ref, _meter->bbt_add (reference_point->bbt(), bbt_offset)); } superclock_t TempoMetric::superclock_at (BBT_Time const & bbt) const { DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("get quarters for %1 = %2 using %3\n", bbt, _meter->quarters_at (bbt), *this)); return _tempo->superclock_at (_meter->quarters_at (bbt)); } MusicTimePoint::MusicTimePoint (TempoMap const & map, XMLNode const & node) : Point (map, node) , TempoPoint (map, *node.child (Tempo::xml_node_name.c_str())) , MeterPoint (map, *node.child (Meter::xml_node_name.c_str())) { node.get_property (X_("name"), _name); /* may fail, leaves name empty */ } XMLNode& MusicTimePoint::get_state () const { XMLNode* node = new XMLNode (X_("MusicTime")); Point::add_state (*node); node->add_child_nocopy (Tempo::get_state()); node->add_child_nocopy (Meter::get_state()); node->set_property (X_("name"), _name); /* failure is OK */ return *node; } void MusicTimePoint::set_name (std::string const & str) { _name = str; /* XXX need a signal or something to announce change */ } bool GridIterator::valid_for (TempoMap const & m, superclock_t start, uint32_t bmod, uint32_t bdiv) const { if (!valid || start != end || map != &m || bar_mod != bmod || beat_div != bdiv) { return false; } return true; } /* TEMPOMAP */ TempoMap::TempoMap (Tempo const & initial_tempo, Meter const & initial_meter) : _scope_owner (nullptr) { TempoPoint* tp = new TempoPoint (*this, initial_tempo, 0, Beats(), BBT_Time()); MeterPoint* mp = new MeterPoint (*this, initial_meter, 0, Beats(), BBT_Time()); _tempos.push_back (*tp); _meters.push_back (*mp); _points.push_back (*tp); _points.push_back (*mp); } TempoMap::TempoMap (XMLNode const & node, int version) : _scope_owner (nullptr) { set_state (node, version); } TempoMap::TempoMap (TempoMap const & other) : _scope_owner (nullptr) { copy_points (other); } void TempoMap::set_scope_owner (ScopedTempoMapOwner& sco) { _scope_owner = &sco; } TempoMap::~TempoMap() { } void TempoMap::clear_scope_owner () { _scope_owner = nullptr; } TempoMap& TempoMap::operator= (TempoMap const & other) { copy_points (other); return *this; } void TempoMap::copy_points (TempoMap const & other) { MusicTimePoint const * mt; TempoPoint const * tp; MeterPoint const * mp; for (auto const & point : other._points) { if ((mt = dynamic_cast (&point))) { MusicTimePoint* mtp = new MusicTimePoint (*mt); _bartimes.push_back (*mtp); _meters.push_back (*mtp); _tempos.push_back (*mtp); _points.push_back (*mtp); mtp->set_map (*this); } else if ((mp = dynamic_cast (&point))) { MeterPoint* mpp = new MeterPoint (*mp); _meters.push_back (*mpp); _points.push_back (*mpp); mpp->set_map (*this); } else if ((tp = dynamic_cast (&point))) { TempoPoint* tpp = new TempoPoint (*tp); _tempos.push_back (*tpp); _points.push_back (*tpp); tpp->set_map (*this); } } #ifndef NDEBUG for (auto & p : _points) { assert (&p.map () == this); } for (auto & t : _tempos) { assert (&t.map () == this); } for (auto & m : _meters) { assert (&m.map () == this); } #endif } TempoMapCutBuffer* TempoMap::cut (timepos_t const & start, timepos_t const & end, bool ripple) { return cut_copy (start, end, false, ripple); } TempoMapCutBuffer* TempoMap::copy ( timepos_t const & start, timepos_t const & end) { return cut_copy (start, end, true, false); } TempoMapCutBuffer* TempoMap::cut_copy (timepos_t const & start, timepos_t const & end, bool copy, bool ripple) { if (!copy && (n_tempos() == 1 && n_meters() == 1)) { return nullptr; } TempoMetric sm (metric_at (start)); TempoMetric em (metric_at (end)); timecnt_t dur = start.distance (end); TempoMapCutBuffer* cb = new TempoMapCutBuffer (dur); superclock_t start_sclock = start.superclocks(); superclock_t end_sclock = end.superclocks(); bool removed = false; Tempo start_tempo (tempo_at (start)); Tempo end_tempo (tempo_at (end)); Meter start_meter (meter_at (start)); Meter end_meter (meter_at (end)); MusicTimePoint* mtp; BBT_Time bbt (bbt_at (start)); Beats b (quarters_at (start)); if (!copy) { mtp = new MusicTimePoint (*this, start_sclock, b, bbt, em.tempo(), em.meter(), _("cut")); } else { mtp = nullptr; } for (Points::iterator p = _points.begin(); p != _points.end(); ) { /* XXX might to check time domain of start/end, and use beat * time here. */ if (p->sclock() >= end_sclock) { break; } if (p->sclock() < start_sclock) { ++p; continue; } Points::iterator nxt (p); ++nxt; TempoPoint const * tp; MeterPoint const * mp; MusicTimePoint const * mtp; if ((mtp = dynamic_cast (&*p))) { cb->add (*mtp); if (!copy && mtp->sclock() != 0) { Point* rp = core_remove_bartime (*mtp); if (rp) { remove_point (*rp); removed = true; } } } else { if ((tp = dynamic_cast (&*p))) { cb->add (*tp); if (!copy && tp->sclock() != 0) { Point* rp = core_remove_tempo (*tp); if (rp) { remove_point (*rp); removed = true; } } } else if ((mp = dynamic_cast (&*p))) { cb->add (*mp); if (!copy && mp->sclock() != 0) { Point* rp = core_remove_meter (*mp); if (rp) { remove_point (*rp); removed = true; } } } } p = nxt; } if (!copy && ripple) { shift (start, -start.distance (end)); } if (mtp) { // add_or_replace_bartime (mtp); } if (!copy && removed) { reset_starting_at (start_sclock); } return cb; } void TempoMap::paste (TempoMapCutBuffer const & cb, timepos_t const & position, bool ripple, std::string suggested_name) { if (cb.empty()) { return; } if (ripple) { shift (position, cb.duration()); } /* We need to look these up first, before we change the map */ const timepos_t end_position = position + cb.duration(); const Tempo end_tempo = tempo_at (end_position); const Meter end_meter = meter_at (end_position); /* iterate over _points since they are already in sclock order, and we * won't need to post-sort the way we would if we handled tempos, * meters, bartimes separately. */ BBT_Time pos_bbt = bbt_at (position); Beats pos_beats = quarters_at (position); bool ignored; bool replaced; MusicTimePoint* mtp; superclock_t s; std::string name; /* Do not try to put a BBT marker at absolute zero or anywhere on a bar */ if (!position.is_zero() && (pos_bbt.ticks != 0 || pos_bbt.beats != 1)) { if (suggested_name.empty()) { name = _("paste>"); } else { name = string_compose (X_("%1>"), suggested_name); } mtp = new MusicTimePoint (*this, position.superclocks(), pos_beats, pos_bbt, tempo_at (position), meter_at (position), name); core_add_bartime (mtp, replaced); if (!replaced) { core_add_tempo (mtp, ignored); core_add_meter (mtp, ignored); core_add_point (mtp); } reset_starting_at (position.superclocks()); } for (auto const & p : cb.points()) { TempoPoint const * tp; MeterPoint const * mp; MusicTimePoint const * mtp; Beats b; BBT_Time bb; s = p.sclock() + position.superclocks(); b = quarters_at_superclock (s); if ((mtp = dynamic_cast (&p))) { tp = dynamic_cast (&p); mp = dynamic_cast (&p); /* not entirely clear what the semantics of this ought * to be. Do we paste the precise same BBT markers, or * do we shift by the paste position */ bb = p.bbt (); MusicTimePoint *ntp = new MusicTimePoint (*this, s, b, bb, *tp, *mp, mtp->name()); core_add_bartime (ntp, replaced); if (!replaced) { core_add_tempo (ntp, ignored); (void) core_add_meter (ntp, ignored); core_add_point (ntp); } } else { if ((tp = dynamic_cast (&p))) { TempoPoint *ntp = new TempoPoint (*this, *tp, s, b, p.bbt()); core_add_tempo (ntp, replaced); if (!replaced) { core_add_point (ntp); } } else if ((mp = dynamic_cast (&p))) { MeterPoint *ntp = new MeterPoint (*this, *mp, s, b, p.bbt()); core_add_meter (ntp, replaced); if (!replaced) { core_add_point (ntp); } } } reset_starting_at (s); } pos_bbt = bbt_at (end_position); pos_beats = quarters_at (end_position); if (pos_bbt.ticks != 0 || pos_bbt.beats != 1) { if (suggested_name.empty()) { name = _("::min()) { /* can't insert time at the front of the map: those entries are fixed */ return; } timecnt_t abs_by (by.abs()); superclock_t distance = abs_by.superclocks (); superclock_t at_superclocks = abs_by.superclocks (); if (distance == 0) { return; } for (auto & p : _points) { if (p.sclock() >= at_superclocks) { if (distance >= 0 || p.sclock() > distance) { if (dynamic_cast (&p)) { continue; } superclock_t s = p.sclock() + distance; BBT_Time bb = bbt_at (s); Beats b = quarters_at_superclock (s); p.set (s, b, bb); } } } reset_starting_at (at_superclocks + distance); } void TempoMap::shift (timepos_t const & at, BBT_Offset const & offset) { /* for now we require BBT-based shifts to be in units of whole bars */ if (std::abs (offset.bars) < 1) { return; } if (offset.beats || offset.ticks) { return; } const superclock_t at_superclocks = at.superclocks(); for (Points::iterator p = _points.begin(); p != _points.end(); ) { Points::iterator nxt = p; ++nxt; if (p->sclock() >= at_superclocks) { if (offset.bars > p->bbt().bars) { TempoPoint* tp; MeterPoint* mp; Point* rp; if (dynamic_cast (&*p)) { break; } else if ((mp = dynamic_cast (&*p))) { rp = core_remove_meter (*mp); } else if ((tp = dynamic_cast (&*p))) { rp = core_remove_tempo (*tp); } if (rp) { remove_point (*rp); } } else { BBT_Time new_bbt (p->bbt().bars + offset.bars, p->bbt().beats, p->bbt().ticks); p->set (p->sclock(), p->beats(), new_bbt); } } p = nxt; } reset_starting_at (at_superclocks); } MeterPoint* TempoMap::add_meter (MeterPoint* mp) { bool replaced; bool reset = _points.back().beats() > mp->beats(); MeterPoint* ret = core_add_meter (mp, replaced); if (!replaced) { core_add_point (mp); } else { delete mp; } if (reset) { reset_starting_at (ret->sclock()); } return ret; } bool TempoMap::clear_tempos_before (timepos_t const & t, bool stop_at_music_time) { if (_tempos.size() < 2) { return false; } bool removed = false; superclock_t sc = t.superclocks(); Tempos::iterator tp = _tempos.end(); --tp; MusicTimePoint* mtp (nullptr); while (tp != _tempos.begin()) { if (tp->sclock() > sc) { --tp; mtp = nullptr; continue; } if ((mtp = dynamic_cast (&*tp)) && stop_at_music_time) { break; } Tempos::iterator nxt = tp; --nxt; if (mtp) { Meters::iterator mpi = _meters.s_iterator_to (*(static_cast (&*mtp))); _meters.erase (mpi); MusicTimes::iterator mtpi = _bartimes.s_iterator_to (*mtp); _bartimes.erase (mtpi); } Points::iterator pi = _points.s_iterator_to (*(static_cast (&*tp))); if (pi != _points.end()) { _points.erase (pi); } _tempos.erase (tp); removed = true; tp = nxt; mtp = nullptr; } if (removed) { reset_starting_at (sc); } return removed; } bool TempoMap::clear_tempos_after (timepos_t const & t, bool stop_at_music_time) { if (_tempos.size() < 2) { return false; } bool removed = false; superclock_t sc = t.superclocks(); Tempos::iterator tp = _tempos.begin(); MusicTimePoint* mtp (nullptr); ++tp; while (tp != _tempos.end()) { if (tp->sclock() < sc) { ++tp; mtp = nullptr; continue; } if ((mtp = dynamic_cast (&*tp)) && stop_at_music_time) { break; } Tempos::iterator nxt = tp; ++nxt; if (mtp) { Meters::iterator mpi = _meters.s_iterator_to (*(static_cast (&*mtp))); _meters.erase (mpi); MusicTimes::iterator mtpi = _bartimes.s_iterator_to (*mtp); _bartimes.erase (mtpi); } Points::iterator pi = _points.s_iterator_to (*(static_cast (&*tp))); if (pi != _points.end()) { _points.erase (pi); } _tempos.erase (tp); removed = true; tp = nxt; mtp = nullptr; } if (removed) { reset_starting_at (sc); } return removed; } void TempoMap::change_tempo (TempoPoint & p, Tempo const & t) { *((Tempo*)&p) = t; reset_starting_at (p.sclock()); } void TempoMap::replace_tempo (TempoPoint const & old, Tempo const & t, timepos_t const & time) { if (old.sclock() == 0) { _tempos.front() = t; reset_starting_at (0); return; } remove_tempo (old, false); set_tempo (t, time); } TempoPoint & TempoMap::set_tempo (Tempo const & t, BBT_Argument const & bbt) { return set_tempo (t, timepos_t (quarters_at (bbt))); } TempoPoint & TempoMap::set_tempo (Tempo const & t, timepos_t const & time) { TempoPoint * ret; DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set tempo @ %1 to %2\n", time, t)); if (time.is_beats()) { /* tempo changes are required to be on-beat */ Beats on_beat = metric_at (time.beats(), false).round_to_beat (time.beats()); superclock_t sc; BBT_Time bbt; /* the metric for the on-beat position may be different than * the one for the raw time, so look it up again. */ TempoMetric metric (metric_at (on_beat, false)); bbt = metric.bbt_at (on_beat); sc = metric.superclock_at (on_beat); TempoPoint* tp = new TempoPoint (*this, t, sc, on_beat, bbt); ret = add_tempo (tp); } else { Beats beats; BBT_Time bbt; superclock_t sc = time.superclocks(); TempoMetric tm (metric_at (sc, false)); /* tempo changes must be on beat */ beats = tm.round_to_beat (tm.quarters_at_superclock (sc)); bbt = tm.bbt_at (beats); /* recompute superclock position of rounded beat */ sc = tm.superclock_at (beats); TempoPoint* tp = new TempoPoint (*this, t, sc, beats, bbt); ret = add_tempo (tp); } #ifndef NDEBUG if (DEBUG_ENABLED (DEBUG::TemporalMap)) { dump (cerr); } #endif return *ret; } TempoPoint & TempoMap::set_tempo (Tempo const & t, timepos_t const & time, Beats const & beats) { assert (!time.is_beats()); BBT_Time bbt; TempoMetric metric (metric_at (beats, false)); bbt = metric.bbt_at (beats); TempoPoint* tp = new TempoPoint (*this, t, time.superclocks(), beats, bbt); TempoPoint* ret = add_tempo (tp); return *ret; } void TempoMap::smf_begin () { _tempos.clear (); _meters.clear (); _points.clear (); _bartimes.clear (); } void TempoMap::smf_end () { } void TempoMap::smf_add (TempoPoint & tp) { assert (&tp.map() == this); /* all other tempos must be earlier; other points must be earlier or identical */ assert (_tempos.empty() || _tempos.back().sclock() < tp.sclock()); assert (_points.empty() || _points.back().sclock() <= tp.sclock()); _points.push_back (tp); _tempos.push_back (tp); } void TempoMap::smf_add (MeterPoint & mp) { assert (&mp.map() == this); /* all other meters must be earlier; other points must be earlier or identical */ assert (_meters.empty() || _meters.back().sclock() < mp.sclock()); assert (_points.empty() || _points.back().sclock() <= mp.sclock()); _points.push_back (mp); _meters.push_back (mp); } void TempoMap::core_add_point (Point* pp) { Points::iterator p; const Beats beats_limit = pp->beats(); for (p = _points.begin(); p != _points.end() && p->beats() < beats_limit; ++p); _points.insert (p, *pp); } TempoPoint* TempoMap::core_add_tempo (TempoPoint* tp, bool& replaced) { Tempos::iterator t; const superclock_t sclock_limit = tp->sclock(); const Beats beats_limit = tp->beats (); for (t = _tempos.begin(); t != _tempos.end() && t->beats() < beats_limit; ++t); if (t != _tempos.end()) { if (t->sclock() == sclock_limit) { /* overwrite Tempo part of this point */ *((Tempo*)&(*t)) = *tp; /* caller must delete tp when replaced is true */ DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("overwrote old tempo with %1\n", *tp)); replaced = true; return &(*t); } } DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("inserted tempo %1\n", *tp)); replaced = false; return &(* _tempos.insert (t, *tp)); } MeterPoint* TempoMap::core_add_meter (MeterPoint* mp, bool& replaced) { Meters::iterator m; const superclock_t sclock_limit = mp->sclock(); const Beats beats_limit = mp->beats (); for (m = _meters.begin(); m != _meters.end() && m->beats() < beats_limit; ++m); if (m != _meters.end()) { if (m->sclock() == sclock_limit) { /* overwrite Meter part of this point */ *((Meter*)&(*m)) = *mp; /* caller must delete mp when replaced is true */ replaced = true; return &(*m); } } replaced = false; return &(*(_meters.insert (m, *mp))); } MusicTimePoint* TempoMap::core_add_bartime (MusicTimePoint* mtp, bool& replaced) { MusicTimes::iterator m; const superclock_t sclock_limit = mtp->sclock(); for (m = _bartimes.begin(); m != _bartimes.end() && m->sclock() < sclock_limit; ++m); if (m != _bartimes.end()) { if (m->sclock() == sclock_limit) { /* overwrite Tempo part of this point */ *m = *mtp; /* caller must delete mtp when replaced is true */ DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("overwrote old bartime with %1\n", mtp)); replaced = true; return &(*m); } } DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("inserted bartime %1\n", mtp)); replaced = false; return &(* _bartimes.insert (m, *mtp)); } TempoPoint* TempoMap::add_tempo (TempoPoint * tp) { bool replaced; bool reset = _points.back().beats() > tp->beats(); TempoPoint* ret = core_add_tempo (tp, replaced); if (!replaced) { core_add_point (tp); } else { delete tp; } if (reset) { TempoPoint* prev = const_cast (previous_tempo (*ret)); if (prev) { reset_starting_at (prev->sclock()); } else { reset_starting_at (ret->sclock()); } } return ret; } void TempoMap::remove_tempo (TempoPoint const & tp, bool with_reset) { if (_tempos.size() < 2) { return; } Point* rp; if ((rp = core_remove_tempo (tp)) == nullptr) { return; } superclock_t sc (tp.sclock()); remove_point (*rp); if (with_reset) { reset_starting_at (sc); } } Point* TempoMap::core_remove_tempo (TempoPoint const & tp) { Tempos::iterator t; /* the argument is likely to be a Point-derived object that doesn't * actually exist in this TempoMap, since the caller called * TempoMap::write_copy() in order to perform an RCU operation, but * will be removing an element known from the original map. * * However, since we do not allow points of the same type (Tempo, * Meter, BarTime) at the same time, we can effectively search here * using what is effectively a duple of (type,time) for the * comparison. * * Once/if found, we will have a pointer to the actual Point-derived * object in this TempoMap, and we can then remove that from the * _points list. */ for (t = _tempos.begin(); t != _tempos.end() && t->sclock() < tp.sclock(); ++t); if (t == _tempos.end()) { /* not found */ return nullptr; } if (t->sclock() != tp.sclock()) { /* error ... no tempo point at the time of tp */ return nullptr; } Tempos::iterator nxt = _tempos.begin(); Tempos::iterator prev = _tempos.end(); if (t != _tempos.end()) { nxt = t; ++nxt; } if (t != _tempos.begin()) { prev = t; --prev; } const bool was_end = (nxt == _tempos.end()); Point* ret (&(*t)); _tempos.erase (t); if (prev != _tempos.end() && was_end) { prev->set_end_npm (prev->note_types_per_minute()); /* remove any ramp */ } return ret; } void TempoMap::set_bartime (BBT_Time const & bbt, timepos_t const & pos, std::string name) { TEMPO_MAP_ASSERT (pos.time_domain() == AudioTime); superclock_t sc (pos.superclocks()); TempoMetric metric (metric_at (sc)); /* MusicTimePoints define a beat position (even if it is not predicted * by the prior tempo map elements. */ Beats b = metric.quarters_at_superclock (sc).round_up_to_beat ();; MusicTimePoint* tp = new MusicTimePoint (*this, sc, b, bbt, metric.tempo(), metric.meter(), name); add_or_replace_bartime (tp); } superclock_t TempoMap::previous_bbt_reference_at_superclock (superclock_t sc) const { if (_bartimes.empty()) { return 0; } if (_bartimes.size() == 1) { if (_bartimes.front().sclock() > sc) { return 0; } return _bartimes.front().sclock(); } MusicTimes::const_iterator m; for (m = _bartimes.begin(); m != _bartimes.end() && (*m).sclock() < sc; ++m); if (m == _bartimes.end()) { return _bartimes.back().sclock(); } --m; return (*m).sclock(); } void TempoMap::replace_bartime (MusicTimePoint & mtp, bool with_reset) { bool ignored; core_add_bartime (&mtp, ignored); if (with_reset) { reset_starting_at (mtp.sclock()); } } MusicTimePoint* TempoMap::add_or_replace_bartime (MusicTimePoint* mtp) { bool replaced; /* A MusicTimePoint by definition defines a beat position. It's not * zero, but it must be "on beat". So ensure that this is true. */ mtp->set (mtp->sclock(), mtp->beats().round_up_to_beat(), mtp->bbt()); MusicTimePoint* ret = core_add_bartime (mtp, replaced); if (!replaced) { bool ignore; (void) core_add_tempo (mtp, ignore); (void) core_add_meter (mtp, ignore); core_add_point (mtp); } else { delete mtp; } reset_starting_at (ret->sclock()); return ret; } Point* TempoMap::core_remove_bartime (MusicTimePoint const & mtp) { MusicTimes::iterator m; /* the argument is likely to be a Point-derived object that doesn't * actually exist in this TempoMap, since the caller called * TempoMap::write_copy() in order to perform an RCU operation, but * will be removing an element known from the original map. * * However, since we do not allow points of the same type (Tempo, * Meter, BarTime) at the same time, we can effectively search here * using what is effectively a duple of (type,time) for the * comparison. * * Once/if found, we will have a pointer to the actual Point-derived * object in this TempoMap, and we can then remove that from the * _points list. */ for (m = _bartimes.begin(); m != _bartimes.end() && m->sclock() < mtp.sclock(); ++m); if (m == _bartimes.end()) { /* error ... not found */ return nullptr; } if (m->sclock() != mtp.sclock()) { /* error ... no music time point at the time of tp */ return nullptr; } Point* ret (&(*m)); remove_point (*ret); core_remove_tempo (mtp); core_remove_meter (mtp); _bartimes.erase (m); return ret; } void TempoMap::remove_bartime (MusicTimePoint const & mtp, bool with_reset) { superclock_t sc (mtp.sclock()); /* Unlike ::remove_tempo() and ::remove_meter(), the core method here * also takes care of the point removal too. */ core_remove_bartime (mtp); if (with_reset) { reset_starting_at (sc, false); } } void TempoMap::remove_point (Point const & point) { for (auto p = _points.begin(); p != _points.end(); ++p) { if (&(*p) == &point) { // XXX need to fix this leak by deleting point; _points.erase (p); break; } } } void TempoMap::reset_starting_at (superclock_t sc, bool constant_bbt) { DEBUG_TRACE (DEBUG::MapReset, string_compose ("reset starting at %1\n", sc)); #ifndef NDEBUG if (DEBUG_ENABLED(DEBUG::MapReset)) { dump (std::cerr); } #endif TEMPO_MAP_ASSERT (!_tempos.empty()); TEMPO_MAP_ASSERT (!_meters.empty()); TempoPoint* tp; MeterPoint* mp; MusicTimePoint* mtp; TempoMetric metric (_tempos.front(), _meters.front()); Points::iterator p; bool need_initial_ramp_reset = false; DEBUG_TRACE (DEBUG::MapReset, string_compose ("we begin at %1 with metric %2\n", sc, metric)); /* Setup the metric that is in effect at the starting point */ for (p = _points.begin(); p != _points.end(); ++p) { DEBUG_TRACE (DEBUG::MapReset, string_compose ("Now looking at %1 => %2 \n", &(*p), *p)); if (p->sclock() > sc) { break; } mtp = nullptr; tp = nullptr; mp = nullptr; if ((mtp = dynamic_cast (&*p)) != 0) { metric = TempoMetric (*mtp, *mtp); DEBUG_TRACE (DEBUG::MapReset, string_compose ("Bartime!, used tempo @ %1\n", (TempoPoint*) mtp)); need_initial_ramp_reset = false; } else if ((tp = dynamic_cast (&*p)) != 0) { metric = TempoMetric (*tp, metric.meter()); if (tp->ramped()) { need_initial_ramp_reset = true; } else { need_initial_ramp_reset = true; } DEBUG_TRACE (DEBUG::MapReset, string_compose ("Tempo! @ %1, metric's tempo is %2\n", tp, &metric.tempo())); } else if ((mp = dynamic_cast (&*p)) != 0) { metric = TempoMetric (metric.tempo(), *mp); DEBUG_TRACE (DEBUG::MapReset, "Meter!\n"); } } /* if the tempo point the defines our starting metric for position * @param sc is ramped, recompute its omega value based on the beat * time of the following tempo point. If we do not do this before we * start, then ::superclock_at() for subsequent points will be * incorrect. */ if (need_initial_ramp_reset) { const TempoPoint *nxt = next_tempo (metric.tempo()); if (nxt) { const_cast (&metric.tempo())->compute_omega_from_next_tempo (*nxt); } need_initial_ramp_reset = false; } MusicTimes::iterator next_mtp = _bartimes.begin(); superclock_t current_section_limit; while (next_mtp != _bartimes.end() && (next_mtp->sclock() <= sc)) { ++next_mtp; } if (next_mtp != _bartimes.end()) { DEBUG_TRACE (DEBUG::MapReset, string_compose ("start rset with section defined by MTP @ %1 %2\n", &*next_mtp, *next_mtp)); current_section_limit = next_mtp->sclock(); } else { current_section_limit = std::numeric_limits::max(); DEBUG_TRACE (DEBUG::MapReset, "start rset with no next MTP (run to end)\n"); } reset_section (p, current_section_limit, metric, constant_bbt); DEBUG_TRACE (DEBUG::MapReset, "RESET DONE\n"); #ifndef NDEBUG if (DEBUG_ENABLED(DEBUG::MapReset)) { dump (std::cerr); } #endif } void TempoMap::reset_section (Points::iterator& begin, superclock_t section_limit, TempoMetric& metric, bool constant_bbt) { TempoPoint* tp; TempoPoint* nxt_tempo (nullptr); MeterPoint* mp; DEBUG_TRACE (DEBUG::MapReset, string_compose ("reset a section starting at %1, ending at %2\n", *begin, section_limit)); Points::iterator p = begin; while (p != _points.end() && p->sclock() < section_limit) { tp = nullptr; mp = nullptr; assert (dynamic_cast (&*p) == 0); if ((tp = dynamic_cast (&*p)) == 0) { mp = dynamic_cast (&*p); } assert (tp || mp); DEBUG_TRACE (DEBUG::MapReset, string_compose ("workong on it! tp = %1 mp %2\n", tp, mp)); if (tp) { Points::iterator pp = p; nxt_tempo = 0; ++pp; while (pp != _points.end()) { TempoPoint* nt = dynamic_cast (&*pp); if (nt) { nxt_tempo = nt; break; } ++pp; } if (tp->ramped() && nxt_tempo) { DEBUG_TRACE (DEBUG::MapReset, string_compose ("computing omega for %1 with nxt = %2\n", *tp, nxt_tempo)); tp->compute_omega_from_next_tempo (*nxt_tempo); } } Points::iterator nxt = p; ++nxt; if (constant_bbt) { DEBUG_TRACE (DEBUG::MapReset, string_compose ("recompute constant BBT %1 using %2\n", p->bbt(), metric)); superclock_t sc = metric.superclock_at (p->bbt()); if (sc >= section_limit) { Point* rp; if (tp) { rp = core_remove_tempo (*tp); } else { rp = core_remove_meter (*mp); } if (rp) { remove_point (*rp); } } else { if (mp) { /* Meter markers must be on-bar */ BBT_Time rounded = metric.meter().round_to_bar (p->bbt()); p->set (sc, metric.meter().quarters_at (rounded), rounded); DEBUG_TRACE (DEBUG::MapReset, string_compose ("\tbased on %1 move meter point to %2,%3\n", p->bbt(), sc, p->beats())); } else { /* Tempo markers must be on-beat */ BBT_Time rounded = metric.meter().round_to_beat (p->bbt()); p->set (sc, metric.meter().quarters_at (rounded), rounded); DEBUG_TRACE (DEBUG::MapReset, string_compose ("\tbased on %1 move tempo point to %2,%3\n", p->bbt(), sc, p->beats())); } } } else { DEBUG_TRACE (DEBUG::MapReset, string_compose ("recompute constant sc %1 using %2\n", p->sclock(), metric)); BBT_Argument bbt = metric.bbt_at_superclock (p->sclock()); if (mp) { /* Meter markers must be on-bar */ BBT_Time rounded = metric.meter().round_to_bar (bbt); p->set (p->sclock(), metric.meter().quarters_at (rounded), rounded); DEBUG_TRACE (DEBUG::MapReset, string_compose ("\tbased on %1 move meter point to %2,%3\n", p->bbt(), p->sclock(), p->beats())); } else { /* Tempo markers must be on-beat */ BBT_Time rounded = metric.meter().round_to_beat (bbt); p->set (p->sclock(), metric.meter().quarters_at (rounded), rounded); DEBUG_TRACE (DEBUG::MapReset, string_compose ("\tbased on %1 move tempo point to %2,%3\n", p->bbt(), p->sclock(), p->beats())); } } /* Now ensure that metric is correct moving forward */ if ((tp = dynamic_cast (&*p)) != 0) { metric = TempoMetric (*tp, metric.meter()); } else if ((mp = dynamic_cast (&*p)) != 0) { metric = TempoMetric (metric.tempo(), *mp); } p = nxt; } } bool TempoMap::move_meter (MeterPoint const & mp, timepos_t const & when, bool push) { TEMPO_MAP_ASSERT (!_tempos.empty()); TEMPO_MAP_ASSERT (!_meters.empty()); if (_meters.size() < 2 || mp == _meters.front()) { /* not movable */ return false; } superclock_t sc; Beats beats; BBT_Time bbt; beats = when.beats (); /* Do not allow moving a meter marker to the same position as * an existing one. */ Tempos::iterator t, prev_t; Meters::iterator m, prev_m; /* meter changes must be on bar */ for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->beats() < beats; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->beats() < beats && *m != mp; ++m) { prev_m = m; } if (prev_m == _meters.end()) { return false; } if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); } TempoMetric metric (*prev_t, *prev_m); bbt = metric.bbt_at (beats); bbt = metric.round_to_bar (bbt); /* Now find the correct TempoMetric for the new BBT position (which may * differ from the one we determined earlier. * * The search for the correct meter will be limited by the meter we're * dragging. But the search for the correct tempo needs to bounded by * both the BBT *and* the beat position, in case there is an upcoming * BBT marker. */ for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->bbt() < bbt && t->beats() < beats; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->bbt() < bbt && *m != mp; ++m) { prev_m = m; } if (prev_m == _meters.end()) { return false; } if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); } if (dynamic_cast (&(*prev_t)) || dynamic_cast (&(*prev_m))) { /* game over ... cannot drag meter through a BBT Marker */ return false; } metric = TempoMetric (*prev_t, *prev_m); beats = metric.quarters_at (bbt); for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end(); ++m) { if (&*m != &mp) { if (m->beats() == beats) { return false; } } } sc = metric.superclock_at (bbt); if (mp.sclock() == sc && mp.beats() == beats && mp.bbt() == bbt) { return false; } const superclock_t old_sc = mp.sclock(); /* reset position of this meter */ const_cast (&mp)->set (sc, beats, bbt); { Meters::iterator current = _meters.end(); Meters::iterator insert_before = _meters.end(); for (Meters::iterator m = _meters.begin(); m != _meters.end(); ++m) { if (*m == mp) { current = m; } if (insert_before == _meters.end() && (m->sclock() > sc)) { insert_before = m; } } /* existing meter must have been found */ TEMPO_MAP_ASSERT (current != _meters.end()); /* reposition in list */ _meters.splice (insert_before, _meters, current); } { Points::iterator current = _points.end(); Points::iterator insert_before = _points.end(); for (Points::iterator m = _points.begin(); m != _points.end(); ++m) { if (*m == mp) { current = m; } if (insert_before == _points.end() && (m->sclock() > sc)) { insert_before = m; } } /* existing meter must have been found */ TEMPO_MAP_ASSERT (current != _points.end()); /* reposition in list */ _points.splice (insert_before, _points, current); } /* recompute 3 domain positions for everything after this */ reset_starting_at (std::min (sc, old_sc)); return true; } bool TempoMap::move_tempo (TempoPoint const & tp, timepos_t const & when, bool push) { TEMPO_MAP_ASSERT (!_tempos.empty()); TEMPO_MAP_ASSERT (!_meters.empty()); if (_tempos.size() < 2 || tp == _tempos.front()) { /* not movable */ return false; } superclock_t sc; Beats beats; BBT_Time bbt; /* tempo changes must be on beat */ beats = when.beats(); /* XXX need to TEMPO_MAP_ASSERT that meter note value is >= 4 */ MeterPoint const & mm (meter_at (beats)); beats.round_to_subdivision (mm.note_value() / 4, RoundNearest); /* Do not allow moving a tempo marker to the same position as * an existing one. */ Tempos::iterator t, prev_t; Meters::iterator m, prev_m; /* find tempo & meter in effect at the new target location */ for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->beats() <= beats && *t != tp; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->beats() <= beats; ++m) { prev_m = m; } if (prev_t == _tempos.end()) { /* moved earlier than first, no movement */ return false; } if (prev_m == _meters.end()) { /* moved earlier than first, no movement */ return false; } /* If the previous tempo is ramped, we need to recompute its omega * constant to cover the (new) duration of the ramp. */ if (prev_t->actually_ramped()) { prev_t->compute_omega_from_distance_and_next_tempo (beats - prev_t->beats(), tp); } TempoMetric metric (*prev_t, *prev_m); const Beats delta ((beats - tp.beats()).abs()); if (delta < Beats::ticks (metric.meter().ticks_per_grid())) { return false; } sc = metric.superclock_at (beats); bbt = metric.bbt_at (beats); if (tp.sclock() == sc && tp.beats() == beats && tp.bbt() == bbt) { return false; } const superclock_t old_sc = tp.sclock(); /* reset position of this tempo */ const_cast (&tp)->set (sc, beats, bbt); /* move to correct position in tempo list */ { Tempos::iterator current = _tempos.end(); Tempos::iterator insert_before = _tempos.end(); for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ++t) { if (*t == tp) { current = t; } if (insert_before == _tempos.end() && (t->sclock() > sc)) { insert_before = t; } } /* existing tempo must have been found */ TEMPO_MAP_ASSERT (current != _tempos.end()); /* reposition in list */ _tempos.splice (insert_before, _tempos, current); } /* move to correct position in points list */ { Points::iterator current = _points.end(); Points::iterator insert_before = _points.end(); for (Points::iterator t = _points.begin(); t != _points.end(); ++t) { if (*t == tp) { current = t; } if (insert_before == _points.end() && (t->sclock() > sc)) { insert_before = t; } } /* existing tempo must have been found */ TEMPO_MAP_ASSERT (current != _points.end()); /* reposition in list */ _points.splice (insert_before, _points, current); } /* recompute 3 domain positions for everything after this */ reset_starting_at (std::min (sc, old_sc)); return true; } MeterPoint & TempoMap::set_meter (Meter const & m, timepos_t const & time) { MeterPoint * ret = 0; DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set meter @ %1 to %2\n", time, m)); if (time.is_beats()) { Beats beats (time.beats()); TempoMetric metric (metric_at (beats)); /* meter changes are required to be on-bar */ BBT_Time rounded_bbt = metric.bbt_at (beats); rounded_bbt = metric.round_to_bar (rounded_bbt); const Beats rounded_beats = metric.quarters_at (rounded_bbt); const superclock_t sc = metric.superclock_at (rounded_beats); MeterPoint* mp = new MeterPoint (*this, m, sc, rounded_beats, rounded_bbt); ret = add_meter (mp); } else { superclock_t sc (time.superclocks()); Beats beats; BBT_Time bbt; TempoMetric metric (metric_at (sc)); /* meter changes must be on bar */ bbt = metric.bbt_at (time); bbt = metric.round_to_bar (bbt); /* compute beat position */ beats = metric.quarters_at (bbt); /* recompute superclock position of bar-rounded position */ sc = metric.superclock_at (beats); MeterPoint* mp = new MeterPoint (*this, m, sc, beats, bbt); ret = add_meter (mp); } return *ret; } MeterPoint & TempoMap::set_meter (Meter const & t, BBT_Argument const & bbt) { return set_meter (t, timepos_t (quarters_at (bbt))); } void TempoMap::remove_meter (MeterPoint const & mp, bool with_reset) { if (_meters.size() < 2) { return; } Point* rp; if ((rp = core_remove_meter (mp)) == nullptr) { return; } superclock_t sc = mp.sclock(); remove_point (*rp); if (with_reset) { reset_starting_at (sc); } } Point* TempoMap::core_remove_meter (MeterPoint const & mp) { Meters::iterator m; /* the argument is likely to be a Point-derived object that doesn't * actually exist in this TempoMap, since the caller called * TempoMap::write_copy() in order to perform an RCU operation, but * will be removing an element known from the original map. * * However, since we do not allow points of the same type (Tempo, * Meter, BarTime) at the same time, we can effectively search here * using what is effectively a duple of (type,time) for the * comparison. * * Once/if found, we will have a pointer to the actual Point-derived * object in this TempoMap, and we can then remove that from the * _points list. */ for (m = _meters.begin(); m != _meters.end() && m->sclock() < mp.sclock(); ++m); if (m == _meters.end()) { /* not found */ return nullptr; } if (m->sclock() != mp.sclock()) { /* error ... no meter point at the time of mp */ return nullptr; } Point* ret (&(*m)); _meters.erase (m); return ret; } Temporal::BBT_Argument TempoMap::bbt_at (timepos_t const & pos) const { if (pos.is_beats()) { return bbt_at (pos.beats()); } return bbt_at (pos.superclocks()); } Temporal::BBT_Argument TempoMap::bbt_at (superclock_t s) const { TempoMetric metric (metric_at (s)); superclock_t ref (std::min (metric.tempo().sclock(), metric.meter().sclock())); return BBT_Argument (ref, metric.bbt_at (timepos_t::from_superclock (s))); } Temporal::BBT_Argument TempoMap::bbt_at (Temporal::Beats const & qn) const { TempoMetric metric (metric_at (qn)); return BBT_Argument (metric.reftime(), metric.bbt_at (qn)); } superclock_t TempoMap::superclock_at (Temporal::Beats const & qn) const { return metric_at (qn).superclock_at (qn); } superclock_t TempoMap::superclock_at (Temporal::BBT_Argument const & bbt) const { return metric_at (bbt).superclock_at (bbt); } superclock_t TempoMap::superclock_at (timepos_t const & pos) const { if (pos.is_beats()) { return superclock_at (pos.beats ()); } /* somewhat nonsensical to call this under these conditions but ... */ return pos.superclocks(); } #define S2Sc(s) (samples_to_superclock ((s), TEMPORAL_SAMPLE_RATE)) #define Sc2S(s) (superclock_to_samples ((s), TEMPORAL_SAMPLE_RATE)) /** Count the number of beats that are equivalent to distance when going forward, starting at pos. */ Temporal::Beats TempoMap::scwalk_to_quarters (superclock_t pos, superclock_t distance) const { TempoMetric first (metric_at (pos)); TempoMetric last (metric_at (pos+distance)); Temporal::Beats a = first.quarters_at_superclock (pos); Temporal::Beats b = last.quarters_at_superclock (pos+distance); return b - a; } Temporal::Beats TempoMap::scwalk_to_quarters (Temporal::Beats const & pos, superclock_t distance) const { /* XXX this converts from beats to superclock and back to beats... which is OK (reversible) */ superclock_t s = metric_at (pos).superclock_at (pos); s += distance; return metric_at (s).quarters_at_superclock (s); } Temporal::Beats TempoMap::bbtwalk_to_quarters (Beats const & pos, BBT_Offset const & distance) const { return quarters_at (bbt_walk (bbt_at (pos), distance)) - pos; } Temporal::Beats TempoMap::bbtwalk_to_quarters (BBT_Argument const & pos, BBT_Offset const & distance) const { return quarters_at (bbt_walk (pos, distance)) - quarters_at (pos); } void TempoMap::sample_rate_changed (samplecnt_t new_sr) { const double ratio = new_sr / (double) TEMPORAL_SAMPLE_RATE; for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ++t) { t->map_reset_set_sclock_for_sr_change (llrint (ratio * t->sclock())); } for (Meters::iterator m = _meters.begin(); m != _meters.end(); ++m) { m->map_reset_set_sclock_for_sr_change (llrint (ratio * m->sclock())); } for (MusicTimes::iterator p = _bartimes.begin(); p != _bartimes.end(); ++p) { p->map_reset_set_sclock_for_sr_change (llrint (ratio * p->sclock())); } } void TempoMap::dump (std::ostream& ostr) const { ostr << "\n\nTEMPO MAP @ " << this << ":\n" << std::dec; ostr << "... tempos...\n"; for (Tempos::const_iterator t = _tempos.begin(); t != _tempos.end(); ++t) { ostr << &*t << ' ' << *t << endl; } ostr << "... meters...\n"; for (Meters::const_iterator m = _meters.begin(); m != _meters.end(); ++m) { ostr << &*m << ' ' << *m << endl; } ostr << "... bartimes...\n"; for (MusicTimes::const_iterator m = _bartimes.begin(); m != _bartimes.end(); ++m) { ostr << &*m << ' ' << *m << endl; } ostr << "... all points ...\n"; for (Points::const_iterator p = _points.begin(); p != _points.end(); ++p) { ostr << &*p << ' ' << *p; if (dynamic_cast (&(*p))) { ostr << " BarTime"; } if (dynamic_cast (&(*p))) { ostr << " Tempo"; } if (dynamic_cast (&(*p))) { ostr << " Meter"; } ostr << endl; } ostr << "------------\n\n\n"; } Points::size_type TempoMap::count_tempos_in_points () const { Points::size_type n = 0; for (Points::const_iterator p = _points.begin(); p != _points.end(); ++p) { if (dynamic_cast (&(*p))) { /* ignore */ } else if (dynamic_cast (&(*p))) { ++n; } } return n; } Points::size_type TempoMap::count_meters_in_points () const { Points::size_type n = 0; for (Points::const_iterator p = _points.begin(); p != _points.end(); ++p) { if (dynamic_cast (&(*p))) { /* ignore */ } else if (dynamic_cast (&(*p))) { ++n; } } return n; } template typename const_traits_t::iterator_type TempoMap::_get_tempo_and_meter (typename const_traits_t::tempo_point_type & tp, typename const_traits_t::meter_point_type & mp, typename const_traits_t::time_reference (Point::*method)() const, typename const_traits_t::time_type arg, typename const_traits_t::iterator_type begini, typename const_traits_t::iterator_type endi, typename const_traits_t::tempo_point_type tstart, typename const_traits_t::meter_point_type mstart, bool can_match, bool ret_iterator_after_not_at) const { typename const_traits_t::iterator_type p; typename const_traits_t::iterator_type last_used = endi; bool tempo_done = false; bool meter_done = false; TEMPO_MAP_ASSERT (!_tempos.empty()); TEMPO_MAP_ASSERT (!_meters.empty()); TEMPO_MAP_ASSERT (!_points.empty()); /* 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 || arg == typename const_traits_t::time_type ()); /* 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 (tp = tstart, mp = mstart, p = begini; p != endi; ++p) { typename const_traits_t::tempo_point_type tpp; typename const_traits_t::meter_point_type mpp; if (!tempo_done && (tpp = dynamic_cast (&(*p))) != 0) { if ((can_match && (((*p).*method)() > arg)) || (!can_match && (((*p).*method)() >= arg))) { tempo_done = true; } else { tp = tpp; last_used = p; } } if (!meter_done && (mpp = dynamic_cast (&(*p))) != 0) { if ((can_match && (((*p).*method)() > arg)) || (!can_match && (((*p).*method)() >= arg))) { meter_done = true; } else { mp = mpp; last_used = p; } } if (meter_done && tempo_done) { break; } } if (!tp || !mp) { return endi; } if (ret_iterator_after_not_at) { p = last_used; if (can_match) { while ((p != endi) && ((*p).*method)() <= arg) ++p; } else { while ((p != endi) && ((*p).*method)() < arg) ++p; } return p; } 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 { if (rstart == end) { return _points.end(); } /* note: @p bar_mod is "bar modulo", and describes the N in "give me every Nth bar". If the caller wants every 4th bar, bar_mod == 4. If we want every point defined by the tempo note type (e.g. every quarter not, then bar_mod is zero. */ TEMPO_MAP_ASSERT (!_tempos.empty()); TEMPO_MAP_ASSERT (!_meters.empty()); TEMPO_MAP_ASSERT (!_points.empty()); #ifndef NDEBUG if (DEBUG_ENABLED (DEBUG::Grid)) { dump (std::cout); } #endif DEBUG_TRACE (DEBUG::Grid, string_compose (">>> GRID START %1 .. %2 (barmod = %3, beat_div = %4)\n", rstart, end, bar_mod, beat_div)); /* The fast path: one tempo, one meter, just do (relatively) simple math */ if (_tempos.size() == 1 && _meters.size() == 1) { TempoMetric metric (_tempos.front(), _meters.front()); /* Figure out the beat preceding rstart */ superclock_t spdiv; if (bar_mod == 1) { spdiv = llrintf (metric.superclocks_per_note_type() * (metric.meter().divisions_per_bar() * (4. / metric.meter().note_value()))); } else { spdiv = metric.superclocks_per_note_type() / beat_div; } superclock_t start = (rstart / spdiv) * spdiv; /* div (bar/beat) preceding rstart */ /* determine BBT and beats at the position. Note that we know * that the tempo and meter must be at zero */ BBT_Time bbt (metric.bbt_at (timepos_t::from_superclock (start))); /* Now round to bar mod or beat_div to keep the grid aligned * with what has been asked for. */ BBT_Time on_bar; if (rstart != 0) { if (bar_mod == 1) { on_bar = metric.meter().round_up_to_bar (bbt); } else { on_bar = metric.meter().round_up_to_beat_div (bbt, beat_div); } BBT_Offset delta = metric.meter().bbt_delta (on_bar, bbt); if (delta != BBT_Offset ()) { bbt = on_bar; Beats beats_delta = _meters.front().to_quarters (delta); DEBUG_TRACE (DEBUG::Grid, string_compose ("simple reset start using bbt %1 via %2 (rounded by %3 beats %4) sc %5\n", bbt, on_bar, delta, beats_delta, start)); } else { DEBUG_TRACE (DEBUG::Grid, string_compose ("bbt %1 was already on-bar or on-beat %2 start is %3\n", bbt, on_bar, start)); } } Beats beats (metric.quarters_at_superclock (start)); DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start to %1 with beats %2 for %3\n", start, beats, bbt)); fill_grid_with_final_metric (ret, metric, start, rstart, end, bar_mod, beat_div, beats, bbt); return _points.end(); } TempoPoint const * tp = 0; MeterPoint const * mp = 0; Points::const_iterator p = _points.begin(); Beats beats; /* Find relevant meter for nominal start point */ p = get_tempo_and_meter (tp, mp, rstart, true, true); /* p now points to either the point *after* start, or the end of the * _points list. * * metric is the TempoMetric that is in effect at start */ TempoMetric metric = TempoMetric (*tp, *mp); DEBUG_TRACE (DEBUG::Grid, string_compose ("metric in effect at %1 = %2\n", rstart, metric)); /* determine the BBT at start. We can discard the reftime of a * BBT_Argument, because it is @var metric that defines it */ BBT_Argument bba = metric.bbt_at (timepos_t::from_superclock (rstart)); BBT_Time bbt (bba.bars, bba.beats, bba.ticks); /* We know that both the tempo point and meter point that make up @var * metric are beat and bar aligned respectively (note: if they are a * MusicTimePoint, they *define* a beat/bar alignment, even if they are * arbitrarily placed with respect to the earlier elements of the tempo * map. * * So we can just start at the later of the two of them, */ superclock_t start; if (tp->sclock() > mp->sclock()) { bbt = tp->bbt(); start = tp->sclock(); } else { bbt = mp->bbt(); start = mp->sclock(); } /* at this point: * * - metric is a TempoMetric that describes the situation at the start time * - p is an iterator pointin to either the end of the _points list, or * the next point in the list after start. */ fill_grid_by_walking (ret, p, metric, start, rstart, end, bar_mod, beat_div, beats, bbt); /* reached the end or no more points to consider, so just * finish by filling the grid to the end, if necessary. */ if (start < end) { fill_grid_with_final_metric (ret, metric, start, rstart, end, bar_mod, beat_div, beats, bbt); } else { if (p == _points.end()) { DEBUG_TRACE (DEBUG::Grid, string_compose ("ended loop with start %1 end %2, p @ END\n", start, end)); } else { DEBUG_TRACE (DEBUG::Grid, string_compose ("ended loop with start %1 end %2, p=> %3\n", start, end, *p)); } } DEBUG_TRACE (DEBUG::Grid, "<<< GRID DONE\n"); return p; } void TempoMap::get_grid (GridIterator& iter, TempoMapPoints& ret, superclock_t rstart, superclock_t end, uint32_t bar_mod, uint32_t beat_div) const { DEBUG_TRACE (DEBUG::Grid, string_compose (">>> GRID-I START %1 .. %2 (barmod = %3) iter valid ? %4 iter for %5\n", rstart, end, bar_mod, iter.valid_for (*this, rstart, bar_mod, beat_div), iter.end)); if (!iter.valid_for (*this, rstart, bar_mod, beat_div)) { Points::const_iterator p = get_grid (ret, rstart, end, bar_mod, beat_div); if (!ret.empty()) { TempoMapPoint& tmp (ret.back()); iter.set (*this, &tmp.tempo(), &tmp.meter(), tmp.sclock(), tmp.beats(), tmp.bbt(), p, end, bar_mod, beat_div); } else { iter.catch_up_to (end); } return; } TempoMetric metric (*iter.tempo, *iter.meter); superclock_t start = iter.sclock; Beats beats = iter.beats; BBT_Time bbt = iter.bbt; Points::const_iterator p = iter.points_iterator; fill_grid_by_walking (ret, p, metric, start, rstart, end, bar_mod, beat_div, beats, bbt); if (start < end) { fill_grid_with_final_metric (ret, metric, start, rstart, end, bar_mod, beat_div, beats, bbt); } if (!ret.empty()) { TempoMapPoint& tmp (ret.back()); iter.set (*this, &metric.tempo(), &metric.meter(), tmp.sclock(), tmp.beats(), tmp.bbt(), p, end, bar_mod, beat_div); } else { iter.catch_up_to (end); } DEBUG_TRACE (DEBUG::Grid, "<<< GRID-I DONE\n"); } void TempoMap::fill_grid_by_walking (TempoMapPoints& ret, Points::const_iterator& p_i, TempoMetric& metric_i, superclock_t& start, superclock_t rstart, superclock_t end, int bar_mod, int beat_div, Beats& beats, BBT_Time& bbt) const { TempoMetric metric (metric_i); Points::const_iterator p (p_i); while (p != _points.end() && start < end) { MusicTimePoint const *mtp = dynamic_cast (&*p); /* Generate grid points (either actual meter-defined * beats, or bars based on bar_mod) up until the next point * in the map */ if (bar_mod != 0) { if (start >= rstart) { if (bbt.is_bar() && (bar_mod == 1 || ((bbt.bars % bar_mod == 1)))) { ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt)); DEBUG_TRACE (DEBUG::Grid, string_compose ("Ga %1\t [%2]\n", metric, ret.back())); } else { DEBUG_TRACE (DEBUG::Grid, string_compose ("-- skip %1 not on bar_mod %2\n", bbt, bar_mod)); } } else { DEBUG_TRACE (DEBUG::Grid, string_compose ("skip-a point @ %1, too early for %2\n", start, rstart)); } /* Advance by the number of bars specified by bar_mod */ bbt.bars += bar_mod; DEBUG_TRACE (DEBUG::Grid, string_compose ("advanced BBT by %1 bars to %2\n", bar_mod, bbt)); } else { if (start >= rstart) { if (beat_div == 1) { ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt)); DEBUG_TRACE (DEBUG::Grid, string_compose ("Gb %1\t [%2]\n", metric, ret.back())); } else { int ticks = (bbt.beats * metric.meter().ticks_per_grid()) + bbt.ticks; int mod = Temporal::ticks_per_beat / beat_div; if ((ticks % mod) == 0) { ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt)); DEBUG_TRACE (DEBUG::Grid, string_compose ("Gd %1\t [%2]\n", metric, ret.back())); } else { DEBUG_TRACE (DEBUG::Grid, string_compose ("-- skip %1 not on beat_div_mod %2\n", ticks, mod)); } } } else { DEBUG_TRACE (DEBUG::Grid, string_compose ("skip-b point @ %1, too early for %2\n", start, rstart)); } /* Note that in a BBT time, the "beats" count is * meter-dependent. So if we're in 4/4 time, the beats * are quarters. If we're in 7/8 time, the beats are in * 1/8 time, etc. */ if (beat_div == 1) { /* Advance beats by 1 meter-defined "beat */ bbt = metric.bbt_add (bbt, BBT_Offset (0, 1, 0)); DEBUG_TRACE (DEBUG::Grid, string_compose ("advanced BBT by 1 beat to %1\n", bbt)); } else { /* Advance beats by a fraction of the * meter-defined "beat" */ bbt = metric.bbt_add (bbt, BBT_Offset (0, 0, Temporal::ticks_per_beat / beat_div)); DEBUG_TRACE (DEBUG::Grid, string_compose ("advanced BBT by beat_div %1 to %2\n", beat_div, bbt)); } } DEBUG_TRACE (DEBUG::Grid, string_compose ("pre-check overrun of next point with bbt @ %1 audio %2 point %3\n", bbt, start, *p)); find_correct_tempometric_to_compute_sc_and_beats: bool reset = false; bool can_goto = true; if (!mtp) { if (bbt == p->bbt()) { // DEBUG_TRACE (DEBUG::Grid, string_compose ("Gc %1\t [%2]\n", metric, ret.back())); DEBUG_TRACE (DEBUG::Grid, string_compose ("we've reached the next point via BBT, BBT %1 audio %2 point %3\n", bbt, start, *p)); reset = true; } else if (bbt > p->bbt()) { DEBUG_TRACE (DEBUG::Grid, string_compose ("we've passed the next point via BBT, BBT %1 audio %2 point %3\n", bbt, start, *p)); reset = true; } } else { /* What does the current metric say about the audio time position of @v bbt ? */ start = metric.superclock_at (bbt); DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start to %1 based on %2 using %3\n", start, bbt, metric)); if (start >= p->sclock()) { /* Yep, too far. So we need to reset and take the next (music time point) into account. */ DEBUG_TRACE (DEBUG::Grid, string_compose ("we've reached/passed the next point via sclock, BBT %1 audio %2 point %3, using metric %4\n", bbt, start, *p, metric)); reset = true; } else { DEBUG_TRACE (DEBUG::Grid, string_compose ("confirmed that BBT %1 has audio time %2 before next point %3\n", bbt, start, *p)); can_goto = false; } } DEBUG_TRACE (DEBUG::Grid, string_compose ("check overrun of next point, reset required ? %4 with bbt @ %1 audio %2 point %3\n", bbt, start, *p, (reset ? "YES" : "NO"))); if (reset) { /* bbt is position for the next grid-line. */ TempoPoint const * tp = &metric.tempo(); MeterPoint const * mp = &metric.meter(); if (mtp) { /* BBT Markers/MusicTimePoints give the user a * chance to "reset" the BBT ruler. We should * do the same, unconditionally. */ tp = dynamic_cast (&*p); mp = dynamic_cast (&*p); TEMPO_MAP_ASSERT (tp); TEMPO_MAP_ASSERT (mp); metric = TempoMetric (*tp, *mp); DEBUG_TRACE (DEBUG::Grid, string_compose ("reset metric from music-time point %1, now %2\n", *mtp, metric)); if (p->bbt().ticks != 0) { /* We do not want an arbitrary off-beat * BBT marker to interrupt the grid. So * round up from the marker's BBT time * to the nearest appropriate beat/bar * unit, and then reset from there. */ BBT_Time on_bar; if (bar_mod == 1) { on_bar = mp->round_up_to_bar (p->bbt()); } else { on_bar = mp->round_up_to_beat_div (p->bbt(), beat_div); } bbt = BBT_Argument (metric.reftime(), on_bar); BBT_Offset delta = metric.meter().bbt_delta (on_bar, p->bbt()); if (delta != BBT_Offset ()) { Beats beats_delta = mp->to_quarters (delta); start = tp->superclock_at (tp->beats() + beats_delta); DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start using bbt %1 as %2 via %3 (rounded by %4 beats %5)\n", p->bbt(), bbt, on_bar, delta, beats_delta)); } else { start = p->sclock(); DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start using bbt %1 as %2 via %3 (rounded by %4)\n", p->bbt(), bbt, on_bar, delta)); } DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start to %1\n", start)); } else { bbt = BBT_Argument (metric.reftime(), p->bbt()); DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start using bbt %1 as %2\n", p->bbt(), bbt)); start = p->sclock(); DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start to %1\n", start)); } /* Advance p to the next point */ ++p; } else { bool rebuild_metric = false; DEBUG_TRACE (DEBUG::Grid, string_compose ("iterating over points to find next, terminal is %1\n", bbt)); if (p != _points.end()) { DEBUG_TRACE (DEBUG::Grid, string_compose ("\tstarting point is %1\n", *p)); } else { DEBUG_TRACE (DEBUG::Grid, "\treached end already\n"); } /* Find all points at this BBT time (the next * grid), then rebuild the TempoMetric with whatever * we find, so that we will use that going forward. */ superclock_t sc = p->sclock(); while (p != _points.end() && p->bbt() <= bbt && p->sclock() == sc) { TempoPoint const * tpp; MeterPoint const * mpp; if ((tpp = dynamic_cast (&(*p))) != 0) { rebuild_metric = true; tp = tpp; } if ((mpp = dynamic_cast (&(*p))) != 0) { rebuild_metric = true; mp = mpp; } ++p; if (p != _points.end()) { DEBUG_TRACE (DEBUG::Grid, string_compose ("\tnext point is @ %1 %2, rebuild_metric = %3\n", &(*p), *p, rebuild_metric)); } else { DEBUG_TRACE (DEBUG::Grid, string_compose ("\tthat was that, rebuild_metric = %1\n", rebuild_metric)); } } if (p != _points.end()) { DEBUG_TRACE (DEBUG::Grid, string_compose ("left inner find-next loop with %1 vs bbt %2 p->sc %3 vs %4\n", *p, bbt, p->sclock(), sc)); } else { DEBUG_TRACE (DEBUG::Grid, "left loop because we reached the end of points\n"); } /* reset the metric to use the most recent tempo & meter */ if (rebuild_metric) { metric = TempoMetric (*tp, *mp); bbt = BBT_Argument (metric.reftime(), bbt); DEBUG_TRACE (DEBUG::Grid, string_compose ("second| with start = %1 aka %2 rebuilt metric from points, now %3\n", start, bbt, metric)); } else { DEBUG_TRACE (DEBUG::Grid, string_compose ("not rebuilding metric, continuing with %1\n", metric)); } } } if (can_goto && (p != _points.end()) && (p->bbt() < bbt)) { /* We reached a point that didn't coincide with the one we were looking at, but we have not yet reached the BBT value for the next grid point. Go back and run the "find next point" loop again. */ mtp = dynamic_cast (&*p); goto find_correct_tempometric_to_compute_sc_and_beats; } DEBUG_TRACE (DEBUG::Grid, string_compose ("reset done, bbt now at %1 with metric %2, get superclock\n", bbt, metric)); start = metric.superclock_at (bbt); /* Update the quarter-note time value to match the BBT and * audio time positions */ DEBUG_TRACE (DEBUG::Grid, string_compose ("get quarters for %1 from %2\n", bbt, metric)); beats = metric.quarters_at (bbt); /* we have a candidate grid point (start,beats,bbt). It might * not be within the range we're generating, but if it is, the iterator * will need to know where the points_iterator and metric * parameters ended up before we return.. */ if (start >= rstart && start < end) { p_i = p; metric_i = metric; } DEBUG_TRACE (DEBUG::Grid, string_compose ("moved to %1 qn %2 sc %3) using metric %4 p at end %5\n", bbt, beats, start, metric, p == _points.end())); } } void TempoMap::fill_grid_with_final_metric (TempoMapPoints& ret, TempoMetric metric, superclock_t start, superclock_t rstart, superclock_t end, int bar_mod, int beat_div, Beats beats, BBT_Time bbt) const { DEBUG_TRACE (DEBUG::Grid, string_compose ("reached end, no more map points, use %5 to finish between %1 .. %2 initial bbt %3, beats %4\n", start, end, bbt, beats.str(), metric)); while (start < end) { DEBUG_TRACE (DEBUG::Grid, string_compose ("bar mod %1 moved to %2 qn %3 sc %4)\n", bar_mod, bbt, beats, start)); /* It is possible we already added the current BBT * point, so check to avoid doubling up */ if (bar_mod != 0) { if ((start >= rstart) && bbt.is_bar() && (bar_mod == 1 || ((bbt.bars % bar_mod == 1)))) { ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt)); DEBUG_TRACE (DEBUG::Grid, string_compose ("GendA %1\t %2\n", metric, ret.back())); } /* Advance by the number of bars specified by bar_mod, then recompute the beats and superclock position corresponding to that BBT time. */ bbt.bars += bar_mod; } else { if (start >= rstart) { if (beat_div == 1) { ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt)); DEBUG_TRACE (DEBUG::Grid, string_compose ("Gendb %1\t [%2]\n", metric, ret.back())); } else { int ticks = (bbt.beats * metric.meter().ticks_per_grid()) + bbt.ticks; int mod = Temporal::ticks_per_beat / beat_div; if ((ticks % mod) == 0) { ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt)); DEBUG_TRACE (DEBUG::Grid, string_compose ("Gendd %1\t [%2]\n", metric, ret.back())); } } } /* move on by 1 meter-defined "beat" */ if (beat_div == 1) { bbt = metric.bbt_add (bbt, BBT_Offset (0, 1, 0)); } else { bbt = metric.bbt_add (bbt, BBT_Offset (0, 0, Temporal::ticks_per_beat / beat_div)); } } /* compute audio and quarter-note time from the new BBT position */ start = metric.superclock_at (bbt); beats = metric.quarters_at (bbt); } } std::ostream& std::operator<<(std::ostream& str, Meter const & m) { return str << m.divisions_per_bar() << '/' << m.note_value(); } std::ostream& std::operator<<(std::ostream& str, Tempo const & t) { if (t.ramped()) { return str << t.note_types_per_minute() << " .. " << t.end_note_types_per_minute() << " 1/" << t.note_type() << " RAMPED notes per minute (" << t.superclocks_per_note_type() << " .. " << t.end_superclocks_per_note_type() << " sc-per-1/" << t.note_type() << ')'; } else { return str << t.note_types_per_minute() << " 1/" << t.note_type() << " notes per minute (" << t.superclocks_per_note_type() << " sc-per-1/" << t.note_type() << ')'; } } std::ostream& std::operator<<(std::ostream& str, Point const & p) { return str << "P@" << p.sclock() << '/' << p.beats() << '/' << p.bbt(); } std::ostream& std::operator<<(std::ostream& str, MeterPoint const & m) { return str << *((Meter const *) &m) << ' ' << *((Point const *) &m); } std::ostream& std::operator<<(std::ostream& str, TempoPoint const & t) { str << *((Tempo const *) &t) << ' ' << *((Point const *) &t); if (t.ramped()) { if (t.actually_ramped()) { str << ' ' << " ramp to " << t.end_note_types_per_minute(); } else { str << ' ' << " !ramp to " << t.end_note_types_per_minute(); } str << " omega = " << std::setprecision(12) << t.omega(); } return str; } std::ostream& std::operator<<(std::ostream& str, MusicTimePoint const & p) { str << "MP " << p.name() << " @ " << *((Point const *) &p) << ' ' << *((Tempo const *) &p) << ' ' << *((Meter const *) &p); return str; } std::ostream& std::operator<<(std::ostream& str, TempoMetric const & tm) { return str << tm.tempo() << ' ' << tm.meter(); } std::ostream& std::operator<<(std::ostream& str, TempoMapPoint const & tmp) { str << '@' << std::setw (12) << tmp.sclock() << ' ' << tmp.sclock() / (double) superclock_ticks_per_second() << " secs " << tmp.sample_is_dangerous (TEMPORAL_SAMPLE_RATE) << " samples" << (tmp.is_explicit_tempo() ? " EXP-T" : " imp-t") << (tmp.is_explicit_meter() ? " EXP-M" : " imp-m") << (tmp.is_explicit_position() ? " EXP-P" : " imp-p") << " qn " << tmp.beats () << " bbt " << tmp.bbt() ; if (tmp.is_explicit_tempo()) { str << " tempo " << tmp.tempo(); } if (tmp.is_explicit_meter()) { str << " meter " << tmp.meter(); } if (tmp.is_explicit_tempo() && tmp.tempo().ramped()) { str << " ramp omega(beats) = " << tmp.tempo().omega(); } return str; } BBT_Argument TempoMap::bbt_walk (BBT_Argument const & bbt, BBT_Offset const & o) const { BBT_Offset offset (o); BBT_Time start (bbt); Tempos::const_iterator t, prev_t, next_t; Meters::const_iterator m, prev_m, next_m; TEMPO_MAP_ASSERT (!_tempos.empty()); TEMPO_MAP_ASSERT (!_meters.empty()); /* trivial (and common) case: single tempo, single meter */ if (_tempos.size() == 1 && _meters.size() == 1) { return BBT_Argument (_meters.front().bbt_add (bbt, o)); } /* Find tempo,meter pair for bbt, and also for the next tempo and meter * after each (if any) */ /* Yes, linear search because the typical size of _tempos and _meters * is 1, and extreme sizes are on the order of 10 */ next_t = _tempos.end(); next_m = _meters.end(); for (t = _tempos.begin(), prev_t = t; t != _tempos.end() && t->bbt() < bbt;) { prev_t = t; ++t; if (t != _tempos.end()) { next_t = t; ++next_t; } } for (m = _meters.begin(), prev_m = m; m != _meters.end() && m->bbt() < bbt;) { prev_m = m; ++m; if (m != _meters.end()) { next_m = m; ++next_m; } } /* may have found tempo and/or meter precisely at the time given */ if (t != _tempos.end() && t->bbt() == bbt) { prev_t = t; } if (m != _meters.end() && m->bbt() == bbt) { prev_m = m; } /* see ::metric_at() for comments about the use of const_cast here */ TempoMetric metric (*const_cast(&*prev_t), *const_cast(&*prev_m)); /* normalize possibly too-large ticks count */ const int32_t tpg = metric.meter().ticks_per_grid (); if (offset.ticks > tpg) { /* normalize */ offset.beats += offset.ticks / tpg; offset.ticks %= tpg; } /* add each beat, 1 by 1, rechecking to see if there's a new * TempoMetric in effect after each addition */ #define TEMPO_CHECK_FOR_NEW_METRIC \ { \ /* need new metric */ \ bool advance_t = false; \ bool advance_m = false; \ if (next_t != _tempos.end() && (start >= next_t->bbt())) { \ advance_t = true; \ }\ if (next_m != _meters.end() && (start >= next_m->bbt())) { \ advance_m = true; \ } \ if (advance_t && advance_m) { \ metric = TempoMetric (*const_cast(&*next_t), *const_cast(&*next_m)); \ ++next_t; \ ++next_m; \ } else if (advance_t && !advance_m) { \ metric = TempoMetric (*const_cast(&*next_t), metric.meter()); \ ++next_t; \ } else if (advance_m && !advance_t) { \ metric = TempoMetric (metric.tempo(), *const_cast(&*next_m)); \ ++next_m; \ } \ } for (int32_t b = 0; b < offset.bars; ++b) { TEMPO_CHECK_FOR_NEW_METRIC; start.bars += 1; } for (int32_t b = 0; b < offset.beats; ++b) { TEMPO_CHECK_FOR_NEW_METRIC; start.beats += 1; if (start.beats > metric.divisions_per_bar()) { start.bars += 1; start.beats = 1; } } #undef TEMPO_CHECK_FOR_NEW_METRIC start.ticks += offset.ticks; if (start.ticks >= ticks_per_beat) { start.beats += 1; start.ticks %= ticks_per_beat; } return BBT_Argument (metric.reftime(), start); } BBT_Offset TempoMap::bbt_distance (BBT_Argument const & strt, BBT_Argument const & nd) const { BBT_Offset offset; BBT_Time start (strt); BBT_Time end (nd); bool reversed = false; Tempos::const_iterator t, prev_t, next_t; Meters::const_iterator m, prev_m, next_m; if (start > end) { std::swap (start, end); reversed = true; } TEMPO_MAP_ASSERT (!_tempos.empty()); TEMPO_MAP_ASSERT (!_meters.empty()); /* trivial (and common) case: single tempo, single meter */ if (_tempos.size() == 1 && _meters.size() == 1) { if (reversed) { return BBT_Offset (start.bars - end.bars, start.beats - end.beats, start.ticks - end.ticks); } else { return BBT_Offset (end.bars - start.bars, end.beats - start.beats, end.ticks - start.ticks); } } /* Find tempo,meter pair for bbt, and also for the next tempo and meter * after each (if any) */ /* Yes, linear search because the typical size of _tempos and _meters * is 1, and extreme sizes are on the order of 10 */ next_t = _tempos.end(); next_m = _meters.end(); for (t = _tempos.begin(), prev_t = t; t != _tempos.end() && t->bbt() < start;) { prev_t = t; ++t; if (t != _tempos.end()) { next_t = t; ++next_t; } } for (m = _meters.begin(), prev_m = m; m != _meters.end() && m->bbt() < start;) { prev_m = m; ++m; if (m != _meters.end()) { next_m = m; ++next_m; } } /* may have found tempo and/or meter precisely at the time given */ if (t != _tempos.end() && t->bbt() == start) { prev_t = t; } if (m != _meters.end() && m->bbt() == start) { prev_m = m; } /* see ::metric_at() for comments about the use of const_cast here */ TempoMetric metric (*const_cast(&*prev_t), *const_cast(&*prev_m)); /* add each beat, 1 by 1, rechecking to see if there's a new * TempoMetric in effect after each addition */ #define TEMPO_CHECK_FOR_NEW_METRIC(b) \ { \ /* need new metric */ \ bool advance_t = false; \ bool advance_m = false; \ if (next_t != _tempos.end() && (b >= next_t->bbt())) { \ advance_t = true; \ }\ if (next_m != _meters.end() && (b >= next_m->bbt())) { \ advance_m = true; \ } \ if (advance_t && advance_m) { \ metric = TempoMetric (*const_cast(&*next_t), *const_cast(&*next_m)); \ ++next_t; \ ++next_m; \ } else if (advance_t && !advance_m) { \ metric = TempoMetric (*const_cast(&*next_t), metric.meter()); \ ++next_t; \ } else if (advance_m && !advance_t) { \ metric = TempoMetric (metric.tempo(), *const_cast(&*next_m)); \ ++next_m; \ } \ } for (;;) { BBT_Time s = metric.meter().bbt_add (start, BBT_Offset (0, 1, 0)); if (s > end) { break; } start = s; offset += BBT_Offset (0, 1, 0); TEMPO_CHECK_FOR_NEW_METRIC (s); } #undef TEMPO_CHECK_FOR_NEW_METRIC /* Deal with ticks */ offset.ticks = end.ticks - start.ticks; if (reversed) { offset = -offset; } return offset; } Temporal::Beats TempoMap::quarters_at (timepos_t const & pos) const { if (pos.is_beats()) { /* a bit redundant */ return pos.beats(); } return quarters_at_superclock (pos.superclocks()); } Temporal::Beats TempoMap::quarters_at (Temporal::BBT_Argument const & bbt) const { return metric_at (bbt).quarters_at (bbt); } Temporal::Beats TempoMap::quarters_at_superclock (superclock_t pos) const { return metric_at (pos).quarters_at_superclock (pos); } XMLNode& TempoMap::get_state () const { XMLNode* node = new XMLNode (X_("TempoMap")); node->set_property (X_("superclocks-per-second"), superclock_ticks_per_second()); XMLNode* children; children = new XMLNode (X_("Tempos")); node->add_child_nocopy (*children); for (Tempos::const_iterator t = _tempos.begin(); t != _tempos.end(); ++t) { if (!dynamic_cast (&(*t))) { children->add_child_nocopy (t->get_state()); } } children = new XMLNode (X_("Meters")); node->add_child_nocopy (*children); for (Meters::const_iterator m = _meters.begin(); m != _meters.end(); ++m) { if (!dynamic_cast (& (*m))) { children->add_child_nocopy (m->get_state()); } } children = new XMLNode (X_("MusicTimes")); node->add_child_nocopy (*children); for (MusicTimes::const_iterator b = _bartimes.begin(); b != _bartimes.end(); ++b) { children->add_child_nocopy (b->get_state()); } return *node; } int TempoMap::set_state (XMLNode const & node, int version) { if (version <= 6000) { return set_state_3x (node); } /* global map properties */ /* XXX this should probably be at the global level in the session file * because it is the time unit for anything in the audio time domain, * and affects a lot more than just the tempo map */ superclock_t sc; if (node.get_property (X_("superclocks-per-second"), sc)) { set_superclock_ticks_per_second (sc); } XMLNodeList const & children (node.children()); /* XXX might be good to have a recovery mechanism in case setting * things from XML fails. Not very likely, however. */ _tempos.clear (); _meters.clear (); _bartimes.clear (); _points.clear (); for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { if ((*c)->name() == X_("Tempos")) { if (set_tempos_from_state (**c)) { return -1; } } if ((*c)->name() == X_("Meters")) { if (set_meters_from_state (**c)) { return -1; } } if ((*c)->name() == X_("MusicTimes")) { if (set_music_times_from_state (**c)) { return -1; } } } return 0; } int TempoMap::set_music_times_from_state (XMLNode const& mt_node) { XMLNodeList const & children (mt_node.children()); try { for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { MusicTimePoint* mp = new MusicTimePoint (*this, **c); add_or_replace_bartime (mp); } } catch (...) { _bartimes.clear (); /* remove any that were created */ return -1; } return 0; } int TempoMap::set_tempos_from_state (XMLNode const& tempos_node) { XMLNodeList const & children (tempos_node.children()); bool ignore; try { for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { TempoPoint* tp = new TempoPoint (*this, **c); core_add_tempo (tp, ignore); core_add_point (tp); } } catch (...) { _tempos.clear (); /* remove any that were created */ return -1; } return 0; } int TempoMap::set_meters_from_state (XMLNode const& meters_node) { XMLNodeList const & children (meters_node.children()); bool ignore; try { for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { MeterPoint* mp = new MeterPoint (*this, **c); core_add_meter (mp, ignore); core_add_point (mp); } } catch (...) { _meters.clear (); /* remove any that were created */ return -1; } return 0; } bool TempoMap::can_remove (TempoPoint const & t) const { return !is_initial (t); } bool TempoMap::is_initial (TempoPoint const & t) const { return t.sclock() == 0; } bool TempoMap::is_initial (MeterPoint const & m) const { return m.sclock() == 0; } bool TempoMap::can_remove (MeterPoint const & m) const { return !is_initial (m); } /** returns the duration (using the domain of @p pos) of the supplied BBT time at a specified sample position in the tempo map. * @param pos the frame position in the tempo map. * @param bbt the distance in BBT time from pos to calculate. * @param dir the rounding direction.. * @return the timecnt_t that @p bbt represents when starting at @p pos, in * the time domain of @p pos */ timecnt_t TempoMap::bbt_duration_at (timepos_t const & pos, BBT_Offset const & dur) const { if (pos.time_domain() == AudioTime) { return timecnt_t::from_superclock (superclock_at (bbt_walk (bbt_at (pos), dur)) - pos.superclocks(), pos); } return timecnt_t (bbtwalk_to_quarters (pos.beats(), dur), pos); } /** Takes a position and distance (both in any time domain), and returns a timecnt_t * that describes that distance from that position in a specified time domain. * * This method is used when converting a (distance,pos) pair (a timecnt_t) into * (distance,pos') in a different time domain. For an english example: "given a * distance of N beats from position P, what is that same distance measured in * superclocks from P' ?" * * A different example: "given a distance N superclocks from position P, what * is that same distance measured in beats from P' ?" * * There is a trivial case in which the requested return domain is the same as * the domain for the distance. In english: "given a distance of N beats from * P, what is the same distance measured in beats from P' ?" In this case, we * can simply construct a new timecnt_t that uses P' instead of P, since the * distance component must necessarily be same. Notice that this true no matter * what domain is used. */ timecnt_t TempoMap::convert_duration (timecnt_t const & duration, timepos_t const & new_position, TimeDomain return_domain) const { timepos_t p (return_domain); Beats b; superclock_t s; if (return_domain == duration.time_domain()) { /* new timecnt_t: same distance, but new position */ return timecnt_t (duration.distance(), new_position); } switch (return_domain) { case AudioTime: switch (duration.time_domain()) { case AudioTime: /*NOTREACHED*/ break; case BeatTime: /* duration is in beats but we're asked to return superclocks */ switch (new_position.time_domain()) { case BeatTime: /* new_position is already in beats */ p = new_position; break; case AudioTime: /* Determine beats at sc pos, so that we can add beats */ p = timepos_t (metric_at (new_position).quarters_at_superclock (new_position.superclocks())); break; } /* add beats */ p += duration; /* determine superclocks */ s = metric_at (p).superclock_at (p.beats()); /* return duration in sc */ return timecnt_t::from_superclock (s - new_position.superclocks(), new_position); break; } break; case BeatTime: switch (duration.time_domain()) { case AudioTime: /* duration is in superclocks but we're asked to return beats */ switch (new_position.time_domain ()) { case AudioTime: /* pos is already in superclocks */ p = new_position; break; case BeatTime: /* determined sc at beat position so we can add superclocks */ p = timepos_t (metric_at (new_position).sample_at (new_position.beats())); break; } /* add superclocks */ p += duration; /* determine beats */ b = metric_at (p).quarters_at_superclock (p.superclocks()); /* return duration in beats */ return timecnt_t (b - new_position.beats(), new_position); break; case BeatTime: /*NOTREACHED*/ break; } break; } /*NOTREACHED*/ abort (); /*NOTREACHED*/ return timecnt_t::from_superclock (0); } uint32_t TempoMap::n_meters () const { return _meters.size(); } uint32_t TempoMap::n_tempos () const { return _tempos.size(); } bool TempoMap::remove_time (timepos_t const & pos, timecnt_t const & duration) { superclock_t start (pos.superclocks()); superclock_t end ((pos + duration).superclocks()); superclock_t shift (duration.superclocks()); TempoPoint* last_tempo = 0; MeterPoint* last_meter = 0; TempoPoint* tempo_after = 0; MeterPoint* meter_after = 0; bool moved = false; for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ) { if (t->sclock() >= start && t->sclock() < end) { last_tempo = &*t; t = _tempos.erase (t); moved = true; } else if (t->sclock() >= start) { t->set (t->sclock() - shift, t->beats(), t->bbt()); moved = true; if (t->sclock() == start) { tempo_after = &*t; } ++t; } } for (Meters::iterator m = _meters.begin(); m != _meters.end(); ) { if (m->sclock() >= start && m->sclock() < end) { last_meter = &*m; m = _meters.erase (m); moved = true; } else if (m->sclock() >= start) { m->set (m->sclock() - shift, m->beats(), m->bbt()); moved = true; if (m->sclock() == start) { meter_after = &*m; } ++m; } } if (last_tempo && !tempo_after) { last_tempo->set (start, last_tempo->beats(), last_tempo->bbt()); moved = true; } if (last_meter && !meter_after) { last_meter->set (start, last_meter->beats(), last_meter->bbt()); moved = true; } if (moved) { reset_starting_at (start); } return moved; } TempoPoint const * TempoMap::next_tempo (TempoPoint const & t) const { Tempos::const_iterator i = _tempos.iterator_to (t); ++i; if (i != _tempos.end()) { return &(*i); } return 0; } TempoPoint const * TempoMap::previous_tempo (TempoPoint const & point) const { Tempos::const_iterator i = _tempos.iterator_to (point); if (i == _tempos.begin()) { return 0; } --i; return &(*i); } MeterPoint const * TempoMap::next_meter (MeterPoint const & t) const { Meters::const_iterator i = _meters.iterator_to (t); ++i; if (i != _meters.end()) { return &(*i); } return 0; } MeterPoint const * TempoMap::previous_meter (MeterPoint const & point) const { Meters::const_iterator i = _meters.iterator_to (point); if (i == _meters.begin()) { return 0; } --i; return &(*i); } double TempoMap::quarters_per_minute_at (timepos_t const & pos) const { TempoPoint const & tp (tempo_at (pos)); const double val = tp.note_types_per_minute_at_DOUBLE (pos) * (4.0 / tp.note_type()); return val; } TempoPoint const & TempoMap::tempo_at (timepos_t const & pos) const { return pos.is_beats() ? tempo_at (pos.beats()) : tempo_at (pos.superclocks()); } MeterPoint const & TempoMap::meter_at (timepos_t const & pos) const { return pos.is_beats() ? meter_at (pos.beats()) : meter_at (pos.superclocks()); } TempoMetric TempoMap::metric_at (timepos_t const & pos) const { if (pos.is_beats()) { return metric_at (pos.beats()); } return metric_at (pos.superclocks()); } TempoMetric TempoMap::metric_at (superclock_t sc, bool can_match) const { TempoPoint const * tp = 0; MeterPoint const * mp = 0; (void) get_tempo_and_meter (tp, mp, sc, can_match, false); return TempoMetric (*tp,* mp); } TempoMetric TempoMap::metric_at (Beats const & b, bool can_match) const { TempoPoint const * tp = 0; MeterPoint const * mp = 0; (void) get_tempo_and_meter (tp, mp, b, can_match, false); return TempoMetric (*tp, *mp); } TempoMetric TempoMap::metric_at (BBT_Argument const & bbt, bool can_match) const { TempoPoint const * tp = 0; MeterPoint const * mp = 0; /* Since the reference time of a BBT_Argument is the time of the * latest tempo/meter marker before or at BBT, we can use the reference * time to get the metric. */ (void) get_tempo_and_meter (tp, mp, bbt, can_match, false); return TempoMetric (*tp, *mp); } bool TempoMap::set_ramped (TempoPoint & tp, bool yn) { TEMPO_MAP_ASSERT (!_tempos.empty()); if (tp.ramped() == yn) { return false; } Tempos::iterator nxt = _tempos.begin(); ++nxt; for (Tempos::iterator t = _tempos.begin(); nxt != _tempos.end(); ++t, ++nxt) { if (tp == *t) { break; } } if (nxt == _tempos.end()) { return false; } std::cerr << "pre ramped to " << yn << std::endl; dump (std::cerr); if (yn) { std::cerr << "set end of " << tp << " to match " << *nxt << std::endl; tp.set_end_npm (nxt->end_note_types_per_minute()); } else { tp.set_end_npm (tp.note_types_per_minute()); } std::cerr << "post ramped to " << yn << std::endl; dump (std::cerr); reset_starting_at (tp.sclock()); std::cerr << "final post ramped to " << yn << std::endl; dump (std::cerr); return true; } bool TempoMap::set_continuing (TempoPoint& tp, bool yn) { if (!yn) { tp.set_continuing (false); return true; /* change made */ } TempoPoint const * prev = previous_tempo (tp); if (!prev) { return false; } tp.set_note_types_per_minute (prev->note_types_per_minute()); return true; } #if 1 void TempoMap::stretch_tempo (TempoPoint& focus, double tempo_value) { /* Our goal is to alter the outound tempo at @param focus and at the same * time create & modify a ramp between the previous tempo and @param focus * so that @param remains in the same location. * * The user has placed @param focus at the correct point, but wanfocus to * adjust the (outbound) tempo without creating an obvious step change * at @param focus. So we want to ramp from prev to focus */ TempoPoint* prev = const_cast (previous_tempo (focus)); TempoPoint old_prev (*prev); TempoPoint old_focus (focus); focus.set_note_types_per_minute (tempo_value); focus.set_end_npm (tempo_value); prev->set_end_npm (tempo_value); prev->compute_omega_from_next_tempo (focus); superclock_t err = prev->superclock_at (focus.beats()) - focus.sclock(); const superclock_t one_sample = superclock_ticks_per_second() / TEMPORAL_SAMPLE_RATE; // const double end_scpqn = focus.superclocks_per_quarter_note(); double scpqn = focus.superclocks_per_quarter_note (); double new_npm; int cnt = 0; reset_starting_at (prev->sclock());; return; while (std::abs(err) >= one_sample) { if (err > 0) { /* estimated > actual: speed end tempo up a little aka reduce scpqn */ scpqn *= 0.99; } else { /* estimated < actual: reduce end tempo a little, aka increase scpqn */ scpqn *= 1.01; } if (scpqn < 1.0) { /* mathematically too small, bail out */ *prev = old_prev; focus = old_focus; return; } /* Convert scpqn to notes-per-minute */ new_npm = ((superclock_ticks_per_second() * 60.0) / scpqn) * (focus.note_type() / 4.0); /* limit range of possible discovered tempo */ if (new_npm < 4.0 && new_npm > 400) { /* too low of a tempo for our taste, bail out */ *prev = old_prev; focus = old_focus; return; } /* set the (initial) tempo, recompute omega and then compute * the (new) error (distance between the predicted position of * the next marker and its actual (fixed) position. */ focus.set_note_types_per_minute (new_npm); focus.set_end_npm (new_npm); prev->set_end_npm (new_npm); prev->compute_omega_from_next_tempo (focus); err = prev->superclock_at (focus.beats()) - focus.sclock(); ++cnt; } // std::cerr << "that took " << cnt << " iterations to get to < 1 sample\n"; // std::cerr << "final focus: " << focus << std::endl; // std::cerr << "final prev: " << *prev << std::endl; reset_starting_at (prev->sclock()); // dump (std::cerr); } #else /* Adjusts the outgoing tempo at @p ts so that the next Tempo point is at @p * end_sample, while keeping the beat time positions of both the same. * * i.e. literally "stretches" out a tempo section (between two markers) by * speeding or slowing the initial outbound tempo and ramping to the end * tempo. */ void TempoMap::stretch_tempo (TempoPoint* ts, samplepos_t sample, samplepos_t end_sample, Beats const & start_qnote, Beats const & end_qnote) { /* Ts (future prev_t) Tnext | | | [drag^] | |----------|---------- e_f qn_beats(sample) */ if (!ts) { return; } TempoPoint* next_t = const_cast (next_tempo (*ts)); /* no stretching of the final tempo, where final includes "terminated * by a BBT marker" */ if (!next_t || dynamic_cast (next_t)) { return; } superclock_t start_sclock = samples_to_superclock (sample, TEMPORAL_SAMPLE_RATE); superclock_t end_sclock = samples_to_superclock (end_sample, TEMPORAL_SAMPLE_RATE); /* minimum allowed measurement distance in samples */ const superclock_t min_delta_sclock = samples_to_superclock (2, TEMPORAL_SAMPLE_RATE); double new_bpm; if (ts->continuing()) { /* this tempo point is required to start using the same bpm * that the previous tempo ended with. */ TempoPoint* prev_to_ts = const_cast (previous_tempo (*ts)); TEMPO_MAP_ASSERT (prev_to_ts); /* the change in samples is the result of changing the slope of at most 2 previous tempo sections. * constant to constant is straightforward, as the tempo prev to ts has constant slope. */ double contribution = 0.0; if (next_t && prev_to_ts->ramped()) { const DoubleableBeats delta_tp = ts->beats() - prev_to_ts->beats(); const DoubleableBeats delta_np = next_t->beats() - prev_to_ts->beats(); contribution = delta_tp.to_double() / delta_np.to_double(); } samplepos_t const fr_off = end_sclock - start_sclock; sampleoffset_t const ts_sample_contribution = fr_off - (contribution * (double) fr_off); if (start_sclock > prev_to_ts->sclock() + min_delta_sclock && (start_sclock + ts_sample_contribution) > prev_to_ts->sclock() + min_delta_sclock) { DoubleableBeats delta_sp = start_qnote - prev_to_ts->beats(); DoubleableBeats delta_ep = end_qnote - prev_to_ts->beats(); new_bpm = ts->note_types_per_minute() * (delta_sp.to_double() / delta_ep.to_double()); } else { new_bpm = ts->note_types_per_minute(); } } else { /* ts is free to have it's bpm changed to any value (within limits) */ if (start_sclock > ts->sclock() + min_delta_sclock && end_sclock > ts->sclock() + min_delta_sclock) { new_bpm = ts->note_types_per_minute() * ((start_sclock - ts->sclock()) / (double) (end_sclock - ts->sclock())); } else { new_bpm = ts->note_types_per_minute(); } new_bpm = std::min (new_bpm, 1000.0); } /* don't clamp and proceed here. testing has revealed that this can go negative, which is an entirely different thing to just being too low. */ if (new_bpm < 0.5) { return; } ts->set_note_types_per_minute (new_bpm); if (ts->continuing()) { TempoPoint* prev = 0; if ((prev = const_cast (previous_tempo (*ts))) != 0) { prev->set_end_npm (ts->end_note_types_per_minute()); } } reset_starting_at (ts->sclock() + 1); } #endif void TempoMap::stretch_tempo_end (TempoPoint* ts, samplepos_t sample, samplepos_t end_sample) { /* Ts (future prev_t) Tnext | | | [drag^] | |----------|---------- e_f qn_beats(sample) */ if (!ts) { return; } const superclock_t start_sclock = samples_to_superclock (sample, TEMPORAL_SAMPLE_RATE); const superclock_t end_sclock = samples_to_superclock (end_sample, TEMPORAL_SAMPLE_RATE); TempoPoint * prev_t = const_cast (previous_tempo (*ts)); if (!prev_t) { return; } /* minimum allowed measurement distance in superclocks */ const superclock_t min_delta_sclock = samples_to_superclock (2, TEMPORAL_SAMPLE_RATE); double new_bpm; if (start_sclock > prev_t->sclock() + min_delta_sclock && end_sclock > prev_t->sclock() + min_delta_sclock) { new_bpm = prev_t->end_note_types_per_minute() * ((prev_t->sclock() - start_sclock) / (double) (prev_t->sclock() - end_sclock)); } else { new_bpm = prev_t->end_note_types_per_minute(); } new_bpm = std::min (new_bpm, (double) 1000.0); if (new_bpm < 0.5) { return; } prev_t->set_end_npm (new_bpm); if (ts->continuing()) { ts->set_note_types_per_minute (prev_t->note_types_per_minute()); } reset_starting_at (prev_t->sclock()); } bool TempoMap::solve_ramped_twist (TempoPoint& earlier, TempoPoint& later) { superclock_t err = earlier.superclock_at (later.beats()) - later.sclock(); const superclock_t one_sample = superclock_ticks_per_second() / TEMPORAL_SAMPLE_RATE; double end_scpqn = earlier.end_superclocks_per_quarter_note(); double new_end_npm; int cnt = 0; while (std::abs(err) >= one_sample) { if (err > 0) { /* estimated > actual: speed end tempo up a little aka reduce scpqn */ end_scpqn *= 0.99; } else { /* estimated < actual: reduce end tempo a little, aka increase scpqn */ end_scpqn *= 1.01; } if (end_scpqn < 1.0) { /* mathematically too small, bail out */ return false; } /* Convert scpqn to notes-per-minute */ new_end_npm = ((superclock_ticks_per_second() * 60.0) / end_scpqn) * (earlier.note_type() / 4.0); /* limit range of possible discovered tempo */ if (new_end_npm < 4.0 && new_end_npm > 400) { /* too low of a tempo for our taste, bail out */ return false; } /* set the (initial) tempo, recompute omega and then compute * the (new) error (distance between the predicted position of * the later marker and its actual (fixed) position. */ earlier.set_end_npm (new_end_npm); earlier.compute_omega_from_next_tempo (later); err = earlier.superclock_at (later.beats()) - later.sclock(); if (cnt > 20000) { // std::cerr << "nn: " << new_end_npm << " err " << err << " @ " << cnt << "solve_ramped_twist FAILED\n"; return false; } ++cnt; } // std::cerr << "that took " << cnt << " iterations to get to < 1 sample\n"; return true; } bool TempoMap::solve_constant_twist (TempoPoint& earlier, TempoPoint& later) { superclock_t err = earlier.superclock_at (later.beats()) - later.sclock(); const superclock_t one_sample = superclock_ticks_per_second() / TEMPORAL_SAMPLE_RATE; double start_npm = earlier.superclocks_per_quarter_note (); int cnt = 0; while (std::abs(err) >= one_sample) { if (err > 0) { /* estimated > actual: speed end tempo up a little aka reduce scpqn */ start_npm *= 0.99; } else { /* estimated < actual: reduce end tempo a little, aka increase scpqn */ start_npm *= 1.01; } /* Convert scpqn to notes-per-minute */ double new_npm = ((superclock_ticks_per_second() * 60.0) / start_npm) * (earlier.note_type() / 4.0); /* limit range of possible discovered tempo */ if (new_npm < 4.0 && new_npm > 400) { /* too low of a tempo for our taste, bail out */ return false; } /* set the (initial) tempo, and then compute * the (new) error (distance between the predicted position of * the later marker and its actual (fixed) position. */ earlier.set_note_types_per_minute (new_npm); earlier.set_end_npm (new_npm); err = earlier.superclock_at (later.beats()) - later.sclock(); if (cnt > 20000) { // std::cerr << "nn: " << new_npm << " err " << err << " @ " << cnt << "solve_constant_twist FAILED\n"; return false; } ++cnt; } // std::cerr << "that took " << cnt << " iterations to get to < 1 sample\n"; return true; } void TempoMap::constant_twist_tempi (TempoPoint& prev, TempoPoint& focus, TempoPoint& next, double tempo_value) { /* Check if the new tempo value is within an acceptable range */ if (tempo_value < 4.0 || tempo_value > 400) { std::cerr << "can't set tempo to " << tempo_value << " ....fail\n"; return; } TempoPoint old_prev (prev); TempoPoint old_focus (focus); /* Our job here is to reposition @param focus without altering the * positions of @param prev and @param next. We do this by changing * the tempo of prev (as opposed to ramped_twist_tempi, below ) */ /* set a fixed tempo for the previous marker (this results in 'focus' moving a bit with the mouse) */ prev.set_note_types_per_minute (tempo_value); prev.set_end_npm (tempo_value); /* reposition focus, using prev to define audio time; leave beat time * and BBT alone */ focus.set (prev.superclock_at (focus.beats()), focus.beats(), focus.bbt()); /* Now iteratively adjust focus.superclocks_per_quarter_note() (the * section's starting tempo) so that next.sclock() remains within 1 * sample of its current position */ if (!solve_constant_twist (focus, next)) { prev = old_prev; focus = old_focus; return; } } void TempoMap::ramped_twist_tempi (TempoPoint& unused, TempoPoint& focus, TempoPoint& next, double tempo_value) { /* Check if the new tempo value is within an acceptable range */ if (tempo_value < 4.0 || tempo_value > 400) { return; } /* Our job here is to tweak the ramp of @param focus without * altering the positions of @param focus and @param next. * We are "twisting" the tempo section between those markers * to enact a change but without moving the markers themselves * * Start by saving the current state of focus in case we need * to bail out because change is impossible. */ TempoPoint old_focus (focus); /* set start tempo of prev tempo marker; we will iteratively solve for the required ramp value */ focus.set_note_types_per_minute (tempo_value); if (!solve_ramped_twist (focus, next)) { focus = old_focus; return; } } void TempoMap::init () { WritableSharedPtr new_map (new TempoMap ()); _map_mgr.init (new_map); fetch (); } TempoMap::WritableSharedPtr TempoMap::write_copy() { return _map_mgr.write_copy(); } int TempoMap::update (TempoMap::WritableSharedPtr m) { if (!_map_mgr.update (m)) { return -1; } /* update thread local map pointer in the calling thread */ update_thread_tempo_map (); #ifndef NDEBUG if (DEBUG_ENABLED (DEBUG::TemporalMap)) { m->dump (std::cerr); } #endif MapChanged (); /* EMIT SIGNAL */ return 0; } void TempoMap::abort_update () { /* drop lock taken by write_copy() */ _map_mgr.abort (); /* update thread local map pointer in calling thread. Note that this will reset _tempo_map_p, which is (almost guaranteed to be) the only reference to the copy of the map made in ::write_copy(), so it will be destroyed here. */ TempoMap::fetch (); } void TempoMap::midi_clock_beat_at_or_after (samplepos_t const pos, samplepos_t& clk_pos, uint32_t& clk_beat) const { /* Sequences are always assumed to start on a MIDI Beat of 0 (ie, the downbeat). * * There are 24 MIDI clock per quarter note (1 Temporal::Beat) * * from http://midi.teragonaudio.com/tech/midispec/seq.htm */ superclock_t sc (samples_to_superclock (pos, TEMPORAL_SAMPLE_RATE)); TempoPoint const & tp (tempo_at (sc)); Temporal::Beats b = (tp.quarters_at_sample (pos)).round_up_to_beat (); again: /* We cannot use * clk_pos = sample_at (b); * because in this case we have to round up to the start * of the next tick, not round to to the current tick. * (compare to 14da117bc88) */ clk_pos = PBD::muldiv_round (superclock_at (b), TEMPORAL_SAMPLE_RATE, superclock_ticks_per_second ()); /* Each MIDI Beat spans 6 MIDI Clocks. * In other words, each MIDI Beat is a 16th note (since there are 24 MIDI * Clocks in a quarter note, therefore 4 MIDI Beats also fit in a quarter). * So, a master can sync playback to a resolution of any particular 16th note. */ clk_beat = b.get_beats () * 4 ; // 4 = 24 / 6; /* It can happen that the computed beat is actually slightly before * pos. For example, if @p pos is 441002, this is less than 1 tick * beyond beat 20 (at 120bpm, 44100Hz, 4/4). The call to * quarters_at_sample(pos) will return 20:0, which does not (and * cannot) be rounded up). But when we convert that back to a sample * time, we get 441000, which is before the time (@p pos) that we are * meant to be computing for. So .. advance to the next beat, and do it * again. */ if (clk_pos < pos) { b += Beats (1, 0); goto again; } } /******** OLD STATE LOADING CODE SECTION *************/ #if 0 static bool bbt_time_to_string (const BBT_Time& bbt, std::string& str) { char buf[256]; int retval = snprintf (buf, sizeof(buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, bbt.bars, bbt.beats, bbt.ticks); if (retval <= 0 || retval >= (int)sizeof(buf)) { return false; } str = buf; return true; } #endif static bool string_to_bbt_time (const std::string& str, BBT_Time& bbt) { if (sscanf (str.c_str (), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, &bbt.bars, &bbt.beats, &bbt.ticks) == 3) { return true; } return false; } int TempoMap::parse_tempo_state_3x (const XMLNode& node, LegacyTempoState& lts) { BBT_Time bbt; std::string start_bbt; // _legacy_bbt.bars = 0; // legacy session check compars .bars != 0; default BBT_Time c'tor uses 1. if (node.get_property ("start", start_bbt)) { if (string_to_bbt_time (start_bbt, bbt)) { /* legacy session - start used to be in bbt*/ // _legacy_bbt = bbt; // set_pulse(-1.0); info << _("Legacy session detected. TempoSection XML node will be altered.") << endmsg; } } if (!node.get_property ("frame", lts.sample)) { error << _("Legacy tempo section XML does not have a \"frame\" node - map will be ignored") << endmsg; cerr << _("Legacy tempo section XML does not have a \"frame\" node - map will be ignored") << endl; return -1; } if (!node.get_property ("pulse", lts.pulses)) { error << _("Legacy tempo section XML does not have a \"pulse\" node - map will be ignored") << endmsg; cerr << _("Legacy tempo section XML does not have a \"pulse\" node - map will be ignored") << endl; return -1; } if (node.get_property ("beats-per-minute", lts.note_types_per_minute)) { if (lts.note_types_per_minute < 0.0) { error << _("TempoSection XML node has an illegal \"beats_per_minute\" value") << endmsg; return -1; } } if (!node.get_property ("note-type", lts.note_type)) { if (lts.note_type < 1.0) { error << _("TempoSection XML node has an illegal \"note-type\" value") << endmsg; return -1; } } else { /* older session, make note type be quarter by default */ lts.note_type = 4.0; } /* older versions used "clamped" as the property name here */ if (!node.get_property ("clamped", lts.continuing)) { lts.continuing = false; } if (node.get_property ("end-beats-per-minute", lts.end_note_types_per_minute)) { if (lts.end_note_types_per_minute < 0.0) { info << _("TempoSection XML node has an illegal \"end-beats-per-minute\" value") << endmsg; return -1; } } Tempo::Type old_type; if (node.get_property ("tempo-type", old_type)) { /* sessions with a tempo-type node contain no end-beats-per-minute. if the legacy node indicates a constant tempo, simply fill this in with the start tempo. otherwise we need the next neighbour to know what it will be. */ if (old_type == Tempo::Constant) { lts.end_note_types_per_minute = lts.note_types_per_minute; } else { lts.end_note_types_per_minute = -1.0; } } return 0; } int TempoMap::parse_meter_state_3x (const XMLNode& node, LegacyMeterState& lms) { std::string bbt_str; if (node.get_property ("start", bbt_str)) { if (string_to_bbt_time (bbt_str, lms.bbt)) { /* legacy session - start used to be in bbt*/ info << _("Legacy session detected - MeterSection XML node will be altered.") << endmsg; // set_pulse (-1.0); } else { error << _("MeterSection XML node has an illegal \"start\" value") << endmsg; } } /* position is the only data we extract from older XML */ if (!node.get_property ("frame", lms.sample)) { error << _("Legacy tempo section XML does not have a \"frame\" node - map will be ignored") << endmsg; return -1; } if (!node.get_property ("pulse", lms.pulses)) { error << _("Legacy meter section XML does not have a \"pulse\" node - map will be ignored") << endmsg; cerr << _("Legacy meter section XML does not have a \"pulse\" node - map will be ignored") << endl; return -1; } if (!node.get_property ("beat", lms.beat)) { lms.beat = 0.0; } if (node.get_property ("bbt", bbt_str)) { if (!string_to_bbt_time (bbt_str, lms.bbt)) { error << _("MeterSection XML node has an illegal \"bbt\" value") << endmsg; return -1; } } else { warning << _("MeterSection XML node has no \"bbt\" property") << endmsg; } /* beats-per-bar is old; divisions-per-bar is new */ if (!node.get_property ("divisions-per-bar", lms.divisions_per_bar)) { if (!node.get_property ("beats-per-bar", lms.divisions_per_bar)) { error << _("MeterSection XML node has no \"beats-per-bar\" or \"divisions-per-bar\" property") << endmsg; return -1; } } if (lms.divisions_per_bar < 0.0) { error << _("MeterSection XML node has an illegal \"divisions-per-bar\" value") << endmsg; return -1; } if (!node.get_property ("note-type", lms.note_type)) { error << _("MeterSection XML node has no \"note-type\" property") << endmsg; return -1; } if (lms.note_type < 0.0) { error << _("MeterSection XML node has an illegal \"note-type\" value") << endmsg; return -1; } return 0; } int TempoMap::set_state_3x (const XMLNode& node) { XMLNodeList nlist; XMLNodeConstIterator niter; nlist = node.children(); /* Need initial tempo & meter points, because subsequent ones will use * set_tempo() and set_meter() which require pre-existing data */ int32_t initial_tempo_index = -1; int32_t initial_meter_index = -1; int32_t index; bool need_points_clear = true; bool initial_tempo_at_zero = true; bool initial_meter_at_zero = true; for (niter = nlist.begin(), index = 0; niter != nlist.end(); ++niter, ++index) { XMLNode* child = *niter; if ((initial_tempo_index < 0) && (child->name() == Tempo::xml_node_name)) { LegacyTempoState lts; if (parse_tempo_state_3x (*child, lts)) { error << _("Tempo map: could not set new state, restoring old one.") << endmsg; break; } if (lts.sample != 0) { initial_tempo_at_zero = false; } Tempo t (lts.note_types_per_minute, lts.end_note_types_per_minute, lts.note_type); TempoPoint* tp = new TempoPoint (*this, t, samples_to_superclock (0, TEMPORAL_SAMPLE_RATE), Beats::from_double (lts.pulses * 4.0), BBT_Time()); tp->set_continuing (lts.continuing); _tempos.clear (); if (need_points_clear) { _points.clear (); need_points_clear = false; } _tempos.push_back (*tp); _points.push_back (*tp); initial_tempo_index = index; } if ((initial_meter_index < 0) && (child->name() == Meter::xml_node_name)) { LegacyMeterState lms; if (parse_meter_state_3x (*child, lms)) { error << _("Tempo map: could not use old meter state, restoring old one.") << endmsg; break; } if (lms.sample != 0) { initial_meter_at_zero = false; } Meter m (lms.divisions_per_bar, lms.note_type); MeterPoint *mp = new MeterPoint (*this, m, 0, Beats(), BBT_Time()); _meters.clear(); if (need_points_clear) { _points.clear (); need_points_clear = false; } _meters.push_back (*mp); _points.push_back (*mp); initial_meter_index = index; } if (initial_tempo_index >= 0 && initial_meter_index >= 0) { break; } } if (initial_tempo_index < 0 || initial_meter_index < 0) { error << _("Old tempo map information is missing either tempo or meter information - ignored") << endmsg; return -1; } for (niter = nlist.begin(), index = 0; niter != nlist.end(); ++niter, ++index) { XMLNode* child = *niter; if (child->name() == Tempo::xml_node_name) { LegacyTempoState lts; if (parse_tempo_state_3x (*child, lts)) { error << _("Tempo map: could not set new state, restoring old one.") << endmsg; break; } if (index == initial_tempo_index) { /* already added */ continue; } if (index == initial_tempo_index) { if (!initial_tempo_at_zero) { /* already added */ continue; } } Tempo t (lts.note_types_per_minute, lts.end_note_types_per_minute, lts.note_type); set_tempo (t, timepos_t (lts.sample), Beats::from_double (lts.pulses * 4.0)); } else if (child->name() == Meter::xml_node_name) { LegacyMeterState lms; if (parse_meter_state_3x (*child, lms)) { error << _("Tempo map: could not use old meter state, restoring old one.") << endmsg; break; } if (index == initial_meter_index) { if (!initial_meter_at_zero) { /* Add a BBT point to fix the meter location */ set_bartime (lms.bbt, timepos_t (lms.sample)); } else { continue; } } Meter m (lms.divisions_per_bar, lms.note_type); set_meter (m, timepos_t (lms.sample)); } } #if 0 /* check for legacy sessions where bbt was the base musical unit for tempo */ for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { TempoSection* t; if ((t = dynamic_cast (*i)) != 0) { if (t->legacy_bbt().bars != 0) { fix_legacy_session(); break; } if (t->end_note_types_per_minute() < 0.0) { fix_legacy_end_session(); break; } } } if (niter == nlist.end()) { MetricSectionSorter cmp; _metrics.sort (cmp); } #endif #if 0 /* check for multiple tempo/meters at the same location, which ardour2 somehow allowed. */ { Tempos::iterator prev = _tempos.end(); for (Tempos::iterator i = _tempos.begin(); i != _tempos.end(); ++i) { if (prev != _tempos.end()) { MeterSection* ms; MeterSection* prev_m; TempoSection* ts; TempoSection* prev_t; if ((prev_m = dynamic_cast(*prev)) != 0 && (ms = dynamic_cast(*i)) != 0) { if (prev_m->beat() == ms->beat()) { error << string_compose (_("Multiple meter definitions found at %1"), prev_m->beat()) << endmsg; return -1; } } else if ((prev_t = dynamic_cast(*prev)) != 0 && (ts = dynamic_cast(*i)) != 0) { if (prev_t->pulse() == ts->pulse()) { error << string_compose (_("Multiple tempo definitions found at %1"), prev_t->pulse()) << endmsg; return -1; } } } prev = i; } } #endif reset_starting_at (0); return 0; } void TempoMap::map_assert (bool expr, char const * exprstr, char const * file, int line) { if (!expr) { TempoMap::SharedPtr map = TempoMap::use(); std::cerr << "TEMPO MAP LOGIC FAILURE: [" << exprstr << "] at " << file << ':' << line << std::endl; map->dump (std::cerr); abort (); } } double TempoMap::max_notes_per_minute() const { double npm = 0; for (auto const & t : _tempos) { if (t.note_types_per_minute() > npm) { npm = t.note_types_per_minute(); } if (t.end_note_types_per_minute() > npm) { npm = t.end_note_types_per_minute(); } } return npm; } double TempoMap::min_notes_per_minute() const { double npm = std::numeric_limits::max(); for (auto const & t : _tempos) { if (t.note_types_per_minute() < npm) { npm = t.note_types_per_minute(); } if (t.end_note_types_per_minute() < npm) { npm = t.end_note_types_per_minute(); } } return npm; } timepos_t TempoMap::duration(TimeDomain td) const { if (_points.empty()) { return timepos_t::zero (td); } if (td == BeatTime) { return timepos_t (_points.back().beats() + Beats (1, 0)); } return timepos_t::from_superclock (_points.back().sclock() + 1); } #if 0 void TempoMap::fix_legacy_session () { MeterSection* prev_m = 0; TempoSection* prev_t = 0; bool have_initial_t = false; for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { MeterSection* m; TempoSection* t; if ((m = dynamic_cast(*i)) != 0) { if (m->initial()) { pair bbt = make_pair (0.0, BBT_Time (1, 1, 0)); m->set_beat (bbt); m->set_pulse (0.0); m->set_minute (0.0); m->set_position_lock_style (AudioTime); prev_m = m; continue; } if (prev_m) { pair start = make_pair (((m->bbt().bars - 1) * prev_m->note_divisor()) + (m->bbt().beats - 1) + (m->bbt().ticks / BBT_Time::ticks_per_beat) , m->bbt()); m->set_beat (start); const double start_beat = ((m->bbt().bars - 1) * prev_m->note_divisor()) + (m->bbt().beats - 1) + (m->bbt().ticks / BBT_Time::ticks_per_beat); m->set_pulse (start_beat / prev_m->note_divisor()); } prev_m = m; } else if ((t = dynamic_cast(*i)) != 0) { if (!t->active()) { continue; } /* Ramp type never existed in the era of this tempo section */ t->set_end_npm (t->note_types_per_minute()); if (t->initial()) { t->set_pulse (0.0); t->set_minute (0.0); t->set_position_lock_style (AudioTime); prev_t = t; have_initial_t = true; continue; } if (prev_t) { /* some 4.x sessions have no initial (non-movable) tempo. */ if (!have_initial_t) { prev_t->set_pulse (0.0); prev_t->set_minute (0.0); prev_t->set_position_lock_style (AudioTime); prev_t->set_initial (true); prev_t->set_locked_to_meter (true); have_initial_t = true; } const double beat = ((t->legacy_bbt().bars - 1) * ((prev_m) ? prev_m->note_divisor() : 4.0)) + (t->legacy_bbt().beats - 1) + (t->legacy_bbt().ticks / BBT_Time::ticks_per_beat); if (prev_m) { t->set_pulse (beat / prev_m->note_divisor()); } else { /* really shouldn't happen but.. */ t->set_pulse (beat / 4.0); } } prev_t = t; } } } void TempoMap::fix_legacy_end_session () { TempoSection* prev_t = 0; for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { TempoSection* t; if ((t = dynamic_cast(*i)) != 0) { if (!t->active()) { continue; } if (prev_t) { if (prev_t->end_note_types_per_minute() < 0.0) { prev_t->set_end_npm (t->note_types_per_minute()); } } prev_t = t; } } if (prev_t) { prev_t->set_end_npm (prev_t->note_types_per_minute()); } } #endif TempoCommand::TempoCommand (XMLNode const & node) : _before (0) , _after (0) { if (!node.get_property (X_("name"), _name)) { throw failed_constructor(); } XMLNodeList const & children (node.children()); for (XMLNodeList::const_iterator n = children.begin(); n != children.end(); ++n) { if ((*n)->name() == X_("before")) { if ((*n)->children().empty()) { throw failed_constructor(); } _before = new XMLNode (*(*n)->children().front()); } else if ((*n)->name() == X_("after")) { if ((*n)->children().empty()) { throw failed_constructor(); } _after = new XMLNode (*(*n)->children().front()); } } if (!_before || !_after) { throw failed_constructor(); } } TempoCommand::TempoCommand (std::string const & str, XMLNode const * before, XMLNode const * after) : _name (str) , _before (before) , _after (after) { } TempoCommand::~TempoCommand () { delete _before; delete _after; } XMLNode& TempoCommand::get_state() const { XMLNode* node = new XMLNode (X_("TempoCommand")); node->set_property (X_("name"), _name); if (_before) { XMLNode* b = new XMLNode (X_("before")); b->add_child_copy (*_before); node->add_child_nocopy (*b); } if (_after) { XMLNode* a = new XMLNode (X_("after")); a->add_child_copy (*_after); node->add_child_nocopy (*a); } return *node; } void TempoCommand::undo () { if (!_before) { return; } TempoMap::WritableSharedPtr map (TempoMap::write_copy()); map->set_state (*_before, Stateful::current_state_version); TempoMap::update (map); } void TempoCommand::operator() () { if (!_after) { return; } TempoMap::WritableSharedPtr map (TempoMap::write_copy()); map->set_state (*_after, Stateful::current_state_version); TempoMap::update (map); } TempoMapCutBuffer::TempoMapCutBuffer (timecnt_t const & dur) : _start_tempo (nullptr) , _end_tempo (nullptr) , _start_meter (nullptr) , _end_meter (nullptr) , _duration (dur) { } TempoMapCutBuffer::~TempoMapCutBuffer () { delete _start_tempo; delete _end_tempo; delete _start_meter; delete _end_meter; } void TempoMapCutBuffer::add_start_tempo (Tempo const & t) { delete _start_tempo; _start_tempo = new Tempo (t); } void TempoMapCutBuffer::add_end_tempo (Tempo const & t) { delete _end_tempo; _end_tempo = new Tempo (t); } void TempoMapCutBuffer::add_start_meter (Meter const & t) { delete _start_meter; _start_meter = new Meter (t); } void TempoMapCutBuffer::add_end_meter (Meter const & t) { delete _end_meter; _end_meter = new Meter (t); } void TempoMapCutBuffer::dump (std::ostream& ostr) { ostr << "TempoMapCutBuffer @ " << this << std::endl; if (_start_tempo) { ostr << "Start Tempo: " << *_start_tempo << std::endl; } if (_end_tempo) { ostr << "End Tempo: " << *_end_tempo << std::endl; } if (_start_meter) { ostr << "Start Meter: " << *_start_meter << std::endl; } if (_end_meter) { ostr << "End Meter: " << *_end_meter << std::endl; } ostr << "Tempos:\n"; for (auto const & t : _tempos) { ostr << '\t' << &t << ' ' << t << std::endl; } ostr << "Meters:\n"; for (auto const & m : _meters) { ostr << '\t' << &m << ' ' << m << std::endl; } } void TempoMapCutBuffer::add (TempoPoint const & tp) { TempoPoint* ntp = new TempoPoint (tp); /* We must reset the audio and beat time position, but we can't do * anything useful with the BBT time designation. */ ntp->set (ntp->sclock() - _duration.position().superclocks(), ntp->beats() - _duration.position().beats(), ntp->bbt()); _tempos.push_back (*ntp); _points.push_back (*ntp); } void TempoMapCutBuffer::add (MeterPoint const & mp) { MeterPoint* ntp = new MeterPoint (mp); /* We must reset the audio and beat time position, but we can't do * anything useful with the BBT time designation. */ ntp->set (ntp->sclock() - _duration.position().superclocks(), ntp->beats() - _duration.position().beats(), ntp->bbt()); _meters.push_back (*ntp); _points.push_back (*ntp); } void TempoMapCutBuffer::add (MusicTimePoint const & mtp) { MusicTimePoint* ntp = new MusicTimePoint (mtp); /* We must reset the audio and beat time position, but we can't do * anything useful with the BBT time designation. */ ntp->set (ntp->sclock() - _duration.position().superclocks(), ntp->beats() - _duration.position().beats(), ntp->bbt()); _bartimes.push_back (*ntp); _tempos.push_back (*ntp); _meters.push_back (*ntp); _points.push_back (*ntp); } void TempoMapCutBuffer::clear () { _tempos.clear (); _meters.clear (); _bartimes.clear (); _points.clear (); } void ScopedTempoMapOwner::start_local_tempo_map (std::shared_ptr map) { /* This one is a little complicated to explain. This is where we set * the _local_tempo_map pointer which holds the local tempo map that * this ScopedTempoMapOwner wants to use every time one of its methods * is used. But we must also emulate the side effects of calling * ::in(), which include incrementing the depth and setting the * thread-local tempo map pointer. * * Subsequent to that, all later in() and out() calls will install and * uninstall the local tempo map as the depth transitions from and to * zero, and will maintain the depth counter appropriately. */ DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: starting local tempo scope\n", scope_name())); #ifndef NDEBUG if (DEBUG_ENABLED (PBD::DEBUG::ScopedTempoMap)) { PBD::stacktrace (std::cerr, 29); map->dump (std::cerr); } #endif map->set_scope_owner (*this); _local_tempo_map = map; local_tempo_map_depth = 0; Temporal::TempoMap::set (_local_tempo_map); } void ScopedTempoMapOwner::end_local_tempo_map () { /* Like ::start_local_tempo_map() and ::in(), this needs to emulate the * side effects of calling ::out(), ehich are reducing the depth * counter to zero and resetting the thread local tempo map pointer to * the current global tempo map. * * The caller need not have used EC_LOCAL_TEMPO_SCOPE before calling * this, however, since there are no side effects to undo when we leave * the caller's scope. */ DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: ending local tempo scope\n", scope_name())); #ifndef NDEBUG if (DEBUG_ENABLED (PBD::DEBUG::ScopedTempoMap)) { PBD::stacktrace (std::cerr, 29); } #endif assert (_local_tempo_map); _local_tempo_map->clear_scope_owner (); _local_tempo_map.reset (); local_tempo_map_depth = 0; Temporal::TempoMap::fetch (); } /* This is defined here for use in an assert() made in tempo.h. Circular * dependencies between ScopedTempoMapOwner and TempoMap mean that we can't * place uses of a ScopedTempoMapOwner in tempo.h */ bool TempoMap::fetch_condition () { bool ret (!_tempo_map_p || !_tempo_map_p->scope_owner() || _tempo_map_p->scope_owner()->depth() == 0); #ifndef NDEBUG if (!ret) { PBD::stacktrace (std::cerr, 29); } #endif return ret; }