Tempo ramps - remove unused code, small meter dilation drag cleanup.
[ardour.git] / libs / ardour / tempo.cc
index cb4ef4d57c430dd7eb81fb51d608dfdd4dac9f6d..8c710ad180896a95f7284e9ffb050f37540dc119 100644 (file)
@@ -207,14 +207,14 @@ TempoSection::set_type (Type type)
 /** returns the tempo in whole pulses per minute at the zero-based (relative to session) frame.
 */
 double
-TempoSection::tempo_at_frame (const frameoffset_t& f, const framecnt_t& frame_rate) const
+TempoSection::tempo_at_frame (const framepos_t& f, const framecnt_t& frame_rate) const
 {
 
        if (_type == Constant || _c_func == 0.0) {
                return pulses_per_minute();
        }
 
-       return pulse_tempo_at_time (frame_to_minute (f - (frameoffset_t) frame(), frame_rate));
+       return pulse_tempo_at_time (frame_to_minute (f - frame(), frame_rate));
 }
 
 /** returns the zero-based frame (relative to session)
@@ -222,7 +222,7 @@ TempoSection::tempo_at_frame (const frameoffset_t& f, const framecnt_t& frame_ra
    beat b is only used for constant tempos.
    note that the tempo map may have multiple such values.
 */
-frameoffset_t
+framepos_t
 TempoSection::frame_at_tempo (const double& ppm, const double& b, const framecnt_t& frame_rate) const
 {
        if (_type == Constant || _c_func == 0.0) {
@@ -277,11 +277,11 @@ TempoSection::pulse_at_frame (const framepos_t& f, const framecnt_t& frame_rate)
    falls.
 */
 
-frameoffset_t
+framepos_t
 TempoSection::frame_at_pulse (const double& p, const framecnt_t& frame_rate) const
 {
        if (_type == Constant || _c_func == 0.0) {
-               return (frameoffset_t) floor ((p - pulse()) * frames_per_pulse (frame_rate)) + frame();
+               return (framepos_t) floor ((p - pulse()) * frames_per_pulse (frame_rate)) + frame();
        }
 
        return minute_to_frame (time_at_pulse (p - pulse()), frame_rate) + frame();
@@ -380,14 +380,14 @@ TempoSection::compute_c_func_frame (const double& end_bpm, const framepos_t& end
        return c_func (end_bpm, frame_to_minute (end_frame - frame(), frame_rate));
 }
 
-frameoffset_t
+framepos_t
 TempoSection::minute_to_frame (const double& time, const framecnt_t& frame_rate) const
 {
-       return (frameoffset_t) floor ((time * 60.0 * (frameoffset_t) frame_rate) + 0.5);
+       return (framepos_t) floor ((time * 60.0 * frame_rate) + 0.5);
 }
 
 double
-TempoSection::frame_to_minute (const frameoffset_t& frame, const framecnt_t& frame_rate) const
+TempoSection::frame_to_minute (const framepos_t& frame, const framecnt_t& frame_rate) const
 {
        return (frame / (double) frame_rate) / 60.0;
 }
@@ -654,6 +654,12 @@ TempoMap::TempoMap (framecnt_t fr)
 
 TempoMap::~TempoMap ()
 {
+       Metrics::const_iterator d = _metrics.begin();
+       while (d != _metrics.end()) {
+               delete (*d);
+               ++d;
+       }
+       _metrics.clear();
 }
 
 void
@@ -756,7 +762,6 @@ TempoMap::do_insert (MetricSection* section)
         */
        MeterSection* m = 0;
        if ((m = dynamic_cast<MeterSection*>(section)) != 0) {
-               //assert (m->bbt().ticks == 0);
 
                if ((m->bbt().beats != 1) || (m->bbt().ticks != 0)) {
 
@@ -874,7 +879,7 @@ TempoMap::do_insert (MetricSection* section)
                }
 
                _metrics.insert (i, section);
-               //dump (_metrics, std::cerr);
+               //dump (_metrics, std::cout);
        }
 }
 
@@ -1079,7 +1084,7 @@ TempoMap::add_meter (const Meter& meter, const framepos_t& frame, const double&
 }
 
 MeterSection*
-TempoMap::add_meter_locked (const Meter& meter, double beat, BBT_Time where, bool recompute)
+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
@@ -1088,37 +1093,32 @@ TempoMap::add_meter_locked (const Meter& meter, double beat, BBT_Time where, boo
 
        */
 
-       if (where.beats != 1) {
-               where.beats = 1;
-               where.bars++;
-       }
-       /* new meters *always* start on a beat. */
-       where.ticks = 0;
        const double pulse = pulse_at_beat_locked (_metrics, beat);
        MeterSection* new_meter = new MeterSection (pulse, beat, where, meter.divisions_per_bar(), meter.note_divisor());
+
        do_insert (new_meter);
 
        if (recompute) {
-               solve_map (_metrics, new_meter, pulse);
+               solve_map (_metrics, new_meter, where);
        }
 
        return new_meter;
 }
 
 MeterSection*
-TempoMap::add_meter_locked (const Meter& meter, framepos_t frame, double beat, Timecode::BBT_Time where, bool recompute)
+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());
-       TempoSection* t = 0;
-       double pulse = pulse_at_frame_locked (_metrics, frame);
-       new_meter->set_pulse (pulse);
+       new_meter->set_pulse (pulse_at_frame_locked (_metrics, frame));
 
        do_insert (new_meter);
 
-       /* add meter-locked tempo */
-       t = add_tempo_locked (tempo_at_locked (_metrics, frame), frame, true, TempoSection::Ramp);
-       t->set_locked_to_meter (true);
-
        if (recompute) {
                solve_map (_metrics, new_meter, frame);
        }
@@ -1315,7 +1315,12 @@ TempoMap::recompute_tempos (Metrics& metrics)
        prev_t->set_c_func (0.0);
 }
 
