fix crash when copy'ing latent plugins
[ardour.git] / libs / ardour / tempo.cc
index 8af5ab3fbebe98621acafd6c896861de51943a11..11da2fc81d3892768dbc2efbd2c17bc439228465 100644 (file)
@@ -33,7 +33,7 @@
 #include "ardour/lmath.h"
 #include "ardour/tempo.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 #include <locale.h>
 
 using namespace std;
@@ -73,13 +73,13 @@ Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
 const string TempoSection::xml_state_node_name = "Tempo";
 
 TempoSection::TempoSection (const XMLNode& node)
-       : MetricSection (0.0)
+       : MetricSection (0.0, 0, MusicTime, true)
        , Tempo (TempoMap::default_tempo())
        , _c_func (0.0)
        , _active (true)
        , _locked_to_meter (false)
 {
-       const XMLProperty *prop;
+       XMLProperty const * prop;
        LocaleGuard lg;
        BBT_Time bbt;
        double pulse;
@@ -179,13 +179,13 @@ TempoSection::get_state() const
        char buf[256];
        LocaleGuard lg;
 
-       snprintf (buf, sizeof (buf), "%f", pulse());
+       snprintf (buf, sizeof (buf), "%lf", pulse());
        root->add_property ("pulse", buf);
        snprintf (buf, sizeof (buf), "%li", frame());
        root->add_property ("frame", buf);
-       snprintf (buf, sizeof (buf), "%f", _beats_per_minute);
+       snprintf (buf, sizeof (buf), "%lf", _beats_per_minute);
        root->add_property ("beats-per-minute", buf);
-       snprintf (buf, sizeof (buf), "%f", _note_type);
+       snprintf (buf, sizeof (buf), "%lf", _note_type);
        root->add_property ("note-type", buf);
        snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
        root->add_property ("movable", buf);
@@ -219,14 +219,14 @@ TempoSection::tempo_at_frame (const framepos_t& f, const framecnt_t& frame_rate)
 
 /** returns the zero-based frame (relative to session)
    where the tempo in whole pulses per minute occurs in this section.
-   beat b is only used for constant tempos.
+   pulse p is only used for constant tempos.
    note that the tempo map may have multiple such values.
 */
 framepos_t
-TempoSection::frame_at_tempo (const double& ppm, const double& b, const framecnt_t& frame_rate) const
+TempoSection::frame_at_tempo (const double& ppm, const double& p, const framecnt_t& frame_rate) const
 {
        if (_type == Constant || _c_func == 0.0) {
-               return ((b - pulse())  * frames_per_pulse (frame_rate))  + frame();
+               return ((p - pulse()) * frames_per_pulse (frame_rate))  + frame();
        }
 
        return minute_to_frame (time_at_pulse_tempo (ppm), frame_rate) + frame();
@@ -453,12 +453,10 @@ TempoSection::time_at_pulse (const double& pulse) const
 const string MeterSection::xml_state_node_name = "Meter";
 
 MeterSection::MeterSection (const XMLNode& node)
-       : MetricSection (0.0), Meter (TempoMap::default_meter())
+       : MetricSection (0.0, 0, MusicTime, false), Meter (TempoMap::default_meter())
 {
        XMLProperty const * prop;
-       BBT_Time start;
        LocaleGuard lg;
-       const XMLProperty *prop;
        BBT_Time bbt;
        double pulse = 0.0;
        double beat = 0.0;
@@ -571,12 +569,12 @@ MeterSection::get_state() const
        root->add_property ("bbt", buf);
        snprintf (buf, sizeof (buf), "%lf", beat());
        root->add_property ("beat", buf);
-       snprintf (buf, sizeof (buf), "%f", _note_type);
+       snprintf (buf, sizeof (buf), "%lf", _note_type);
        root->add_property ("note-type", buf);
        snprintf (buf, sizeof (buf), "%li", frame());
        root->add_property ("frame", buf);
        root->add_property ("lock-style", enum_2_string (position_lock_style()));
-       snprintf (buf, sizeof (buf), "%f", _divisions_per_bar);
+       snprintf (buf, sizeof (buf), "%lf", _divisions_per_bar);
        root->add_property ("divisions-per-bar", buf);
        snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
        root->add_property ("movable", buf);
@@ -588,39 +586,90 @@ MeterSection::get_state() const
 /*
   Tempo Map Overview
 
-  Tempo can be thought of as a source of the musical pulse.
-  Meters divide that pulse into measures and beats.
-  Tempo pulses can be divided to be in sympathy with the meter, but this does not affect the beat
-  at any particular time.
-  Note that Tempo::beats_per_minute() has nothing to do with musical beats.
-  It should rather be thought of as tempo note divisions per minute.
+  The Shaggs - Things I Wonder
+  https://www.youtube.com/watch?v=9wQK6zMJOoQ
 
-  TempoSections, which are nice to think of in whole pulses per minute,
-  and MeterSecions which divide tempo pulses into measures (via divisions_per_bar)
-  and beats (via note_divisor) are used to form a tempo map.
-  TempoSections and MeterSections may be locked to either audio or music (position lock style).
-  We construct the tempo map by first using the frame or pulse position (depending on position lock style) of each tempo.
-  We then use this pulse/frame layout to find the beat & pulse or frame position of each meter (again depending on lock style).
+  Tempo is the rate of the musical pulse.
+  Meters divide the pulses into measures and beats.
 
-  Having done this, we can now find any one of tempo, beat, frame or pulse if a beat, frame, pulse or tempo is known.
+  TempoSections - provide pulses in the form of beats_per_minute() and note_type() where note_type is the division of a whole pulse,
+  and beats_per_minute is the number of note_types in one minute (unlike what its name suggests).
+  Note that Tempo::beats_per_minute() has nothing to do with musical beats. It has been left that way because
+  a shorter one hasn't been found yet (pulse_divisions_per_minute()?).
+
+  MeterSecions - divide pulses into measures (via divisions_per_bar) and beats (via note_divisor).
+
+  Both tempos and meters have a pulse position and a frame position.
+  Meters also have a beat position, which is always 0.0 for the first meter.
+  TempoSections and MeterSections may be locked to either audio or music (position lock style).
+  The lock style determines the 'true' position of the section wich is used to calculate the other postion parameters of the section.
 
   The first tempo and first meter are special. they must move together, and must be locked to audio.
   Audio locked tempos which lie before the first meter are made inactive.
   They will be re-activated if the first meter is again placed before them.
 
-  Both tempos and meters have a pulse position and a frame position.
-  Meters also have a beat position, which is always 0.0 for the first meter.
+  With tepo sections potentially being ramped, meters provide a way of mapping beats to whole pulses without
+  referring to the tempo function(s) involved as the distance in whole pulses between a meter and a subsequent beat is
+  sb->beat() - meter->beat() / meter->note_divisor().
+  Because every meter falls on a known pulse, (derived from its bar), the rest is easy as the duration in pulses between
+  two meters is of course
+  (meater_b->bar - meter_a->bar) * meter_a->divisions_per_bar / meter_a->note_divisor.
+
+  Below, beat calculations are based on meter sections and all pulse and tempo calculations are based on tempo sections.
+  Beat to frame conversion of course requires the use of meter and tempo.
 
-  A tempo locked to music is locked to musical pulses.
-  A meter locked to music is locked to beats.
+  Remembering that ramped tempo sections interact, it is important to avoid referring to any other tempos when moving tempo sections,
+  Here, beats (meters) are used to determine the new pulse (see predict_tempo_position())
 
-  Recomputing the tempo map is the process where the 'missing' position
+  Recomputing the map is the process where the 'missing' position
   (tempo pulse or meter pulse & beat in the case of AudioTime, frame for MusicTime) is calculated.
+  We construct the tempo map by first using the frame or pulse position (depending on position lock style) of each tempo.
+  We then use this tempo map (really just the tempos) to find the pulse or frame position of each meter (again depending on lock style).
+
+  Having done this, we can now find any musical duration by selecting the tempo and meter covering the position (or tempo) in question
+  and querying its appropriate meter/tempo.
 
   It is important to keep the _metrics in an order that makes sense.
   Because ramped MusicTime and AudioTime tempos can interact with each other,
   reordering is frequent. Care must be taken to keep _metrics in a solved state.
   Solved means ordered by frame or pulse with frame-accurate precision (see check_solved()).
+
+  Music and Audio
+
+  Music and audio-locked objects may seem interchangeable on the surface, but when translating
+  between audio samples and beats, keep in mind that a sample is only a quantised approximation
+  of the actual time (in minutes) of a beat.
+  Thus if a gui user points to the frame occupying the start of a music-locked object on 1|3|0, it does not
+  mean that this frame is the actual location in time of 1|3|0.
+
+  You cannot use a frame measurement to determine beat distance except under special circumstances
+  (e.g. where the user has requested that a beat lie on a SMPTE frame or if the tempo is known to be constant over the duration).
+
+  This means is that a user operating on a musical grid must supply the desired beat position and/or current beat quantization in order for the
+  sample space the user is operating at to be translated correctly to the object.
+
+  The current approach is to interpret the supplied frame using the grid division the user has currently selected.
+  If the user has no musical grid set, they are actually operating in sample space (even SMPTE frames are rounded to audio frame), so
+  the supplied audio frame is interpreted as the desired musical location (beat_at_frame()).
+
+  tldr: Beat, being a function of time, has nothing to do with sample rate, but time quantization can get in the way of precision.
+
+  When frame_at_beat() is called, the position calculation is performed in pulses and minutes.
+  The result is rounded to audio frames.
+  When beat_at_frame() is called, the frame is converted to minutes, with no rounding performed on the result.
+
+  So :
+  frame_at_beat (beat_at_frame (frame)) == frame
+  but :
+  beat_at_frame (frame_at_beat (beat)) != beat due to the time quantization of frame_at_beat().
+
+  Doing the second one will result in a beat distance error of up to 0.5 audio samples.
+  So instead work in pulses and/or beats and only use beat position to caclulate frame position (e.g. after tempo change).
+  For audio-locked objects, use frame position to calculate beat position.
+
+  The above pointless example would then do:
+  beat_at_pulse (pulse_at_beat (beat)) to avoid rounding.
+
 */
 struct MetricSectionSorter {
     bool operator() (const MetricSection* a, const MetricSection* b) {
@@ -639,8 +688,8 @@ TempoMap::TempoMap (framecnt_t fr)
        _frame_rate = fr;
        BBT_Time start (1, 1, 0);
 
-       TempoSection *t = new TempoSection ((framepos_t) 0, _default_tempo.beats_per_minute(), _default_tempo.note_type(), TempoSection::Constant);
-       MeterSection *m = new MeterSection ((framepos_t) 0, 0.0, start, _default_meter.divisions_per_bar(), _default_meter.note_divisor());
+       TempoSection *t = new TempoSection (0.0, 0, _default_tempo.beats_per_minute(), _default_tempo.note_type(), TempoSection::Ramp, AudioTime);
+       MeterSection *m = new MeterSection (0.0, 0, 0.0, start, _default_meter.divisions_per_bar(), _default_meter.note_divisor(), AudioTime);
 
        t->set_movable (false);
        m->set_movable (false);
@@ -723,13 +772,13 @@ TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
 bool
 TempoMap::remove_meter_locked (const MeterSection& meter)
 {
-       Metrics::iterator i;
 
-       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
-               TempoSection* t = 0;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if (meter.frame() == (*i)->frame()) {
-                               if (t->locked_to_meter()) {
+       if (meter.position_lock_style() == AudioTime) {
+               /* remove meter-locked tempo */
+               for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+                       TempoSection* t = 0;
+                       if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                               if (t->locked_to_meter() && meter.frame() == (*i)->frame()) {
                                        delete (*i);
                                        _metrics.erase (i);
                                        break;
@@ -738,7 +787,7 @@ TempoMap::remove_meter_locked (const MeterSection& meter)
                }
        }
 
-       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
+       for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                if (dynamic_cast<MeterSection*> (*i) != 0) {
                        if (meter.frame() == (*i)->frame()) {
                                if ((*i)->movable()) {
@@ -765,10 +814,10 @@ TempoMap::do_insert (MetricSection* section)
 
                if ((m->bbt().beats != 1) || (m->bbt().ticks != 0)) {
 
-                       pair<double, BBT_Time> corrected = make_pair (m->pulse(), m->bbt());
+                       pair<double, BBT_Time> corrected = make_pair (m->beat(), m->bbt());
                        corrected.second.beats = 1;
                        corrected.second.ticks = 0;
-                       corrected.first = bbt_to_beats_locked (_metrics, corrected.second);
+                       corrected.first = beat_at_bbt_locked (_metrics, corrected.second);
                        warning << string_compose (_("Meter changes can only be positioned on the first beat of a bar. Moving from %1 to %2"),
                                                   m->bbt(), corrected.second) << endmsg;
                        //m->set_pulse (corrected);
@@ -879,44 +928,40 @@ TempoMap::do_insert (MetricSection* section)
                }
 
                _metrics.insert (i, section);
-               //dump (_metrics, std::cerr);
+               //dump (_metrics, std::cout);
        }
 }
 
-void
-TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const double& pulse, TempoSection::Type type)
+TempoSection*
+TempoMap::add_tempo (const Tempo& tempo, const double& pulse, const framepos_t& frame, ARDOUR::TempoSection::Type type, PositionLockStyle pls)
 {
+       TempoSection* ts = 0;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection& first (first_tempo());
-               if (ts.pulse() != first.pulse()) {
-                       remove_tempo_locked (ts);
-                       add_tempo_locked (tempo, pulse, true, type);
-               } else {
-                       first.set_type (type);
-                       {
-                               /* cannot move the first tempo section */
-                               *static_cast<Tempo*>(&first) = tempo;
-                               recompute_map (_metrics);
-                       }
-               }
+               ts = add_tempo_locked (tempo, pulse, frame, type, pls, true);
        }
 
+
        PropertyChanged (PropertyChange ());
+
+       return ts;
 }
 
 void
-TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const framepos_t& frame, TempoSection::Type type)
+TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const double& pulse, const framepos_t& frame, TempoSection::Type type, PositionLockStyle pls)
 {
+       const bool locked_to_meter = ts.locked_to_meter();
+
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
                TempoSection& first (first_tempo());
                if (ts.frame() != first.frame()) {
                        remove_tempo_locked (ts);
-                       add_tempo_locked (tempo, frame, true, type);
+                       add_tempo_locked (tempo, pulse, frame, type, pls, true, locked_to_meter);
                } else {
                        first.set_type (type);
                        first.set_pulse (0.0);
+                       first.set_frame (frame);
                        first.set_position_lock_style (AudioTime);
                        {
                                /* cannot move the first tempo section */
@@ -930,97 +975,62 @@ TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const frame
 }
 
 TempoSection*
-TempoMap::add_tempo (const Tempo& tempo, const double& pulse, ARDOUR::TempoSection::Type type)
-{
-       TempoSection* ts = 0;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               ts = add_tempo_locked (tempo, pulse, true, type);
-       }
-
-       PropertyChanged (PropertyChange ());
-
-       return ts;
-}
-
-TempoSection*
-TempoMap::add_tempo (const Tempo& tempo, const framepos_t& frame, ARDOUR::TempoSection::Type type)
-{
-       TempoSection* ts = 0;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               ts = add_tempo_locked (tempo, frame, true, type);
-       }
-
-
-       PropertyChanged (PropertyChange ());
-
-       return ts;
-}
-
-TempoSection*
-TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, bool recompute, ARDOUR::TempoSection::Type type)
+TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, framepos_t frame
+                           , TempoSection::Type type, PositionLockStyle pls, bool recompute, bool locked_to_meter)
 {
-       TempoSection* t = new TempoSection (pulse, tempo.beats_per_minute(), tempo.note_type(), type);
+       TempoSection* t = new TempoSection (pulse, frame, tempo.beats_per_minute(), tempo.note_type(), type, pls);
+       t->set_locked_to_meter (locked_to_meter);
+       bool solved = false;
 
        do_insert (t);
 
        if (recompute) {
-               solve_map (_metrics, t, t->pulse());
+               if (pls == AudioTime) {
+                       solved = solve_map_frame (_metrics, t, t->frame());
+               } else {
+                       solved = solve_map_pulse (_metrics, t, t->pulse());
+               }
                recompute_meters (_metrics);
        }
 
-       return t;
-}
-
-TempoSection*
-TempoMap::add_tempo_locked (const Tempo& tempo, framepos_t frame, bool recompute, ARDOUR::TempoSection::Type type)
-{
-       TempoSection* t = new TempoSection (frame, tempo.beats_per_minute(), tempo.note_type(), type);
-
-       do_insert (t);
-
-       if (recompute) {
-               solve_map (_metrics, t, t->frame());
-               recompute_meters (_metrics);
+       if (!solved && recompute) {
+               warning << "Adding tempo may have left the tempo map unsolved." << endmsg;
+               recompute_map (_metrics);
        }
 
        return t;
 }
 
-void
-TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
+MeterSection*
+TempoMap::add_meter (const Meter& meter, const double& beat, const Timecode::BBT_Time& where, const framepos_t& frame, PositionLockStyle pls)
 {
+       MeterSection* m = 0;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
+               m = add_meter_locked (meter, beat, where, frame, pls, true);
+       }
 
-               if (ms.movable()) {
-                       remove_meter_locked (ms);
-                       add_meter_locked (meter, bbt_to_beats_locked (_metrics, where), where, true);
-               } else {
-                       MeterSection& first (first_meter());
-                       /* cannot move the first meter section */
-                       *static_cast<Meter*>(&first) = meter;
-                       first.set_position_lock_style (AudioTime);
-               }
-               recompute_map (_metrics);
+
+#ifndef NDEBUG
+       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
+               dump (_metrics, std::cerr);
        }
+#endif
 
        PropertyChanged (PropertyChange ());
+       return m;
 }
 
 void
-TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const framepos_t& frame)
+TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where, const framepos_t& frame, PositionLockStyle pls)
 {
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-
-               const double beat = ms.beat();
-               const BBT_Time bbt = ms.bbt();
+               const double beat = beat_at_bbt_locked (_metrics, where);
 
                if (ms.movable()) {
                        remove_meter_locked (ms);
-                       add_meter_locked (meter, frame, beat, bbt, true);
+                       add_meter_locked (meter, beat, where, frame, pls, true);
                } else {
                        MeterSection& first (first_meter());
                        TempoSection& first_t (first_tempo());
@@ -1034,98 +1044,55 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const frame
                        first_t.set_frame (first.frame());
                        first_t.set_pulse (0.0);
                        first_t.set_position_lock_style (AudioTime);
+                       recompute_map (_metrics);
                }
-               recompute_map (_metrics);
-       }
-       PropertyChanged (PropertyChange ());
-}
-
-
-MeterSection*
-TempoMap::add_meter (const Meter& meter, const double& beat, const BBT_Time& where)
-{
-       MeterSection* m = 0;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               m = add_meter_locked (meter, beat, where, true);
-       }
-
-
-#ifndef NDEBUG
-       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
-               dump (_metrics, std::cerr);
        }
