Tempo ramps - rename meter_at() -> meter_at_frame() & make the ticker slightly more...
[ardour.git] / libs / ardour / tempo.cc
index 32272788759660fd6c54f22d27b177008da666c3..36ac3a8203d03703ffccb8b2a001615d3eec14d5 100644 (file)
@@ -73,13 +73,13 @@ Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
 const string TempoSection::xml_state_node_name = "Tempo";
 
 TempoSection::TempoSection (const XMLNode& node)
-       : MetricSection (0.0)
+       : MetricSection (0.0, 0, MusicTime)
        , Tempo (TempoMap::default_tempo())
        , _c_func (0.0)
        , _active (true)
        , _locked_to_meter (false)
 {
-       const XMLProperty *prop;
+       XMLProperty const * prop;
        LocaleGuard lg;
        BBT_Time bbt;
        double pulse;
@@ -453,12 +453,10 @@ TempoSection::time_at_pulse (const double& pulse) const
 const string MeterSection::xml_state_node_name = "Meter";
 
 MeterSection::MeterSection (const XMLNode& node)
-       : MetricSection (0.0), Meter (TempoMap::default_meter())
+       : MetricSection (0.0, 0, MusicTime), Meter (TempoMap::default_meter())
 {
        XMLProperty const * prop;
-       BBT_Time start;
        LocaleGuard lg;
-       const XMLProperty *prop;
        BBT_Time bbt;
        double pulse = 0.0;
        double beat = 0.0;
@@ -588,10 +586,9 @@ MeterSection::get_state() const
 /*
   Tempo Map Overview
 
-  Tempo can be thought of as a source of the musical pulse.
-  Meters divide that pulse into measures and beats.
-  Tempo pulses can be divided to be in sympathy with the meter, but this does not affect the beat
-  at any particular time.
+  Tempo is the rate of the musical pulse.
+  Meters divide the pulses into measures and beats.
+
   Note that Tempo::beats_per_minute() has nothing to do with musical beats.
   It should rather be thought of as tempo note divisions per minute.
 
@@ -604,6 +601,19 @@ MeterSection::get_state() const
 
   Having done this, we can now find any one of tempo, beat, frame or pulse if a beat, frame, pulse or tempo is known.
 
+  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())
+
   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.
@@ -639,8 +649,8 @@ TempoMap::TempoMap (framecnt_t fr)
        _frame_rate = fr;
        BBT_Time start (1, 1, 0);
 
-       TempoSection *t = new TempoSection ((framepos_t) 0, _default_tempo.beats_per_minute(), _default_tempo.note_type(), TempoSection::Constant);
-       MeterSection *m = new MeterSection ((framepos_t) 0, 0.0, start, _default_meter.divisions_per_bar(), _default_meter.note_divisor());
+       TempoSection *t = new TempoSection (0.0, 0, _default_tempo.beats_per_minute(), _default_tempo.note_type(), TempoSection::Ramp, AudioTime);
+       MeterSection *m = new MeterSection (0.0, 0, 0.0, start, _default_meter.divisions_per_bar(), _default_meter.note_divisor(), AudioTime);
 
        t->set_movable (false);
        m->set_movable (false);
@@ -765,10 +775,10 @@ TempoMap::do_insert (MetricSection* section)
 
                if ((m->bbt().beats != 1) || (m->bbt().ticks != 0)) {
 
-                       pair<double, BBT_Time> corrected = make_pair (m->pulse(), m->bbt());
+                       pair<double, BBT_Time> corrected = make_pair (m->beat(), m->bbt());
                        corrected.second.beats = 1;
                        corrected.second.ticks = 0;
-                       corrected.first = bbt_to_beats_locked (_metrics, corrected.second);
+                       corrected.first = beat_at_bbt_locked (_metrics, corrected.second);
                        warning << string_compose (_("Meter changes can only be positioned on the first beat of a bar. Moving from %1 to %2"),
                                                   m->bbt(), corrected.second) << endmsg;
                        //m->set_pulse (corrected);
@@ -883,40 +893,36 @@ TempoMap::do_insert (MetricSection* section)
        }
 }
 
-void
-TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const double& pulse, TempoSection::Type type)
+TempoSection*
+TempoMap::add_tempo (const Tempo& tempo, const double& pulse, const framepos_t& frame, ARDOUR::TempoSection::Type type, PositionLockStyle pls)
 {
+       TempoSection* ts = 0;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection& first (first_tempo());
-               if (ts.pulse() != first.pulse()) {
-                       remove_tempo_locked (ts);
-                       add_tempo_locked (tempo, pulse, true, type);
-               } else {
-                       first.set_type (type);
-                       {
-                               /* cannot move the first tempo section */
-                               *static_cast<Tempo*>(&first) = tempo;
-                               recompute_map (_metrics);
-                       }
-               }
+               ts = add_tempo_locked (tempo, pulse, frame, type, pls, true);
        }
 
+
        PropertyChanged (PropertyChange ());
+
+       return ts;
 }
 
 void
-TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const framepos_t& frame, TempoSection::Type type)
+TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const double& pulse, const framepos_t& frame, TempoSection::Type type, PositionLockStyle pls)
 {
+       const bool locked_to_meter = ts.locked_to_meter();
+
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
                TempoSection& first (first_tempo());
                if (ts.frame() != first.frame()) {
                        remove_tempo_locked (ts);
-                       add_tempo_locked (tempo, frame, true, type);
+                       add_tempo_locked (tempo, pulse, frame, type, pls, true, locked_to_meter);
                } else {
                        first.set_type (type);
                        first.set_pulse (0.0);
+                       first.set_frame (frame);
                        first.set_position_lock_style (AudioTime);
                        {
                                /* cannot move the first tempo section */
@@ -930,97 +936,56 @@ TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const frame
 }
 
 TempoSection*
-TempoMap::add_tempo (const Tempo& tempo, const double& pulse, ARDOUR::TempoSection::Type type)
-{
-       TempoSection* ts = 0;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               ts = add_tempo_locked (tempo, pulse, true, type);
-       }
-
-       PropertyChanged (PropertyChange ());
-
-       return ts;
-}
-
-TempoSection*
-TempoMap::add_tempo (const Tempo& tempo, const framepos_t& frame, ARDOUR::TempoSection::Type type)
-{
-       TempoSection* ts = 0;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               ts = add_tempo_locked (tempo, frame, true, type);
-       }
-
-
-       PropertyChanged (PropertyChange ());
-
-       return ts;
-}
-
-TempoSection*
-TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, bool recompute, ARDOUR::TempoSection::Type type)
+TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, framepos_t frame
+                           , TempoSection::Type type, PositionLockStyle pls, bool recompute, bool locked_to_meter)
 {
-       TempoSection* t = new TempoSection (pulse, tempo.beats_per_minute(), tempo.note_type(), type);
+       TempoSection* t = new TempoSection (pulse, frame, tempo.beats_per_minute(), tempo.note_type(), type, pls);
+       t->set_locked_to_meter (locked_to_meter);
 
        do_insert (t);
 
        if (recompute) {
-               solve_map (_metrics, t, t->pulse());
-               recompute_meters (_metrics);
-       }
-
-       return t;
-}
-
-TempoSection*
-TempoMap::add_tempo_locked (const Tempo& tempo, framepos_t frame, bool recompute, ARDOUR::TempoSection::Type type)
-{
-       TempoSection* t = new TempoSection (frame, tempo.beats_per_minute(), tempo.note_type(), type);
-
-       do_insert (t);
-
-       if (recompute) {
-               solve_map (_metrics, t, t->frame());
+               if (pls == AudioTime) {
+                       solve_map_frame (_metrics, t, t->frame());
+               } else {
+                       solve_map_pulse (_metrics, t, t->pulse());
+               }
                recompute_meters (_metrics);
        }
 
        return t;
 }
 
