globally remove all trailing whitespace from ardour code base.
[ardour.git] / libs / ardour / tempo.cc
index 579b523fa08b475596aeb4ebb6e8bab9374a7068..df7c1ac2e3cbf2a5f9257487a3d732877cbce7be 100644 (file)
 
 #include <algorithm>
 #include <stdexcept>
-
-#include <unistd.h>
-
 #include <cmath>
 
+#include <unistd.h>
 
-#include <glibmm/thread.h>
+#include <glibmm/threads.h>
 #include "pbd/xml++.h"
+#include "evoral/types.hpp"
 #include "ardour/debug.h"
+#include "ardour/lmath.h"
 #include "ardour/tempo.h"
-#include "ardour/utils.h"
 
 #include "i18n.h"
 #include <locale.h>
@@ -45,17 +44,25 @@ using Timecode::BBT_Time;
 Meter    TempoMap::_default_meter (4.0, 4.0);
 Tempo    TempoMap::_default_tempo (120.0);
 
-double Tempo::frames_per_beat (framecnt_t sr, const Meter& meter) const
+/***********************************************************************/
+
+double
+Meter::frames_per_grid (const Tempo& tempo, framecnt_t sr) const
 {
-       return  ((60.0 * sr) / (_beats_per_minute * meter.note_divisor()/_note_type));
-}
+       /* This is tempo- and meter-sensitive. The number it returns
+          is based on the interval between any two lines in the
+          grid that is constructed from tempo and meter sections.
 
-/***********************************************************************/
+          The return value IS NOT interpretable in terms of "beats".
+       */
+
+       return (60.0 * sr) / (tempo.beats_per_minute() * (_note_type/tempo.note_type()));
+}
 
 double
 Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
 {
-       return ((60.0 * sr * _beats_per_bar) / (tempo.beats_per_minute() * _note_type/tempo.note_type()));
+       return frames_per_grid (tempo, sr) * _divisions_per_bar;
 }
 
 /***********************************************************************/
@@ -67,7 +74,7 @@ TempoSection::TempoSection (const XMLNode& node)
 {
        const XMLProperty *prop;
        BBT_Time start;
-       LocaleGuard lg (X_("POSIX"));
+       LocaleGuard lg (X_("C"));
 
        if ((prop = node.property ("start")) == 0) {
                error << _("TempoSection XML node has no \"start\" property") << endmsg;
@@ -110,6 +117,15 @@ TempoSection::TempoSection (const XMLNode& node)
        }
 
        set_movable (string_is_affirmative (prop->value()));
+
+       if ((prop = node.property ("bar-offset")) == 0) {
+               _bar_offset = -1.0;
+       } else {
+               if (sscanf (prop->value().c_str(), "%lf", &_bar_offset) != 1 || _bar_offset < 0.0) {
+                       error << _("TempoSection XML node has an illegal \"bar-offset\" value") << endmsg;
+                       throw failed_constructor();
+               }
+       }
 }
 
 XMLNode&
@@ -117,7 +133,7 @@ TempoSection::get_state() const
 {
        XMLNode *root = new XMLNode (xml_state_node_name);
        char buf[256];
-       LocaleGuard lg (X_("POSIX"));
+       LocaleGuard lg (X_("C"));
 
        snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
                  start().bars,
@@ -128,12 +144,49 @@ TempoSection::get_state() const
        root->add_property ("beats-per-minute", buf);
        snprintf (buf, sizeof (buf), "%f", _note_type);
        root->add_property ("note-type", buf);
+       // snprintf (buf, sizeof (buf), "%f", _bar_offset);
+       // root->add_property ("bar-offset", buf);
        snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
        root->add_property ("movable", buf);
 
        return *root;
 }
 
+void
+
+TempoSection::update_bar_offset_from_bbt (const Meter& m)
+{
+       _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_beat + start().ticks) /
+               (m.divisions_per_bar() * BBT_Time::ticks_per_beat);
+
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Tempo set bar offset to %1 from %2 w/%3\n", _bar_offset, start(), m.divisions_per_bar()));
+}
+
+void
+TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
+{
+       BBT_Time new_start;
+
+       if (_bar_offset < 0.0) {
+               /* not set yet */
+               return;
+       }
+
+       new_start.bars = start().bars;
+       
+       double ticks = BBT_Time::ticks_per_beat * meter.divisions_per_bar() * _bar_offset;
+       new_start.beats = (uint32_t) floor (ticks/BBT_Time::ticks_per_beat);
+       new_start.ticks = 0; /* (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat); */
+
+       /* remember the 1-based counting properties of beats */
+       new_start.beats += 1;
+                                       
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("from bar offset %1 and dpb %2, ticks = %3->%4 beats = %5\n",
+                                                      _bar_offset, meter.divisions_per_bar(), ticks, new_start.ticks, new_start.beats));
+
+       set_start (new_start);
+}
+
 /***********************************************************************/
 
 const string MeterSection::xml_state_node_name = "Meter";
