fix bypassing plugins with sidechain i/o
[ardour.git] / libs / ardour / tempo.cc
index 9fa77a71a99c04eabbfef7143f43bf8b0a6633ec..c5c7e2c52fa90ad158c22b4126f4c0473acade76 100644 (file)
@@ -219,14 +219,14 @@ TempoSection::tempo_at_frame (const framepos_t& f, const framecnt_t& frame_rate)
 
 /** returns the zero-based frame (relative to session)
    where the tempo in whole pulses per minute occurs in this section.
-   beat b is only used for constant tempos.
+   pulse p is only used for constant tempos.
    note that the tempo map may have multiple such values.
 */
 framepos_t
-TempoSection::frame_at_tempo (const double& ppm, const double& b, const framecnt_t& frame_rate) const
+TempoSection::frame_at_tempo (const double& ppm, const double& p, const framecnt_t& frame_rate) const
 {
        if (_type == Constant || _c_func == 0.0) {
-               return ((b - pulse())  * frames_per_pulse (frame_rate))  + frame();
+               return ((p - pulse()) * frames_per_pulse (frame_rate))  + frame();
        }
 
        return minute_to_frame (time_at_pulse_tempo (ppm), frame_rate) + frame();
@@ -589,17 +589,20 @@ MeterSection::get_state() const
   Tempo is the rate of the musical pulse.
   Meters divide the pulses into measures and beats.
 
+  TempoSections - provide pulses in the form of beats_per_minute() and note_type() where note_type is the division of a whole pulse,
+  and beats_per_minute is the number of note_types in one minute (unlike what its name suggests).
   Note that Tempo::beats_per_minute() has nothing to do with musical beats.
-  It should rather be thought of as tempo note divisions per minute.
 
-  TempoSections, which are nice to think of in whole pulses per minute,
-  and MeterSecions which divide tempo pulses into measures (via divisions_per_bar)
-  and beats (via note_divisor) are used to form a tempo map.
+  MeterSecions - divide pulses into measures (via divisions_per_bar) and beats (via note_divisor).
+
+  Both tempos and meters have a pulse position and a frame position.
+  Meters also have a beat position, which is always 0.0 for the first meter.
   TempoSections and MeterSections may be locked to either audio or music (position lock style).
-  We construct the tempo map by first using the frame or pulse position (depending on position lock style) of each tempo.
-  We then use this pulse/frame layout to find the beat & pulse or frame position of each meter (again depending on lock style).
+  The lock style determines the 'true' position of the section wich is used to calculate the other postion parameters of the section.
 
-  Having done this, we can now find any one of tempo, beat, frame or pulse if a beat, frame, pulse or tempo is known.
+  The first tempo and first meter are special. they must move together, and must be locked to audio.
+  Audio locked tempos which lie before the first meter are made inactive.
+  They will be re-activated if the first meter is again placed before them.
 
   With tepo sections potentially being ramped, meters provide a way of mapping beats to whole pulses without
   referring to the tempo function(s) involved as the distance in whole pulses between a meter and a subsequent beat is
@@ -614,18 +617,13 @@ MeterSection::get_state() const
   Remembering that ramped tempo sections interact, it is important to avoid referring to any other tempos when moving tempo sections,
   Here, beats (meters) are used to determine the new pulse (see predict_tempo_position())
 
-  The first tempo and first meter are special. they must move together, and must be locked to audio.
-  Audio locked tempos which lie before the first meter are made inactive.
-  They will be re-activated if the first meter is again placed before them.
-
-  Both tempos and meters have a pulse position and a frame position.
-  Meters also have a beat position, which is always 0.0 for the first meter.
-
-  A tempo locked to music is locked to musical pulses.
-  A meter locked to music is locked to beats.
-
-  Recomputing the tempo map is the process where the 'missing' position
+  Recomputing the map is the process where the 'missing' position
   (tempo pulse or meter pulse & beat in the case of AudioTime, frame for MusicTime) is calculated.
+  We construct the tempo map by first using the frame or pulse position (depending on position lock style) of each tempo.
+  We then use this tempo map (really just the tempos) to find the pulse or frame position of each meter (again depending on lock style).
+
+  Having done this, we can now find any musical duration by selecting the tempo and meter covering the position (or tempo) in question
+  and querying its appropriate meter/tempo.
 
   It is important to keep the _metrics in an order that makes sense.
   Because ramped MusicTime and AudioTime tempos can interact with each other,
@@ -1009,12 +1007,12 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T
 MeterSection*
 TempoMap::add_meter_locked (const Meter& meter, double beat, const Timecode::BBT_Time& where, framepos_t frame, PositionLockStyle pls, bool recompute)
 {
-       const MeterSection& prev_m = meter_section_at_locked  (_metrics, frame - 1);
+       const MeterSection& prev_m = meter_section_at_frame_locked  (_metrics, frame - 1);
        const double pulse = ((where.bars - prev_m.bbt().bars) * (prev_m.divisions_per_bar() / prev_m.note_divisor())) + prev_m.pulse();
 
        if (pls == AudioTime) {
                /* add meter-locked tempo */
-               add_tempo_locked (tempo_at_locked (_metrics, frame), pulse,  frame, TempoSection::Ramp, AudioTime, true, true);
+               add_tempo_locked (tempo_at_frame_locked (_metrics, frame), pulse,  frame, TempoSection::Ramp, AudioTime, true, true);
        }
 
        MeterSection* new_meter = new MeterSection (pulse, frame, beat, where, meter.divisions_per_bar(), meter.note_divisor(), pls);
@@ -1273,24 +1271,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));
                        }