-#endif
 
        PropertyChanged (PropertyChange ());
-
-       return m;
 }
 
 MeterSection*
-TempoMap::add_meter (const Meter& meter, const framepos_t& frame, const double& beat, const Timecode::BBT_Time& where)
+TempoMap::add_meter_locked (const Meter& meter, double beat, const BBT_Time& where, framepos_t frame, PositionLockStyle pls, bool recompute)
 {
-       MeterSection* m = 0;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               m = add_meter_locked (meter, frame, beat, where, true);
-       }
-
-
-#ifndef NDEBUG
-       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
-               dump (_metrics, std::cerr);
-       }
-#endif
+       const MeterSection& prev_m = meter_section_at_frame_locked  (_metrics, frame - 1);
+       const double pulse = ((where.bars - prev_m.bbt().bars) * (prev_m.divisions_per_bar() / prev_m.note_divisor())) + prev_m.pulse();
+       TempoSection* mlt = 0;
 
-       PropertyChanged (PropertyChange ());
+       if (pls == AudioTime) {
+               /* add meter-locked tempo */
+               mlt = add_tempo_locked (tempo_at_frame_locked (_metrics, frame), pulse,  frame, TempoSection::Ramp, AudioTime, true, true);
 
-       return m;
-}
+               if (!mlt) {
+                       return 0;
+               }
 
-MeterSection*
-TempoMap::add_meter_locked (const Meter& meter, double beat, BBT_Time where, bool recompute)
-{
-       /* 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.
+       }
 
-       */
+       MeterSection* new_meter = new MeterSection (pulse, frame, beat, where, meter.divisions_per_bar(), meter.note_divisor(), pls);
+       bool solved = false;
 
-       if (where.beats != 1) {
-               where.beats = 1;
-               where.bars++;
-       }
-       /* new meters *always* start on a beat. */
-       where.ticks = 0;
-       const double pulse = pulse_at_beat_locked (_metrics, beat);
-       MeterSection* new_meter = new MeterSection (pulse, beat, where, meter.divisions_per_bar(), meter.note_divisor());
        do_insert (new_meter);
 
        if (recompute) {
-               solve_map (_metrics, new_meter, where);
-       }
-
-       return new_meter;
-}
-
-MeterSection*
-TempoMap::add_meter_locked (const Meter& meter, framepos_t frame, double beat, Timecode::BBT_Time where, bool recompute)
-{
-       MeterSection* new_meter = new MeterSection (frame, beat, where, meter.divisions_per_bar(), meter.note_divisor());
-       TempoSection* t = 0;
-       double pulse = pulse_at_frame_locked (_metrics, frame);
-       new_meter->set_pulse (pulse);
-
-       do_insert (new_meter);
 
-       /* add meter-locked tempo */
-       t = add_tempo_locked (tempo_at_locked (_metrics, frame), frame, true, TempoSection::Ramp);
-       t->set_locked_to_meter (true);
+               if (pls == AudioTime) {
+                       solved = solve_map_frame (_metrics, new_meter, frame);
+               } else {
+                       solved = solve_map_bbt (_metrics, new_meter, where);
+                       /* required due to resetting the pulse of meter-locked tempi above.
+                          Arguably  solve_map_bbt() should use solve_map_pulse (_metrics, TempoSection) instead,
+                          but afaict this cannot cause the map to be left unsolved (these tempi are all audio locked).
+                       */
+                       recompute_map (_metrics);
+               }
+       }
 
-       if (recompute) {
-               solve_map (_metrics, new_meter, frame);
+       if (!solved && recompute) {
+               /* if this has failed to solve, there is little we can do other than to ensure that
+                  the new map is recalculated.
+               */
+               warning << "Adding meter may have left the tempo map unsolved." << endmsg;
+               recompute_map (_metrics);
        }
 
        return new_meter;
@@ -1283,14 +1250,15 @@ TempoMap::first_tempo ()
        return *t;
 }
 void
