Set tempo sections to the exact pulse when snapped to a musical grid.
[ardour.git] / libs / ardour / tempo.cc
index ef7c7d0239ce69d6893cfd1f0efc7d0036ce1288..15323751b41f59ac1db61eda324dbb3d6cc6bed4 100644 (file)
 #include <unistd.h>
 
 #include <glibmm/threads.h>
+
+#include "pbd/enumwriter.h"
 #include "pbd/xml++.h"
 #include "evoral/Beats.hpp"
+
 #include "ardour/debug.h"
 #include "ardour/lmath.h"
 #include "ardour/tempo.h"
@@ -70,26 +73,47 @@ 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 (BBT_Time()), Tempo (TempoMap::default_tempo())
+       : MetricSection (0.0, 0, MusicTime)
+       , Tempo (TempoMap::default_tempo())
+       , _c_func (0.0)
+       , _active (true)
+       , _locked_to_meter (false)
 {
        XMLProperty const * prop;
-       BBT_Time start;
        LocaleGuard lg;
+       BBT_Time bbt;
+       double pulse;
+       uint32_t frame;
 
-       if ((prop = node.property ("start")) == 0) {
-               error << _("TempoSection XML node has no \"start\" property") << endmsg;
-               throw failed_constructor();
+       _legacy_bbt = BBT_Time (0, 0, 0);
+
+       if ((prop = node.property ("start")) != 0) {
+               if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
+                           &bbt.bars,
+                           &bbt.beats,
+                           &bbt.ticks) == 3) {
+                       /* legacy session - start used to be in bbt*/
+                       _legacy_bbt = bbt;
+                       pulse = -1.0;
+                       info << _("Legacy session detected. TempoSection XML node will be altered.") << endmsg;
+               }
        }
 
-       if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
-                   &start.bars,
-                   &start.beats,
-                   &start.ticks) < 3) {
-               error << _("TempoSection XML node has an illegal \"start\" value") << endmsg;
-               throw failed_constructor();
+       if ((prop = node.property ("pulse")) != 0) {
+               if (sscanf (prop->value().c_str(), "%lf", &pulse) != 1) {
+                       error << _("TempoSection XML node has an illegal \"pulse\" value") << endmsg;
+               }
        }
 
-       set_start (start);
+       set_pulse (pulse);
+
+       if ((prop = node.property ("frame")) != 0) {
+               if (sscanf (prop->value().c_str(), "%" PRIu32, &frame) != 1) {
+                       error << _("TempoSection XML node has an illegal \"frame\" value") << endmsg;
+               } else {
+                       set_frame (frame);
+               }
+       }
 
        if ((prop = node.property ("beats-per-minute")) == 0) {
                error << _("TempoSection XML node has no \"beats-per-minute\" property") << endmsg;
@@ -118,23 +142,33 @@ TempoSection::TempoSection (const XMLNode& node)
 
        set_movable (string_is_affirmative (prop->value()));
 
-       if ((prop = node.property ("bar-offset")) == 0) {
-               _bar_offset = -1.0;
+       if ((prop = node.property ("active")) == 0) {
+               warning << _("TempoSection XML node has no \"active\" property") << endmsg;
+               set_active(true);
        } else {
-               if (sscanf (prop->value().c_str(), "%lf", &_bar_offset) != 1 || _bar_offset < 0.0) {
-                       error << _("TempoSection XML node has an illegal \"bar-offset\" value") << endmsg;
-                       throw failed_constructor();
-               }
+               set_active (string_is_affirmative (prop->value()));
        }
 
        if ((prop = node.property ("tempo-type")) == 0) {
-               _type = Type::Constant;
+               _type = Constant;
        } else {
-               if (strstr(prop->value().c_str(),"Constant")) {
-                       _type = Type::Constant;
+               _type = Type (string_2_enum (prop->value(), _type));
+       }
+
+       if ((prop = node.property ("lock-style")) == 0) {
+               if (movable()) {
+                       set_position_lock_style (MusicTime);
                } else {
-                       _type = Type::Ramp;
+                       set_position_lock_style (AudioTime);
                }
+       } else {
+               set_position_lock_style (PositionLockStyle (string_2_enum (prop->value(), position_lock_style())));
+       }
+
+       if ((prop = node.property ("locked-to-meter")) == 0) {
+               set_locked_to_meter (false);
+       } else {
+               set_locked_to_meter (string_is_affirmative (prop->value()));
        }
 }
 
@@ -145,195 +179,273 @@ TempoSection::get_state() const
        char buf[256];
        LocaleGuard lg;
 
-       snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
-                 start().bars,
-                 start().beats,
-                 start().ticks);
-       root->add_property ("start", buf);
+       snprintf (buf, sizeof (buf), "%f", 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);
        root->add_property ("beats-per-minute", buf);
        snprintf (buf, sizeof (buf), "%f", _note_type);
        root->add_property ("note-type", buf);
-       // snprintf (buf, sizeof (buf), "%f", _bar_offset);
-       // root->add_property ("bar-offset", buf);
        snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
        root->add_property ("movable", buf);
-
-       snprintf (buf, sizeof (buf), "%s", _type == Constant?"Constant":"Ramp");
-       root->add_property ("tempo-type", buf);
+       snprintf (buf, sizeof (buf), "%s", active()?"yes":"no");
+       root->add_property ("active", buf);
+       root->add_property ("tempo-type", enum_2_string (_type));
+       root->add_property ("lock-style", enum_2_string (position_lock_style()));
+       root->add_property ("locked-to-meter", locked_to_meter()?"yes":"no");
 
        return *root;
 }
 
-void
-
-TempoSection::update_bar_offset_from_bbt (const Meter& m)
-{
-       _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_beat + start().ticks) /
-               (m.divisions_per_bar() * BBT_Time::ticks_per_beat);
-
-       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Tempo set bar offset to %1 from %2 w/%3\n", _bar_offset, start(), m.divisions_per_bar()));
-}
-
 void
 TempoSection::set_type (Type type)
 {
        _type = type;
 }
 
+/** returns the tempo in whole pulses per minute at the zero-based (relative to session) frame.
+*/
 double
-TempoSection::tempo_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+TempoSection::tempo_at_frame (const framepos_t& f, const framecnt_t& frame_rate) const
 {
 
-       if (_type == Constant) {
-               return beats_per_minute();
+       if (_type == Constant || _c_func == 0.0) {
+               return pulses_per_minute();
        }
 
-       return tick_tempo_at_time (frame_to_minute (frame, frame_rate), end_bpm *  BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)) / BBT_Time::ticks_per_beat;
+       return pulse_tempo_at_time (frame_to_minute (f - frame(), frame_rate));
 }
 
+/** returns the zero-based frame (relative to session)
+   where the tempo in whole pulses per minute occurs in this section.
+   pulse p is only used for constant tempos.
+   note that the tempo map may have multiple such values.
+*/
 framepos_t
-TempoSection::frame_at_tempo (double tempo, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+TempoSection::frame_at_tempo (const double& ppm, const double& p, const framecnt_t& frame_rate) const
 {
-       if (_type == Constant) {
-               return 0;
+       if (_type == Constant || _c_func == 0.0) {
+               return ((p - pulse()) * frames_per_pulse (frame_rate))  + frame();
        }
 
-       return minute_to_frame (time_at_tick_tempo (tempo *  BBT_Time::ticks_per_beat,  end_bpm *  BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)), frame_rate);
+       return minute_to_frame (time_at_pulse_tempo (ppm), frame_rate) + frame();
 }
-
+/** returns the tempo in pulses per minute at the zero-based (relative to session) beat.
+*/
 double
-TempoSection::tick_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+TempoSection::tempo_at_pulse (const double& p) const
 {
-       if (_type == Constant) {
-               return (frame / frames_per_beat (frame_rate)) * BBT_Time::ticks_per_beat;
-       }
 
-       return tick_at_time (frame_to_minute (frame, frame_rate), end_bpm *  BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate));
+       if (_type == Constant || _c_func == 0.0) {
+               return pulses_per_minute();
+       }
+       double const ppm = pulse_tempo_at_pulse (p - pulse());
+       return ppm;
 }
 
-framepos_t
-TempoSection::frame_at_tick (double tick, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+/** 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.
+*/
+double
+TempoSection::pulse_at_tempo (const double& ppm, const framepos_t& f, const framecnt_t& frame_rate) const
 {
-       if (_type == Constant) {
-               return (framepos_t) floor ((tick  / BBT_Time::ticks_per_beat) * frames_per_beat (frame_rate));
+       if (_type == Constant || _c_func == 0.0) {
+               double const pulses = ((f - frame()) / frames_per_pulse (frame_rate)) + pulse();
+               return  pulses;
        }
-
-       return minute_to_frame (time_at_tick (tick, end_bpm *  BBT_Time::ticks_per_beat, frame_to_minute (end_frame, frame_rate)), frame_rate);
+       return pulse_at_pulse_tempo (ppm) + pulse();
 }
 
-double TempoSection::beat_at_frame (framepos_t frame, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
+/** returns the zero-based pulse (relative to session origin)
+   where the zero-based frame (relative to session)
+   lies.
+*/
+double
+TempoSection::pulse_at_frame (const framepos_t& f, const framecnt_t& frame_rate) const
 {
-       return tick_at_frame (frame, end_bpm, end_frame, frame_rate) / BBT_Time::ticks_per_beat;
-}
+       if (_type == Constant || _c_func == 0.0) {
+               return ((f - frame()) / frames_per_pulse (frame_rate)) + pulse();
+       }
 
-framepos_t TempoSection::frame_at_beat (double beat, double end_bpm, framepos_t end_frame, framecnt_t frame_rate) const
-{
-       return frame_at_tick (beat * BBT_Time::ticks_per_beat, end_bpm, end_frame, frame_rate);
+       return pulse_at_time (frame_to_minute (f - frame(), frame_rate)) + pulse();
 }
 
-framecnt_t
-TempoSection::minute_to_frame (double time, framecnt_t frame_rate) const
+/** returns the zero-based frame (relative to session start frame)
+   where the zero-based pulse (relative to session start)
+   falls.
+*/
+
+framepos_t
+TempoSection::frame_at_pulse (const double& p, const framecnt_t& frame_rate) const
 {
-       return time * 60.0 * frame_rate;
+       if (_type == Constant || _c_func == 0.0) {
+               return (framepos_t) floor ((p - pulse()) * frames_per_pulse (frame_rate)) + frame();
+       }
+
+       return minute_to_frame (time_at_pulse (p - pulse()), frame_rate) + frame();
 }
 
+/*
+Ramp Overview
+
+      |                     *
+Tempo |                   *
+Tt----|-----------------*|
+Ta----|--------------|*  |
+      |            * |   |
+      |         *    |   |
+      |     *        |   |
+T0----|*             |   |
+  *   |              |   |
+      _______________|___|____
+      time           a   t (next tempo)
+      [        c         ] defines c
+
+Duration in beats at time a is the integral of some Tempo function.
+In our case, the Tempo function (Tempo at time t) is
+T(t) = T0(e^(ct))
+
+with function constant
+c = log(Ta/T0)/a
+so
+a = log(Ta/T0)/c
+
+The integral over t of our Tempo function (the beat function, which is the duration in beats at some time t) is:
+b(t) = T0(e^(ct) - 1) / c
+
+To find the time t at beat duration b, we use the inverse function of the beat function (the time function) which can be shown to be:
+t(b) = log((c.b / T0) + 1) / c
+
+The time t at which Tempo T occurs is a as above:
+t(T) = log(T / T0) / c
+
+The beat at which a Tempo T occurs is:
+b(T) = (T - T0) / c
+
+The Tempo at which beat b occurs is:
+T(b) = b.c + T0
+
+We define c for this tempo ramp by placing a new tempo section at some time t after this one.
+Our problem is that we usually don't know t.
+We almost always know the duration in beats between this and the new section, so we need to find c in terms of the beat function.
+Where a = t (i.e. when a is equal to the time of the next tempo section), the beat function reveals:
+t = b log (Ta / T0) / (T0 (e^(log (Ta / T0)) - 1))
+
+By substituting our expanded t as a in the c function above, our problem is reduced to:
+c = T0 (e^(log (Ta / T0)) - 1) / b
+
+Of course the word 'beat' has been left loosely defined above.
+In music, a beat is defined by the musical pulse (which comes from the tempo)
+and the meter in use at a particular time (how many  pulse divisions there are in one bar).
+It would be more accurate to substitute the work 'pulse' for 'beat' above.
+
+Anyway ...
+
+We can now store c for future time calculations.
+If the following tempo section (the one that defines c in conjunction with this one)
+is changed or moved, c is no longer valid.
+
+The public methods are session-relative.
+
+Most of this stuff is taken from this paper:
+
+WHERE’S THE BEAT?
+TOOLS FOR DYNAMIC TEMPO CALCULATIONS
+Jan C. Schacher
+Martin Neukom
+Zurich University of Arts
+Institute for Computer Music and Sound Technology
+
+https://www.zhdk.ch/fileadmin/data_subsites/data_icst/Downloads/Timegrid/ICST_Tempopolyphony_ICMC07.pdf
+
+*/
+
+/*
+  compute this ramp's function constant using the end tempo (in whole pulses per minute)
+  and duration (pulses into global start) of some later tempo section.
+*/
 double
-TempoSection::frame_to_minute (framecnt_t frame, framecnt_t frame_rate) const
+TempoSection::compute_c_func_pulse (const double& end_bpm, const double& end_pulse, const framecnt_t& frame_rate)
 {
-       return (frame / (double) frame_rate) / 60.0;
+       double const log_tempo_ratio = log (end_bpm / pulses_per_minute());
+       return pulses_per_minute() *  (expm1 (log_tempo_ratio)) / (end_pulse - pulse());
 }
 
-/* constant for exp */
+/* compute the function constant from some later tempo section, given tempo (whole pulses/min.) and distance (in frames) from session origin */
 double
-TempoSection::a_func (double begin_tpm, double end_tpm, double end_time) const
+TempoSection::compute_c_func_frame (const double& end_bpm, const framepos_t& end_frame, const framecnt_t& frame_rate) const
 {
-       return log (end_tpm / ticks_per_minute()) /  c_func (end_tpm, end_time);
+       return c_func (end_bpm, frame_to_minute (end_frame - frame(), frame_rate));
 }
-double
-TempoSection::c_func (double end_tpm, double end_time) const
+
+framepos_t
+TempoSection::minute_to_frame (const double& time, const framecnt_t& frame_rate) const
 {
-       return log (end_tpm / ticks_per_minute()) /  end_time;
+       return (framepos_t) floor ((time * 60.0 * frame_rate) + 0.5);
 }
 
-/* tempo in tpm at time in minutes */
 double
-TempoSection::tick_tempo_at_time (double time, double end_tpm, double end_time) const
+TempoSection::frame_to_minute (const framepos_t& frame, const framecnt_t& frame_rate) const
 {
-       return exp (c_func (end_tpm, end_time) * time) * ticks_per_minute();
+       return (frame / (double) frame_rate) / 60.0;
 }
 
-/* time in minutes at tempo in tpm */
+/* position function */
 double
-TempoSection::time_at_tick_tempo (double tick_tempo, double end_tpm, double end_time) const
+TempoSection::a_func (double end_ppm, double c_func) const
 {
-       return log (tick_tempo / ticks_per_minute()) / c_func (end_tpm, end_time);
+       return log (end_ppm / pulses_per_minute()) /  c_func;
 }
 
-/* tempo in bpm at time in minutes */
+/*function constant*/
 double
-TempoSection::tempo_at_time (double time, double end_bpm, double end_time) const
+TempoSection::c_func (double end_ppm, double end_time) const
 {
-       return tick_tempo_at_time (time, end_bpm *  BBT_Time::ticks_per_beat, end_time) / BBT_Time::ticks_per_beat;
+       return log (end_ppm / pulses_per_minute()) /  end_time;
 }
 
-/* time in minutes at tempo in bpm */
+/* tempo in ppm at time in minutes */
 double
-TempoSection::time_at_tempo (double tempo, double end_bpm, double end_time) const
+TempoSection::pulse_tempo_at_time (const double& time) const
 {
-       return time_at_tick_tempo (tempo * BBT_Time::ticks_per_beat, end_bpm * BBT_Time::ticks_per_beat, end_time);
+       return exp (_c_func * time) * pulses_per_minute();
 }
 
-/* tick at time in minutes */
+/* time in minutes at tempo in ppm */
 double
-TempoSection::tick_at_time (double time, double end_tpm, double end_time) const
+TempoSection::time_at_pulse_tempo (const double& pulse_tempo) const
 {
-       return ((exp (c_func (end_tpm, end_time) * time)) - 1) * ticks_per_minute() / c_func (end_tpm, end_time);
+       return log (pulse_tempo / pulses_per_minute()) / _c_func;
 }
 
-/* time in minutes at tick */
+/* tick at tempo in ppm */
 double
-TempoSection::time_at_tick (double tick, double end_tpm, double end_time) const
+TempoSection::pulse_at_pulse_tempo (const double& pulse_tempo) const
 {
-       return log (((c_func (end_tpm, end_time) * tick) / ticks_per_minute()) + 1) / c_func (end_tpm, end_time);
+       return (pulse_tempo - pulses_per_minute()) / _c_func;
 }
 
-/* beat at time in minutes */
+/* tempo in ppm at tick */
 double
-TempoSection::beat_at_time (double time, double end_tpm, double end_time) const
+TempoSection::pulse_tempo_at_pulse (const double& pulse) const
 {
-       return tick_at_time (time, end_tpm, end_time) / BBT_Time::ticks_per_beat;
+       return (pulse * _c_func) + pulses_per_minute();
 }
 
-/* time in munutes at beat */
+/* pulse at time in minutes */
 double
-TempoSection::time_at_beat (double beat, double end_tpm, double end_time) const
+TempoSection::pulse_at_time (const double& time) const
 {
-       return time_at_tick (beat * BBT_Time::ticks_per_beat, end_tpm, end_time);
+       return expm1 (_c_func * time) * (pulses_per_minute() / _c_func);
 }
 
-void
-TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
+/* time in minutes at pulse */
+double
+TempoSection::time_at_pulse (const double& pulse) const
 {
-       BBT_Time new_start;
-
-       if (_bar_offset < 0.0) {
-               /* not set yet */
-               return;
-       }
-
-       new_start.bars = start().bars;
-
-       double ticks = BBT_Time::ticks_per_beat * meter.divisions_per_bar() * _bar_offset;
-       new_start.beats = (uint32_t) floor (ticks/BBT_Time::ticks_per_beat);
-       new_start.ticks = 0; /* (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat); */
-
-       /* remember the 1-based counting properties of beats */
-       new_start.beats += 1;
-       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("from bar offset %1 and dpb %2, ticks = %3->%4 beats = %5\n",
-                                                      _bar_offset, meter.divisions_per_bar(), ticks, new_start.ticks, new_start.beats));
-
-       set_start (new_start);
+       return log1p ((_c_func * pulse) / pulses_per_minute()) / _c_func;
 }
 
 /***********************************************************************/
@@ -341,26 +453,64 @@ TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
 const string MeterSection::xml_state_node_name = "Meter";
 
 MeterSection::MeterSection (const XMLNode& node)
-       : MetricSection (BBT_Time()), Meter (TempoMap::default_meter())
+       : MetricSection (0.0, 0, MusicTime), Meter (TempoMap::default_meter())
 {
        XMLProperty const * prop;
-       BBT_Time start;
        LocaleGuard lg;
+       BBT_Time bbt;
+       double pulse = 0.0;
+       double beat = 0.0;
+       framepos_t frame = 0;
+       pair<double, BBT_Time> start;
+
+       if ((prop = node.property ("start")) != 0) {
+               if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
+                   &bbt.bars,
+                   &bbt.beats,
+                   &bbt.ticks) < 3) {
+                       error << _("MeterSection XML node has an illegal \"start\" value") << endmsg;
+               } else {
+                       /* legacy session - start used to be in bbt*/
+                       info << _("Legacy session detected - MeterSection XML node will be altered.") << endmsg;
+                       pulse = -1.0;
+               }
+       }
 
-       if ((prop = node.property ("start")) == 0) {
-               error << _("MeterSection XML node has no \"start\" property") << endmsg;
-               throw failed_constructor();
+       if ((prop = node.property ("pulse")) != 0) {
+               if (sscanf (prop->value().c_str(), "%lf", &pulse) != 1) {
+                       error << _("MeterSection XML node has an illegal \"pulse\" value") << endmsg;
+               }
+       }
+       set_pulse (pulse);
+
+       if ((prop = node.property ("beat")) != 0) {
+               if (sscanf (prop->value().c_str(), "%lf", &beat) != 1) {
+                       error << _("MeterSection XML node has an illegal \"beat\" value") << endmsg;
+               }
        }
 
-       if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
-                   &start.bars,
-                   &start.beats,
-                   &start.ticks) < 3) {
-               error << _("MeterSection XML node has an illegal \"start\" value") << endmsg;
+       start.first = beat;
+
+       if ((prop = node.property ("bbt")) == 0) {
+               warning << _("MeterSection XML node has no \"bbt\" property") << endmsg;
+       } else if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
+                   &bbt.bars,
+                   &bbt.beats,
+                   &bbt.ticks) < 3) {
+               error << _("MeterSection XML node has an illegal \"bbt\" value") << endmsg;
                throw failed_constructor();
        }
 
