more documentation in tempo.cc, fix constant bug in pulse_at_tempo_locked()
[ardour.git] / libs / ardour / tempo.cc
index 9fa77a71a99c04eabbfef7143f43bf8b0a6633ec..07c008ab7ce8a0d805047f170236cf55a05a20b3 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;
@@ -47,6 +47,18 @@ using Timecode::BBT_Time;
 Meter    TempoMap::_default_meter (4.0, 4.0);
 Tempo    TempoMap::_default_tempo (120.0);
 
+framepos_t
+MetricSection::frame_at_minute (const double& time) const
+{
+       return (framepos_t) floor ((time * 60.0 * _sample_rate) + 0.5);
+}
+
+double
+MetricSection::minute_at_frame (const framepos_t& frame) const
+{
+       return (frame / (double) _sample_rate) / 60.0;
+}
+
 /***********************************************************************/
 
 double
@@ -72,8 +84,8 @@ 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, 0, MusicTime)
+TempoSection::TempoSection (const XMLNode& node, framecnt_t sample_rate)
+       : MetricSection (0.0, 0, MusicTime, true, sample_rate)
        , Tempo (TempoMap::default_tempo())
        , _c_func (0.0)
        , _active (true)
@@ -84,6 +96,7 @@ TempoSection::TempoSection (const XMLNode& node)
        BBT_Time bbt;
        double pulse;
        uint32_t frame;
+       double minute;
 
        _legacy_bbt = BBT_Time (0, 0, 0);
 
@@ -111,7 +124,15 @@ TempoSection::TempoSection (const XMLNode& node)
                if (sscanf (prop->value().c_str(), "%" PRIu32, &frame) != 1) {
                        error << _("TempoSection XML node has an illegal \"frame\" value") << endmsg;
                } else {
-                       set_frame (frame);
+                       set_minute (minute_at_frame (frame));
+               }
+       }
+
+       if ((prop = node.property ("minute")) != 0) {
+               if (sscanf (prop->value().c_str(), "%lf", &minute) != 1) {
+                       error << _("TempoSection XML node has an illegal \"minute\" value") << endmsg;
+               } else {
+                       set_minute (minute);
                }
        }
 
@@ -132,7 +153,7 @@ TempoSection::TempoSection (const XMLNode& node)
                if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 1.0) {
                        error << _("TempoSection XML node has an illegal \"note-type\" value") << endmsg;
                        throw failed_constructor();
-               }
+       }
        }
 
        if ((prop = node.property ("movable")) == 0) {
@@ -179,13 +200,15 @@ 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", minute());
+       root->add_property ("minute", buf);
+       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);
@@ -204,87 +227,83 @@ TempoSection::set_type (Type type)
        _type = type;
 }
 
-/** returns the tempo in whole pulses per minute at the zero-based (relative to session) frame.
+/** returns the tempo in beats per minute at the zero-based (relative to session) minute.
 */
 double
-TempoSection::tempo_at_frame (const framepos_t& f, const framecnt_t& frame_rate) const
+TempoSection::tempo_at_minute (const double& m) const
 {
 
        if (_type == Constant || _c_func == 0.0) {
-               return pulses_per_minute();
+               return beats_per_minute();
        }
 
-       return pulse_tempo_at_time (frame_to_minute (f - frame(), frame_rate));
+       return _tempo_at_time (m - minute());
 }
 
-/** 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.
+/** returns the zero-based minute (relative to session)
+   where the tempo in beats per minute occurs in this section.
+   pulse p is only used for constant tempi.
    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
+double
+TempoSection::minute_at_tempo (const double& bpm, const double& p) const
 {
        if (_type == Constant || _c_func == 0.0) {
-               return ((b - pulse())  * frames_per_pulse (frame_rate))  + frame();
+               return (((p - pulse())  * note_type()) / beats_per_minute()) + minute();
        }
 
-       return minute_to_frame (time_at_pulse_tempo (ppm), frame_rate) + frame();
+       return _time_at_tempo (bpm) + minute();
 }
-/** returns the tempo in pulses per minute at the zero-based (relative to session) beat.
+/** returns the tempo in beats per minute at the supplied pulse.
 */
 double
 TempoSection::tempo_at_pulse (const double& p) const
 {
 
        if (_type == Constant || _c_func == 0.0) {
-               return pulses_per_minute();
+               return beats_per_minute();
        }
-       double const ppm = pulse_tempo_at_pulse (p - pulse());
-       return ppm;
+
+       return _tempo_at_pulse (p - pulse());
 }
 
-/** returns the zero-based beat (relative to session)
-   where the tempo in whole pulses per minute occurs given frame f. frame f is only used for constant tempos.
-   note that the session tempo map may have multiple beats at a given tempo.
+/** returns the pulse where the tempo in beats per minute occurs given frame f.
+    frame f is only used for constant tempi.
+    note that the session tempo map may have multiple locations where a given tempo occurs.
 */
 double
-TempoSection::pulse_at_tempo (const double& ppm, const framepos_t& f, const framecnt_t& frame_rate) const
+TempoSection::pulse_at_tempo (const double& bpm, const double& m) const
 {
        if (_type == Constant || _c_func == 0.0) {
-               double const pulses = ((f - frame()) / frames_per_pulse (frame_rate)) + pulse();
-               return  pulses;
+               const double pulses = (((m - minute()) * beats_per_minute()) / note_type()) + pulse();
+               return pulses;
        }
-       return pulse_at_pulse_tempo (ppm) + pulse();
+
+       return _pulse_at_tempo (bpm) + pulse();
 }
 
-/** returns the zero-based pulse (relative to session origin)
-   where the zero-based frame (relative to session)
-   lies.
+/** returns the pulse at the supplied session-relative minute.
 */
 double
-TempoSection::pulse_at_frame (const framepos_t& f, const framecnt_t& frame_rate) const
+TempoSection::pulse_at_minute (const double& m) const
 {
        if (_type == Constant || _c_func == 0.0) {
-               return ((f - frame()) / frames_per_pulse (frame_rate)) + pulse();
+               return (((m - minute()) * beats_per_minute()) / _note_type) + pulse();
        }
 
-       return pulse_at_time (frame_to_minute (f - frame(), frame_rate)) + pulse();
+       return _pulse_at_time (m - minute()) + pulse();
 }
 
-/** returns the zero-based frame (relative to session start frame)
-   where the zero-based pulse (relative to session start)
-   falls.
+/** returns the minute (relative to session start) at the supplied pulse.
 */
-
-framepos_t
-TempoSection::frame_at_pulse (const double& p, const framecnt_t& frame_rate) const
+double
+TempoSection::minute_at_pulse (const double& p) const
 {
        if (_type == Constant || _c_func == 0.0) {
-               return (framepos_t) floor ((p - pulse()) * frames_per_pulse (frame_rate)) + frame();
+               return (((p - pulse()) * note_type()) / beats_per_minute()) + minute();
        }
 
-       return minute_to_frame (time_at_pulse (p - pulse()), frame_rate) + frame();
+       return _time_at_pulse (p - pulse()) + minute();
 }
 
 /*
@@ -363,97 +382,85 @@ https://www.zhdk.ch/fileadmin/data_subsites/data_icst/Downloads/Timegrid/ICST_Te
 */
 
 /*
-  compute this ramp's function constant using the end tempo (in whole pulses per minute)
+  compute this ramp's function constant using the end tempo (in qn beats per minute)
   and duration (pulses into global start) of some later tempo section.
 */
 double
-TempoSection::compute_c_func_pulse (const double& end_bpm, const double& end_pulse, const framecnt_t& frame_rate)
-{
-       double const log_tempo_ratio = log (end_bpm / pulses_per_minute());
-       return pulses_per_minute() *  (expm1 (log_tempo_ratio)) / (end_pulse - pulse());
-}
-
-/* compute the function constant from some later tempo section, given tempo (whole pulses/min.) and distance (in frames) from session origin */
-double
-TempoSection::compute_c_func_frame (const double& end_bpm, const framepos_t& end_frame, const framecnt_t& frame_rate) const
-{
-       return c_func (end_bpm, frame_to_minute (end_frame - frame(), frame_rate));
-}
-
-framepos_t
-TempoSection::minute_to_frame (const double& time, const framecnt_t& frame_rate) const
+TempoSection::compute_c_func_pulse (const double& end_bpm, const double& end_pulse) const
 {
-       return (framepos_t) floor ((time * 60.0 * frame_rate) + 0.5);
+       double const log_tempo_ratio = log (end_bpm / beats_per_minute());
+       return (beats_per_minute() * expm1 (log_tempo_ratio)) / ((end_pulse - pulse()) * _note_type);
 }
 
+/* compute the function constant from some later tempo section, given tempo (quarter notes/min.) and distance (in frames) from session origin */
 double
-TempoSection::frame_to_minute (const framepos_t& frame, const framecnt_t& frame_rate) const
+TempoSection::compute_c_func_minute (const double& end_bpm, const double& end_minute) const
 {
-       return (frame / (double) frame_rate) / 60.0;
+       return c_func (end_bpm, end_minute - minute());
 }
 
 /* position function */
 double
-TempoSection::a_func (double end_ppm, double c_func) const
+TempoSection::a_func (double end_bpm, double c_func) const
 {
-       return log (end_ppm / pulses_per_minute()) /  c_func;
+       return log (end_bpm / beats_per_minute()) / c_func;
 }
 
 /*function constant*/
 double
-TempoSection::c_func (double end_ppm, double end_time) const
+TempoSection::c_func (double end_bpm, double end_time) const
 {
-       return log (end_ppm / pulses_per_minute()) /  end_time;
+       return log (end_bpm / beats_per_minute()) / end_time;
 }
 
-/* tempo in ppm at time in minutes */
+/* tempo in bpm at time in minutes */
 double
-TempoSection::pulse_tempo_at_time (const double& time) const
+TempoSection::_tempo_at_time (const double& time) const
 {
-       return exp (_c_func * time) * pulses_per_minute();
+       return exp (_c_func * time) * beats_per_minute();
 }
 
-/* time in minutes at tempo in ppm */
+/* time in minutes at tempo in bpm */
 double
-TempoSection::time_at_pulse_tempo (const double& pulse_tempo) const
+TempoSection::_time_at_tempo (const double& tempo) const
 {
-       return log (pulse_tempo / pulses_per_minute()) / _c_func;
+       return log (tempo / beats_per_minute()) / _c_func;
 }
 
-/* tick at tempo in ppm */
+/* pulse at tempo in bpm */
 double
-TempoSection::pulse_at_pulse_tempo (const double& pulse_tempo) const
+TempoSection::_pulse_at_tempo (const double& tempo) const
 {
-       return (pulse_tempo - pulses_per_minute()) / _c_func;
+       return ((tempo - beats_per_minute()) / _c_func) / _note_type;
 }
 
-/* tempo in ppm at tick */
+/* tempo in bpm at pulse */
 double
-TempoSection::pulse_tempo_at_pulse (const double& pulse) const
+TempoSection::_tempo_at_pulse (const double& pulse) const
 {
-       return (pulse * _c_func) + pulses_per_minute();
+       return (pulse * _note_type * _c_func) + beats_per_minute();
 }
 
 /* pulse at time in minutes */
 double
-TempoSection::pulse_at_time (const double& time) const
+TempoSection::_pulse_at_time (const double& time) const
 {
-       return expm1 (_c_func * time) * (pulses_per_minute() / _c_func);
+       return expm1 (_c_func * time) * (beats_per_minute() / (_c_func * _note_type));
 }
 
 /* time in minutes at pulse */
 double