-TempoMap::recompute_tempos (Metrics& metrics)
+TempoMap::recompute_tempi (Metrics& metrics)
 {
        TempoSection* prev_t = 0;
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                TempoSection* t;
 
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
                        if (!t->active()) {
                                continue;
                        }
@@ -1322,7 +1290,6 @@ TempoMap::recompute_tempos (Metrics& metrics)
 
 /* tempos must be positioned correctly.
    the current approach is to use a meter's bbt time as its base position unit.
-   this means that a meter's beat may change, but its bbt may not.
    an audio-locked meter requires a recomputation of pulse and beat (but not bbt),
    while a music-locked meter requires recomputations of frame pulse and beat (but not bbt)
 */
@@ -1333,14 +1300,16 @@ TempoMap::recompute_meters (Metrics& metrics)
        MeterSection* prev_m = 0;
 
        for (Metrics::const_iterator mi = metrics.begin(); mi != metrics.end(); ++mi) {
-               if ((meter = dynamic_cast<MeterSection*> (*mi)) != 0) {
+               if (!(*mi)->is_tempo()) {
+                       meter = static_cast<MeterSection*> (*mi);
                        if (meter->position_lock_style() == AudioTime) {
                                double pulse = 0.0;
                                pair<double, BBT_Time> b_bbt;
                                TempoSection* meter_locked_tempo = 0;
                                for (Metrics::const_iterator ii = metrics.begin(); ii != metrics.end(); ++ii) {
                                        TempoSection* t;
-                                       if ((t = dynamic_cast<TempoSection*> (*ii)) != 0) {
+                                       if ((*ii)->is_tempo()) {
+                                               t = static_cast<TempoSection*> (*ii);
                                                if ((t->locked_to_meter() || !t->movable()) && t->frame() == meter->frame()) {
                                                        meter_locked_tempo = t;
                                                        break;
@@ -1365,7 +1334,6 @@ TempoMap::recompute_meters (Metrics& metrics)
                                }
                                if (meter_locked_tempo) {
                                        meter_locked_tempo->set_pulse (pulse);
-                                       recompute_tempos (metrics);
                                }
                                meter->set_beat (b_bbt);
                                meter->set_pulse (pulse);
@@ -1373,24 +1341,24 @@ TempoMap::recompute_meters (Metrics& metrics)
                        } else {
                                /* MusicTime */
                                double pulse = 0.0;
-                               pair<double, BBT_Time> new_beat;
+                               pair<double, BBT_Time> b_bbt;
                                if (prev_m) {
                                        const double beats = (meter->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar();
                                        if (beats + prev_m->beat() != meter->beat()) {
                                                /* reordering caused a bbt change */
-                                               new_beat = make_pair (beats + prev_m->beat()
+                                               b_bbt = make_pair (beats + prev_m->beat()
                                                                   , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0));
                                        } else {
-                                               new_beat = make_pair (beats + prev_m->beat(), meter->bbt());
+                                               b_bbt = make_pair (beats + prev_m->beat(), meter->bbt());
                                        }
                                        pulse = (beats / prev_m->note_divisor()) + prev_m->pulse();
                                } else {
                                        /* shouldn't happen - the first is audio-locked */
                                        pulse = pulse_at_beat_locked (metrics, meter->beat());
-                                       new_beat = make_pair (meter->beat(), meter->bbt());
+                                       b_bbt = make_pair (meter->beat(), meter->bbt());
                                }
 
-                               meter->set_beat (new_beat);
+                               meter->set_beat (b_bbt);
                                meter->set_pulse (pulse);
                                meter->set_frame (frame_at_pulse_locked (metrics, pulse));
                        }
@@ -1398,7 +1366,6 @@ TempoMap::recompute_meters (Metrics& metrics)
                        prev_m = meter;
                }
        }
-       //dump (_metrics, std::cerr;
 }
 
 void
@@ -1423,7 +1390,7 @@ TempoMap::recompute_map (Metrics& metrics, framepos_t end)
                return;
        }
 
-       recompute_tempos (metrics);
+       recompute_tempi (metrics);
        recompute_meters (metrics);
 }
 
@@ -1472,7 +1439,8 @@ TempoMap::metric_at (BBT_Time bbt) const
 
        for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                MeterSection* mw;
-               if ((mw = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       mw = static_cast<MeterSection*> (*i);
                        BBT_Time section_start (mw->bbt());
 
                        if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) {
@@ -1487,198 +1455,310 @@ TempoMap::metric_at (BBT_Time bbt) const
 }
 
 double
-TempoMap::pulse_at_beat_locked (const Metrics& metrics, const double& beat) const
+TempoMap::beat_at_frame (const framecnt_t& frame) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return beat_at_frame_locked (_metrics, frame);
+}
+
+/* meter / tempo section based */
+double
+TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
 {
+       const TempoSection& ts = tempo_section_at_frame_locked (metrics, frame);
        MeterSection* prev_m = 0;
+       MeterSection* next_m = 0;
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       if (prev_m && m->beat() > beat) {
+               if (!(*i)->is_tempo()) {
+                       if (prev_m && (*i)->frame() > frame) {
+                               next_m = static_cast<MeterSection*> (*i);
                                break;
                        }
-                       prev_m = m;
+                       prev_m = static_cast<MeterSection*> (*i);
                }
+       }
+       if (frame < prev_m->frame()) {
+               return 0.0;
+       }
+       const double beat = prev_m->beat() + (ts.pulse_at_frame (frame, _frame_rate) - prev_m->pulse()) * prev_m->note_divisor();
 
+       /* audio locked meters fake their beat */
+       if (next_m && next_m->beat() < beat) {
+               return next_m->beat();
        }
-       double const ret = prev_m->pulse() + ((beat - prev_m->beat()) / prev_m->note_divisor());
-       return ret;
+
+       return beat;
 }
 
-double
-TempoMap::pulse_at_beat (const double& beat) const
+framepos_t
+TempoMap::frame_at_beat (const double& beat) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return pulse_at_beat_locked (_metrics, beat);
+       return frame_at_beat_locked (_metrics, beat);
 }
 
-double
-TempoMap::beat_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+/* meter & tempo section based */
+framepos_t
+TempoMap::frame_at_beat_locked (const Metrics& metrics, const double& beat) const
 {
        MeterSection* prev_m = 0;
+       TempoSection* prev_t = 0;
+
+       MeterSection* m;
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       if (prev_m && m->pulse() > pulse) {
-                               if (((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > m->beat()) {
-                                       break;
-                               }
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
+                       if (prev_m && m->beat() > beat) {
+                               break;
                        }
                        prev_m = m;
                }
        }
 
-       double const ret = ((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat();
-       return ret;
+       TempoSection* t;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
+                       if (prev_t && ((t->pulse() - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > beat) {
+                               break;
+                       }
+                       prev_t = t;
+               }
+
+       }
+
+       return prev_t->frame_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse(), _frame_rate);
 }
 
-double
-TempoMap::beat_at_pulse (const double& pulse) const
+Tempo
+TempoMap::tempo_at_frame (const framepos_t& frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return beat_at_pulse_locked (_metrics, pulse);
+       return tempo_at_frame_locked (_metrics, frame);
 }
 
-/* tempo section based */
-double
-TempoMap::pulse_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
+Tempo
+TempoMap::tempo_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const
 {
-       /* HOLD (at least) THE READER LOCK */
        TempoSection* prev_t = 0;
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+       TempoSection* t;
+
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
                        if (!t->active()) {
                                continue;
                        }
-                       if (prev_t && t->frame() > frame) {
-                               /*the previous ts is the one containing the frame */
-                               const double ret = prev_t->pulse_at_frame (frame, _frame_rate);
-                               return ret;
+                       if ((prev_t) && t->frame() > frame) {
+                               /* t is the section past frame */
+                               const double ret_bpm = prev_t->tempo_at_frame (frame, _frame_rate) * prev_t->note_type();
+                               const Tempo ret_tempo (ret_bpm, prev_t->note_type());
+                               return ret_tempo;
                        }
                        prev_t = t;
                }
        }
 
-       /* treated as constant for this ts */
-       const double pulses_in_section = (frame - prev_t->frame()) / prev_t->frames_per_pulse (_frame_rate);
+       const double ret = prev_t->beats_per_minute();
+       const Tempo ret_tempo (ret, prev_t->note_type ());
 
-       return pulses_in_section + prev_t->pulse();
+       return ret_tempo;
 }
 
-double
-TempoMap::pulse_at_frame (const framecnt_t& frame) const
+/** returns the frame at which the supplied tempo occurs, or
+ *  the frame of the last tempo section (search exhausted)
+ *  only the position of the first occurence will be returned
+ *  (extend me)
+*/
+framepos_t
+TempoMap::frame_at_tempo (const Tempo& tempo) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return pulse_at_frame_locked (_metrics, frame);
+       return frame_at_tempo_locked (_metrics, tempo);
 }
 
-/* tempo section based */
-framecnt_t
-TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+
+framepos_t
+TempoMap::frame_at_tempo_locked (const Metrics& metrics, const Tempo& tempo) const
 {
-       /* HOLD THE READER LOCK */
+       TempoSection* prev_t = 0;
+       const double tempo_ppm = tempo.beats_per_minute() / tempo.note_type();
 
-       const TempoSection* prev_t = 0;
+       Metrics::const_iterator i;
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
                TempoSection* t;
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
 
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
                        if (!t->active()) {
                                continue;
                        }
-                       if (prev_t && t->pulse() > pulse) {
-                               return prev_t->frame_at_pulse (pulse, _frame_rate);
+
+                       const double t_ppm = t->beats_per_minute() / t->note_type();
+
+                       if (t_ppm == tempo_ppm) {
+                               return t->frame();
                        }
 
+                       if (prev_t) {
+                               const double prev_t_ppm = prev_t->beats_per_minute() / prev_t->note_type();
+
+                               if ((t_ppm > tempo_ppm && prev_t_ppm < tempo_ppm) || (t_ppm < tempo_ppm && prev_t_ppm > tempo_ppm)) {
+                                       return prev_t->frame_at_tempo (tempo_ppm, prev_t->pulse(), _frame_rate);
+                               }
+                       }
                        prev_t = t;
                }
        }
-       /* must be treated as constant, irrespective of _type */
-       double const pulses_in_section = pulse - prev_t->pulse();
-       double const dtime = pulses_in_section * prev_t->frames_per_pulse (_frame_rate);
 
-       framecnt_t const ret = (framecnt_t) floor (dtime) + prev_t->frame();
+       return prev_t->frame();
+}
 
-       return ret;
+/** more precise than doing tempo_at_frame (frame_at_beat (b)),
+ *  as there is no intermediate frame rounding.
+ */
+Tempo
+TempoMap::tempo_at_beat (const double& beat) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       const MeterSection* prev_m = &meter_section_at_beat_locked (_metrics, beat);
+       const TempoSection* prev_t = &tempo_section_at_beat_locked (_metrics, beat);
+       const double note_type = prev_t->note_type();
+
+       return Tempo (prev_t->tempo_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse()) * note_type, note_type);
 }
 
-framecnt_t
-TempoMap::frame_at_pulse (const double& pulse) const
+double
+TempoMap::pulse_at_beat (const double& beat) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return frame_at_pulse_locked (_metrics, pulse);
+       return pulse_at_beat_locked (_metrics, beat);
 }
 
-/* meter section based */
 double
-TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
+TempoMap::pulse_at_beat_locked (const Metrics& metrics, const double& beat) const
+{
+       const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat);
+
+       return prev_m->pulse() + ((beat - prev_m->beat()) / prev_m->note_divisor());
+}
+
+double
+TempoMap::beat_at_pulse (const double& pulse) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return beat_at_pulse_locked (_metrics, pulse);
+}
+
+double
+TempoMap::beat_at_pulse_locked (const Metrics& metrics, const double& pulse) const
 {
-       const TempoSection& ts = tempo_section_at_locked (metrics, frame);
        MeterSection* prev_m = 0;
-       MeterSection* next_m = 0;
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       if (prev_m && m->frame() > frame) {
-                               next_m = m;
-                               break;
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
+                       if (prev_m && m->pulse() > pulse) {
+                               if (((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > m->beat()) {
+                                       break;
+                               }
                        }
                        prev_m = m;
                }
        }
-       if (frame < prev_m->frame()) {
-               return 0.0;
-       }
-       const double beat = prev_m->beat() + (ts.pulse_at_frame (frame, _frame_rate) - prev_m->pulse()) * prev_m->note_divisor();
 
-       if (next_m && next_m->beat() < beat) {
-               return next_m->beat();
-       }
+       double const ret = ((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat();
+       return ret;
+}
 
-       return beat;
+double
+TempoMap::pulse_at_frame (const framepos_t& frame) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return pulse_at_frame_locked (_metrics, frame);
 }
 
+/* tempo section based */
 double
-TempoMap::beat_at_frame (const framecnt_t& frame) const
+TempoMap::pulse_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const
+{
+       /* HOLD (at least) THE READER LOCK */
+       TempoSection* prev_t = 0;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               TempoSection* t;
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
+                       if (!t->active()) {
+                               continue;
+                       }
+                       if (prev_t && t->frame() > frame) {
+                               /*the previous ts is the one containing the frame */
+                               const double ret = prev_t->pulse_at_frame (frame, _frame_rate);
+                               return ret;
+                       }
+                       prev_t = t;
+               }
+       }
+
+       /* treated as constant for this ts */
+       const double pulses_in_section = (frame - prev_t->frame()) / prev_t->frames_per_pulse (_frame_rate);
+
+       return pulses_in_section + prev_t->pulse();
+}
+
+framepos_t
+TempoMap::frame_at_pulse (const double& pulse) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return beat_at_frame_locked (_metrics, frame);
+       return frame_at_pulse_locked (_metrics, pulse);
 }
 