-       set_start (start);
+       start.second = bbt;
+       set_beat (start);
+
+       if ((prop = node.property ("frame")) != 0) {
+               if (sscanf (prop->value().c_str(), "%li", &frame) != 1) {
+                       error << _("MeterSection XML node has an illegal \"frame\" value") << endmsg;
+               } else {
+                       set_frame (frame);
+               }
+       }
 
        /* beats-per-bar is old; divisions-per-bar is new */
 
@@ -370,9 +520,8 @@ MeterSection::MeterSection (const XMLNode& node)
                        throw failed_constructor();
                }
        }
-
        if (sscanf (prop->value().c_str(), "%lf", &_divisions_per_bar) != 1 || _divisions_per_bar < 0.0) {
-               error << _("MeterSection XML node has an illegal \"beats-per-bar\" or \"divisions-per-bar\" value") << endmsg;
+               error << _("MeterSection XML node has an illegal \"divisions-per-bar\" value") << endmsg;
                throw failed_constructor();
        }
 
@@ -380,7 +529,6 @@ MeterSection::MeterSection (const XMLNode& node)
                error << _("MeterSection XML node has no \"note-type\" property") << endmsg;
                throw failed_constructor();
        }
-
        if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 0.0) {
                error << _("MeterSection XML node has an illegal \"note-type\" value") << endmsg;
                throw failed_constructor();
@@ -392,6 +540,17 @@ MeterSection::MeterSection (const XMLNode& node)
        }
 
        set_movable (string_is_affirmative (prop->value()));
+
+       if ((prop = node.property ("lock-style")) == 0) {
+               warning << _("MeterSection XML node has no \"lock-style\" property") << endmsg;
+               if (movable()) {
+                       set_position_lock_style (MusicTime);
+               } else {
+                       set_position_lock_style (AudioTime);
+               }
+       } else {
+               set_position_lock_style (PositionLockStyle (string_2_enum (prop->value(), position_lock_style())));
+       }
 }
 
 XMLNode&
@@ -401,13 +560,20 @@ MeterSection::get_state() const
        char buf[256];
        LocaleGuard lg;
 
+       snprintf (buf, sizeof (buf), "%lf", pulse());
+       root->add_property ("pulse", buf);
        snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
-                 start().bars,
-                 start().beats,
-                 start().ticks);
-       root->add_property ("start", buf);
+                 bbt().bars,
+                 bbt().beats,
+                 bbt().ticks);
+       root->add_property ("bbt", buf);
+       snprintf (buf, sizeof (buf), "%lf", beat());
+       root->add_property ("beat", buf);
        snprintf (buf, sizeof (buf), "%f", _note_type);
        root->add_property ("note-type", buf);
+       snprintf (buf, sizeof (buf), "%li", frame());
+       root->add_property ("frame", buf);
+       root->add_property ("lock-style", enum_2_string (position_lock_style()));
        snprintf (buf, sizeof (buf), "%f", _divisions_per_bar);
        root->add_property ("divisions-per-bar", buf);
        snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
@@ -417,36 +583,91 @@ MeterSection::get_state() const
 }
 
 /***********************************************************************/
+/*
+  Tempo Map Overview
+
+  Tempo is the rate of the musical pulse.
+  Meters divide the pulses into measures and beats.
+
+  TempoSections - provide pulses in the form of beats_per_minute() and note_type() where note_type is the division of a whole pulse,
+  and beats_per_minute is the number of note_types in one minute (unlike what its name suggests).
+  Note that Tempo::beats_per_minute() has nothing to do with musical beats.
+
+  MeterSecions - divide pulses into measures (via divisions_per_bar) and beats (via note_divisor).
+
+  Both tempos and meters have a pulse position and a frame position.
+  Meters also have a beat position, which is always 0.0 for the first meter.
+  TempoSections and MeterSections may be locked to either audio or music (position lock style).
+  The lock style determines the 'true' position of the section wich is used to calculate the other postion parameters of the section.
+
+  The first tempo and first meter are special. they must move together, and must be locked to audio.
+  Audio locked tempos which lie before the first meter are made inactive.
+  They will be re-activated if the first meter is again placed before them.
 
+  With tepo sections potentially being ramped, meters provide a way of mapping beats to whole pulses without
+  referring to the tempo function(s) involved as the distance in whole pulses between a meter and a subsequent beat is
+  sb->beat() - meter->beat() / meter->note_divisor().
+  Because every meter falls on a known pulse, (derived from its bar), the rest is easy as the duration in pulses between
+  two meters is of course
+  (meater_b->bar - meter_a->bar) * meter_a->divisions_per_bar / meter_a->note_divisor.
+
+  Below, beat calculations are based on meter sections and all pulse and tempo calculations are based on tempo sections.
+  Beat to frame conversion of course requires the use of meter and tempo.
+
+  Remembering that ramped tempo sections interact, it is important to avoid referring to any other tempos when moving tempo sections,
+  Here, beats (meters) are used to determine the new pulse (see predict_tempo_position())
+
+  Recomputing the 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()).
+*/
 struct MetricSectionSorter {
     bool operator() (const MetricSection* a, const MetricSection* b) {
-           return a->start() < b->start();
+           return a->pulse() < b->pulse();
+    }
+};
+
+struct MetricSectionFrameSorter {
+    bool operator() (const MetricSection* a, const MetricSection* b) {
+           return a->frame() < b->frame();
     }
 };
 
 TempoMap::TempoMap (framecnt_t fr)
 {
        _frame_rate = fr;
-       BBT_Time start;
+       BBT_Time start (1, 1, 0);
 
-       start.bars = 1;
-       start.beats = 1;
-       start.ticks = 0;
-
-       TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type(), TempoSection::Type::Ramp);
-       MeterSection *m = new MeterSection (start, _default_meter.divisions_per_bar(), _default_meter.note_divisor());
+       TempoSection *t = new TempoSection (0.0, 0, _default_tempo.beats_per_minute(), _default_tempo.note_type(), TempoSection::Ramp, AudioTime);
+       MeterSection *m = new MeterSection (0.0, 0, 0.0, start, _default_meter.divisions_per_bar(), _default_meter.note_divisor(), AudioTime);
 
        t->set_movable (false);
        m->set_movable (false);
 
        /* note: frame time is correct (zero) for both of these */
 
-       metrics.push_back (t);
-       metrics.push_back (m);
+       _metrics.push_back (t);
+       _metrics.push_back (m);
+
 }
 
 TempoMap::~TempoMap ()
 {
+       Metrics::const_iterator d = _metrics.begin();
+       while (d != _metrics.end()) {
+               delete (*d);
+               ++d;
+       }
+       _metrics.clear();
 }
 
 void
@@ -458,7 +679,7 @@ TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation)
                Glib::Threads::RWLock::WriterLock lm (lock);
                if ((removed = remove_tempo_locked (tempo))) {
                        if (complete_operation) {
-                               recompute_map (true);
+                               recompute_map (_metrics);
                        }
                }
        }
@@ -473,11 +694,12 @@ TempoMap::remove_tempo_locked (const TempoSection& tempo)
 {
        Metrics::iterator i;
 
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
+       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
                if (dynamic_cast<TempoSection*> (*i) != 0) {
                        if (tempo.frame() == (*i)->frame()) {
                                if ((*i)->movable()) {
-                                       metrics.erase (i);
+                                       delete (*i);
+                                       _metrics.erase (i);
                                        return true;
                                }
                        }
@@ -496,7 +718,7 @@ TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
                Glib::Threads::RWLock::WriterLock lm (lock);
                if ((removed = remove_meter_locked (tempo))) {
                        if (complete_operation) {
-                               recompute_map (true);
+                               recompute_map (_metrics);
                        }
                }
        }
@@ -507,15 +729,29 @@ TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
 }
 
 bool