-void
-TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
+MeterSection*
+TempoMap::add_meter (const Meter& meter, const double& beat, const Timecode::BBT_Time& where, const framepos_t& frame, PositionLockStyle pls)
 {
+       MeterSection* m = 0;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
+               m = add_meter_locked (meter, beat, where, frame, pls, true);
+       }
 
-               if (ms.movable()) {
-                       remove_meter_locked (ms);
-                       add_meter_locked (meter, bbt_to_beats_locked (_metrics, where), where, true);
-               } else {
-                       MeterSection& first (first_meter());
-                       /* cannot move the first meter section */
-                       *static_cast<Meter*>(&first) = meter;
-                       first.set_position_lock_style (AudioTime);
-               }
-               recompute_map (_metrics);
+
+#ifndef NDEBUG
+       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
+               dump (_metrics, std::cerr);
        }
+#endif
 
        PropertyChanged (PropertyChange ());
+       return m;
 }
 
 void
-TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const framepos_t& frame)
+TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where, const framepos_t& frame, PositionLockStyle pls)
 {
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-
-               const double beat = ms.beat();
-               const BBT_Time bbt = ms.bbt();
+               const double beat = beat_at_bbt_locked (_metrics, where);
 
                if (ms.movable()) {
                        remove_meter_locked (ms);
-                       add_meter_locked (meter, frame, beat, bbt, true);
+                       add_meter_locked (meter, beat, where, frame, pls, true);
                } else {
                        MeterSection& first (first_meter());
                        TempoSection& first_t (first_tempo());
@@ -1034,93 +999,35 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const frame
                        first_t.set_frame (first.frame());
                        first_t.set_pulse (0.0);
                        first_t.set_position_lock_style (AudioTime);
+                       recompute_map (_metrics);
                }
-               recompute_map (_metrics);
-       }
-       PropertyChanged (PropertyChange ());
-}
-
-
-MeterSection*
-TempoMap::add_meter (const Meter& meter, const double& beat, const BBT_Time& where)
-{
-       MeterSection* m = 0;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               m = add_meter_locked (meter, beat, where, true);
-       }
-
-
-#ifndef NDEBUG
-       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
-               dump (_metrics, std::cerr);
        }
-#endif
 
        PropertyChanged (PropertyChange ());
-
-       return m;
 }
 
 MeterSection*
-TempoMap::add_meter (const Meter& meter, const framepos_t& frame, const double& beat, const Timecode::BBT_Time& where)
+TempoMap::add_meter_locked (const Meter& meter, double beat, const Timecode::BBT_Time& where, framepos_t frame, PositionLockStyle pls, bool recompute)
 {
-       MeterSection* m = 0;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               m = add_meter_locked (meter, frame, beat, where, true);
-       }
-
+       const MeterSection& prev_m = meter_section_at_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();
 
-#ifndef NDEBUG
-       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
-               dump (_metrics, std::cerr);
+       if (pls == AudioTime) {
+               /* add meter-locked tempo */
+               add_tempo_locked (tempo_at_frame_locked (_metrics, frame), pulse,  frame, TempoSection::Ramp, AudioTime, true, true);
        }
-#endif
 
-       PropertyChanged (PropertyChange ());
-
-       return m;
-}
-
-MeterSection*
-TempoMap::add_meter_locked (const Meter& meter, double beat, const BBT_Time& where, bool recompute)
-{
-       /* a new meter always starts a new bar on the first beat. so
-          round the start time appropriately. remember that
-          `where' is based on the existing tempo map, not
-          the result after we insert the new meter.
-
-       */
-
-       const double pulse = pulse_at_beat_locked (_metrics, beat);
-       MeterSection* new_meter = new MeterSection (pulse, beat, where, meter.divisions_per_bar(), meter.note_divisor());
+       MeterSection* new_meter = new MeterSection (pulse, frame, beat, where, meter.divisions_per_bar(), meter.note_divisor(), pls);
 
        do_insert (new_meter);
 
        if (recompute) {
-               solve_map (_metrics, new_meter, where);
-       }
-
-       return new_meter;
-}
-
-MeterSection*
-TempoMap::add_meter_locked (const Meter& meter, framepos_t frame, double beat, const Timecode::BBT_Time& where, bool recompute)
-{
-       /* add meter-locked tempo */
-       TempoSection* t = add_tempo_locked (tempo_at_locked (_metrics, frame), frame, true, TempoSection::Ramp);
-       if (t) {
-               t->set_locked_to_meter (true);
-       }
-
-       MeterSection* new_meter = new MeterSection (frame, beat, where, meter.divisions_per_bar(), meter.note_divisor());
-       new_meter->set_pulse (pulse_at_frame_locked (_metrics, frame));
-
-       do_insert (new_meter);
 
-       if (recompute) {
-               solve_map (_metrics, new_meter, frame);
+               if (pls == AudioTime) {
+                       solve_map_frame (_metrics, new_meter, frame);
+               } else {
+                       solve_map_bbt (_metrics, new_meter, where);
+               }
        }
 
        return new_meter;
@@ -1317,7 +1224,6 @@ TempoMap::recompute_tempos (Metrics& metrics)
 
 /* tempos must be positioned correctly.
    the current approach is to use a meter's bbt time as its base position unit.
-   this means that a meter's beat may change, but its bbt may not.
    an audio-locked meter requires a recomputation of pulse and beat (but not bbt),
    while a music-locked meter requires recomputations of frame pulse and beat (but not bbt)
 */
@@ -1367,24 +1273,24 @@ TempoMap::recompute_meters (Metrics& metrics)
                        } else {
                                /* MusicTime */
                                double pulse = 0.0;
-                               pair<double, BBT_Time> new_beat;
+                               pair<double, BBT_Time> b_bbt;
                                if (prev_m) {
                                        const double beats = (meter->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar();
                                        if (beats + prev_m->beat() != meter->beat()) {
                                                /* reordering caused a bbt change */
-                                               new_beat = make_pair (beats + prev_m->beat()
+                                               b_bbt = make_pair (beats + prev_m->beat()
                                                                   , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0));
                                        } else {
-                                               new_beat = make_pair (beats + prev_m->beat(), meter->bbt());
+                                               b_bbt = make_pair (beats + prev_m->beat(), meter->bbt());
                                        }
                                        pulse = (beats / prev_m->note_divisor()) + prev_m->pulse();
                                } else {
                                        /* shouldn't happen - the first is audio-locked */
                                        pulse = pulse_at_beat_locked (metrics, meter->beat());
-                                       new_beat = make_pair (meter->beat(), meter->bbt());
+                                       b_bbt = make_pair (meter->beat(), meter->bbt());
                                }
 
-                               meter->set_beat (new_beat);
+                               meter->set_beat (b_bbt);
                                meter->set_pulse (pulse);
                                meter->set_frame (frame_at_pulse_locked (metrics, pulse));
                        }
@@ -1479,6 +1385,77 @@ TempoMap::metric_at (BBT_Time bbt) const
        return m;
 }
 