@@ -143,7 +196,7 @@ MeterSection::MeterSection (const XMLNode& node)
 {
        const XMLProperty *prop;
        BBT_Time start;
-       LocaleGuard lg (X_("POSIX"));
+       LocaleGuard lg (X_("C"));
 
        if ((prop = node.property ("start")) == 0) {
                error << _("MeterSection XML node has no \"start\" property") << endmsg;
@@ -160,13 +213,17 @@ MeterSection::MeterSection (const XMLNode& node)
 
        set_start (start);
 
-       if ((prop = node.property ("beats-per-bar")) == 0) {
-               error << _("MeterSection XML node has no \"beats-per-bar\" property") << endmsg;
-               throw failed_constructor();
+       /* beats-per-bar is old; divisions-per-bar is new */
+
+       if ((prop = node.property ("divisions-per-bar")) == 0) {
+               if ((prop = node.property ("beats-per-bar")) == 0) {
+                       error << _("MeterSection XML node has no \"beats-per-bar\" or \"divisions-per-bar\" property") << endmsg;
+                       throw failed_constructor();
+               }
        }
 
-       if (sscanf (prop->value().c_str(), "%lf", &_beats_per_bar) != 1 || _beats_per_bar < 0.0) {
-               error << _("MeterSection XML node has an illegal \"beats-per-bar\" value") << endmsg;
+       if (sscanf (prop->value().c_str(), "%lf", &_divisions_per_bar) != 1 || _divisions_per_bar < 0.0) {
+               error << _("MeterSection XML node has an illegal \"beats-per-bar\" or \"divisions-per-bar\" value") << endmsg;
                throw failed_constructor();
        }
 
@@ -193,7 +250,7 @@ MeterSection::get_state() const
 {
        XMLNode *root = new XMLNode (xml_state_node_name);
        char buf[256];
-       LocaleGuard lg (X_("POSIX"));
+       LocaleGuard lg (X_("C"));
 
        snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
                  start().bars,
@@ -202,8 +259,8 @@ MeterSection::get_state() const
        root->add_property ("start", buf);
        snprintf (buf, sizeof (buf), "%f", _note_type);
        root->add_property ("note-type", buf);
-       snprintf (buf, sizeof (buf), "%f", _beats_per_bar);
-       root->add_property ("beats-per-bar", buf);
+       snprintf (buf, sizeof (buf), "%f", _divisions_per_bar);
+       root->add_property ("divisions-per-bar", buf);
        snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
        root->add_property ("movable", buf);
 
@@ -220,9 +277,7 @@ struct MetricSectionSorter {
 
 TempoMap::TempoMap (framecnt_t fr)
 {
-       metrics = new Metrics;
        _frame_rate = fr;
-       last_bbt_valid = false;
        BBT_Time start;
 
        start.bars = 1;
@@ -230,317 +285,349 @@ TempoMap::TempoMap (framecnt_t fr)
        start.ticks = 0;
 
        TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type());
-       MeterSection *m = new MeterSection (start, _default_meter.beats_per_bar(), _default_meter.note_divisor());
+       MeterSection *m = new MeterSection (start, _default_meter.divisions_per_bar(), _default_meter.note_divisor());
 
        t->set_movable (false);
        m->set_movable (false);
 
        /* note: frame time is correct (zero) for both of these */
 
-       metrics->push_back (t);
-       metrics->push_back (m);
+       metrics.push_back (t);
+       metrics.push_back (m);
 }
 
 TempoMap::~TempoMap ()
 {
 }
 
-int
-TempoMap::move_metric_section (MetricSection& section, const BBT_Time& when)
+void
+TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation)
 {
-       if (when == section.start() || !section.movable()) {
-               return -1;
-       }
-
-       Glib::RWLock::WriterLock  lm (lock);
-       MetricSectionSorter cmp;
-
-       if (when.beats != 1) {
-
-               /* position by audio frame, then recompute BBT timestamps from the audio ones */
-
-               framepos_t frame = frame_time (when);
-               // cerr << "nominal frame time = " << frame << endl;
-
-               framepos_t prev_frame = round_to_type (frame, -1, Beat);
-               framepos_t next_frame = round_to_type (frame, 1, Beat);
-
-               // cerr << "previous beat at " << prev_frame << " next at " << next_frame << endl;
-
-               /* use the closest beat */
+       bool removed = false;
 
-               if ((frame - prev_frame) < (next_frame - frame)) {
-                       frame = prev_frame;
-               } else {
-                       frame = next_frame;
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               if ((removed = remove_tempo_locked (tempo))) {
+                       if (complete_operation) {
+                               recompute_map (true);
+                       }
                }
-
-               // cerr << "actual frame time = " << frame << endl;
-               section.set_frame (frame);
-               // cerr << "frame time = " << section.frame() << endl;
-               timestamp_metrics (false);
-               // cerr << "new BBT time = " << section.start() << endl;
-               metrics->sort (cmp);
-
-       } else {
-
-               /* positioned at bar start already, so just put it there */
-
-               section.set_start (when);
-               metrics->sort (cmp);
-               timestamp_metrics (true);
        }
 
-
-       return 0;
-}
-
-void
-TempoMap::move_tempo (TempoSection& tempo, const BBT_Time& when)
-{
-       if (move_metric_section (tempo, when) == 0) {
+       if (removed && complete_operation) {
                PropertyChanged (PropertyChange ());
        }
 }
 
-void
-TempoMap::move_meter (MeterSection& meter, const BBT_Time& when)
+bool
+TempoMap::remove_tempo_locked (const TempoSection& tempo)
 {
-       if (move_metric_section (meter, when) == 0) {
-               PropertyChanged (PropertyChange ());
+       Metrics::iterator i;
+
+       for (i = metrics.begin(); i != metrics.end(); ++i) {
+               if (dynamic_cast<TempoSection*> (*i) != 0) {
+                       if (tempo.frame() == (*i)->frame()) {
+                               if ((*i)->movable()) {
+                                       metrics.erase (i);
+                                       return true;
+                               }
+                       }
+               }
        }
-}
 
+       return false;
+}      
+       
 void
-TempoMap::remove_tempo (const TempoSection& tempo)
+TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
 {
        bool removed = false;
 
        {
-               Glib::RWLock::WriterLock lm (lock);
-               Metrics::iterator i;
-
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
-                       if (dynamic_cast<TempoSection*> (*i) != 0) {
-                               if (tempo.frame() == (*i)->frame()) {
-                                       if ((*i)->movable()) {
-                                               metrics->erase (i);
-                                               removed = true;
-                                               break;
-                                       }
-                               }
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               if ((removed = remove_meter_locked (tempo))) {
+                       if (complete_operation) {
+                               recompute_map (true);
                        }
                }
        }
 
-       if (removed) {
+       if (removed && complete_operation) {
                PropertyChanged (PropertyChange ());
        }
 }
 
-void
-TempoMap::remove_meter (const MeterSection& tempo)
+bool
+TempoMap::remove_meter_locked (const MeterSection& tempo)
 {
-       bool removed = false;
-
-       {
-               Glib::RWLock::WriterLock lm (lock);
-               Metrics::iterator i;
-
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
-                       if (dynamic_cast<MeterSection*> (*i) != 0) {
-                               if (tempo.frame() == (*i)->frame()) {
-                                       if ((*i)->movable()) {
-                                               metrics->erase (i);
-                                               removed = true;
-                                               break;
-                                       }
+       Metrics::iterator i;
+       
+       for (i = metrics.begin(); i != metrics.end(); ++i) {
+               if (dynamic_cast<MeterSection*> (*i) != 0) {
+                       if (tempo.frame() == (*i)->frame()) {
+                               if ((*i)->movable()) {
+                                       metrics.erase (i);
+                                       return true;
                                }
                        }
                }
        }
 
-       if (removed) {
-               PropertyChanged (PropertyChange ());
-       }
+       return false;
 }
 
 void
-TempoMap::do_insert (MetricSection* section, bool with_bbt)
+TempoMap::do_insert (MetricSection* section)
 {
-       Metrics::iterator i;
+       bool need_add = true;
+
+       assert (section->start().ticks == 0);
+
+       /* we only allow new meters to be inserted on beat 1 of an existing
+        * measure.
+        */
+
+       if (dynamic_cast<MeterSection*>(section)) {
+
+               /* we need to (potentially) update the BBT times of tempo
+                  sections based on this new meter.
+               */
+               
+               if ((section->start().beats != 1) || (section->start().ticks != 0)) {
+                       
+                       BBT_Time corrected = section->start();
+                       corrected.beats = 1;
+                       corrected.ticks = 0;
+                       
+                       warning << string_compose (_("Meter changes can only be positioned on the first beat of a bar. Moving from %1 to %2"),
+                                                  section->start(), corrected) << endmsg;
+                       
+                       section->set_start (corrected);
+               }
+       }
+
+       
 
        /* Look for any existing MetricSection that is of the same type and
-          at the same time as the new one, and remove it before adding
-          the new one.
+          in the same bar as the new one, and remove it before adding
+          the new one. Note that this means that if we find a matching,
+          existing section, we can break out of the loop since we're
+          guaranteed that there is only one such match.
        */
 
-       Metrics::iterator to_remove = metrics->end ();
+       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
-       for (i = metrics->begin(); i != metrics->end(); ++i) {
+               bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
+               bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
 
-               int const c = (*i)->compare (section, with_bbt);
+               if (iter_is_tempo && insert_is_tempo) {
 
-               if (c < 0) {
-                       /* this section is before the one to be added; go back round */
-                       continue;
-               } else if (c > 0) {
-                       /* this section is after the one to be added; there can't be any at the same time */
-                       break;
-               }
+                       /* Tempo sections */
 
-               /* hacky comparison of type */
-               bool const a = dynamic_cast<TempoSection*> (*i) != 0;
-               bool const b = dynamic_cast<TempoSection*> (section) != 0;
+                       if ((*i)->start().bars == section->start().bars &&
+                           (*i)->start().beats == section->start().beats) {
 
-               if (a == b) {
-                       to_remove = i;
-                       break;
-               }
-       }
+                               if (!(*i)->movable()) {
+                                       
+                                       /* can't (re)move this section, so overwrite
+                                        * its data content (but not its properties as
+                                        * a section).
+                                        */
+                                       
+                                       *(dynamic_cast<Tempo*>(*i)) = *(dynamic_cast<Tempo*>(section));
+                                       need_add = false;
+                               } else {
+                                       metrics.erase (i);
+                               }
+                               break;
+                       }
 
-       if (to_remove != metrics->end()) {
-               /* remove the MetricSection at the same time as the one we are about to add */
-               metrics->erase (to_remove);
-       }
+               } else if (!iter_is_tempo && !insert_is_tempo) {
 
-       /* Add the given MetricSection */
+                       /* Meter Sections */
 
-       for (i = metrics->begin(); i != metrics->end(); ++i) {
+                       if ((*i)->start().bars == section->start().bars) {
 
-               if ((*i)->compare (section, with_bbt) < 0) {
-                       continue;
-               }
+                               if (!(*i)->movable()) {
+                                       
+                                       /* can't (re)move this section, so overwrite
+                                        * its data content (but not its properties as
+                                        * a section
+                                        */
+                                       
+                                       *(dynamic_cast<Meter*>(*i)) = *(dynamic_cast<Meter*>(section));
+                                       need_add = false;
+                               } else {
+                                       metrics.erase (i);
+                                       
+                               }
 
-               metrics->insert (i, section);
-               break;
+                               break;
+                       }
+               } else {
+                       /* non-matching types, so we don't care */
+               }
        }
 
-       if (i == metrics->end()) {
-               metrics->insert (metrics->end(), section);
-       }
+       /* Add the given MetricSection, if we didn't just reset an existing
+        * one above
+        */
+
+       if (need_add) {
+
+               Metrics::iterator i;
 
-       timestamp_metrics (with_bbt);
+               for (i = metrics.begin(); i != metrics.end(); ++i) {
+                       if ((*i)->start() > section->start()) {
+                               break;
+                       }
+               }
+               
+               metrics.insert (i, section);
+       }
 }
 
 void
-TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
+TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where)
 {
        {
-               Glib::RWLock::WriterLock lm (lock);
-
-               /* new tempos always start on a beat */
-               where.ticks = 0;
-
-               do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), true);
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               TempoSection& first (first_tempo());
+               
+               if (ts.start() != first.start()) {
+                       remove_tempo_locked (ts);
+                       add_tempo_locked (tempo, where, true);
+               } else {
+                       {
+                               /* cannot move the first tempo section */
+                               *static_cast<Tempo*>(&first) = tempo;
+                               recompute_map (false);
+                       }
+               }
        }
 
        PropertyChanged (PropertyChange ());
 }
 
 void
-TempoMap::add_tempo (const Tempo& tempo, framepos_t where)
+TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
 {
        {
-               Glib::RWLock::WriterLock lm (lock);
-               do_insert (new TempoSection (where, tempo.beats_per_minute(), tempo.note_type()), false);
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               add_tempo_locked (tempo, where, true);
        }
 
+
        PropertyChanged (PropertyChange ());
 }
 
 void
-TempoMap::replace_tempo (TempoSection& existing, const Tempo& replacement)
+TempoMap::add_tempo_locked (const Tempo& tempo, BBT_Time where, bool recompute)
 {
-       bool replaced = false;
-
-       {
-               Glib::RWLock::WriterLock lm (lock);
-               Metrics::iterator i;
-
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
-                       TempoSection *ts;
-
-                       if ((ts = dynamic_cast<TempoSection*>(*i)) != 0 && ts == &existing) {
-
-                                *((Tempo *) ts) = replacement;
-
-                               replaced = true;
-                               timestamp_metrics (true);
-
-                               break;
-                       }
+       /* new tempos always start on a beat */
+       where.ticks = 0;
+       
+       TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type());
+       
+       /* find the meter to use to set the bar offset of this
+        * tempo section.
+        */
+       
+       const Meter* meter = &first_meter();
+       
+       /* as we start, we are *guaranteed* to have m.meter and m.tempo pointing
+          at something, because we insert the default tempo and meter during
+          TempoMap construction.
+       
+          now see if we can find better candidates.
+       */
+       
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               
+               const MeterSection* m;
+               
+               if (where < (*i)->start()) {
+                       break;
+               }
+               
+               if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+                       meter = m;
                }
        }
+       
+       ts->update_bar_offset_from_bbt (*meter);
+       
+       /* and insert it */
+       
+       do_insert (ts);
 
-       if (replaced) {
-               PropertyChanged (PropertyChange ());
+       if (recompute) {
+               recompute_map (false);
        }
-}
+}      
 
 void
-TempoMap::add_meter (const Meter& meter, BBT_Time where)
+TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
 {
        {
-               Glib::RWLock::WriterLock lm (lock);
-
-               /* a new meter always starts a new bar on the first beat. so
-                  round the start time appropriately. remember that
-                  `where' is based on the existing tempo map, not
-                  the result after we insert the new meter.
-
-               */
-
-               if (where.beats != 1) {
-                       where.beats = 1;
-                       where.bars++;
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               MeterSection& first (first_meter());
+               
+               if (ms.start() != first.start()) {
+                       remove_meter_locked (ms);
+                       add_meter_locked (meter, where, true);
+               } else {
+                       /* cannot move the first meter section */
+                       *static_cast<Meter*>(&first) = meter;
+                       recompute_map (true);
                }
-
-               /* new meters *always* start on a beat. */
-               where.ticks = 0;
-
-               do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), true);
        }
 
        PropertyChanged (PropertyChange ());
 }
 
 void
-TempoMap::add_meter (const Meter& meter, framepos_t where)
+TempoMap::add_meter (const Meter& meter, BBT_Time where)
 {
        {
-               Glib::RWLock::WriterLock lm (lock);
-               do_insert (new MeterSection (where, meter.beats_per_bar(), meter.note_divisor()), false);
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               add_meter_locked (meter, where, true);
+       }
+
+       
+#ifndef NDEBUG
+       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
+               dump (std::cerr);
        }
+#endif
 
        PropertyChanged (PropertyChange ());
 }
 
 void
-TempoMap::replace_meter (MeterSection& existing, const Meter& replacement)
+TempoMap::add_meter_locked (const Meter& meter, BBT_Time where, bool recompute)
 {
-       bool replaced = false;
-
-       {
-               Glib::RWLock::WriterLock lm (lock);
-               Metrics::iterator i;
-
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
-                       MeterSection *ms;
-                       if ((ms = dynamic_cast<MeterSection*>(*i)) != 0 && ms == &existing) {
-
-                               *((Meter*) ms) = replacement;
-
-                               replaced = true;
-                               timestamp_metrics (true);
-                               break;
-                       }
-               }
+       /* a new meter always starts a new bar on the first beat. so
+          round the start time appropriately. remember that
+          `where' is based on the existing tempo map, not
+          the result after we insert the new meter.
+       
+       */
+       
+       if (where.beats != 1) {
+               where.beats = 1;
+               where.bars++;
        }
+       
+       /* new meters *always* start on a beat. */
+       where.ticks = 0;
+       
+       do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor()));
 
-       if (replaced) {
-               PropertyChanged (PropertyChange ());
+       if (recompute) {
+               recompute_map (true);
        }
+               
 }
 
 void
@@ -549,9 +636,13 @@ TempoMap::change_initial_tempo (double beats_per_minute, double note_type)
        Tempo newtempo (beats_per_minute, note_type);
        TempoSection* t;
 
-       for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       *((Tempo*) t) = newtempo;
+                       {
+                               Glib::Threads::RWLock::WriterLock lm (lock);
+                               *((Tempo*) t) = newtempo;
+                               recompute_map (false);
+                       }
                        PropertyChanged (PropertyChange ());
                        break;
                }
@@ -570,7 +661,7 @@ TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, d
        /* find the TempoSection immediately preceding "where"
         */
 
-       for (first = 0, i = metrics->begin(), prev = 0; i != metrics->end(); ++i) {
+       for (first = 0, i = metrics.begin(), prev = 0; i != metrics.end(); ++i) {
 
                if ((*i)->frame() > where) {
                        break;
@@ -597,7 +688,13 @@ TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, d
 
        /* reset */
 
-       *((Tempo*)prev) = newtempo;
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               /* cannot move the first tempo section */
+               *((Tempo*)prev) = newtempo;
+               recompute_map (false);
+       }
+
        PropertyChanged (PropertyChange ());
 }
 
@@ -606,14 +703,32 @@ TempoMap::first_meter () const
 {
        const MeterSection *m = 0;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if ((m = dynamic_cast<const MeterSection *> (*i)) != 0) {
                        return *m;
                }
        }
 
        fatal << _("programming error: no tempo section in tempo map!") << endmsg;
-       /*NOTREACHED*/
+       abort(); /*NOTREACHED*/
+       return *m;
+}
+
+MeterSection&
+TempoMap::first_meter ()
+{
+       MeterSection *m = 0;
+
+       /* CALLER MUST HOLD LOCK */
+
+       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+                       return *m;
+               }
+       }
+
+       fatal << _("programming error: no tempo section in tempo map!") << endmsg;
+       abort(); /*NOTREACHED*/
        return *m;
 }
 
@@ -622,133 +737,382 @@ TempoMap::first_tempo () const
 {
        const TempoSection *t = 0;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       /* CALLER MUST HOLD LOCK */
+       
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if ((t = dynamic_cast<const TempoSection *> (*i)) != 0) {
                        return *t;
                }
        }
 
        fatal << _("programming error: no tempo section in tempo map!") << endmsg;
-       /*NOTREACHED*/
+       abort(); /*NOTREACHED*/
        return *t;
 }
 
-void
-TempoMap::timestamp_metrics (bool use_bbt)
+TempoSection&
+TempoMap::first_tempo ()
 {
-       Metrics::iterator i;
-       const Meter* meter;
-       const Tempo* tempo;
-       Meter *m;
-       Tempo *t;
+       TempoSection *t = 0;
 
-       meter = &first_meter ();
-       tempo = &first_tempo ();
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               if ((t = dynamic_cast<TempoSection *> (*i)) != 0) {
+                       return *t;
+               }
+       }
 
-       if (use_bbt) {
+       fatal << _("programming error: no tempo section in tempo map!") << endmsg;
+       abort(); /*NOTREACHED*/
+       return *t;
+}
 
-               // cerr << "\n\n\n ######################\nTIMESTAMP via BBT ##############\n" << endl;
+void
+TempoMap::require_map_to (framepos_t pos)
+{
+       Glib::Threads::RWLock::WriterLock lm (lock);
 
-               framepos_t current = 0;
-               framepos_t section_frames;
-               BBT_Time start;
-               BBT_Time end;
+       if (_map.empty() || _map.back().frame < pos) {
+               extend_map (pos);
+       }
+}
 
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
+void
+TempoMap::require_map_to (const BBT_Time& bbt)
+{
+       Glib::Threads::RWLock::WriterLock lm (lock);
 
-                       end = (*i)->start();
+       /* since we have no idea where BBT is if its off the map, see the last
+        * point in the map is past BBT, and if not add an arbitrary amount of
+        * time until it is.
+        */
 
-                       section_frames = count_frames_between_metrics (*meter, *tempo, start, end);
+       int additional_minutes = 1;
+       
+       while (1) {
+               if (!_map.empty() && _map.back().bar >= (bbt.bars + 1)) {
+                       break;
+               }
+               /* add some more distance, using bigger steps each time */
+               extend_map (_map.back().frame + (_frame_rate * 60 * additional_minutes));
+               additional_minutes *= 2;
+       }
+}
 