-/* meter section based */
-framecnt_t
-TempoMap::frame_at_beat_locked (const Metrics& metrics, const double& beat) const
+/* tempo section based */
+framepos_t
+TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) const
 {
-       const TempoSection& prev_t = tempo_section_at_beat_locked (metrics, beat);
-       MeterSection* prev_m = 0;
+       /* HOLD THE READER LOCK */
+
+       const TempoSection* prev_t = 0;
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       if (prev_m && m->beat() > beat) {
-                               break;
+               TempoSection* t;
+
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
+                       if (!t->active()) {
+                               continue;
                        }
-                       prev_m = m;
+                       if (prev_t && t->pulse() > pulse) {
+                               return prev_t->frame_at_pulse (pulse, _frame_rate);
+                       }
+
+                       prev_t = t;
                }
        }
+       /* must be treated as constant, irrespective of _type */
+       double const dtime = (pulse - prev_t->pulse()) * prev_t->frames_per_pulse (_frame_rate);
 
-       return prev_t.frame_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse(), _frame_rate);
+       return (framecnt_t) floor (dtime) + prev_t->frame();
 }
 
-framecnt_t
-TempoMap::frame_at_beat (const double& beat) const
+double
+TempoMap::beat_at_bbt (const Timecode::BBT_Time& bbt)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return frame_at_beat_locked (_metrics, beat);
+       return beat_at_bbt_locked (_metrics, bbt);
 }
 
+
 double