+double
+TempoMap::beat_at_frame (const framecnt_t& frame) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return beat_at_frame_locked (_metrics, frame);
+}
+
+/* meter / tempo section based */
+double
+TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
+{
+       const TempoSection& ts = tempo_section_at_locked (metrics, frame);
+       MeterSection* prev_m = 0;
+       MeterSection* next_m = 0;
+
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               MeterSection* m;
+               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+                       if (prev_m && m->frame() > frame) {
+                               next_m = m;
+                               break;
+                       }
+                       prev_m = m;
+               }
+       }
+       if (frame < prev_m->frame()) {
+               return 0.0;
+       }
+       const double beat = prev_m->beat() + (ts.pulse_at_frame (frame, _frame_rate) - prev_m->pulse()) * prev_m->note_divisor();
+
+       if (next_m && next_m->beat() < beat) {
+               return next_m->beat();
+       }
+
+       return beat;
+}
+
+framecnt_t
+TempoMap::frame_at_beat (const double& beat) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return frame_at_beat_locked (_metrics, 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);
+       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_t.frame_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse(), _frame_rate);
+}
+
+double
+TempoMap::pulse_at_beat (const double& beat) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return pulse_at_beat_locked (_metrics, beat);
+}
+
 double
 TempoMap::pulse_at_beat_locked (const Metrics& metrics, const double& beat) const
 {
@@ -1499,10 +1476,10 @@ TempoMap::pulse_at_beat_locked (const Metrics& metrics, const double& beat) cons
 }
 
 double
-TempoMap::pulse_at_beat (const double& beat) const
+TempoMap::beat_at_pulse (const double& pulse) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return pulse_at_beat_locked (_metrics, beat);
+       return beat_at_pulse_locked (_metrics, pulse);
 }
 
 double
@@ -1527,10 +1504,10 @@ TempoMap::beat_at_pulse_locked (const Metrics& metrics, const double& pulse) con
 }
 
 double
-TempoMap::beat_at_pulse (const double& pulse) const
+TempoMap::pulse_at_frame (const framecnt_t& frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return beat_at_pulse_locked (_metrics, pulse);
+       return pulse_at_frame_locked (_metrics, frame);
 }
 
 /* tempo section based */
@@ -1561,11 +1538,11 @@ TempoMap::pulse_at_frame_locked (const Metrics& metrics, const framecnt_t& frame
        return pulses_in_section + prev_t->pulse();
 }
 
-double
-TempoMap::pulse_at_frame (const framecnt_t& frame) const
+framecnt_t
+TempoMap::frame_at_pulse (const double& pulse) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return pulse_at_frame_locked (_metrics, frame);
+       return frame_at_pulse_locked (_metrics, pulse);
 }
 
 /* tempo section based */
@@ -1599,79 +1576,53 @@ TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) co
        return ret;
 }
 
-framecnt_t
-TempoMap::frame_at_pulse (const double& pulse) const
+const Tempo
+TempoMap::tempo_at_frame (const framepos_t& frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return frame_at_pulse_locked (_metrics, pulse);
+       return tempo_at_frame_locked (_metrics, frame);
 }
 
-/* meter section based */
-double
-TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
+const Tempo
+TempoMap::tempo_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const
 {
-       const TempoSection& ts = tempo_section_at_locked (metrics, frame);
-       MeterSection* prev_m = 0;
-       MeterSection* next_m = 0;
+       TempoSection* prev_t = 0;
 
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               MeterSection* m;
-               if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       if (prev_m && m->frame() > frame) {
-                               next_m = m;
-                               break;
+       Metrics::const_iterator i;
+
+       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
+               TempoSection* t;
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                       if (!t->active()) {
+                               continue;
                        }
-                       prev_m = m;
+                       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;
                }
        }
-       if (frame < prev_m->frame()) {
-               return 0.0;
-       }
-       const double beat = prev_m->beat() + (ts.pulse_at_frame (frame, _frame_rate) - prev_m->pulse()) * prev_m->note_divisor();
 
-       if (next_m && next_m->beat() < beat) {
-               return next_m->beat();
-       }
+       const double ret = prev_t->beats_per_minute();
+       const Tempo ret_tempo (ret, prev_t->note_type ());
 
-       return beat;
+       return ret_tempo;
 }
 
+
 double
-TempoMap::beat_at_frame (const framecnt_t& frame) const
+TempoMap::beat_at_bbt (const Timecode::BBT_Time& bbt)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return beat_at_frame_locked (_metrics, frame);
+       return beat_at_bbt_locked (_metrics, bbt);
 }
 
-/* meter section based */
-framecnt_t
-TempoMap::frame_at_beat_locked (const Metrics& metrics, const double& beat) const
-{
-       const TempoSection& prev_t = tempo_section_at_beat_locked (metrics, beat);
-       MeterSection* prev_m = 0;
-
-       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_t.frame_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse(), _frame_rate);
-}
-
-framecnt_t
-TempoMap::frame_at_beat (const double& beat) const
-{
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-       return frame_at_beat_locked (_metrics, beat);
-}
 
 double
-TempoMap::bbt_to_beats_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
+TempoMap::beat_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
 {
        /* CALLER HOLDS READ LOCK */
 
@@ -1700,19 +1651,19 @@ TempoMap::bbt_to_beats_locked (const Metrics& metrics, const Timecode::BBT_Time&
        return ret;
 }
 
-double
-TempoMap::bbt_to_beats (const Timecode::BBT_Time& bbt)
+Timecode::BBT_Time
+TempoMap::bbt_at_beat (const double& beats)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return bbt_to_beats_locked (_metrics, bbt);
+       return bbt_at_beat_locked (_metrics, beats);
 }
 
 Timecode::BBT_Time
-TempoMap::beats_to_bbt_locked (const Metrics& metrics, const double& b) const
+TempoMap::bbt_at_beat_locked (const Metrics& metrics, const double& b) const
 {
        /* CALLER HOLDS READ LOCK */
        MeterSection* prev_m = 0;
-       const double beats = (b < 0.0) ? 0.0 : b;
+       const double beats = max (0.0, b);
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                MeterSection* m = 0;
@@ -1758,20 +1709,57 @@ TempoMap::beats_to_bbt_locked (const Metrics& metrics, const double& b) const
        return ret;
 }
 