-                       current += section_frames;
+void
+TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
+{
+       /* CALLER MUST HOLD WRITE LOCK */
 
-                       start = end;
+       MeterSection* meter = 0;
+       TempoSection* tempo = 0;
+       double current_frame;
+       BBT_Time current;
+       Metrics::iterator next_metric;
 
-                       (*i)->set_frame (current);
+       if (end < 0) {
 
-                       if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
-                               tempo = t;
-                       } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
-                               meter = m;
-                       } else {
-                               fatal << _("programming error: unhandled MetricSection type") << endmsg;
-                               /*NOTREACHED*/
-                       }
-               }
+               /* we will actually stop once we hit
+                  the last metric.
+               */
+               end = max_framepos;
 
        } else {
+               if (!_map.empty ()) {
+                       /* never allow the map to be shortened */
+                       end = max (end, _map.back().frame);
+               }
+       }
 
-               // cerr << "\n\n\n ######################\nTIMESTAMP via AUDIO ##############\n" << endl;
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
 
-               bool first = true;
-               MetricSection* prev = 0;
+       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               MeterSection* ms;
 
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
+               if ((ms = dynamic_cast<MeterSection *> (*i)) != 0) {
+                       meter = ms;
+                       break;
+               }
+       }
 
-                       BBT_Time bbt;
-                       TempoMetric metric (*meter, *tempo);
+       assert(meter);
 
-                       if (prev) {
-                               metric.set_start (prev->start());
-                               metric.set_frame (prev->frame());
-                       } else {
-                               // metric will be at frames=0 bbt=1|1|0 by default
-                               // which is correct for our purpose
-                       }
+       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               TempoSection* ts;
 
-                       bbt_time_with_metric ((*i)->frame(), bbt, metric);
+               if ((ts = dynamic_cast<TempoSection *> (*i)) != 0) {
+                       tempo = ts;
+                       break;
+               }
+       }
 
-                       // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
+       assert(tempo);
 
+       /* assumes that the first meter & tempo are at frame zero */
+       current_frame = 0;
+       meter->set_frame (0);
+       tempo->set_frame (0);
 
-                       if (first) {
-                               first = false;
-                       } else {
+       /* assumes that the first meter & tempo are at 1|1|0 */
+       current.bars = 1;
+       current.beats = 1;
+       current.ticks = 0;
 
-                               if (bbt.ticks > BBT_Time::ticks_per_beat/2) {
-                                       /* round up to next beat */
-                                       bbt.beats += 1;
-                               }
+       if (reassign_tempo_bbt) {
 
-                               bbt.ticks = 0;
+               MeterSection* rmeter = meter;
 
-                               if (bbt.beats != 1) {
-                                       /* round up to next bar */
-                                       bbt.bars += 1;
-                                       bbt.beats = 1;
-                               }
-                       }
+               DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n");
 
-                       //s cerr << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << endl;
+               for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
-                       (*i)->set_start (bbt);
+                       TempoSection* ts;
+                       MeterSection* ms;
+       
+                       if ((ts = dynamic_cast<TempoSection*>(*i)) != 0) {
 
-                       if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
-                               tempo = t;
-                               // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
-                       } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
-                               meter = m;
-                               // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
+                               /* reassign the BBT time of this tempo section
+                                * based on its bar offset position.
+                                */
+
+                               ts->update_bbt_time_from_bar_offset (*rmeter);
+
+                       } else if ((ms = dynamic_cast<MeterSection*>(*i)) != 0) {
+                               rmeter = ms;
                        } else {
                                fatal << _("programming error: unhandled MetricSection type") << endmsg;
-                               /*NOTREACHED*/
+                               abort(); /*NOTREACHED*/
                        }
+               }
+       }
 
-                       prev = (*i);
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2\n", *((Meter*)meter), *((Tempo*)tempo)));
+
+       next_metric = metrics.begin();
+       ++next_metric; // skip meter (or tempo)
+       ++next_metric; // skip tempo (or meter)
+
+       _map.clear ();
+
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add first bar at 1|1 @ %2\n", current.bars, current_frame));
+       _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), 1, 1));
+
+       if (end == 0) {
+               /* silly call from Session::process() during startup
+                */
+               return;
+       }
+
+       _extend_map (tempo, meter, next_metric, current, current_frame, end);
+}
+
+void
+TempoMap::extend_map (framepos_t end)
+{
+       /* CALLER MUST HOLD WRITE LOCK */
+
+       if (_map.empty()) {
+               recompute_map (false, end);
+               return;
+       }
+
+       BBTPointList::const_iterator i = _map.end();    
+       Metrics::iterator next_metric;
+
+       --i;
+
+       BBT_Time last_metric_start;
+
+       if ((*i).tempo->frame() > (*i).meter->frame()) {
+               last_metric_start = (*i).tempo->start();
+       } else {
+               last_metric_start = (*i).meter->start();
+       }
+
+       /* find the metric immediately after the tempo + meter sections for the
+        * last point in the map
+        */
+
+       for (next_metric = metrics.begin(); next_metric != metrics.end(); ++next_metric) {
+               if ((*next_metric)->start() > last_metric_start) {
+                       break;
                }
        }
 
-       // dump (cerr);
-       // cerr << "###############################################\n\n\n" << endl;
+       /* we cast away const here because this is the one place where we need
+        * to actually modify the frame time of each metric section.
+        */
+
+       _extend_map (const_cast<TempoSection*> ((*i).tempo),
+                    const_cast<MeterSection*> ((*i).meter),
+                    next_metric, BBT_Time ((*i).bar, (*i).beat, 0), (*i).frame, end);
+}
+
+void
+TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter,
+                      Metrics::iterator next_metric,
+                      BBT_Time current, framepos_t current_frame, framepos_t end)
+{
+       /* CALLER MUST HOLD WRITE LOCK */
+
+       TempoSection* ts;
+       MeterSection* ms;
+       double beat_frames;
+       double current_frame_exact;
+       framepos_t bar_start_frame;
+
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Extend map to %1 from %2 = %3\n", end, current, current_frame));
+
+       if (current.beats == 1) {
+               bar_start_frame = current_frame;
+       } else {
+               bar_start_frame = 0;
+       }
+
+       beat_frames = meter->frames_per_grid (*tempo,_frame_rate);
+       current_frame_exact = current_frame;
+
+       while (current_frame < end) {
+
+               current.beats++;
+               current_frame_exact += beat_frames;
+               current_frame = llrint(current_frame_exact);
+
+               if (current.beats > meter->divisions_per_bar()) {
+                       current.bars++;
+                       current.beats = 1;
+               }
+
+               if (next_metric != metrics.end()) {
+
+                       /* no operator >= so invert operator < */
+
+                       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1 next metric @ %2\n", current, (*next_metric)->start()));
+
+                       if (!(current < (*next_metric)->start())) {
+
+               set_metrics:
+                               if (((ts = dynamic_cast<TempoSection*> (*next_metric)) != 0)) {
+
+                                       tempo = ts;
+
+                                       /* new tempo section: if its on a beat,
+                                        * we don't have to do anything other
+                                        * than recompute various distances,
+                                        * done further below as we transition
+                                        * the next metric section.
+                                        *
+                                        * if its not on the beat, we have to
+                                        * compute the duration of the beat it
+                                        * is within, which will be different
+                                        * from the preceding following ones
+                                        * since it takes part of its duration
+                                        * from the preceding tempo and part
+                                        * from this new tempo.
+                                        */
+
+                                       if (tempo->start().ticks != 0) {
+                                               
+                                               double next_beat_frames = tempo->frames_per_beat (_frame_rate);                                 
+                                               
+                                               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into non-beat-aligned tempo metric at %1 = %2, adjust next beat using %3\n",
+                                                                                              tempo->start(), current_frame, tempo->bar_offset()));
+                                               
+                                               /* back up to previous beat */
+                                               current_frame_exact -= beat_frames;
+                                               current_frame = llrint(current_frame_exact);
+
+                                               /* set tempo section location
+                                                * based on offset from last
+                                                * bar start
+                                                */
+                                               tempo->set_frame (bar_start_frame +
+                                                                 llrint ((ts->bar_offset() * meter->divisions_per_bar() * beat_frames)));
+                                               
+                                               /* advance to the location of
+                                                * the new (adjusted) beat. do
+                                                * this by figuring out the
+                                                * offset within the beat that
+                                                * would have been there
+                                                * without the tempo
+                                                * change. then stretch the
+                                                * beat accordingly.
+                                                */
+
+                                               double offset_within_old_beat = (tempo->frame() - current_frame) / beat_frames;
+
+                                               current_frame_exact += (offset_within_old_beat * beat_frames) + ((1.0 - offset_within_old_beat) * next_beat_frames);
+                                               current_frame = llrint(current_frame_exact);
+
+                                               /* next metric doesn't have to
+                                                * match this precisely to
+                                                * merit a reloop ...
+                                                */
+                                               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Adjusted last beat to %1\n", current_frame));
+                                               
+                                       } else {
+                                               
+                                               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into beat-aligned tempo metric at %1 = %2\n",
+                                                                                              tempo->start(), current_frame));
+                                               tempo->set_frame (current_frame);
+                                       }
+
+                               } else if ((ms = dynamic_cast<MeterSection*>(*next_metric)) != 0) {
+                                       
+                                       meter = ms;
 
+                                       /* new meter section: always defines the
+                                        * start of a bar.
+                                        */
+                                       
+                                       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into meter section at %1 vs %2 (%3)\n",
+                                                                                      meter->start(), current, current_frame));
+                                       
+                                       assert (current.beats == 1);
+
+                                       meter->set_frame (current_frame);
+                               }
+                               
+                               beat_frames = meter->frames_per_grid (*tempo, _frame_rate);
+                               
+                               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n",
+                                                                              beat_frames, meter->divisions_per_bar(), *((Meter*)meter), *((Tempo*)tempo)));
+                       
+                               ++next_metric;
+
+                               if (next_metric != metrics.end() && ((*next_metric)->start() == current)) {
+                                       /* same position so go back and set this one up before advancing
+                                        */
+                                       goto set_metrics;
+                               }
+
+                       }
+               }
+
+               if (current.beats == 1) {
+                       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Bar at %1|1 @ %2\n", current.bars, current_frame));
+                       _map.push_back (BBTPoint (*meter, *tempo, current_frame, current.bars, 1));
+                       bar_start_frame = current_frame;
+               } else {
+                       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Beat at %1|%2 @ %3\n", current.bars, current.beats, current_frame));
+                       _map.push_back (BBTPoint (*meter, *tempo, current_frame, current.bars, current.beats));
+               }
+
+               if (next_metric == metrics.end()) {
+                       /* no more metrics - we've timestamped them all, stop here */
+                       if (end == max_framepos) {
+                               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("stop extending map now that we've reach the end @ %1|%2 = %3\n",
+                                                                              current.bars, current.beats, current_frame));
+                               break;
+                       }
+               }
+       }
 }
 
 TempoMetric
-TempoMap::metric_at (framepos_t frame) const
+TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const
 {
+       Glib::Threads::RWLock::ReaderLock lm (lock);
        TempoMetric m (first_meter(), first_tempo());
-       const Meter* meter;
-       const Tempo* tempo;
 
        /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
           at something, because we insert the default tempo and meter during
@@ -757,31 +1121,27 @@ TempoMap::metric_at (framepos_t frame) const
           now see if we can find better candidates.
        */
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
                if ((*i)->frame() > frame) {
                        break;
                }
 
-               if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                       m.set_tempo (*tempo);
-               } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                       m.set_meter (*meter);
-               }
+               m.set_metric(*i);
 
-               m.set_frame ((*i)->frame ());
-               m.set_start ((*i)->start ());
+               if (last) {
+                       *last = i;
+               }
        }
-
+       
        return m;
 }
 
 TempoMetric
 TempoMap::metric_at (BBT_Time bbt) const
 {
+       Glib::Threads::RWLock::ReaderLock lm (lock);
        TempoMetric m (first_meter(), first_tempo());
-       const Meter* meter;
-       const Tempo* tempo;
 
        /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
           at something, because we insert the default tempo and meter during
@@ -790,7 +1150,7 @@ TempoMap::metric_at (BBT_Time bbt) const
           now see if we can find better candidates.
        */
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
                BBT_Time section_start ((*i)->start());
 
@@ -798,725 +1158,479 @@ TempoMap::metric_at (BBT_Time bbt) const
                        break;
                }
 
-               if ((tempo = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                       m.set_tempo (*tempo);
-               } else if ((meter = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                       m.set_meter (*meter);
-               }
-
-               m.set_frame ((*i)->frame ());
-               m.set_start (section_start);
+               m.set_metric (*i);
        }
 
        return m;
 }
 
 void
-TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt) const
+TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
 {
-       {
-               Glib::RWLock::ReaderLock lm (lock);
-               bbt_time_unlocked (frame, bbt);
+       require_map_to (frame);
+
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       if (frame < 0) {
+               bbt.bars = 1;
+               bbt.beats = 1;
+               bbt.ticks = 0;
+               warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg;
+               return;
        }
-}
 
-void
-TempoMap::bbt_time_unlocked (framepos_t frame, BBT_Time& bbt) const
-{
-       bbt_time_with_metric (frame, bbt, metric_at (frame));
+       return bbt_time (frame, bbt, bbt_before_or_at (frame));
 }
 
 void
-TempoMap::bbt_time_with_metric (framepos_t frame, BBT_Time& bbt, const TempoMetric& metric) const
+TempoMap::bbt_time_rt (framepos_t frame, BBT_Time& bbt)
 {
-       framecnt_t frame_diff;
-
-       // cerr << "---- BBT time for " << frame << " using metric @ " << metric.frame() << " BBT " << metric.start() << endl;
-
-       const double beats_per_bar = metric.meter().beats_per_bar();
-       const double ticks_per_frame = metric.tempo().frames_per_beat (_frame_rate, metric.meter()) / BBT_Time::ticks_per_beat;
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
 
-       /* now compute how far beyond that point we actually are. */
-
-       frame_diff = frame - metric.frame();
-
-       bbt.ticks = metric.start().ticks + (uint32_t)round((double)frame_diff / ticks_per_frame);
-       uint32_t xtra_beats = bbt.ticks / (uint32_t)BBT_Time::ticks_per_beat;
-       bbt.ticks %= (uint32_t)BBT_Time::ticks_per_beat;
-
-       bbt.beats = metric.start().beats + xtra_beats - 1; // correction for 1-based counting, see below for matching operation.
-       bbt.bars = metric.start().bars + (uint32_t)floor((double)bbt.beats / beats_per_bar);
-       bbt.beats = (uint32_t)fmod((double)bbt.beats, beats_per_bar);
-
-       /* if we have a fractional number of beats per bar, we see if
-          we're in the last beat (the fractional one).  if so, we
-          round ticks appropriately and bump to the next bar. */
-       double beat_fraction = beats_per_bar - floor(beats_per_bar);
-       /* XXX one problem here is that I'm not sure how to handle
-          fractional beats that don't evenly divide ticks_per_beat.
-          If they aren't handled consistently, I would guess we'll
-          continue to have strange discrepancies occuring.  Perhaps
-          this will also behave badly in the case of meters like
-          0.1/4, but I can't be bothered to test that.
-       */
-       uint32_t ticks_on_last_beat = (uint32_t)floor(BBT_Time::ticks_per_beat * beat_fraction);
-
-       if (bbt.beats > (uint32_t)floor(beats_per_bar) && bbt.ticks >= ticks_on_last_beat) {
-               bbt.ticks -= ticks_on_last_beat;
-               bbt.beats = 0;
-               bbt.bars++;
+       if (!lm.locked()) {
+               throw std::logic_error ("TempoMap::bbt_time_rt() could not lock tempo map");
+       }
+       
+       if (_map.empty() || _map.back().frame < frame) {
+               throw std::logic_error (string_compose ("map not long enough to reach %1", frame));
        }
 
-       bbt.beats++; // correction for 1-based counting, see above for matching operation.
-
-       // cerr << "-----\t RETURN " << bbt << endl;
+       return bbt_time (frame, bbt, bbt_before_or_at (frame));
 }
 
-framecnt_t
-TempoMap::count_frames_between (const BBT_Time& start, const BBT_Time& end) const
+void
+TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_iterator& i)
 {
-       /* for this to work with fractional measure types, start and end have to be
-          "legal" BBT types, that means that the beats and ticks should be inside
-          a bar
-       */
-
-       framecnt_t frames = 0;
-       framepos_t start_frame = 0;
-       framepos_t end_frame = 0;
-
-       TempoMetric m = metric_at (start);
-
-       uint32_t bar_offset = start.bars - m.start().bars;
-
-       double  beat_offset = bar_offset*m.meter().beats_per_bar() - (m.start().beats-1) + (start.beats -1)
-               + start.ticks/BBT_Time::ticks_per_beat;
-
-
-       start_frame = m.frame() + (framepos_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter()));
-
-       m =  metric_at(end);
-
-       bar_offset = end.bars - m.start().bars;
-
-       beat_offset = bar_offset * m.meter().beats_per_bar() - (m.start().beats -1) + (end.beats - 1)
-               + end.ticks/BBT_Time::ticks_per_beat;
-
-       end_frame = m.frame() + (framepos_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter()));
+       /* CALLER MUST HOLD READ LOCK */
 
-       frames = end_frame - start_frame;
-
-       return frames;
+       bbt.bars = (*i).bar;
+       bbt.beats = (*i).beat;
 
+       if ((*i).frame == frame) {
+               bbt.ticks = 0;
+       } else {
+               bbt.ticks = llrint (((frame - (*i).frame) / (*i).tempo->frames_per_beat(_frame_rate)) *
+                                   BBT_Time::ticks_per_beat);
+       }
 }
 
-framecnt_t
-TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, const BBT_Time& start, const BBT_Time& end) const
+framepos_t
+TempoMap::frame_time (const BBT_Time& bbt)
 {
-       /* this is used in timestamping the metrics by actually counting the beats */
-
-       framecnt_t frames = 0;
-       uint32_t bar = start.bars;
-       double beat = (double) start.beats;
-       double beats_counted = 0;
-       double beats_per_bar = 0;
-       double beat_frames = 0;
-
-       beats_per_bar = meter.beats_per_bar();
-       beat_frames = tempo.frames_per_beat (_frame_rate,meter);
-
-       frames = 0;
-
-       while (bar < end.bars || (bar == end.bars && beat < end.beats)) {
-
-               if (beat >= beats_per_bar) {
-                       beat = 1;
-                       ++bar;
-                       ++beats_counted;
-
-                       if (beat > beats_per_bar) {
-
-                               /* this is a fractional beat at the end of a fractional bar
-                                  so it should only count for the fraction
-                               */
-
-                               beats_counted -= (ceil(beats_per_bar) - beats_per_bar);
-                       }
-
-               } else {
-                       ++beat;
-                       ++beats_counted;
-               }
+       if (bbt.bars < 1) {
+               warning << string_compose (_("tempo map asked for frame time at bar < 1  (%1)\n"), bbt) << endmsg;
+               return 0;
+       }
+       
+       if (bbt.beats < 1) {
+               throw std::logic_error ("beats are counted from one");
        }
 
-       // cerr << "Counted " << beats_counted << " from " << start << " to " << end
-       // << " bpb were " << beats_per_bar
-       // << " fpb was " << beat_frames
-       // << endl;
-
-       frames = (framecnt_t) llrint (floor (beats_counted * beat_frames));
+       require_map_to (bbt);
 
-       return frames;
+       Glib::Threads::RWLock::ReaderLock lm (lock);
 
-}
+       BBTPointList::const_iterator s = bbt_before_or_at (BBT_Time (1, 1, 0));
+       BBTPointList::const_iterator e = bbt_before_or_at (BBT_Time (bbt.bars, bbt.beats, 0));
 
-framepos_t
-TempoMap::frame_time (const BBT_Time& bbt) const
-{
-       BBT_Time start ; /* 1|1|0 */
-
-       return count_frames_between (start, bbt);
+       if (bbt.ticks != 0) {
+               return ((*e).frame - (*s).frame) +
+                       llrint ((*e).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat));
+       } else {
+               return ((*e).frame - (*s).frame);
+       }
 }
 
 framecnt_t
-TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) const
+TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
 {
-       framecnt_t frames = 0;
-
        BBT_Time when;
-       bbt_time(pos, when);
-
-       {
-               Glib::RWLock::ReaderLock lm (lock);
-               frames = bbt_duration_at_unlocked (when, bbt,dir);
-       }
-
-       return frames;
+       bbt_time (pos, when);
+       
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return bbt_duration_at_unlocked (when, bbt, dir);
 }
 
 framecnt_t
-TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir) const
+TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int /*dir*/)
 {
-       framecnt_t frames = 0;
-
-       double beats_per_bar;
-       BBT_Time result;
-
-       result.bars = max(1U, when.bars + dir * bbt.bars) ;
-       result.beats = 1;
-       result.ticks = 0;
-
-       TempoMetric     metric = metric_at(result);
-       beats_per_bar = metric.meter().beats_per_bar();
-
-       /* Reduce things to legal bbt values we have to handle possible
-         fractional=shorter beats at the end of measures and things like 0|11|9000
-         as a duration in a 4.5/4 measure the musical decision is that the
-         fractional beat is also a beat , although a shorter one
-       */
-
-       if (dir >= 0) {
-               result.beats = when.beats +  bbt.beats;
-               result.ticks = when.ticks +  bbt.ticks;
-
-               while (result.beats >= (beats_per_bar + 1)) {
-                       result.bars++;
-                       result.beats -=  (uint32_t) ceil(beats_per_bar);
-                       metric = metric_at(result); // maybe there is a meter change
-                       beats_per_bar = metric.meter().beats_per_bar();
-
-               }
-
-               /* We now counted the beats and landed in the target measure, now deal
-                 with ticks this seems complicated, but we want to deal with the
-                 corner case of a sequence of time signatures like 0.2/4-0.7/4 and
-                 with request like bbt = 3|2|9000 ,so we repeat the same loop but add
-                 ticks
-               */
-
-               /* of course gtk_ardour only allows bar with at least 1.0 beats .....
-                */
-
-               uint32_t ticks_at_beat = (uint32_t) (result.beats == ceil(beats_per_bar) ?
-                                       (1 - (ceil(beats_per_bar) - beats_per_bar))* BBT_Time::ticks_per_beat
-                                          : BBT_Time::ticks_per_beat );
-
-               while (result.ticks >= ticks_at_beat) {
-                       result.beats++;
-                       result.ticks -= ticks_at_beat;
-                       if  (result.beats >= (beats_per_bar + 1)) {
-                               result.bars++;
-                               result.beats = 1;
-                               metric = metric_at(result); // maybe there is a meter change
-                               beats_per_bar = metric.meter().beats_per_bar();
-                       }
-                       ticks_at_beat= (uint32_t) (result.beats == ceil(beats_per_bar) ?
-                                      (1 - (ceil(beats_per_bar) - beats_per_bar) ) * BBT_Time::ticks_per_beat
-                                      : BBT_Time::ticks_per_beat);
-               }
-
-
-       } else {
-               uint32_t b = bbt.beats;
-
-               /* count beats */
-               while (b > when.beats) {
-                       --result.bars;
-                       result.bars = max(1U, result.bars);
-                       metric = metric_at(result); // maybe there is a meter change
-                       beats_per_bar = metric.meter().beats_per_bar();
-                       if (b >= ceil(beats_per_bar)) {
-                               b -= (uint32_t) ceil(beats_per_bar);
-                       } else {
-                               b = (uint32_t) ceil(beats_per_bar) - b + when.beats ;
-                       }
-               }
-               result.beats = when.beats - b;
-
-               /* count ticks */
-
-               if (bbt.ticks <= when.ticks) {
-                       result.ticks = when.ticks - bbt.ticks;
-               } else {
-
-                       uint32_t ticks_at_beat= (uint32_t) BBT_Time::ticks_per_beat;
-                       uint32_t t = bbt.ticks - when.ticks;
+       if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) {
+               return 0;
+       }
 
-                       do {
+       /* round back to the previous precise beat */
+       BBTPointList::const_iterator wi = bbt_before_or_at (BBT_Time (when.bars, when.beats, 0));
+       BBTPointList::const_iterator start (wi);
 
-                               if (result.beats == 1) {
-                                       --result.bars;
-                                       result.bars = max(1U, result.bars) ;
-                                       metric = metric_at(result); // maybe there is a meter change
-                                       beats_per_bar = metric.meter().beats_per_bar();
-                                       result.beats = (uint32_t) ceil(beats_per_bar);
-                                       ticks_at_beat = (uint32_t) ((1 - (ceil(beats_per_bar) - beats_per_bar)) * BBT_Time::ticks_per_beat) ;
-                               } else {
-                                       --result.beats;
-                                       ticks_at_beat = (uint32_t) BBT_Time::ticks_per_beat;
-                               }
+       assert (wi != _map.end());
 
-                               if (t <= ticks_at_beat) {
-                                       result.ticks = ticks_at_beat - t;
-                               } else {
-                                       t-= ticks_at_beat;
-                               }
-                       } while (t > ticks_at_beat);
+       uint32_t bars = 0;
+       uint32_t beats = 0;
 
+       while (wi != _map.end() && bars < bbt.bars) {
+               ++wi;
+               if ((*wi).is_bar()) {
+                       ++bars;
                }
+       }
+       assert (wi != _map.end());
 
-
+       while (wi != _map.end() && beats < bbt.beats) {
+               ++wi;
+               ++beats;
        }
+       assert (wi != _map.end());
+
+       /* add any additional frames related to ticks in the added value */
 
-       if (dir < 0) {
-               frames = count_frames_between(result, when);
+       if (bbt.ticks != 0) {
+               return ((*wi).frame - (*start).frame) +
+                       (*wi).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat);
        } else {
-               frames = count_frames_between(when,result);
+               return ((*wi).frame - (*start).frame);
        }
-
-       return frames;
 }
 
-
-
 framepos_t
-TempoMap::round_to_bar (framepos_t fr, int dir)
+TempoMap::round_to_bar (framepos_t fr, RoundMode dir)
 {
-       {
-               Glib::RWLock::ReaderLock lm (lock);
-               return round_to_type (fr, dir, Bar);
-       }
+       return round_to_type (fr, dir, Bar);
 }
 
-
 framepos_t
-TempoMap::round_to_beat (framepos_t fr, int dir)
+TempoMap::round_to_beat (framepos_t fr, RoundMode dir)
 {
-       {
-               Glib::RWLock::ReaderLock lm (lock);
-               return round_to_type (fr, dir, Beat);
-       }
+       return round_to_type (fr, dir, Beat);
 }
 
 framepos_t
-TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
+TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
 {
+       require_map_to (fr);
+
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       BBTPointList::const_iterator i = bbt_before_or_at (fr);
        BBT_Time the_beat;
-       uint32_t ticks_one_half_subdivisions_worth;
        uint32_t ticks_one_subdivisions_worth;
-       uint32_t difference;
 
-       bbt_time(fr, the_beat);
+       bbt_time (fr, the_beat, i);
+
+       DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round %1 to nearest 1/%2 beat, before-or-at = %3 @ %4|%5 precise = %6\n",
+                                                    fr, sub_num, (*i).frame, (*i).bar, (*i).beat, the_beat));
 
        ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_beat / sub_num;