-/* tempos must be positioned correctly */
+/* 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)
+*/
 void
 TempoMap::recompute_meters (Metrics& metrics)
 {
@@ -1327,35 +1332,66 @@ TempoMap::recompute_meters (Metrics& metrics)
                        if (meter->position_lock_style() == AudioTime) {
                                double pulse = 0.0;
                                pair<double, BBT_Time> b_bbt;
-                               if (meter->movable()) {
-                                       b_bbt = make_pair (meter->beat(), meter->bbt());
-                                       pulse = pulse_at_frame_locked (metrics, meter->frame());
+                               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 (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);
+
                        } else {
+                               /* MusicTime */
                                double pulse = 0.0;
                                pair<double, BBT_Time> new_beat;
                                if (prev_m) {
-                                       pulse = prev_m->pulse() + ((meter->bbt().bars - prev_m->bbt().bars) *  prev_m->divisions_per_bar() / prev_m->note_divisor());
-                                       //new_beat = make_pair (((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat(), meter->bbt());
+                                       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()
+                                                                  , 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());
+                                       }
+                                       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());
                                }
 
-                               //meter->set_beat (new_beat);
-                               meter->set_frame (frame_at_pulse_locked (metrics, pulse));
+                               meter->set_beat (new_beat);
                                meter->set_pulse (pulse);
+                               meter->set_frame (frame_at_pulse_locked (metrics, pulse));
                        }
 
                        prev_m = meter;
                }
        }
-       //dump (_metrics, std::cerr;
 }
 
 void