-Timecode::BBT_Time
-TempoMap::beats_to_bbt (const double& beats)
+double
+TempoMap::pulse_at_bbt (const Timecode::BBT_Time& bbt)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return beats_to_bbt_locked (_metrics, beats);
+
+       return pulse_at_bbt_locked (_metrics, bbt);
+}
+
+double
+TempoMap::pulse_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
+{
+       /* CALLER HOLDS READ LOCK */
+
+       MeterSection* prev_m = 0;
+
+       /* because audio-locked meters have 'fake' integral beats,
+          there is no pulse offset here.
+       */
+       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::pulse_to_bbt (const double& pulse)
+TempoMap::bbt_at_pulse (const double& pulse)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return bbt_at_pulse_locked (_metrics, pulse);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+{
        MeterSection* prev_m = 0;
 
-       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                MeterSection* m = 0;
 
                if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
@@ -1831,17 +1819,7 @@ TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
        Glib::Threads::RWLock::ReaderLock lm (lock);
        const double beat = beat_at_frame_locked (_metrics, frame);
 
-       bbt = beats_to_bbt_locked (_metrics, beat);
-}
-
-/* meter section based */
-framepos_t
-TempoMap::frame_time_locked (const Metrics& metrics, const BBT_Time& bbt) const
-{
-       /* HOLD THE READER LOCK */
-
-       const framepos_t ret = frame_at_beat_locked (metrics, bbt_to_beats_locked (metrics, bbt));
-       return ret;
+       bbt = bbt_at_beat_locked (_metrics, beat);
 }
 
 framepos_t
@@ -1860,8 +1838,18 @@ TempoMap::frame_time (const BBT_Time& bbt)
        return frame_time_locked (_metrics, bbt);
 }
 
+/* meter section based */
+framepos_t
+TempoMap::frame_time_locked (const Metrics& metrics, const BBT_Time& bbt) const
+{
+       /* HOLD THE READER LOCK */
+
+       const framepos_t ret = frame_at_beat_locked (metrics, beat_at_bbt_locked (metrics, bbt));
+       return ret;
+}
+
 bool
-TempoMap::check_solved (const Metrics& metrics, bool by_frame) const
+TempoMap::check_solved (const Metrics& metrics) const
 {
        TempoSection* prev_t = 0;
        MeterSection* prev_m = 0;
@@ -1874,16 +1862,12 @@ TempoMap::check_solved (const Metrics& metrics, bool by_frame) const
                                continue;
                        }
                        if (prev_t) {
-                               if ((by_frame && t->frame() < prev_t->frame()) || (!by_frame && t->pulse() < prev_t->pulse())) {
-                                       return false;
-                               }
-
-                               if (t->frame() == prev_t->frame()) {
+                               if ((t->frame() <= prev_t->frame()) || (t->pulse() <= prev_t->pulse())) {
                                        return false;
                                }
 
-                               /* precision check ensures pulses and frames align.*/
-                               if (t->frame() != prev_t->frame_at_pulse (t->pulse(), _frame_rate)) {
+                               /* precision check ensures tempo and frames align.*/
+                               if (t->frame() != prev_t->frame_at_tempo (t->pulses_per_minute(), t->pulse(), _frame_rate)) {
                                        if (!t->locked_to_meter()) {
                                                return false;
                                        }
@@ -1935,7 +1919,7 @@ TempoMap::set_active_tempos (const Metrics& metrics, const framepos_t& frame)
 }
 
 bool
-TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const framepos_t& frame)
+TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const framepos_t& frame)
 {
        TempoSection* prev_t = 0;
        TempoSection* section_prev = 0;
@@ -1990,31 +1974,18 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const framepos_t
                }
        }
 
-       if (section->position_lock_style() == MusicTime) {
-               /* we're setting the frame */
-               section->set_position_lock_style (AudioTime);
-               recompute_tempos (imaginary);
-               section->set_position_lock_style (MusicTime);
-       } else {
-               recompute_tempos (imaginary);
-       }
+       recompute_tempos (imaginary);
 
-       if (check_solved (imaginary, true)) {
+       if (check_solved (imaginary)) {
                return true;
        }
 
        MetricSectionFrameSorter fcmp;
        imaginary.sort (fcmp);
-       if (section->position_lock_style() == MusicTime) {
-               /* we're setting the frame */
-               section->set_position_lock_style (AudioTime);
-               recompute_tempos (imaginary);
-               section->set_position_lock_style (MusicTime);
-       } else {
-               recompute_tempos (imaginary);
-       }
 
-       if (check_solved (imaginary, true)) {
+       recompute_tempos (imaginary);
+
+       if (check_solved (imaginary)) {
                return true;
        }
 
@@ -2022,7 +1993,7 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const framepos_t
 }
 
 bool
-TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const double& pulse)
+TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const double& pulse)
 {
        TempoSection* prev_t = 0;
        TempoSection* section_prev = 0;
@@ -2058,36 +2029,33 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const double& pu
                        prev_t = t;
                }
        }
+
        if (section_prev) {
                section_prev->set_c_func (section_prev->compute_c_func_pulse (section->pulses_per_minute(), pulse, _frame_rate));
                section->set_frame (section_prev->frame_at_pulse (pulse, _frame_rate));
        }
 
-       if (section->position_lock_style() == AudioTime) {
-               /* we're setting the pulse */
-               section->set_position_lock_style (MusicTime);
-               recompute_tempos (imaginary);
-               section->set_position_lock_style (AudioTime);
-       } else {
-               recompute_tempos (imaginary);
-       }
+       recompute_tempos (imaginary);
 
-       if (check_solved (imaginary, false)) {
+       if (check_solved (imaginary)) {
                return true;
        }
 
        MetricSectionSorter cmp;
        imaginary.sort (cmp);
-       if (section->position_lock_style() == AudioTime) {
-               /* we're setting the pulse */
-               section->set_position_lock_style (MusicTime);
-               recompute_tempos (imaginary);
-               section->set_position_lock_style (AudioTime);
-       } else {
-               recompute_tempos (imaginary);
-       }
 
-       if (check_solved (imaginary, false)) {
+       recompute_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;
        }
 
@@ -2095,7 +2063,7 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const double& pu
 }
 
 bool
-TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t& frame)
+TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const framepos_t& frame)
 {
        /* disallow moving first meter past any subsequent one, and any movable meter before the first one */
        const MeterSection* other =  &meter_section_at_locked (imaginary, frame);
@@ -2124,7 +2092,14 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
                }
        }
 
+       if (!meter_locked_tempo) {
+               return false;
+       }
+
        MeterSection* prev_m = 0;
+       Metrics future_map;
+       TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
+       bool solved = false;
 
        for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
                MeterSection* m;