-TempoSection::time_at_pulse (const double& pulse) const
+TempoSection::_time_at_pulse (const double& pulse) const
 {
-       return log1p ((_c_func * pulse) / pulses_per_minute()) / _c_func;
+       return log1p ((_c_func * pulse * _note_type) / beats_per_minute()) / _c_func;
 }
 
 /***********************************************************************/
 
 const string MeterSection::xml_state_node_name = "Meter";
 
-MeterSection::MeterSection (const XMLNode& node)
-       : MetricSection (0.0, 0, MusicTime), Meter (TempoMap::default_meter())
+MeterSection::MeterSection (const XMLNode& node, const framecnt_t sample_rate)
+       : MetricSection (0.0, 0, MusicTime, false, sample_rate), Meter (TempoMap::default_meter())
 {
        XMLProperty const * prop;
        LocaleGuard lg;
@@ -462,6 +469,7 @@ MeterSection::MeterSection (const XMLNode& node)
        double beat = 0.0;
        framepos_t frame = 0;
        pair<double, BBT_Time> start;
+       double minute = 0.0;
 
        if ((prop = node.property ("start")) != 0) {
                if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
@@ -508,7 +516,14 @@ MeterSection::MeterSection (const XMLNode& node)
                if (sscanf (prop->value().c_str(), "%li", &frame) != 1) {
                        error << _("MeterSection XML node has an illegal \"frame\" value") << endmsg;
                } else {
-                       set_frame (frame);
+                       set_minute (minute_at_frame (frame));
+               }
+       }
+       if ((prop = node.property ("minute")) != 0) {
+               if (sscanf (prop->value().c_str(), "%lf", &minute) != 1) {
+                       error << _("MeterSection XML node has an illegal \"frame\" value") << endmsg;
+               } else {
+                       set_minute (minute);
                }
        }
 
@@ -569,12 +584,14 @@ 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);
+       snprintf (buf, sizeof (buf), "%lf", minute());
+       root->add_property ("minute", 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);
@@ -586,51 +603,89 @@ MeterSection::get_state() const
 /*
   Tempo Map Overview
 
+  The Shaggs - Things I Wonder
+  https://www.youtube.com/watch?v=9wQK6zMJOoQ
+
   Tempo is the rate of the musical pulse.
-  Meters divide the pulses into measures and beats.
+  Meter divides pulse into measures and beats.
 
-  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.
+  TempoSection - provides pulse in the form of beats_per_minute() - the number of quarter notes in one minute.
+  Note that 'beats' in Tempo::beats_per_minute() are quarter notes (pulse based). In the rest of tempo map,
+  'beat' usually refers to accumulated BBT beats (pulse and meter based).
 
-  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).
+  MeterSecion - divides pulse into measures (via divisions_per_bar) and beats (via note_divisor).
 
-  Having done this, we can now find any one of tempo, beat, frame or pulse if a beat, frame, pulse or tempo is known.
+  Both tempo and meter have a pulse position and a frame position.
+  Meters also have a beat position, which is always 0.0 for the first one.
+  TempoSection and MeterSection 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.
 
-  With tepo sections potentially being ramped, meters provide a way of mapping beats to whole pulses without
+  With tempo 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 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.
 
   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())
 
-  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.
-
-  A tempo locked to music is locked to musical pulses.
-  A meter locked to music is locked to beats.
-
-  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 beat, remember 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) {
@@ -649,8 +704,8 @@ TempoMap::TempoMap (framecnt_t fr)
        _frame_rate = fr;
        BBT_Time start (1, 1, 0);
 
-       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);
+       TempoSection *t = new TempoSection (0.0, 0.0, _default_tempo.beats_per_minute(), _default_tempo.note_type(), TempoSection::Ramp, AudioTime, fr);
+       MeterSection *m = new MeterSection (0.0, 0.0, 0.0, start, _default_meter.divisions_per_bar(), _default_meter.note_divisor(), AudioTime, fr);
 
        t->set_movable (false);
        m->set_movable (false);
@@ -672,6 +727,18 @@ TempoMap::~TempoMap ()
        _metrics.clear();
 }
 
+framepos_t
+TempoMap::frame_at_minute (const double time) const
+{
+       return (framepos_t) floor ((time * 60.0 * _frame_rate) + 0.5);
+}
+
+double
+TempoMap::minute_at_frame (const framepos_t frame) const
+{
+       return (frame / (double) _frame_rate) / 60.0;
+}
+
 void
 TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation)
 {
@@ -733,13 +800,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;
@@ -748,7 +815,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()) {
@@ -892,14 +959,14 @@ TempoMap::do_insert (MetricSection* section)
                //dump (_metrics, std::cout);
        }
 }
-
+/* user supplies the exact pulse if pls == MusicTime */
 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);
-               ts = add_tempo_locked (tempo, pulse, frame, type, pls, true);
+               ts = add_tempo_locked (tempo, pulse, minute_at_frame (frame), type, pls, true);
        }
 
 
@@ -918,11 +985,11 @@ TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const doubl
                TempoSection& first (first_tempo());
                if (ts.frame() != first.frame()) {
                        remove_tempo_locked (ts);
-                       add_tempo_locked (tempo, pulse, frame, type, pls, true, locked_to_meter);
+                       add_tempo_locked (tempo, pulse, minute_at_frame (frame), type, pls, true, locked_to_meter);
                } else {
                        first.set_type (type);
                        first.set_pulse (0.0);
-                       first.set_frame (frame);
+                       first.set_minute (minute_at_frame (frame));
                        first.set_position_lock_style (AudioTime);
                        {
                                /* cannot move the first tempo section */
@@ -936,33 +1003,38 @@ TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const doubl
 }
 
 TempoSection*
-TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, framepos_t frame
+TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, double minute
                            , TempoSection::Type type, PositionLockStyle pls, bool recompute, bool locked_to_meter)
 {
-       TempoSection* t = new TempoSection (pulse, frame, tempo.beats_per_minute(), tempo.note_type(), type, pls);
+       TempoSection* t = new TempoSection (pulse, minute, tempo.beats_per_minute(), tempo.note_type(), type, pls, _frame_rate);
        t->set_locked_to_meter (locked_to_meter);
+       bool solved = false;
 
        do_insert (t);
 
        if (recompute) {
                if (pls == AudioTime) {
-                       solve_map_frame (_metrics, t, t->frame());
+                       solved = solve_map_minute (_metrics, t, t->minute());
                } else {
-                       solve_map_pulse (_metrics, t, t->pulse());
+                       solved = solve_map_pulse (_metrics, t, t->pulse());
                }
                recompute_meters (_metrics);
        }
 
+       if (!solved && recompute) {
+               recompute_map (_metrics);
+       }
+
        return t;
 }
 
 MeterSection*
-TempoMap::add_meter (const Meter& meter, const double& beat, const Timecode::BBT_Time& where, const framepos_t& frame, PositionLockStyle pls)
+TempoMap::add_meter (const Meter& meter, const double& beat, const Timecode::BBT_Time& where, PositionLockStyle pls)
 {
        MeterSection* m = 0;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               m = add_meter_locked (meter, beat, where, frame, pls, true);
+               m = add_meter_locked (meter, beat, where, pls, true);
        }
 
 
@@ -977,7 +1049,7 @@ TempoMap::add_meter (const Meter& meter, const double& beat, const Timecode::BBT
 }
 
 void
-TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where, const framepos_t& frame, PositionLockStyle pls)
+TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where, PositionLockStyle pls)
 {
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
@@ -985,7 +1057,7 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T
 
                if (ms.movable()) {
                        remove_meter_locked (ms);
-                       add_meter_locked (meter, beat, where, frame, pls, true);
+                       add_meter_locked (meter, beat, where, pls, true);
                } else {
                        MeterSection& first (first_meter());
                        TempoSection& first_t (first_tempo());
@@ -993,10 +1065,10 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T
                        *static_cast<Meter*>(&first) = meter;
                        first.set_position_lock_style (AudioTime);
                        first.set_pulse (0.0);
-                       first.set_frame (frame);
+                       //first.set_minute (minute_at_frame (frame));
                        pair<double, BBT_Time> beat = make_pair (0.0, BBT_Time (1, 1, 0));
                        first.set_beat (beat);
-                       first_t.set_frame (first.frame());
+                       first_t.set_minute (first.minute());
                        first_t.set_pulse (0.0);
                        first_t.set_position_lock_style (AudioTime);
                        recompute_map (_metrics);
@@ -1007,29 +1079,50 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T
 }
 
 MeterSection*
-TempoMap::add_meter_locked (const Meter& meter, double beat, const Timecode::BBT_Time& where, framepos_t frame, PositionLockStyle pls, bool recompute)
+TempoMap::add_meter_locked (const Meter& meter, double beat, const BBT_Time& where, PositionLockStyle pls, bool recompute)
 {
-       const MeterSection& prev_m = meter_section_at_locked  (_metrics, frame - 1);
+       const MeterSection& prev_m = meter_section_at_minute_locked  (_metrics, minute_at_beat_locked (_metrics, beat) - minute_at_frame (1));
        const double pulse = ((where.bars - prev_m.bbt().bars) * (prev_m.divisions_per_bar() / prev_m.note_divisor())) + prev_m.pulse();
+       const double time_minutes = minute_at_pulse_locked (_metrics, pulse);
+       TempoSection* mlt = 0;
 
        if (pls == AudioTime) {
                /* add meter-locked tempo */
-               add_tempo_locked (tempo_at_locked (_metrics, frame), pulse,  frame, TempoSection::Ramp, AudioTime, true, true);
+               mlt = add_tempo_locked (tempo_at_minute_locked (_metrics, time_minutes), pulse,  time_minutes, TempoSection::Ramp, AudioTime, true, true);
+
+               if (!mlt) {
+                       return 0;
+               }
+
        }
 
-       MeterSection* new_meter = new MeterSection (pulse, frame, beat, where, meter.divisions_per_bar(), meter.note_divisor(), pls);
+       MeterSection* new_meter = new MeterSection (pulse, time_minutes, beat, where, meter.divisions_per_bar(), meter.note_divisor(), pls, _frame_rate);
+       bool solved = false;
 
        do_insert (new_meter);
 
        if (recompute) {
 
                if (pls == AudioTime) {
-                       solve_map_frame (_metrics, new_meter, frame);
+                       solved = solve_map_minute (_metrics, new_meter, time_minutes);
                } else {
-                       solve_map_bbt (_metrics, new_meter, where);
+                       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 (!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;
 }
 
@@ -1185,14 +1278,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;
                        }
@@ -1205,14 +1299,14 @@ TempoMap::recompute_tempos (Metrics& metrics)
                        }
                        if (prev_t) {
                                if (t->position_lock_style() == AudioTime) {
-                                       prev_t->set_c_func (prev_t->compute_c_func_frame (t->pulses_per_minute(), t->frame(), _frame_rate));
+                                       prev_t->set_c_func (prev_t->compute_c_func_minute (t->beats_per_minute(), t->minute()));
                                        if (!t->locked_to_meter()) {
-                                               t->set_pulse (prev_t->pulse_at_tempo (t->pulses_per_minute(), t->frame(), _frame_rate));
+                                               t->set_pulse (prev_t->pulse_at_tempo (t->beats_per_minute(), t->minute()));
                                        }
 
                                } else {
-                                       prev_t->set_c_func (prev_t->compute_c_func_pulse (t->pulses_per_minute(), t->pulse(), _frame_rate));
-                                       t->set_frame (prev_t->frame_at_tempo (t->pulses_per_minute(), t->pulse(), _frame_rate));
+                                       prev_t->set_c_func (prev_t->compute_c_func_pulse (t->beats_per_minute(), t->pulse()));
+                                       t->set_minute (prev_t->minute_at_tempo (t->beats_per_minute(), t->pulse()));
 
                                }
                        }
@@ -1234,14 +1328,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;
@@ -1273,26 +1369,26 @@ 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));
+                               meter->set_minute (minute_at_pulse_locked (metrics, pulse));
                        }
 
                        prev_m = meter;