-TempoMap::remove_meter_locked (const MeterSection& tempo)
+TempoMap::remove_meter_locked (const MeterSection& meter)
 {
        Metrics::iterator i;
 
-       for (i = metrics.begin(); i != metrics.end(); ++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()) {
+                                       delete (*i);
+                                       _metrics.erase (i);
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
                if (dynamic_cast<MeterSection*> (*i) != 0) {
-                       if (tempo.frame() == (*i)->frame()) {
+                       if (meter.frame() == (*i)->frame()) {
                                if ((*i)->movable()) {
-                                       metrics.erase (i);
+                                       delete (*i);
+                                       _metrics.erase (i);
                                        return true;
                                }
                        }
@@ -529,34 +765,24 @@ void
 TempoMap::do_insert (MetricSection* section)
 {
        bool need_add = true;
-
-       assert (section->start().ticks == 0);
-
        /* we only allow new meters to be inserted on beat 1 of an existing
         * measure.
         */
+       MeterSection* m = 0;
+       if ((m = dynamic_cast<MeterSection*>(section)) != 0) {
 
-       if (dynamic_cast<MeterSection*>(section)) {
-
-               /* we need to (potentially) update the BBT times of tempo
-                  sections based on this new meter.
-               */
-
-               if ((section->start().beats != 1) || (section->start().ticks != 0)) {
-
-                       BBT_Time corrected = section->start();
-                       corrected.beats = 1;
-                       corrected.ticks = 0;
+               if ((m->bbt().beats != 1) || (m->bbt().ticks != 0)) {
 
+                       pair<double, BBT_Time> corrected = make_pair (m->beat(), m->bbt());
+                       corrected.second.beats = 1;
+                       corrected.second.ticks = 0;
+                       corrected.first = beat_at_bbt_locked (_metrics, corrected.second);
                        warning << string_compose (_("Meter changes can only be positioned on the first beat of a bar. Moving from %1 to %2"),
-                                                  section->start(), corrected) << endmsg;
-
-                       section->set_start (corrected);
+                                                  m->bbt(), corrected.second) << endmsg;
+                       //m->set_pulse (corrected);
                }
        }
 
-
-
        /* Look for any existing MetricSection that is of the same type and
           in the same bar as the new one, and remove it before adding
           the new one. Note that this means that if we find a matching,
@@ -564,51 +790,61 @@ TempoMap::do_insert (MetricSection* section)
           guaranteed that there is only one such match.
        */
 
-       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
 
-               bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
-               bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
+               TempoSection* const tempo = dynamic_cast<TempoSection*> (*i);
+               TempoSection* const insert_tempo = dynamic_cast<TempoSection*> (section);
+               MeterSection* const meter = dynamic_cast<MeterSection*> (*i);
+               MeterSection* const insert_meter = dynamic_cast<MeterSection*> (section);
 
-               if (iter_is_tempo && insert_is_tempo) {
+               if (tempo && insert_tempo) {
 
                        /* Tempo sections */
+                       bool const ipm = insert_tempo->position_lock_style() == MusicTime;
+                       if ((ipm && tempo->pulse() == insert_tempo->pulse()) || (!ipm && tempo->frame() == insert_tempo->frame())) {
 
-                       if ((*i)->start().bars == section->start().bars &&
-                           (*i)->start().beats == section->start().beats) {
-
-                               if (!(*i)->movable()) {
+                               if (!tempo->movable()) {
 
                                        /* can't (re)move this section, so overwrite
                                         * its data content (but not its properties as
                                         * a section).
                                         */
 
-                                       *(dynamic_cast<Tempo*>(*i)) = *(dynamic_cast<Tempo*>(section));
+                                       *(dynamic_cast<Tempo*>(*i)) = *(dynamic_cast<Tempo*>(insert_tempo));
+                                       (*i)->set_position_lock_style (AudioTime);
+                                       TempoSection* t;
+                                       if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
+                                               t->set_type (insert_tempo->type());
+                                       }
                                        need_add = false;
                                } else {
-                                       metrics.erase (i);
+                                       delete (*i);
+                                       _metrics.erase (i);
                                }
                                break;
                        }
 
-               } else if (!iter_is_tempo && !insert_is_tempo) {
+               } else if (meter && insert_meter) {
 
                        /* Meter Sections */
 
-                       if ((*i)->start().bars == section->start().bars) {
+                       bool const ipm = insert_meter->position_lock_style() == MusicTime;
+
+                       if ((ipm && meter->beat() == insert_meter->beat()) || (!ipm && meter->frame() == insert_meter->frame())) {
 
-                               if (!(*i)->movable()) {
+                               if (!meter->movable()) {
 
                                        /* can't (re)move this section, so overwrite
                                         * its data content (but not its properties as
                                         * a section
                                         */
 
-                                       *(dynamic_cast<Meter*>(*i)) = *(dynamic_cast<Meter*>(section));
+                                       *(dynamic_cast<Meter*>(*i)) = *(dynamic_cast<Meter*>(insert_meter));
+                                       (*i)->set_position_lock_style (AudioTime);
                                        need_add = false;
                                } else {
-                                       metrics.erase (i);
-
+                                       delete (*i);
+                                       _metrics.erase (i);
                                }
 
                                break;
@@ -623,160 +859,176 @@ TempoMap::do_insert (MetricSection* section)
         */
 
        if (need_add) {
-
+               MeterSection* const insert_meter = dynamic_cast<MeterSection*> (section);
+               TempoSection* const insert_tempo = dynamic_cast<TempoSection*> (section);
                Metrics::iterator i;
-
-               for (i = metrics.begin(); i != metrics.end(); ++i) {
-                       if ((*i)->start() > section->start()) {
-                               break;
+               if (insert_meter) {
+                       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
+                               MeterSection* const meter = dynamic_cast<MeterSection*> (*i);
+
+                               if (meter) {
+                                       bool const ipm = insert_meter->position_lock_style() == MusicTime;
+                                       if ((ipm && meter->beat() > insert_meter->beat()) || (!ipm && meter->frame() > insert_meter->frame())) {
+                                               break;
+                                       }
+                               }
+                       }
+               } else if (insert_tempo) {
+                       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
+                               TempoSection* const tempo = dynamic_cast<TempoSection*> (*i);
+
+                               if (tempo) {
+                                       bool const ipm = insert_tempo->position_lock_style() == MusicTime;
+                                       if ((ipm && tempo->pulse() > insert_tempo->pulse()) || (!ipm && tempo->frame() > insert_tempo->frame())) {
+                                               break;
+                                       }
+                               }
                        }
                }
 
-               metrics.insert (i, section);
+               _metrics.insert (i, section);
+               //dump (_metrics, std::cout);
        }
 }
 
-void
-TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where, TempoSection::Type type)
+TempoSection*
+TempoMap::add_tempo (const Tempo& tempo, const double& pulse, const framepos_t& frame, ARDOUR::TempoSection::Type type, PositionLockStyle pls)
 {
+       TempoSection* ts = 0;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection& first (first_tempo());
-
-               if (ts.start() != first.start()) {
-                       remove_tempo_locked (ts);
-                       add_tempo_locked (tempo, where, true, type);
-               } else {
-                       {
-                               /* cannot move the first tempo section */
-                               first.set_type (type);
-                               *static_cast<Tempo*>(&first) = tempo;
-                               recompute_map (false);
-                       }
-               }
+               ts = add_tempo_locked (tempo, pulse, frame, type, pls, true);
        }
 
+
        PropertyChanged (PropertyChange ());
+
+       return ts;
 }
 
 void
-TempoMap::add_tempo (const Tempo& tempo, BBT_Time where, ARDOUR::TempoSection::Type type)
+TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const double& pulse, const framepos_t& frame, TempoSection::Type type, PositionLockStyle pls)
 {
+       const bool locked_to_meter = ts.locked_to_meter();
+
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               add_tempo_locked (tempo, where, true, type);
+               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);
+               } else {
+                       first.set_type (type);
+                       first.set_pulse (0.0);
+                       first.set_frame (frame);
+                       first.set_position_lock_style (AudioTime);
+                       {
+                               /* cannot move the first tempo section */
+                               *static_cast<Tempo*>(&first) = tempo;
+                               recompute_map (_metrics);
+                       }
+               }
        }
 
-
        PropertyChanged (PropertyChange ());
 }
 
-void
-TempoMap::add_tempo_locked (const Tempo& tempo, BBT_Time where, bool recompute, ARDOUR::TempoSection::Type type)
+TempoSection*
+TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, framepos_t frame
+                           , TempoSection::Type type, PositionLockStyle pls, bool recompute, bool locked_to_meter)
 {
-       /* new tempos always start on a beat */
-       where.ticks = 0;
-       TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type(), type);
-
-       /* find the meter to use to set the bar offset of this
-        * tempo section.
-        */
-
-       const Meter* meter = &first_meter();
-
-       /* as we start, we are *guaranteed* to have m.meter and m.tempo pointing
-          at something, because we insert the default tempo and meter during
-          TempoMap construction.
-
-          now see if we can find better candidates.
-       */
-
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-
-               const MeterSection* m;
+       TempoSection* t = new TempoSection (pulse, frame, tempo.beats_per_minute(), tempo.note_type(), type, pls);
+       t->set_locked_to_meter (locked_to_meter);
 
-               if (where < (*i)->start()) {
-                       break;
-               }
+       do_insert (t);
 
-               if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                       meter = m;
+       if (recompute) {
+               if (pls == AudioTime) {
+                       solve_map_frame (_metrics, t, t->frame());
+               } else {
+                       solve_map_pulse (_metrics, t, t->pulse());
                }
+               recompute_meters (_metrics);
        }
 
-       ts->update_bar_offset_from_bbt (*meter);
-
-       /* and insert it */
-
-       do_insert (ts);
-
-       if (recompute) {
-               recompute_map (false);
-       }
+       return t;
 }
 
-void
-TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
+MeterSection*
+TempoMap::add_meter (const Meter& meter, const double& beat, const Timecode::BBT_Time& where, const framepos_t& frame, PositionLockStyle pls)
 {
+       MeterSection* m = 0;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               MeterSection& first (first_meter());
+               m = add_meter_locked (meter, beat, where, frame, pls, true);
+       }
 
-               if (ms.start() != first.start()) {
-                       remove_meter_locked (ms);
-                       add_meter_locked (meter, where, true);
-               } else {
-                       /* cannot move the first meter section */
-                       *static_cast<Meter*>(&first) = meter;
-                       recompute_map (true);
-               }
+
+#ifndef NDEBUG
+       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
+               dump (_metrics, std::cerr);
        }
+#endif
 
        PropertyChanged (PropertyChange ());
+       return m;
 }
 
 void
-TempoMap::add_meter (const Meter& meter, BBT_Time where)
+TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where, const framepos_t& frame, PositionLockStyle pls)
 {
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               add_meter_locked (meter, where, true);
-       }
+               const double beat = beat_at_bbt_locked (_metrics, where);
 
-
-#ifndef NDEBUG
-       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
-               dump (std::cerr);
+               if (ms.movable()) {
+                       remove_meter_locked (ms);
+                       add_meter_locked (meter, beat, where, frame, pls, true);
+               } else {
+                       MeterSection& first (first_meter());
+                       TempoSection& first_t (first_tempo());
+                       /* cannot move the first meter section */
+                       *static_cast<Meter*>(&first) = meter;
+                       first.set_position_lock_style (AudioTime);
+                       first.set_pulse (0.0);
+                       first.set_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_pulse (0.0);
+                       first_t.set_position_lock_style (AudioTime);
+                       recompute_map (_metrics);
+               }
        }
-#endif
 
        PropertyChanged (PropertyChange ());
 }
 
-void
-TempoMap::add_meter_locked (const Meter& meter, BBT_Time where, bool recompute)
+MeterSection*
+TempoMap::add_meter_locked (const Meter& meter, double beat, const Timecode::BBT_Time& where, framepos_t frame, PositionLockStyle pls, bool recompute)
 {
-       /* a new meter always starts a new bar on the first beat. so
-          round the start time appropriately. remember that
-          `where' is based on the existing tempo map, not
-          the result after we insert the new meter.
-
-       */
+       const MeterSection& prev_m = meter_section_at_frame_locked  (_metrics, frame - 1);
+       const double pulse = ((where.bars - prev_m.bbt().bars) * (prev_m.divisions_per_bar() / prev_m.note_divisor())) + prev_m.pulse();
 
-       if (where.beats != 1) {
-               where.beats = 1;
-               where.bars++;
+       if (pls == AudioTime) {
+               /* add meter-locked tempo */
+               add_tempo_locked (tempo_at_frame_locked (_metrics, frame), pulse,  frame, TempoSection::Ramp, AudioTime, true, true);
        }
 
-       /* new meters *always* start on a beat. */
-       where.ticks = 0;
+       MeterSection* new_meter = new MeterSection (pulse, frame, beat, where, meter.divisions_per_bar(), meter.note_divisor(), pls);
 
-       do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor()));
+       do_insert (new_meter);
 
        if (recompute) {
-               recompute_map (true);
+
+               if (pls == AudioTime) {
+                       solve_map_frame (_metrics, new_meter, frame);
+               } else {
+                       solve_map_bbt (_metrics, new_meter, where);
+               }
        }
 
+       return new_meter;
 }
 
 void
@@ -785,12 +1037,15 @@ TempoMap::change_initial_tempo (double beats_per_minute, double note_type)
        Tempo newtempo (beats_per_minute, note_type);
        TempoSection* t;
 
-       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                       if (!t->active()) {
+                               continue;
+                       }
                        {
                                Glib::Threads::RWLock::WriterLock lm (lock);
                                *((Tempo*) t) = newtempo;
-                               recompute_map (false);
+                               recompute_map (_metrics);
                        }
                        PropertyChanged (PropertyChange ());
                        break;
@@ -810,7 +1065,7 @@ TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, d
        /* find the TempoSection immediately preceding "where"
         */
 
-       for (first = 0, i = metrics.begin(), prev = 0; i != metrics.end(); ++i) {
+       for (first = 0, i = _metrics.begin(), prev = 0; i != _metrics.end(); ++i) {
 
                if ((*i)->frame() > where) {
                        break;
@@ -819,6 +1074,9 @@ TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, d
                TempoSection* t;
 
                if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
+                       if (!t->active()) {
+                               continue;
+                       }
                        if (!first) {
                                first = t;
                        }
@@ -841,7 +1099,7 @@ TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, d
                Glib::Threads::RWLock::WriterLock lm (lock);
                /* cannot move the first tempo section */
                *((Tempo*)prev) = newtempo;
-               recompute_map (false);
+               recompute_map (_metrics);
        }
 
        PropertyChanged (PropertyChange ());
@@ -852,13 +1110,13 @@ TempoMap::first_meter () const
 {
        const MeterSection *m = 0;
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                if ((m = dynamic_cast<const MeterSection *> (*i)) != 0) {
                        return *m;
                }
        }
 
-       fatal << _("programming error: no tempo section in tempo map!") << endmsg;
+       fatal << _("programming error: no meter section in tempo map!") << endmsg;
        abort(); /*NOTREACHED*/
        return *m;
 }
@@ -870,7 +1128,7 @@ TempoMap::first_meter ()
 
        /* CALLER MUST HOLD LOCK */
 
-       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
                        return *m;
                }
@@ -888,9 +1146,14 @@ TempoMap::first_tempo () const
 
        /* CALLER MUST HOLD LOCK */
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                if ((t = dynamic_cast<const TempoSection *> (*i)) != 0) {
-                       return *t;
+                       if (!t->active()) {
+                               continue;
+                       }
+                       if (!t->movable()) {
+                               return *t;
+                       }
                }
        }
 
@@ -904,9 +1167,14 @@ TempoMap::first_tempo ()
 {
        TempoSection *t = 0;
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                if ((t = dynamic_cast<TempoSection *> (*i)) != 0) {
-                       return *t;
+                       if (!t->active()) {
+                               continue;
+                       }
+                       if (!t->movable()) {
+                               return *t;
+                       }
                }
        }
 
@@ -914,102 +1182,137 @@ TempoMap::first_tempo ()
        abort(); /*NOTREACHED*/
        return *t;
 }
-
 void
-TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
+TempoMap::recompute_tempos (Metrics& metrics)
 {
-       /* CALLER MUST HOLD WRITE LOCK */
-
-       MeterSection* meter = 0;
-       TempoSection* tempo = 0;
-       double current_frame;
-       BBT_Time current;
-       Metrics::iterator next_metric;
+       TempoSection* prev_t = 0;
 
-       if (end < 0) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               TempoSection* t;
 
-               /* we will actually stop once we hit
-                  the last metric.
-               */
-               end = max_framepos;
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                       if (!t->active()) {
+                               continue;
+                       }
+                       if (!t->movable()) {
+                               if (!prev_t) {
+                                       t->set_pulse (0.0);
+                                       prev_t = t;
+                                       continue;
+                               }
+                       }
+                       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));
+                                       if (!t->locked_to_meter()) {
+                                               t->set_pulse (prev_t->pulse_at_tempo (t->pulses_per_minute(), t->frame(), _frame_rate));
+                                       }
 
-       } else {
-               /*
-               if (!_map.empty ()) {
-                       /* never allow the map to be shortened /
-                       end = max (end, _map.back().frame);
+                               } 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 = t;
                }
-               */
        }
+       prev_t->set_c_func (0.0);
+}
 
-       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
-
-       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* ms;
+/* tempos must be positioned correctly.
+   the current approach is to use a meter's bbt time as its base position unit.
+   an audio-locked meter requires a recomputation of pulse and beat (but not bbt),
+   while a music-locked meter requires recomputations of frame pulse and beat (but not bbt)
+*/
+void
+TempoMap::recompute_meters (Metrics& metrics)
+{
+       MeterSection* meter = 0;
+       MeterSection* prev_m = 0;
+
+       for (Metrics::const_iterator mi = metrics.begin(); mi != metrics.end(); ++mi) {
+               if ((meter = dynamic_cast<MeterSection*> (*mi)) != 0) {
+                       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 ((t->locked_to_meter() || !t->movable()) && t->frame() == meter->frame()) {
+                                                       meter_locked_tempo = t;
+                                                       break;
+                                               }
+                                       }
+                               }
 
-               if ((ms = dynamic_cast<MeterSection *> (*i)) != 0) {
-                       meter = ms;
-                       break;
-               }
-       }
+                               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 */
+                                               b_bbt = make_pair (beats + prev_m->beat()
+                                                                  , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0));
+                                               pulse = prev_m->pulse() + (beats / prev_m->note_divisor());
+
+                                       } else if (meter->movable()) {
+                                               b_bbt = make_pair (meter->beat(), meter->bbt());
+                                               pulse = prev_m->pulse() + (beats / prev_m->note_divisor());
+                                       }
+                               } else {
+                                       b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
+                               }
+                               if (meter_locked_tempo) {
+                                       meter_locked_tempo->set_pulse (pulse);
+                               }
+                               meter->set_beat (b_bbt);
+                               meter->set_pulse (pulse);
 
-       assert(meter);
+                       } else {
+                               /* MusicTime */
+                               double pulse = 0.0;
+                               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 */
+                                               b_bbt = make_pair (beats + prev_m->beat()
+                                                                  , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0));
+                                       } else {
+                                               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());
+                                       b_bbt = make_pair (meter->beat(), meter->bbt());
+                               }
 
-       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               TempoSection* ts;
+                               meter->set_beat (b_bbt);
+                               meter->set_pulse (pulse);
+                               meter->set_frame (frame_at_pulse_locked (metrics, pulse));
+                       }
 