@@ -1386,29 +1384,171 @@ TempoMap::metric_at (BBT_Time bbt) const
 }
 
 double
-TempoMap::pulse_at_beat (const double& beat) const
+TempoMap::beat_at_frame (const framecnt_t& frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return pulse_at_beat_locked (_metrics, beat);
+       return beat_at_frame_locked (_metrics, frame);
 }
 
+/* meter / tempo section based */
 double
-TempoMap::pulse_at_beat_locked (const Metrics& metrics, const double& beat) const
+TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
 {
+       const TempoSection& ts = tempo_section_at_frame_locked (metrics, frame);
        MeterSection* prev_m = 0;
+       MeterSection* next_m = 0;
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                MeterSection* m;
                if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
-                       if (prev_m && m->beat() > beat) {
+                       if (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();
 
+       /* audio locked meters fake their beat */
+       if (next_m && next_m->beat() < beat) {
+               return next_m->beat();
        }
-       double const ret = prev_m->pulse() + ((beat - prev_m->beat()) / prev_m->note_divisor());
-       return ret;
+
+       return beat;
+}
+
+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);
+       const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat);
+
+       return prev_t->frame_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse(), _frame_rate);
+}
+
+Tempo
+TempoMap::tempo_at_frame (const framepos_t& frame) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return tempo_at_frame_locked (_metrics, frame);
+}
+
+Tempo
+TempoMap::tempo_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const
+{
+       TempoSection* prev_t = 0;
+
+       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+               TempoSection* t;
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                       if (!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;
+}
+
+/** returns the frame at which the supplied tempo occurs, or
+ *  the frame of the last tempo section (search exhausted)
+ *  only the position of the first occurence will be returned
+ *  (extend me)
+*/
+framepos_t
+TempoMap::frame_at_tempo (const Tempo& tempo) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       return frame_at_tempo_locked (_metrics, tempo);
+}
+
+
+framepos_t
+TempoMap::frame_at_tempo_locked (const Metrics& metrics, const Tempo& tempo) const
+{
+       TempoSection* prev_t = 0;
+       const double tempo_ppm = tempo.beats_per_minute() / tempo.note_type();
+
+       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;
+                       }
+
+                       const double t_ppm = t->beats_per_minute() / t->note_type();
+
+                       if (t_ppm == tempo_ppm) {
+                               return t->frame();
+                       }
+
+                       if (prev_t) {
+                               const double prev_t_ppm = prev_t->beats_per_minute() / prev_t->note_type();
+
+                               if ((t_ppm > tempo_ppm && prev_t_ppm < tempo_ppm) || (t_ppm < tempo_ppm && prev_t_ppm > tempo_ppm)) {
+                                       const framepos_t ret_frame = prev_t->frame_at_tempo (tempo_ppm, prev_t->pulse(), _frame_rate);
+                                       return ret_frame;
+                               }
+                       }
+                       prev_t = t;
+               }
+       }
+
+       return prev_t->frame();
+}
+
+/** more precise than doing tempo_at_frame (frame_at_beat (b)),
+ *  as there is no intermediate frame rounding.
+ */
+Tempo
+TempoMap::tempo_at_beat (const double& beat) const
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+       const MeterSection* prev_m = &meter_section_at_beat_locked (_metrics, beat);
+       const TempoSection* prev_t = &tempo_section_at_beat_locked (_metrics, beat);
+       const double note_type = prev_t->note_type();
+
+       return Tempo (prev_t->tempo_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse()) * note_type, note_type);
+}
+
+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
+{
+       const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat);
+
+       return prev_m->pulse() + ((beat - prev_m->beat()) / prev_m->note_divisor());
 }
 
 double