-TempoMap::bbt_to_beats_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
+TempoMap::beat_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
 {
        /* CALLER HOLDS READ LOCK */
 
@@ -1687,9 +1767,11 @@ TempoMap::bbt_to_beats_locked (const Metrics& metrics, const Timecode::BBT_Time&
        /* because audio-locked meters have 'fake' integral beats,
           there is no pulse offset here.
        */
+       MeterSection* m;
+
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
                        if (prev_m) {
                                const double bars_to_m = (m->beat() - prev_m->beat()) / prev_m->divisions_per_bar();
                                if ((bars_to_m + (prev_m->bbt().bars - 1)) > (bbt.bars - 1)) {
@@ -1707,24 +1789,25 @@ TempoMap::bbt_to_beats_locked (const Metrics& metrics, const Timecode::BBT_Time&
        return ret;
 }
 
-double
-TempoMap::bbt_to_beats (const Timecode::BBT_Time& bbt)
+Timecode::BBT_Time
+TempoMap::bbt_at_beat (const double& beats)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return bbt_to_beats_locked (_metrics, bbt);
+       return bbt_at_beat_locked (_metrics, beats);
 }
 
 Timecode::BBT_Time
-TempoMap::beats_to_bbt_locked (const Metrics& metrics, const double& b) const
+TempoMap::bbt_at_beat_locked (const Metrics& metrics, const double& b) const
 {
        /* CALLER HOLDS READ LOCK */
        MeterSection* prev_m = 0;
-       const double beats = (b < 0.0) ? 0.0 : b;
+       const double beats = max (0.0, b);
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* m = 0;
+       MeterSection* m = 0;
 
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
                        if (prev_m) {
                                if (m->beat() > beats) {
                                        /* this is the meter after the one our beat is on*/
@@ -1765,23 +1848,64 @@ TempoMap::beats_to_bbt_locked (const Metrics& metrics, const double& b) const
        return ret;
 }
 
-Timecode::BBT_Time
-TempoMap::beats_to_bbt (const double& beats)
+double
+TempoMap::pulse_at_bbt (const Timecode::BBT_Time& bbt)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return beats_to_bbt_locked (_metrics, beats);
+
+       return pulse_at_bbt_locked (_metrics, bbt);
+}
+
+double
+TempoMap::pulse_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
+{
+       /* CALLER HOLDS READ LOCK */
+
+       MeterSection* prev_m = 0;
+
+       /* because audio-locked meters have 'fake' integral beats,
+          there is no pulse offset here.
+       */
+       MeterSection* m;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
+                       if (prev_m) {
+                               if (m->bbt().bars > bbt.bars) {
+                                       break;
+                               }
+                       }
+                       prev_m = m;
+               }
+       }
+
+       const double remaining_bars = bbt.bars - prev_m->bbt().bars;
+       const double remaining_pulses = remaining_bars * prev_m->divisions_per_bar() / prev_m->note_divisor();
+       const double ret = remaining_pulses + prev_m->pulse() + (((bbt.beats - 1) + (bbt.ticks / BBT_Time::ticks_per_beat)) / prev_m->note_divisor());
+
+       return ret;
 }
 
 Timecode::BBT_Time
-TempoMap::pulse_to_bbt (const double& pulse)
+TempoMap::bbt_at_pulse (const double& pulse)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return bbt_at_pulse_locked (_metrics, pulse);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+{
        MeterSection* prev_m = 0;
 
-       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
-               MeterSection* m = 0;
+       MeterSection* m = 0;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
 
                        if (prev_m) {
                                double const pulses_to_m = m->pulse() - prev_m->pulse();
@@ -1824,35 +1948,107 @@ TempoMap::pulse_to_bbt (const double& pulse)
        return ret;
 }
 
-void
-TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
+BBT_Time
+TempoMap::bbt_at_frame (framepos_t frame)
 {
-
        if (frame < 0) {
+               BBT_Time bbt;
                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;
+               return bbt;
        }
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       const double beat = beat_at_frame_locked (_metrics, frame);
 
-       bbt = beats_to_bbt_locked (_metrics, beat);
+       return bbt_at_frame_locked (_metrics, frame);
 }
 
-/* meter section based */
-framepos_t
-TempoMap::frame_time_locked (const Metrics& metrics, const BBT_Time& bbt) const
+BBT_Time
+TempoMap::bbt_at_frame_rt (framepos_t frame)
 {
-       /* HOLD THE READER LOCK */
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
+
+       if (!lm.locked()) {
+               throw std::logic_error ("TempoMap::bbt_time_rt() could not lock tempo map");
+       }
+
+       return bbt_at_frame_locked (_metrics, frame);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const
+{
+       if (frame < 0) {
+               BBT_Time bbt;
+               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 bbt;
+       }
+
+       const TempoSection& ts = tempo_section_at_frame_locked (metrics, frame);
+       MeterSection* prev_m = 0;
+       MeterSection* next_m = 0;
+
+       MeterSection* m;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
+                       if (prev_m && m->frame() > frame) {
+                               next_m = m;
+                               break;
+                       }
+                       prev_m = m;
+               }
+       }
+
+       double beat = prev_m->beat() + (ts.pulse_at_frame (frame, _frame_rate) - prev_m->pulse()) * prev_m->note_divisor();
+
+       /* handle frame before first meter */
+       if (frame < prev_m->frame()) {
+               beat = 0.0;
+       }
+       /* audio locked meters fake their beat */
+       if (next_m && next_m->beat() < beat) {
+               beat = next_m->beat();
+       }
+
+       beat = max (0.0, beat);
+
+       const double beats_in_ms = beat - prev_m->beat();
+       const uint32_t bars_in_ms = (uint32_t) floor (beats_in_ms / prev_m->divisions_per_bar());
+       const uint32_t total_bars = bars_in_ms + (prev_m->bbt().bars - 1);
+       const double remaining_beats = beats_in_ms - (bars_in_ms * prev_m->divisions_per_bar());
+       const double remaining_ticks = (remaining_beats - floor (remaining_beats)) * BBT_Time::ticks_per_beat;
+
+       BBT_Time ret;
+
+       ret.ticks = (uint32_t) floor (remaining_ticks + 0.5);
+       ret.beats = (uint32_t) floor (remaining_beats);
+       ret.bars = total_bars;
+
+       /* 0 0 0 to 1 1 0 - based mapping*/
+       ++ret.bars;
+       ++ret.beats;
+
+       if (ret.ticks >= BBT_Time::ticks_per_beat) {
+               ++ret.beats;
+               ret.ticks -= BBT_Time::ticks_per_beat;
+       }
+
+       if (ret.beats >= prev_m->divisions_per_bar() + 1) {
+               ++ret.bars;
+               ret.beats = 1;
+       }
 
-       const framepos_t ret = frame_at_beat_locked (metrics, bbt_to_beats_locked (metrics, bbt));
        return ret;
 }
 
 framepos_t
-TempoMap::frame_time (const BBT_Time& bbt)
+TempoMap::frame_at_bbt (const BBT_Time& bbt)
 {
        if (bbt.bars < 1) {
                warning << string_compose (_("tempo map asked for frame time at bar < 1  (%1)\n"), bbt) << endmsg;
@@ -1864,11 +2060,21 @@ TempoMap::frame_time (const BBT_Time& bbt)
        }
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       return frame_time_locked (_metrics, bbt);
+       return frame_at_bbt_locked (_metrics, bbt);
+}
+
+/* meter & tempo section based */
+framepos_t
+TempoMap::frame_at_bbt_locked (const Metrics& metrics, const BBT_Time& bbt) const
+{
+       /* HOLD THE READER LOCK */
+
+       const framepos_t ret = frame_at_beat_locked (metrics, beat_at_bbt_locked (metrics, bbt));
+       return ret;
 }
 
 bool
-TempoMap::check_solved (const Metrics& metrics, bool by_frame) const
+TempoMap::check_solved (const Metrics& metrics) const
 {
        TempoSection* prev_t = 0;
        MeterSection* prev_m = 0;
@@ -1876,36 +2082,50 @@ TempoMap::check_solved (const Metrics& metrics, bool by_frame) const
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                TempoSection* t;
                MeterSection* m;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
                        if (!t->active()) {
                                continue;
                        }
                        if (prev_t) {
-                               if ((by_frame && t->frame() < prev_t->frame()) || (!by_frame && t->pulse() < prev_t->pulse())) {
-                                       return false;
-                               }
-
-                               if (t->frame() == prev_t->frame()) {
+                               /* check ordering */
+                               if ((t->frame() <= prev_t->frame()) || (t->pulse() <= prev_t->pulse())) {
                                        return false;
                                }
 
-                               /* precision check ensures pulses and frames align.*/
-                               if (t->frame() != prev_t->frame_at_pulse (t->pulse(), _frame_rate)) {
+                               /* precision check ensures tempo and frames align.*/
+                               if (t->frame() != prev_t->frame_at_tempo (t->pulses_per_minute(), t->pulse(), _frame_rate)) {
                                        if (!t->locked_to_meter()) {
                                                return false;
                                        }
                                }
+
+                               /* gradient limit - who knows what it should be?
+                                  things are also ok (if a little chaotic) without this
+                               */
+                               if (fabs (prev_t->c_func()) > 1000.0) {
+                                       //std::cout << "c : " << prev_t->c_func() << std::endl;
+                                       return false;
+                               }
                        }
                        prev_t = t;
                }
 
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
                        if (prev_m && m->position_lock_style() == AudioTime) {
-                               TempoSection* t = const_cast<TempoSection*>(&tempo_section_at_locked (metrics, m->frame() - 1));
-                               const double nascent_m_pulse = ((m->beat() - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse();
-                               const framepos_t nascent_m_frame = t->frame_at_pulse (nascent_m_pulse, _frame_rate);
-
-                               if (t && (nascent_m_frame > m->frame() || nascent_m_frame < 0)) {
+                               const TempoSection* t = &tempo_section_at_frame_locked (metrics, m->frame() - 1);
+                               const framepos_t nascent_m_frame = t->frame_at_pulse (m->pulse(), _frame_rate);
+                               /* Here we check that a preceding section of music doesn't overlap a subsequent one.
+                                  It is complicated by the fact that audio locked meters represent a discontinuity in the pulse
+                                  (they place an exact pulse at a particular time expressed only in frames).
+                                  This has the effect of shifting the calculated frame at the meter pulse (wrt the previous section of music)
+                                  away from its actual frame (which is now the frame location of the exact pulse).
+                                  This can result in the calculated frame (from the previous musical section)
+                                  differing from the exact frame by one sample.
+                                  Allow for that.
+                               */
+                               if (t && (nascent_m_frame > m->frame() + 1 || nascent_m_frame < 0)) {
                                        return false;
                                }
                        }
@@ -1923,7 +2143,8 @@ TempoMap::set_active_tempos (const Metrics& metrics, const framepos_t& frame)
 {
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
                        if (!t->movable()) {
                                t->set_active (true);
                                continue;
@@ -1942,15 +2163,17 @@ TempoMap::set_active_tempos (const Metrics& metrics, const framepos_t& frame)
 }
 
 bool
-TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const framepos_t& frame)
+TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const framepos_t& frame)
 {
        TempoSection* prev_t = 0;
        TempoSection* section_prev = 0;
        framepos_t first_m_frame = 0;
 
+       /* can't move a tempo before the first meter */
        for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
                MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
                        if (!m->movable()) {
                                first_m_frame = m->frame();
                                break;
@@ -1966,7 +2189,8 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const framepos_t
 
        for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
                TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
 
                        if (!t->active()) {
                                continue;
@@ -1974,6 +2198,9 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const framepos_t
                        if (prev_t) {
                                if (t == section) {
                                        section_prev = prev_t;
+                                       if (t->locked_to_meter()) {
+                                               prev_t = t;
+                                       }
                                        continue;
                                }
                                if (t->position_lock_style() == MusicTime) {
@@ -1997,41 +2224,30 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const framepos_t
                }
        }
 
-       if (section->position_lock_style() == MusicTime) {
-               /* we're setting the frame */
-               section->set_position_lock_style (AudioTime);
-               recompute_tempos (imaginary);
-               section->set_position_lock_style (MusicTime);
-       } else {
-               recompute_tempos (imaginary);
-       }
+#if (0)
+       recompute_tempi (imaginary);
 
-       if (check_solved (imaginary, true)) {
+       if (check_solved (imaginary)) {
                return true;
+       } else {
+               dunp (imaginary, std::cout);
        }
+#endif
 
        MetricSectionFrameSorter fcmp;
        imaginary.sort (fcmp);
-       if (section->position_lock_style() == MusicTime) {
-               /* we're setting the frame */
-               section->set_position_lock_style (AudioTime);
-               recompute_tempos (imaginary);
-               section->set_position_lock_style (MusicTime);
-       } else {
-               recompute_tempos (imaginary);
-       }
 
-       if (check_solved (imaginary, true)) {
+       recompute_tempi (imaginary);
+
+       if (check_solved (imaginary)) {
                return true;
        }
 
-       //dump (imaginary, std::cerr);
-
        return false;
 }
 
 bool
-TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const double& pulse)
+TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const double& pulse)
 {
        TempoSection* prev_t = 0;
        TempoSection* section_prev = 0;
@@ -2040,7 +2256,8 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const double& pu
 
        for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
                TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
                        if (!t->active()) {
                                continue;
                        }
@@ -2067,49 +2284,48 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const double& pu
                        prev_t = t;
                }
        }
+
        if (section_prev) {
                section_prev->set_c_func (section_prev->compute_c_func_pulse (section->pulses_per_minute(), pulse, _frame_rate));
                section->set_frame (section_prev->frame_at_pulse (pulse, _frame_rate));
        }
 
-       if (section->position_lock_style() == AudioTime) {
-               /* we're setting the pulse */
-               section->set_position_lock_style (MusicTime);
-               recompute_tempos (imaginary);
-               section->set_position_lock_style (AudioTime);
-       } else {
-               recompute_tempos (imaginary);
-       }
+#if (0)
+       recompute_tempi (imaginary);
 
-       if (check_solved (imaginary, false)) {
+       if (check_solved (imaginary)) {
                return true;
+       } else {
+               dunp (imaginary, std::cout);
        }
+#endif
 
        MetricSectionSorter cmp;
        imaginary.sort (cmp);
-       if (section->position_lock_style() == AudioTime) {
-               /* we're setting the pulse */
-               section->set_position_lock_style (MusicTime);
-               recompute_tempos (imaginary);
-               section->set_position_lock_style (AudioTime);
-       } else {
-               recompute_tempos (imaginary);
-       }
 
-       if (check_solved (imaginary, false)) {
+       recompute_tempi (imaginary);
+       /* Reordering
+        * XX need a restriction here, but only for this case,
+        * as audio locked tempos don't interact in the same way.
+        *
+        * With music-locked tempos, the solution to cross-dragging can fly off the screen
+        * e.g.
+        * |50 bpm                        |250 bpm |60 bpm
+        *                drag 250 to the pulse after 60->
+        * a clue: dragging the second 60 <- past the 250 would cause no such problem.
+        */
+       if (check_solved (imaginary)) {
                return true;
        }
 
-       //dump (imaginary, std::cerr);
-
        return false;
 }
 
 bool
-TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t& frame)
+TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const framepos_t& frame)
 {
        /* disallow moving first meter past any subsequent one, and any movable meter before the first one */
-       const MeterSection* other =  &meter_section_at_locked (imaginary, frame);
+       const MeterSection* other =  &meter_section_at_frame_locked (imaginary, frame);
        if ((!section->movable() && other->movable()) || (!other->movable() && section->movable() && other->frame() >= frame)) {
                return false;
        }
@@ -2121,13 +2337,12 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
                }
        }
 
-       /* it would make sense to bail out if there is no audio-locked meter,
-          however it may be desirable to move a music-locked meter by frame at some point.
-       */
        TempoSection* meter_locked_tempo = 0;
+
        for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) {
                TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*ii)) != 0) {
+               if ((*ii)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*ii);
                        if ((t->locked_to_meter() || !t->movable()) && t->frame() == section->frame()) {
                                meter_locked_tempo = t;
                                break;
@@ -2135,93 +2350,62 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
                }
        }
 
+       if (!meter_locked_tempo) {
+               return false;
+       }
+
        MeterSection* prev_m = 0;
+       Metrics future_map;
+       TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
+       bool solved = false;
 
        for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
                MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
                        if (m == section){
                                if (prev_m && section->movable()) {
                                        const double beats = (pulse_at_frame_locked (imaginary, frame) - prev_m->pulse()) * prev_m->note_divisor();
                                        if (beats + prev_m->beat() < section->beat()) {
-                                               /* disallow position change if it will alter our beat
-                                                  we allow tempo changes to do this in recompute_meters().
-                                                  blocking this is an option, but i'm not convinced that
-                                                  this is what the user would actually want.
-                                                  here we set the frame/pulse corresponding to its musical position.
+                                               /* set the frame/pulse corresponding to its musical position,
+                                                * as an earlier time than this has been requested.
                                                */
+                                               const double new_pulse = ((section->beat() - prev_m->beat())
+                                                                         / prev_m->note_divisor()) + prev_m->pulse();
 
-                                               if (meter_locked_tempo) {
-                                                       Metrics future_map;
-                                                       bool solved = false;
-                                                       TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
-                                                       const double new_pulse = ((section->beat() - prev_m->beat())
-                                                                                 / prev_m->note_divisor()) + prev_m->pulse();
-                                                       const framepos_t smallest_frame = frame_at_pulse_locked (future_map, new_pulse);
-                                                       if ((solved = solve_map (future_map, tempo_copy, smallest_frame))) {
-                                                               meter_locked_tempo->set_pulse (new_pulse);
-                                                               solve_map (imaginary, meter_locked_tempo, smallest_frame);
-                                                               section->set_frame (smallest_frame);
-                                                               section->set_pulse (new_pulse);
-                                                       } else {
-                                                               solved = false;
-                                                       }
-
-                                                       Metrics::const_iterator d = future_map.begin();
-                                                       while (d != future_map.end()) {
-                                                               delete (*d);
-                                                               ++d;
-                                                       }
-
-                                                       if (!solved) {
-                                                               return false;
-                                                       }
-                                               }
-                                               return false;
-                                       } else {
-                                               if (meter_locked_tempo) {
-                                                       Metrics future_map;
-                                                       bool solved = false;
+                                               const framepos_t smallest_frame = frame_at_pulse_locked (future_map, new_pulse);
 
-                                                       TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
-                                                       MeterSection* meter_copy = const_cast<MeterSection*> (&meter_section_at_locked (future_map, section->frame()));
-                                                       meter_copy->set_frame (frame);
+                                               if ((solved = solve_map_frame (future_map, tempo_copy, smallest_frame))) {
+                                                       meter_locked_tempo->set_pulse (new_pulse);
+                                                       solve_map_frame (imaginary, meter_locked_tempo, smallest_frame);
+                                                       section->set_frame (smallest_frame);
+                                                       section->set_pulse (new_pulse);
+                                               } else {
+                                                       solved = false;
+                                               }
 
-                                                       if ((solved = solve_map (future_map, tempo_copy, frame))) {
-                                                               section->set_frame (frame);
-                                                               meter_locked_tempo->set_pulse (((section->beat() - prev_m->beat())
-                                                                                               / prev_m->note_divisor()) + prev_m->pulse());
-                                                               solve_map (imaginary, meter_locked_tempo, frame);
-                                                       } else {
-                                                               solved = false;
-                                                       }
-
-                                                       Metrics::const_iterator d = future_map.begin();
-                                                       while (d != future_map.end()) {
-                                                               delete (*d);
-                                                               ++d;
-                                                       }
-
-                                                       if (!solved) {
-                                                               return false;
-                                                       }
+                                               Metrics::const_iterator d = future_map.begin();
+                                               while (d != future_map.end()) {
+                                                       delete (*d);
+                                                       ++d;
                                                }
-                                       }
-                               } else {
-                                       /* not movable (first meter atm) */
-                                       if (meter_locked_tempo) {
-                                               Metrics future_map;
-                                               bool solved = false;
-                                               TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
 
-                                               tempo_copy->set_frame (frame);
-                                               tempo_copy->set_pulse (0.0);
+                                               if (!solved) {
+                                                       return false;
+                                               }
+                                       } else {
+                                               /* all is ok. set section's locked tempo if allowed.
+                                                  possibly disallowed if there is an adjacent audio-locked tempo.
+                                                  XX this check could possibly go. its never actually happened here.
+                                               */
+                                               MeterSection* meter_copy = const_cast<MeterSection*> (&meter_section_at_frame_locked (future_map, section->frame()));
+                                               meter_copy->set_frame (frame);
 
-                                               if ((solved = solve_map (future_map, tempo_copy, frame))) {
+                                               if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
                                                        section->set_frame (frame);
-                                                       meter_locked_tempo->set_frame (frame);
-                                                       meter_locked_tempo->set_pulse (0.0);
-                                                       solve_map (imaginary, meter_locked_tempo, frame);
+                                                       meter_locked_tempo->set_pulse (((section->beat() - prev_m->beat())
+                                                                                               / prev_m->note_divisor()) + prev_m->pulse());
+                                                       solve_map_frame (imaginary, meter_locked_tempo, frame);
                                                } else {
                                                        solved = false;
                                                }
@@ -2235,10 +2419,32 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
                                                if (!solved) {
                                                        return false;
                                                }
+                                       }
+                               } else {
+                                       /* not movable (first meter atm) */
+
+                                       tempo_copy->set_frame (frame);
+                                       tempo_copy->set_pulse (0.0);
 
+                                       if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
+                                               section->set_frame (frame);
+                                               meter_locked_tempo->set_frame (frame);
+                                               meter_locked_tempo->set_pulse (0.0);
+                                               solve_map_frame (imaginary, meter_locked_tempo, frame);
                                        } else {
+                                               solved = false;
+                                       }
+
+                                       Metrics::const_iterator d = future_map.begin();
+                                       while (d != future_map.end()) {
+                                               delete (*d);
+                                               ++d;
+                                       }
+
+                                       if (!solved) {
                                                return false;
                                        }
+
                                        pair<double, BBT_Time> b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
                                        section->set_beat (b_bbt);
                                        section->set_pulse (0.0);
@@ -2253,26 +2459,21 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
 
        MetricSectionFrameSorter fcmp;
        imaginary.sort (fcmp);
-       if (section->position_lock_style() == MusicTime) {
-               /* we're setting the frame */
-               section->set_position_lock_style (AudioTime);
-               recompute_meters (imaginary);
-               section->set_position_lock_style (MusicTime);
-       } else {
-               recompute_meters (imaginary);
-       }
-       //dump (imaginary, std::cerr);
+
+       recompute_meters (imaginary);
+
        return true;
 }
 
 bool
-TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time& when)
+TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Time& when)
 {
        /* disallow setting section to an existing meter's bbt */
        for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
                MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       if (m->bbt().bars == when.bars) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
+                       if (m != section && m->bbt().bars == when.bars) {
                                return false;
                        }
                }
@@ -2283,7 +2484,8 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time&
 
        for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
                MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
                        pair<double, BBT_Time> b_bbt;
                        double new_pulse = 0.0;
 
@@ -2302,9 +2504,11 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time&
 
                        if (m->position_lock_style() == AudioTime) {
                                TempoSection* meter_locked_tempo = 0;
+
                                for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) {
                                        TempoSection* t;
-                                       if ((t = dynamic_cast<TempoSection*> (*ii)) != 0) {
+                                       if ((*ii)->is_tempo()) {
+                                               t = static_cast<TempoSection*> (*ii);
                                                if ((t->locked_to_meter() || !t->movable()) && t->frame() == m->frame()) {
                                                        meter_locked_tempo = t;
                                                        break;
@@ -2312,6 +2516,10 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time&
                                        }
                                }
 
+                               if (!meter_locked_tempo) {
+                                       return false;
+                               }
+
                                if (prev_m) {
                                        const double beats = ((m->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar());
 
@@ -2327,10 +2535,8 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time&
                                } else {
                                        b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
                                }
-                               if (meter_locked_tempo) {
-                                       meter_locked_tempo->set_pulse (new_pulse);
-                                       recompute_tempos (imaginary);
-                               }
+
+                               meter_locked_tempo->set_pulse (new_pulse);
                                m->set_beat (b_bbt);
                                m->set_pulse (new_pulse);
 
@@ -2368,14 +2574,8 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time&
 
        MetricSectionSorter cmp;
        imaginary.sort (cmp);
-       if (section->position_lock_style() == AudioTime) {
-               /* we're setting the pulse */
-               section->set_position_lock_style (MusicTime);
-               recompute_meters (imaginary);
-               section->set_position_lock_style (AudioTime);
-       } else {
-               recompute_meters (imaginary);
-       }
+
+       recompute_meters (imaginary);
 
        return true;
 }
@@ -2391,7 +2591,8 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSe
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                TempoSection* t;
                MeterSection* m;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
                        if (t == section) {
                                ret = new TempoSection (*t);
                                copy.push_back (ret);
@@ -2401,7 +2602,8 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSe
                        TempoSection* cp = new TempoSection (*t);
                        copy.push_back (cp);
                }
-               if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection *> (*i);
                        MeterSection* cp = new MeterSection (*m);
                        copy.push_back (cp);
                }
@@ -2418,12 +2620,14 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, MeterSe
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                TempoSection* t;
                MeterSection* m;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
                        TempoSection* cp = new TempoSection (*t);
                        copy.push_back (cp);
                }
 
-               if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection *> (*i);
                        if (m == section) {
                                ret = new MeterSection (*m);
                                copy.push_back (ret);
@@ -2437,6 +2641,13 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, MeterSe
        return ret;
 }
 
+/** answers the question "is this a valid beat position for this tempo section?".
+ *  it returns true if the tempo section can be moved to the requested bbt position,
+ *  leaving the tempo map in a solved state.
+ * @param section the tempo section to be moved
+ * @param bbt the requested new position for the tempo section
+ * @return true if the tempo section can be moved to the position, otherwise false.
+ */
 bool
 TempoMap::can_solve_bbt (TempoSection* ts, const BBT_Time& bbt)
 {
@@ -2451,129 +2662,42 @@ TempoMap::can_solve_bbt (TempoSection* ts, const BBT_Time& bbt)
                }
        }
 