@@ -1860,7 +1896,8 @@ TempoMap::check_solved (const Metrics& metrics, bool by_frame) const
                        if (prev_m && m->position_lock_style() == AudioTime) {
                                TempoSection* t = const_cast<TempoSection*>(&tempo_section_at_locked (metrics, m->frame() - 1));
                                const double nascent_m_pulse = ((m->beat() - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse();
-                               const frameoffset_t nascent_m_frame = t->frame_at_pulse (nascent_m_pulse, _frame_rate);
+                               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;
                                }
@@ -1962,7 +1999,6 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const framepos_t
                recompute_tempos (imaginary);
        }
 
-       recompute_meters (imaginary);
        if (check_solved (imaginary, true)) {
                return true;
        }
@@ -1978,13 +2014,10 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const framepos_t
                recompute_tempos (imaginary);
        }
 
-       recompute_meters (imaginary);
        if (check_solved (imaginary, true)) {
                return true;
        }
 
-       //dump (imaginary, std::cerr);
-
        return false;
 }
 
@@ -2039,7 +2072,6 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const double& pu
                recompute_tempos (imaginary);
        }
 
-       recompute_meters (imaginary);
        if (check_solved (imaginary, false)) {
                return true;
        }
@@ -2055,13 +2087,10 @@ TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const double& pu
                recompute_tempos (imaginary);
        }
 
-       recompute_meters (imaginary);
        if (check_solved (imaginary, false)) {
                return true;
        }
 
-       //dump (imaginary, std::cerr);
-
        return false;
 }
 
@@ -2081,15 +2110,16 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
                }
        }
 
+       /* it would make sense to bail out if there is no audio-locked meter,
+          however it may be desirable to move a music-locked meter by frame at some point.
+       */
        TempoSection* meter_locked_tempo = 0;
-       for (Metrics::const_iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+       for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) {
                TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+               if ((t = dynamic_cast<TempoSection*> (*ii)) != 0) {
                        if ((t->locked_to_meter() || !t->movable()) && t->frame() == section->frame()) {
-                               if (t->frame() == section->frame()) {
-                                       meter_locked_tempo = t;
-                                       break;
-                               }
+                               meter_locked_tempo = t;
+                               break;
                        }
                }
        }
@@ -2112,19 +2142,27 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
 
                                                if (meter_locked_tempo) {
                                                        Metrics future_map;
-                                                       TempoSection* new_section = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
-                                                       if (!new_section) {
-                                                               return false;
-                                                       }
+                                                       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();
-
-                                                       if (solve_map (future_map, new_section, section->frame())) {
+                                                       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, section->frame());
-                                                               section->set_frame (frame_at_pulse_locked (imaginary, 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;
                                                        }
                                                }
@@ -2132,40 +2170,61 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
                                        } else {
                                                if (meter_locked_tempo) {
                                                        Metrics future_map;
-                                                       TempoSection* new_section = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
-                                                       if (!new_section) {
-                                                               return false;
-                                                       }
-                                                       new_section->set_active (true);
+                                                       bool solved = false;
 
-                                                       if (solve_map (future_map, new_section, frame)) {
+                                                       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 (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;
                                                        }
                                                }
                                        }
                                } else {
+                                       /* not movable (first meter atm) */
                                        if (meter_locked_tempo) {
                                                Metrics future_map;
-                                               TempoSection* new_section = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
-                                               if (!new_section) {
-                                                       return false;
-                                               }
-                                               new_section->set_frame (frame);
-                                               new_section->set_pulse (0.0);
-                                               new_section->set_active (true);
+                                               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 (solve_map (future_map, new_section, frame)) {
+                                               if ((solved = solve_map (future_map, tempo_copy, frame))) {
+                                                       section->set_frame (frame);
                                                        meter_locked_tempo->set_frame (frame);
                                                        meter_locked_tempo->set_pulse (0.0);
-                                                       meter_locked_tempo->set_active (true);
                                                        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;
                                                }
+
                                        } else {
                                                return false;
                                        }
@@ -2174,7 +2233,6 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
                                        section->set_pulse (0.0);
 
                                }
-                               section->set_frame (frame);
                                break;
                        }
 
@@ -2192,63 +2250,114 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const framepos_t
        } else {
                recompute_meters (imaginary);
        }