@@ -1512,70 +1652,6 @@ TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) co
        return ret;
 }
 
-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::beat_at_bbt (const Timecode::BBT_Time& bbt)
 {
@@ -1704,7 +1780,7 @@ TempoMap::pulse_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time&
 
        const double remaining_bars = bbt.bars - prev_m->bbt().bars;
        const double remaining_pulses = remaining_bars * prev_m->divisions_per_bar() / prev_m->note_divisor();
-       const double ret = remaining_pulses + prev_m->pulse();
+       const double ret = remaining_pulses + prev_m->pulse() + (((bbt.beats - 1) + (bbt.ticks / BBT_Time::ticks_per_beat)) / prev_m->note_divisor());
 
        return ret;
 }
@@ -1768,25 +1844,52 @@ TempoMap::bbt_at_pulse_locked (const Metrics& metrics, const double& pulse) cons
        return ret;
 }
 
-void
-TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
+BBT_Time
+TempoMap::bbt_at_frame (framepos_t frame)
 {
-
        if (frame < 0) {
+               BBT_Time bbt;
                bbt.bars = 1;
                bbt.beats = 1;
                bbt.ticks = 0;
                warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg;
-               return;
+               return bbt;
        }
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       const double beat = beat_at_frame_locked (_metrics, frame);
 
-       bbt = bbt_at_beat_locked (_metrics, beat);
+       return bbt_at_frame_locked (_metrics, frame);
+}
+
+BBT_Time
+TempoMap::bbt_at_frame_rt (framepos_t frame)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
+
+       if (!lm.locked()) {
+               throw std::logic_error ("TempoMap::bbt_time_rt() could not lock tempo map");
+       }
+
+       return bbt_at_frame_locked (_metrics, frame);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const
+{
+       if (frame < 0) {
+               BBT_Time bbt;
+               bbt.bars = 1;
+               bbt.beats = 1;
+               bbt.ticks = 0;
+               warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg;
+               return bbt;
+       }
+       const double beat = beat_at_frame_locked (metrics, frame);
+
+       return bbt_at_beat_locked (metrics, beat);
 }
 
 framepos_t
-TempoMap::frame_time (const BBT_Time& bbt)
+TempoMap::frame_at_bbt (const BBT_Time& bbt)
 {
        if (bbt.bars < 1) {
                warning << string_compose (_("tempo map asked for frame time at bar < 1  (%1)\n"), bbt) << endmsg;
@@ -1798,12 +1901,12 @@ TempoMap::frame_time (const BBT_Time& bbt)
        }
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       return frame_time_locked (_metrics, bbt);
+       return frame_at_bbt_locked (_metrics, bbt);
 }
 
-/* meter section based */
+/* meter & tempo section based */
 framepos_t