@@ -1305,15 +1401,6 @@ TempoMap::recompute_map (Metrics& metrics, framepos_t end)
 {
        /* CALLER MUST HOLD WRITE LOCK */
 
-       if (end < 0) {
-
-               /* we will actually stop once we hit
-                  the last metric.
-               */
-               end = max_framepos;
-
-       }
-
        DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
 
        if (end == 0) {
@@ -1322,7 +1409,7 @@ TempoMap::recompute_map (Metrics& metrics, framepos_t end)
                return;
        }
 
-       recompute_tempos (metrics);
+       recompute_tempi (metrics);
        recompute_meters (metrics);
 }
 
@@ -1371,7 +1458,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)) {
@@ -1385,218 +1473,421 @@ TempoMap::metric_at (BBT_Time bbt) const
        return m;
 }
 
+/** Returns the BBT (meter-based) beat corresponding to the supplied frame, possibly returning a negative value.
+ * @param frame The session frame position.
+ * @return The beat duration according to the tempo map at the supplied frame.
+ *
+ * If the supplied frame lies before the first meter, the returned beat duration will be negative.
+ * The returned beat is obtained using the first meter and the continuation of the tempo curve (backwards).
+ *
+ * This function uses both tempo and meter.
+ */
 double
-TempoMap::pulse_at_beat (const double& beat) const
+TempoMap::beat_at_frame (const framecnt_t& frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return pulse_at_beat_locked (_metrics, beat);
+
+       return beat_at_minute_locked (_metrics, minute_at_frame (frame));
 }
 
+/* This function uses both tempo and meter.*/
 double
-TempoMap::pulse_at_beat_locked (const Metrics& metrics, const double& beat) const
+TempoMap::beat_at_minute_locked (const Metrics& metrics, const double& minute) const
 {
+       const TempoSection& ts = tempo_section_at_minute_locked (metrics, minute);
        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)->minute() > minute) {
+                               next_m = static_cast<MeterSection*> (*i);
                                break;
                        }
-                       prev_m = m;
+                       prev_m = static_cast<MeterSection*> (*i);
                }
+       }
 
+       const double beat = prev_m->beat() + (ts.pulse_at_minute (minute) - 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::beat_at_pulse (const double& pulse) const
+/** Returns the frame corresponding to the supplied BBT (meter-based) beat.
+ * @param beat The BBT (meter-based) beat.
+ * @return The frame duration according to the tempo map at the supplied BBT (meter-based) beat.
+ *
+ * This function uses both tempo and meter.
+ */
+framepos_t
+TempoMap::frame_at_beat (const double& beat) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return beat_at_pulse_locked (_metrics, pulse);
+
+       return frame_at_minute (minute_at_beat_locked (_metrics, beat));
 }
 
+/* meter & tempo section based */
 double
-TempoMap::beat_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+TempoMap::minute_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->minute_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse());
 }
 
-double
-TempoMap::pulse_at_frame (const framecnt_t& frame) const
+/** Returns a Tempo corresponding to the supplied frame position.
+ * @param frame The audio frame.
+ * @return a Tempo according to the tempo map at the supplied frame.
+ *
+ */
+Tempo
+TempoMap::tempo_at_frame (const framepos_t& frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return pulse_at_frame_locked (_metrics, frame);
+
+       return tempo_at_minute_locked (_metrics, minute_at_frame (frame));
 }
 
-/* tempo section based */
-double
-TempoMap::pulse_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
+Tempo
+TempoMap::tempo_at_minute_locked (const Metrics& metrics, const double& minute) const
 {
-       /* HOLD (at least) THE READER LOCK */
        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 ((*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->minute() > minute) {
+                               /* t is the section past frame */
+                               const double ret_bpm = prev_t->tempo_at_minute (minute);
+                               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;
 }
 
-framecnt_t
-TempoMap::frame_at_pulse (const double& pulse) 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 frame_at_pulse_locked (_metrics, pulse);
+
+       return frame_at_minute (minute_at_tempo_locked (_metrics, tempo));
 }
 
-/* tempo section based */
-framecnt_t
-TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+double
+TempoMap::minute_at_tempo_locked (const Metrics& metrics, const Tempo& tempo) const
 {
-       /* HOLD THE READER LOCK */
+       TempoSection* prev_t = 0;
+       const double tempo_bpm = tempo.beats_per_minute();
 
-       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_bpm = t->beats_per_minute();
+
+                       if (t_bpm == tempo_bpm) {
+                               return t->minute();
                        }
 
+                       if (prev_t) {
+                               const double prev_t_bpm = prev_t->beats_per_minute();
+
+                               if ((t_bpm > tempo_bpm && prev_t_bpm < tempo_bpm) || (t_bpm < tempo_bpm && prev_t_bpm > tempo_bpm)) {
+                                       return prev_t->minute_at_tempo (tempo_bpm, prev_t->pulse());
+                               }
+                       }
                        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 ret;
+       return prev_t->minute();
 }
 
-double
-TempoMap::beat_at_frame (const framecnt_t& frame) const
+Tempo
+TempoMap::tempo_at_pulse_locked (const Metrics& metrics, const double& pulse) const
 {
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-       return beat_at_frame_locked (_metrics, frame);
-}
+       TempoSection* prev_t = 0;
 
-/* meter / tempo section based */
-double
-TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
-{
-       const TempoSection& ts = tempo_section_at_locked (metrics, frame);
-       MeterSection* prev_m = 0;
-       MeterSection* next_m = 0;
+       TempoSection* t;
 
        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()) {
+                       t = static_cast<TempoSection*> (*i);
+                       if (!t->active()) {
+                               continue;
                        }
-                       prev_m = m;
+                       if ((prev_t) && t->pulse() > pulse) {
+                               /* t is the section past frame */
+                               const double ret_bpm = prev_t->tempo_at_pulse (pulse);
+                               const Tempo ret_tempo (ret_bpm, prev_t->note_type());
+                               return ret_tempo;
+                       }
+                       prev_t = t;
                }
        }
-       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();
-       }
+       const double ret = prev_t->beats_per_minute();
+       const Tempo ret_tempo (ret, prev_t->note_type ());
 
-       return beat;
+       return ret_tempo;
 }
 
-framecnt_t
-TempoMap::frame_at_beat (const double& beat) const
+double
+TempoMap::pulse_at_tempo_locked (const Metrics& metrics, const Tempo& tempo) const
 {
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-       return frame_at_beat_locked (_metrics, beat);
-}
+       TempoSection* prev_t = 0;
+       const double tempo_bpm = tempo.beats_per_minute();
 
-/* meter section based */
-framecnt_t
-TempoMap::frame_at_beat_locked (const Metrics& metrics, const double& beat) const
-{
-       const TempoSection& prev_t = tempo_section_at_beat_locked (metrics, beat);
-       MeterSection* prev_m = 0;
+       Metrics::const_iterator i;
 
-       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;
+       for (i = metrics.begin(); i != metrics.end(); ++i) {
+               TempoSection* t;
+               if ((*i)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*i);
+
+                       if (!t->active()) {
+                               continue;
                        }
-                       prev_m = m;
+
+                       const double t_bpm = t->beats_per_minute();
+
+                       if (t_bpm == tempo_bpm) {
+                               return t->pulse();
+                       }
+
+                       if (prev_t) {
+                               const double prev_t_bpm = prev_t->beats_per_minute();
+
+                               if ((t_bpm > tempo_bpm && prev_t_bpm < tempo_bpm) || (t_bpm < tempo_bpm && prev_t_bpm > tempo_bpm)) {
+                                       return prev_t->pulse_at_tempo (tempo_bpm, prev_t->minute());
+                               }
+                       }
+                       prev_t = t;
                }
        }
 
-       return prev_t.frame_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse(), _frame_rate);
+       return prev_t->pulse();
 }
 
-double
-TempoMap::beat_at_bbt (const Timecode::BBT_Time& bbt)
+/** Returns a Tempo corresponding to the supplied position in quarter-note beats.
+ * @param qn the position in quarter note beats.
+ * @return the Tempo at the supplied quarter-note.
+ */
+Tempo
+TempoMap::tempo_at_beat (const double& beat) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return beat_at_bbt_locked (_metrics, bbt);
-}
+       const MeterSection* prev_m = &meter_section_at_beat_locked (_metrics, beat);
+       const TempoSection* prev_t = &tempo_section_at_beat_locked (_metrics, beat);
 
+       return Tempo (prev_t->tempo_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse()), prev_t->note_type());
+}
 
+/** Returns the position in quarter-note beats corresponding to the supplied Tempo.
+ * @param tempo the tempo.
+ * @return the position in quarter-note beats where the map bpm
+ * is equal to that of the Tempo. currently ignores note_type.
+ */
 double
-TempoMap::beat_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
+TempoMap::beat_at_tempo (const Tempo& tempo) const
 {
-       /* CALLER HOLDS READ LOCK */
-
-       MeterSection* prev_m = 0;
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       const double pulse = pulse_at_tempo_locked (_metrics, tempo);
+
+       return beat_at_pulse_locked (_metrics, pulse);
+}
+
+/** Returns the whole-note pulse corresponding to the supplied  BBT (meter-based) beat.
+ * @param metrics the list of metric sections used to calculate the pulse.
+ * @param beat The BBT (meter-based) beat.
+ * @return the whole-note pulse at the supplied BBT (meter-based) beat.
+ *
+ * a pulse or whole note is the base musical position of a MetricSection.
+ * it is equivalent to four quarter notes.
+ *
+ */
+double
+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());
+}
+
+/** Returns the BBT (meter-based) beat corresponding to the supplied whole-note pulse .
+ * @param metrics the list of metric sections used to calculate the beat.
+ * @param pulse the whole-note pulse.
+ * @return the meter-based beat at the supplied whole-note pulse.
+ *
+ * a pulse or whole note is the base musical position of a MetricSection.
+ * it is equivalent to four quarter notes.
+ */
+double
+TempoMap::beat_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;
+               if (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
+                       if (prev_m && m->pulse() > pulse) {
+                               break;
+                       }
+                       prev_m = m;
+               }
+       }
+
+       double const ret = ((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat();
+       return ret;
+}
+
+/* tempo section based */
+double
+TempoMap::pulse_at_minute_locked (const Metrics& metrics, const double& minute) 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->minute() > minute) {
+                               /*the previous ts is the one containing the frame */
+                               const double ret = prev_t->pulse_at_minute (minute);
+                               /* audio locked section in new meter*/
+                               if (t->pulse() < ret) {
+                                       return t->pulse();
+                               }
+                               return ret;
+                       }
+                       prev_t = t;
+               }
+       }
+
+       /* treated as constant for this ts */
+       const double pulses_in_section = ((minute - prev_t->minute()) * prev_t->beats_per_minute()) / prev_t->note_type();
+
+       return pulses_in_section + prev_t->pulse();
+}
+
+/* tempo section based */
+double
+TempoMap::minute_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+{
+       /* HOLD THE READER LOCK */
+
+       const 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->pulse() > pulse) {
+                               return prev_t->minute_at_pulse (pulse);
+                       }
+
+                       prev_t = t;
+               }
+       }
+       /* must be treated as constant, irrespective of _type */
+       double const dtime = ((pulse - prev_t->pulse()) * prev_t->note_type()) / prev_t->beats_per_minute();
+
+       return dtime + prev_t->minute();
+}
+
+/** Returns the BBT (meter-based) beat corresponding to the supplied BBT time.
+ * @param bbt The BBT time (meter-based).
+ * @return bbt The BBT beat (meter-based) at the supplied BBT time.
+ *
+ */
+double
+TempoMap::beat_at_bbt (const Timecode::BBT_Time& bbt)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return beat_at_bbt_locked (_metrics, bbt);
+}
+
+
+double
+TempoMap::beat_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) {
-               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)) {
@@ -1614,11 +1905,16 @@ TempoMap::beat_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time&
        return ret;
 }
 