-               if ((ts = dynamic_cast<TempoSection *> (*i)) != 0) {
-                       tempo = ts;
-                       break;
+                       prev_m = meter;
                }
        }
+}
 
-       assert(tempo);
-
-       /* assumes that the first meter & tempo are at frame zero */
-       current_frame = 0;
-       meter->set_frame (0);
-       tempo->set_frame (0);
-
-       /* assumes that the first meter & tempo are at 1|1|0 */
-       current.bars = 1;
-       current.beats = 1;
-       current.ticks = 0;
-       if (reassign_tempo_bbt) {
-
-               MeterSection* rmeter = meter;
-
-               DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n");
-
-               for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
-
-                       TempoSection* ts;
-                       MeterSection* ms;
-
-                       if ((ts = dynamic_cast<TempoSection*>(*i)) != 0) {
+void
+TempoMap::recompute_map (Metrics& metrics, framepos_t end)
+{
+       /* CALLER MUST HOLD WRITE LOCK */
 
-                               /* reassign the BBT time of this tempo section
-                                * based on its bar offset position.
-                                */
+       if (end < 0) {
 
-                               ts->update_bbt_time_from_bar_offset (*rmeter);
+               /* we will actually stop once we hit
+                  the last metric.
+               */
+               end = max_framepos;
 
-                       } else if ((ms = dynamic_cast<MeterSection*>(*i)) != 0) {
-                               rmeter = ms;
-                       } else {
-                               fatal << _("programming error: unhandled MetricSection type") << endmsg;
-                               abort(); /*NOTREACHED*/
-                       }
-               }
        }
 
-       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2\n", *((Meter*)meter), *((Tempo*)tempo)));
-
-       next_metric = metrics.begin();
-       ++next_metric; // skip meter (or tempo)
-       ++next_metric; // skip tempo (or meter)
-
-       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add first bar at 1|1 @ %2\n", current.bars, current_frame));
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
 
        if (end == 0) {
                /* silly call from Session::process() during startup
@@ -1017,86 +1320,10 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
                return;
        }
 
-       _extend_map (tempo, meter, next_metric, current, current_frame, end);
-}
-
-void
-TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter,
-                      Metrics::iterator next_metric,
-                      BBT_Time current, framepos_t current_frame, framepos_t end)
-{
-       /* CALLER MUST HOLD WRITE LOCK */
-
-       uint32_t first_tick_in_new_meter = 0;
-       Metrics::const_iterator i;
-       Metrics::const_iterator mi;
-
-       TempoSection* prev_ts = tempo;
-
-       for (mi = metrics.begin(); mi != metrics.end(); ++mi) {
-               MeterSection* m = 0;
-
-               if ((m = dynamic_cast<MeterSection*> (*mi)) != 0) {
-
-                       if (m->start() >= prev_ts->start()) {
-                               first_tick_in_new_meter = ((((m->start().bars - 1) * meter->divisions_per_bar()) + (m->start().beats - 1)) * BBT_Time::ticks_per_beat) + m->start().ticks; // expressed in ticks from the previous meter
-                               for (i = metrics.begin(); i != metrics.end(); ++i) {
-                                       TempoSection* t;
-
-                                       if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-
-                                               if (t->start() >= m->start() && t->start() > prev_ts->start()) {
-                                                       //cerr << "new ts start bars = " << t->start().bars << " beats = " << t->start().beats << " ticks = " << t->start().ticks << endl;
-                                                       //cerr << "prev ts start bars = " << prev_ts->start().bars << " beats = " << prev_ts->start().beats << " ticks = " << prev_ts->start().ticks << endl;
-
-                                                       /*tempo section (t) lies in the previous meter */
-                                                       double ticks_at_ts = ((((t->start().bars - 1 ) * meter->divisions_per_bar()) + (t->start().beats - 1) )  * BBT_Time::ticks_per_beat) + t->start().ticks;
-
-
-                                                       double ticks_at_prev_ts = ((((prev_ts->start().bars - 1) * meter->divisions_per_bar()) + (prev_ts->start().beats - 1))  * BBT_Time::ticks_per_beat) + prev_ts->start().ticks;
-
-                                                       double ticks_relative_to_prev_ts = ticks_at_ts - ticks_at_prev_ts;
-                                                       /* assume (falsely) that the target tempo is constant */
-                                                       double length_estimate = (ticks_relative_to_prev_ts /  BBT_Time::ticks_per_beat) * meter->frames_per_grid (*t, _frame_rate);
-                                                       if (prev_ts->type() == TempoSection::Type::Constant) {
-                                                               cerr << "constant type " << endl;
-                                                               length_estimate = (ticks_relative_to_prev_ts / BBT_Time::ticks_per_beat) * prev_ts->frames_per_beat (_frame_rate);
-                                                       }
-                                                       cerr<< "initial length extimate = " << length_estimate << " ticks_relative_to_prev_ts " << ticks_relative_to_prev_ts << endl;
-                                                       double system_precision_at_target_tempo =  (_frame_rate / t->ticks_per_minute());
-                                                       cerr << " system_precision_at_target_tempo = " << system_precision_at_target_tempo << endl;
-                                                       double tick_error = system_precision_at_target_tempo + 1.0; // sorry for the wtf
-
-                                                       while (fabs (tick_error) >= system_precision_at_target_tempo) {
-
-                                                               double actual_ticks = prev_ts->tick_at_frame (length_estimate, t->beats_per_minute(), (framepos_t) length_estimate, _frame_rate);
-                                                               tick_error = ticks_relative_to_prev_ts - actual_ticks;
-                                                               length_estimate += (tick_error / BBT_Time::ticks_per_beat) * meter->frames_per_grid (*t, _frame_rate);
-                                                               //cerr << "actual ticks = " << actual_ticks << endl;
-
-                                                               //cerr << "tick error  = " << tick_error << endl;
-                                                       }
-                                                       cerr << "setting t frame to " << length_estimate + prev_ts->frame() << "tick error  = " << tick_error << endl;
-                                                       t->set_frame (length_estimate + prev_ts->frame());
-
-                                                       if (m->start() < t->start() && m->start() == prev_ts->start()) {
-                                                               m->set_frame (prev_ts->frame());
-                                                       } else if (m->start() < t->start() && m->start() > prev_ts->start()) {
-                                                               cerr << "setting m frame to " << prev_ts->frame_at_tick ((first_tick_in_new_meter - ticks_at_prev_ts), t->beats_per_minute(), (framepos_t) length_estimate, _frame_rate) << " ticks = " << first_tick_in_new_meter - ticks_at_prev_ts  << endl;
-
-                                                               m->set_frame (prev_ts->frame_at_tick ((first_tick_in_new_meter - ticks_at_prev_ts), t->beats_per_minute(), (framepos_t) length_estimate, _frame_rate));
-                                                       }
-                                               }
-                                               prev_ts = t;
-                                       }
-                               }
-                       }
-                       meter = m;
-               }
-       }
+       recompute_tempos (metrics);
+       recompute_meters (metrics);
 }
 
-
 TempoMetric
 TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const
 {
@@ -1110,7 +1337,7 @@ TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const
           now see if we can find better candidates.
        */
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
 
                if ((*i)->frame() > frame) {
                        break;
@@ -1126,6 +1353,7 @@ TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const
        return m;
 }
 
+/* XX meters only */
 TempoMetric
 TempoMap::metric_at (BBT_Time bbt) const
 {
@@ -1139,412 +1367,1445 @@ TempoMap::metric_at (BBT_Time bbt) const
           now see if we can find better candidates.
        */
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+               MeterSection* mw;
+               if ((mw = dynamic_cast<MeterSection*> (*i)) != 0) {
+                       BBT_Time section_start (mw->bbt());
 
-               BBT_Time section_start ((*i)->start());
+                       if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) {
+                               break;
+                       }
 
-               if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) {
-                       break;
+                       m.set_metric (*i);
                }
-
-               m.set_metric (*i);
        }
 
        return m;
 }
 
-void
-TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
+double
+TempoMap::beat_at_frame (const framecnt_t& frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-
-       if (frame < 0) {
-               bbt.bars = 1;
-               bbt.beats = 1;
-               bbt.ticks = 0;
-               warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg;
-               return;
-       }
-       bbt = beats_to_bbt (beat_at_frame (frame));
+       return beat_at_frame_locked (_metrics, frame);
 }
 
-int32_t
-TempoMap::bars_in_meter_section (MeterSection* ms) const
+/* meter / tempo section based */
+double
+TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
 {
-       /* YOU MUST HAVE THE READ LOCK */
-       Metrics::const_iterator i;
-
-       MeterSection* next_ms = 0;
-       const MeterSection* prev_ms = &first_meter();
+       const TempoSection& ts = tempo_section_at_frame_locked (metrics, frame);
+       MeterSection* prev_m = 0;
+       MeterSection* next_m = 0;
 
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                MeterSection* m;
                if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       if (ms->frame() < m->frame()) {
-                               next_ms = m;
+                       if (prev_m && m->frame() > frame) {
+                               next_m = m;
                                break;
                        }
-                       prev_ms = m;
+                       prev_m = m;
                }
        }
-       if (next_ms) {
-               double ticks_at_next = tick_at_frame (next_ms->frame());
-               double ticks_at_prev = tick_at_frame (prev_ms->frame());
-               double ticks_in_meter = ticks_at_next - ticks_at_prev;
+       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();
 
-               return (int32_t) floor ((ticks_in_meter / BBT_Time::ticks_per_beat) / prev_ms->note_divisor());
+       /* audio locked meters fake their beat */
+       if (next_m && next_m->beat() < beat) {
+               return next_m->beat();
        }
-       return -1;
+
+       return beat;
 }
 
-Timecode::BBT_Time
-TempoMap::beats_to_bbt (double beats)
+framecnt_t
+TempoMap::frame_at_beat (const double& beat) const
 {
-       /* CALLER HOLDS READ LOCK */
-       BBT_Time ret;
-       MeterSection* prev_ms = &first_meter();
-
-       framecnt_t frame = frame_at_beat (beats);
-       uint32_t cnt = 0;
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return frame_at_beat_locked (_metrics, beat);
+}
 
-       if (n_meters() < 2) {
-               uint32_t bars = (uint32_t) floor (beats / prev_ms->note_divisor());
-               double remaining_beats = beats - (bars *  prev_ms->note_divisor());
-               double remaining_ticks = (remaining_beats - floor (remaining_beats)) * BBT_Time::ticks_per_beat;
+/* 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);
+       const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat);
 
-               ret.ticks = (uint32_t) floor (remaining_ticks + 0.5);
-               ret.beats = (uint32_t) floor (remaining_beats);
-               ret.bars = bars;
+       return prev_t->frame_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse(), _frame_rate);
+}
 
-               /* 0 0 0 to 1 1 0 - based mapping*/
-               ++ret.bars;
-               ++ret.beats;
+Tempo
+TempoMap::tempo_at_frame (const framepos_t& frame) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return tempo_at_frame_locked (_metrics, frame);
+}
 
-               if (ret.ticks >= BBT_Time::ticks_per_beat) {
-                       ++ret.beats;
-                       ret.ticks -= BBT_Time::ticks_per_beat;
-               }
+Tempo
+TempoMap::tempo_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const
+{
+       TempoSection* prev_t = 0;
 
-               if (ret.beats > prev_ms->note_divisor()) {
-                       ++ret.bars;
-                       ret.beats = 1;
+       for (Metrics::const_iterator 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;
                }
-
-               return ret;
        }
 
-       uint32_t first_beat_in_meter = 0;
-       uint32_t accumulated_bars = 0;
-       Metrics::const_iterator i;
-
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* m = 0;
+       const double ret = prev_t->beats_per_minute();
+       const Tempo ret_tempo (ret, prev_t->note_type ());
 
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       first_beat_in_meter = beat_at_frame (m->frame());
+       return ret_tempo;
+}
 
-                       if (beats < first_beat_in_meter) {
-                               /* this is the meter after the one our beat is on*/
-                               break;
-                       }
-                       int32_t const bars_in_ms = bars_in_meter_section (m);
+/** 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_tempo_locked (_metrics, tempo);
+}
 
-                       if (bars_in_ms > 0) {
-                               accumulated_bars += bars_in_ms;
-                       }
 
-                       prev_ms = m;
-                       ++cnt;
-               }
-       }
-       //cerr << "beats to bbr with beats = " << beats << " first_beat_in_meter =  " << first_beat_in_meter << " accumulated_bars = " << accumulated_bars <<  endl;
+framepos_t
+TempoMap::frame_at_tempo_locked (const Metrics& metrics, const Tempo& tempo) const
+{
+       TempoSection* prev_t = 0;
+       const double tempo_ppm = tempo.beats_per_minute() / tempo.note_type();
 
-       if (beats > first_beat_in_meter) {
-               /* prev_ms is the relevant one here */
+       Metrics::const_iterator i;
 
-               /* now get the ticks at frame */
-               double ticks_at_frame = tick_at_frame (frame);
+       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
+               TempoSection* t;
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
 
-               /* find the number of ticks at the beginning of the meter section (bar 1)*/
-               double ticks_at_ms = tick_at_frame (prev_ms->frame());
+                       if (!t->active()) {
+                               continue;
+                       }
 
-               double beats_used_by_ms = (ticks_at_frame - ticks_at_ms) / BBT_Time::ticks_per_beat;
+                       const double t_ppm = t->beats_per_minute() / t->note_type();
 
-               uint32_t bars = (uint32_t) floor (beats_used_by_ms / prev_ms->note_divisor());
-               double remaining_beats = beats_used_by_ms - (bars *  prev_ms->note_divisor());
-               double remaining_ticks = (remaining_beats - floor (remaining_beats)) * BBT_Time::ticks_per_beat;
+                       if (t_ppm == tempo_ppm) {
+                               return t->frame();
+                       }
 
-               ret.bars = bars + accumulated_bars;
-               ret.beats = (uint32_t) floor (remaining_beats);
-               ret.ticks = (uint32_t) floor (remaining_ticks + 0.5);
+                       if (prev_t) {
+                               const double prev_t_ppm = prev_t->beats_per_minute() / prev_t->note_type();
 
-               /* now ensure we srtart at 1 1 0 */
-               ++ret.bars;
-               ++ret.beats;
-               //cerr << "part 1 ret bars = " << ret.bars << " ret beats = " << ret.beats << " ret ticks = " << ret.ticks << endl;
-               if (ret.ticks >= BBT_Time::ticks_per_beat) {
-                       ++ret.beats;
-                       ret.ticks -= BBT_Time::ticks_per_beat;
+                               if ((t_ppm > tempo_ppm && prev_t_ppm < tempo_ppm) || (t_ppm < tempo_ppm && prev_t_ppm > tempo_ppm)) {
+                                       const framepos_t ret_frame = prev_t->frame_at_tempo (tempo_ppm, prev_t->pulse(), _frame_rate);
+                                       return ret_frame;
+                               }
+                       }
+                       prev_t = t;
                }
+       }
 
-               if (ret.beats > prev_ms->note_divisor()) {
-                       ++ret.bars;
-                       ret.beats = 1;
-               }
+       return prev_t->frame();
+}
 
-               return ret;
-       }
+/** more precise than doing tempo_at_frame (frame_at_beat (b)),
+ *  as there is no intermediate frame rounding.
+ */
+Tempo
+TempoMap::tempo_at_beat (const double& beat) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       const MeterSection* prev_m = &meter_section_at_beat_locked (_metrics, beat);
+       const TempoSection* prev_t = &tempo_section_at_beat_locked (_metrics, beat);
+       const double note_type = prev_t->note_type();
 
-       /* find the number of ticks at the beginning of the meter section (bar 1)*/
-       double ticks_at_ms = tick_at_frame (prev_ms->frame());
+       return Tempo (prev_t->tempo_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse()) * note_type, note_type);
+}
 
-       /* now get the ticks at frame */
-       double ticks_at_frame = tick_at_frame (frame);
+double
+TempoMap::pulse_at_beat (const double& beat) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return pulse_at_beat_locked (_metrics, beat);
+}
 