-       //dump (imaginary, std::cerr);
+
        return true;
 }
 
 bool
-TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const double& pulse)
+TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const BBT_Time& when)
 {
-       MeterSection* prev_m = 0;
+       /* 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;
+                       }
+               }
+       }
 
-       section->set_pulse (pulse);
+       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) {
-                       double new_pulse = 0.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);
 
-                       if (prev_m && m == section){
-                               /* the first meter is always audio-locked, so prev_m should exist.
-                                  should we allow setting audio locked meters by pulse?
-                               */
-                               const double beats = floor (((pulse - prev_m->pulse()) * prev_m->note_divisor()) + 0.5);
-                               const int32_t bars = (beats) / prev_m->divisions_per_bar();
-                               pair<double, BBT_Time> b_bbt = make_pair (beats + prev_m->beat(), BBT_Time (bars + prev_m->bbt().bars, 1, 0));
                                section->set_beat (b_bbt);
+                               section->set_pulse (pulse);
                                section->set_frame (frame_at_pulse_locked (imaginary, pulse));
-                               prev_m = m;
+                               prev_m = section;
                                continue;
                        }
+
                        if (m->position_lock_style() == AudioTime) {
-                               if (m->movable()) {
+                               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 (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). */
-                                               const double floor_beats = beats - fmod (beats, prev_m->divisions_per_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 {
+                                       } else if (m->movable()) {
                                                b_bbt = make_pair (m->beat(), m->bbt());
-                                               new_pulse = pulse_at_frame_locked (imaginary, m->frame());
+                                               new_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 (new_pulse);
+                                       recompute_tempos (imaginary);
+                               }
+                               m->set_beat (b_bbt);
+                               m->set_pulse (new_pulse);
+
                        } else {
-                               new_pulse = prev_m->pulse() + ((m->bbt().bars - prev_m->bbt().bars) *  prev_m->divisions_per_bar() / prev_m->note_divisor());
-                               b_bbt = make_pair (((new_pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat(), m->bbt());
+                               /* 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));
                        }
-                       m->set_beat (b_bbt);
-                       m->set_pulse (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);
+
        if (section->position_lock_style() == AudioTime) {
                /* we're setting the pulse */
                section->set_position_lock_style (MusicTime);
@@ -2257,6 +2366,7 @@ TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const double& pu
        } else {
                recompute_meters (imaginary);
        }
+
        return true;
 }
 
@@ -2321,18 +2431,18 @@ bool
 TempoMap::can_solve_bbt (TempoSection* ts, const BBT_Time& bbt)
 {
        Metrics copy;
-       TempoSection* new_section = 0;
+       TempoSection* tempo_copy = 0;
 
        {
                Glib::Threads::RWLock::ReaderLock lm (lock);
-               new_section = copy_metrics_and_point (_metrics, copy, ts);
-               if (!new_section) {
+               tempo_copy = copy_metrics_and_point (_metrics, copy, ts);
+               if (!tempo_copy) {
                        return false;
                }
        }
 
        const double beat = bbt_to_beats_locked (copy, bbt);
-       const bool ret = solve_map (copy, new_section, pulse_at_beat_locked (copy, beat));
+       const bool ret = solve_map (copy, tempo_copy, pulse_at_beat_locked (copy, beat));
 
        Metrics::const_iterator d = copy.begin();
        while (d != copy.end()) {
@@ -2357,16 +2467,16 @@ TempoMap::predict_tempo_frame (TempoSection* section, const BBT_Time& bbt)
        Glib::Threads::RWLock::ReaderLock lm (lock);
        Metrics future_map;
        framepos_t ret = 0;
-       TempoSection* new_section = copy_metrics_and_point (_metrics, future_map, section);
-       if (!new_section) {
+       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, section);
+       if (!tempo_copy) {
                return 0;
        }
        const double beat = bbt_to_beats_locked (future_map, bbt);
 
-       if (solve_map (future_map, new_section, pulse_at_beat_locked (future_map, beat))) {
-               ret = new_section->frame();
+       if (solve_map (future_map, tempo_copy, pulse_at_beat_locked (future_map, beat))) {
+               ret = tempo_copy->frame();
        } else {
-               ret = frame_at_beat_locked (_metrics, beat);
+               ret = section->frame();
        }
 
        Metrics::const_iterator d = future_map.begin();
@@ -2383,12 +2493,12 @@ TempoMap::predict_tempo_pulse (TempoSection* section, const framepos_t& frame)
        Glib::Threads::RWLock::ReaderLock lm (lock);
        Metrics future_map;
        double ret = 0.0;
-       TempoSection* new_section = copy_metrics_and_point (_metrics, future_map, section);
+       TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, section);
 
-       if (solve_map (future_map, new_section, frame)) {
-               ret = new_section->pulse();
+       if (solve_map (future_map, tempo_copy, frame)) {
+               ret = tempo_copy->pulse();
        } else {
-               ret = pulse_at_frame_locked (_metrics, frame);
+               ret = section->pulse();
        }
 
        Metrics::const_iterator d = future_map.begin();
@@ -2405,8 +2515,8 @@ TempoMap::gui_move_tempo_frame (TempoSection* ts, const framepos_t& frame)
        Metrics future_map;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection* new_section = copy_metrics_and_point (_metrics, future_map, ts);
-               if (solve_map (future_map, new_section, frame)) {
+               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);
                }
@@ -2427,8 +2537,8 @@ TempoMap::gui_move_tempo_beat (TempoSection* ts, const double& beat)
        Metrics future_map;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection* new_section = copy_metrics_and_point (_metrics, future_map, ts);
-               if (solve_map (future_map, new_section, pulse_at_beat_locked (future_map, beat))) {
+               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);
                }
@@ -2452,18 +2562,36 @@ TempoMap::gui_move_meter (MeterSection* ms, const framepos_t&  frame)
                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);
                }
        }
 
+       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 double& pulse)
+TempoMap::gui_move_meter (MeterSection* ms, const Timecode::BBT_Time& bbt)
 {
+       Metrics future_map;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               solve_map (_metrics, ms, pulse);
+               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);
+               }
+       }
+
+       Metrics::const_iterator d = future_map.begin();
+       while (d != future_map.end()) {
+               delete (*d);
+               ++d;
        }
 
        MetricPositionChanged (); // Emit Signal