+/** Returns the BBT time corresponding to the supplied BBT (meter-based) beat.
+ * @param beat The BBT (meter-based) beat.
+ * @return The BBT time (meter-based) at the supplied meter-based beat.
+ *
+ */
 Timecode::BBT_Time
-TempoMap::bbt_at_beat (const double& beats)
+TempoMap::bbt_at_beat (const double& beat)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return bbt_at_beat_locked (_metrics, beats);
+       return bbt_at_beat_locked (_metrics, beat);
 }
 
 Timecode::BBT_Time
@@ -1628,10 +1924,11 @@ TempoMap::bbt_at_beat_locked (const Metrics& metrics, const double& b) const
        MeterSection* prev_m = 0;
        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*/
@@ -1672,12 +1969,24 @@ TempoMap::bbt_at_beat_locked (const Metrics& metrics, const double& b) const
        return ret;
 }
 
+/** Returns the quarter-note beat corresponding to the supplied BBT time (meter-based).
+ * @param bbt The BBT time (meter-based).
+ * @return the quarter note beat at the supplied BBT time
+ *
+ * quarter-notes ignore meter and are based on pulse (the musical unit of MetricSection).
+ *
+ * while the input uses meter, the output does not.
+ */
 double
-TempoMap::pulse_at_bbt (const Timecode::BBT_Time& bbt)
+TempoMap::quarter_note_at_bbt_rt (const Timecode::BBT_Time& bbt)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       return pulse_at_bbt_locked (_metrics, bbt);
+       if (!lm.locked()) {
+               throw std::logic_error ("TempoMap::quarter_note_at_bbt_rt() could not lock tempo map");
+       }
+
+       return pulse_at_bbt_locked (_metrics, bbt) * 4.0;
 }
 
 double
@@ -1690,9 +1999,11 @@ TempoMap::pulse_at_bbt_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) {
                                if (m->bbt().bars > bbt.bars) {
                                        break;
@@ -1704,28 +2015,31 @@ TempoMap::pulse_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time&
 
        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();
+       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::bbt_at_pulse (const double& pulse)
-{
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-
-       return bbt_at_pulse_locked (_metrics, pulse);
-}
-
+/** Returns the BBT time (meter-based) corresponding to the supplied whole-note pulse position.
+ * @param metrics The list of metric sections used to determine the result.
+ * @param pulse The whole-note pulse.
+ * @return The BBT time at the supplied whole-note pulse.
+ *
+ * a pulse or whole note is the basic musical position of a MetricSection.
+ * it is equivalent to four quarter notes.
+ * while the output uses meter, the input does not.
+ */
 Timecode::BBT_Time
 TempoMap::bbt_at_pulse_locked (const Metrics& metrics, const double& pulse) const
 {
        MeterSection* prev_m = 0;
 
+       MeterSection* m = 0;
+
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* m = 0;
 
-               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();
@@ -1768,25 +2082,116 @@ TempoMap::bbt_at_pulse_locked (const Metrics& metrics, const double& pulse) cons
        return ret;
 }
 
-void
-TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
+/** Returns the BBT time corresponding to the supplied frame position.
+ * @param frame the position in audio samples.
+ * @return the BBT time at the frame position .
+ *
+ */
+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 = bbt_at_beat_locked (_metrics, beat);
+       return bbt_at_minute_locked (_metrics, minute_at_frame (frame));
+}
+
+BBT_Time
+TempoMap::bbt_at_frame_rt (framepos_t frame)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
+
+       if (!lm.locked()) {
+               throw std::logic_error ("TempoMap::bbt_at_frame_rt() could not lock tempo map");
+       }
+
+       return bbt_at_minute_locked (_metrics, minute_at_frame (frame));
 }
 
+Timecode::BBT_Time
+TempoMap::bbt_at_minute_locked (const Metrics& metrics, const double& minute) const
+{
+       if (minute < 0) {
+               BBT_Time bbt;
+               bbt.bars = 1;
+               bbt.beats = 1;
+               bbt.ticks = 0;
+               return bbt;
+       }
+
+       const TempoSection& ts = tempo_section_at_minute_locked (metrics, minute);
+       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->minute() > minute) {
+                               next_m = m;
+                               break;
+                       }
+                       prev_m = m;
+               }
+       }
+
+       double beat = prev_m->beat() + (ts.pulse_at_minute (minute) - prev_m->pulse()) * prev_m->note_divisor();
+
+       /* handle frame before first meter */
+       if (minute < prev_m->minute()) {
+               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;
+       }
+
+       return ret;
+}
+
+/** Returns the frame position corresponding to the supplied BBT time.
+ * @param bbt the position in BBT time.
+ * @return the frame position at bbt.
+ *
+ */
 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;
@@ -1798,19 +2203,158 @@ TempoMap::frame_time (const BBT_Time& bbt)
        }
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       return frame_time_locked (_metrics, bbt);
+       return frame_at_minute (minute_at_bbt_locked (_metrics, bbt));
 }
 
-/* meter section based */
-framepos_t
-TempoMap::frame_time_locked (const Metrics& metrics, const BBT_Time& bbt) const
+/* meter & tempo section based */
+double
+TempoMap::minute_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));
+       const double ret = minute_at_beat_locked (metrics, beat_at_bbt_locked (metrics, bbt));
+       return ret;
+}
+
+/**
+ * Returns the quarter-note beat position corresponding to the supplied frame.
+ *
+ * @param frame the position in frames.
+ * @return The quarter-note position of the supplied frame. Ignores meter.
+ *
+*/
+double
+TempoMap::quarter_note_at_frame (const framepos_t frame)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       const double ret = quarter_note_at_minute_locked (_metrics, minute_at_frame (frame));
+
        return ret;
 }
 
+double
+TempoMap::quarter_note_at_minute_locked (const Metrics& metrics, const double minute) const
+{
+       const double ret = pulse_at_minute_locked (metrics, minute) * 4.0;
+
+       return ret;
+}
+
+double
+TempoMap::quarter_note_at_frame_rt (const framepos_t frame)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
+
+       if (!lm.locked()) {
+               throw std::logic_error ("TempoMap::quarter_note_at_frame_rt() could not lock tempo map");
+       }
+
+       const double ret = pulse_at_minute_locked (_metrics, minute_at_frame (frame)) * 4.0;
+
+       return ret;
+}
+
+/**
+ * Returns the frame position corresponding to the supplied quarter-note beat.
+ *
+ * @param quarter_note the quarter-note position.
+ * @return the frame position of the supplied quarter-note. Ignores meter.
+ *
+ *
+*/
+framepos_t
+TempoMap::frame_at_quarter_note (const double quarter_note)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       const framepos_t ret = frame_at_minute (minute_at_quarter_note_locked (_metrics, quarter_note));
+
+       return ret;
+}
+
+double
+TempoMap::minute_at_quarter_note_locked (const Metrics& metrics, const double quarter_note) const
+{
+       const double ret = minute_at_pulse_locked (metrics, quarter_note / 4.0);
+
+       return ret;
+}
+
+/** Returns the quarter-note beats corresponding to the supplied BBT (meter-based) beat.
+ * @param beat The BBT (meter-based) beat.
+ * @return The quarter-note position of the supplied BBT (meter-based) beat.
+ *
+ * a quarter-note may be compared with and assigned to Evoral::Beats.
+ *
+ */
+double
+TempoMap::quarter_note_at_beat (const double beat)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       const double ret = quarter_note_at_beat_locked (_metrics, beat);
+
+       return ret;
+}
+
+double
+TempoMap::quarter_note_at_beat_locked (const Metrics& metrics, const double beat) const
+{
+       const double ret = pulse_at_beat_locked (metrics, beat) * 4.0;
+
+       return ret;
+}
+
+/** Returns the BBT (meter-based) beat position corresponding to the supplied quarter-note beats.
+ * @param quarter_note The position in quarter-note beats.
+ * @return the BBT (meter-based) beat position of the supplied quarter-note beats.
+ *
+ * a quarter-note is the musical unit of Evoral::Beats.
+ *
+ */
+double
+TempoMap::beat_at_quarter_note (const double quarter_note)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       const double ret = beat_at_quarter_note_locked (_metrics, quarter_note);
+
+       return ret;
+}
+
+double
+TempoMap::beat_at_quarter_note_locked (const Metrics& metrics, const double quarter_note) const
+{
+
+       return beat_at_pulse_locked (metrics, quarter_note / 4.0);
+}
+
+/** Returns the duration in frames between two supplied quarter-note beat positions.
+ * @param start the first position in quarter-note beats.
+ * @param end the end position in quarter-note beats.
+ * @return the frame distance ober the quarter-note beats duration.
+ *
+ * use this rather than e.g.
+ * frame_at-quarter_note (end_beats) - frame_at_quarter_note (start_beats).
+ * frames_between_quarter_notes() doesn't round to audio frames as an intermediate step,
+ *
+ */
+framecnt_t
+TempoMap::frames_between_quarter_notes (const double start, const double end)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return frame_at_minute (minutes_between_quarter_notes_locked (_metrics, start, end));
+}
+
+double
+TempoMap::minutes_between_quarter_notes_locked (const Metrics& metrics, const double start, const double end)
+{
+
+       return minute_at_pulse_locked (metrics, end / 4.0) - minute_at_pulse_locked (metrics, start / 4.0);
+}
+
 bool
 TempoMap::check_solved (const Metrics& metrics) const
 {
@@ -1820,32 +2364,43 @@ TempoMap::check_solved (const Metrics& metrics) 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 ((t->frame() <= prev_t->frame()) || (t->pulse() <= prev_t->pulse())) {
+                               /* check ordering */
+                               if ((t->minute() <= prev_t->minute()) || (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() != frame_at_minute (prev_t->minute_at_tempo (t->beats_per_minute(), t->pulse()))) {
                                        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_minute_locked (metrics, minute_at_frame (m->frame() - 1));
+                               const double nascent_m_minute = t->minute_at_pulse (m->pulse());
+                               /* Here we check that a preceding section of music doesn't overlap a subsequent one.
+                               */
+                               if (t && (nascent_m_minute > m->minute() || nascent_m_minute < 0.0)) {
                                        return false;
                                }
                        }
@@ -1863,7 +2418,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;
@@ -1882,31 +2438,34 @@ TempoMap::set_active_tempos (const Metrics& metrics, const framepos_t& frame)
 }
 
 bool
-TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const framepos_t& frame)
+TempoMap::solve_map_minute (Metrics& imaginary, TempoSection* section, const double& minute)
 {
        TempoSection* prev_t = 0;
        TempoSection* section_prev = 0;
-       framepos_t first_m_frame = 0;
+       double first_m_minute = 0.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();
+                               first_m_minute = m->minute();
                                break;
                        }
                }
        }