-       const double beat = bbt_to_beats_locked (copy, bbt);
-       const bool ret = solve_map (copy, tempo_copy, pulse_at_beat_locked (copy, beat));
-
-       Metrics::const_iterator d = copy.begin();
-       while (d != copy.end()) {
-               delete (*d);
-               ++d;
-       }
-
-       return ret;
-}
-
-/**
-* This is for a gui that needs to know the frame of a tempo section if it were to be moved to some bbt time,
-* taking any possible reordering as a consequence of this into account.
-* @param section - the section to be altered
-* @param bpm - the new Tempo
-* @param bbt - the bbt where the altered tempo will fall
-* @return returns - the position in frames where the new tempo section will lie.
-*/
-framepos_t
-TempoMap::predict_tempo_frame (TempoSection* section, const BBT_Time& bbt)
-{
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-       Metrics future_map;
-       framepos_t ret = 0;
-       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, section);
-       if (!tempo_copy) {
-               return 0;
-       }
-       const double beat = bbt_to_beats_locked (future_map, bbt);
-
-       if (solve_map (future_map, tempo_copy, pulse_at_beat_locked (future_map, beat))) {
-               ret = tempo_copy->frame();
-       } else {
-               ret = section->frame();
-       }
-
-       Metrics::const_iterator d = future_map.begin();
-       while (d != future_map.end()) {
-               delete (*d);
-               ++d;
-       }
-       return ret;
-}
-
-double
-TempoMap::predict_tempo_pulse (TempoSection* section, const framepos_t& frame)
-{
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-       Metrics future_map;
-       double ret = 0.0;
-       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, section);
-
-       if (solve_map (future_map, tempo_copy, frame)) {
-               ret = tempo_copy->pulse();
-       } else {
-               ret = section->pulse();
-       }
-
-       Metrics::const_iterator d = future_map.begin();
-       while (d != future_map.end()) {
-               delete (*d);
-               ++d;
-       }
-       return ret;
-}
-
-void
-TempoMap::gui_move_tempo_frame (TempoSection* ts, const framepos_t& frame)
-{
-       Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
-               if (solve_map (future_map, tempo_copy, frame)) {
-                       solve_map (_metrics, ts, frame);
-                       recompute_meters (_metrics);
-               }
-       }
-
-       Metrics::const_iterator d = future_map.begin();
-       while (d != future_map.end()) {
-               delete (*d);
-               ++d;
-       }
-
-       MetricPositionChanged (); // Emit Signal
-}
+       const bool ret = solve_map_pulse (copy, tempo_copy, pulse_at_bbt_locked (copy, bbt));
 
-void
-TempoMap::gui_move_tempo_beat (TempoSection* ts, const double& beat)
-{
-       Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
-               if (solve_map (future_map, tempo_copy, pulse_at_beat_locked (future_map, beat))) {
-                       solve_map (_metrics, ts, pulse_at_beat_locked (_metrics, beat));
-                       recompute_meters (_metrics);
-               }
-       }
-
-       Metrics::const_iterator d = future_map.begin();
-       while (d != future_map.end()) {
+       Metrics::const_iterator d = copy.begin();
+       while (d != copy.end()) {
                delete (*d);
                ++d;
        }
 
-       MetricPositionChanged (); // Emit Signal
+       return ret;
 }
 
-void
-TempoMap::gui_move_tempo_pulse (TempoSection* ts, const double& pulse)
+/**
+* This is for a gui that needs to know the pulse or frame of a tempo section if it were to be moved to some bbt time,
+* taking any possible reordering as a consequence of this into account.
+* @param section - the section to be altered
+* @param bbt - the bbt where the altered tempo will fall
+* @return returns - the position in pulses and frames (as a pair) where the new tempo section will lie.
+*/
+pair<double, framepos_t>
+TempoMap::predict_tempo_position (TempoSection* section, const BBT_Time& bbt)
 {
        Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
-               if (solve_map (future_map, tempo_copy, pulse)) {
-                       solve_map (_metrics, ts, pulse);
-                       recompute_meters (_metrics);
-               }
+       pair<double, framepos_t> ret = make_pair (0.0, 0);
+
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, section);
+
+       const double beat = beat_at_bbt_locked (future_map, bbt);
+
+       if (solve_map_pulse (future_map, tempo_copy, pulse_at_beat_locked (future_map, beat))) {
+               ret.first = tempo_copy->pulse();
+               ret.second = tempo_copy->frame();
+       } else {
+               ret.first = section->pulse();
+               ret.second = section->frame();
        }
 
        Metrics::const_iterator d = future_map.begin();
@@ -2581,23 +2705,52 @@ TempoMap::gui_move_tempo_pulse (TempoSection* ts, const double& pulse)
                delete (*d);
                ++d;
        }
-
-       MetricPositionChanged (); // Emit Signal
+       return ret;
 }
 
 void
-TempoMap::gui_move_meter (MeterSection* ms, const framepos_t&  frame)
+TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int& sub_num)
 {
        Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
-               if (solve_map (future_map, copy, frame)) {
-                       solve_map (_metrics, ms, frame);
-                       recompute_tempos (_metrics);
+       bool was_musical = ts->position_lock_style() == MusicTime;
+
+       if (sub_num == 0 && was_musical) {
+               /* if we're not snapping to music,
+                  AudioTime and MusicTime may be treated identically.
+               */
+               ts->set_position_lock_style (AudioTime);
+       }
+
+       if (ts->position_lock_style() == MusicTime) {
+               {
+                       /* if we're snapping to a musical grid, set the pulse exactly instead of via the supplied frame. */
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
+                       const double beat = exact_beat_at_frame_locked (future_map, frame, sub_num);
+                       double pulse = pulse_at_beat_locked (future_map, beat);
+
+                       if (solve_map_pulse (future_map, tempo_copy, pulse)) {
+                               solve_map_pulse (_metrics, ts, pulse);
+                               recompute_meters (_metrics);
+                       }
+               }
+
+       } else {
+
+               {
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
+                       if (solve_map_frame (future_map, tempo_copy, frame)) {
+                               solve_map_frame (_metrics, ts, frame);
+                               recompute_meters (_metrics);
+                       }
                }
        }
 
+       if (sub_num == 0 && was_musical) {
+               ts->set_position_lock_style (MusicTime);
+       }
+
        Metrics::const_iterator d = future_map.begin();
        while (d != future_map.end()) {
                delete (*d);
@@ -2608,15 +2761,33 @@ TempoMap::gui_move_meter (MeterSection* ms, const framepos_t&  frame)
 }
 
 void
-TempoMap::gui_move_meter (MeterSection* ms, const Timecode::BBT_Time& bbt)
+TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame)
 {
        Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
-               if (solve_map (future_map, copy, bbt)) {
-                       solve_map (_metrics, ms, bbt);
-                       recompute_tempos (_metrics);
+
+       if (ms->position_lock_style() == AudioTime) {
+
+               {
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
+
+                       if (solve_map_frame (future_map, copy, frame)) {
+                               solve_map_frame (_metrics, ms, frame);
+                               recompute_tempi (_metrics);
+                       }
+               }
+       } else {
+               {
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
+
+                       const double beat = beat_at_frame_locked (_metrics, frame);
+                       const Timecode::BBT_Time bbt = bbt_at_beat_locked (_metrics, beat);
+
+                       if (solve_map_bbt (future_map, copy, bbt)) {
+                               solve_map_bbt (_metrics, ms, bbt);
+                               recompute_tempi (_metrics);
+                       }
                }
        }
 
@@ -2638,9 +2809,9 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm)
                Glib::Threads::RWLock::WriterLock lm (lock);
                TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
                tempo_copy->set_beats_per_minute (bpm.beats_per_minute());
-               recompute_tempos (future_map);
+               recompute_tempi (future_map);
 
-               if (check_solved (future_map, true)) {
+               if (check_solved (future_map)) {
                        ts->set_beats_per_minute (bpm.beats_per_minute());
                        recompute_map (_metrics);
                        can_solve = true;
@@ -2659,59 +2830,79 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm)
 }
 
 void
-TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
-{
-       Metrics future_map;
-       TempoSection* ts = 0;
+TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const framepos_t& end_frame, const double& pulse)
+{
+       /*
+         Ts (future prev_t)   Tnext
+         |                    |
+         |     [drag^]        |
+         |----------|----------
+               e_f  pulse(frame)
+       */
 
-       if (ms->position_lock_style() == AudioTime) {
-               /* disabled for now due to faked tempo locked to meter pulse */
-               return;
-       }
+       Metrics future_map;
 
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               ts = const_cast<TempoSection*>(&tempo_section_at_locked (_metrics, ms->frame() - 1));
+
                if (!ts) {
                        return;
                }
+
                TempoSection* prev_t = copy_metrics_and_point (_metrics, future_map, ts);
                TempoSection* prev_to_prev_t = 0;
-               const frameoffset_t fr_off = frame - ms->frame();
-               double new_bpm = 0.0;
+               const frameoffset_t fr_off = end_frame - frame;
 
                if (prev_t && prev_t->pulse() > 0.0) {
-                       prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_locked (future_map, prev_t->frame() - 1));
+                       prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_frame_locked (future_map, prev_t->frame() - 1));
+               }
+
+               TempoSection* next_t = 0;
+               for (Metrics::iterator i = future_map.begin(); i != future_map.end(); ++i) {
+                       TempoSection* t = 0;
+                       if ((*i)->is_tempo()) {
+                               t = static_cast<TempoSection*> (*i);
+                               if (t->frame() > ts->frame()) {
+                                       next_t = t;
+                                       break;
+                               }
+                       }
                }
+               /* minimum allowed measurement distance in frames */
+               const framepos_t min_dframe = 2;
 
                /* the change in frames is the result of changing the slope of at most 2 previous tempo sections.
                   constant to constant is straightforward, as the tempo prev to prev_t has constant slope.
                */
                double contribution = 0.0;
-               frameoffset_t frame_contribution = 0.0;
-               frameoffset_t prev_t_frame_contribution = 0.0;
 
-               if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                       /* prev to prev_t's position will remain constant in terms of frame and pulse. lets use frames. */
-                       contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (ms->frame() - prev_to_prev_t->frame());
-                       frame_contribution = contribution * (double) fr_off;
-                       prev_t_frame_contribution = fr_off - frame_contribution;
+               if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+                       contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame());
                }
 