@@ -2133,84 +2108,42 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
                                if (prev_m && section->movable()) {
                                        const double beats = (pulse_at_frame_locked (imaginary, frame) - prev_m->pulse()) * prev_m->note_divisor();
                                        if (beats + prev_m->beat() < section->beat()) {
-                                               /* disallow position change if it will alter our beat
-                                                  we allow tempo changes to do this in recompute_meters().
-                                                  blocking this is an option, but i'm not convinced that
-                                                  this is what the user would actually want.
-                                                  here we set the frame/pulse corresponding to its musical position.
+                                               /* set the frame/pulse corresponding to its musical position,
+                                                * as an earlier time than this has been requested.
                                                */
+                                               const double new_pulse = ((section->beat() - prev_m->beat())
+                                                                         / prev_m->note_divisor()) + prev_m->pulse();
 
-                                               if (meter_locked_tempo) {
-                                                       Metrics future_map;
-                                                       bool solved = false;
-                                                       TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
-                                                       const double new_pulse = ((section->beat() - prev_m->beat())
-                                                                                 / prev_m->note_divisor()) + prev_m->pulse();
-                                                       const framepos_t smallest_frame = frame_at_pulse_locked (future_map, new_pulse);
-                                                       if ((solved = solve_map (future_map, tempo_copy, smallest_frame))) {
-                                                               meter_locked_tempo->set_pulse (new_pulse);
-                                                               solve_map (imaginary, meter_locked_tempo, smallest_frame);
-                                                               section->set_frame (smallest_frame);
-                                                               section->set_pulse (new_pulse);
-                                                       } else {
-                                                               solved = false;
-                                                       }
-
-                                                       Metrics::const_iterator d = future_map.begin();
-                                                       while (d != future_map.end()) {
-                                                               delete (*d);
-                                                               ++d;
-                                                       }
-
-                                                       if (!solved) {
-                                                               return false;
-                                                       }
-                                               }
-                                               return false;
-                                       } else {
-                                               if (meter_locked_tempo) {
-                                                       Metrics future_map;
-                                                       bool solved = false;
+                                               const framepos_t smallest_frame = frame_at_pulse_locked (future_map, new_pulse);
 
-                                                       TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
-                                                       MeterSection* meter_copy = const_cast<MeterSection*> (&meter_section_at_locked (future_map, section->frame()));
-                                                       meter_copy->set_frame (frame);
+                                               if ((solved = solve_map_frame (future_map, tempo_copy, smallest_frame))) {
+                                                       meter_locked_tempo->set_pulse (new_pulse);
+                                                       solve_map_frame (imaginary, meter_locked_tempo, smallest_frame);
+                                                       section->set_frame (smallest_frame);
+                                                       section->set_pulse (new_pulse);
+                                               } else {
+                                                       solved = false;
+                                               }
 
-                                                       if ((solved = solve_map (future_map, tempo_copy, frame))) {
-                                                               section->set_frame (frame);
-                                                               meter_locked_tempo->set_pulse (((section->beat() - prev_m->beat())
-                                                                                               / prev_m->note_divisor()) + prev_m->pulse());
-                                                               solve_map (imaginary, meter_locked_tempo, frame);
-                                                       } else {
-                                                               solved = false;
-                                                       }
-
-                                                       Metrics::const_iterator d = future_map.begin();
-                                                       while (d != future_map.end()) {
-                                                               delete (*d);
-                                                               ++d;
-                                                       }
-
-                                                       if (!solved) {
-                                                               return false;
-                                                       }
+                                               Metrics::const_iterator d = future_map.begin();
+                                               while (d != future_map.end()) {
+                                                       delete (*d);
+                                                       ++d;
                                                }
-                                       }
-                               } else {
-                                       /* not movable (first meter atm) */
-                                       if (meter_locked_tempo) {
-                                               Metrics future_map;
-                                               bool solved = false;
-                                               TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
 
-                                               tempo_copy->set_frame (frame);
-                                               tempo_copy->set_pulse (0.0);
+                                               if (!solved) {
+                                                       return false;
+                                               }
+                                       } else {
+                                               /* all is ok. set section's tempo */
+                                               MeterSection* meter_copy = const_cast<MeterSection*> (&meter_section_at_locked (future_map, section->frame()));
+                                               meter_copy->set_frame (frame);
 
-                                               if ((solved = solve_map (future_map, tempo_copy, frame))) {
+                                               if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
                                                        section->set_frame (frame);
-                                                       meter_locked_tempo->set_frame (frame);
-                                                       meter_locked_tempo->set_pulse (0.0);
-                                                       solve_map (imaginary, meter_locked_tempo, frame);
+                                                       meter_locked_tempo->set_pulse (((section->beat() - prev_m->beat())
+                                                                                               / prev_m->note_divisor()) + prev_m->pulse());
+                                                       solve_map_frame (imaginary, meter_locked_tempo, frame);
                                                } else {
                                                        solved = false;
                                                }
@@ -2224,10 +2157,32 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
                                                if (!solved) {
                                                        return false;
                                                }
+                                       }
+                               } else {
+                                       /* not movable (first meter atm) */
 
+                                       tempo_copy->set_frame (frame);
+                                       tempo_copy->set_pulse (0.0);
+
+                                       if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
+                                               section->set_frame (frame);
+                                               meter_locked_tempo->set_frame (frame);
+                                               meter_locked_tempo->set_pulse (0.0);
+                                               solve_map_frame (imaginary, meter_locked_tempo, frame);
                                        } else {
+                                               solved = false;
+                                       }
+
+                                       Metrics::const_iterator d = future_map.begin();
+                                       while (d != future_map.end()) {
+                                               delete (*d);
+                                               ++d;
+                                       }
+
+                                       if (!solved) {
                                                return false;
                                        }
+
                                        pair<double, BBT_Time> b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
                                        section->set_beat (b_bbt);
                                        section->set_pulse (0.0);
@@ -2242,20 +2197,14 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
 
        MetricSectionFrameSorter fcmp;
        imaginary.sort (fcmp);
-       if (section->position_lock_style() == MusicTime) {
-               /* we're setting the frame */
-               section->set_position_lock_style (AudioTime);
-               recompute_meters (imaginary);
-               section->set_position_lock_style (MusicTime);
-       } else {
-               recompute_meters (imaginary);
-       }
+
+       recompute_meters (imaginary);
 
        return true;
 }
 
 bool
-TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time& when)
+TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Time& when)
 {
        /* disallow setting section to an existing meter's bbt */
        for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
@@ -2291,6 +2240,7 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time&
 
                        if (m->position_lock_style() == AudioTime) {
                                TempoSection* meter_locked_tempo = 0;
+
                                for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) {
                                        TempoSection* t;
                                        if ((t = dynamic_cast<TempoSection*> (*ii)) != 0) {
@@ -2301,6 +2251,10 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time&
                                        }
                                }
 
+                               if (!meter_locked_tempo) {
+                                       return false;
+                               }
+
                                if (prev_m) {
                                        const double beats = ((m->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar());
 
@@ -2316,10 +2270,8 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time&
                                } else {
                                        b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
                                }
-                               if (meter_locked_tempo) {
-                                       meter_locked_tempo->set_pulse (new_pulse);
-                                       recompute_tempos (imaginary);
-                               }
+
+                               meter_locked_tempo->set_pulse (new_pulse);
                                m->set_beat (b_bbt);
                                m->set_pulse (new_pulse);
 
@@ -2358,14 +2310,7 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time&
        MetricSectionSorter cmp;
        imaginary.sort (cmp);
 
-       if (section->position_lock_style() == AudioTime) {
-               /* we're setting the pulse */
-               section->set_position_lock_style (MusicTime);
-               recompute_meters (imaginary);
-               section->set_position_lock_style (AudioTime);
-       } else {
-               recompute_meters (imaginary);
-       }
+       recompute_meters (imaginary);
 
        return true;
 }
@@ -2441,8 +2386,7 @@ TempoMap::can_solve_bbt (TempoSection* ts, const BBT_Time& bbt)
                }
        }
 