-TempoMap::frame_time_locked (const Metrics& metrics, const BBT_Time& bbt) const
+TempoMap::frame_at_bbt_locked (const Metrics& metrics, const BBT_Time& bbt) const
 {
        /* HOLD THE READER LOCK */
 
@@ -1825,23 +1928,32 @@ TempoMap::check_solved (const Metrics& metrics) const
                                continue;
                        }
                        if (prev_t) {
+                               /* check ordering */
                                if ((t->frame() <= prev_t->frame()) || (t->pulse() <= prev_t->pulse())) {
                                        return false;
                                }
 
-                               /* precision check ensures pulses and frames align.*/
-                               if (t->frame() != prev_t->frame_at_pulse (t->pulse(), _frame_rate)) {
+                               /* precision check ensures tempo and frames align.*/
+                               if (t->frame() != prev_t->frame_at_tempo (t->pulses_per_minute(), t->pulse(), _frame_rate)) {
                                        if (!t->locked_to_meter()) {
                                                return false;
                                        }
                                }
+
+                               /* gradient limit - who knows what it should be?
+                                  things are also ok (if a little chaotic) without this
+                               */
+                               if (fabs (prev_t->c_func()) > 1000.0) {
+                                       //std::cout << "c : " << prev_t->c_func() << std::endl;
+                                       return false;
+                               }
                        }
                        prev_t = t;
                }
 
                if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
                        if (prev_m && m->position_lock_style() == AudioTime) {
-                               TempoSection* t = const_cast<TempoSection*>(&tempo_section_at_locked (metrics, m->frame() - 1));
+                               TempoSection* t = const_cast<TempoSection*>(&tempo_section_at_frame_locked (metrics, m->frame() - 1));
                                const double nascent_m_pulse = ((m->beat() - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse();
                                const framepos_t nascent_m_frame = t->frame_at_pulse (nascent_m_pulse, _frame_rate);
 
@@ -1888,6 +2000,7 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram
        TempoSection* section_prev = 0;
        framepos_t first_m_frame = 0;
 
+       /* can't move a tempo before the first meter */
        for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
                MeterSection* m;
                if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
@@ -1914,6 +2027,9 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram
                        if (prev_t) {
                                if (t == section) {
                                        section_prev = prev_t;
+                                       if (t->locked_to_meter()) {
+                                               prev_t = t;
+                                       }
                                        continue;
                                }
                                if (t->position_lock_style() == MusicTime) {
@@ -1937,11 +2053,15 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram
                }
        }
 
+#if (0)
        recompute_tempos (imaginary);
 
        if (check_solved (imaginary)) {
                return true;
+       } else {
+               dunp (imaginary, std::cout);
        }
+#endif
 
        MetricSectionFrameSorter fcmp;
        imaginary.sort (fcmp);
@@ -1998,11 +2118,15 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub
                section->set_frame (section_prev->frame_at_pulse (pulse, _frame_rate));
        }
 
+#if (0)
        recompute_tempos (imaginary);
 
        if (check_solved (imaginary)) {
                return true;
+       } else {
+               dunp (imaginary, std::cout);
        }
+#endif
 
        MetricSectionSorter cmp;
        imaginary.sort (cmp);
@@ -2029,7 +2153,7 @@ bool
 TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const framepos_t& frame)
 {
        /* disallow moving first meter past any subsequent one, and any movable meter before the first one */
-       const MeterSection* other =  &meter_section_at_locked (imaginary, frame);
+       const MeterSection* other =  &meter_section_at_frame_locked (imaginary, frame);
        if ((!section->movable() && other->movable()) || (!other->movable() && section->movable() && other->frame() >= frame)) {
                return false;
        }
@@ -2041,10 +2165,8 @@ TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const fram
                }
        }
 
-       /* it would make sense to bail out if there is no audio-locked meter,
-          however it may be desirable to move a music-locked meter by frame at some point.
-       */
        TempoSection* meter_locked_tempo = 0;
+
        for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) {
                TempoSection* t;
                if ((t = dynamic_cast<TempoSection*> (*ii)) != 0) {
@@ -2098,8 +2220,11 @@ TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const fram
                                                        return false;
                                                }
                                        } else {
-                                               /* all is ok. set section's tempo */
-                                               MeterSection* meter_copy = const_cast<MeterSection*> (&meter_section_at_locked (future_map, section->frame()));
+                                               /* all is ok. set section's locked tempo if allowed.
+                                                  possibly disallowed if there is an adjacent audio-locked tempo.
+                                                  XX this check could possibly go. its never actually happened here.
+                                               */
+                                               MeterSection* meter_copy = const_cast<MeterSection*> (&meter_section_at_frame_locked (future_map, section->frame()));
                                                meter_copy->set_frame (frame);
 
                                                if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
@@ -2335,6 +2460,13 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, MeterSe
        return ret;
 }
 