-       double ticks_within_ms = ticks_at_frame - ticks_at_ms;
+double
+TempoMap::pulse_at_beat_locked (const Metrics& metrics, const double& beat) const
+{
+       const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat);
 
-       ret.bars = (uint32_t) floor (((ticks_within_ms / BBT_Time::ticks_per_beat) / prev_ms->note_divisor())) + accumulated_bars;
-       uint32_t remaining_ticks = ticks_within_ms - (ret.bars * prev_ms->note_divisor() * BBT_Time::ticks_per_beat);
-       ret.beats = (uint32_t) floor (remaining_ticks);
-       remaining_ticks -= ret.beats * BBT_Time::ticks_per_beat;
+       return prev_m->pulse() + ((beat - prev_m->beat()) / prev_m->note_divisor());
+}
 
-       /* only round ticks */
-       ret.ticks = (uint32_t) floor (remaining_ticks + 0.5);
+double
+TempoMap::beat_at_pulse (const double& pulse) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return beat_at_pulse_locked (_metrics, pulse);
+}
 
-       /* now ensure we srtart at 1 1 0 */
-       ++ret.bars;
-       ++ret.beats;
-       if (ret.ticks >= BBT_Time::ticks_per_beat) {
-               ++ret.beats;
-               ret.ticks -= BBT_Time::ticks_per_beat;
-       }
+double
+TempoMap::beat_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+{
+       MeterSection* prev_m = 0;
 
-       if (ret.beats > prev_ms->note_divisor()) {
-               ++ret.bars;
-               ret.beats = 1;
+       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;
+                               }
+                       }
+                       prev_m = m;
+               }
        }
 
+       double const ret = ((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat();
        return ret;
 }
 
 double
-TempoMap::tick_at_frame (framecnt_t frame) const
+TempoMap::pulse_at_frame (const framecnt_t& frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
+       return pulse_at_frame_locked (_metrics, frame);
+}
 
-       Metrics::const_iterator i;
-       const TempoSection* prev_ts = &first_tempo();
-       double accumulated_ticks = 0.0;
-       uint32_t cnt = 0;
+/* tempo section based */
+double
+TempoMap::pulse_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
+{
+       /* HOLD (at least) THE READER LOCK */
+       TempoSection* prev_t = 0;
 
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                TempoSection* t;
-
                if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-
-                       if (frame < t->frame()) {
-                               /*the previous ts is the one containing the frame */
-
-                               framepos_t time = frame - prev_ts->frame();
-                               framepos_t last_frame = t->frame() - prev_ts->frame();
-                               double last_beats_per_minute = t->beats_per_minute();
-
-                               return prev_ts->tick_at_frame (time, last_beats_per_minute, last_frame, _frame_rate) + accumulated_ticks;
+                       if (!t->active()) {
+                               continue;
                        }
-
-                       if (cnt > 0 && t->frame() > prev_ts->frame()) {
-                               framepos_t time = t->frame() - prev_ts->frame();
-                               framepos_t last_frame = t->frame() - prev_ts->frame();
-                               double last_beats_per_minute = t->beats_per_minute();
-                               accumulated_ticks += prev_ts->tick_at_frame (time, last_beats_per_minute, last_frame, _frame_rate);
+                       if (prev_t && t->frame() > frame) {
+                               /*the previous ts is the one containing the frame */
+                               const double ret = prev_t->pulse_at_frame (frame, _frame_rate);
+                               return ret;
                        }
-
-                       prev_ts = t;
-                       ++cnt;
+                       prev_t = t;
                }
        }
 
-       /* treated s linear for this ts */
-       framecnt_t frames_in_section = frame - prev_ts->frame();
-       double ticks_in_section = (frames_in_section / prev_ts->frames_per_beat (_frame_rate)) * Timecode::BBT_Time::ticks_per_beat;
-
-       return ticks_in_section + accumulated_ticks;
+       /* treated as constant for this ts */
+       const double pulses_in_section = (frame - prev_t->frame()) / prev_t->frames_per_pulse (_frame_rate);
 
+       return pulses_in_section + prev_t->pulse();
 }
 
 framecnt_t
-TempoMap::frame_at_tick (double tick) const
+TempoMap::frame_at_pulse (const double& pulse) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
+       return frame_at_pulse_locked (_metrics, pulse);
+}
 
-       double accumulated_ticks = 0.0;
-       const TempoSection* prev_ts =  &first_tempo();
-       uint32_t cnt = 0;
+/* tempo section based */
+framecnt_t
+TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+{
+       /* HOLD THE READER LOCK */
 
-       Metrics::const_iterator i;
+       const TempoSection* prev_t = 0;
 
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
 
-                       if (cnt > 0 && t->frame() > prev_ts->frame()) {
-                               framepos_t time = t->frame() - prev_ts->frame();
-                               framepos_t last_time = t->frame() - prev_ts->frame();
-                               double last_beats_per_minute = t->beats_per_minute();
-                               accumulated_ticks += prev_ts->tick_at_frame (time, last_beats_per_minute, last_time, _frame_rate);
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                       if (!t->active()) {
+                               continue;
                        }
-
-                       if (tick < accumulated_ticks) {
-                               /* prev_ts is the one affecting us. */
-
-                               double ticks_in_section = tick - tick_at_frame (prev_ts->frame());
-                               framepos_t section_start = prev_ts->frame();
-                               framepos_t last_time = t->frame() - prev_ts->frame();
-                               double last_beats_per_minute = t->beats_per_minute();
-                               return prev_ts->frame_at_tick (ticks_in_section, last_beats_per_minute, last_time, _frame_rate) + section_start;
+                       if (prev_t && t->pulse() > pulse) {
+                               return prev_t->frame_at_pulse (pulse, _frame_rate);
                        }
 
-                       prev_ts = t;
-                       ++cnt;
+                       prev_t = t;
                }
        }
-       double ticks_in_section = tick - tick_at_frame (prev_ts->frame());
-       double dtime = (ticks_in_section / BBT_Time::ticks_per_beat) * prev_ts->frames_per_beat(_frame_rate);
-       framecnt_t ret = ((framecnt_t) floor (dtime)) + prev_ts->frame();
+       /* 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;
 }
 
 double
-TempoMap::beat_at_frame (framecnt_t frame) const
+TempoMap::beat_at_bbt (const Timecode::BBT_Time& bbt)
 {
-       return tick_at_frame (frame) / BBT_Time::ticks_per_beat;
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return beat_at_bbt_locked (_metrics, bbt);
 }
 
-framecnt_t
-TempoMap::frame_at_beat (double beat) const
-{
-       return frame_at_tick (beat * BBT_Time::ticks_per_beat);
-}
 
-framepos_t
-TempoMap::frame_time (const BBT_Time& bbt)
+double
+TempoMap::beat_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
 {
-       if (bbt.bars < 1) {
-               warning << string_compose (_("tempo map asked for frame time at bar < 1  (%1)\n"), bbt) << endmsg;
-               return 0;
-       }
-
-       if (bbt.beats < 1) {
-               throw std::logic_error ("beats are counted from one");
-       }
-
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-
-       Metrics::const_iterator i;
-       uint32_t accumulated_bars = 0;
+       /* CALLER HOLDS READ LOCK */
 
-       MeterSection* prev_ms = &first_meter();
+       MeterSection* prev_m = 0;
 
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
+       /* because audio-locked meters have 'fake' integral beats,
+          there is no pulse offset here.
+       */
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                MeterSection* m;
                if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       int32_t const bims = bars_in_meter_section (m);
-
-                       if (bims < 0 || bbt.bars <= (accumulated_bars + bims)) {
-                               break;
+                       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)) {
+                                       break;
+                               }
+                       }
+                       prev_m = m;
+               }
+       }
+
+       const double remaining_bars = bbt.bars - prev_m->bbt().bars;
+       const double remaining_bars_in_beats = remaining_bars * prev_m->divisions_per_bar();
+       const double ret = remaining_bars_in_beats + prev_m->beat() + (bbt.beats - 1) + (bbt.ticks / BBT_Time::ticks_per_beat);
+
+       return ret;
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_beat (const double& beats)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return bbt_at_beat_locked (_metrics, beats);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_beat_locked (const Metrics& metrics, const double& b) const
+{
+       /* CALLER HOLDS READ LOCK */
+       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;
+
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+                       if (prev_m) {
+                               if (m->beat() > beats) {
+                                       /* this is the meter after the one our beat is on*/
+                                       break;
+                               }
+                       }
+
+                       prev_m = m;
+               }
+       }
+
+       const double beats_in_ms = beats - 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;
+}
+
+double
+TempoMap::pulse_at_bbt (const Timecode::BBT_Time& bbt)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return pulse_at_bbt_locked (_metrics, bbt);
+}
+
+double
+TempoMap::pulse_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
+{
+       /* CALLER HOLDS READ LOCK */
+
+       MeterSection* prev_m = 0;
+
+       /* because audio-locked meters have 'fake' integral beats,
+          there is no pulse offset here.
+       */
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               MeterSection* m;
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+                       if (prev_m) {
+                               if (m->bbt().bars > bbt.bars) {
+                                       break;
+                               }
+                       }
+                       prev_m = m;
+               }
+       }
+
+       const double remaining_bars = bbt.bars - prev_m->bbt().bars;
+       const double remaining_pulses = remaining_bars * prev_m->divisions_per_bar() / prev_m->note_divisor();
+       const double ret = remaining_pulses + prev_m->pulse() + (((bbt.beats - 1) + (bbt.ticks / BBT_Time::ticks_per_beat)) / prev_m->note_divisor());
+
+       return ret;
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_pulse (const double& pulse)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return bbt_at_pulse_locked (_metrics, pulse);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+{
+       MeterSection* prev_m = 0;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               MeterSection* m = 0;
+
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+
+                       if (prev_m) {
+                               double const pulses_to_m = m->pulse() - prev_m->pulse();
+                               if (prev_m->pulse() + pulses_to_m > pulse) {
+                                       /* this is the meter after the one our beat is on*/
+                                       break;
+                               }
+                       }
+
+                       prev_m = m;
+               }
+       }
+
+       const double beats_in_ms = (pulse - prev_m->pulse()) * prev_m->note_divisor();
+       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 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;
+}
+
+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 bbt;
+       }
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return bbt_at_frame_locked (_metrics, 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_time_rt() could not lock tempo map");
+       }
+
+       return bbt_at_frame_locked (_metrics, frame);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const
+{
+       if (frame < 0) {
+               BBT_Time bbt;
+               bbt.bars = 1;
+               bbt.beats = 1;
+               bbt.ticks = 0;
+               warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg;
+               return bbt;
+       }
+       const double beat = beat_at_frame_locked (metrics, frame);
+
+       return bbt_at_beat_locked (metrics, beat);
+}
+
+framepos_t
+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;
+               return 0;
+       }
+
+       if (bbt.beats < 1) {
+               throw std::logic_error ("beats are counted from one");
+       }
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return frame_at_bbt_locked (_metrics, bbt);
+}
+
+/* meter & tempo section based */
+framepos_t
+TempoMap::frame_at_bbt_locked (const Metrics& metrics, const BBT_Time& bbt) const
+{
+       /* HOLD THE READER LOCK */
+
+       const framepos_t ret = frame_at_beat_locked (metrics, beat_at_bbt_locked (metrics, bbt));
+       return ret;
+}
+
+bool
+TempoMap::check_solved (const Metrics& metrics) const
+{
+       TempoSection* prev_t = 0;
+       MeterSection* prev_m = 0;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               TempoSection* t;
+               MeterSection* m;
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                       if (!t->active()) {
+                               continue;
+                       }
+                       if (prev_t) {
+                               /* check ordering */
+                               if ((t->frame() <= prev_t->frame()) || (t->pulse() <= prev_t->pulse())) {
+                                       return false;
+                               }
+
+                               /* precision check ensures tempo and frames align.*/
+                               if (t->frame() != prev_t->frame_at_tempo (t->pulses_per_minute(), t->pulse(), _frame_rate)) {
+                                       if (!t->locked_to_meter()) {
+                                               return false;
+                                       }
+                               }
+
+                               /* gradient limit - who knows what it should be?
+                                  things are also ok (if a little chaotic) without this
+                               */
+                               if (fabs (prev_t->c_func()) > 1000.0) {
+                                       //std::cout << "c : " << prev_t->c_func() << std::endl;
+                                       return false;
+                               }
+                       }
+                       prev_t = t;
+               }
+
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+                       if (prev_m && m->position_lock_style() == AudioTime) {
+                               TempoSection* t = const_cast<TempoSection*>(&tempo_section_at_frame_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)) {
+                                       return false;
+                               }
+                       }
+
+                       prev_m = m;
+               }
+
+       }
+
+       return true;
+}
+
+bool
+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 (!t->movable()) {
+                               t->set_active (true);
+                               continue;
+                       }
+                       if (t->movable() && t->active () && t->position_lock_style() == AudioTime && t->frame() < frame) {
+                               t->set_active (false);
+                               t->set_pulse (0.0);
+                       } else if (t->movable() && t->position_lock_style() == AudioTime && t->frame() > frame) {
+                               t->set_active (true);
+                       } else if (t->movable() && t->position_lock_style() == AudioTime && t->frame() == frame) {
+                               return false;
+                       }
+               }
+       }
+       return true;
+}
+
+bool
+TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const framepos_t& frame)
+{
+       TempoSection* prev_t = 0;
+       TempoSection* section_prev = 0;
+       framepos_t first_m_frame = 0;
+
+       /* can't move a tempo before the first meter */
+       for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+               MeterSection* m;
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+                       if (!m->movable()) {
+                               first_m_frame = m->frame();
+                               break;
+                       }
+               }
+       }
+       if (section->movable() && frame <= first_m_frame) {
+               return false;
+       }
+
+       section->set_active (true);
+       section->set_frame (frame);
+
+       for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+               TempoSection* t;
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+
+                       if (!t->active()) {
+                               continue;
+                       }
+                       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));
+                               } else {
+                                       prev_t->set_c_func (prev_t->compute_c_func_frame (t->pulses_per_minute(), t->frame(), _frame_rate));
+                                       if (!t->locked_to_meter()) {
+                                               t->set_pulse (prev_t->pulse_at_frame (t->frame(), _frame_rate));
+                                       }
+                               }
+                       }
+                       prev_t = t;
+               }
+       }
+
+       if (section_prev) {
+               section_prev->set_c_func (section_prev->compute_c_func_frame (section->pulses_per_minute(), frame, _frame_rate));
+               if (!section->locked_to_meter()) {
+                       section->set_pulse (section_prev->pulse_at_frame (frame, _frame_rate));
+               }
+       }
+
+#if (0)
+       recompute_tempos (imaginary);
+
+       if (check_solved (imaginary)) {
+               return true;
+       } else {
+               dunp (imaginary, std::cout);
+       }
+#endif
+
+       MetricSectionFrameSorter fcmp;
+       imaginary.sort (fcmp);
+
+       recompute_tempos (imaginary);
+
+       if (check_solved (imaginary)) {
+               return true;
+       }
+
+       return false;
+}
+
+bool
+TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const double& pulse)
+{
+       TempoSection* prev_t = 0;
+       TempoSection* section_prev = 0;
+
+       section->set_pulse (pulse);
+
+       for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+               TempoSection* t;
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                       if (!t->active()) {
+                               continue;
+                       }
+                       if (!t->movable()) {
+                               t->set_pulse (0.0);
+                               prev_t = t;
+                               continue;
+                       }
+                       if (prev_t) {
+                               if (t == section) {
+                                       section_prev = prev_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));
+                               } else {
+                                       prev_t->set_c_func (prev_t->compute_c_func_frame (t->pulses_per_minute(), t->frame(), _frame_rate));
+                                       if (!t->locked_to_meter()) {
+                                               t->set_pulse (prev_t->pulse_at_frame (t->frame(), _frame_rate));
+                                       }
+                               }
+                       }
+                       prev_t = t;
+               }
+       }
+
+       if (section_prev) {
+               section_prev->set_c_func (section_prev->compute_c_func_pulse (section->pulses_per_minute(), pulse, _frame_rate));
+               section->set_frame (section_prev->frame_at_pulse (pulse, _frame_rate));
+       }
+
+#if (0)
+       recompute_tempos (imaginary);
+
+       if (check_solved (imaginary)) {
+               return true;
+       } else {
+               dunp (imaginary, std::cout);
+       }
+#endif
+
+       MetricSectionSorter cmp;
+       imaginary.sort (cmp);
+
+       recompute_tempos (imaginary);
+       /* Reordering
+        * XX need a restriction here, but only for this case,
+        * as audio locked tempos don't interact in the same way.
+        *
+        * With music-locked tempos, the solution to cross-dragging can fly off the screen
+        * e.g.
+        * |50 bpm                        |250 bpm |60 bpm
+        *                drag 250 to the pulse after 60->
+        * a clue: dragging the second 60 <- past the 250 would cause no such problem.
+        */
+       if (check_solved (imaginary)) {
+               return true;
+       }
+
+       return false;
+}
+
+bool
+TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const framepos_t& frame)
+{
+       /* disallow moving first meter past any subsequent one, and any movable meter before the first one */
+       const MeterSection* other =  &meter_section_at_frame_locked (imaginary, frame);
+       if ((!section->movable() && other->movable()) || (!other->movable() && section->movable() && other->frame() >= frame)) {
+               return false;
+       }
+
+       if (!section->movable()) {
+               /* lock the first tempo to our first meter */
+               if (!set_active_tempos (imaginary, frame)) {
+                       return false;
+               }
+       }
+
+       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()) {
+                               meter_locked_tempo = t;
+                               break;
                        }