-       const double beat = bbt_to_beats_locked (copy, bbt);
-       const bool ret = solve_map (copy, tempo_copy, pulse_at_beat_locked (copy, beat));
+       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()) {
@@ -2454,51 +2398,30 @@ TempoMap::can_solve_bbt (TempoSection* ts, const BBT_Time& bbt)
 }
 
 /**
-* This is for a gui that needs to know the frame of a tempo section if it were to be moved to some bbt time,
+* 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 bpm - the new Tempo
 * @param bbt - the bbt where the altered tempo will fall
-* @return returns - the position in frames where the new tempo section will lie.
+* @return returns - the position in pulses and frames (as a pair) where the new tempo section will lie.
 */
-framepos_t
-TempoMap::predict_tempo_frame (TempoSection* section, const BBT_Time& bbt)
+pair<double, framepos_t>
+TempoMap::predict_tempo_position (TempoSection* section, const BBT_Time& bbt)
 {
-       Glib::Threads::RWLock::ReaderLock lm (lock);
        Metrics future_map;
-       framepos_t ret = 0;
-       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, section);
-       if (!tempo_copy) {
-               return 0;
-       }
-       const double beat = bbt_to_beats_locked (future_map, bbt);
+       pair<double, framepos_t> ret = make_pair (0.0, 0);
 
-       if (solve_map (future_map, tempo_copy, pulse_at_beat_locked (future_map, beat))) {
-               ret = tempo_copy->frame();
-       } else {
-               ret = section->frame();
-       }
-
-       Metrics::const_iterator d = future_map.begin();
-       while (d != future_map.end()) {
-               delete (*d);
-               ++d;
-       }
-       return ret;
-}
-
-double
-TempoMap::predict_tempo_pulse (TempoSection* section, const framepos_t& frame)
-{
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       Metrics future_map;
-       double ret = 0.0;
+
        TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, section);
 
-       if (solve_map (future_map, tempo_copy, frame)) {
-               ret = tempo_copy->pulse();
+       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 = section->pulse();
+               ret.first = section->pulse();
+               ret.second = section->frame();
        }
 
        Metrics::const_iterator d = future_map.begin();
@@ -2510,37 +2433,30 @@ TempoMap::predict_tempo_pulse (TempoSection* section, const framepos_t& frame)
 }
 
 void
-TempoMap::gui_move_tempo_frame (TempoSection* ts, const framepos_t& frame)
+TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame)
 {
        Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
-               if (solve_map (future_map, tempo_copy, frame)) {
-                       solve_map (_metrics, ts, frame);
-                       recompute_meters (_metrics);
-               }
-       }
 
-       Metrics::const_iterator d = future_map.begin();
-       while (d != future_map.end()) {
-               delete (*d);
-               ++d;
-       }
+       if (ts->position_lock_style() == MusicTime) {
+               {
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
+                       const double pulse = pulse_at_frame_locked (future_map, frame);
+                       if (solve_map_pulse (future_map, tempo_copy, pulse)) {
+                               solve_map_pulse (_metrics, ts, pulse);
+                               recompute_meters (_metrics);
+                       }
+               }
 
-       MetricPositionChanged (); // Emit Signal
-}
+       } else {
 
-void
-TempoMap::gui_move_tempo_beat (TempoSection* ts, const double& beat)
-{
-       Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
-               if (solve_map (future_map, tempo_copy, pulse_at_beat_locked (future_map, beat))) {
-                       solve_map (_metrics, ts, pulse_at_beat_locked (_metrics, beat));
-                       recompute_meters (_metrics);
+               {
+                       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);
+                       }
                }
        }
 
@@ -2554,59 +2470,33 @@ TempoMap::gui_move_tempo_beat (TempoSection* ts, const double& beat)
 }
 
 void
-TempoMap::gui_move_tempo_pulse (TempoSection* ts, const double& pulse)
+TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame)
 {
        Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
-               if (solve_map (future_map, tempo_copy, pulse)) {
-                       solve_map (_metrics, ts, pulse);
-                       recompute_meters (_metrics);
-               }
-       }
 
-       Metrics::const_iterator d = future_map.begin();
-       while (d != future_map.end()) {
-               delete (*d);
-               ++d;
-       }
+       if (ms->position_lock_style() == AudioTime) {
 
-       MetricPositionChanged (); // Emit Signal
-}
+               {
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
 
-void
-TempoMap::gui_move_meter (MeterSection* ms, const framepos_t&  frame)
-{
-       Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
-               if (solve_map (future_map, copy, frame)) {
-                       solve_map (_metrics, ms, frame);
-                       recompute_tempos (_metrics);
+                       if (solve_map_frame (future_map, copy, frame)) {
+                               solve_map_frame (_metrics, ms, frame);
+                               recompute_tempos (_metrics);
+                       }
                }
-       }
-
-       Metrics::const_iterator d = future_map.begin();
-       while (d != future_map.end()) {
-               delete (*d);
-               ++d;
-       }
+       } else {
+               {
+                       Glib::Threads::RWLock::WriterLock lm (lock);
+                       MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
 
-       MetricPositionChanged (); // Emit Signal
-}
+                       const double beat = beat_at_frame_locked (_metrics, frame);
+                       const Timecode::BBT_Time bbt = bbt_at_beat_locked (_metrics, beat);
 
-void
-TempoMap::gui_move_meter (MeterSection* ms, const Timecode::BBT_Time& bbt)
-{
-       Metrics future_map;
-       {
-               Glib::Threads::RWLock::WriterLock lm (lock);
-               MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
-               if (solve_map (future_map, copy, bbt)) {
-                       solve_map (_metrics, ms, bbt);
-                       recompute_tempos (_metrics);
+                       if (solve_map_bbt (future_map, copy, bbt)) {
+                               solve_map_bbt (_metrics, ms, bbt);
+                               recompute_tempos (_metrics);
+                       }
                }
        }
 
@@ -2630,7 +2520,7 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm)
                tempo_copy->set_beats_per_minute (bpm.beats_per_minute());
                recompute_tempos (future_map);
 
-               if (check_solved (future_map, true)) {
+               if (check_solved (future_map)) {
                        ts->set_beats_per_minute (bpm.beats_per_minute());
                        recompute_map (_metrics);
                        can_solve = true;
@@ -2649,59 +2539,71 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm)
 }
 
 void
-TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
-{
-       Metrics future_map;
-       TempoSection* ts = 0;
+TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const framepos_t& end_frame, const double& pulse)
+{
+       /*
+         Ts (future prev_t)   Tnext
+         |                    |
+         |     [drag^]        |
+         |----------|----------
+               e_f  pulse(frame)
+       */
 
-       if (ms->position_lock_style() == AudioTime) {
-               /* disabled for now due to faked tempo locked to meter pulse */
-               return;
-       }
+       Metrics future_map;
 
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               ts = const_cast<TempoSection*>(&tempo_section_at_locked (_metrics, ms->frame() - 1));
+
                if (!ts) {
                        return;
                }
+
                TempoSection* prev_t = copy_metrics_and_point (_metrics, future_map, ts);
                TempoSection* prev_to_prev_t = 0;
-               const frameoffset_t fr_off = frame - ms->frame();
-               double new_bpm = 0.0;
+               const frameoffset_t fr_off = end_frame - frame;
 
                if (prev_t && prev_t->pulse() > 0.0) {
                        prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_locked (future_map, prev_t->frame() - 1));
                }
 
+               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;
+                               }
+                       }
+               }
+
                /* the change in frames is the result of changing the slope of at most 2 previous tempo sections.
                   constant to constant is straightforward, as the tempo prev to prev_t has constant slope.
                */
                double contribution = 0.0;