+/** answers the question "is this a valid beat position for this tempo section?".
+ *  it returns true if the tempo section can be moved to the requested bbt position,
+ *  leaving the tempo map in a solved state.
+ * @param section the tempo section to be moved
+ * @param bbt the requested new position for the tempo section
+ * @return true if the tempo section can be moved to the position, otherwise false.
+ */
 bool
 TempoMap::can_solve_bbt (TempoSection* ts, const BBT_Time& bbt)
 {
@@ -2396,15 +2528,42 @@ TempoMap::predict_tempo_position (TempoSection* section, const BBT_Time& bbt)
 }
 
 void
-TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame)
+TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int& sub_num)
 {
        Metrics future_map;
+       bool was_musical = ts->position_lock_style() == MusicTime;
+
+       if (sub_num == 0 && was_musical) {
+               /* if we're not snapping to music,
+                  AudioTime and MusicTime may be treated identically.
+               */
+               ts->set_position_lock_style (AudioTime);
+       }
 
        if (ts->position_lock_style() == MusicTime) {
                {
+                       /* if we're snapping to a musical grid, set the pulse exactly instead of via the supplied frame. */
                        Glib::Threads::RWLock::WriterLock lm (lock);
                        TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
-                       const double pulse = pulse_at_frame_locked (future_map, frame);
+                       double beat = beat_at_frame_locked (future_map, frame);
+
+                       if (sub_num > 1) {
+                               beat = floor (beat) + (floor (((beat - floor (beat)) * (double) sub_num) + 0.5) / sub_num);
+                       } else if (sub_num == 1) {
+                               /* snap to beat */
+                               beat = floor (beat + 0.5);
+                       }
+
+                       double pulse = pulse_at_beat_locked (future_map, beat);
+
+                       if (sub_num == -1) {
+                               /* snap to  bar */
+                               BBT_Time bbt = bbt_at_beat_locked (future_map, beat);
+                               bbt.beats = 1;
+                               bbt.ticks = 0;
+                               pulse = pulse_at_bbt_locked (future_map, bbt);
+                       }
+
                        if (solve_map_pulse (future_map, tempo_copy, pulse)) {
                                solve_map_pulse (_metrics, ts, pulse);
                                recompute_meters (_metrics);
@@ -2423,6 +2582,10 @@ TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame)
                }
        }
 
+       if (sub_num == 0 && was_musical) {
+               ts->set_position_lock_style (MusicTime);
+       }
+
        Metrics::const_iterator d = future_map.begin();
        while (d != future_map.end()) {
                delete (*d);
@@ -2526,7 +2689,7 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
                const frameoffset_t fr_off = end_frame - frame;
 
                if (prev_t && prev_t->pulse() > 0.0) {
-                       prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_locked (future_map, prev_t->frame() - 1));
+                       prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_frame_locked (future_map, prev_t->frame() - 1));
                }
 
                TempoSection* next_t = 0;
@@ -2539,33 +2702,40 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
                                }
                        }
                }
+               /* minimum allowed measurement distance in frames */
+               const framepos_t min_dframe = 2;
 
                /* the change in frames is the result of changing the slope of at most 2 previous tempo sections.
                   constant to constant is straightforward, as the tempo prev to prev_t has constant slope.
                */
                double contribution = 0.0;
-               double start_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
 
                if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
                        contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame());
                }
 