+               const frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
+
+               const double start_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
+               const double end_pulse = prev_t->pulse_at_frame (end_frame, _frame_rate);
+
+               double new_bpm;
+
                if (prev_t->type() == TempoSection::Constant || prev_t->c_func() == 0.0) {
 
                        if (prev_t->position_lock_style() == MusicTime) {
                                if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                                       new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame())
-                                                                               / (double) (ms->frame() + prev_t_frame_contribution - prev_t->frame()));
+                                       if (frame > prev_to_prev_t->frame() + min_dframe && (frame + prev_t_frame_contribution) > prev_to_prev_t->frame() + min_dframe) {
 
+                                               new_bpm = prev_t->beats_per_minute() * ((frame - prev_to_prev_t->frame())
+                                                                                       / (double) ((frame + prev_t_frame_contribution) - prev_to_prev_t->frame()));
+                                       } else {
+                                               new_bpm = prev_t->beats_per_minute();
+                                       }
                                } else {
                                        /* prev to prev is irrelevant */
-                                       const double meter_pulse = prev_t->pulse_at_frame (ms->frame(), _frame_rate);
-                                       const double frame_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
 
-                                       if (frame_pulse != prev_t->pulse()) {
-                                               new_bpm = prev_t->beats_per_minute() * ((meter_pulse - prev_t->pulse()) / (frame_pulse - prev_t->pulse()));
+                                       if (start_pulse > prev_t->pulse() && end_pulse > prev_t->pulse()) {
+                                               new_bpm = prev_t->beats_per_minute() * ((start_pulse - prev_t->pulse()) / (end_pulse - prev_t->pulse()));
                                        } else {
                                                new_bpm = prev_t->beats_per_minute();
                                        }
@@ -2719,62 +2910,60 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
                        } else {
                                /* AudioTime */
                                if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                                       new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame())
-                                                                               / (double) (ms->frame() + prev_t_frame_contribution - prev_t->frame()));
+                                       if (frame > prev_to_prev_t->frame() + min_dframe && end_frame > prev_to_prev_t->frame() + min_dframe) {
+
+                                               new_bpm = prev_t->beats_per_minute() * ((frame - prev_to_prev_t->frame())
+                                                                                       / (double) ((end_frame) - prev_to_prev_t->frame()));
+                                       } else {
+                                               new_bpm = prev_t->beats_per_minute();
+                                       }
                                } else {
                                        /* prev_to_prev_t is irrelevant */
 
-                                       if (frame != prev_t->frame()) {
-                                               new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame()) / (double) (frame - prev_t->frame()));
+                                       if (frame > prev_t->frame() + min_dframe && end_frame > prev_t->frame() + min_dframe) {
+                                               new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame()) / (double) (end_frame - prev_t->frame()));
                                        } else {
                                                new_bpm = prev_t->beats_per_minute();
                                        }
                                }
                        }
-               } else if (prev_t->c_func() < 0.0) {
-                       if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                               new_bpm = prev_t->tempo_at_frame (prev_t->frame() + frame_contribution, _frame_rate) * (double) prev_t->note_type();
-                       } else {
-                               /* prev_to_prev_t is irrelevant */
-                               new_bpm = prev_t->tempo_at_frame (prev_t->frame() + fr_off, _frame_rate) * (double) prev_t->note_type();
-                       }
+               } else {
 
-                       const double diff = (prev_t->tempo_at_frame (frame, _frame_rate) * prev_t->note_type()) - prev_t->beats_per_minute();
-                       if (diff > -0.1 && diff  < 0.1) {
-                               new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame())
-                                                                       / (double) ((ms->frame() + prev_t_frame_contribution) - prev_t->frame()));
-                       }
+                       double frame_ratio = 1.0;
+                       double pulse_ratio = 1.0;
+                       const framepos_t pulse_pos = prev_t->frame_at_pulse (pulse, _frame_rate);
 
-               } else if (prev_t->c_func() > 0.0) {
-                       if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                               new_bpm = prev_t->tempo_at_frame (prev_t->frame() - frame_contribution, _frame_rate) * (double) prev_t->note_type();
+                       if (prev_to_prev_t) {
+                               if (pulse_pos > prev_to_prev_t->frame() + min_dframe && (pulse_pos - fr_off) > prev_to_prev_t->frame() + min_dframe) {
+                                       frame_ratio = (((pulse_pos - fr_off) - prev_to_prev_t->frame()) / (double) ((pulse_pos) - prev_to_prev_t->frame()));
+                               }
+                               if (end_pulse > prev_to_prev_t->pulse() && start_pulse > prev_to_prev_t->pulse()) {
+                                       pulse_ratio = ((start_pulse - prev_to_prev_t->pulse()) / (end_pulse - prev_to_prev_t->pulse()));
+                               }
                        } else {
-                               /* prev_to_prev_t is irrelevant */
-                               new_bpm = prev_t->tempo_at_frame (prev_t->frame() - fr_off, _frame_rate) * (double) prev_t->note_type();
-                       }
-
-                       /* limits - a bit clunky, but meh */
-                       const double diff = (prev_t->tempo_at_frame (frame, _frame_rate) * prev_t->note_type()) - prev_t->beats_per_minute();
-                       if (diff > -0.1 && diff  < 0.1) {
-                               new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame())
-                                                                       / (double) ((ms->frame() + prev_t_frame_contribution) - prev_t->frame()));
+                               if (pulse_pos > prev_t->frame() + min_dframe && (pulse_pos - fr_off) > prev_t->frame() + min_dframe) {
+                                       frame_ratio = (((pulse_pos - fr_off) - prev_t->frame()) / (double) ((pulse_pos) - prev_t->frame()));
+                               }
+                               pulse_ratio = (start_pulse / end_pulse);
                        }
+                       new_bpm = prev_t->beats_per_minute() * (pulse_ratio * frame_ratio);
                }
 
+               /* don't clamp and proceed here.
+                  testing has revealed that this can go negative,
+                  which is an entirely different thing to just being too low.
+               */
+               if (new_bpm < 0.5) {
+                       return;
+               }
+               new_bpm = min (new_bpm, (double) 1000.0);
                prev_t->set_beats_per_minute (new_bpm);
-               recompute_tempos (future_map);
+               recompute_tempi (future_map);
                recompute_meters (future_map);
 
-               if (check_solved (future_map, true)) {
-
-                       prev_t = const_cast<TempoSection*>(&tempo_section_at_locked (_metrics, ms->frame() - 1));
-                       prev_t->set_beats_per_minute (new_bpm);
-                       recompute_tempos (_metrics);
-
-                       if (ms->position_lock_style() == AudioTime) {
-                               ms->set_frame (frame);
-                       }
-
+               if (check_solved (future_map)) {
+                       ts->set_beats_per_minute (new_bpm);
+                       recompute_tempi (_metrics);
                        recompute_meters (_metrics);
                }
        }
@@ -2788,6 +2977,33 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
        MetricPositionChanged (); // Emit Signal
 }
 
+double
+TempoMap::exact_beat_at_frame (const framepos_t& frame, const int32_t sub_num)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return exact_beat_at_frame_locked (_metrics, frame, sub_num);
+}
+
+double
+TempoMap::exact_beat_at_frame_locked (const Metrics& metrics, const framepos_t& frame, const int32_t sub_num)
+{
+       double beat = beat_at_frame_locked (metrics, frame);
+       if (sub_num > 1) {
+               beat = floor (beat) + (floor (((beat - floor (beat)) * (double) sub_num) + 0.5) / sub_num);
+       } else if (sub_num == 1) {
+               /* snap to beat */
+               beat = floor (beat + 0.5);
+       } else if (sub_num == -1) {
+               /* snap to  bar */
+               Timecode::BBT_Time bbt = bbt_at_beat_locked (metrics, beat);
+               bbt.beats = 1;
+               bbt.ticks = 0;
+               beat = beat_at_bbt_locked (metrics, bbt);
+       }
+       return beat;
+}
+
 framecnt_t
 TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
 {
@@ -2908,119 +3124,13 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
        return ret_frame;
 }
 
-void
-TempoMap::round_bbt (BBT_Time& when, const int32_t& sub_num, RoundMode dir)
-{
-       if (sub_num == -1) {
-               if (dir > 0) {
-                       ++when.bars;
-                       when.beats = 1;
-                       when.ticks = 0;
-               } else if (dir < 0) {
-                       when.beats = 1;
-                       when.ticks = 0;
-               } else {
-                       const double bpb = meter_section_at_beat (bbt_to_beats_locked (_metrics, when)).divisions_per_bar();
-                       if ((double) when.beats > bpb / 2.0) {
-                               ++when.bars;
-                       }
-                       when.beats = 1;
-                       when.ticks = 0;
-               }
-
-               return;
-
-       } else if (sub_num == 0) {
-               const double bpb = meter_section_at_beat (bbt_to_beats_locked (_metrics, when)).divisions_per_bar();
-               if ((double) when.ticks > BBT_Time::ticks_per_beat / 2.0) {
-                       ++when.beats;
-                       while ((double) when.beats > bpb) {
-                               ++when.bars;
-                               when.beats -= (uint32_t) floor (bpb);
-                       }
-               }
-               when.ticks = 0;
-
-               return;
-       }
-
-       const uint32_t ticks_one_subdivisions_worth = BBT_Time::ticks_per_beat / sub_num;
-
-       if (dir > 0) {
-               /* round to next (or same iff dir == RoundUpMaybe) */
-
-               uint32_t mod = when.ticks % ticks_one_subdivisions_worth;
-
-               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 */
-                       when.ticks += ticks_one_subdivisions_worth;
-
-               } else {
-                       /* not on subdivision, compute distance to next subdivision */
-
-                       when.ticks += ticks_one_subdivisions_worth - mod;
-               }
-
-               if (when.ticks >= BBT_Time::ticks_per_beat) {
-                       when.ticks -= BBT_Time::ticks_per_beat;
-               }
-
-       } else if (dir < 0) {
-               /* round to previous (or same iff dir == RoundDownMaybe) */
-
-               uint32_t difference = when.ticks % ticks_one_subdivisions_worth;
-
-               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;
-               }
-
-               if (when.ticks < difference) {
-                       when.ticks = BBT_Time::ticks_per_beat - when.ticks;
-               } else {
-                       when.ticks -= difference;
-               }
-
-       } else {
-               /* round to nearest */  double rem;
-               if ((rem = fmod ((double) when.ticks, (double) ticks_one_subdivisions_worth)) > (ticks_one_subdivisions_worth / 2.0)) {
-                       /* closer to the next subdivision, so shift forward */
-
-                       when.ticks = when.ticks + (ticks_one_subdivisions_worth - rem);
-
-                       if (when.ticks > Timecode::BBT_Time::ticks_per_beat) {
-                               ++when.beats;
-                               when.ticks -= Timecode::BBT_Time::ticks_per_beat;
-                       }
-
-               } else if (rem > 0) {
-                       /* closer to previous subdivision, so shift backward */
-
-                       if (rem > when.ticks) {
-                               if (when.beats == 0) {
-                                       /* can't go backwards past zero, so ... */
-                               }
-                               /* step back to previous beat */
-                               --when.beats;
-                               when.ticks = Timecode::BBT_Time::ticks_per_beat - rem;
-                       } else {
-                               when.ticks = when.ticks - rem;
-                       }
-               }
-       }
-}
-
 framepos_t
 TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
        const double beat_at_framepos = beat_at_frame_locked (_metrics, frame);
