From 4c6d6b14e561d7be751deef259fa23e756a20d1c Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Wed, 5 Aug 2020 16:02:39 -0600 Subject: [PATCH] Temporal: add new TempoMap implementation source --- libs/temporal/tempo.cc | 3112 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3112 insertions(+) create mode 100644 libs/temporal/tempo.cc diff --git a/libs/temporal/tempo.cc b/libs/temporal/tempo.cc new file mode 100644 index 0000000000..849bf30160 --- /dev/null +++ b/libs/temporal/tempo.cc @@ -0,0 +1,3112 @@ +/* + 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 + +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