/* 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 "pbd/error.h" #include "pbd/i18n.h" #include "pbd/compose.h" #include "pbd/enumwriter.h" #include "pbd/failed_constructor.h" #include "pbd/stacktrace.h" #include "temporal/debug.h" #include "temporal/tempo.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"); superclock_t Temporal::superclock_ticks_per_second = 508032000; // 2^10 * 3^4 * 5^3 * 7^2 thread_local TempoMap::SharedPtr TempoMap::_tempo_map_p; 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) : _map (&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 timepos_t Point::time() const { switch (_map->time_domain()) { case AudioTime: return timepos_t (sclock()); case BeatTime: return timepos_t (beats()); case BarTime: /*NOTREACHED*/ break; } /*NOTREACHED*/ abort(); /*NOTREACHED*/ return timepos_t(); } Tempo::Tempo (XMLNode const & node) { assert (node.name() == xml_node_name); if (!node.get_property (X_("scpnt-start"), _superclocks_per_note_type)) { throw failed_constructor (); } if (!node.get_property (X_("scpnt-end"), _end_superclocks_per_note_type)) { throw failed_constructor (); } if (!node.get_property (X_("note-type"), _note_type)) { throw failed_constructor (); } if (!node.get_property (X_("type"), _type)) { throw failed_constructor (); } if (!node.get_property (X_("active"), _active)) { throw failed_constructor (); } } bool Tempo::set_ramped (bool yn) { _type = (yn ? Ramped : Constant); return true; } bool Tempo::set_clamped (bool) { #warning implement Tempo::set_clamped return true; } XMLNode& Tempo::get_state () const { XMLNode* node = new XMLNode (xml_node_name); node->set_property (X_("scpnt-start"), superclocks_per_note_type()); node->set_property (X_("scpnt-end"), end_superclocks_per_note_type()); node->set_property (X_("note-type"), note_type()); node->set_property (X_("type"), type()); node->set_property (X_("active"), active()); return *node; } int Tempo::set_state (XMLNode const & node, int /*version*/) { if (node.name() != xml_node_name) { return -1; } node.get_property (X_("scpnt-start"), _superclocks_per_note_type); node.get_property (X_("scpnt-end"), _end_superclocks_per_note_type); node.get_property (X_("note-type"), _note_type); node.get_property (X_("type"), _type); node.get_property (X_("active"), _active); return 0; } Meter::Meter (XMLNode const & node) { 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 -= (r.ticks / tpg); r.ticks = tpg + (r.ticks % Temporal::Beats::PPQN); } if (r.beats < 0) { r.beats += 1; r.bars -= r.beats / _divisions_per_bar; r.beats = r.beats % _divisions_per_bar; r.beats -= 1; } if (r.bars <= 0) { r.bars -= 1; } return Temporal::BBT_Time (r.bars, r.beats, r.ticks); } Temporal::BBT_Time Meter::round_to_bar (Temporal::BBT_Time const & bbt) const { Temporal::BBT_Time b = bbt.round_to_beat (); if (b.beats > _divisions_per_bar/2) { b.bars++; } b.beats = 1; return b; } Temporal::BBT_Time Meter::round_up_to_bar (Temporal::BBT_Time const & bbt) const { if (bbt.ticks == 0 && bbt.beats == 1) { return bbt; } BBT_Time b = bbt.round_up_to_beat (); if (b.beats > 1) { b.bars += 1; b.beats = 1; } return b; } Temporal::BBT_Time Meter::round_down_to_bar (Temporal::BBT_Time const & bbt) const { if (bbt.ticks == 0 && bbt.beats == 1) { return bbt; } BBT_Time b = bbt.round_down_to_beat (); if (b.beats > 1) { b.beats = 1; } return b; } Temporal::BBT_Time Meter::round_up_to_beat (Temporal::BBT_Time const & bbt) const { Temporal::BBT_Time b = bbt.round_up_to_beat (); if (b.beats > _divisions_per_bar) { b.bars++; b.beats = 1; } return b; } Temporal::Beats Meter::to_quarters (Temporal::BBT_Offset const & offset) const { int64_t ticks = 0; 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) { node.get_property (X_("omega"), _omega); } 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) : Tempo (node) , Point (map, node) , _omega (0) { } /* To understand the math(s) behind ramping, see the file doc/tempo.{pdf,tex} */ void TempoPoint::compute_omega (samplecnt_t sr, superclock_t end_scpqn, Temporal::Beats const & quarter_duration) { if ((superclocks_per_quarter_note () == end_scpqn) || (_type == Constant)) { _omega = 0.0; return; } _omega = ((1.0/end_scpqn) - (1.0/superclocks_per_quarter_note())) / quarter_duration.to_double(); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("computed omega = %1%2 dur was %3\n", std::setprecision(12),_omega, quarter_duration.to_double())); } superclock_t TempoPoint::superclock_at (Temporal::Beats const & qn) const { if (qn == _quarters) { return _sclock; } if (!actually_ramped()) { /* not ramped, use linear */ const superclock_t spqn = superclocks_per_quarter_note (); return (spqn * qn.get_beats()) + int_div_round ((spqn * qn.get_ticks()), superclock_t (Temporal::ticks_per_beat)); } return _sclock + llrint (log1p (superclocks_per_quarter_note() * _omega * (qn - _quarters).to_double()) / _omega); } 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()); } Temporal::Beats TempoPoint::quarters_at (superclock_t sc) const { if (!actually_ramped()) { /* convert sc into superbeats, given that sc represents some number of seconds */ const superclock_t whole_seconds = sc / superclock_ticks_per_second; const superclock_t remainder = sc - (whole_seconds * superclock_ticks_per_second); const superclock_t superbeats = ((_super_note_type_per_second/4) * whole_seconds) + int_div_round (superclock_t ((_super_note_type_per_second/4) * remainder), superclock_ticks_per_second); /* convert superbeats to beats:ticks */ int32_t b; int32_t t; Tempo::superbeats_to_beats_ticks (superbeats, b, t); return Beats (b, t); } 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) : Meter (node) , Point (map, 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 { Temporal::BBT_Offset offset = bbt_delta (bbt, _bbt); 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; } Temporal::BBT_Time TempoMetric::bbt_at (superclock_t sc) const { const Beats dq = _tempo->quarters_at (sc) - _meter->beats(); const BBT_Offset bbt_offset (0, dq.get_beats(), dq.get_ticks()); return _meter->bbt_add (_meter->bbt(), bbt_offset); } superclock_t TempoMetric::superclock_at (BBT_Time const & bbt) const { return _tempo->superclock_at (_meter->quarters_at (bbt)); } MusicTimePoint::MusicTimePoint (TempoMap const & map, XMLNode const & node) : Point (map, node) { } XMLNode& MusicTimePoint::get_state () const { XMLNode* node = new XMLNode (X_("MusicTime")); Point::add_state (*node); return *node; } void TempoMapPoint::start_float () { _floating = true; } void TempoMapPoint::end_float () { _floating = false; } /* TEMPOMAP */ struct TraceableWriterLock : public Glib::Threads::RWLock::WriterLock { TraceableWriterLock (Glib::Threads::RWLock & lock) : Glib::Threads::RWLock::WriterLock (lock) { } ~TraceableWriterLock() { } }; TempoMap::TempoMap (Tempo const & initial_tempo, Meter const & initial_meter, samplecnt_t sr) : _sample_rate (sr) , _dirty (false) , _generation (0) // XXX needs to be reloaded from saved state?? , _initial_tempo (*this, initial_tempo, 0, Beats(), BBT_Time()) , _initial_meter (*this, initial_meter, 0, Beats(), BBT_Time()) , _initial_music_time (*this) { TraceableWriterLock lm (_lock); _tempos.push_back (_initial_tempo); _meters.push_back (_initial_meter); _bartimes.push_back (_initial_music_time); _points.push_back (_initial_tempo); _points.push_back (_initial_meter); _points.push_back (_initial_music_time); set_dirty (true); } TempoMap::~TempoMap() { } void TempoMap::set_dirty (bool yn) { DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("tempo map @ %1 dirty set to %2\n", this, yn)); _dirty = yn; } void TempoMap::set_time_domain (TimeDomain td) { if (td == time_domain()) { return; } #warning paul tempo_map::set_time_domain needs implementing #if 0 switch (td) { case AudioTime: for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ++t) { t->set_sclock (t->superclock_at (t->beats ())); } for (Meters::iterator m = _meters.begin(); m != _meters.end(); ++m) { m->set_sclock (m->superclock_at (m->beats ())); } break; default: for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ++t) { t->set_beats (t->quarters_at (t->sclock())); } for (Meters::iterator m = _meters.begin(); m != _meters.end(); ++m) { m->set_beats (m->quarters_at (m->sclock())); } } #endif _time_domain = td; set_dirty (true); } MeterPoint* TempoMap::add_meter (MeterPoint & mp) { /* CALLER MUST HOLD LOCK */ Meters::iterator m; switch (time_domain()) { case AudioTime: for (m = _meters.begin(); m != _meters.end() && m->sclock() < mp.sclock(); ++m); break; case BeatTime: for (m = _meters.begin(); m != _meters.end() && m->beats() < mp.beats(); ++m); break; case BarTime: for (m = _meters.begin(); m != _meters.end() && m->bbt() < mp.bbt(); ++m); break; } bool replaced = false; MeterPoint* ret = 0; if (m != _meters.end()) { if (m->sclock() == mp.sclock()) { /* overwrite Meter part of this point */ *((Meter*)&(*m)) = mp; ret = &(*m); replaced = true; } } if (!replaced) { ret = &(*(_meters.insert (m, mp))); } reset_starting_at (mp.sclock()); set_dirty (true); return ret; } void TempoMap::change_tempo (TempoPoint & p, Tempo const & t) { TraceableWriterLock lm (_lock); *((Tempo*)&p) = t; set_dirty (true); } TempoPoint & TempoMap::set_tempo (Tempo const & t, superclock_t sc) { TempoPoint * ret; { TraceableWriterLock lm (_lock); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set tempo @ %1 to %2\n", sc, t)); Beats beats; BBT_Time bbt; TempoMetric tm (metric_at_locked (sc, false)); /* tempo changes must be on beat */ beats = tm.quarters_at (sc).round_to_beat (); bbt = tm.bbt_at (beats); /* recompute superclock position of rounded beat */ sc = tm.superclock_at (beats); TempoPoint tp (*this, t, sc, beats, bbt); ret = add_tempo (tp); } Changed (); return *ret; } TempoPoint & TempoMap::set_tempo (Tempo const & t, BBT_Time const & bbt) { TempoPoint * ret = 0; { TraceableWriterLock lm (_lock); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set tempo @ %1 to %2\n", bbt, t)); /* tempo changes are required to be on-beat */ BBT_Time on_beat = bbt.round_to_beat(); superclock_t sc; Beats beats; TempoMetric metric (metric_at_locked (on_beat, false)); beats = metric.quarters_at (on_beat); sc = metric.superclock_at (on_beat); TempoPoint tp (*this, t, sc, beats, on_beat); ret = add_tempo (tp); } Changed (); return *ret; } TempoPoint & TempoMap::set_tempo (Tempo const & t, Beats const & beats) { TempoPoint * ret; { TraceableWriterLock lm (_lock); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set tempo @ %1 to %2\n", beats, t)); /* tempo changes are required to be on-beat */ Beats on_beat = beats.round_to_beat(); superclock_t sc; BBT_Time bbt; TempoMetric metric (metric_at_locked (on_beat, false)); bbt = metric.bbt_at (on_beat); sc = metric.superclock_at (on_beat); TempoPoint tp (*this, t, sc, on_beat, bbt); ret = add_tempo (tp); } Changed (); return *ret; } TempoPoint & TempoMap::set_tempo (Tempo const & t, timepos_t const & time) { if (time.is_beats()) { return set_tempo (t, time.beats()); } return set_tempo (t, time.superclocks()); } TempoPoint* TempoMap::add_tempo (TempoPoint & tp) { /* CALLER MUST HOLD LOCK */ Tempos::iterator t; switch (time_domain()) { case AudioTime: for (t = _tempos.begin(); t != _tempos.end() && t->sclock() < tp.sclock(); ++t); break; case BeatTime: for (t = _tempos.begin(); t != _tempos.end() && t->beats() < tp.beats(); ++t); break; case BarTime: for (t = _tempos.begin(); t != _tempos.end() && t->bbt() < tp.bbt(); ++t); break; } bool replaced = false; TempoPoint* ret = 0; if (t != _tempos.end()) { if (t->sclock() == tp.sclock()) { /* overwrite Tempo part of this point */ *((Tempo*)&(*t)) = tp; ret = &(*t); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("overwrote old tempo with %1\n", tp)); replaced = true; } } if (!replaced) { t = _tempos.insert (t, tp); ret = &*t; DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("inserted tempo %1\n", tp)); } /* t is guaranteed not to be _tempos.end() : it was either the * TempoPoint we overwrote, or its the one we inserted. */ assert (t != _tempos.end()); Tempos::iterator nxt = t; ++nxt; if (t->ramped() && nxt != _tempos.end()) { DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("compute ramp over %1 .. %2 aka %3 .. %4\n", t->sclock(), nxt->sclock(), t->beats(), nxt->beats())); t->compute_omega (_sample_rate, nxt->superclocks_per_quarter_note (), nxt->beats() - t->beats()); } reset_starting_at (tp.sclock()); set_dirty (true); return ret; } void TempoMap::remove_tempo (TempoPoint const & tp) { { TraceableWriterLock lm (_lock); superclock_t sc (tp.sclock()); Tempos::iterator t; for (t = _tempos.begin(); t != _tempos.end() && t->sclock() < tp.sclock(); ++t); if (t->sclock() != tp.sclock()) { /* error ... no tempo point at the time of tp */ return; } _tempos.erase (t); reset_starting_at (sc); set_dirty (true); } Changed (); } MusicTimePoint & TempoMap::set_bartime (BBT_Time const & bbt, timepos_t const & pos) { MusicTimePoint * ret; assert (pos.time_domain() == AudioTime); { TraceableWriterLock lm (_lock); superclock_t sc (pos.superclocks()); TempoMetric metric (metric_at_locked (sc)); MusicTimePoint tp (bbt, Point (*this, sc, metric.quarters_at (sc), bbt)); ret = add_or_replace_bartime (tp); } Changed (); return *ret; } MusicTimePoint* TempoMap::add_or_replace_bartime (MusicTimePoint & tp) { /* CALLER MUST HOLD LOCK */ MusicTimes::iterator m; for (m = _bartimes.begin(); m != _bartimes.end() && m->sclock() < tp.sclock(); ++m); bool replaced = false; MusicTimePoint* ret = 0; if (m != _bartimes.end()) { if (m->sclock() == tp.sclock()) { /* overwrite the point with */ *m = tp; ret = &(*m); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("overwrote old bartime with %1\n", tp)); replaced = true; } } if (!replaced) { m = _bartimes.insert (m, tp); ret = &*m; DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("inserted bartime %1\n", tp)); } /* m is guaranteed not to be _bartimes.end() : it was either the * TempoPoint we overwrote, or its the one we inserted. */ assert (m != _bartimes.end()); reset_starting_at (tp.sclock()); set_dirty (true); return ret; } void TempoMap::remove_bartime (MusicTimePoint const & tp) { { TraceableWriterLock lm (_lock); superclock_t sc (tp.sclock()); MusicTimes::iterator m; for (m = _bartimes.begin(); m != _bartimes.end() && m->sclock() < tp.sclock(); ++m); if (m->sclock() != tp.sclock()) { /* error ... no tempo point at the time of tp */ return; } _bartimes.erase (m); reset_starting_at (sc); set_dirty (true); } Changed (); } void TempoMap::reset_starting_at (superclock_t sc) { /* CALLER MUST HOLD LOCK */ Tempos::iterator t; Meters::iterator m; MusicTimes::iterator b; DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("reset starting at %1\n", sc)); assert (!_tempos.empty()); assert (!_meters.empty()); TempoPoint* current_tempo = 0; MeterPoint* current_meter = 0; /* our task: 1) set t, m and b to the iterators for the tempo, meter and bartime markers (if any) closest to but after @param sc. 2) set current_tempo and current_meter to point to the tempo and meter in effect at @param sc */ if (sc) { for (t = _tempos.begin(); t != _tempos.end() && t->sclock() <= sc; ++t) { current_tempo = &*t; } for (m = _meters.begin(); m != _meters.end() && m->sclock() <= sc; ++m) { current_meter = &*m; } for (b = _bartimes.begin(); b != _bartimes.end() && b->sclock() <= sc; ++b); } else { t = _tempos.begin(); m = _meters.begin(); b = _bartimes.begin(); current_meter = &*m; current_tempo = &*t; } Tempos::iterator nxt_tempo = _tempos.begin(); while ((t != _tempos.end()) || (m != _meters.end()) || (b != _bartimes.end())) { /* UPDATE RAMP COEFFICIENTS WHEN NECESSARY */ if (t->ramped() && (nxt_tempo != _tempos.begin()) && (nxt_tempo != _tempos.end())) { t->compute_omega (_sample_rate, nxt_tempo->superclocks_per_quarter_note (), nxt_tempo->beats() - t->beats()); } /* figure out which of the 1, 2 or 3 possible iterators defines the next explicit point (we want the earliest on the timeline, but there may be more than 1 at the same location). */ Point* first_of_three = 0; superclock_t limit = INT64_MAX; bool is_bartime = false; if (m != _meters.end() && m->sclock() < limit) { first_of_three = &*m; limit = m->sclock(); } if (t != _tempos.end() && t->sclock() < limit) { first_of_three = &*t; limit = t->sclock(); } if (b != _bartimes.end() && b->sclock() < limit) { first_of_three = &*b; limit = b->sclock(); is_bartime = true; } assert (first_of_three); /* Determine whether a tempo or meter or bartime point (or any combination thereof) is defining this new point */ bool advance_meter = false; bool advance_tempo = false; bool advance_bartime = false; TempoMetric metric (*current_tempo, *current_meter); if (m->sclock() == first_of_three->sclock()) { advance_meter = true; current_meter = &*m; DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("\tcurrent point defines meter %1\n", *current_meter)); } if (t->sclock() == first_of_three->sclock()) { advance_tempo = true; current_tempo = &*t; DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("\tcurrent point defines tempo %1\n", *current_tempo)); } if ((b != _bartimes.end()) && (b->sclock() == first_of_three->sclock())) { advance_bartime = true; DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("\tcurrent point defines bartime %1\n", *b)); } if (!is_bartime) { superclock_t sc = metric.superclock_at (first_of_three->bbt()); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("\tbased on %1 move to %2,%3\n", first_of_three->bbt(), sc, first_of_three->beats())); first_of_three->set (sc, first_of_three->beats(), first_of_three->bbt()); } else { } if (advance_meter && (m != _meters.end())) { ++m; } if (advance_tempo && (t != _tempos.end())) { ++t; nxt_tempo = t; ++nxt_tempo; } if (advance_bartime && (b != _bartimes.end())) { ++b; } } DEBUG_TRACE (DEBUG::TemporalMap, "reset done\n"); #ifndef NDEBUG if (DEBUG_ENABLED (DEBUG::TemporalMap)) { dump_locked (cerr); } #endif } bool TempoMap::move_meter (MeterPoint const & mp, timepos_t const & when, bool push) { { TraceableWriterLock lm (_lock); assert (time_domain() != BarTime); assert (!_tempos.empty()); assert (!_meters.empty()); if (_meters.size() < 2 || mp == _meters.front()) { /* not movable */ return false; } superclock_t sc; Beats beats; BBT_Time bbt; TimeDomain td (time_domain()); bool round_up; /* if time domains differ, then asking for the "when" value in * the map domain may involve a tempo map lookup. This requires * a lock, so we have to drop the lock while we do this. */ if (when.time_domain() != time_domain()) { lm.release (); } switch (td) { case AudioTime: sc = when.superclocks(); if (sc > mp.sclock()) { round_up = true; } else { round_up = false; } break; case BeatTime: beats = when.beats (); if (beats > mp.beats ()) { round_up = true; } else { round_up = false; } break; } if (when.time_domain() != time_domain()) { lm.acquire (); } /* 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; switch (time_domain()) { case AudioTime: { for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->sclock() < sc; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->sclock() < sc && *m != mp; ++m) { prev_m = m; } assert (prev_m != _meters.end()); if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); } TempoMetric metric (*prev_t, *prev_m); bbt = metric.bbt_at (sc); bbt = metric.meter().round_to_bar (bbt); for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->bbt() < bbt && *m != mp; ++m) {prev_m = m; } for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->bbt() < bbt; ++t) { prev_t = t; } assert (prev_m != _meters.end()); if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); } metric = TempoMetric (*prev_t, *prev_m); sc = metric.superclock_at (bbt); for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end(); ++m) { if (&*m != &mp) { if (m->sclock() == sc) { return false; } } } beats = metric.quarters_at (bbt); break; } case BeatTime: { /* 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; } assert (prev_m != _meters.end()); if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); } TempoMetric metric (*prev_t, *prev_m); bbt = metric.bbt_at (beats); if (round_up) { bbt = metric.meter().round_up_to_bar (bbt); } else { bbt = metric.meter().round_down_to_bar (bbt); } for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->bbt() < bbt; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->bbt() < bbt && *m != mp; ++m) { prev_m = m; } assert (prev_m != _meters.end()); if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); } 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); break; } default: /* NOTREACHED */ return false; } if (mp.sclock() == sc && mp.beats() == beats && mp.bbt() == bbt) { return false; } const superclock_t old_sc = mp.sclock(); 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 */ assert (current != _meters.end()); /* reset position of this meter */ current->set (sc, beats, bbt); /* reposition in list */ _meters.splice (insert_before, _meters, current); /* recompute 3 domain positions for everything after this */ reset_starting_at (std::min (sc, old_sc)); } Changed (); return true; } bool TempoMap::move_tempo (TempoPoint const & tp, timepos_t const & when, bool push) { { TraceableWriterLock lm (_lock); assert (time_domain() != BarTime); assert (!_tempos.empty()); assert (!_meters.empty()); if (_tempos.size() < 2 || tp == _tempos.front()) { /* not movable */ return false; } superclock_t sc; Beats beats; BBT_Time bbt; TimeDomain td (time_domain()); /* if time domains differ, then asking for the "when" value in * the map domain may involve a tempo map lookup. This requires * a lock, so we have to drop the lock while we do this. */ if (when.time_domain() != time_domain()) { lm.release (); } switch (td) { case AudioTime: sc = when.superclocks(); break; case BeatTime: beats = when.beats (); break; } if (when.time_domain() != time_domain()) { lm.acquire (); } /* 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; switch (time_domain()) { case AudioTime: { for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->sclock() < sc && *t != tp; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->sclock() < sc; ++m) { prev_m = m; } assert (prev_t != _tempos.end()); if (prev_m == _meters.end()) { prev_m = _meters.begin(); } TempoMetric metric (*prev_t, *prev_m); beats = metric.quarters_at (sc); /* tempo changes must be on beat, so round and then * recompute superclock and BBT with rounded result */ beats = beats.round_to_beat (); for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->sclock() < sc && *t != tp; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->sclock() < sc; ++m) { prev_m = m; } assert (prev_t != _tempos.end()); if (prev_m == _meters.end()) { prev_m = _meters.begin(); } metric = TempoMetric (*prev_t, *prev_m); sc = metric.superclock_at (beats); bbt = metric.bbt_at (beats); break; } case BeatTime: { /* tempo changes must be on beat */ beats = beats.round_to_beat (); 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; } assert (prev_t != _tempos.end()); assert (prev_m != _meters.end()); TempoMetric metric (*prev_t, *prev_m); sc = metric.superclock_at (beats); bbt = metric.bbt_at (beats); break; } default: /* NOTREACHED */ return false; } if (tp.sclock() == sc && tp.beats() == beats && tp.bbt() == bbt) { return false; } const superclock_t old_sc = tp.sclock(); 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 */ assert (current != _tempos.end()); /* reset position of this tempo */ current->set (sc, beats, bbt); /* reposition in list */ _tempos.splice (insert_before, _tempos, current); /* Update ramp coefficients when necessary */ if (current->ramped() && insert_before != _tempos.end()) { current->compute_omega (_sample_rate, insert_before->superclocks_per_quarter_note (), insert_before->beats() - current->beats()); } /* recompute 3 domain positions for everything after this */ reset_starting_at (std::min (sc, old_sc)); } Changed (); return true; } MeterPoint & TempoMap::set_meter (Meter const & m, timepos_t const & time) { if (time.is_beats()) { return set_meter (m, time.beats()); } return set_meter (m, time.superclocks()); } MeterPoint & TempoMap::set_meter (Meter const & t, BBT_Time const & bbt) { MeterPoint * ret = 0; { TraceableWriterLock lm (_lock); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set meter @ %1 to %2\n", bbt, t)); TempoMetric metric (metric_at_locked (bbt)); superclock_t sc; Beats beats; BBT_Time rounded_bbt; /* meter changes are required to be on-bar */ rounded_bbt = metric.round_to_bar (bbt); beats = metric.quarters_at (rounded_bbt); sc = metric.superclock_at (beats); MeterPoint mp (*this, t, sc, beats, rounded_bbt); ret = add_meter (mp); reset_starting_at (mp.sclock()); } Changed (); return *ret; } MeterPoint & TempoMap::set_meter (Meter const & t, Beats const & beats) { MeterPoint * ret = 0; { TraceableWriterLock lm (_lock); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set meter @ %1 to %2\n", beats, t)); TempoMetric metric (metric_at_locked (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 (*this, t, sc, rounded_beats, rounded_bbt); ret = add_meter (mp); } Changed (); return *ret; } MeterPoint & TempoMap::set_meter (Meter const & m, superclock_t sc) { MeterPoint * ret = 0; { TraceableWriterLock lm (_lock); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set meter @ %1 to %2\n", sc, m)); Beats beats; BBT_Time bbt; TempoMetric metric (metric_at_locked (sc)); /* meter changes must be on bar */ bbt = metric.bbt_at (beats); 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 (*this, m, sc, beats, bbt); ret = add_meter (mp); } Changed (); return *ret; } void TempoMap::remove_meter (MeterPoint const & mp) { { TraceableWriterLock lm (_lock); superclock_t sc = mp.sclock(); Meters::iterator m = std::upper_bound (_meters.begin(), _meters.end(), mp, Point::sclock_comparator()); if (m->sclock() != mp.sclock()) { /* error ... no meter point at the time of mp */ return; } _meters.erase (m); reset_starting_at (sc); set_dirty (true); } Changed (); } Temporal::BBT_Time TempoMap::bbt_at (timepos_t const & pos) const { if (pos.is_beats()) { return bbt_at (pos.beats()); } return bbt_at (pos.superclocks()); } Temporal::BBT_Time TempoMap::bbt_at (superclock_t s) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (s).bbt_at (s); } Temporal::BBT_Time TempoMap::bbt_at (Temporal::Beats const & qn) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (qn).bbt_at (qn); } #if 0 samplepos_t TempoMap::sample_at (Temporal::Beats const & qn) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return superclock_to_samples (metric_at_locked (qn).superclock_at (qn), _sample_rate); } samplepos_t TempoMap::sample_at (Temporal::BBT_Time const & bbt) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return samples_to_superclock (metric_at_locked (bbt).superclock_at (bbt), _sample_rate); } samplepos_t TempoMap::sample_at (timepos_t const & pos) const { if (pos.is_beat()) { return sample_at (pos.beats ()); } /* somewhat nonsensical to call this under these conditions but ... */ return pos.superclocks(); } #endif superclock_t TempoMap::superclock_at (Temporal::Beats const & qn) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (qn).superclock_at (qn); } superclock_t TempoMap::superclock_at (Temporal::BBT_Time const & bbt) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (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(); } superclock_t TempoMap::superclock_plus_bbt (superclock_t pos, BBT_Time op) const { Glib::Threads::RWLock::ReaderLock lm (_lock); BBT_Time pos_bbt = bbt_at (pos); pos_bbt.ticks += op.ticks; if (pos_bbt.ticks >= ticks_per_beat) { ++pos_bbt.beats; pos_bbt.ticks -= ticks_per_beat; } pos_bbt.beats += op.beats; double divisions_per_bar = metric_at_locked (pos_bbt).divisions_per_bar(); while (pos_bbt.beats >= divisions_per_bar + 1) { ++pos_bbt.bars; divisions_per_bar = metric_at_locked (pos_bbt).divisions_per_bar(); pos_bbt.beats -= divisions_per_bar; } pos_bbt.bars += op.bars; return superclock_at (pos_bbt); } #define S2Sc(s) (samples_to_superclock ((s), _sample_rate)) #define Sc2S(s) (superclock_to_samples ((s), _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 { Glib::Threads::RWLock::ReaderLock lm (_lock); TempoMetric first (metric_at (pos)); TempoMetric last (metric_at (pos+distance)); Temporal::Beats a = first.quarters_at (pos); Temporal::Beats b = last.quarters_at (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) */ Glib::Threads::RWLock::ReaderLock lm (_lock); superclock_t s = metric_at_locked (pos).superclock_at (pos); s += distance; return metric_at_locked (s).quarters_at (s); } Temporal::Beats TempoMap::bbtwalk_to_quarters (Beats const & pos, BBT_Offset const & distance) const { return quarter_note_at (bbt_walk (bbt_at (pos), distance)) - pos; } void TempoMap::set_sample_rate (samplecnt_t new_sr) { TraceableWriterLock lm (_lock); const double ratio = new_sr / (double) _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 { Glib::Threads::RWLock::ReaderLock lm (_lock); dump_locked (ostr); } void TempoMap::dump_locked (std::ostream& ostr) const { for (Tempos::const_iterator t = _tempos.begin(); t != _tempos.end(); ++t) { ostr << &*t << ' ' << *t << endl; } for (Meters::const_iterator m = _meters.begin(); m != _meters.end(); ++m) { ostr << &*m << ' ' << *m << endl; } } void TempoMap::get_grid (TempoMapPoints& ret, superclock_t start, superclock_t end, uint32_t bar_mod) { Glib::Threads::RWLock::ReaderLock lm (_lock); assert (!_tempos.empty()); assert (!_meters.empty()); DEBUG_TRACE (DEBUG::TemporalMap, string_compose (">>> GRID START %1 .. %2 (barmod = %3)\n", start, end, bar_mod)); Tempos::iterator t (_tempos.begin()); Meters::iterator m (_meters.begin()); MusicTimes::iterator b (_bartimes.begin()); TempoMetric metric = metric_at_locked (start, false); BBT_Time bbt = metric.bbt_at (start); #ifndef NDEBUG /* Sanity Check */ if (DEBUG_ENABLED(PBD::DEBUG::TemporalMap)) { TempoMetric emetric = metric_at_locked (end, false); BBT_Time ebbt = metric_at_locked (end).bbt_at (end); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("get grid between %1..%2 [ %4 .. %5 ] { %6 .. %7 } at bar_mod = %3\n", start, end, bar_mod, start, end, bbt, ebbt)); if (metric.quarters_at (bbt).diff (metric.quarters_at (start)) > Beats::ticks (1)) { cerr << "MM1: " << start << " / " << metric.quarters_at (start) << " vs. " << metric.superclock_at (bbt) << " / " << metric.quarters_at (bbt) << " delta " << start - metric.superclock_at (bbt) << " dB " << metric.quarters_at (bbt).diff (metric.quarters_at (start)) << "\n\tused " << metric << endl; abort (); } if (emetric.quarters_at (ebbt).diff (emetric.quarters_at (end)) > Beats::ticks (1)) { cerr << "MM2: " << end << " / " << emetric.quarters_at (end) << " vs. " << emetric.superclock_at (ebbt) << " / " << emetric.quarters_at (ebbt) << " delta " << end - emetric.superclock_at (ebbt) << " dB " << emetric.quarters_at (ebbt).diff (emetric.quarters_at (end)) << "\n\tused " << emetric << endl; abort (); } dump (cerr); } #endif /* first task: get to the right starting point for the requested * grid. if bar_mod is zero, then we'll start on the next beat after * @param start. if bar_mod is non-zero, we'll start on the first bar * after @param start. This bar position may or may not be a part of the * grid, depending on whether or not it is a multiple of bar_mod. */ if (bar_mod == 0) { /* round to next beat, then find the tempo/meter/bartime points * in effect at that time. */ bbt = metric.meter().round_up_to_beat (bbt); for (Tempos::iterator tt = _tempos.begin(); tt != _tempos.end() && tt->sclock() < start; ++tt) { t = tt; } for (Meters::iterator mm = _meters.begin(); mm != _meters.end() && mm->sclock() < start; ++mm) { m = mm; } for (MusicTimes::iterator bb = _bartimes.begin(); bb != _bartimes.end() && bb->sclock() < start; ++bb) { b = bb; } /* reset metric */ metric = TempoMetric (*t, *m); /* recompute superclock position */ superclock_t new_start = metric.superclock_at (bbt); if (new_start < start) { abort (); } start = new_start; } else { /* this rounding cannot change the meter in effect, because it remains within the bar. But it could change the tempo (which are only quantized to grid positions within a bar). */ BBT_Time bar = bbt.round_down_to_bar (); if (bar_mod != 1) { bar.bars -= bar.bars % bar_mod; ++bar.bars; } bbt = bar; for (Tempos::iterator tt = _tempos.begin(); tt != _tempos.end() && tt->bbt() < bbt; ++tt) { t = tt; } for (Meters::iterator mm = _meters.begin(); mm != _meters.end() && mm->bbt() < bbt; ++mm) { m = mm; } for (MusicTimes::iterator bb = _bartimes.begin(); bb != _bartimes.end() && bb->bbt() < bbt; ++bb) { b = bb; } /* t, m and b are now all iterators for the tempo, meter and * position markers BEFORE pos. b may be _bartimes.end(), but * the other two are guaranteed to be valid references into * the tempos and meters */ metric = TempoMetric (*t, *m); start = metric.superclock_at (bbt); } /* advance t, m and b so that the point to the *next* * tempo/meter/position marker (if any) */ Tempos::iterator nxt_t = t; ++nxt_t; Meters::iterator nxt_m = m; ++nxt_m; MusicTimes::iterator nxt_b = b; ++nxt_b; /* at this point: * * - metric is a TempoMetric that describes the situation at pos * - t, m and b reference tempo, meter and position markers at or prior to pos (if any) * - nxt_t, nxt_m, nxt_b reference the tempo, meter and position markers after pos (if any) * * t and m must be valid; b, nxt_t, nxt_m, nxt_b may all refer to ::end() of their respective containers. */ /* outer loop: compute next marker position, if any, and then set limit to the earlier of that position or @param e. * Then run the inner loop to actually add grid points up until limit. Repeat till done. */ while (start < end) { bool advance_tempo = false; bool advance_meter = false; bool advance_bartime = false; Point* first_of_three = 0; superclock_t limit = INT64_MAX; if (nxt_t != _tempos.end() && limit >= nxt_t->sclock()) { first_of_three = &*nxt_t; limit = first_of_three->sclock(); } if (nxt_m != _meters.end() && limit >= nxt_m->sclock()) { first_of_three = &*nxt_m; limit = first_of_three->sclock(); } if (nxt_b != _bartimes.end() && limit >= nxt_b->sclock()) { first_of_three = &*nxt_b; limit = first_of_three->sclock(); } if (first_of_three) { if (nxt_m != _meters.end() && nxt_m->sclock() == first_of_three->sclock()) { advance_meter = true; } if (nxt_t != _tempos.end() && nxt_t->sclock() == first_of_three->sclock()) { advance_tempo = true; } if ((nxt_b != _bartimes.end()) && (b->sclock() == first_of_three->sclock())) { advance_bartime = true; } limit = std::min (end, first_of_three->sclock()); } else { limit = end; } if (start >= limit) { break; } /* Inner loop: add grid points until we hit limit, which is defined by either @param e or the next marker of some kind */ do { /* we already have the superclock and BBT time for the next point, either computed before the loop, or at the bottom of this one. * So now complete the triplet of (superclock,quarters,bbt) */ const Temporal::Beats beats = metric.quarters_at (start); /* add point to grid */ ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt)); DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("G %1\t %2\n", metric, ret.back())); /* Advance by the meter note value size */ superclock_t step; if (bar_mod == 0) { step = metric.superclocks_per_note_type_at_superclock (start); start += step; } else { bbt.bars += bar_mod; /* could have invalidated the current metric */ if (first_of_three && (bbt > first_of_three->bbt())) { start = first_of_three->sclock(); break; } /* move superclock time forward to next (included) bar. Note that we know that metric is still valid because we just checked above if we crossed a marker. */ start = metric.superclock_at (bbt); } if (start >= limit) { /* go back to outer loop to advance iterators and get a new metric */ break; } if (bar_mod == 0) { bbt = metric.bbt_at (start); } } while (true); /* back in outer loop. Check to see if we passed a marker */ if (first_of_three && (start >= first_of_three->sclock())) { if (advance_tempo) { if (nxt_t != _tempos.end()) { t = nxt_t; ++nxt_t; } } if (advance_meter) { if (nxt_m != _meters.end()) { m = nxt_m; ++nxt_m; } } if (advance_bartime) { b = nxt_b; if (nxt_b != _bartimes.end()) { ++nxt_b; } } if (advance_tempo || advance_meter || advance_bartime) { /* we overstepped a marker * if bar_mod is zero, then by definition any * such marker qualifies as a grid point. * * if bar_mod != zero, then check to see if the new * BBT position matches the interval we've been asked * for. If so, use it, otherwise just continue around * the loop, using the new position and metric. */ bbt = first_of_three->bbt (); if (bar_mod != 0) { /* check to see if it matches the interval */ if (!bbt.is_bar() || (bbt.bars % bar_mod != 0)) { /* not usable */ bbt = bbt.round_up_to_bar (); /* reset iterators for new position */ for (Tempos::iterator tt = t; tt != _tempos.end() && tt->bbt() < bbt; ++tt) { t = tt; } for (Meters::iterator mm = m; mm != _meters.end() && mm->bbt() < bbt; ++mm) { m = mm; } for (MusicTimes::iterator bb = b; bb != _bartimes.end() && bb->bbt() < bbt; ++bb) { b = bb; } nxt_t = t; ++nxt_t; nxt_m = m; ++nxt_m; nxt_b = b; ++nxt_b; } } metric = TempoMetric (*t, *m); start = metric.superclock_at (bbt); /* ready to loop because metric, start and bbt are all set correctly, as they were when entering the outer loop */ } } } DEBUG_TRACE (DEBUG::TemporalMap, "<<< GRID DONE\n"); } 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) { 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, 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 << (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 = " << tmp.tempo().omega(); } return str; } superclock_t TempoMap::superclock_plus_quarters_as_superclock (superclock_t start, Temporal::Beats const & distance) const { Glib::Threads::RWLock::ReaderLock lm (_lock); TempoMetric metric (metric_at_locked (start)); const Temporal::Beats start_qn = metric.quarters_at (start); const Temporal::Beats end_qn = start_qn + distance; TempoMetric end_metric (metric_at (end_qn)); return superclock_to_samples (end_metric.superclock_at (end_qn), _sample_rate); } Temporal::Beats TempoMap::superclock_delta_as_quarters (superclock_t start, superclock_t distance) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return quarter_note_at (start + distance) - quarter_note_at (start); } Temporal::superclock_t TempoMap::superclock_quarters_delta_as_superclock (superclock_t start, Temporal::Beats const & distance) const { Glib::Threads::RWLock::ReaderLock lm (_lock); Temporal::Beats start_qn = metric_at_locked (start).quarters_at (start); start_qn += distance; return metric_at_locked (start_qn).superclock_at (start_qn); } #if 0 void TempoMap::update_one_domain_from_another (timepos_t const & src, void* dst, TimeDomain target_domain) const { switch (target_domain) { case AudioTime: *((samplepos_t*) dst) = sample_at (src); break; case BeatTime: *((Beats*) dst) = quarter_note_at (src); break; case BarTime: *((BBT_Time*) dst) = bbt_at (src); break; } } int TempoMap::update_music_times (int generation, samplepos_t pos, Temporal::Beats & b, Temporal::BBT_Time & bbt, bool force) { Glib::Threads::RWLock::ReaderLock lm (_lock); if (force || (generation != _generation)) { const superclock_t sc = S2Sc (pos); TempoMetric tm (metric_at_locked (sc)); b = tm.quarters_at (sc); bbt = tm.bbt_at (sc); } return _generation; } int TempoMap::update_samples_and_bbt_times (int generation, Temporal::Beats const & b, samplepos_t & pos, Temporal::BBT_Time & bbt, bool force) { Glib::Threads::RWLock::ReaderLock lm (_lock); if (force || (generation != _generation)) { TempoMetric metric (metric_at_locked (b)); pos = samples_to_superclock (metric.superclock_at (b), _sample_rate); bbt = metric.bbt_at (b); } return _generation; } int TempoMap::update_samples_and_beat_times (int generation, Temporal::BBT_Time const & bbt, samplepos_t & pos, Temporal::Beats & b, bool force) { Glib::Threads::RWLock::ReaderLock lm (_lock); if (force || (generation != _generation)) { TempoMetric metric = metric_at_locked (bbt); pos = superclock_to_samples (metric.superclock_at (bbt), _sample_rate); b = metric.quarters_at (bbt); } return _generation; } #endif superclock_t TempoMap::superclock_per_quarter_note_at (superclock_t pos) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (pos).superclocks_per_quarter_note (); } BBT_Time TempoMap::bbt_walk (BBT_Time const & bbt, BBT_Offset const & o) const { Glib::Threads::RWLock::ReaderLock lm (_lock); BBT_Offset offset (o); Tempos::const_iterator t, prev_t, next_t; Meters::const_iterator m, prev_m, next_m; assert (!_tempos.empty()); assert (!_meters.empty()); /* trivial (and common) case: single tempo, single meter */ if (_tempos.size() == 1 && _meters.size() == 1) { return _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 tiem given */ if (t != _tempos.end() && t->bbt() == bbt) { prev_t = t; } if (m != _meters.end() && m->bbt() == bbt) { prev_m = m; } /* see ::metric_at_locked() for comments about the use of const_cast here */ TempoMetric metric (*const_cast(&*prev_t), *const_cast(&*prev_m)); superclock_t pos = metric.superclock_at (bbt); /* 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 tick count, now guaranteed to be less than 1 grid unit */ if (offset.ticks) { pos += metric.superclocks_per_ppqn () * offset.ticks; } /* 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 \ if (((next_t != _tempos.end()) && (pos >= next_t->sclock())) || \ ((next_m != _meters.end()) && (pos >= next_m->sclock()))) { \ /* need new metric */ \ if (pos >= next_t->sclock()) { \ if (pos >= next_m->sclock()) { \ metric = TempoMetric (*const_cast(&*next_t), *const_cast(&*next_m)); \ ++next_t; \ ++next_m; \ } else { \ metric = TempoMetric (*const_cast(&*next_t), metric.meter()); \ ++next_t; \ } \ } else if (pos >= next_m->sclock()) { \ metric = TempoMetric (metric.tempo(), *const_cast(&*next_m)); \ ++next_m; \ } \ } for (int32_t b = 0; b < offset.beats; ++b) { TEMPO_CHECK_FOR_NEW_METRIC; pos += metric.superclocks_per_grid (_sample_rate); } /* add each bar, 1 by 1, rechecking to see if there's a new * TempoMetric in effect after each addition */ for (int32_t b = 0; b < offset.bars; ++b) { TEMPO_CHECK_FOR_NEW_METRIC; pos += metric.superclocks_per_bar (_sample_rate); } return metric.bbt_at (pos); } Temporal::Beats TempoMap::quarter_note_at (timepos_t const & pos) const { if (pos.is_beats()) { /* a bit redundant */ return pos.beats(); } return quarter_note_at (pos); } Temporal::Beats TempoMap::quarter_note_at (Temporal::BBT_Time const & bbt) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (bbt).quarters_at (bbt); } Temporal::Beats TempoMap::quarter_note_at (superclock_t pos) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (pos).quarters_at (pos); } XMLNode& TempoMap::get_state () { XMLNode* node = new XMLNode (X_("TempoMap")); Glib::Threads::RWLock::ReaderLock lm (_lock); node->set_property (X_("time-domain"), _time_domain); 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) { 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) { 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*/) { { TraceableWriterLock lm (_lock); /* global map properties */ /* XXX this should probably be at the global level in the session file because it affects a lot more than just the tempo map, potentially */ node.get_property (X_("superclocks-per-second"), superclock_ticks_per_second); node.get_property (X_("time-domain"), _time_domain); XMLNodeList const & children (node.children()); 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; } } } } Changed (); return 0; } int TempoMap::set_music_times_from_state (XMLNode const& tempos_node) { return 0; } int TempoMap::set_tempos_from_state (XMLNode const& tempos_node) { /* CALLER MUST HOLD LOCK */ XMLNodeList const & children (tempos_node.children()); try { _tempos.clear (); for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { TempoPoint tp (*this, **c); // _tempos.push_back (TempoPoint (*this, **c)); _tempos.push_back (tp); } } catch (...) { _tempos.clear (); /* remove any that were created */ return -1; } return 0; } int TempoMap::set_meters_from_state (XMLNode const& meters_node) { /* CALLER MUST HOLD LOCK */ XMLNodeList const & children (meters_node.children()); try { _meters.clear (); for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { MeterPoint mp (*this, **c); _meters.push_back (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 sample duration 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 duration in superclocks between pos and bbt */ superclock_t TempoMap::bbt_duration_at (superclock_t pos, const BBT_Time& bbt, int /* dir_ignored */ ) const { #warning paul maybe get rid of this // return full_duration_at (pos, timecnt_t (bbt, pos), AudioTime).sclock(); return 0; } /** Takes a duration (in any time domain) and considers it as a distance from the given position. * Returns a distance in the requested domain, taking tempo changes into account. * * Obviously, if the given distance is in the same time domain as the requested domain, * the returned distance is identical to the given one. */ timecnt_t TempoMap::full_duration_at (timepos_t const & pos, timecnt_t const & duration, TimeDomain return_domain) const { timepos_t p; Beats b; superclock_t s; assert (pos.time_domain() != BarTime); assert (duration.time_domain() != BarTime); assert (return_domain != BarTime); if (return_domain == duration.time_domain()) { return duration; } 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 (pos.time_domain()) { case BeatTime: /* pos is already in beats */ p = pos; break; case AudioTime: /* Determine beats at sc pos, so that we can add beats */ p = metric_at (pos).quarters_at (pos.superclocks()); break; } /* add beats */ p += duration; /* determine superclocks */ s = metric_at (p).superclock_at (p.beats()); /* return duration in sc */ return timecnt_t (s - pos.superclocks(), pos); break; } break; case BeatTime: switch (duration.time_domain()) { case AudioTime: /* duration is in superclocks but we're asked to return beats */ switch (pos.time_domain ()) { case AudioTime: /* pos is already in superclocks */ p = pos; break; case BeatTime: /* determined sc at beat position so we can add superclocks */ p = metric_at (pos).superclock_at (pos.beats()); break; } /* add superclocks */ p += duration; /* determine beats */ b = metric_at (p).quarters_at (p.superclocks()); /* return duration in beats */ return timecnt_t (b - pos.beats(), pos); break; case BeatTime: /*NOTREACHED*/ break; } break; } /*NOTREACHED*/ abort (); /*NOTREACHED*/ return timecnt_t (0, timepos_t()); } Tempo const * TempoMap::next_tempo (Tempo const & t) const { Glib::Threads::RWLock::ReaderLock lm (_lock); Tempos::const_iterator p = _tempos.begin(); while (p != _tempos.end()) { if (&t == &*p) { break; } ++p; } if (p != _tempos.end()) { ++p; if (p != _tempos.end()) { return &*p;; } } return 0; } uint32_t TempoMap::n_meters () const { Glib::Threads::RWLock::ReaderLock lm (_lock); return _meters.size(); } uint32_t TempoMap::n_tempos () const { Glib::Threads::RWLock::ReaderLock lm (_lock); return _tempos.size(); } void TempoMap::insert_time (timepos_t const & pos, timecnt_t const & duration) { assert (time_domain() != BarTime); assert (!_tempos.empty()); assert (!_meters.empty()); if (pos == std::numeric_limits::min()) { /* can't insert time at the front of the map: those entries are fixed */ return; } { TraceableWriterLock lm (_lock); Tempos::iterator t (_tempos.begin()); Meters::iterator m (_meters.begin()); MusicTimes::iterator b (_bartimes.begin()); TempoPoint current_tempo = *t; MeterPoint current_meter = *m; MusicTimePoint current_time_point (*this); if (_bartimes.size() > 0) { current_time_point = *b; } superclock_t sc; Beats beats; BBT_Time bbt; /* set these to true so that we set current_* on our first pass * through the while loop(s) */ bool moved_tempo = true; bool moved_meter = true; bool moved_bartime = true; switch (duration.time_domain()) { case AudioTime: sc = pos.superclocks(); /* handle a common case quickly */ if ((_tempos.size() < 2 || sc > _tempos.back().sclock()) && (_meters.size() < 2 || sc > _meters.back().sclock()) && (_bartimes.size() < 2 || (_bartimes.empty() || sc > _bartimes.back().sclock()))) { /* only one tempo, plus one meter and zero or one bartimes, or insertion point is after last item. nothing to do here. */ return; } /* advance fundamental iterators to correct position */ while (t != _tempos.end() && t->sclock() < sc) ++t; while (m != _meters.end() && m->sclock() < sc) ++m; while (b != _bartimes.end() && b->sclock() < sc) ++b; while (t != _tempos.end() && m != _meters.end() && b != _bartimes.end()) { if (moved_tempo && t != _tempos.end()) { current_tempo = *t; moved_tempo = false; } if (moved_meter && m != _meters.end()) { current_meter = *m; moved_meter = false; } if (moved_bartime && b != _bartimes.end()) { current_time_point = *b; moved_bartime = false; } /* for each of t, m and b: if the point is earlier than the other two, recompute the superclock, beat and bbt positions, and reset the point. */ if (t->sclock() < m->sclock() && t->sclock() < b->sclock()) { sc = t->sclock() + duration.superclocks(); beats = current_tempo.quarters_at (sc); /* round tempo to beats */ beats = beats.round_to_beat (); sc = current_tempo.superclock_at (beats); bbt = current_meter.bbt_at (beats); t->set (sc, beats, bbt); ++t; moved_tempo = true; } if (m->sclock() < t->sclock() && m->sclock() < b->sclock()) { sc = m->sclock() + duration.superclocks(); beats = current_tempo.quarters_at (sc); /* round meter to bars */ bbt = current_meter.bbt_at (beats); beats = current_meter.quarters_at (current_meter.round_to_bar(bbt)); /* recompute */ sc = current_tempo.superclock_at (beats); m->set (sc, beats, bbt); ++m; moved_meter = true; } if (b->sclock() < t->sclock() && b->sclock() < m->sclock()) { sc = b->sclock() + duration.superclocks(); beats = current_tempo.quarters_at (sc); /* round bartime to beats */ beats = beats.round_to_beat(); sc = current_tempo.superclock_at (beats); bbt = current_meter.bbt_at (beats); m->set (sc, beats, bbt); ++m; moved_meter = true; } } break; case BeatTime: break; } set_dirty (true); } Changed (); } bool TempoMap::remove_time (timepos_t const & pos, timecnt_t const & duration) { bool moved = false; if (moved) { Changed (); } return moved; } TempoPoint const * TempoMap::previous_tempo (TempoPoint const & point) const { Tempos::const_iterator t = _tempos.begin(); Tempos::const_iterator prev = _tempos.end(); while (t != _tempos.end()) { if (t->sclock() == point.sclock()) { if (prev != _tempos.end()) { return &*prev; } } prev = t; ++t; } return 0; } 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 s) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (s); } TempoMetric TempoMap::metric_at (Beats const & b) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (b); } TempoMetric TempoMap::metric_at (BBT_Time const & bbt) const { Glib::Threads::RWLock::ReaderLock lm (_lock); return metric_at_locked (bbt); } TempoMetric TempoMap::metric_at_locked (superclock_t sc, bool can_match) const { Tempos::const_iterator t, prev_t; Meters::const_iterator m, prev_m; assert (!_tempos.empty()); assert (!_meters.empty()); /* Yes, linear search because the typical size of _tempos and _meters * is 1, and extreme sizes are on the order of 10 */ for (t = _tempos.begin(), prev_t = t; t != _tempos.end() && t->sclock() < sc; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = m; m != _meters.end() && m->sclock() < sc; ++m) { prev_m = m; } if (can_match || sc == 0) { /* may have found tempo and/or meter precisely at @param sc */ if (t != _tempos.end() && t->sclock() == sc) { prev_t = t; } if (m != _meters.end() && m->sclock() == sc) { prev_m = m; } } /* I hate doing this const_cast<>, but making this method non-const * propagates into everything that just calls metric_at(), and that's a * bit ridiculous. Yes, the TempoMetric returned here can be used to * change the map, and that's bad, but the non-const propagation is * worse. */ return TempoMetric (*const_cast(&*prev_t), *const_cast (&*prev_m)); } TempoMetric TempoMap::metric_at_locked (Beats const & b, bool can_match) const { Tempos::const_iterator t, prev_t; Meters::const_iterator m, prev_m; assert (!_tempos.empty()); assert (!_meters.empty()); /* Yes, linear search because the typical size of _tempos and _meters * is 1, and extreme sizes are on the order of 10 */ for (t = _tempos.begin(), prev_t = t; t != _tempos.end() && t->beats() < b; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = m; m != _meters.end() && m->beats() < b; ++m) { prev_m = m; } if (can_match || b == Beats()) { /* may have found tempo and/or meter precisely at @param b */ if (t != _tempos.end() && t->beats() == b) { prev_t = t; } if (m != _meters.end() && m->beats() == b) { prev_m = m; } } /* I hate doing this const_cast<>, but making this method non-const * propagates into everything that just calls metric_at(), and that's a * bit ridiculous. Yes, the TempoMetric returned here can be used to * change the map, and that's bad, but the non-const propagation is * worse. */ return TempoMetric (*const_cast(&*prev_t), *const_cast (&*prev_m)); } TempoMetric TempoMap::metric_at_locked (BBT_Time const & bbt, bool can_match) const { Tempos::const_iterator t, prev_t; Meters::const_iterator m, prev_m; assert (!_tempos.empty()); assert (!_meters.empty()); /* Yes, linear search because the typical size of _tempos and _meters * is 1, and extreme sizes are on the order of 10 */ for (t = _tempos.begin(), prev_t = t; t != _tempos.end() && t->bbt() < bbt; ++t) { prev_t = t; } for (m = _meters.begin(), prev_m = m; m != _meters.end() && m->bbt() < bbt; ++m) { prev_m = m; } if (can_match || bbt == BBT_Time()) { /* may have found tempo and/or meter precisely at @param bbt */ if (t != _tempos.end() && t->bbt() == bbt) { prev_t = t; } if (m != _meters.end() && m->bbt() == bbt) { prev_m = m; } } /* I hate doing this const_cast<>, but making this method non-const * propagates into everything that just calls metric_at(), and that's a * bit ridiculous. Yes, the TempoMetric returned here can be used to * change the map, and that's bad, but the non-const propagation is * worse. */ return TempoMetric (*const_cast(&*prev_t), *const_cast (&*prev_m)); } bool TempoMap::set_ramped (TempoPoint & tp, bool yn) { Rampable & r (tp); bool ret = r.set_ramped (yn); if (ret) { reset_starting_at (tp.sclock()); } return ret; } #if 0 bool TempoMap::twist_tempi (TempoSection* ts, const Tempo& bpm, const framepos_t frame, const framepos_t end_frame) { TempoSection* next_t = 0; TempoSection* next_to_next_t = 0; Metrics future_map; bool can_solve = false; /* minimum allowed measurement distance in frames */ framepos_t const min_dframe = 2; { Glib::Threads::RWLock::WriterLock lm (lock); if (!ts) { return false; } TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts); TempoSection* prev_to_prev_t = 0; const frameoffset_t fr_off = end_frame - frame; if (!tempo_copy) { return false; } if (tempo_copy->pulse() > 0.0) { prev_to_prev_t = const_cast(&tempo_section_at_minute_locked (future_map, minute_at_frame (tempo_copy->frame() - 1))); } for (Metrics::const_iterator i = future_map.begin(); i != future_map.end(); ++i) { if ((*i)->is_tempo() && (*i)->minute() > tempo_copy->minute()) { next_t = static_cast (*i); break; } } if (!next_t) { return false; } for (Metrics::const_iterator i = future_map.begin(); i != future_map.end(); ++i) { if ((*i)->is_tempo() && (*i)->minute() > next_t->minute()) { next_to_next_t = static_cast (*i); break; } } if (!next_to_next_t) { return false; } double prev_contribution = 0.0; if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) { prev_contribution = (tempo_copy->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame()); } const frameoffset_t tempo_copy_frame_contribution = fr_off - (prev_contribution * (double) fr_off); framepos_t old_tc_minute = tempo_copy->minute(); double old_next_minute = next_t->minute(); double old_next_to_next_minute = next_to_next_t->minute(); double new_bpm; double new_next_bpm; double new_copy_end_bpm; if (frame > tempo_copy->frame() + min_dframe && (frame + tempo_copy_frame_contribution) > tempo_copy->frame() + min_dframe) { new_bpm = tempo_copy->note_types_per_minute() * ((frame - tempo_copy->frame()) / (double) (end_frame - tempo_copy->frame())); } else { new_bpm = tempo_copy->note_types_per_minute(); } /* 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 false; } new_bpm = min (new_bpm, (double) 1000.0); tempo_copy->set_note_types_per_minute (new_bpm); if (tempo_copy->type() == TempoSection::Constant) { tempo_copy->set_end_note_types_per_minute (new_bpm); } recompute_tempi (future_map); if (check_solved (future_map)) { if (!next_t) { return false; } ts->set_note_types_per_minute (new_bpm); if (ts->type() == TempoSection::Constant) { ts->set_end_note_types_per_minute (new_bpm); } recompute_map (_metrics); can_solve = true; } if (next_t->type() == TempoSection::Constant || next_t->c() == 0.0) { if (frame > tempo_copy->frame() + min_dframe && end_frame > tempo_copy->frame() + min_dframe) { new_next_bpm = next_t->note_types_per_minute() * ((next_to_next_t->minute() - old_next_minute) / (double) ((old_next_to_next_minute) - old_next_minute)); } else { new_next_bpm = next_t->note_types_per_minute(); } next_t->set_note_types_per_minute (new_next_bpm); recompute_tempi (future_map); if (check_solved (future_map)) { for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { if ((*i)->is_tempo() && (*i)->minute() > ts->minute()) { next_t = static_cast (*i); break; } } if (!next_t) { return false; } next_t->set_note_types_per_minute (new_next_bpm); recompute_map (_metrics); can_solve = true; } } else { double next_frame_ratio = 1.0; double copy_frame_ratio = 1.0; if (next_to_next_t) { next_frame_ratio = (next_to_next_t->minute() - old_next_minute) / (old_next_to_next_minute - old_next_minute); copy_frame_ratio = ((old_tc_minute - next_t->minute()) / (double) (old_tc_minute - old_next_minute)); } new_next_bpm = next_t->note_types_per_minute() * next_frame_ratio; new_copy_end_bpm = tempo_copy->end_note_types_per_minute() * copy_frame_ratio; tempo_copy->set_end_note_types_per_minute (new_copy_end_bpm); if (next_t->clamped()) { next_t->set_note_types_per_minute (new_copy_end_bpm); } else { next_t->set_note_types_per_minute (new_next_bpm); } recompute_tempi (future_map); if (check_solved (future_map)) { for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { if ((*i)->is_tempo() && (*i)->minute() > ts->minute()) { next_t = static_cast (*i); break; } } if (!next_t) { return false; } if (next_t->clamped()) { next_t->set_note_types_per_minute (new_copy_end_bpm); } else { next_t->set_note_types_per_minute (new_next_bpm); } ts->set_end_note_types_per_minute (new_copy_end_bpm); recompute_map (_metrics); can_solve = true; } } } Metrics::const_iterator d = future_map.begin(); while (d != future_map.end()) { delete (*d); ++d; } MetricPositionChanged (PropertyChange ()); // Emit Signal return can_solve; } #endif