-       BBT_Time bbt (beats_to_bbt_locked (_metrics, beat_at_framepos));
+       BBT_Time bbt (bbt_at_beat_locked (_metrics, beat_at_framepos));
 
        switch (type) {
        case Bar:
@@ -3028,22 +3138,22 @@ TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
                        /* find bar previous to 'frame' */
                        bbt.beats = 1;
                        bbt.ticks = 0;
-                       return frame_time_locked (_metrics, bbt);
+                       return frame_at_bbt_locked (_metrics, bbt);
 
                } else if (dir > 0) {
                        /* find bar following 'frame' */
                        ++bbt.bars;
                        bbt.beats = 1;
                        bbt.ticks = 0;
-                       return frame_time_locked (_metrics, bbt);
+                       return frame_at_bbt_locked (_metrics, bbt);
                } else {
                        /* true rounding: find nearest bar */
-                       framepos_t raw_ft = frame_time_locked (_metrics, bbt);
+                       framepos_t raw_ft = frame_at_bbt_locked (_metrics, bbt);
                        bbt.beats = 1;
                        bbt.ticks = 0;
-                       framepos_t prev_ft = frame_time_locked (_metrics, bbt);
+                       framepos_t prev_ft = frame_at_bbt_locked (_metrics, bbt);
                        ++bbt.bars;
-                       framepos_t next_ft = frame_time_locked (_metrics, bbt);
+                       framepos_t next_ft = frame_at_bbt_locked (_metrics, bbt);
 
                        if ((raw_ft - prev_ft) > (next_ft - prev_ft) / 2) { 
                                return next_ft;
@@ -3079,33 +3189,39 @@ TempoMap::get_grid (vector<TempoMap::BBTPoint>& points,
        if (cnt < 0.0) {
                cnt = 0.0;
        }
+
+       if (frame_at_beat_locked (_metrics, cnt) >= upper) {
+               return;
+       }
+
        while (pos < upper) {
                pos = frame_at_beat_locked (_metrics, cnt);
-               const TempoSection tempo = tempo_section_at_locked (_metrics, pos);
-               const MeterSection meter = meter_section_at_locked (_metrics, pos);
-               const BBT_Time bbt = beats_to_bbt (cnt);
-               points.push_back (BBTPoint (meter, tempo_at_locked (_metrics, pos), pos, bbt.bars, bbt.beats, tempo.c_func()));
+               const TempoSection tempo = tempo_section_at_frame_locked (_metrics, pos);
+               const MeterSection meter = meter_section_at_frame_locked (_metrics, pos);
+               const BBT_Time bbt = bbt_at_beat_locked (_metrics, cnt);
+               points.push_back (BBTPoint (meter, tempo_at_frame_locked (_metrics, pos), pos, bbt.bars, bbt.beats, tempo.c_func()));
                ++cnt;
        }
 }
 
 const TempoSection&
-TempoMap::tempo_section_at (framepos_t frame) const
+TempoMap::tempo_section_at_frame (framepos_t frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return tempo_section_at_locked (_metrics, frame);
+       return tempo_section_at_frame_locked (_metrics, frame);
 }
 
 const TempoSection&
-TempoMap::tempo_section_at_locked (const Metrics& metrics, framepos_t frame) const
+TempoMap::tempo_section_at_frame_locked (const Metrics& metrics, framepos_t frame) const
 {
-       Metrics::const_iterator i;
        TempoSection* prev = 0;
 
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
-               TempoSection* t;
+       TempoSection* t;
 
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
                        if (!t->active()) {
                                continue;
                        }
@@ -3131,28 +3247,12 @@ TempoMap::tempo_section_at_beat_locked (const Metrics& metrics, const double& be
        TempoSection* prev_t = 0;
        const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat);
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if (prev_t && ((t->pulse() - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > beat) {
-                               break;
-                       }
-                       prev_t = t;
-               }
-
-       }
-       return *prev_t;
-}
-
-const TempoSection&
-TempoMap::tempo_section_at_pulse_locked (const Metrics& metrics, const double& pulse) const
-{
-       TempoSection* prev_t = 0;
+       TempoSection* t;
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if (prev_t && t->pulse() > pulse) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
+                       if (prev_t && ((t->pulse() - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > beat) {
                                break;
                        }
                        prev_t = t;
@@ -3170,77 +3270,45 @@ TempoMap::frames_per_beat_at (const framepos_t& frame, const framecnt_t& sr) con
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       const TempoSection* ts_at = &tempo_section_at_locked (_metrics, frame);
+       const TempoSection* ts_at = 0;
        const TempoSection* ts_after = 0;
        Metrics::const_iterator i;
+       TempoSection* t;
 
        for (i = _metrics.begin(); i != _metrics.end(); ++i) {
-               TempoSection* t;
 
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
                        if (!t->active()) {
                                continue;
                        }
-                       if ((*i)->frame() > frame) {
+                       if (ts_at && (*i)->frame() > frame) {
                                ts_after = t;
                                break;
                        }
+                       ts_at = t;
                }
        }
 
        if (ts_after) {
-               return  (60.0 * _frame_rate) / (ts_at->tempo_at_frame (frame, _frame_rate));
+               return  (60.0 * _frame_rate) / (ts_at->tempo_at_frame (frame, _frame_rate) * ts_at->note_type());
        }
        /* must be treated as constant tempo */
        return ts_at->frames_per_beat (_frame_rate);
 }
 
-const Tempo
-TempoMap::tempo_at_locked (const Metrics& metrics, const framepos_t& frame) const
-{
-       TempoSection* prev_t = 0;
-
-       Metrics::const_iterator i;
-
-       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
-               TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if (!t->active()) {
-                               continue;
-                       }
-                       if ((prev_t) && t->frame() > frame) {
-                               /* t is the section past frame */
-                               const double ret_bpm = prev_t->tempo_at_frame (frame, _frame_rate) * prev_t->note_type();
-                               const Tempo ret_tempo (ret_bpm, prev_t->note_type());
-                               return ret_tempo;
-                       }
-                       prev_t = t;
-               }
-       }
-
-       const double ret = prev_t->beats_per_minute();
-       const Tempo ret_tempo (ret, prev_t->note_type ());
-
-       return ret_tempo;
-}
-
-const Tempo
-TempoMap::tempo_at (const framepos_t& frame) const
-{
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-       return tempo_at_locked (_metrics, frame);
-}
-
 const MeterSection&
-TempoMap::meter_section_at_locked (const Metrics& metrics, framepos_t frame) const
+TempoMap::meter_section_at_frame_locked (const Metrics& metrics, framepos_t frame) const
 {
        Metrics::const_iterator i;
        MeterSection* prev = 0;
 
+       MeterSection* m;
+
        for (i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* m;
 
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
 
                        if (prev && (*i)->frame() > frame) {
                                break;
@@ -3260,10 +3328,10 @@ TempoMap::meter_section_at_locked (const Metrics& metrics, framepos_t frame) con
 
 
 const MeterSection&
-TempoMap::meter_section_at (framepos_t frame) const
+TempoMap::meter_section_at_frame (framepos_t frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return meter_section_at_locked (_metrics, frame);
+       return meter_section_at_frame_locked (_metrics, frame);
 }
 
 const MeterSection&
@@ -3273,7 +3341,8 @@ TempoMap::meter_section_at_beat_locked (const Metrics& metrics, const double& be
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
                        if (prev_m && m->beat() > beat) {
                                break;
                        }
@@ -3292,7 +3361,7 @@ TempoMap::meter_section_at_beat (double beat) const
 }
 
 const Meter&
-TempoMap::meter_at (framepos_t frame) const
+TempoMap::meter_at_frame (framepos_t frame) const
 {
        TempoMetric m (metric_at (frame));
        return m.meter();
@@ -3402,6 +3471,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                                catch (failed_constructor& err){
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
                                        _metrics = old_metrics;
+                                       old_metrics.clear();
                                        break;
                                }
 
@@ -3415,6 +3485,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                                catch (failed_constructor& err) {
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
                                        _metrics = old_metrics;
+                                       old_metrics.clear();
                                        break;
                                }
                        }
@@ -3466,6 +3537,13 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                }
 
                recompute_map (_metrics);
+
+               Metrics::const_iterator d = old_metrics.begin();
+               while (d != old_metrics.end()) {
+                       delete (*d);
+                       ++d;
+               }
+               old_metrics.clear ();
        }
 
        PropertyChanged (PropertyChange ());
@@ -3507,7 +3585,7 @@ TempoMap::n_tempos() const
        int cnt = 0;
 
        for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
-               if (dynamic_cast<const TempoSection*>(*i) != 0) {
+               if ((*i)->is_tempo()) {
                        cnt++;
                }
        }
@@ -3522,7 +3600,7 @@ TempoMap::n_meters() const
        int cnt = 0;
 
        for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
-               if (dynamic_cast<const MeterSection*>(*i) != 0) {
+               if (!(*i)->is_tempo()) {
                        cnt++;
                }
        }
@@ -3594,7 +3672,7 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount)
                                                        continue;
                                                }
                                                const double beat = beat_at_pulse_locked (_metrics, t->pulse());
-                                               pair<double, BBT_Time> start = make_pair (beat, beats_to_bbt_locked (_metrics, beat));
+                                               pair<double, BBT_Time> start = make_pair (beat, bbt_at_beat_locked (_metrics, beat));
                                                ms->set_beat (start);
                                                ms->set_pulse (t->pulse());
                                        }
@@ -3616,7 +3694,7 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount)
                                tempo = t;
                                // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " beat = " << (*i)->pulse() <<endl;
                        } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
-                               bbt_time (m->frame(), bbt);
+                               bbt = bbt_at_frame_locked (_metrics, m->frame());
 
                                // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
 
@@ -3721,16 +3799,41 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount)
  *  pos can be -ve, if required.
  */
 framepos_t
-TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const
+TempoMap::framepos_plus_beats (framepos_t frame, Evoral::Beats beats) const
 {
-       return frame_at_beat (beat_at_frame (pos) + beats.to_double());
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       const TempoSection& ts = tempo_section_at_frame_locked (_metrics, frame);
+       MeterSection* prev_m = 0;
+       MeterSection* next_m = 0;
+
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+               if (!(*i)->is_tempo()) {
+                       if (prev_m && (*i)->frame() > frame) {
+                               next_m = static_cast<MeterSection*> (*i);
+                               break;
+                       }
+                       prev_m = static_cast<MeterSection*> (*i);
+               }
+       }
+
+       double pos_beat = prev_m->beat() + (ts.pulse_at_frame (frame, _frame_rate) - prev_m->pulse()) * prev_m->note_divisor();
+
+       /* audio locked meters fake their beat */
+       if (next_m && next_m->beat() < pos_beat) {
+               pos_beat = next_m->beat();
+       }
+
+       return frame_at_beat_locked (_metrics, pos_beat + beats.to_double());
 }
 
 /** 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
 {
-       return frame_at_beat (beat_at_frame (pos) - beats.to_double());
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return frame_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos) - beats.to_double());
 }
 
 /** Add the BBT interval op to pos and return the result */
@@ -3739,7 +3842,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       BBT_Time pos_bbt = beats_to_bbt_locked (_metrics, beat_at_frame_locked (_metrics, pos));
+       BBT_Time pos_bbt = bbt_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos));
        pos_bbt.ticks += op.ticks;
        if (pos_bbt.ticks >= BBT_Time::ticks_per_beat) {
                ++pos_bbt.beats;
@@ -3747,15 +3850,15 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
        }
        pos_bbt.beats += op.beats;
        /* the meter in effect will start on the bar */
-       double divisions_per_bar = meter_section_at_beat (bbt_to_beats_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
+       double divisions_per_bar = meter_section_at_beat (beat_at_bbt_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
        while (pos_bbt.beats >= divisions_per_bar + 1) {
                ++pos_bbt.bars;
-               divisions_per_bar = meter_section_at_beat (bbt_to_beats_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
+               divisions_per_bar = meter_section_at_beat (beat_at_bbt_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
                pos_bbt.beats -= divisions_per_bar;
        }
        pos_bbt.bars += op.bars;
 
-       return frame_time_locked (_metrics, pos_bbt);
+       return frame_at_bbt_locked (_metrics, pos_bbt);
 }
 
 /** Count the number of beats that are equivalent to distance when going forward,
@@ -3764,7 +3867,9 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 Evoral::Beats
 TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 {
-       return Evoral::Beats (beat_at_frame (pos + distance) - beat_at_frame (pos));
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return Evoral::Beats (beat_at_frame_locked (_metrics, pos + distance) - beat_at_frame_locked (_metrics, pos));
 }
 
 struct bbtcmp {