-               frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
-               double end_pulse = prev_t->pulse_at_frame (end_frame, _frame_rate);
+               const frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
+
+               const double start_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
+               const double end_pulse = prev_t->pulse_at_frame (end_frame, _frame_rate);
+
                double new_bpm;
 
                if (prev_t->type() == TempoSection::Constant || prev_t->c_func() == 0.0) {
 
                        if (prev_t->position_lock_style() == MusicTime) {
                                if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+                                       if (frame > prev_to_prev_t->frame() + min_dframe && (frame + prev_t_frame_contribution) > prev_to_prev_t->frame() + min_dframe) {
 
-                                       new_bpm = prev_t->beats_per_minute() * ((frame - prev_to_prev_t->frame())
-                                                                               / (double) ((frame + prev_t_frame_contribution) - prev_to_prev_t->frame()));
-
+                                               new_bpm = prev_t->beats_per_minute() * ((frame - prev_to_prev_t->frame())
+                                                                                       / (double) ((frame + prev_t_frame_contribution) - prev_to_prev_t->frame()));
+                                       } else {
+                                               new_bpm = prev_t->beats_per_minute();
+                                       }
                                } else {
                                        /* prev to prev is irrelevant */
 
-                                       if (start_pulse != prev_t->pulse()) {
+                                       if (start_pulse > prev_t->pulse() && end_pulse > prev_t->pulse()) {
                                                new_bpm = prev_t->beats_per_minute() * ((start_pulse - prev_t->pulse()) / (end_pulse - prev_t->pulse()));
                                        } else {
                                                new_bpm = prev_t->beats_per_minute();
@@ -2574,12 +2744,17 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
                        } else {
                                /* AudioTime */
                                if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
-                                       new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame())
-                                                                               / (double) ((frame + prev_t_frame_contribution) - prev_t->frame()));
+                                       if (frame > prev_to_prev_t->frame() + min_dframe && end_frame > prev_to_prev_t->frame() + min_dframe) {
+
+                                               new_bpm = prev_t->beats_per_minute() * ((frame - prev_to_prev_t->frame())
+                                                                                       / (double) ((end_frame) - prev_to_prev_t->frame()));
+                                       } else {
+                                               new_bpm = prev_t->beats_per_minute();
+                                       }
                                } else {
                                        /* prev_to_prev_t is irrelevant */
 
-                                       if (end_frame != prev_t->frame()) {
+                                       if (frame > prev_t->frame() + min_dframe && end_frame > prev_t->frame() + min_dframe) {
                                                new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame()) / (double) (end_frame - prev_t->frame()));
                                        } else {
                                                new_bpm = prev_t->beats_per_minute();
@@ -2588,22 +2763,34 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
                        }
                } else {
 
-                       double frame_ratio;
-                       double pulse_ratio;
+                       double frame_ratio = 1.0;
+                       double pulse_ratio = 1.0;
                        const framepos_t pulse_pos = prev_t->frame_at_pulse (pulse, _frame_rate);
 
                        if (prev_to_prev_t) {
-
-                               frame_ratio = (((pulse_pos - fr_off) - prev_to_prev_t->frame()) / (double) ((pulse_pos) - prev_to_prev_t->frame()));
-                               pulse_ratio = ((start_pulse - prev_to_prev_t->pulse()) / (end_pulse - prev_to_prev_t->pulse()));
+                               if (pulse_pos > prev_to_prev_t->frame() + min_dframe && (pulse_pos - fr_off) > prev_to_prev_t->frame() + min_dframe) {
+                                       frame_ratio = (((pulse_pos - fr_off) - prev_to_prev_t->frame()) / (double) ((pulse_pos) - prev_to_prev_t->frame()));
+                               }
+                               if (end_pulse > prev_to_prev_t->pulse() && start_pulse > prev_to_prev_t->pulse()) {
+                                       pulse_ratio = ((start_pulse - prev_to_prev_t->pulse()) / (end_pulse - prev_to_prev_t->pulse()));
+                               }
                        } else {
-
-                               frame_ratio = (((pulse_pos - fr_off) - prev_t->frame()) / (double) ((pulse_pos) - prev_t->frame()));
+                               if (pulse_pos > prev_t->frame() + min_dframe && (pulse_pos - fr_off) > prev_t->frame() + min_dframe) {
+                                       frame_ratio = (((pulse_pos - fr_off) - prev_t->frame()) / (double) ((pulse_pos) - prev_t->frame()));
+                               }
                                pulse_ratio = (start_pulse / end_pulse);
                        }
                        new_bpm = prev_t->beats_per_minute() * (pulse_ratio * frame_ratio);
                }
 
+               /* don't clamp and proceed here.
+                  testing has revealed that this can go negative,
+                  which is an entirely different thing to just being too low.
+               */
+               if (new_bpm < 0.5) {
+                       return;
+               }
+               new_bpm = min (new_bpm, (double) 1000.0);
                prev_t->set_beats_per_minute (new_bpm);
                recompute_tempos (future_map);
                recompute_meters (future_map);