-       ticks_one_half_subdivisions_worth = ticks_one_subdivisions_worth / 2;
 
        if (dir > 0) {
 
-               /* round to next */
+               /* round to next (or same iff dir == RoundUpMaybe) */
 
                uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
 
-               if (mod == 0) {
+               if (mod == 0 && dir == RoundUpMaybe) {
+                       /* right on the subdivision, which is fine, so do nothing */
+
+               } else if (mod == 0) {
                        /* right on the subdivision, so the difference is just the subdivision ticks */
-                       difference = ticks_one_subdivisions_worth;
+                       the_beat.ticks += ticks_one_subdivisions_worth;
 
                } else {
                        /* not on subdivision, compute distance to next subdivision */
 
-                       difference = ticks_one_subdivisions_worth - mod;
+                       the_beat.ticks += ticks_one_subdivisions_worth - mod;
+               }
+
+               if (the_beat.ticks > BBT_Time::ticks_per_beat) {
+                       assert (i != _map.end());
+                       ++i;
+                       assert (i != _map.end());
+                       the_beat.ticks -= BBT_Time::ticks_per_beat;
                }
 
-               the_beat = bbt_add (the_beat, BBT_Time (0, 0, difference));
 
        } else if (dir < 0) {
 
-               /* round to previous */
+               /* round to previous (or same iff dir == RoundDownMaybe) */
 
-               uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
+               uint32_t difference = the_beat.ticks % ticks_one_subdivisions_worth;
 
-               if (mod == 0) {
-                       /* right on the subdivision, so the difference is just the subdivision ticks */
+               if (difference == 0 && dir == RoundDownAlways) {
+                       /* right on the subdivision, but force-rounding down,
+                          so the difference is just the subdivision ticks */
                        difference = ticks_one_subdivisions_worth;
-               } else {
-                       /* not on subdivision, compute distance to previous subdivision, which
-                          is just the modulus.
-                       */
-
-                       difference = mod;
                }
 
-               try {
-                       the_beat = bbt_subtract (the_beat, BBT_Time (0, 0, difference));
-               } catch (...) {
-                       /* can't go backwards from wherever pos is, so just return it */
-                       return fr;
+               if (the_beat.ticks < difference) {
+                       if (i == _map.begin()) {
+                               /* can't go backwards from wherever pos is, so just return it */
+                               return fr;
+                       }
+                       --i;
+                       the_beat.ticks = BBT_Time::ticks_per_beat - the_beat.ticks;
+               } else {
+                       the_beat.ticks -= difference;
                }
 
        } else {
                /* round to nearest */
 
-               if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) {
-                       difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth);
-                       the_beat = bbt_add (the_beat, BBT_Time (0, 0, difference));
+               double rem;
+
+               /* compute the distance to the previous and next subdivision */
+               
+               if ((rem = fmod ((double) the_beat.ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) {
+                       
+                       /* closer to the next subdivision, so shift forward */
+
+                       the_beat.ticks = lrint (the_beat.ticks + (ticks_one_subdivisions_worth - rem));
+
+                       DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", the_beat.ticks));
+
+                       if (the_beat.ticks > BBT_Time::ticks_per_beat) {
+                               assert (i != _map.end());
+                               ++i;
+                               assert (i != _map.end());
+                               the_beat.ticks -= BBT_Time::ticks_per_beat;
+                               DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", the_beat));
+                       }
+
+               } else if (rem > 0) {
+                       
+                       /* closer to previous subdivision, so shift backward */
+
+                       if (rem > the_beat.ticks) {
+                               if (i == _map.begin()) {
+                                       /* can't go backwards past zero, so ... */
+                                       return 0;
+                               }
+                               /* step back to previous beat */
+                               --i;
+                               the_beat.ticks = lrint (BBT_Time::ticks_per_beat - rem);
+                               DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", the_beat));
+                       } else {
+                               the_beat.ticks = lrint (the_beat.ticks - rem);
+                               DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", the_beat.ticks));
+                       }
                } else {
-                       // difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth);
-                       the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth;
+                       /* on the subdivision, do nothing */
                }
        }
 
-       return frame_time (the_beat);
+       return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_beat) *
+               (*i).tempo->frames_per_beat (_frame_rate);
 }
 
 framepos_t
-TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
+TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
 {
-       TempoMetric metric = metric_at (frame);
-       BBT_Time bbt;
-       BBT_Time start;
-       BBT_Time one_bar (1,0,0);
-       BBT_Time one_beat (0,1,0);
+       require_map_to (frame);
+
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       BBTPointList::const_iterator fi;
+
+       if (dir > 0) {
+               fi = bbt_after_or_at (frame);
+       } else {
+               fi = bbt_before_or_at (frame);
+       }
 
-       bbt_time_with_metric (frame, bbt, metric);
+       assert (fi != _map.end());
 
+       DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to %6 in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame,
+                                                    (type == Bar ? "bar" : "beat")));
+               
        switch (type) {
        case Bar:
-               DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3) to bars in direction %2\n", frame, dir, bbt));
-
                if (dir < 0) {
+                       /* find bar previous to 'frame' */
 
-                       /* find bar position preceding frame */
-
-                       try {
-                               bbt = bbt_subtract (bbt, one_bar);
+                       if (fi == _map.begin()) {
+                               return 0;
                        }
 
-                       catch (...) {
-                               return frame;
+                       if ((*fi).is_bar() && (*fi).frame == frame) {
+                               if (dir == RoundDownMaybe) {
+                                       return frame;
+                               }
+                               --fi;
                        }
 
+                       while (!(*fi).is_bar()) {
+                               if (fi == _map.begin()) {
+                                       break;
+                               }
+                               fi--;
+                       }
+                       DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
+                                                                    (*fi).bar, (*fi).beat, (*fi).frame));
+                       return (*fi).frame;
 
                } else if (dir > 0) {
 
-                       /* find bar position following frame */
+                       /* find bar following 'frame' */
 
-                       try {
-                               bbt = bbt_add (bbt, one_bar, metric);
+                       if ((*fi).is_bar() && (*fi).frame == frame) {
+                               if (dir == RoundUpMaybe) {
+                                       return frame;
+                               }
+                               ++fi;
                        }
-                       catch (...) {
-                               return frame;
+
+                       while (!(*fi).is_bar()) {
+                               fi++;
+                               if (fi == _map.end()) {
+                                       --fi;
+                                       break;
+                               }
                        }
 
+                       DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
+                                                                    (*fi).bar, (*fi).beat, (*fi).frame));
+                       return (*fi).frame;
+
                } else {
+                       
+                       /* true rounding: find nearest bar */
 
-                       /* "true" rounding */
+                       BBTPointList::const_iterator prev = fi;
+                       BBTPointList::const_iterator next = fi;
 
-                       float midbar_beats;
-                       float midbar_ticks;
+                       if ((*fi).frame == frame) {
+                               return frame;
+                       }
 
-                       midbar_beats = metric.meter().beats_per_bar() / 2 + 1;
-                       midbar_ticks = BBT_Time::ticks_per_beat * fmod (midbar_beats, 1.0f);
-                       midbar_beats = floor (midbar_beats);
+                       while ((*prev).beat != 1) {
+                               if (prev == _map.begin()) {
+                                       break;
+                               }
+                               prev--;
+                       }
 
-                       BBT_Time midbar (bbt.bars, lrintf (midbar_beats), lrintf (midbar_ticks));
+                       while ((next != _map.end()) && (*next).beat != 1) {
+                               next++;
+                       }
 
-                       if (bbt < midbar) {
-                               /* round down */
-                               bbt.beats = 1;
-                               bbt.ticks = 0;
+                       if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) {
+                               return (*prev).frame;
                        } else {
-                               /* round up */
-                               bbt.bars++;
-                               bbt.beats = 1;
-                               bbt.ticks = 0;
+                               return (*next).frame;
                        }
+                       
                }
-               /* force beats & ticks to their values at the start of a bar */
-               bbt.beats = 1;
-               bbt.ticks = 0;
+
                break;
 
        case Beat:
-               DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3) to beat in direction %2\n", frame, (dir < 0 ? "back" : "forward"), bbt));
-
                if (dir < 0) {
 
-                       /* find beat position preceding frame */
-
-                       try {
-                               bbt = bbt_subtract (bbt, one_beat);
+                       if (fi == _map.begin()) {
+                               return 0;
                        }
 
-                       catch (...) {
-                               return frame;
+                       if ((*fi).frame > frame || ((*fi).frame == frame && dir == RoundDownAlways)) {
+                               DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step back\n");
+                               --fi;
                        }
-
-
+                       DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
+                                                                    (*fi).bar, (*fi).beat, (*fi).frame));
+                       return (*fi).frame;
                } else if (dir > 0) {
-
-                       /* find beat position following frame */
-
-                       try {
-                               bbt = bbt_add (bbt, one_beat, metric);
+                       if ((*fi).frame < frame || ((*fi).frame == frame && dir == RoundUpAlways)) {
+                               DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step forward\n");
+                               ++fi;
                        }
-                       catch (...) {
+                       DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
+                                                                    (*fi).bar, (*fi).beat, (*fi).frame));
+                       return (*fi).frame;
+               } else {
+                       /* find beat nearest to frame */
+                       if ((*fi).frame == frame) {
                                return frame;
                        }
 
-               } else {
-
-                       /* "true" rounding */
+                       BBTPointList::const_iterator prev = fi;
+                       BBTPointList::const_iterator next = fi;
 
-                       /* round to nearest beat */
-                       if (bbt.ticks >= (BBT_Time::ticks_per_beat/2)) {
-
-                               try {
-                                       bbt = bbt_add (bbt, one_beat, metric);
-                               }
-                               catch (...) {
-                                       return frame;
-                               }
+                       /* fi is already the beat before_or_at frame, and
+                          we've just established that its not at frame, so its
+                          the beat before frame.
+                       */
+                       ++next;
+                       
+                       if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) {
+                               return (*prev).frame;
+                       } else {
+                               return (*next).frame;
                        }
                }
-               /* force ticks to the value at the start of a beat */
-               bbt.ticks = 0;
                break;
-
        }
 
-       DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("\tat %1 count frames from %2 to %3 = %4\n", metric.frame(), metric.start(), bbt, count_frames_between (metric.start(), bbt)));
-       return metric.frame() + count_frames_between (metric.start(), bbt);
+       abort(); /* NOTREACHED */
+       return 0;
 }
 