-       if (section->movable() && frame <= first_m_frame) {
+       if (section->movable() && minute <= first_m_minute) {
                return false;
        }
 
        section->set_active (true);
-       section->set_frame (frame);
+       section->set_minute (minute);
 
        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;
@@ -1914,15 +2473,18 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram
                        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) {
-                                       prev_t->set_c_func (prev_t->compute_c_func_pulse (t->pulses_per_minute(), t->pulse(), _frame_rate));
-                                       t->set_frame (prev_t->frame_at_pulse (t->pulse(), _frame_rate));
+                                       prev_t->set_c_func (prev_t->compute_c_func_pulse (t->beats_per_minute(), t->pulse()));
+                                       t->set_minute (prev_t->minute_at_tempo (t->beats_per_minute(), t->pulse()));
                                } else {
-                                       prev_t->set_c_func (prev_t->compute_c_func_frame (t->pulses_per_minute(), t->frame(), _frame_rate));
+                                       prev_t->set_c_func (prev_t->compute_c_func_minute (t->beats_per_minute(), t->minute()));
                                        if (!t->locked_to_meter()) {
-                                               t->set_pulse (prev_t->pulse_at_frame (t->frame(), _frame_rate));
+                                               t->set_pulse (prev_t->pulse_at_tempo (t->beats_per_minute(), t->minute()));
                                        }
                                }
                        }
@@ -1931,22 +2493,26 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram
        }
 
        if (section_prev) {
-               section_prev->set_c_func (section_prev->compute_c_func_frame (section->pulses_per_minute(), frame, _frame_rate));
+               section_prev->set_c_func (section_prev->compute_c_func_minute (section->beats_per_minute(), minute));
                if (!section->locked_to_meter()) {
-                       section->set_pulse (section_prev->pulse_at_frame (frame, _frame_rate));
+                       section->set_pulse (section_prev->pulse_at_tempo (section->beats_per_minute(), minute));
                }
        }
 
-       recompute_tempos (imaginary);
+#if (0)
+       recompute_tempi (imaginary);
 
        if (check_solved (imaginary)) {
                return true;
+       } else {
+               dunp (imaginary, std::cout);
        }
+#endif
 
        MetricSectionFrameSorter fcmp;
        imaginary.sort (fcmp);
 
-       recompute_tempos (imaginary);
+       recompute_tempi (imaginary);
 
        if (check_solved (imaginary)) {
                return true;
@@ -1965,7 +2531,8 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub
 
        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;
                        }
@@ -1980,12 +2547,12 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub
                                        continue;
                                }
                                if (t->position_lock_style() == MusicTime) {
-                                       prev_t->set_c_func (prev_t->compute_c_func_pulse (t->pulses_per_minute(), t->pulse(), _frame_rate));
-                                       t->set_frame (prev_t->frame_at_pulse (t->pulse(), _frame_rate));
+                                       prev_t->set_c_func (prev_t->compute_c_func_pulse (t->beats_per_minute(), t->pulse()));
+                                       t->set_minute (prev_t->minute_at_tempo (t->beats_per_minute(), t->pulse()));
                                } else {
-                                       prev_t->set_c_func (prev_t->compute_c_func_frame (t->pulses_per_minute(), t->frame(), _frame_rate));
+                                       prev_t->set_c_func (prev_t->compute_c_func_minute (t->beats_per_minute(), t->minute()));
                                        if (!t->locked_to_meter()) {
-                                               t->set_pulse (prev_t->pulse_at_frame (t->frame(), _frame_rate));
+                                               t->set_pulse (prev_t->pulse_at_tempo (t->beats_per_minute(), t->minute()));
                                        }
                                }
                        }
@@ -1994,20 +2561,24 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub
        }
 
        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));
+               section_prev->set_c_func (section_prev->compute_c_func_pulse (section->beats_per_minute(), pulse));
+               section->set_minute (section_prev->minute_at_tempo (section->beats_per_minute(), pulse));
        }
 
-       recompute_tempos (imaginary);
+#if (0)
+       recompute_tempi (imaginary);
 
        if (check_solved (imaginary)) {
                return true;
+       } else {
+               dunp (imaginary, std::cout);
        }
+#endif
 
        MetricSectionSorter cmp;
        imaginary.sort (cmp);
 
-       recompute_tempos (imaginary);
+       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.
@@ -2026,29 +2597,28 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub
 }
 
 bool
-TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const framepos_t& frame)
+TempoMap::solve_map_minute (Metrics& imaginary, MeterSection* section, const double& minute)
 {
        /* 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);
-       if ((!section->movable() && other->movable()) || (!other->movable() && section->movable() && other->frame() >= frame)) {
+       const MeterSection* other =  &meter_section_at_minute_locked (imaginary, minute);
+       if ((!section->movable() && other->movable()) || (!other->movable() && section->movable() && other->minute() >= minute)) {
                return false;
        }
 
        if (!section->movable()) {
                /* lock the first tempo to our first meter */
-               if (!set_active_tempos (imaginary, frame)) {
+               if (!set_active_tempos (imaginary, section->frame_at_minute (minute))) {
                        return false;
                }
        }
 
-       /* 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 ((t->locked_to_meter() || !t->movable()) && t->frame() == section->frame()) {
+               if ((*ii)->is_tempo()) {
+                       t = static_cast<TempoSection*> (*ii);
+                       if ((t->locked_to_meter() || !t->movable()) && t->minute() == section->minute()) {
                                meter_locked_tempo = t;
                                break;
                        }
@@ -2066,24 +2636,28 @@ TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const fram
 
        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();
+                                       const double beats = (pulse_at_minute_locked (imaginary, minute) - prev_m->pulse()) * prev_m->note_divisor();
                                        if (beats + prev_m->beat() < section->beat()) {
-                                               /* set the frame/pulse corresponding to its musical position,
+                                               /* set the section pulse according 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();
 
-                                               const framepos_t smallest_frame = frame_at_pulse_locked (future_map, new_pulse);
-
-                                               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);
+                                               tempo_copy->set_position_lock_style (MusicTime);
+                                               if ((solved = solve_map_pulse (future_map, tempo_copy, new_pulse))) {
+                                                       meter_locked_tempo->set_position_lock_style (MusicTime);
+                                                       section->set_position_lock_style (MusicTime);
                                                        section->set_pulse (new_pulse);
+                                                       solve_map_pulse (imaginary, meter_locked_tempo, new_pulse);
+                                                       meter_locked_tempo->set_position_lock_style (AudioTime);
+                                                       section->set_position_lock_style (AudioTime);
+                                                       section->set_minute (meter_locked_tempo->minute());
+
                                                } else {
                                                        solved = false;
                                                }
@@ -2098,15 +2672,20 @@ TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const fram
                                                        return false;
                                                }
                                        } else {
-                                               /* all is ok. set section's tempo */
-                                               MeterSection* meter_copy = const_cast<MeterSection*> (&meter_section_at_locked (future_map, section->frame()));
-                                               meter_copy->set_frame (frame);
+                                               /* 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_minute_locked (future_map, section->minute()));
+
+                                               meter_copy->set_minute (minute);
 
-                                               if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
-                                                       section->set_frame (frame);
+                                               if ((solved = solve_map_minute (future_map, tempo_copy, minute))) {
+                                                       section->set_minute (minute);
                                                        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);
+                                                       solve_map_minute (imaginary, meter_locked_tempo, minute);
                                                } else {
                                                        solved = false;
                                                }
@@ -2124,14 +2703,14 @@ TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const fram
                                } else {
                                        /* not movable (first meter atm) */
 
-                                       tempo_copy->set_frame (frame);
+                                       tempo_copy->set_minute (minute);
                                        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);
+                                       if ((solved = solve_map_minute (future_map, tempo_copy, minute))) {
+                                               section->set_minute (minute);
+                                               meter_locked_tempo->set_minute (minute);
                                                meter_locked_tempo->set_pulse (0.0);
-                                               solve_map_frame (imaginary, meter_locked_tempo, frame);
+                                               solve_map_minute (imaginary, meter_locked_tempo, minute);
                                        } else {
                                                solved = false;
                                        }
@@ -2172,7 +2751,8 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti
        /* 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 (!(*i)->is_tempo()) {
+                       m = static_cast<MeterSection*> (*i);
                        if (m != section && m->bbt().bars == when.bars) {
                                return false;
                        }
@@ -2184,7 +2764,8 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti
 
        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;
 
@@ -2196,7 +2777,7 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti
 
                                section->set_beat (b_bbt);
                                section->set_pulse (pulse);
-                               section->set_frame (frame_at_pulse_locked (imaginary, pulse));
+                               section->set_minute (minute_at_pulse_locked (imaginary, pulse));
                                prev_m = section;
                                continue;
                        }
@@ -2206,7 +2787,8 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti
 
                                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;
@@ -2252,7 +2834,7 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti
                                new_pulse = (beats / prev_m->note_divisor()) + prev_m->pulse();
                                m->set_beat (b_bbt);
                                m->set_pulse (new_pulse);
-                               m->set_frame (frame_at_pulse_locked (imaginary, new_pulse));
+                               m->set_minute (minute_at_pulse_locked (imaginary, new_pulse));
                        }
 
                        prev_m = m;
@@ -2267,7 +2849,7 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti
 
                section->set_beat (b_bbt);
                section->set_pulse (pulse);
-               section->set_frame (frame_at_pulse_locked (imaginary, pulse));
+               section->set_minute (minute_at_pulse_locked (imaginary, pulse));
        }
 
        MetricSectionSorter cmp;
@@ -2289,7 +2871,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);
@@ -2299,7 +2882,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);
                }
@@ -2316,12 +2900,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);
@@ -2335,6 +2921,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 ts 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)
 {
@@ -2364,7 +2957,7 @@ TempoMap::can_solve_bbt (TempoSection* ts, const BBT_Time& bbt)
 * 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
+* @param bbt - the BBT time  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>
@@ -2395,19 +2988,43 @@ TempoMap::predict_tempo_position (TempoSection* section, const BBT_Time& bbt)
        return ret;
 }
 
+/** moves a TempoSection to a specified position.
+ * @param ts - the section to be moved
+ * @param frame - the new position in frames for the tempo
+ * @param sub_num - the snap division to use if using musical time.
+ *
+ * if sub_num is non-zero, the frame position is used to calculate an exact
+ * musical position.
+ * sub_num   | effect
+ * -1        | snap to bars (meter-based)
+ *  0        | no snap - use audio frame for musical position
+ *  1        | snap to meter-based (BBT) beat
+ * >1        | snap to quarter-note subdivision (i.e. 4 will snap to sixteenth notes)
+ *
+ * this follows the snap convention in the gui.
+ * if sub_num is zero, the musical position will be taken from the supplied frame.
+ */
 void
-TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame)
+TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int& sub_num)
 {
        Metrics future_map;
 
        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 pulse = pulse_at_frame_locked (future_map, frame);
-                       if (solve_map_pulse (future_map, tempo_copy, pulse)) {
-                               solve_map_pulse (_metrics, ts, pulse);
-                               recompute_meters (_metrics);
+
+                       tempo_copy->set_position_lock_style (AudioTime);
+
+                       if (solve_map_minute (future_map, tempo_copy, minute_at_frame (frame))) {
+                               const double beat = exact_beat_at_frame_locked (future_map, frame, sub_num);
+                               const 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);
+                               }
                        }
                }
 
@@ -2416,9 +3033,33 @@ TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame)
                {
                        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 (solve_map_minute (future_map, tempo_copy, minute_at_frame (frame))) {
+                               if (sub_num != 0) {
+                                       /* We're moving the object that defines the grid while snapping to it...
+                                        * Placing the ts at the beat corresponding to the requested frame may shift the
+                                        * grid in such a way that the mouse is left hovering over a completerly different division,
+                                        * causing jittering when the mouse next moves (esp. large tempo deltas).
+                                        * To avoid this, place the ts at the requested frame in a dummy map
+                                        * then find the closest beat subdivision to that frame in the dummy.
+                                        * This alters the snap behaviour slightly in that we snap to beat divisions
+                                        * in the future map rather than the existing one.
+                                        */
+                                       const double beat = exact_beat_at_frame_locked (future_map, frame, sub_num);
+                                       const double pulse = pulse_at_beat_locked (future_map, beat);
+
+                                       if (solve_map_pulse (future_map, tempo_copy, pulse)) {
+                                               /* snapping to a grid. force MusicTime temporarily. */
+                                               ts->set_position_lock_style (MusicTime);
+                                               solve_map_pulse (_metrics, ts, pulse);
+                                               ts->set_position_lock_style (AudioTime);
+
+                                               recompute_meters (_metrics);
+                                       }
+                               } else {
+                                       solve_map_minute (_metrics, ts, minute_at_frame (frame));
+                                       recompute_meters (_metrics);
+                               }
                        }
                }
        }
@@ -2432,6 +3073,14 @@ TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame)
        MetricPositionChanged (); // Emit Signal
 }
 
+/** moves a MeterSection to a specified position.
+ * @param ms - the section to be moved
+ * @param frame - the new position in frames for the meter
+ *
+ * as a meter cannot snap to anything but bars,
+ * the supplied frame is rounded to the nearest bar, possibly
+ * leaving the meter position unchanged.
+ */
 void
 TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame)
 {
@@ -2443,9 +3092,9 @@ TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame)
                        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_tempos (_metrics);
+                       if (solve_map_minute (future_map, copy, minute_at_frame (frame))) {
+                               solve_map_minute (_metrics, ms, minute_at_frame (frame));
+                               recompute_tempi (_metrics);
                        }
                }
        } else {
@@ -2453,12 +3102,12 @@ TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame)
                        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 double beat = beat_at_minute_locked (_metrics, minute_at_frame (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_tempos (_metrics);
+                               recompute_tempi (_metrics);
                        }
                }
        }
@@ -2481,7 +3130,7 @@ 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)) {
                        ts->set_beats_per_minute (bpm.beats_per_minute());
@@ -2502,14 +3151,14 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm)
 }
 
 void
-TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const framepos_t& end_frame, const double& pulse)
+TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const framepos_t& end_frame)
 {
        /*
          Ts (future prev_t)   Tnext
          |                    |
          |     [drag^]        |
          |----------|----------
-               e_f  pulse(frame)
+               e_f  qn_beats(frame)
        */
 
        Metrics future_map;
@@ -2526,46 +3175,54 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
                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_minute_locked (future_map, minute_at_frame (prev_t->frame() - 1)));
                }
 
                TempoSection* next_t = 0;
                for (Metrics::iterator i = future_map.begin(); i != future_map.end(); ++i) {
                        TempoSection* t = 0;
-                       if ((t = dynamic_cast<TempoSection*> (*i)) != 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;
-               double start_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
 
                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());
                }
 
-               frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
-               double end_pulse = prev_t->pulse_at_frame (end_frame, _frame_rate);
+               const frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
+
+               const double start_pulse = prev_t->pulse_at_minute (minute_at_frame (frame));
+               const double end_pulse = prev_t->pulse_at_minute (minute_at_frame (end_frame));
+
                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) {
+                                       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()));
-
+                                               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 */
 
-                                       if (start_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();
@@ -2574,12 +3231,17 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
                        } else {
                                /* AudioTime */
                                if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                                       new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame())
-                                                                               / (double) ((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 (end_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();
@@ -2588,52 +3250,196 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
                        }
                } else {
 
-                       double frame_ratio;
-                       double pulse_ratio;
-                       const framepos_t pulse_pos = prev_t->frame_at_pulse (pulse, _frame_rate);
+                       double frame_ratio = 1.0;
+                       double pulse_ratio = 1.0;
+                       const double pulse_pos = frame;
 
                        if (prev_to_prev_t) {
-
-                               frame_ratio = (((pulse_pos - fr_off) - prev_to_prev_t->frame()) / (double) ((pulse_pos) - prev_to_prev_t->frame()));
-                               pulse_ratio = ((start_pulse - prev_to_prev_t->pulse()) / (end_pulse - prev_to_prev_t->pulse()));
+                               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 {
-
-                               frame_ratio = (((pulse_pos - fr_off) - prev_t->frame()) / (double) ((pulse_pos) - 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)) {
-                       ts->set_beats_per_minute (new_bpm);
-                       recompute_tempos (_metrics);
-                       recompute_meters (_metrics);
-               }
-       }
+               if (check_solved (future_map)) {
+                       ts->set_beats_per_minute (new_bpm);
+                       recompute_tempi (_metrics);
+                       recompute_meters (_metrics);
+               }
+       }
+
+       Metrics::const_iterator d = future_map.begin();
+       while (d != future_map.end()) {
+               delete (*d);
+               ++d;
+       }
+
+       MetricPositionChanged (); // Emit Signal
+}
+
+/** Returns the exact bbt-based beat corresponding to the bar, beat or quarter note subdivision nearest to
+ * the supplied frame, possibly returning a negative value.
+ * @param frame  The session frame position.
+ * @param sub_num The subdivision to use when rounding the beat.
+ *                A value of -1 indicates rounding to BBT bar. 1 indicates rounding to BBT beats.
+ *                Positive integers indicate quarter note (non BBT) divisions.
+ *                0 indicates that the returned beat should not be rounded (equivalent to quarter_note_at_frame()).
+ * @return The beat position of the supplied frame.
+ *
+ * If the supplied frame lies before the first meter, the return will be negative,
+ * in which case the returned beat uses the first meter (for BBT subdivisions) and
+ * the continuation of the tempo curve (backwards).
+ *
+ * This function uses both tempo and meter.
+ */
+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 divisions)
+{
+       return beat_at_pulse_locked (_metrics, exact_qn_at_frame_locked (metrics, frame, divisions) / 4.0);
+}
+
+/** Returns the exact quarter note corresponding to the bar, beat or quarter note subdivision nearest to
+ * the supplied frame, possibly returning a negative value.
+ * Supplying a frame position with a non-zero sub_num is equivalent to supplying
+ * a quarter-note musical position without frame rounding (see below)
+ *
+ * @param frame  The session frame position.
+ * @param sub_num The subdivision to use when rounding the quarter note.
+ *                A value of -1 indicates rounding to BBT bar. 1 indicates rounding to BBT beats.
+ *                Positive integers indicate quarter note (non BBT) divisions.
+ *                0 indicates that the returned quarter note should not be rounded (equivalent to quarter_note_at_frame()).
+ * @return The quarter note position of the supplied frame.
+ *
+ * If the supplied frame lies before the first meter, the return will be negative,
+ * in which case the returned quarter note uses the first meter (for BBT subdivisions) and
+ * the continuation of the tempo curve (backwards).
+ *
+ * This function uses both tempo and meter.
+ */
+double
+TempoMap::exact_qn_at_frame (const framepos_t& frame, const int32_t sub_num)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return exact_qn_at_frame_locked (_metrics, frame, sub_num);
+}
+
+double
+TempoMap::exact_qn_at_frame_locked (const Metrics& metrics, const framepos_t& frame, const int32_t sub_num)
+{
+       double qn = quarter_note_at_minute_locked (metrics, minute_at_frame (frame));
+
+       if (sub_num > 1) {
+               qn = floor (qn) + (floor (((qn - floor (qn)) * (double) sub_num) + 0.5) / sub_num);
+       } else if (sub_num == 1) {
+               /* the gui requested exact musical (BBT) beat */
+               qn = quarter_note_at_beat_locked (metrics, floor (beat_at_minute_locked (metrics, minute_at_frame (frame)) + 0.5));
+       } else if (sub_num == -1) {
+               /* snap to  bar */
+               Timecode::BBT_Time bbt = bbt_at_pulse_locked (metrics, qn / 4.0);
+               bbt.beats = 1;
+               bbt.ticks = 0;
+
+               const double prev_b = pulse_at_bbt_locked (metrics, bbt) * 4.0;
+               ++bbt.bars;
+               const double next_b = pulse_at_bbt_locked (metrics, bbt) * 4.0;
 
-       Metrics::const_iterator d = future_map.begin();
-       while (d != future_map.end()) {
-               delete (*d);
-               ++d;
+               if ((qn - prev_b) > (next_b - prev_b) / 2.0) {
+                       qn = next_b;
+               } else {
+                       qn = prev_b;
+               }
        }
 
-       MetricPositionChanged (); // Emit Signal
+       return qn;
 }
 
+/** returns the frame duration of the supplied BBT time at a specified frame position in the tempo map.
+ * @param pos the frame position in the tempo map.
+ * @param bbt the distance in BBT time from pos to calculate.
+ * @param dir the rounding direction..
+ * @return the duration in frames between pos and bbt
+*/
 framecnt_t
 TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       const double tick_at_time = beat_at_frame_locked (_metrics, pos) * BBT_Time::ticks_per_beat;
-       const double bbt_ticks = bbt.ticks + (bbt.beats * BBT_Time::ticks_per_beat);
-       const double total_beats = (tick_at_time + bbt_ticks) / BBT_Time::ticks_per_beat;
+       BBT_Time pos_bbt = bbt_at_minute_locked (_metrics, minute_at_frame (pos));
+       const framecnt_t offset = frame_at_minute (minute_at_bbt_locked (_metrics, pos_bbt));
+       const double divisions = meter_section_at_minute_locked (_metrics, minute_at_frame (pos)).divisions_per_bar();
+
+       if (dir > 0) {
+               pos_bbt.bars += bbt.bars;
+
+               pos_bbt.ticks += bbt.ticks;
+               if ((double) pos_bbt.ticks > BBT_Time::ticks_per_beat) {
+                       pos_bbt.beats += 1;
+                       pos_bbt.ticks -= BBT_Time::ticks_per_beat;
+               }
+
+               pos_bbt.beats += bbt.beats;
+               if ((double) pos_bbt.beats > divisions) {
+                       pos_bbt.bars += 1;
+                       pos_bbt.beats -= divisions;
+               }
+
+               return frame_at_minute (minute_at_bbt_locked (_metrics, pos_bbt)) - offset;
+       } else {
+               pos_bbt.bars -= bbt.bars;
+
+               if (pos_bbt.ticks < bbt.ticks) {
+                       if (pos_bbt.beats == 1) {
+                               pos_bbt.bars--;
+                               pos_bbt.beats = divisions;
+                       } else {
+                               pos_bbt.beats--;
+                       }
+                       pos_bbt.ticks = BBT_Time::ticks_per_beat - (bbt.ticks - pos_bbt.ticks);
+               } else {
+                       pos_bbt.ticks -= bbt.ticks;
+               }
+
+               if (pos_bbt.beats <= bbt.beats) {
+                       pos_bbt.bars--;
+                       pos_bbt.beats = divisions - (bbt.beats - pos_bbt.beats);
+               } else {
+                       pos_bbt.beats -= bbt.beats;
+               }
+
+               return offset - frame_at_minute (minute_at_bbt_locked (_metrics, pos_bbt));
+       }
 
-       return frame_at_beat_locked (_metrics, total_beats);
+       return 0;
 }
 
 framepos_t