@@ -2758,22 +2945,22 @@ TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
                        /* find bar previous to 'frame' */
                        bbt.beats = 1;
                        bbt.ticks = 0;
-                       return frame_time_locked (_metrics, bbt);
+                       return frame_at_bbt_locked (_metrics, bbt);
 
                } else if (dir > 0) {
                        /* find bar following 'frame' */
                        ++bbt.bars;
                        bbt.beats = 1;
                        bbt.ticks = 0;
-                       return frame_time_locked (_metrics, bbt);
+                       return frame_at_bbt_locked (_metrics, bbt);
                } else {
                        /* true rounding: find nearest bar */
-                       framepos_t raw_ft = frame_time_locked (_metrics, bbt);
+                       framepos_t raw_ft = frame_at_bbt_locked (_metrics, bbt);
                        bbt.beats = 1;
                        bbt.ticks = 0;
-                       framepos_t prev_ft = frame_time_locked (_metrics, bbt);
+                       framepos_t prev_ft = frame_at_bbt_locked (_metrics, bbt);
                        ++bbt.bars;
-                       framepos_t next_ft = frame_time_locked (_metrics, bbt);
+                       framepos_t next_ft = frame_at_bbt_locked (_metrics, bbt);
 
                        if ((raw_ft - prev_ft) > (next_ft - prev_ft) / 2) { 
                                return next_ft;
@@ -2809,25 +2996,30 @@ TempoMap::get_grid (vector<TempoMap::BBTPoint>& points,
        if (cnt < 0.0) {
                cnt = 0.0;
        }
+
+       if (frame_at_beat_locked (_metrics, cnt) >= upper) {
+               return;
+       }
+
        while (pos < upper) {
                pos = frame_at_beat_locked (_metrics, cnt);
-               const TempoSection tempo = tempo_section_at_locked (_metrics, pos);
-               const MeterSection meter = meter_section_at_locked (_metrics, pos);
+               const TempoSection tempo = tempo_section_at_frame_locked (_metrics, pos);
+               const MeterSection meter = meter_section_at_frame_locked (_metrics, pos);
                const BBT_Time bbt = bbt_at_beat_locked (_metrics, cnt);
-               points.push_back (BBTPoint (meter, tempo_at_locked (_metrics, pos), pos, bbt.bars, bbt.beats, tempo.c_func()));
+               points.push_back (BBTPoint (meter, tempo_at_frame_locked (_metrics, pos), pos, bbt.bars, bbt.beats, tempo.c_func()));
                ++cnt;
        }
 }
 
 const TempoSection&
-TempoMap::tempo_section_at (framepos_t frame) const
+TempoMap::tempo_section_at_frame (framepos_t frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return tempo_section_at_locked (_metrics, frame);
+       return tempo_section_at_frame_locked (_metrics, frame);
 }
 
 const TempoSection&
-TempoMap::tempo_section_at_locked (const Metrics& metrics, framepos_t frame) const
+TempoMap::tempo_section_at_frame_locked (const Metrics& metrics, framepos_t frame) const
 {
        Metrics::const_iterator i;
        TempoSection* prev = 0;
@@ -2874,24 +3066,6 @@ TempoMap::tempo_section_at_beat_locked (const Metrics& metrics, const double& be
        return *prev_t;
 }
 
-const TempoSection&
-TempoMap::tempo_section_at_pulse_locked (const Metrics& metrics, const double& pulse) const
-{
-       TempoSection* prev_t = 0;
-
-       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
-               TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if (prev_t && t->pulse() > pulse) {
-                               break;
-                       }
-                       prev_t = t;
-               }
-
-       }
-       return *prev_t;
-}
-
 /* don't use this to calculate length (the tempo is only correct for this frame).
    do that stuff based on the beat_at_frame and frame_at_beat api
 */
@@ -2900,7 +3074,7 @@ TempoMap::frames_per_beat_at (const framepos_t& frame, const framecnt_t& sr) con
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       const TempoSection* ts_at = &tempo_section_at_locked (_metrics, frame);
+       const TempoSection* ts_at = &tempo_section_at_frame_locked (_metrics, frame);
        const TempoSection* ts_after = 0;
        Metrics::const_iterator i;
 
@@ -2919,50 +3093,14 @@ TempoMap::frames_per_beat_at (const framepos_t& frame, const framecnt_t& sr) con
        }
 
        if (ts_after) {
-               return  (60.0 * _frame_rate) / (ts_at->tempo_at_frame (frame, _frame_rate));
+               return  (60.0 * _frame_rate) / (ts_at->tempo_at_frame (frame, _frame_rate) * ts_at->note_type());
        }
        /* must be treated as constant tempo */
        return ts_at->frames_per_beat (_frame_rate);
 }
 