-TempoMap::BBTPointList *
-TempoMap::get_points (framepos_t lower, framepos_t upper) const
+void
+TempoMap::get_grid (TempoMap::BBTPointList::const_iterator& begin,
+                   TempoMap::BBTPointList::const_iterator& end,
+                   framepos_t lower, framepos_t upper)
 {
-
-       Metrics::const_iterator i;
-       BBTPointList *points;
-       double current;
-       const MeterSection* meter;
-       const MeterSection* m;
-       const TempoSection* tempo;
-       const TempoSection* t;
-       uint32_t bar;
-       uint32_t beat;
-       double beats_per_bar;
-       double beat_frame;
-       double beat_frames;
-       double frames_per_bar;
-       double delta_bars;
-       double delta_beats;
-       double dummy;
-       framepos_t limit;
-
-       meter = &first_meter ();
-       tempo = &first_tempo ();
-
-       /* find the starting point */
-
-       for (i = metrics->begin(); i != metrics->end(); ++i) {
-
-               if ((*i)->frame() > lower) {
-                       break;
-               }
-
-               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                       tempo = t;
-               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                       meter = m;
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               if (_map.empty() || (_map.back().frame < upper)) {
+                       recompute_map (false, upper);
                }
        }
 
-       /* We now have:
-
-          meter -> the Meter for "lower"
-          tempo -> the Tempo for "lower"
-          i     -> for first new metric after "lower", possibly metrics->end()
-
-          Now start generating points.
-       */
-
-       beats_per_bar = meter->beats_per_bar ();
-       frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
-       beat_frames = tempo->frames_per_beat (_frame_rate, *meter);
-
-       if (meter->frame() > tempo->frame()) {
-               bar = meter->start().bars;
-               beat = meter->start().beats;
-               current = meter->frame();
-       } else {
-               bar = tempo->start().bars;
-               beat = tempo->start().beats;
-               current = tempo->frame();
-       }
-
-       /* initialize current to point to the bar/beat just prior to the
-          lower frame bound passed in.  assumes that current is initialized
-          above to be on a beat.
-       */
-
-       delta_bars = (lower-current) / frames_per_bar;
-       delta_beats = modf(delta_bars, &dummy) * beats_per_bar;
-       current += (floor(delta_bars) * frames_per_bar) +  (floor(delta_beats) * beat_frames);
-
-       // adjust bars and beats too
-       bar += (uint32_t) (floor(delta_bars));
-       beat += (uint32_t) (floor(delta_beats));
-
-       points = new BBTPointList;
-
-       do {
-
-               if (i == metrics->end()) {
-                       limit = upper;
-                       // cerr << "== limit set to end of request @ " << limit << endl;
-               } else {
-                       // cerr << "== limit set to next metric @ " << (*i)->frame() << endl;
-                       limit = (*i)->frame();
-               }
-
-               limit = min (limit, upper);
-
-               while (current < limit) {
-
-                       /* if we're at the start of a bar, add bar point */
-
-                       if (beat == 1) {
-                               if (current >= lower) {
-                                       // cerr << "Add Bar at " << bar << "|1" << " @ " << current << endl;
-                                       points->push_back (BBTPoint (*meter, *tempo,(framepos_t)rint(current), Bar, bar, 1));
-
-                               }
-                       }
-
-                       /* add some beats if we can */
+       begin = lower_bound (_map.begin(), _map.end(), lower);
+       end = upper_bound (_map.begin(), _map.end(), upper);
+}
 
-                       beat_frame = current;
+const TempoSection&
+TempoMap::tempo_section_at (framepos_t frame) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       Metrics::const_iterator i;
+       TempoSection* prev = 0;
 
-                       while (beat <= ceil(beats_per_bar) && beat_frame < limit) {
-                               if (beat_frame >= lower) {
-                                       // cerr << "Add Beat at " << bar << '|' << beat << " @ " << beat_frame << endl;
-                                       points->push_back (BBTPoint (*meter, *tempo, (framepos_t) rint(beat_frame), Beat, bar, beat));
-                               }
-                               beat_frame += beat_frames;
-                               current+= beat_frames;
+       for (i = metrics.begin(); i != metrics.end(); ++i) {
+               TempoSection* t;
 
-                               beat++;
-                       }
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
 
-                       //  cerr << "out of beats, @ end ? " << (i == metrics->end()) << " out of bpb ? "
-                       // << (beat > ceil(beats_per_bar))
-                       // << endl;
-
-                       if (beat > ceil(beats_per_bar) || i != metrics->end()) {
-
-                               /* we walked an entire bar. its
-                                  important to move `current' forward
-                                  by the actual frames_per_bar, not move it to
-                                  an integral beat_frame, so that metrics with
-                                  non-integral beats-per-bar have
-                                  their bar positions set
-                                  correctly. consider a metric with
-                                  9-1/2 beats-per-bar. the bar we
-                                  just filled had  10 beat marks,
-                                  but the bar end is 1/2 beat before
-                                  the last beat mark.
-                                  And it is also possible that a tempo
-                                  change occured in the middle of a bar,
-                                  so we subtract the possible extra fraction from the current
-                               */
-
-                               if (beat > ceil (beats_per_bar)) {
-                                       /* next bar goes where the numbers suggest */
-                                       current -=  beat_frames * (ceil(beats_per_bar)-beats_per_bar);
-                                       // cerr << "++ next bar from numbers\n";
-                               } else {
-                                       /* next bar goes where the next metric is */
-                                       current = limit;
-                                       // cerr << "++ next bar at next metric\n";
-                               }
-                               bar++;
-                               beat = 1;
+                       if ((*i)->frame() > frame) {
+                               break;
                        }
 
+                       prev = t;
                }
+       }
 
-               /* if we're done, then we're done */
-
-               if (current >= upper) {
-                       break;
-               }
-
-               /* i is an iterator that refers to the next metric (or none).
-                  if there is a next metric, move to it, and continue.
-               */
-
-               if (i != metrics->end()) {
-
-                       if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                               tempo = t;
-                       } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                               meter = m;
-                               /* new MeterSection, beat always returns to 1 */
-                               beat = 1;
-                       }
-
-                       current = (*i)->frame ();
-                       // cerr << "loop around with current @ " << current << endl;
-
-                       beats_per_bar = meter->beats_per_bar ();
-                       frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
-                       beat_frames = tempo->frames_per_beat (_frame_rate, *meter);
-
-                       ++i;
-               }
+       if (prev == 0) {
+               fatal << endmsg;
+               abort(); /*NOTREACHED*/
+       }
 
-       } while (1);
+       return *prev;
+}
 
-       return points;
+const Tempo&
+TempoMap::tempo_at (framepos_t frame) const
+{
+       TempoMetric m (metric_at (frame));
+       return m.tempo();
 }
 
-const TempoSection&
-TempoMap::tempo_section_at (framepos_t frame) const
+const MeterSection&
+TempoMap::meter_section_at (framepos_t frame) const
 {
-       Glib::RWLock::ReaderLock lm (lock);
+       Glib::Threads::RWLock::ReaderLock lm (lock);
        Metrics::const_iterator i;
-       TempoSection* prev = 0;
+       MeterSection* prev = 0;
 
-       for (i = metrics->begin(); i != metrics->end(); ++i) {
-               TempoSection* t;
+       for (i = metrics.begin(); i != metrics.end(); ++i) {
+               MeterSection* t;
 
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((t = dynamic_cast<MeterSection*> (*i)) != 0) {
 
                        if ((*i)->frame() > frame) {
                                break;
@@ -1528,19 +1642,12 @@ TempoMap::tempo_section_at (framepos_t frame) const
 
        if (prev == 0) {
                fatal << endmsg;
+               abort(); /*NOTREACHED*/
        }
 
        return *prev;
 }
 
-const Tempo&
-TempoMap::tempo_at (framepos_t frame) const
-{
-       TempoMetric m (metric_at (frame));
-       return m.tempo();
-}
-
-
 const Meter&
 TempoMap::meter_at (framepos_t frame) const
 {
@@ -1555,8 +1662,8 @@ TempoMap::get_state ()
        XMLNode *root = new XMLNode ("TempoMap");
 
        {
-               Glib::RWLock::ReaderLock lm (lock);
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
+               Glib::Threads::RWLock::ReaderLock lm (lock);
+               for (i = metrics.begin(); i != metrics.end(); ++i) {
                        root->add_child_nocopy ((*i)->get_state());
                }
        }
@@ -1568,51 +1675,84 @@ int
 TempoMap::set_state (const XMLNode& node, int /*version*/)
 {
        {
-               Glib::RWLock::WriterLock lm (lock);
+               Glib::Threads::RWLock::WriterLock lm (lock);
 
                XMLNodeList nlist;
                XMLNodeConstIterator niter;
-               Metrics old_metrics (*metrics);
-
-               metrics->clear();
+               Metrics old_metrics (metrics);
+               MeterSection* last_meter = 0;
+               metrics.clear();
 
                nlist = node.children();
-
+               
                for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
                        XMLNode* child = *niter;
 
                        if (child->name() == TempoSection::xml_state_node_name) {
 
                                try {
-                                       metrics->push_back (new TempoSection (*child));
+                                       TempoSection* ts = new TempoSection (*child);
+                                       metrics.push_back (ts);
+
+                                       if (ts->bar_offset() < 0.0) {
+                                               if (last_meter) {
+                                                       ts->update_bar_offset_from_bbt (*last_meter);
+                                               }
+                                       }
                                }
 
                                catch (failed_constructor& err){
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
-                                       *metrics = old_metrics;
+                                       metrics = old_metrics;
                                        break;
                                }
 
                        } else if (child->name() == MeterSection::xml_state_node_name) {
 
                                try {
-                                       metrics->push_back (new MeterSection (*child));
+                                       MeterSection* ms = new MeterSection (*child);
+                                       metrics.push_back (ms);
+                                       last_meter = ms;
                                }
 
                                catch (failed_constructor& err) {
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
-                                       *metrics = old_metrics;
+                                       metrics = old_metrics;
                                        break;
                                }
                        }
                }
 
                if (niter == nlist.end()) {
-
                        MetricSectionSorter cmp;
-                       metrics->sort (cmp);
-                       timestamp_metrics (true);
+                       metrics.sort (cmp);
+               }
+
+               /* check for multiple tempo/meters at the same location, which
+                  ardour2 somehow allowed.
+               */
+
+               Metrics::iterator prev = metrics.end();
+               for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+                       if (prev != metrics.end()) {
+                               if (dynamic_cast<MeterSection*>(*prev) && dynamic_cast<MeterSection*>(*i)) {
+                                       if ((*prev)->start() == (*i)->start()) {
+                                               cerr << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg;
+                                               error << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg;
+                                               return -1;
+                                       }
+                               } else if (dynamic_cast<TempoSection*>(*prev) && dynamic_cast<TempoSection*>(*i)) {
+                                       if ((*prev)->start() == (*i)->start()) {
+                                               cerr << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg;
+                                               error << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg;
+                                               return -1;
+                                       }
+                               }
+                       }
+                       prev = i;
                }
+
+               recompute_map (true, -1);
        }
 
        PropertyChanged (PropertyChange ());
@@ -1623,17 +1763,18 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 void
 TempoMap::dump (std::ostream& o) const
 {
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
        const MeterSection* m;
        const TempoSection* t;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
                if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                       o << "Tempo @ " << *i << ' ' << t->beats_per_minute() << " BPM (denom = " << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (move? "
+                       o << "Tempo @ " << *i << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (movable? "
                          << t->movable() << ')' << endl;
                } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                       o << "Meter @ " << *i << ' ' << m->beats_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
-                         << " (move? " << m->movable() << ')' << endl;
+                       o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
+                         << " (movable? " << m->movable() << ')' << endl;
                }
        }
 }
@@ -1641,10 +1782,10 @@ TempoMap::dump (std::ostream& o) const
 int
 TempoMap::n_tempos() const
 {
-       Glib::RWLock::ReaderLock lm (lock);
+       Glib::Threads::RWLock::ReaderLock lm (lock);
        int cnt = 0;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if (dynamic_cast<const TempoSection*>(*i) != 0) {
                        cnt++;
                }
@@ -1656,10 +1797,10 @@ TempoMap::n_tempos() const
 int
 TempoMap::n_meters() const
 {
-       Glib::RWLock::ReaderLock lm (lock);
+       Glib::Threads::RWLock::ReaderLock lm (lock);
        int cnt = 0;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if (dynamic_cast<const MeterSection*>(*i) != 0) {
                        cnt++;
                }
@@ -1671,252 +1812,376 @@ TempoMap::n_meters() const
 void
 TempoMap::insert_time (framepos_t where, framecnt_t amount)
 {
-       for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
-               if ((*i)->frame() >= where && (*i)->movable ()) {
-                       (*i)->set_frame ((*i)->frame() + amount);
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+                       if ((*i)->frame() >= where && (*i)->movable ()) {
+                               (*i)->set_frame ((*i)->frame() + amount);
+                       }
+               }
+
+               /* now reset the BBT time of all metrics, based on their new
+                * audio time. This is the only place where we do this reverse
+                * timestamp.
+                */
+
+               Metrics::iterator i;
+               const MeterSection* meter;
+               const TempoSection* tempo;
+               MeterSection *m;
+               TempoSection *t;
+               
+               meter = &first_meter ();
+               tempo = &first_tempo ();
+               
+               BBT_Time start;
+               BBT_Time end;
+               
+               // cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl;
+               
+               bool first = true;
+               MetricSection* prev = 0;
+               
+               for (i = metrics.begin(); i != metrics.end(); ++i) {
+                       
+                       BBT_Time bbt;
+                       TempoMetric metric (*meter, *tempo);
+                       
+                       if (prev) {
+                               metric.set_start (prev->start());
+                               metric.set_frame (prev->frame());
+                       } else {
+                               // metric will be at frames=0 bbt=1|1|0 by default
+                               // which is correct for our purpose
+                       }
+                       
+                       BBTPointList::const_iterator bi = bbt_before_or_at ((*i)->frame());
+                       bbt_time ((*i)->frame(), bbt, bi);
+                       
+                       // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
+                       
+                       if (first) {
+                               first = false;
+                       } else {
+                               
+                               if (bbt.ticks > BBT_Time::ticks_per_beat/2) {
+                                       /* round up to next beat */
+                                       bbt.beats += 1;
+                               }
+                               
+                               bbt.ticks = 0;
+                               
+                               if (bbt.beats != 1) {
+                                       /* round up to next bar */
+                                       bbt.bars += 1;
+                                       bbt.beats = 1;
+                               }
+                       }
+                       
+                       // cerr << bbt << endl;
+                       
+                       (*i)->set_start (bbt);
+                       
+                       if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
+                               tempo = t;
+                               // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
+                       } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
+                               meter = m;
+                               // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
+                       } else {
+                               fatal << _("programming error: unhandled MetricSection type") << endmsg;
+                               abort(); /*NOTREACHED*/
+                       }
+                       
+                       prev = (*i);
                }
+               
+               recompute_map (true);
        }
 
-       timestamp_metrics (false);
 
        PropertyChanged (PropertyChange ());
 }
-
-BBT_Time
-TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& other) const
+bool
+TempoMap::remove_time (framepos_t where, framecnt_t amount)
 {
-       TempoMetric metric =  metric_at (start);
-       return bbt_add (start, other, metric);
+       bool moved = false;
+
+       std::list<MetricSection*> metric_kill_list;
+       
+       TempoSection* last_tempo = NULL;
+       MeterSection* last_meter = NULL;
+       bool tempo_after = false; // is there a tempo marker at the first sample after the removed range?
+       bool meter_after = false; // is there a meter marker likewise?
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+               for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+                       if ((*i)->frame() >= where && (*i)->frame() < where+amount) {
+                               metric_kill_list.push_back(*i);
+                               TempoSection *lt = dynamic_cast<TempoSection*> (*i);
+                               if (lt)
+                                       last_tempo = lt;
+                               MeterSection *lm = dynamic_cast<MeterSection*> (*i);
+                               if (lm)
+                                       last_meter = lm;
+                       }
+                       else if ((*i)->frame() >= where) {
+                               // TODO: make sure that moved tempo/meter markers are rounded to beat/bar boundaries
+                               (*i)->set_frame ((*i)->frame() - amount);
+                               if ((*i)->frame() == where) {
+                                       // marker was immediately after end of range
+                                       tempo_after = dynamic_cast<TempoSection*> (*i);
+                                       meter_after = dynamic_cast<MeterSection*> (*i);
+                               }
+                               moved = true;
+                       }
+               }
+
+               //find the last TEMPO and METER metric (if any) and move it to the cut point so future stuff is correct
+               if (last_tempo && !tempo_after) {
+                       metric_kill_list.remove(last_tempo);
+                       last_tempo->set_frame(where);
+                       moved = true;
+               }
+               if (last_meter && !meter_after) {
+                       metric_kill_list.remove(last_meter);
+                       last_meter->set_frame(where);
+                       moved = true;
+               }
+
+               //remove all the remaining metrics
+               for (std::list<MetricSection*>::iterator i = metric_kill_list.begin(); i != metric_kill_list.end(); ++i) {
+                       metrics.remove(*i);
+                       moved = true;
+               }
+
+               if (moved) {
+                       recompute_map (true);
+               }
+       }       
+       PropertyChanged (PropertyChange ());
+       return moved;
 }
 