@@ -2652,7 +3458,103 @@ framepos_t
 TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       uint32_t ticks = (uint32_t) floor (beat_at_frame_locked (_metrics, fr) * BBT_Time::ticks_per_beat);
+       uint32_t ticks = (uint32_t) floor (max (0.0, beat_at_minute_locked (_metrics, minute_at_frame (fr))) * BBT_Time::ticks_per_beat);
+       uint32_t beats = (uint32_t) floor (ticks / BBT_Time::ticks_per_beat);
+       uint32_t ticks_one_subdivisions_worth = (uint32_t) BBT_Time::ticks_per_beat / sub_num;
+
+       ticks -= beats * BBT_Time::ticks_per_beat;
+
+       if (dir > 0) {
+               /* round to next (or same iff dir == RoundUpMaybe) */
+
+               uint32_t mod = 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 */
+                       ticks += ticks_one_subdivisions_worth;
+
+               } else {
+                       /* not on subdivision, compute distance to next subdivision */
+
+                       ticks += ticks_one_subdivisions_worth - mod;
+               }
+
+               if (ticks >= BBT_Time::ticks_per_beat) {
+                       ticks -= BBT_Time::ticks_per_beat;
+               }
+       } else if (dir < 0) {
+
+               /* round to previous (or same iff dir == RoundDownMaybe) */
+
+               uint32_t difference = 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 (ticks < difference) {
+                       ticks = BBT_Time::ticks_per_beat - ticks;
+               } else {
+                       ticks -= difference;
+               }
+
+       } else {
+               /* round to nearest */
+               double rem;
+
+               /* compute the distance to the previous and next subdivision */
+
+               if ((rem = fmod ((double) ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) {
+
+                       /* closer to the next subdivision, so shift forward */
+
+                       ticks = lrint (ticks + (ticks_one_subdivisions_worth - rem));
+
+                       DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", ticks));
+
+                       if (ticks > BBT_Time::ticks_per_beat) {
+                               ++beats;
+                               ticks -= BBT_Time::ticks_per_beat;
+                               DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", beats));
+                       }
+
+               } else if (rem > 0) {
+
+                       /* closer to previous subdivision, so shift backward */
+
+                       if (rem > ticks) {
+                               if (beats == 0) {
+                                       /* can't go backwards past zero, so ... */
+                                       return 0;
+                               }
+                               /* step back to previous beat */
+                               --beats;
+                               ticks = lrint (BBT_Time::ticks_per_beat - rem);
+                               DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", beats));
+                       } else {
+                               ticks = lrint (ticks - rem);
+                               DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", ticks));
+                       }
+               } else {
+                       /* on the subdivision, do nothing */
+               }
+       }
+
+       const framepos_t ret_frame = frame_at_minute (minute_at_beat_locked (_metrics, beats + (ticks / BBT_Time::ticks_per_beat)));
+
+       return ret_frame;
+}
+
+framepos_t
+TempoMap::round_to_quarter_note_subdivision (framepos_t fr, int sub_num, RoundMode dir)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       uint32_t ticks = (uint32_t) floor (max (0.0, quarter_note_at_minute_locked (_metrics, minute_at_frame (fr))) * BBT_Time::ticks_per_beat);
        uint32_t beats = (uint32_t) floor (ticks / BBT_Time::ticks_per_beat);
        uint32_t ticks_one_subdivisions_worth = (uint32_t) BBT_Time::ticks_per_beat / sub_num;
 
@@ -2739,7 +3641,7 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
                }
        }
 
-       const framepos_t ret_frame = frame_at_beat_locked (_metrics, beats + (ticks / BBT_Time::ticks_per_beat));
+       const framepos_t ret_frame = frame_at_minute (minute_at_quarter_note_locked (_metrics, beats + (ticks / BBT_Time::ticks_per_beat)));
 
        return ret_frame;
 }
@@ -2749,7 +3651,7 @@ 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);
+       const double beat_at_framepos = max (0.0, beat_at_minute_locked (_metrics, minute_at_frame (frame)));
        BBT_Time bbt (bbt_at_beat_locked (_metrics, beat_at_framepos));
 
        switch (type) {
@@ -2758,22 +3660,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_minute (minute_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_minute (minute_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_minute (minute_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_minute (minute_at_bbt_locked (_metrics, bbt));
                        ++bbt.bars;
-                       framepos_t next_ft = frame_time_locked (_metrics, bbt);
+                       framepos_t next_ft = frame_at_minute (minute_at_bbt_locked (_metrics, bbt));
 
                        if ((raw_ft - prev_ft) > (next_ft - prev_ft) / 2) { 
                                return next_ft;
@@ -2786,11 +3688,11 @@ TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
 
        case Beat:
                if (dir < 0) {
-                       return frame_at_beat_locked (_metrics, floor (beat_at_framepos));
+                       return frame_at_minute (minute_at_beat_locked (_metrics, floor (beat_at_framepos)));
                } else if (dir > 0) {
-                       return frame_at_beat_locked (_metrics, ceil (beat_at_framepos));
+                       return frame_at_minute (minute_at_beat_locked (_metrics, ceil (beat_at_framepos)));
                } else {
-                       return frame_at_beat_locked (_metrics, floor (beat_at_framepos + 0.5));
+                       return frame_at_minute (minute_at_beat_locked (_metrics, floor (beat_at_framepos + 0.5)));
                }
                break;
        }
@@ -2800,46 +3702,71 @@ TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
 
 void
 TempoMap::get_grid (vector<TempoMap::BBTPoint>& points,
-                   framepos_t lower, framepos_t upper)
+                   framepos_t lower, framepos_t upper, uint32_t bar_mod)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       int32_t cnt = ceil (beat_at_frame_locked (_metrics, lower));
+       int32_t cnt = ceil (beat_at_minute_locked (_metrics, minute_at_frame (lower)));
        framecnt_t pos = 0;
        /* although the map handles negative beats, bbt doesn't. */
        if (cnt < 0.0) {
                cnt = 0.0;
        }
-       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 = bbt_at_beat_locked (_metrics, cnt);
-               points.push_back (BBTPoint (meter, tempo_at_locked (_metrics, pos), pos, bbt.bars, bbt.beats, tempo.c_func()));
-               ++cnt;
+
+       if (minute_at_beat_locked (_metrics, cnt) >= minute_at_frame (upper)) {
+               return;
+       }
+       if (bar_mod == 0) {
+               while (pos >= 0 && pos < upper) {
+                       pos = frame_at_minute (minute_at_beat_locked (_metrics, cnt));
+                       const TempoSection tempo = tempo_section_at_minute_locked (_metrics, minute_at_frame (pos));
+                       const MeterSection meter = meter_section_at_minute_locked (_metrics, minute_at_frame (pos));
+                       const BBT_Time bbt = bbt_at_beat_locked (_metrics, cnt);
+                       points.push_back (BBTPoint (meter, tempo_at_minute_locked (_metrics, minute_at_frame (pos)), pos, bbt.bars, bbt.beats, tempo.c_func()));
+                       ++cnt;
+               }
+       } else {
+               BBT_Time bbt = bbt_at_minute_locked (_metrics, minute_at_frame (lower));
+               bbt.beats = 1;
+               bbt.ticks = 0;
+
+               if (bar_mod != 1) {
+                       bbt.bars -= bbt.bars % bar_mod;
+                       ++bbt.bars;
+               }
+
+               while (pos >= 0 && pos < upper) {
+                       pos = frame_at_minute (minute_at_bbt_locked (_metrics, bbt));
+                       const TempoSection tempo = tempo_section_at_minute_locked (_metrics, minute_at_frame (pos));
+                       const MeterSection meter = meter_section_at_minute_locked (_metrics, minute_at_frame (pos));
+                       points.push_back (BBTPoint (meter, tempo_at_minute_locked (_metrics, minute_at_frame (pos)), pos, bbt.bars, bbt.beats, tempo.c_func()));
+                       bbt.bars += bar_mod;
+               }
        }
 }
 
 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_minute_locked (_metrics, minute_at_frame (frame));
 }
 
 const TempoSection&
-TempoMap::tempo_section_at_locked (const Metrics& metrics, framepos_t frame) const
+TempoMap::tempo_section_at_minute_locked (const Metrics& metrics, double minute) 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;
                        }