-                       if (bims > 0 ) {
-                               accumulated_bars += bims;
+               }
+       }
+
+       if (!meter_locked_tempo) {
+               return false;
+       }
+
+       MeterSection* prev_m = 0;
+       Metrics future_map;
+       TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
+       bool solved = false;
+
+       for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+               MeterSection* m;
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+                       if (m == section){
+                               if (prev_m && section->movable()) {
+                                       const double beats = (pulse_at_frame_locked (imaginary, frame) - prev_m->pulse()) * prev_m->note_divisor();
+                                       if (beats + prev_m->beat() < section->beat()) {
+                                               /* set the frame/pulse corresponding to its musical position,
+                                                * as an earlier time than this has been requested.
+                                               */
+                                               const double new_pulse = ((section->beat() - prev_m->beat())
+                                                                         / prev_m->note_divisor()) + prev_m->pulse();
+
+                                               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);
+                                                       section->set_pulse (new_pulse);
+                                               } else {
+                                                       solved = false;
+                                               }
+
+                                               Metrics::const_iterator d = future_map.begin();
+                                               while (d != future_map.end()) {
+                                                       delete (*d);
+                                                       ++d;
+                                               }
+
+                                               if (!solved) {
+                                                       return false;
+                                               }
+                                       } else {
+                                               /* all is ok. set section's locked tempo if allowed.
+                                                  possibly disallowed if there is an adjacent audio-locked tempo.
+                                                  XX this check could possibly go. its never actually happened here.
+                                               */
+                                               MeterSection* meter_copy = const_cast<MeterSection*> (&meter_section_at_frame_locked (future_map, section->frame()));
+                                               meter_copy->set_frame (frame);
+
+                                               if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
+                                                       section->set_frame (frame);
+                                                       meter_locked_tempo->set_pulse (((section->beat() - prev_m->beat())
+                                                                                               / prev_m->note_divisor()) + prev_m->pulse());
+                                                       solve_map_frame (imaginary, meter_locked_tempo, frame);
+                                               } else {
+                                                       solved = false;
+                                               }
+
+                                               Metrics::const_iterator d = future_map.begin();
+                                               while (d != future_map.end()) {
+                                                       delete (*d);
+                                                       ++d;
+                                               }
+
+                                               if (!solved) {
+                                                       return false;
+                                               }
+                                       }
+                               } else {
+                                       /* not movable (first meter atm) */
+
+                                       tempo_copy->set_frame (frame);
+                                       tempo_copy->set_pulse (0.0);
+
+                                       if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
+                                               section->set_frame (frame);
+                                               meter_locked_tempo->set_frame (frame);
+                                               meter_locked_tempo->set_pulse (0.0);
+                                               solve_map_frame (imaginary, meter_locked_tempo, frame);
+                                       } else {
+                                               solved = false;
+                                       }
+
+                                       Metrics::const_iterator d = future_map.begin();
+                                       while (d != future_map.end()) {
+                                               delete (*d);
+                                               ++d;
+                                       }
+
+                                       if (!solved) {
+                                               return false;
+                                       }
+
+                                       pair<double, BBT_Time> b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
+                                       section->set_beat (b_bbt);
+                                       section->set_pulse (0.0);
+
+                               }
+                               break;
                        }
-                       prev_ms = m;
+
+                       prev_m = m;
                }
        }
 
-       uint32_t remaining_bars = bbt.bars - accumulated_bars - 1; // back to zero - based bars
-       double const ticks_within_prev_taken_by_remaining_bars = remaining_bars * prev_ms->note_divisor() * BBT_Time::ticks_per_beat;
-       double const ticks_after_space_used_by_bars = ((bbt.beats - 1) * BBT_Time::ticks_per_beat) + bbt.ticks; // back to zero - based beats
-       double const ticks_target = ticks_within_prev_taken_by_remaining_bars + ticks_after_space_used_by_bars;
+       MetricSectionFrameSorter fcmp;
+       imaginary.sort (fcmp);
 
-       TempoSection* prev_ts = &first_tempo();
-       double accumulated_ticks = 0.0;
-       uint32_t cnt = 0;
+       recompute_meters (imaginary);
 
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
+       return true;
+}
+
+bool
+TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Time& when)
+{
+       /* disallow setting section to an existing meter's bbt */
+       for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+               MeterSection* m;
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+                       if (m != section && m->bbt().bars == when.bars) {
+                               return false;
+                       }
+               }
+       }
+
+       MeterSection* prev_m = 0;
+       MeterSection* section_prev = 0;
+
+       for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+               MeterSection* m;
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+                       pair<double, BBT_Time> b_bbt;
+                       double new_pulse = 0.0;
+
+                       if (prev_m && m->bbt().bars > when.bars && !section_prev){
+                               section_prev = prev_m;
+                               const double beats = (when.bars - section_prev->bbt().bars) * section_prev->divisions_per_bar();
+                               const double pulse = (beats / section_prev->note_divisor()) + section_prev->pulse();
+                               pair<double, BBT_Time> b_bbt = make_pair (beats + section_prev->beat(), when);
+
+                               section->set_beat (b_bbt);
+                               section->set_pulse (pulse);
+                               section->set_frame (frame_at_pulse_locked (imaginary, pulse));
+                               prev_m = section;
+                               continue;
+                       }
+
+                       if (m->position_lock_style() == AudioTime) {
+                               TempoSection* meter_locked_tempo = 0;
+
+                               for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) {
+                                       TempoSection* t;
+                                       if ((t = dynamic_cast<TempoSection*> (*ii)) != 0) {
+                                               if ((t->locked_to_meter() || !t->movable()) && t->frame() == m->frame()) {
+                                                       meter_locked_tempo = t;
+                                                       break;
+                                               }
+                                       }
+                               }
+
+                               if (!meter_locked_tempo) {
+                                       return false;
+                               }
+
+                               if (prev_m) {
+                                       const double beats = ((m->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar());
+
+                                       if (beats + prev_m->beat() != m->beat()) {
+                                               /* tempo/ meter change caused a change in beat (bar). */
+                                               b_bbt = make_pair (beats + prev_m->beat()
+                                                                  , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0));
+                                               new_pulse = prev_m->pulse() + (beats / prev_m->note_divisor());
+                                       } else if (m->movable()) {
+                                               b_bbt = make_pair (m->beat(), m->bbt());
+                                               new_pulse = prev_m->pulse() + (beats / prev_m->note_divisor());
+                                       }
+                               } else {
+                                       b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
+                               }
+
+                               meter_locked_tempo->set_pulse (new_pulse);
+                               m->set_beat (b_bbt);
+                               m->set_pulse (new_pulse);
+
+                       } else {
+                               /* MusicTime */
+                               const double beats = ((m->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar());
+                               if (beats + prev_m->beat() != m->beat()) {
+                                       /* tempo/ meter change caused a change in beat (bar). */
+                                       b_bbt = make_pair (beats + prev_m->beat()
+                                                          , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0));
+                               } else {
+                                       b_bbt = make_pair (beats + prev_m->beat()
+                                                          , m->bbt());
+                               }
+                               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));
+                       }
+
+                       prev_m = m;
+               }
+       }
+
+       if (!section_prev) {
+
+               const double beats = (when.bars - prev_m->bbt().bars) * prev_m->divisions_per_bar();
+               const double pulse = (beats / prev_m->note_divisor()) + prev_m->pulse();
+               pair<double, BBT_Time> b_bbt = make_pair (beats + prev_m->beat(), when);
+
+               section->set_beat (b_bbt);
+               section->set_pulse (pulse);
+               section->set_frame (frame_at_pulse_locked (imaginary, pulse));
+       }
+
+       MetricSectionSorter cmp;
+       imaginary.sort (cmp);
+
+       recompute_meters (imaginary);
+
+       return true;
+}
+
+/** places a copy of _metrics into copy and returns a pointer
+ *  to section's equivalent in copy.
+ */
+TempoSection*
+TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSection* section)
+{
+       TempoSection* ret = 0;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                TempoSection* t;
+               MeterSection* m;
                if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if (t->frame() < prev_ms->frame()) {
+                       if (t == section) {
+                               ret = new TempoSection (*t);
+                               copy.push_back (ret);
                                continue;
                        }
 
-                       if (cnt > 0 && t->frame() > prev_ts->frame()) {
-                               /*find the number of ticke in this section */
-                               framepos_t const time = t->frame() - prev_ts->frame();
-                               framepos_t const last_time = t->frame() - prev_ts->frame();
-                               double const last_beats_per_minute = t->beats_per_minute();
-                               accumulated_ticks += prev_ts->tick_at_frame (time, last_beats_per_minute, last_time, _frame_rate);
+                       TempoSection* cp = new TempoSection (*t);
+                       copy.push_back (cp);
+               }
+               if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+                       MeterSection* cp = new MeterSection (*m);
+                       copy.push_back (cp);
+               }
+       }
+
+       return ret;
+}
+
+MeterSection*
+TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, MeterSection* section)
+{
+       MeterSection* ret = 0;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               TempoSection* t;
+               MeterSection* m;
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                       TempoSection* cp = new TempoSection (*t);
+                       copy.push_back (cp);
+               }
+
+               if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+                       if (m == section) {
+                               ret = new MeterSection (*m);
+                               copy.push_back (ret);
+                               continue;
                        }
+                       MeterSection* cp = new MeterSection (*m);
+                       copy.push_back (cp);
+               }
+       }
 
-                       if (ticks_target < accumulated_ticks) {
-                               double const ticks_in_section = ticks_target - tick_at_frame (prev_ts->frame());
-                               framepos_t const section_start_time = prev_ts->frame();
-                               framepos_t const last_time = t->frame() - prev_ts->frame();
-                               double const last_beats_per_minute = t->beats_per_minute();
-                               framepos_t const ret = prev_ts->frame_at_tick (ticks_in_section, last_beats_per_minute, last_time, _frame_rate) + section_start_time;
-                               return ret;
+       return ret;
+}
+
+/** answers the question "is this a valid beat position for this tempo section?".
+ *  it returns true if the tempo section can be moved to the requested bbt position,
+ *  leaving the tempo map in a solved state.
+ * @param section the tempo section to be moved
+ * @param bbt the requested new position for the tempo section
+ * @return true if the tempo section can be moved to the position, otherwise false.
+ */
+bool
+TempoMap::can_solve_bbt (TempoSection* ts, const BBT_Time& bbt)
+{
+       Metrics copy;
+       TempoSection* tempo_copy = 0;
+
+       {
+               Glib::Threads::RWLock::ReaderLock lm (lock);
+               tempo_copy = copy_metrics_and_point (_metrics, copy, ts);
+               if (!tempo_copy) {
+                       return false;
+               }
+       }
+
+       const bool ret = solve_map_pulse (copy, tempo_copy, pulse_at_bbt_locked (copy, bbt));
+
+       Metrics::const_iterator d = copy.begin();
+       while (d != copy.end()) {
+               delete (*d);
+               ++d;
+       }
+
+       return ret;
+}
+
+/**
+* This is for a gui that needs to know the pulse or frame of a tempo section if it were to be moved to some bbt time,
+* taking any possible reordering as a consequence of this into account.
+* @param section - the section to be altered
+* @param bbt - the bbt where the altered tempo will fall
+* @return returns - the position in pulses and frames (as a pair) where the new tempo section will lie.
+*/
+pair<double, framepos_t>
+TempoMap::predict_tempo_position (TempoSection* section, const BBT_Time& bbt)
+{
+       Metrics future_map;
+       pair<double, framepos_t> ret = make_pair (0.0, 0);
+
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, section);
+
+       const double beat = beat_at_bbt_locked (future_map, bbt);
+
+       if (solve_map_pulse (future_map, tempo_copy, pulse_at_beat_locked (future_map, beat))) {
+               ret.first = tempo_copy->pulse();
+               ret.second = tempo_copy->frame();
+       } else {
+               ret.first = section->pulse();
+               ret.second = section->frame();
+       }
+
+       Metrics::const_iterator d = future_map.begin();
+       while (d != future_map.end()) {
+               delete (*d);
+               ++d;
+       }
+       return ret;
+}
+
+void
+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);
+                       double beat = beat_at_frame_locked (future_map, frame);
+
+                       if (sub_num > 0) {
+                               beat = floor (beat) + (floor (((beat - floor (beat)) * (double) sub_num) + 0.5) / sub_num);
+                       } else if (sub_num == -2) {
+                               /* snap to beat */
+                               beat = floor (beat + 0.5);
+                       }
+
+                       double pulse = pulse_at_beat_locked (future_map, beat);
+
+                       if (sub_num == -3) {
+                               /* snap to  bar */
+                               pulse = floor (pulse + 0.5);
+                       }
+
+                       if (solve_map_pulse (future_map, tempo_copy, pulse)) {
+                               solve_map_pulse (_metrics, ts, pulse);
+                               recompute_meters (_metrics);
+                       }
+               }
+
+       } else {
+
+               {
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
+                       if (solve_map_frame (future_map, tempo_copy, frame)) {
+                               solve_map_frame (_metrics, ts, frame);
+                               recompute_meters (_metrics);
+                       }
+               }
+       }
+
+       Metrics::const_iterator d = future_map.begin();
+       while (d != future_map.end()) {
+               delete (*d);
+               ++d;
+       }
+
+       MetricPositionChanged (); // Emit Signal
+}
+
+void
+TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame)
+{
+       Metrics future_map;
+
+       if (ms->position_lock_style() == AudioTime) {
+
+               {
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
+
+                       if (solve_map_frame (future_map, copy, frame)) {
+                               solve_map_frame (_metrics, ms, frame);
+                               recompute_tempos (_metrics);
+                       }
+               }
+       } else {
+               {
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
+
+                       const double beat = beat_at_frame_locked (_metrics, frame);
+                       const Timecode::BBT_Time bbt = bbt_at_beat_locked (_metrics, beat);
+
+                       if (solve_map_bbt (future_map, copy, bbt)) {
+                               solve_map_bbt (_metrics, ms, bbt);
+                               recompute_tempos (_metrics);
+                       }
+               }
+       }
+
+       Metrics::const_iterator d = future_map.begin();
+       while (d != future_map.end()) {
+               delete (*d);
+               ++d;
+       }
+
+       MetricPositionChanged (); // Emit Signal
+}
+
+bool
+TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm)
+{
+       Metrics future_map;
+       bool can_solve = false;
+       {
+               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);
+
+               if (check_solved (future_map)) {
+                       ts->set_beats_per_minute (bpm.beats_per_minute());
+                       recompute_map (_metrics);
+                       can_solve = true;
+               }
+       }
+
+       Metrics::const_iterator d = future_map.begin();
+       while (d != future_map.end()) {
+               delete (*d);
+               ++d;
+       }
+       if (can_solve) {
+               MetricPositionChanged (); // Emit Signal
+       }
+       return can_solve;
+}
+
+void
+TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const framepos_t& end_frame, const double& pulse)
+{
+       /*
+         Ts (future prev_t)   Tnext
+         |                    |
+         |     [drag^]        |
+         |----------|----------
+               e_f  pulse(frame)
+       */
+
+       Metrics future_map;
+
+       {
+               Glib::Threads::RWLock::WriterLock lm (lock);
+
+               if (!ts) {
+                       return;
+               }
+
+               TempoSection* prev_t = copy_metrics_and_point (_metrics, future_map, ts);
+               TempoSection* prev_to_prev_t = 0;
+               const frameoffset_t fr_off = end_frame - frame;
+
+               if (prev_t && prev_t->pulse() > 0.0) {
+                       prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_frame_locked (future_map, prev_t->frame() - 1));
+               }
+
+               TempoSection* next_t = 0;
+               for (Metrics::iterator i = future_map.begin(); i != future_map.end(); ++i) {
+                       TempoSection* t = 0;
+                       if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                               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;
+
+               if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+                       contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame());
+               }
+
+               const frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
+
+               const double start_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
+               const double end_pulse = prev_t->pulse_at_frame (end_frame, _frame_rate);
+
+               double new_bpm;
+
+               if (prev_t->type() == TempoSection::Constant || prev_t->c_func() == 0.0) {
+
+                       if (prev_t->position_lock_style() == MusicTime) {
+                               if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+                                       if (frame > prev_to_prev_t->frame() + min_dframe && (frame + prev_t_frame_contribution) > prev_to_prev_t->frame() + min_dframe) {
+
+                                               new_bpm = prev_t->beats_per_minute() * ((frame - prev_to_prev_t->frame())
+                                                                                       / (double) ((frame + prev_t_frame_contribution) - prev_to_prev_t->frame()));
+                                       } else {
+                                               new_bpm = prev_t->beats_per_minute();
+                                       }
+                               } else {
+                                       /* prev to prev is irrelevant */
+
+                                       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();
+                                       }
+                               }
+                       } else {
+                               /* AudioTime */
+                               if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+                                       if (frame > prev_to_prev_t->frame() + min_dframe && end_frame > prev_to_prev_t->frame() + min_dframe) {
+
+                                               new_bpm = prev_t->beats_per_minute() * ((frame - prev_to_prev_t->frame())
+                                                                                       / (double) ((end_frame) - prev_to_prev_t->frame()));
+                                       } else {
+                                               new_bpm = prev_t->beats_per_minute();
+                                       }
+                               } else {
+                                       /* prev_to_prev_t is irrelevant */
+
+                                       if (frame > prev_t->frame() + min_dframe && end_frame > prev_t->frame() + min_dframe) {
+                                               new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame()) / (double) (end_frame - prev_t->frame()));
+                                       } else {
+                                               new_bpm = prev_t->beats_per_minute();
+                                       }
+                               }
+                       }
+               } else {
+
+                       double frame_ratio = 1.0;
+                       double pulse_ratio = 1.0;
+                       const framepos_t pulse_pos = prev_t->frame_at_pulse (pulse, _frame_rate);
+
+                       if (prev_to_prev_t) {
+                               if (pulse_pos > prev_to_prev_t->frame() + min_dframe && (pulse_pos - fr_off) > prev_to_prev_t->frame() + min_dframe) {
+                                       frame_ratio = (((pulse_pos - fr_off) - prev_to_prev_t->frame()) / (double) ((pulse_pos) - prev_to_prev_t->frame()));
+                               }
+                               if (end_pulse > prev_to_prev_t->pulse() && start_pulse > prev_to_prev_t->pulse()) {
+                                       pulse_ratio = ((start_pulse - prev_to_prev_t->pulse()) / (end_pulse - prev_to_prev_t->pulse()));
+                               }
+                       } else {
+                               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_meters (future_map);
 
-                       prev_ts = t;
-                       ++cnt;
+               if (check_solved (future_map)) {
+                       ts->set_beats_per_minute (new_bpm);
+                       recompute_tempos (_metrics);
+                       recompute_meters (_metrics);
                }
        }
 
-       /*treat this ts as constant tempo */
-       double const ticks_in_this_ts = ticks_target - tick_at_frame (prev_ts->frame());
-       double const dtime = (ticks_in_this_ts / BBT_Time::ticks_per_beat) * prev_ts->frames_per_beat(_frame_rate);
-       framecnt_t const ret = ((framecnt_t) floor (dtime)) + prev_ts->frame();
-       return ret;
-}
+       Metrics::const_iterator d = future_map.begin();
+       while (d != future_map.end()) {
+               delete (*d);
+               ++d;
+       }
 