-/**
- * add the BBT interval @param increment to  @param start and return the result
+/** Add some (fractional) beats to a session frame position, and return the result in frames.
+ *  pos can be -ve, if required.
  */
-BBT_Time
-TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& increment, const TempoMetric& /*metric*/) const
+framepos_t
+TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const
 {
-       BBT_Time result = start;
-       BBT_Time op = increment; /* argument is const, but we need to modify it */
-       uint32_t ticks = result.ticks + op.ticks;
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       Metrics::const_iterator next_tempo;
+       const TempoSection* tempo = 0;
 
-       if (ticks >= BBT_Time::ticks_per_beat) {
-               op.beats++;
-               result.ticks = ticks % (uint32_t) BBT_Time::ticks_per_beat;
-       } else {
-               result.ticks += op.ticks;
-       }
+       /* Find the starting tempo metric */
 
-       /* now comes the complicated part. we have to add one beat a time,
-          checking for a new metric on every beat.
-       */
+       for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
+
+               const TempoSection* t;
+
+               if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
 
-       /* grab all meter sections */
+                       /* This is a bit of a hack, but pos could be -ve, and if it is,
+                          we consider the initial metric changes (at time 0) to actually
+                          be in effect at pos.
+                       */
 
-       list<const MeterSection*> meter_sections;
+                       framepos_t f = (*next_tempo)->frame ();
 
-       for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) {
-               const MeterSection* ms;
-               if ((ms = dynamic_cast<const MeterSection*>(*x)) != 0) {
-                       meter_sections.push_back (ms);
+                       if (pos < 0 && f == 0) {
+                               f = pos;
+                       }
+                       
+                       if (f > pos) {
+                               break;
+                       }
+                       
+                       tempo = t;
                }
        }
 
-       assert (!meter_sections.empty());
-
-       list<const MeterSection*>::const_iterator next_meter;
-       const Meter* meter = 0;
+       /* We now have:
 
-       /* go forwards through the meter sections till we get to the one
-          covering the current value of result. this positions i to point to
-          the next meter section too, or the end.
+          tempo       -> the Tempo for "pos"
+          next_tempo  -> first tempo after "pos", possibly metrics.end()
        */
+       assert(tempo);
 
-       for (next_meter = meter_sections.begin(); next_meter != meter_sections.end(); ++next_meter) {
+       DEBUG_TRACE (DEBUG::TempoMath,
+                    string_compose ("frame %1 plus %2 beats, start with tempo = %3 @ %4\n",
+                                    pos, beats, *((const Tempo*)tempo), tempo->frame()));
 
-               if (result < (*next_meter)->start()) {
-                       /* this metric is past the result time. stop looking, we have what we need */
-                       break;
-               }
+       while (!!beats) {
 
-               if (result == (*next_meter)->start()) {
-                       /* this meter section starts at result, push i beyond it so that it points
-                          to the NEXT section, opwise we will get stuck later, and use this meter section.
-                       */
-                       meter = *next_meter;
-                       ++next_meter;
-                       break;
-               }
+               /* Distance to the end of this section in frames */
+               framecnt_t distance_frames = (next_tempo == metrics.end() ? max_framepos : ((*next_tempo)->frame() - pos));
 
-               meter = *next_meter;
-       }
+               /* Distance to the end in beats */
+               Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate(
+                       distance_frames, tempo->frames_per_beat (_frame_rate));
 
-       assert (meter != 0);
+               /* Amount to subtract this time */
+               Evoral::Beats const delta = min (distance_beats, beats);
 
-       /* OK, now have the meter for the bar start we are on, and i is an iterator
-          that points to the metric after the one we are currently dealing with
-          (or to metrics->end(), of course)
-       */
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n",
+                                                              (next_tempo == metrics.end() ? max_framepos : (*next_tempo)->frame()),
+                                                              distance_frames, distance_beats));
 
-       while (op.beats) {
+               /* Update */
+               beats -= delta;
+               pos += delta.to_ticks(tempo->frames_per_beat (_frame_rate));
 
-               /* given the current meter, have we gone past the end of the bar ? */
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left\n", pos, beats));
 
-               if (result.beats >= meter->beats_per_bar()) {
-                       /* move to next bar, first beat */
-                       result.bars++;
-                       result.beats = 1;
-               } else {
-                       result.beats++;
-               }
+               /* step forwards to next tempo section */
 
-               /* one down ... */
+               if (next_tempo != metrics.end()) {
 
-               op.beats--;
+                       tempo = dynamic_cast<const TempoSection*>(*next_tempo);
 
-               /* check if we need to use a new meter section: has adding beats to result taken us
-                  to or after the start of the next meter section? in which case, use it.
-               */
+                       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n",
+                                                                      *((const Tempo*)tempo), tempo->frame(),
+                                                                      tempo->frames_per_beat (_frame_rate)));
 
-               if (next_meter != meter_sections.end() && (((*next_meter)->start () < result) || (result == (*next_meter)->start()))) {
-                       meter = *next_meter;
-                       ++next_meter;
+                       while (next_tempo != metrics.end ()) {
+
+                               ++next_tempo;
+                               
+                               if (next_tempo != metrics.end() && dynamic_cast<const TempoSection*>(*next_tempo)) {
+                                       break;
+                               }
+                       }
                }
        }
 
-       /* finally, add bars */
-
-       result.bars += op.bars++;
-
-       return result;
+       return pos;
 }
 
-/**
- * subtract the BBT interval @param decrement from @param start and return the result
- */
-BBT_Time
-TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const
+/** Subtract some (fractional) beats from a frame position, and return the result in frames */
+framepos_t
+TempoMap::framepos_minus_beats (framepos_t pos, Evoral::Beats beats) const
 {
-       BBT_Time result = start;
-       BBT_Time op = decrement; /* argument is const, but we need to modify it */
-
-       if (op.ticks > result.ticks) {
-               /* subtract an extra beat later; meanwhile set ticks to the right "carry" value */
-               op.beats++;
-               result.ticks = BBT_Time::ticks_per_beat - (op.ticks - result.ticks);
-       } else {
-               result.ticks -= op.ticks;
-       }
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       Metrics::const_reverse_iterator prev_tempo;
+       const TempoSection* tempo = 0;
 
-       /* now comes the complicated part. we have to subtract one beat a time,
-          checking for a new metric on every beat.
-       */
-
-       /* grab all meter sections */
-
-       list<const MeterSection*> meter_sections;
+       /* Find the starting tempo metric */
 
-       for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) {
-               const MeterSection* ms;
-               if ((ms = dynamic_cast<const MeterSection*>(*x)) != 0) {
-                       meter_sections.push_back (ms);
-               }
-               }
+       for (prev_tempo = metrics.rbegin(); prev_tempo != metrics.rend(); ++prev_tempo) {
 
-       assert (!meter_sections.empty());
+               const TempoSection* t;
 
-       /* go backwards through the meter sections till we get to the one
-          covering the current value of result. this positions i to point to
-          the next (previous) meter section too, or the end.
-       */
+               if ((t = dynamic_cast<const TempoSection*>(*prev_tempo)) != 0) {
 
-       const MeterSection* meter = 0;
-       list<const MeterSection*>::reverse_iterator next_meter; // older versions of GCC don't
-                                                               // support const_reverse_iterator::operator!=()
+                       /* This is a bit of a hack, but pos could be -ve, and if it is,
+                          we consider the initial metric changes (at time 0) to actually
+                          be in effect at pos.
+                       */
 
-       for (next_meter = meter_sections.rbegin(); next_meter != meter_sections.rend(); ++next_meter) {
+                       framepos_t f = (*prev_tempo)->frame ();
 
-               /* when we find the first meter section that is before or at result, use it,
-                  and set next_meter to the previous one
-               */
+                       if (pos < 0 && f == 0) {
+                               f = pos;
+                       }
 
-               if ((*next_meter)->start() < result || (*next_meter)->start() == result) {
-                       meter = *next_meter;
-                       ++next_meter;
-                       break;
+                       /* this is slightly more complex than the forward case
+                          because we reach the tempo in effect at pos after
+                          passing through pos (rather before, as in the
+                          forward case). having done that, we then need to
+                          keep going to get the previous tempo (or
+                          metrics.rend())
+                       */
+                       
+                       if (f <= pos) {
+                               if (tempo == 0) {
+                                       /* first tempo with position at or
+                                          before pos
+                                       */
+                                       tempo = t;
+                               } else if (f < pos) {
+                                       /* some other tempo section that
+                                          is even earlier than 'tempo'
+                                       */
+                                       break;
+                               }
+                       }
                }
        }
 
-       assert (meter != 0);
+       assert(tempo);
+       DEBUG_TRACE (DEBUG::TempoMath,
+                    string_compose ("frame %1 minus %2 beats, start with tempo = %3 @ %4 prev at beg? %5\n",
+                                    pos, beats, *((const Tempo*)tempo), tempo->frame(),
+                                    prev_tempo == metrics.rend()));
 
-       /* OK, now have the meter for the bar start we are on, and i is an iterator
-          that points to the metric after the one we are currently dealing with
-          (or to metrics->end(), of course)
+       /* We now have:
+
+          tempo       -> the Tempo for "pos"
+          prev_tempo  -> the first metric before "pos", possibly metrics.rend()
        */
 
-       while (op.beats) {
+       while (!!beats) {
+               
+               /* Distance to the start of this section in frames */
+               framecnt_t distance_frames = (pos - tempo->frame());
 
-               /* have we reached the start of the bar? if so, move to the last beat of the previous
-                  bar. opwise, just step back 1 beat.
-               */
+               /* Distance to the start in beats */
+               Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate(
+                       distance_frames, tempo->frames_per_beat (_frame_rate));
 
-               if (result.beats == 1) {
+               /* Amount to subtract this time */
+               Evoral::Beats const sub = min (distance_beats, beats);
 
-                       /* move to previous bar, last beat */
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n",
+                                                              tempo->frame(), distance_frames, distance_beats));
+               /* Update */
 
-                       if (result.bars <= 1) {
-                               /* i'm sorry dave, i can't do that */
-                               throw std::out_of_range ("illegal BBT subtraction");
-                       }
+               beats -= sub;
+               pos -= sub.to_double() * tempo->frames_per_beat (_frame_rate);
 
-                       result.bars--;
-                       result.beats = meter->beats_per_bar();
-               } else {
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left, prev at end ? %3\n", pos, beats,
+                                                              (prev_tempo == metrics.rend())));
 
-                       /* back one beat */
+               /* step backwards to prior TempoSection */
 
-                       result.beats--;
-               }
+               if (prev_tempo != metrics.rend()) {
 
-               /* one down ... */
-               op.beats--;
+                       tempo = dynamic_cast<const TempoSection*>(*prev_tempo);
 
-               /* check if we need to use a new meter section: has subtracting beats to result taken us
-                  to before the start of the current meter section? in which case, use the prior one.
-               */
+                       DEBUG_TRACE (DEBUG::TempoMath,
+                                    string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n",
+                                                    *((const Tempo*)tempo), tempo->frame(),
+                                                    tempo->frames_per_beat (_frame_rate)));
 