-               frameoffset_t frame_contribution = 0.0;
-               frameoffset_t prev_t_frame_contribution = 0.0;
+               double start_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
 
-               if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                       /* prev to prev_t's position will remain constant in terms of frame and pulse. lets use frames. */
-                       contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (ms->frame() - prev_to_prev_t->frame());
-                       frame_contribution = contribution * (double) fr_off;
-                       prev_t_frame_contribution = fr_off - frame_contribution;
+               if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+                       contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame());
                }
 
+               frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
+               double end_pulse = prev_t->pulse_at_frame (end_frame, _frame_rate);
+               double new_bpm;
+
                if (prev_t->type() == TempoSection::Constant || prev_t->c_func() == 0.0) {
 
                        if (prev_t->position_lock_style() == MusicTime) {
                                if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                                       new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame())
-                                                                               / (double) (ms->frame() + prev_t_frame_contribution - prev_t->frame()));
+
+                                       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 {
                                        /* prev to prev is irrelevant */
-                                       const double meter_pulse = prev_t->pulse_at_frame (ms->frame(), _frame_rate);
-                                       const double frame_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
 
-                                       if (frame_pulse != prev_t->pulse()) {
-                                               new_bpm = prev_t->beats_per_minute() * ((meter_pulse - prev_t->pulse()) / (frame_pulse - prev_t->pulse()));
+                                       if (start_pulse != prev_t->pulse()) {
+                                               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();
                                        }
@@ -2709,62 +2611,43 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
                        } else {
                                /* AudioTime */
                                if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                                       new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame())
-                                                                               / (double) (ms->frame() + prev_t_frame_contribution - prev_t->frame()));
+                                       new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame())
+                                                                               / (double) ((frame + prev_t_frame_contribution) - prev_t->frame()));
                                } else {
                                        /* prev_to_prev_t is irrelevant */
 
-                                       if (frame != prev_t->frame()) {
-                                               new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame()) / (double) (frame - prev_t->frame()));
+                                       if (end_frame != prev_t->frame()) {
+                                               new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame()) / (double) (end_frame - prev_t->frame()));
                                        } else {
                                                new_bpm = prev_t->beats_per_minute();
                                        }
                                }
                        }
-               } else if (prev_t->c_func() < 0.0) {
-                       if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                               new_bpm = prev_t->tempo_at_frame (prev_t->frame() + frame_contribution, _frame_rate) * (double) prev_t->note_type();
-                       } else {
-                               /* prev_to_prev_t is irrelevant */
-                               new_bpm = prev_t->tempo_at_frame (prev_t->frame() + fr_off, _frame_rate) * (double) prev_t->note_type();
-                       }
+               } else {
 
-                       const double diff = (prev_t->tempo_at_frame (frame, _frame_rate) * prev_t->note_type()) - prev_t->beats_per_minute();
-                       if (diff > -0.1 && diff  < 0.1) {
-                               new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame())
-                                                                       / (double) ((ms->frame() + prev_t_frame_contribution) - prev_t->frame()));
-                       }
+                       double frame_ratio;
+                       double pulse_ratio;
+                       const framepos_t pulse_pos = prev_t->frame_at_pulse (pulse, _frame_rate);
+
+                       if (prev_to_prev_t) {
 
-               } else if (prev_t->c_func() > 0.0) {
-                       if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                               new_bpm = prev_t->tempo_at_frame (prev_t->frame() - frame_contribution, _frame_rate) * (double) prev_t->note_type();
+                               frame_ratio = (((pulse_pos - fr_off) - prev_to_prev_t->frame()) / (double) ((pulse_pos) - prev_to_prev_t->frame()));
+                               pulse_ratio = ((start_pulse - prev_to_prev_t->pulse()) / (end_pulse - prev_to_prev_t->pulse()));
                        } else {
-                               /* prev_to_prev_t is irrelevant */
-                               new_bpm = prev_t->tempo_at_frame (prev_t->frame() - fr_off, _frame_rate) * (double) prev_t->note_type();
-                       }
 
-                       /* limits - a bit clunky, but meh */
-                       const double diff = (prev_t->tempo_at_frame (frame, _frame_rate) * prev_t->note_type()) - prev_t->beats_per_minute();
-                       if (diff > -0.1 && diff  < 0.1) {
-                               new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame())
-                                                                       / (double) ((ms->frame() + prev_t_frame_contribution) - prev_t->frame()));
+                               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);
                }
 
                prev_t->set_beats_per_minute (new_bpm);
                recompute_tempos (future_map);
                recompute_meters (future_map);
 
-               if (check_solved (future_map, true)) {
-
-                       prev_t = const_cast<TempoSection*>(&tempo_section_at_locked (_metrics, ms->frame() - 1));
-                       prev_t->set_beats_per_minute (new_bpm);
+               if (check_solved (future_map)) {
+                       ts->set_beats_per_minute (new_bpm);
                        recompute_tempos (_metrics);
-
-                       if (ms->position_lock_style() == AudioTime) {
-                               ms->set_frame (frame);
-                       }
-
                        recompute_meters (_metrics);
                }
        }
@@ -2898,119 +2781,13 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
        return ret_frame;
 }
 