-                       if (prev && t->frame() > frame) {
+                       if (prev && t->minute() > minute) {
                                break;
                        }
 
@@ -2861,28 +3788,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;
@@ -2900,79 +3811,54 @@ 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_minute (minute_at_frame (frame));
        }
        /* 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
+const MeterSection&
+TempoMap::meter_section_at_frame (framepos_t frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return tempo_at_locked (_metrics, frame);
+       return meter_section_at_minute_locked (_metrics, minute_at_frame (frame));
 }
 
 const MeterSection&
-TempoMap::meter_section_at_locked (const Metrics& metrics, framepos_t frame) const
+TempoMap::meter_section_at_minute_locked (const Metrics& metrics, double minute) 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) {
+                       if (prev && (*i)->minute() > minute) {
                                break;
                        }
 
@@ -2988,14 +3874,6 @@ TempoMap::meter_section_at_locked (const Metrics& metrics, framepos_t frame) con
        return *prev;
 }
 
-
-const MeterSection&
-TempoMap::meter_section_at (framepos_t frame) const
-{
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-       return meter_section_at_locked (_metrics, frame);
-}
-
 const MeterSection&
 TempoMap::meter_section_at_beat_locked (const Metrics& metrics, const double& beat) const
 {
@@ -3003,7 +3881,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;
                        }
@@ -3022,7 +3901,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();
@@ -3043,7 +3922,7 @@ TempoMap::fix_legacy_session ()
                                pair<double, BBT_Time> bbt = make_pair (0.0, BBT_Time (1, 1, 0));
                                m->set_beat (bbt);
                                m->set_pulse (0.0);
-                               m->set_frame (0);
+                               m->set_minute (0.0);
                                m->set_position_lock_style (AudioTime);
                                prev_m = m;
                                continue;
@@ -3068,7 +3947,7 @@ TempoMap::fix_legacy_session ()
 
                        if (!t->movable()) {
                                t->set_pulse (0.0);
-                               t->set_frame (0);
+                               t->set_minute (0.0);
                                t->set_position_lock_style (AudioTime);
                                prev_t = t;
                                continue;
@@ -3125,26 +4004,28 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                        if (child->name() == TempoSection::xml_state_node_name) {
 
                                try {
-                                       TempoSection* ts = new TempoSection (*child);
+                                       TempoSection* ts = new TempoSection (*child, _frame_rate);
                                        _metrics.push_back (ts);
                                }
 
                                catch (failed_constructor& err){
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
                                        _metrics = old_metrics;
+                                       old_metrics.clear();
                                        break;
                                }
 
                        } else if (child->name() == MeterSection::xml_state_node_name) {
 
                                try {
-                                       MeterSection* ms = new MeterSection (*child);
+                                       MeterSection* ms = new MeterSection (*child, _frame_rate);
                                        _metrics.push_back (ms);
                                }
 
                                catch (failed_constructor& err) {
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
                                        _metrics = old_metrics;
+                                       old_metrics.clear();
                                        break;
                                }
                        }
@@ -3196,6 +4077,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 ());
@@ -3214,17 +4102,25 @@ TempoMap::dump (const Metrics& metrics, std::ostream& o) const
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
                if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                       o << "Tempo @ " << *i << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->pulse() << " frame= " << t->frame() << " (movable? "
-                         << t->movable() << ')' << " pos lock: " << enum_2_string (t->position_lock_style()) << std::endl;
-                       o << "current      : " << t->beats_per_minute() << " | " << t->pulse() << " | " << t->frame() << std::endl;
+                       o << "Tempo @ " << *i << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type()
+                         << " type= " << enum_2_string (t->type()) << ") "  << " at pulse= " << t->pulse()
+                         << " minute= " << t->minute() << " frame= " << t->frame() << " (movable? " << t->movable() << ')'
+                         << " pos lock: " << enum_2_string (t->position_lock_style()) << std::endl;
                        if (prev_t) {
-                               o << "previous     : " << prev_t->beats_per_minute() << " | " << prev_t->pulse() << " | " << prev_t->frame() << std::endl;
-                               o << "calculated   : " << prev_t->tempo_at_pulse (t->pulse()) *  prev_t->note_type() << " | " << prev_t->pulse_at_tempo (t->pulses_per_minute(), t->frame(), _frame_rate) <<  " | " << prev_t->frame_at_tempo (t->pulses_per_minute(), t->pulse(), _frame_rate) << std::endl;
+                               o << std::setprecision (17) << "  current      : " << t->beats_per_minute()
+                                 << " | " << t->pulse() << " | " << t->frame() << " | " << t->minute() << std::endl;
+                               o << "  previous     : " << prev_t->beats_per_minute()
+                                 << " | " << prev_t->pulse() << " | " << prev_t->frame() << " | " << prev_t->minute() << std::endl;
+                               o << "  calculated   : " << prev_t->tempo_at_pulse (t->pulse())
+                                 << " | " << prev_t->pulse_at_tempo (t->beats_per_minute(), t->minute())
+                                 << " | " << frame_at_minute (prev_t->minute_at_tempo (t->beats_per_minute(), t->pulse()))
+                                 << " | " << prev_t->minute_at_tempo (t->beats_per_minute(), t->pulse()) << std::endl;
                        }
                        prev_t = t;
                } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                       o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->bbt() << " frame= " << m->frame()
-                         << " pulse: " << m->pulse() <<  " beat : " << m->beat() << " pos lock: " << enum_2_string (m->position_lock_style()) << " (movable? " << m->movable() << ')' << endl;
+                       o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->bbt()
+                         << " frame= " << m->frame() << " pulse: " << m->pulse() <<  " beat : " << m->beat()
+                         << " pos lock: " << enum_2_string (m->position_lock_style()) << " (movable? " << m->movable() << ')' << endl;
                }
        }
        o << "------" << std::endl;
@@ -3237,7 +4133,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++;
                }
        }
@@ -3252,7 +4148,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++;
                }
        }
@@ -3263,129 +4159,24 @@ TempoMap::n_meters() const
 void
 TempoMap::insert_time (framepos_t where, framecnt_t amount)
 {
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
-                       if ((*i)->frame() >= where && (*i)->movable ()) {
-                               (*i)->set_frame ((*i)->frame() + amount);
-                       }
-               }
-
-               /* now reset the BBT time of all metrics, based on their new
-                * audio time. This is the only place where we do this reverse
-                * timestamp.
-                */
-
-               Metrics::iterator i;
-               const MeterSection* meter;
-               const TempoSection* tempo;
-               MeterSection *m;
-               TempoSection *t;
-
-               meter = &first_meter ();
-               tempo = &first_tempo ();
-
-               BBT_Time start;
-               BBT_Time end;
-
-               // cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl;
-
-               bool first = true;
-               MetricSection* prev = 0;
-
-               for (i = _metrics.begin(); i != _metrics.end(); ++i) {
-
-                       BBT_Time bbt;
-                       //TempoMetric metric (*meter, *tempo);
-                       MeterSection* ms = const_cast<MeterSection*>(meter);
-                       TempoSection* ts = const_cast<TempoSection*>(tempo);
-                       if (prev) {
-                               if (ts){
-                                       if ((t = dynamic_cast<TempoSection*>(prev)) != 0) {
-                                               if (!t->active()) {
-                                                       continue;
-                                               }
-                                               ts->set_pulse (t->pulse());
-                                       }
-                                       if ((m = dynamic_cast<MeterSection*>(prev)) != 0) {
-                                               ts->set_pulse (m->pulse());
-                                       }
-                                       ts->set_frame (prev->frame());
+       for (Metrics::reverse_iterator i = _metrics.rbegin(); i != _metrics.rend(); ++i) {
+               if ((*i)->frame() >= where && (*i)->movable ()) {
+                       MeterSection* ms;
+                       TempoSection* ts;
 
-                               }
-                               if (ms) {
-                                       if ((m = dynamic_cast<MeterSection*>(prev)) != 0) {
-                                               pair<double, BBT_Time> start = make_pair (m->beat(), m->bbt());
-                                               ms->set_beat (start);
-                                               ms->set_pulse (m->pulse());
-                                       }
-                                       if ((t = dynamic_cast<TempoSection*>(prev)) != 0) {
-                                               if (!t->active()) {
-                                                       continue;
-                                               }
-                                               const double beat = beat_at_pulse_locked (_metrics, t->pulse());
-                                               pair<double, BBT_Time> start = make_pair (beat, bbt_at_beat_locked (_metrics, beat));
-                                               ms->set_beat (start);
-                                               ms->set_pulse (t->pulse());
-                                       }
-                                       ms->set_frame (prev->frame());
-                               }
-
-                       } else {
-                               // metric will be at frames=0 bbt=1|1|0 by default
-                               // which is correct for our purpose
+                       if ((ms = dynamic_cast <MeterSection*>(*i)) != 0) {
+                               gui_move_meter (ms, (*i)->frame() + amount);
                        }
 
-                       // cerr << bbt << endl;
-
-                       if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
-                               if (!t->active()) {
-                                       continue;
-                               }
-                               t->set_pulse (pulse_at_frame_locked (_metrics, m->frame()));
-                               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);
-
-                               // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
-
-                               if (first) {
-                                       first = false;
-                               } else {
-
-                                       if (bbt.ticks > BBT_Time::ticks_per_beat/2) {
-                                               /* round up to next beat */
-                                               bbt.beats += 1;
-                                       }
-
-                                       bbt.ticks = 0;
-
-                                       if (bbt.beats != 1) {
-                                               /* round up to next bar */
-                                               bbt.bars += 1;
-                                               bbt.beats = 1;
-                                       }
-                               }
-                               pair<double, BBT_Time> start = make_pair (beat_at_frame_locked (_metrics, m->frame()), bbt);
-                               m->set_beat (start);
-                               m->set_pulse (pulse_at_frame_locked (_metrics, m->frame()));
-                               meter = m;
-                               // cerr << "NEW METER, frame = " << (*i)->frame() << " beat = " << (*i)->pulse() <<endl;
-                       } else {
-                               fatal << _("programming error: unhandled MetricSection type") << endmsg;
-                               abort(); /*NOTREACHED*/
+                       if ((ts = dynamic_cast <TempoSection*>(*i)) != 0) {
+                               gui_move_tempo (ts, (*i)->frame() + amount, 0);
                        }
-
-                       prev = (*i);
                }
-
-               recompute_map (_metrics);
        }
 
-
        PropertyChanged (PropertyChange ());
 }
+
 bool
 TempoMap::remove_time (framepos_t where, framecnt_t amount)
 {
@@ -3411,7 +4202,7 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount)
                        }
                        else if ((*i)->frame() >= where) {
                                // TODO: make sure that moved tempo/meter markers are rounded to beat/bar boundaries
-                               (*i)->set_frame ((*i)->frame() - amount);
+                               (*i)->set_minute ((*i)->minute() - minute_at_frame (amount));
                                if ((*i)->frame() == where) {
                                        // marker was immediately after end of range
                                        tempo_after = dynamic_cast<TempoSection*> (*i);
@@ -3424,12 +4215,12 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount)
                //find the last TEMPO and METER metric (if any) and move it to the cut point so future stuff is correct
                if (last_tempo && !tempo_after) {
                        metric_kill_list.remove(last_tempo);
-                       last_tempo->set_frame(where);
+                       last_tempo->set_minute (minute_at_frame (where));
                        moved = true;
                }
                if (last_meter && !meter_after) {
                        metric_kill_list.remove(last_meter);
-                       last_meter->set_frame(where);
+                       last_meter->set_minute (minute_at_frame (where));
                        moved = true;
                }
 
@@ -3447,29 +4238,23 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount)
        return moved;
 }
 
-/** Add some (fractional) beats to a session frame position, and return the result in frames.
+/** Add some (fractional) Beats to a session frame position, and return the result in frames.
  *  pos can be -ve, if required.
  */
 framepos_t
-TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const
+TempoMap::framepos_plus_qn (framepos_t frame, Evoral::Beats quarter_note) const
 {
-       return frame_at_beat (beat_at_frame (pos) + beats.to_double());
-}
+       Glib::Threads::RWLock::ReaderLock lm (lock);
 
-/** 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());
+       return frame_at_minute (minute_at_quarter_note_locked (_metrics, quarter_note_at_minute_locked (_metrics, minute_at_frame (frame)) + quarter_note.to_double()));
 }
 
-/** Add the BBT interval op to pos and return the result */
 framepos_t
 TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       BBT_Time pos_bbt = bbt_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos));
+       BBT_Time pos_bbt = bbt_at_beat_locked (_metrics, beat_at_minute_locked (_metrics, minute_at_frame (pos)));
        pos_bbt.ticks += op.ticks;
        if (pos_bbt.ticks >= BBT_Time::ticks_per_beat) {
                ++pos_bbt.beats;
@@ -3485,16 +4270,18 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
        }
        pos_bbt.bars += op.bars;
 
-       return frame_time_locked (_metrics, pos_bbt);
+       return frame_at_minute (minute_at_bbt_locked (_metrics, pos_bbt));
 }
 
 /** Count the number of beats that are equivalent to distance when going forward,
     starting at pos.
 */
 Evoral::Beats
-TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
+TempoMap::framewalk_to_qn (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 (quarter_note_at_minute_locked (_metrics, minute_at_frame (pos + distance)) - quarter_note_at_minute_locked (_metrics, minute_at_frame (pos)));
 }
 
 struct bbtcmp {