-               if (result < meter->start() && next_meter != meter_sections.rend()) {
-                       meter = *next_meter;
-                       ++next_meter;
-               }
-       }
+                       while (prev_tempo != metrics.rend ()) {
 
-       /* finally, subtract bars */
+                               ++prev_tempo;
 
-       if (op.bars >= result.bars) {
-               /* i'm sorry dave, i can't do that */
-               throw std::out_of_range ("illegal BBT subtraction");
+                               if (prev_tempo != metrics.rend() && dynamic_cast<const TempoSection*>(*prev_tempo) != 0) {
+                                       break;
+                               }
+                       }
+               } else {
+                       pos -= llrint (beats.to_double() * tempo->frames_per_beat (_frame_rate));
+                       beats = Evoral::Beats();
+               }
        }
 
-       result.bars -= op.bars;
-       return result;
+       return pos;
 }
 
 /** Add the BBT interval op to pos and return the result */
 framepos_t
 TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 {
-       /* XXX: this is a little inaccurate as small errors are introduced
-          every time a probably-fractional product of something and
-          frames_per_beat is rounded.  Other errors can be introduced
-          by op.ticks' integer nature.
-       */
-
+       Glib::Threads::RWLock::ReaderLock lm (lock);
        Metrics::const_iterator i;
        const MeterSection* meter;
        const MeterSection* m;
        const TempoSection* tempo;
        const TempoSection* t;
-       framecnt_t frames_per_beat;
+       double frames_per_beat;
+       framepos_t effective_pos = max (pos, (framepos_t) 0);
 
        meter = &first_meter ();
        tempo = &first_tempo ();
@@ -1926,9 +2191,9 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 
        /* find the starting metrics for tempo & meter */
 
-       for (i = metrics->begin(); i != metrics->end(); ++i) {
+       for (i = metrics.begin(); i != metrics.end(); ++i) {
 
-               if ((*i)->frame() > pos) {
+               if ((*i)->frame() > effective_pos) {
                        break;
                }
 
@@ -1943,69 +2208,96 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 
           meter -> the Meter for "pos"
           tempo -> the Tempo for "pos"
-          i     -> for first new metric after "pos", possibly metrics->end()
+          i     -> for first new metric after "pos", possibly metrics.end()
        */
 
        /* now comes the complicated part. we have to add one beat a time,
           checking for a new metric on every beat.
        */
 
-       frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter);
+       frames_per_beat = tempo->frames_per_beat (_frame_rate);
+
+       uint64_t bars = 0;
 
        while (op.bars) {
 
-               pos += llrint (frames_per_beat * meter->beats_per_bar());
+               bars++;
                op.bars--;
 
                /* check if we need to use a new metric section: has adding frames moved us
                   to or after the start of the next metric section? in which case, use it.
                */
 
-               if (i != metrics->end()) {
+               if (i != metrics.end()) {
                        if ((*i)->frame() <= pos) {
 
+                               /* about to change tempo or meter, so add the
+                                * number of frames for the bars we've just
+                                * traversed before we change the
+                                * frames_per_beat value.
+                                */
+                               
+                               pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
+                               bars = 0;
+
                                if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
                                        tempo = t;
                                } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
                                        meter = m;
                                }
                                ++i;
-                               frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter);
+                               frames_per_beat = tempo->frames_per_beat (_frame_rate);
 
                        }
                }
 
        }
 
+       pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
+
+       uint64_t beats = 0;
+
        while (op.beats) {
 
                /* given the current meter, have we gone past the end of the bar ? */
 
-               pos += frames_per_beat;
+               beats++;
                op.beats--;
 
                /* check if we need to use a new metric section: has adding frames moved us
                   to or after the start of the next metric section? in which case, use it.
                */
 
-               if (i != metrics->end()) {
+               if (i != metrics.end()) {
                        if ((*i)->frame() <= pos) {
 
+                               /* about to change tempo or meter, so add the
+                                * number of frames for the beats we've just
+                                * traversed before we change the
+                                * frames_per_beat value.
+                                */
+
+                               pos += llrint (beats * frames_per_beat);
+                               beats = 0;
+
                                if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
                                        tempo = t;
                                } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
                                        meter = m;
                                }
                                ++i;
-                               frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter);
+                               frames_per_beat = tempo->frames_per_beat (_frame_rate);
                        }
                }
        }
 
+       pos += llrint (beats * frames_per_beat);
+
        if (op.ticks) {
                if (op.ticks >= BBT_Time::ticks_per_beat) {
-                       pos += frames_per_beat;
-                       pos += llrint (frames_per_beat * ((op.ticks % (uint32_t) BBT_Time::ticks_per_beat) / (double) BBT_Time::ticks_per_beat));
+                       pos += llrint (frames_per_beat + /* extra beat */
+                                      (frames_per_beat * ((op.ticks % (uint32_t) BBT_Time::ticks_per_beat) /
+                                                          (double) BBT_Time::ticks_per_beat)));
                } else {
                        pos += llrint (frames_per_beat * (op.ticks / (double) BBT_Time::ticks_per_beat));
                }
@@ -2014,121 +2306,197 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
        return pos;
 }
 
-/** Count the number of beats that are equivalent to distance when starting at pos */
-double
+/** Count the number of beats that are equivalent to distance when going forward,
+    starting at pos.
+*/
+Evoral::Beats
 TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 {
-       Metrics::const_iterator i;
-       double beats = 0;
-       const MeterSection* meter;
-       const MeterSection* m;
-       const TempoSection* tempo;
-       const TempoSection* t;
-       double frames_per_beat;
-
-       double ddist = distance;
-       double dpos = pos;
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       Metrics::const_iterator next_tempo;
+       const TempoSection* tempo = 0;
+       framepos_t effective_pos = max (pos, (framepos_t) 0);
 
-       meter = &first_meter ();
-       tempo = &first_tempo ();
+       /* Find the relevant initial tempo metric  */
 
-       assert (meter);
-       assert (tempo);
+       for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
 
-       /* find the starting metrics for tempo & meter */
+               const TempoSection* t;
 
-       for (i = metrics->begin(); i != metrics->end(); ++i) {
+               if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
 
-               if ((*i)->frame() > pos) {
-                       break;
-               }
+                       if ((*next_tempo)->frame() > effective_pos) {
+                               break;
+                       }
 
-               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
                        tempo = t;
-               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                       meter = m;
                }
        }
 
        /* We now have:
 
-          meter -> the Meter for "pos"
           tempo -> the Tempo for "pos"
-          i     -> for first new metric after "pos", possibly metrics->end()
-       */
-
-       /* now comes the complicated part. we have to add one beat a time,
-          checking for a new metric on every beat.
+          next_tempo -> the next tempo after "pos", possibly metrics.end()
        */
+       assert (tempo);
 
-       frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter);
+       DEBUG_TRACE (DEBUG::TempoMath,
+                    string_compose ("frame %1 walk by %2 frames, start with tempo = %3 @ %4\n",
+                                    pos, distance, *((const Tempo*)tempo), tempo->frame()));
+       
+       Evoral::Beats beats = Evoral::Beats();
 
-       while (ddist > 0) {
+       while (distance) {
 
-               /* if we're nearly at the end, but have a fractional beat left,
-                  compute the fraction and then its all over
-               */
+               /* End of this section */
+               framepos_t end;
+               /* Distance to `end' in frames */
+               framepos_t distance_to_end;
 
-               if (ddist < frames_per_beat) {
-                       beats += ddist / frames_per_beat;
-                       break;
+               if (next_tempo == metrics.end ()) {
+                       /* We can't do (end - pos) if end is max_framepos, as it will overflow if pos is -ve */
+                       end = max_framepos;
+                       distance_to_end = max_framepos;
+               } else {
+                       end = (*next_tempo)->frame ();
+                       distance_to_end = end - pos;
                }
 
-               /* walk one beat */
+               /* Amount to subtract this time in frames */
+               framecnt_t const sub = min (distance, distance_to_end);
 
-               ddist -= frames_per_beat;
-               dpos += frames_per_beat;
-               beats += 1.0;
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("to reach end at %1 (end ? %2), distance= %3 sub=%4\n", end, (next_tempo == metrics.end()),
+                                                              distance_to_end, sub));
 
-               /* check if we need to use a new metric section: has adding frames moved us
-                  to or after the start of the next metric section? in which case, use it.
-               */
+               /* Update */
+               pos += sub;
+               distance -= sub;
+               assert (tempo);
+               beats += Evoral::Beats::ticks_at_rate(sub, tempo->frames_per_beat (_frame_rate));
 
-               if (i != metrics->end()) {
-                       if ((*i)->frame() <= (framepos_t) dpos) {
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1, beats = %2 distance left %3\n",
+                                                              pos, beats, distance));
 
-                               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                                       tempo = t;
-                               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                                       meter = m;
+               /* Move on if there's anything to move to */
+
+               if (next_tempo != metrics.end()) {
+
+                       tempo = dynamic_cast<const TempoSection*>(*next_tempo);
+
+                       DEBUG_TRACE (DEBUG::TempoMath,
+                                    string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n",
+                                                    *((const Tempo*)tempo), tempo->frame(),
+                                                    tempo->frames_per_beat (_frame_rate)));
+
+                       while (next_tempo != metrics.end ()) {
+
+                               ++next_tempo;
+                               
+                               if (next_tempo != metrics.end() && dynamic_cast<const TempoSection*>(*next_tempo)) {
+                                       break;
                                }
-                               ++i;
-                               frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter);
                        }
-               }
 
+                       if (next_tempo == metrics.end()) {
+                               DEBUG_TRACE (DEBUG::TempoMath, "no more tempo sections\n");
+                       } else {
+                               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("next tempo section is %1 @ %2\n",
+                                                                              **next_tempo, (*next_tempo)->frame()));
+                       }
+
+               }
+               assert (tempo);
        }
 
        return beats;
 }
 
+TempoMap::BBTPointList::const_iterator
+TempoMap::bbt_before_or_at (framepos_t pos)
+{
+       /* CALLER MUST HOLD READ LOCK */
 
-/** Compare the time of this with that of another MetricSection.
- *  @param with_bbt True to compare using start(), false to use frame().
- *  @return -1 for less than, 0 for equal, 1 for greater than.
- */
+       BBTPointList::const_iterator i;
 
-int
-MetricSection::compare (MetricSection* other, bool with_bbt) const
+       if (pos < 0) {
+               /* not really correct, but we should catch pos < 0 at a higher
+                  level
+               */
+               return _map.begin();
+       }
+
+       i = lower_bound (_map.begin(), _map.end(), pos);
+       assert (i != _map.end());
+       if ((*i).frame > pos) {
+               assert (i != _map.begin());
+               --i;
+       }
+       return i;
+}
+
+struct bbtcmp {
+    bool operator() (const BBT_Time& a, const BBT_Time& b) {
+           return a < b;
+    }
+};
+
+TempoMap::BBTPointList::const_iterator
+TempoMap::bbt_before_or_at (const BBT_Time& bbt)
 {
-       if (with_bbt) {
-               if (start() == other->start()) {
-                       return 0;
-               } else if (start() < other->start()) {
-                       return -1;
-               } else {
-                       return 1;
-               }
-       } else {
-               if (frame() == other->frame()) {
-                       return 0;
-               } else if (frame() < other->frame()) {
-                       return -1;
-               } else {
-                       return 1;
-               }
+       BBTPointList::const_iterator i;
+       bbtcmp cmp;
+
+       i = lower_bound (_map.begin(), _map.end(), bbt, cmp);
+       assert (i != _map.end());
+       if ((*i).bar > bbt.bars || (*i).beat > bbt.beats) {
+               assert (i != _map.begin());
+               --i;
        }
+       return i;
+}
 
-       /* NOTREACHED */
-       return 0;
+TempoMap::BBTPointList::const_iterator
+TempoMap::bbt_after_or_at (framepos_t pos)
+{
+       /* CALLER MUST HOLD READ LOCK */
+
+       BBTPointList::const_iterator i;
+
+       if (_map.back().frame == pos) {
+               i = _map.end();
+               assert (i != _map.begin());
+               --i;
+               return i;
+       }
+
+       i = upper_bound (_map.begin(), _map.end(), pos);
+       assert (i != _map.end());
+       return i;
+}
+
+std::ostream&
+operator<< (std::ostream& o, const Meter& m) {
+       return o << m.divisions_per_bar() << '/' << m.note_divisor();
+}
+
+std::ostream&
+operator<< (std::ostream& o, const Tempo& t) {
+       return o << t.beats_per_minute() << " 1/" << t.note_type() << "'s per minute";
+}
+
+std::ostream&
+operator<< (std::ostream& o, const MetricSection& section) {
+
+       o << "MetricSection @ " << section.frame() << " aka " << section.start() << ' ';
+
+       const TempoSection* ts;
+       const MeterSection* ms;
+
+       if ((ts = dynamic_cast<const TempoSection*> (&section)) != 0) {
+               o << *((const Tempo*) ts);
+       } else if ((ms = dynamic_cast<const MeterSection*> (&section)) != 0) {
+               o << *((const Meter*) ms);
+       }
+
+       return o;
 }