-const Tempo
-TempoMap::tempo_at_locked (const Metrics& metrics, const framepos_t& frame) const
-{
-       TempoSection* prev_t = 0;
-
-       Metrics::const_iterator i;
-
-       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
-               TempoSection* t;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if (!t->active()) {
-                               continue;
-                       }
-                       if ((prev_t) && t->frame() > frame) {
-                               /* t is the section past frame */
-                               const double ret_bpm = prev_t->tempo_at_frame (frame, _frame_rate) * prev_t->note_type();
-                               const Tempo ret_tempo (ret_bpm, prev_t->note_type());
-                               return ret_tempo;
-                       }
-                       prev_t = t;
-               }
-       }
-
-       const double ret = prev_t->beats_per_minute();
-       const Tempo ret_tempo (ret, prev_t->note_type ());
-
-       return ret_tempo;
-}
-
-const Tempo
-TempoMap::tempo_at (const framepos_t& frame) const
-{
-       Glib::Threads::RWLock::ReaderLock lm (lock);
-       return tempo_at_locked (_metrics, frame);
-}
-
 const MeterSection&
-TempoMap::meter_section_at_locked (const Metrics& metrics, framepos_t frame) const
+TempoMap::meter_section_at_frame_locked (const Metrics& metrics, framepos_t frame) const
 {
        Metrics::const_iterator i;
        MeterSection* prev = 0;
@@ -2990,10 +3128,10 @@ TempoMap::meter_section_at_locked (const Metrics& metrics, framepos_t frame) con
 
 
 const MeterSection&
-TempoMap::meter_section_at (framepos_t frame) const
+TempoMap::meter_section_at_frame (framepos_t frame) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       return meter_section_at_locked (_metrics, frame);
+       return meter_section_at_frame_locked (_metrics, frame);
 }
 
 const MeterSection&
@@ -3022,7 +3160,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();
@@ -3346,7 +3484,7 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount)
                                tempo = t;
                                // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " beat = " << (*i)->pulse() <<endl;
                        } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
-                               bbt_time (m->frame(), bbt);
+                               bbt = bbt_at_frame_locked (_metrics, m->frame());
 
                                // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
 
@@ -3453,14 +3591,18 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount)
 framepos_t
 TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const
 {
-       return frame_at_beat (beat_at_frame (pos) + beats.to_double());
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return frame_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos) + beats.to_double());
 }
 
 /** Subtract some (fractional) beats from a frame position, and return the result in frames */
 framepos_t
 TempoMap::framepos_minus_beats (framepos_t pos, Evoral::Beats beats) const
 {
-       return frame_at_beat (beat_at_frame (pos) - beats.to_double());
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return frame_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos) - beats.to_double());
 }
 
 /** Add the BBT interval op to pos and return the result */
@@ -3485,7 +3627,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
        }
        pos_bbt.bars += op.bars;
 
-       return frame_time_locked (_metrics, pos_bbt);
+       return frame_at_bbt_locked (_metrics, pos_bbt);
 }
 
 /** Count the number of beats that are equivalent to distance when going forward,
@@ -3494,7 +3636,9 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 Evoral::Beats
 TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 {
-       return Evoral::Beats (beat_at_frame (pos + distance) - beat_at_frame (pos));
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       return Evoral::Beats (beat_at_frame_locked (_metrics, pos + distance) - beat_at_frame_locked (_metrics, pos));
 }
 
 struct bbtcmp {