@@ -2476,8 +2604,8 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm)
        bool can_solve = false;
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
-               TempoSection* new_section = copy_metrics_and_point (_metrics, future_map, ts);
-               new_section->set_beats_per_minute (bpm.beats_per_minute());
+               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, true)) {
@@ -2504,10 +2632,15 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
        Metrics future_map;
        TempoSection* ts = 0;
 
+       if (frame <= first_meter().frame()) {
+               return;
+       }
+
        if (ms->position_lock_style() == AudioTime) {
                /* disabled for now due to faked tempo locked to meter pulse */
                return;
        }
+
        {
                Glib::Threads::RWLock::WriterLock lm (lock);
                ts = const_cast<TempoSection*>(&tempo_section_at_locked (_metrics, ms->frame() - 1));
@@ -2519,7 +2652,7 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
                const frameoffset_t fr_off = frame - ms->frame();
                double new_bpm = 0.0;
 
-               if (prev_t) {
+               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));
                }
 
@@ -2527,8 +2660,8 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
                   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;
+               frameoffset_t frame_contribution = 0;
+               frameoffset_t prev_t_frame_contribution = fr_off;
 
                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. */
@@ -2578,12 +2711,6 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
                                new_bpm = prev_t->tempo_at_frame (prev_t->frame() + fr_off, _frame_rate) * (double) prev_t->note_type();
                        }
 