+       MetricPositionChanged (); // Emit Signal
+}
 
 framecnt_t
 TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
 {
-
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       Metrics::const_iterator i;
-       TempoSection* first = 0;
-       TempoSection* second = 0;
-
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
-               TempoSection* t;
-
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-
-                       if ((*i)->frame() > pos) {
-                               second = t;
-                               break;
-                       }
-
-                       first = t;
-               }
-       }
-       if (first && second) {
-               framepos_t const last_time = second->frame() - first->frame();
-               double const last_beats_per_minute = second->beats_per_minute();
-
-               framepos_t const time = pos - first->frame();
-               double const tick_at_time = first->tick_at_frame (time, last_beats_per_minute, last_time, _frame_rate);
-               double const bbt_ticks = bbt.ticks + (bbt.beats * BBT_Time::ticks_per_beat);
+       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;
 
-               double const time_at_bbt = first->frame_at_tick (tick_at_time + bbt_ticks, last_beats_per_minute, last_time, _frame_rate);
-
-               return time_at_bbt - time;
-       }
-
-       double const ticks = bbt.ticks + (bbt.beats * BBT_Time::ticks_per_beat);
-       return (framecnt_t) floor ((ticks / BBT_Time::ticks_per_beat) * first->frames_per_beat(_frame_rate));
+       return frame_at_beat_locked (_metrics, total_beats);
 }
 
 framepos_t
@@ -1563,10 +2824,9 @@ 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 (tick_at_frame (fr) + 0.5);
+       uint32_t ticks = (uint32_t) floor (beat_at_frame_locked (_metrics, 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;
+       uint32_t ticks_one_subdivisions_worth = (uint32_t) BBT_Time::ticks_per_beat / sub_num;
 
        ticks -= beats * BBT_Time::ticks_per_beat;
 
@@ -1650,7 +2910,10 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
                        /* on the subdivision, do nothing */
                }
        }
-       return frame_at_tick ((beats * BBT_Time::ticks_per_beat) + ticks);
+
+       const framepos_t ret_frame = frame_at_beat_locked (_metrics, beats + (ticks / BBT_Time::ticks_per_beat));
+
+       return ret_frame;
 }
 
 framepos_t
@@ -1658,9 +2921,8 @@ TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       double const beat_at_framepos = beat_at_frame (frame);
-
-       BBT_Time bbt (beats_to_bbt (beat_at_framepos));
+       const double beat_at_framepos = beat_at_frame_locked (_metrics, frame);
+       BBT_Time bbt (bbt_at_beat_locked (_metrics, beat_at_framepos));
 
        switch (type) {
        case Bar:
@@ -1668,22 +2930,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 (bbt);
+                       return frame_at_bbt_locked (_metrics, bbt);
 
                } else if (dir > 0) {
                        /* find bar following 'frame' */
                        ++bbt.bars;
                        bbt.beats = 1;
                        bbt.ticks = 0;
-                       return frame_time (bbt);
+                       return frame_at_bbt_locked (_metrics, bbt);
                } else {
                        /* true rounding: find nearest bar */
-                       framepos_t raw_ft = frame_time (bbt);
+                       framepos_t raw_ft = frame_at_bbt_locked (_metrics, bbt);
                        bbt.beats = 1;
                        bbt.ticks = 0;
-                       framepos_t prev_ft = frame_time (bbt);
+                       framepos_t prev_ft = frame_at_bbt_locked (_metrics, bbt);
                        ++bbt.bars;
-                       framepos_t next_ft = frame_time (bbt);
+                       framepos_t next_ft = frame_at_bbt_locked (_metrics, bbt);
 
                        if ((raw_ft - prev_ft) > (next_ft - prev_ft) / 2) { 
                                return next_ft;
@@ -1696,11 +2958,11 @@ TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
 
        case Beat:
                if (dir < 0) {
-                       return frame_at_beat (floor (beat_at_framepos));
+                       return frame_at_beat_locked (_metrics, floor (beat_at_framepos));
                } else if (dir > 0) {
-                       return frame_at_beat (ceil (beat_at_framepos));
+                       return frame_at_beat_locked (_metrics, ceil (beat_at_framepos));
                } else {
-                       return frame_at_beat (floor (beat_at_framepos + 0.5));
+                       return frame_at_beat_locked (_metrics, floor (beat_at_framepos + 0.5));
                }
                break;
        }
@@ -1713,49 +2975,37 @@ TempoMap::get_grid (vector<TempoMap::BBTPoint>& points,
                    framepos_t lower, framepos_t upper)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       uint32_t const upper_beat = (uint32_t) floor (beat_at_frame (upper));
-       uint32_t cnt = (uint32_t) ceil (beat_at_frame (lower));
+       int32_t cnt = ceil (beat_at_frame_locked (_metrics, lower));
+       framecnt_t pos = 0;
+       /* although the map handles negative beats, bbt doesn't. */
+       if (cnt < 0.0) {
+               cnt = 0.0;
+       }
 
-       while (cnt <= upper_beat) {
-               framecnt_t const pos = frame_at_beat (cnt);
-               MeterSection const meter = meter_section_at (pos);
-               Tempo const tempo = tempo_at (pos);
-               BBT_Time const bbt = beats_to_bbt ((double) cnt);
+       if (frame_at_beat_locked (_metrics, cnt) >= upper) {
+               return;
+       }
 
-               points.push_back (BBTPoint (meter, tempo, pos, bbt.bars, bbt.beats));
+       while (pos < upper) {
+               pos = frame_at_beat_locked (_metrics, cnt);
+               const TempoSection tempo = tempo_section_at_frame_locked (_metrics, pos);
+               const MeterSection meter = meter_section_at_frame_locked (_metrics, pos);
+               const BBT_Time bbt = bbt_at_beat_locked (_metrics, cnt);
+               points.push_back (BBTPoint (meter, tempo_at_frame_locked (_metrics, pos), pos, bbt.bars, bbt.beats, tempo.c_func()));
                ++cnt;
        }
 }
 
-TempoSection*
-TempoMap::tempo_section_after (framepos_t frame) const
+const TempoSection&
+TempoMap::tempo_section_at_frame (framepos_t frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-
-       Metrics::const_iterator i;
-       TempoSection* next = 0;
-
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
-               TempoSection* t;
-
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-
-                       if ((*i)->frame() > frame) {
-                               next = t;
-                               break;
-                       }
-               }
-       }
-
-       return next;
+       return tempo_section_at_frame_locked (_metrics, frame);
 }
 
-
 const TempoSection&
-TempoMap::tempo_section_at (framepos_t frame) const
+TempoMap::tempo_section_at_frame_locked (const Metrics& metrics, framepos_t frame) const
 {
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-
        Metrics::const_iterator i;
        TempoSection* prev = 0;
 
@@ -1763,8 +3013,10 @@ TempoMap::tempo_section_at (framepos_t frame) const
                TempoSection* t;
 
                if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-
-                       if ((*i)->frame() > frame) {
+                       if (!t->active()) {
+                               continue;
+                       }
+                       if (prev && t->frame() > frame) {
                                break;
                        }
 
@@ -1780,71 +3032,74 @@ TempoMap::tempo_section_at (framepos_t frame) const
        return *prev;
 }
 
-/* don't use this to calculate length (the tempo is only correct for this frame).
-   do that stuff based on the beat_at_frame and frame_at_beat api
-*/
-double
-TempoMap::frames_per_beat_at (framepos_t frame, framecnt_t sr) const
+const TempoSection&
+TempoMap::tempo_section_at_beat_locked (const Metrics& metrics, const double& beat) const
 {
-       Glib::Threads::RWLock::ReaderLock lm (lock);
+       TempoSection* prev_t = 0;
+       const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat);
 
-       const TempoSection* ts_at = &tempo_section_at (frame);
-       const TempoSection* ts_after = tempo_section_after (frame);
+       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;
+               }
 
-       if (ts_after) {
-               return  (60.0 * _frame_rate) / (ts_at->tempo_at_frame (frame - ts_at->frame(), ts_after->beats_per_minute(), ts_after->frame(), _frame_rate));
        }
-       /* must be treated as constant tempo */
-       return ts_at->frames_per_beat (_frame_rate);
+       return *prev_t;
 }
 
-const Tempo
-TempoMap::tempo_at (framepos_t frame) const
+/* don't use this to calculate length (the tempo is only correct for this frame).
+   do that stuff based on the beat_at_frame and frame_at_beat api
+*/
+double
+TempoMap::frames_per_beat_at (const framepos_t& frame, const framecnt_t& sr) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       TempoMetric m (metric_at (frame));
-       TempoSection* prev_ts = 0;
-
+       const TempoSection* ts_at = &tempo_section_at_frame_locked (_metrics, frame);
+       const TempoSection* ts_after = 0;
        Metrics::const_iterator i;
 
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
+       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
                TempoSection* t;
+
                if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if ((prev_ts) && t->frame() > frame) {
-                               /* this is the one past frame */
-                               framepos_t const time = frame - prev_ts->frame();
-                               framepos_t const last_time = t->frame() - prev_ts->frame();
-                               double const last_beats_per_minute = t->beats_per_minute();
-                               double const ret = prev_ts->tempo_at_frame (time, last_beats_per_minute, last_time, _frame_rate);
-                               Tempo const ret_tempo (ret, m.tempo().note_type ());
-                               return ret_tempo;
+                       if (!t->active()) {
+                               continue;
+                       }
+                       if ((*i)->frame() > frame) {
+                               ts_after = t;
+                               break;
                        }
-                       prev_ts = t;
                }
        }
 
-       return m.tempo();
-
+       if (ts_after) {
+               return  (60.0 * _frame_rate) / (ts_at->tempo_at_frame (frame, _frame_rate) * ts_at->note_type());
+       }
+       /* must be treated as constant tempo */
+       return ts_at->frames_per_beat (_frame_rate);
 }
 
 const MeterSection&
-TempoMap::meter_section_at (framepos_t frame) const
+TempoMap::meter_section_at_frame_locked (const Metrics& metrics, framepos_t frame) const
 {
-       Glib::Threads::RWLock::ReaderLock lm (lock);
        Metrics::const_iterator i;
        MeterSection* prev = 0;
 
        for (i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* t;
+               MeterSection* m;
 
-               if ((t = dynamic_cast<MeterSection*> (*i)) != 0) {
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
 
-                       if ((*i)->frame() > frame) {
+                       if (prev && (*i)->frame() > frame) {
                                break;
                        }
 
-                       prev = t;
+                       prev = m;
                }
        }
 
@@ -1856,13 +3111,108 @@ TempoMap::meter_section_at (framepos_t frame) const
        return *prev;
 }
 
+
+const MeterSection&
+TempoMap::meter_section_at_frame (framepos_t frame) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return meter_section_at_frame_locked (_metrics, frame);
+}
+
+const MeterSection&
+TempoMap::meter_section_at_beat_locked (const Metrics& metrics, const double& beat) const
+{
+       MeterSection* prev_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) {
+                               break;
+                       }
+                       prev_m = m;
+               }
+
+       }
+       return *prev_m;
+}
+
+const MeterSection&
+TempoMap::meter_section_at_beat (double beat) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return meter_section_at_beat_locked (_metrics, beat);
+}
+
 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();
 }
 