-void
-TempoMap::round_bbt (BBT_Time& when, const int32_t& sub_num, RoundMode dir)
-{
-       if (sub_num == -1) {
-               if (dir > 0) {
-                       ++when.bars;
-                       when.beats = 1;
-                       when.ticks = 0;
-               } else if (dir < 0) {
-                       when.beats = 1;
-                       when.ticks = 0;
-               } else {
-                       const double bpb = meter_section_at_beat (bbt_to_beats_locked (_metrics, when)).divisions_per_bar();
-                       if ((double) when.beats > bpb / 2.0) {
-                               ++when.bars;
-                       }
-                       when.beats = 1;
-                       when.ticks = 0;
-               }
-
-               return;
-
-       } else if (sub_num == 0) {
-               const double bpb = meter_section_at_beat (bbt_to_beats_locked (_metrics, when)).divisions_per_bar();
-               if ((double) when.ticks > BBT_Time::ticks_per_beat / 2.0) {
-                       ++when.beats;
-                       while ((double) when.beats > bpb) {
-                               ++when.bars;
-                               when.beats -= (uint32_t) floor (bpb);
-                       }
-               }
-               when.ticks = 0;
-
-               return;
-       }
-
-       const uint32_t ticks_one_subdivisions_worth = BBT_Time::ticks_per_beat / sub_num;
-
-       if (dir > 0) {
-               /* round to next (or same iff dir == RoundUpMaybe) */
-
-               uint32_t mod = when.ticks % ticks_one_subdivisions_worth;
-
-               if (mod == 0 && dir == RoundUpMaybe) {
-                       /* right on the subdivision, which is fine, so do nothing */
-
-               } else if (mod == 0) {
-                       /* right on the subdivision, so the difference is just the subdivision ticks */
-                       when.ticks += ticks_one_subdivisions_worth;
-
-               } else {
-                       /* not on subdivision, compute distance to next subdivision */
-
-                       when.ticks += ticks_one_subdivisions_worth - mod;
-               }
-
-               if (when.ticks >= BBT_Time::ticks_per_beat) {
-                       when.ticks -= BBT_Time::ticks_per_beat;
-               }
-
-       } else if (dir < 0) {
-               /* round to previous (or same iff dir == RoundDownMaybe) */
-
-               uint32_t difference = when.ticks % ticks_one_subdivisions_worth;
-
-               if (difference == 0 && dir == RoundDownAlways) {
-                       /* right on the subdivision, but force-rounding down,
-                          so the difference is just the subdivision ticks */
-                       difference = ticks_one_subdivisions_worth;
-               }
-
-               if (when.ticks < difference) {
-                       when.ticks = BBT_Time::ticks_per_beat - when.ticks;
-               } else {
-                       when.ticks -= difference;
-               }
-
-       } else {
-               /* round to nearest */  double rem;
-               if ((rem = fmod ((double) when.ticks, (double) ticks_one_subdivisions_worth)) > (ticks_one_subdivisions_worth / 2.0)) {
-                       /* closer to the next subdivision, so shift forward */
-
-                       when.ticks = when.ticks + (ticks_one_subdivisions_worth - rem);
-
-                       if (when.ticks > Timecode::BBT_Time::ticks_per_beat) {
-                               ++when.beats;
-                               when.ticks -= Timecode::BBT_Time::ticks_per_beat;
-                       }
-
-               } else if (rem > 0) {
-                       /* closer to previous subdivision, so shift backward */
-
-                       if (rem > when.ticks) {
-                               if (when.beats == 0) {
-                                       /* can't go backwards past zero, so ... */
-                               }
-                               /* step back to previous beat */
-                               --when.beats;
-                               when.ticks = Timecode::BBT_Time::ticks_per_beat - rem;
-                       } else {
-                               when.ticks = when.ticks - rem;
-                       }
-               }
-       }
-}
-
 framepos_t
 TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
        const double beat_at_framepos = beat_at_frame_locked (_metrics, frame);
-       BBT_Time bbt (beats_to_bbt_locked (_metrics, beat_at_framepos));
+       BBT_Time bbt (bbt_at_beat_locked (_metrics, beat_at_framepos));
 
        switch (type) {
        case Bar:
@@ -3073,8 +2850,8 @@ TempoMap::get_grid (vector<TempoMap::BBTPoint>& points,
                pos = frame_at_beat_locked (_metrics, cnt);
                const TempoSection tempo = tempo_section_at_locked (_metrics, pos);
                const MeterSection meter = meter_section_at_locked (_metrics, pos);
-               const BBT_Time bbt = beats_to_bbt (cnt);
-               points.push_back (BBTPoint (meter, tempo_at_locked (_metrics, pos), pos, bbt.bars, bbt.beats, tempo.c_func()));
+               const 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;
        }
 }
@@ -3185,42 +2962,6 @@ TempoMap::frames_per_beat_at (const framepos_t& frame, const framecnt_t& sr) con
        return ts_at->frames_per_beat (_frame_rate);
 }
 
-const Tempo
-TempoMap::tempo_at_locked (const Metrics& metrics, const framepos_t& frame) const
-{
-       TempoSection* prev_t = 0;
-
-       Metrics::const_iterator i;
-
-       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
-               TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if (!t->active()) {
-                               continue;
-                       }
-                       if ((prev_t) && t->frame() > frame) {
-                               /* t is the section past frame */
-                               const double ret_bpm = prev_t->tempo_at_frame (frame, _frame_rate) * prev_t->note_type();
-                               const Tempo ret_tempo (ret_bpm, prev_t->note_type());
-                               return ret_tempo;
-                       }
-                       prev_t = t;
-               }
-       }
-
-       const double ret = prev_t->beats_per_minute();
-       const Tempo ret_tempo (ret, prev_t->note_type ());
-
-       return ret_tempo;
-}
-
-const Tempo
-TempoMap::tempo_at (const framepos_t& frame) const
-{
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-       return tempo_at_locked (_metrics, frame);
-}
-
 const MeterSection&
 TempoMap::meter_section_at_locked (const Metrics& metrics, framepos_t frame) const
 {
@@ -3282,7 +3023,7 @@ TempoMap::meter_section_at_beat (double beat) const
 }
 
 const Meter&
-TempoMap::meter_at (framepos_t frame) const
+TempoMap::meter_at_frame (framepos_t frame) const
 {
        TempoMetric m (metric_at (frame));
        return m.meter();
@@ -3584,7 +3325,7 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount)
                                                        continue;
                                                }
                                                const double beat = beat_at_pulse_locked (_metrics, t->pulse());
-                                               pair<double, BBT_Time> start = make_pair (beat, beats_to_bbt_locked (_metrics, beat));
+                                               pair<double, BBT_Time> start = make_pair (beat, bbt_at_beat_locked (_metrics, beat));
                                                ms->set_beat (start);
                                                ms->set_pulse (t->pulse());
                                        }
@@ -3729,7 +3470,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       BBT_Time pos_bbt = beats_to_bbt_locked (_metrics, beat_at_frame_locked (_metrics, pos));
+       BBT_Time pos_bbt = bbt_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos));
        pos_bbt.ticks += op.ticks;
        if (pos_bbt.ticks >= BBT_Time::ticks_per_beat) {
                ++pos_bbt.beats;
@@ -3737,10 +3478,10 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
        }
        pos_bbt.beats += op.beats;
        /* the meter in effect will start on the bar */
-       double divisions_per_bar = meter_section_at_beat (bbt_to_beats_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
+       double divisions_per_bar = meter_section_at_beat (beat_at_bbt_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
        while (pos_bbt.beats >= divisions_per_bar + 1) {
                ++pos_bbt.bars;
-               divisions_per_bar = meter_section_at_beat (bbt_to_beats_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
+               divisions_per_bar = meter_section_at_beat (beat_at_bbt_locked (_metrics, BBT_Time (pos_bbt.bars + op.bars, 1, 0))).divisions_per_bar();
                pos_bbt.beats -= divisions_per_bar;
        }
        pos_bbt.bars += op.bars;