-                       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()));
-                       }
-
                } 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();
@@ -2591,13 +2718,13 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
                                /* 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()));
-                       }
+               /* 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 > -1.0 && diff  < 1.0) {
+                       new_bpm = prev_t->beats_per_minute() * ((ms->frame() - prev_t->frame())
+                                                               / (double) ((ms->frame() + prev_t_frame_contribution) - prev_t->frame()));
                }
 
                prev_t->set_beats_per_minute (new_bpm);
@@ -2625,7 +2752,129 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
        }
 
        MetricPositionChanged (); // Emit Signal
-       return;
+}
+
+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_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;
+               double start_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
+
+               if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+                       contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame());
+               }
+
+               frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
+               double end_pulse = prev_t->pulse_at_frame (end_frame, _frame_rate);
+               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() * ((frame - prev_to_prev_t->frame())
+                                                                               / (double) ((frame + prev_t_frame_contribution) - prev_to_prev_t->frame()));
+
+                               } else {
+                                       /* prev to prev is irrelevant */
+
+                                       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();
+                                       }
+                               }
+                       } else {
+                               /* AudioTime */
+                               if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+                                       new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame())
+                                                                               / (double) ((frame + prev_t_frame_contribution) - prev_t->frame()));
+                               } else {
+                                       /* prev_to_prev_t is irrelevant */
+
+                                       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 {
+
+                       double frame_ratio;
+                       double pulse_ratio;
+                       const framepos_t pulse_pos = prev_t->frame_at_pulse (pulse, _frame_rate);
+
+                       if (prev_to_prev_t) {
+
+                               frame_ratio = (((pulse_pos - fr_off) - prev_to_prev_t->frame()) / (double) ((pulse_pos) - prev_to_prev_t->frame()));
+                               pulse_ratio = ((start_pulse - prev_to_prev_t->pulse()) / (end_pulse - prev_to_prev_t->pulse()));
+                       } else {
+
+                               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)) {
+                       ts->set_beats_per_minute (new_bpm);
+                       recompute_tempos (_metrics);
+                       recompute_meters (_metrics);
+               }
+       }
+
+       Metrics::const_iterator d = future_map.begin();
+       while (d != future_map.end()) {
+               delete (*d);
+               ++d;
+       }
+
+       MetricPositionChanged (); // Emit Signal
 }
 
 framecnt_t
@@ -2749,16 +2998,27 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
 }
 
 void
-TempoMap::round_bbt (BBT_Time& when, const int32_t& sub_num)
+TempoMap::round_bbt (BBT_Time& when, const int32_t& sub_num, RoundMode dir)
 {
        if (sub_num == -1) {
-               const double bpb = meter_section_at_beat (bbt_to_beats_locked (_metrics, when)).divisions_per_bar();
-               if ((double) when.beats > bpb / 2.0) {
+               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;
                }
-               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) {
@@ -2769,32 +3029,76 @@ TempoMap::round_bbt (BBT_Time& when, const int32_t& sub_num)
                        }
                }
                when.ticks = 0;
+
                return;
        }
+
        const uint32_t ticks_one_subdivisions_worth = BBT_Time::ticks_per_beat / sub_num;
-       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 (dir > 0) {
+               /* round to next (or same iff dir == RoundUpMaybe) */
+
+               uint32_t mod = when.ticks % ticks_one_subdivisions_worth;
 
-               if (when.ticks > Timecode::BBT_Time::ticks_per_beat) {
-                       ++when.beats;
-                       when.ticks -= Timecode::BBT_Time::ticks_per_beat;
+               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;
                }
 
-       } else if (rem > 0) {
-               /* closer to previous subdivision, so shift backward */
+               if (when.ticks >= BBT_Time::ticks_per_beat) {
+                       when.ticks -= BBT_Time::ticks_per_beat;
+               }
 
-               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 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 = when.ticks - rem;
+                       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;
+                       }
                }
        }
 }