+void
+TempoMap::fix_legacy_session ()
+{
+       MeterSection* prev_m = 0;
+       TempoSection* prev_t = 0;
+
+       for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+               MeterSection* m;
+               TempoSection* t;
+
+               if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
+                       if (!m->movable()) {
+                               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_position_lock_style (AudioTime);
+                               prev_m = m;
+                               continue;
+                       }
+                       if (prev_m) {
+                               pair<double, BBT_Time> start = make_pair (((m->bbt().bars - 1) * prev_m->note_divisor())
+                                                                         + (m->bbt().beats - 1)
+                                                                         + (m->bbt().ticks / BBT_Time::ticks_per_beat)
+                                                                         , m->bbt());
+                               m->set_beat (start);
+                               const double start_beat = ((m->bbt().bars - 1) * prev_m->note_divisor())
+                                       + (m->bbt().beats - 1)
+                                       + (m->bbt().ticks / BBT_Time::ticks_per_beat);
+                               m->set_pulse (start_beat / prev_m->note_divisor());
+                       }
+                       prev_m = m;
+               } else if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
+
+                       if (!t->active()) {
+                               continue;
+                       }
+
+                       if (!t->movable()) {
+                               t->set_pulse (0.0);
+                               t->set_frame (0);
+                               t->set_position_lock_style (AudioTime);
+                               prev_t = t;
+                               continue;
+                       }
+
+                       if (prev_t) {
+                               const double beat = ((t->legacy_bbt().bars - 1) * ((prev_m) ? prev_m->note_divisor() : 4.0))
+                                       + (t->legacy_bbt().beats - 1)
+                                       + (t->legacy_bbt().ticks / BBT_Time::ticks_per_beat);
+                               if (prev_m) {
+                                       t->set_pulse (beat / prev_m->note_divisor());
+                               } else {
+                                       /* really shouldn't happen but.. */
+                                       t->set_pulse (beat / 4.0);
+                               }
+                       }
+                       prev_t = t;
+               }
+       }
+}
+
 XMLNode&
 TempoMap::get_state ()
 {
@@ -1871,7 +3221,7 @@ TempoMap::get_state ()
 
        {
                Glib::Threads::RWLock::ReaderLock lm (lock);
-               for (i = metrics.begin(); i != metrics.end(); ++i) {
+               for (i = _metrics.begin(); i != _metrics.end(); ++i) {
                        root->add_child_nocopy ((*i)->get_state());
                }
        }
@@ -1887,9 +3237,8 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 
                XMLNodeList nlist;
                XMLNodeConstIterator niter;
-               Metrics old_metrics (metrics);
-               MeterSection* last_meter = 0;
-               metrics.clear();
+               Metrics old_metrics (_metrics);
+               _metrics.clear();
 
                nlist = node.children();
 
@@ -1900,18 +3249,12 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 
                                try {
                                        TempoSection* ts = new TempoSection (*child);
-                                       metrics.push_back (ts);
-
-                                       if (ts->bar_offset() < 0.0) {
-                                               if (last_meter) {
-                                                       ts->update_bar_offset_from_bbt (*last_meter);
-                                               }
-                                       }
+                                       _metrics.push_back (ts);
                                }
 
                                catch (failed_constructor& err){
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
-                                       metrics = old_metrics;
+                                       _metrics = old_metrics;
                                        break;
                                }
 
@@ -1919,13 +3262,12 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 
                                try {
                                        MeterSection* ms = new MeterSection (*child);
-                                       metrics.push_back (ms);
-                                       last_meter = ms;
+                                       _metrics.push_back (ms);
                                }
 
                                catch (failed_constructor& err) {
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
-                                       metrics = old_metrics;
+                                       _metrics = old_metrics;
                                        break;
                                }
                        }
@@ -1933,26 +3275,42 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 
                if (niter == nlist.end()) {
                        MetricSectionSorter cmp;
-                       metrics.sort (cmp);
+                       _metrics.sort (cmp);
+               }
+
+               /* check for legacy sessions where bbt was the base musical unit for tempo */
+               for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+                       TempoSection* t;
+                       if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                               if (t->legacy_bbt().bars != 0) {
+                                       fix_legacy_session();
+                                       break;
+                               }
+                               break;
+                       }
                }
 
                /* check for multiple tempo/meters at the same location, which
                   ardour2 somehow allowed.
                */
 
-               Metrics::iterator prev = metrics.end();
-               for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
-                       if (prev != metrics.end()) {
-                               if (dynamic_cast<MeterSection*>(*prev) && dynamic_cast<MeterSection*>(*i)) {
-                                       if ((*prev)->start() == (*i)->start()) {
-                                               cerr << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg;
-                                               error << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg;
+               Metrics::iterator prev = _metrics.end();
+               for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+                       if (prev != _metrics.end()) {
+                               MeterSection* ms;
+                               MeterSection* prev_m;
+                               TempoSection* ts;
+                               TempoSection* prev_t;
+                               if ((prev_m = dynamic_cast<MeterSection*>(*prev)) != 0 && (ms = dynamic_cast<MeterSection*>(*i)) != 0) {
+                                       if (prev_m->pulse() == ms->pulse()) {
+                                               cerr << string_compose (_("Multiple meter definitions found at %1"), prev_m->pulse()) << endmsg;
+                                               error << string_compose (_("Multiple meter definitions found at %1"), prev_m->pulse()) << endmsg;
                                                return -1;
                                        }
-                               } else if (dynamic_cast<TempoSection*>(*prev) && dynamic_cast<TempoSection*>(*i)) {
-                                       if ((*prev)->start() == (*i)->start()) {
-                                               cerr << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg;
-                                               error << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg;
+                               } else if ((prev_t = dynamic_cast<TempoSection*>(*prev)) != 0 && (ts = dynamic_cast<TempoSection*>(*i)) != 0) {
+                                       if (prev_t->pulse() == ts->pulse()) {
+                                               cerr << string_compose (_("Multiple tempo definitions found at %1"), prev_t->pulse()) << endmsg;
+                                               error << string_compose (_("Multiple tempo definitions found at %1"), prev_t->pulse()) << endmsg;
                                                return -1;
                                        }
                                }
@@ -1960,7 +3318,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                        prev = i;
                }
 
-               recompute_map (true, -1);
+               recompute_map (_metrics);
        }
 
        PropertyChanged (PropertyChange ());
@@ -1969,22 +3327,30 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 }
 
 void
-TempoMap::dump (std::ostream& o) const
+TempoMap::dump (const Metrics& metrics, std::ostream& o) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
        const MeterSection* m;
        const TempoSection* t;
+       const TempoSection* prev_t = 0;
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
                if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                       o << "Tempo @ " << *i << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (movable? "
-                         << t->movable() << ')' << endl;
+                       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;
+                       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;
+                       }
+                       prev_t = t;
                } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                       o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
-                         << " (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;
 }
 
 int
@@ -1993,7 +3359,7 @@ TempoMap::n_tempos() const
        Glib::Threads::RWLock::ReaderLock lm (lock);
        int cnt = 0;
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                if (dynamic_cast<const TempoSection*>(*i) != 0) {
                        cnt++;
                }
@@ -2008,7 +3374,7 @@ TempoMap::n_meters() const
        Glib::Threads::RWLock::ReaderLock lm (lock);
        int cnt = 0;
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                if (dynamic_cast<const MeterSection*>(*i) != 0) {
                        cnt++;
                }
@@ -2022,7 +3388,7 @@ 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) {
+               for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                        if ((*i)->frame() >= where && (*i)->movable ()) {
                                (*i)->set_frame ((*i)->frame() + amount);
                        }
@@ -2050,51 +3416,85 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount)
                bool first = true;
                MetricSection* prev = 0;
 
-               for (i = metrics.begin(); i != metrics.end(); ++i) {
+               for (i = _metrics.begin(); i != _metrics.end(); ++i) {
 
                        BBT_Time bbt;
-                       TempoMetric metric (*meter, *tempo);
-
+                       //TempoMetric metric (*meter, *tempo);
+                       MeterSection* ms = const_cast<MeterSection*>(meter);
+                       TempoSection* ts = const_cast<TempoSection*>(tempo);
                        if (prev) {
-                               metric.set_start (prev->start());
-                               metric.set_frame (prev->frame());
+                               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());
+
+                               }
+                               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
                        }
 
-                       bbt_time ((*i)->frame(), bbt);
-
-                       // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
-
-                       if (first) {
-                               first = false;
-                       } else {
+                       // cerr << bbt << endl;
 
-                               if (bbt.ticks > BBT_Time::ticks_per_beat/2) {
-                                       /* round up to next beat */
-                                       bbt.beats += 1;
+                       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 = bbt_at_frame_locked (_metrics, m->frame());
 
-                               bbt.ticks = 0;
+                               // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
 
-                               if (bbt.beats != 1) {
-                                       /* round up to next bar */
-                                       bbt.bars += 1;
-                                       bbt.beats = 1;
-                               }
-                       }
+                               if (first) {
+                                       first = false;
+                               } else {
 
-                       // cerr << bbt << endl;
+                                       if (bbt.ticks > BBT_Time::ticks_per_beat/2) {
+                                               /* round up to next beat */
+                                               bbt.beats += 1;
+                                       }
 
-                       (*i)->set_start (bbt);
+                                       bbt.ticks = 0;
 
-                       if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
-                               tempo = t;
-                               // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
-                       } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
+                                       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() << " start = " << (*i)->start() <<endl;
+                               // cerr << "NEW METER, frame = " << (*i)->frame() << " beat = " << (*i)->pulse() <<endl;
                        } else {
                                fatal << _("programming error: unhandled MetricSection type") << endmsg;
                                abort(); /*NOTREACHED*/
@@ -2103,7 +3503,7 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount)
                        prev = (*i);
                }
 
-               recompute_map (true);
+               recompute_map (_metrics);
        }
 
 
@@ -2122,7 +3522,7 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount)
        bool meter_after = false; // is there a meter marker likewise?
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                        if ((*i)->frame() >= where && (*i)->frame() < where+amount) {
                                metric_kill_list.push_back(*i);
                                TempoSection *lt = dynamic_cast<TempoSection*> (*i);
@@ -2158,12 +3558,12 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount)
 
                //remove all the remaining metrics
                for (std::list<MetricSection*>::iterator i = metric_kill_list.begin(); i != metric_kill_list.end(); ++i) {
-                       metrics.remove(*i);
+                       _metrics.remove(*i);
                        moved = true;
                }
 
                if (moved) {
-                       recompute_map (true);
+                       recompute_map (_metrics);
                }
        }
        PropertyChanged (PropertyChange ());
@@ -2176,183 +3576,43 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount)
 framepos_t
 TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const
 {
-       return frame_at_beat (beat_at_frame (pos) + beats.to_double());
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return frame_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos) + beats.to_double());
 }
 
 /** Subtract some (fractional) beats from a frame position, and return the result in frames */
 framepos_t
 TempoMap::framepos_minus_beats (framepos_t pos, Evoral::Beats beats) const
 {
-       return frame_at_beat (beat_at_frame (pos) - beats.to_double());
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return frame_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos) - beats.to_double());
 }
 
 /** Add the BBT interval op to pos and return the result */
 framepos_t
 TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 {
-       cerr << "framepos_plus_bbt - untested" << endl;
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       Metrics::const_iterator i;
-       const MeterSection* meter;
-       const MeterSection* m;
-       const TempoSection* tempo;
-       const TempoSection* next_tempo = 0;
-       const TempoSection* t;
-       double frames_per_beat;
-       framepos_t effective_pos = max (pos, (framepos_t) 0);
-
-       meter = &first_meter ();
-       tempo = &first_tempo ();
-
-       assert (meter);
-       assert (tempo);
-
-       /* find the starting metrics for tempo & meter */
-
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
-
-               if ((*i)->frame() > effective_pos) {
-                       break;
-               }
-
-               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                       tempo = t;
-               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                       meter = m;
-               }
-       }
-
-       for (i = metrics.begin(); i != metrics.end(); ++i) {
-               if ((*i)->frame() > effective_pos) {
-                       if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                               next_tempo = t;
-                       }
-                       break;
-               }
-       }
-
-       /* We now have:
-
-          meter -> the Meter for "pos"
-          tempo -> the Tempo for "pos"
-          next_tempo -> the Tempo after "pos" or 0
-          i     -> for first new metric after "pos", possibly metrics.end()
-       */
-
-       /* now comes the complicated part. we have to add one beat a time,
-          checking for a new metric on every beat.
-       */
-
-       uint64_t bars = 0;
-       /* fpb is used for constant tempo */
-       frames_per_beat = tempo->frames_per_beat (_frame_rate);
-
-       while (op.bars) {
-
-               bars++;
-               op.bars--;
-
-               /* check if we need to use a new metric section: has adding frames moved us
-                  to or after the start of the next metric section? in which case, use it.
-               */
-
-               if (i != metrics.end()) {
-                       if ((*i)->frame() <= pos) {
-
-                               /* about to change tempo or meter, so add the
-                                * number of frames for the bars we've just
-                                * traversed before we change the
-                                * frames_per_beat value.
-                                */
-
-                               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                                       next_tempo = t;
-                               }
-
-                               if (next_tempo) {
-                                       pos += tempo->frame_at_beat (bars * meter->divisions_per_bar(), next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
-                               } else {
-                                       pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
-                               }
-
-                               bars = 0;
-
-                               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                                       tempo = t;
-                               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                                       meter = m;
-                               }
-                               ++i;
-                               frames_per_beat = tempo->frames_per_beat (_frame_rate);
-                       }
-               }
-
-       }
-
-       if (next_tempo) {
-               pos += tempo->frame_at_beat (bars * meter->divisions_per_bar(), next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
-       } else {
-               pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
-       }
-
-       uint64_t beats = 0;
-
-       while (op.beats) {
-
-               /* given the current meter, have we gone past the end of the bar ? */
-
-               beats++;
-               op.beats--;
-
-               /* check if we need to use a new metric section: has adding frames moved us
-                  to or after the start of the next metric section? in which case, use it.
-               */
-
-               if (i != metrics.end()) {
-                       if ((*i)->frame() <= pos) {
-
-                               /* about to change tempo or meter, so add the
-                                * number of frames for the beats we've just
-                                * traversed before we change the
-                                * frames_per_beat value.
-                                */
-
-                               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                                       next_tempo = t;
-                               }
-
-                               if (next_tempo) {
-                                       pos += tempo->frame_at_beat (beats, next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
-                               } else {
-                                       pos += llrint (beats * frames_per_beat);
-                               }
-
-                               beats = 0;
-
-                               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
-                                       tempo = t;
-                               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
-                                       meter = m;
-                               }
-                               ++i;
-                               frames_per_beat = tempo->frames_per_beat (_frame_rate);
-                       }
-               }
-       }
-
-       if (next_tempo) {
-               pos += tempo->frame_at_beat (beats, next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
-       } else {
-               pos += llrint (beats * frames_per_beat);
+       BBT_Time pos_bbt = bbt_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos));
+       pos_bbt.ticks += op.ticks;
+       if (pos_bbt.ticks >= BBT_Time::ticks_per_beat) {
+               ++pos_bbt.beats;
+               pos_bbt.ticks -= BBT_Time::ticks_per_beat;
        }
-
-       if (op.ticks) {
-               pos += tempo->frame_at_tick (op.ticks, next_tempo->beats_per_minute(), next_tempo->frame(), _frame_rate);
+       pos_bbt.beats += op.beats;
+       /* the meter in effect will start on the bar */
+       double divisions_per_bar = meter_section_at_beat (beat_at_bbt_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
+       while (pos_bbt.beats >= divisions_per_bar + 1) {
+               ++pos_bbt.bars;
+               divisions_per_bar = meter_section_at_beat (beat_at_bbt_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
+               pos_bbt.beats -= divisions_per_bar;
        }
+       pos_bbt.bars += op.bars;
 
-       return pos;
-
+       return frame_at_bbt_locked (_metrics, pos_bbt);
 }
 
 /** Count the number of beats that are equivalent to distance when going forward,
@@ -2361,7 +3621,9 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 Evoral::Beats
 TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 {
-       return Evoral::Beats(beat_at_frame (pos + distance) - beat_at_frame (pos));
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return Evoral::Beats (beat_at_frame_locked (_metrics, pos + distance) - beat_at_frame_locked (_metrics, pos));
 }
 
 struct bbtcmp {
@@ -2383,7 +3645,7 @@ operator<< (std::ostream& o, const Tempo& t) {
 std::ostream&
 operator<< (std::ostream& o, const MetricSection& section) {
 
-       o << "MetricSection @ " << section.frame() << " aka " << section.start() << ' ';
+       o << "MetricSection @ " << section.frame() << ' ';
 
        const TempoSection* ts;
        const MeterSection* ms;