tempo map debugging with dlp
[ardour.git] / libs / ardour / tempo.cc
index 54bb23d51743613e2faf21469a8334c995f38176..c39b8d07579dca3207b854a269ea8d5540961f52 100644 (file)
@@ -54,15 +54,22 @@ Tempo::frames_per_beat (framecnt_t sr) const
 /***********************************************************************/
 
 double 
-Meter::frames_per_division (const Tempo& tempo, framecnt_t sr) const
+Meter::frames_per_grid (const Tempo& tempo, framecnt_t sr) const
 {
+       /* This is tempo- and meter-sensitive. The number it returns
+          is based on the interval between any two lines in the 
+          grid that is constructed from tempo and meter sections.
+
+          The return value IS NOT interpretable in terms of "beats".
+       */
+
        return (60.0 * sr) / (tempo.beats_per_minute() * (_note_type/tempo.note_type()));
 }
 
 double
 Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
 {
-       return frames_per_division (tempo, sr) * _divisions_per_bar;
+       return frames_per_grid (tempo, sr) * _divisions_per_bar;
 }
 
 /***********************************************************************/
@@ -156,8 +163,8 @@ void
 
 TempoSection::update_bar_offset_from_bbt (const Meter& m)
 {
-       _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_bar_division + start().ticks) / 
-               (m.divisions_per_bar() * BBT_Time::ticks_per_bar_division);
+       _bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_beat + start().ticks) / 
+               (m.divisions_per_bar() * BBT_Time::ticks_per_beat);
 
        DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Tempo set bar offset to %1 from %2 w/%3\n", _bar_offset, start(), m.divisions_per_bar()));
 }
@@ -174,9 +181,9 @@ TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
 
        new_start.bars = start().bars;
        
-       double ticks = BBT_Time::ticks_per_bar_division * meter.divisions_per_bar() * _bar_offset;
-       new_start.beats = (uint32_t) floor(ticks/BBT_Time::ticks_per_bar_division);
-       new_start.ticks = (uint32_t) fmod (ticks, BBT_Time::ticks_per_bar_division);
+       double ticks = BBT_Time::ticks_per_beat * meter.divisions_per_bar() * _bar_offset;
+       new_start.beats = (uint32_t) floor(ticks/BBT_Time::ticks_per_beat);
+       new_start.ticks = (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat);
 
        /* remember the 1-based counting properties of beats */
        new_start.beats += 1;
@@ -277,9 +284,7 @@ struct MetricSectionSorter {
 
 TempoMap::TempoMap (framecnt_t fr)
 {
-       metrics = new Metrics;
        _frame_rate = fr;
-       last_bbt_valid = false;
        BBT_Time start;
 
        start.bars = 1;
@@ -294,8 +299,8 @@ TempoMap::TempoMap (framecnt_t fr)
 
        /* note: frame time is correct (zero) for both of these */
 
-       metrics->push_back (t);
-       metrics->push_back (m);
+       metrics.push_back (t);
+       metrics.push_back (m);
 }
 
 TempoMap::~TempoMap ()
@@ -311,11 +316,11 @@ TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation)
                Glib::RWLock::WriterLock lm (lock);
                Metrics::iterator i;
 
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
+               for (i = metrics.begin(); i != metrics.end(); ++i) {
                        if (dynamic_cast<TempoSection*> (*i) != 0) {
                                if (tempo.frame() == (*i)->frame()) {
                                        if ((*i)->movable()) {
-                                               metrics->erase (i);
+                                               metrics.erase (i);
                                                removed = true;
                                                break;
                                        }
@@ -342,23 +347,21 @@ TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
                Glib::RWLock::WriterLock lm (lock);
                Metrics::iterator i;
 
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
+               for (i = metrics.begin(); i != metrics.end(); ++i) {
                        if (dynamic_cast<MeterSection*> (*i) != 0) {
                                if (tempo.frame() == (*i)->frame()) {
                                        if ((*i)->movable()) {
-                                               metrics->erase (i);
+                                               metrics.erase (i);
                                                removed = true;
                                                break;
                                        }
                                }
                        }
                }
-               
+
                if (removed && complete_operation) {
                        recompute_map (true);
                }
-
-
        }
 
        if (removed && complete_operation) {
@@ -369,9 +372,6 @@ TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
 void
 TempoMap::do_insert (MetricSection* section)
 {
-       /* CALLER MUST HOLD WRITE LOCK */
-
-       bool reassign_tempo_bbt = false;
        bool need_add = true;
 
        assert (section->start().ticks == 0);
@@ -386,8 +386,6 @@ TempoMap::do_insert (MetricSection* section)
                   sections based on this new meter.
                */
                
-               reassign_tempo_bbt = true;
-
                if ((section->start().beats != 1) || (section->start().ticks != 0)) {
                        
                        BBT_Time corrected = section->start();
@@ -401,76 +399,85 @@ TempoMap::do_insert (MetricSection* section)
                }
        }
 
-       Metrics::iterator i;
+       
 
        /* Look for any existing MetricSection that is of the same type and
-          at the same time as the new one, and remove it before adding
-          the new one.
+          in the same bar as the new one, and remove it before adding
+          the new one. Note that this means that if we find a matching,
+          existing section, we can break out of the loop since we're
+          guaranteed that there is only one such match.
        */
 
-       Metrics::iterator to_remove = metrics->end ();
+       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
-       for (i = metrics->begin(); i != metrics->end(); ++i) {
+               bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
+               bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
 
-               int const c = (*i)->compare (*section);
+               if (iter_is_tempo && insert_is_tempo) {
 
-               if (c < 0) {
-                       /* this section is before the one to be added; go back round */
-                       continue;
-               } else if (c > 0) {
-                       /* this section is after the one to be added; there can't be any at the same time */
-                       break;
-               }
+                       /* Tempo sections */
 
-               /* hacky comparison of type */
-               bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
-               bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
+                       if ((*i)->start().bars == section->start().bars &&
+                           (*i)->start().beats == section->start().beats) {
+
+                               if (!(*i)->movable()) {
+                                       
+                                       /* can't (re)move this section, so overwrite
+                                        * its data content (but not its properties as
+                                        * a section).
+                                        */
+                                       
+                                       *(dynamic_cast<Tempo*>(*i)) = *(dynamic_cast<Tempo*>(section));
+                                       need_add = false;
+                               } else {
+                                       metrics.erase (i);
+                               }
+                               break;
+                       } 
 
-               if (iter_is_tempo == insert_is_tempo) {
+               } else if (!iter_is_tempo && !insert_is_tempo) {
 
-                       if (!(*i)->movable()) {
+                       /* Meter Sections */
 
-                               /* can't (re)move this section, so overwrite it
-                                */
+                       if ((*i)->start().bars == section->start().bars) {
 
-                               if (!iter_is_tempo) {
-                                       *(dynamic_cast<MeterSection*>(*i)) = *(dynamic_cast<MeterSection*>(section));
+                               if (!(*i)->movable()) {
+                                       
+                                       /* can't (re)move this section, so overwrite
+                                        * its data content (but not its properties as
+                                        * a section
+                                        */
+                                       
+                                       *(dynamic_cast<Meter*>(*i)) = *(dynamic_cast<Meter*>(section));
+                                       need_add = false;
                                } else {
-                                       *(dynamic_cast<TempoSection*>(*i)) = *(dynamic_cast<TempoSection*>(section));
+                                       metrics.erase (i);
+                                       
                                }
-                               need_add = false;
+
                                break;
                        }
-
-                       to_remove = i;
-                       break;
+               } else {
+                       /* non-matching types, so we don't care */
                }
        }
 
-       if (to_remove != metrics->end()) {
-               /* remove the MetricSection at the same time as the one we are about to add */
-               metrics->erase (to_remove);
-       }
-
-       /* Add the given MetricSection */
+       /* Add the given MetricSection, if we didn't just reset an existing
+        * one above
+        */
 
        if (need_add) {
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
-                       
-                       if ((*i)->compare (*section) < 0) {
-                               continue;
-                       }
-                       
-                       metrics->insert (i, section);
-                       break;
-               }
 
-               if (i == metrics->end()) {
-                       metrics->insert (metrics->end(), section);
+               Metrics::iterator i;
+
+               for (i = metrics.begin(); i != metrics.end(); ++i) {
+                       if ((*i)->start() > section->start()) {
+                               break;
+                       }
                }
+               
+               metrics.insert (i, section);
        }
-
-       recompute_map (reassign_tempo_bbt);
 }
 
 void
@@ -478,14 +485,16 @@ TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_T
 {
        const TempoSection& first (first_tempo());
 
-       if (ts != first) {
+       if (ts.start() != first.start()) {
                remove_tempo (ts, false);
                add_tempo (tempo, where);
        } else {
-               Glib::RWLock::WriterLock lm (lock);
-               /* cannot move the first tempo section */
-               *((Tempo*)&first) = tempo;
-               recompute_map (false);
+               {
+                       Glib::RWLock::WriterLock lm (lock);
+                       /* cannot move the first tempo section */
+                       *((Tempo*)&first) = tempo;
+                       recompute_map (false);
+               }
        }
 
        PropertyChanged (PropertyChange ());
@@ -515,7 +524,7 @@ TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
                   now see if we can find better candidates.
                */
                
-               for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+               for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                        
                        const MeterSection* m;
                        
@@ -531,10 +540,13 @@ TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
                ts->update_bar_offset_from_bbt (*meter);
 
                /* and insert it */
-
+               
                do_insert (ts);
+
+               recompute_map (false);
        }
 
+
        PropertyChanged (PropertyChange ());
 }
 
@@ -543,14 +555,16 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T
 {
        const MeterSection& first (first_meter());
 
-       if (ms != first) {
+       if (ms.start() != first.start()) {
                remove_meter (ms, false);
                add_meter (meter, where);
        } else {
-               Glib::RWLock::WriterLock lm (lock);
-               /* cannot move the first meter section */
-               *((Meter*)&first) = meter;
-               recompute_map (true);
+               {
+                       Glib::RWLock::WriterLock lm (lock);
+                       /* cannot move the first meter section */
+                       *((Meter*)&first) = meter;
+                       recompute_map (true);
+               }
        }
 
        PropertyChanged (PropertyChange ());
@@ -576,10 +590,12 @@ TempoMap::add_meter (const Meter& meter, BBT_Time where)
 
                /* new meters *always* start on a beat. */
                where.ticks = 0;
-
+               
                do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor()));
+               recompute_map (true);
        }
 
+       
 #ifndef NDEBUG
        if (DEBUG_ENABLED(DEBUG::TempoMap)) {
                dump (std::cerr);
@@ -595,9 +611,9 @@ TempoMap::change_initial_tempo (double beats_per_minute, double note_type)
        Tempo newtempo (beats_per_minute, note_type);
        TempoSection* t;
 
-       for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       {
+                       { 
                                Glib::RWLock::WriterLock lm (lock);
                                *((Tempo*) t) = newtempo;
                                recompute_map (false);
@@ -620,7 +636,7 @@ TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, d
        /* find the TempoSection immediately preceding "where"
         */
 
-       for (first = 0, i = metrics->begin(), prev = 0; i != metrics->end(); ++i) {
+       for (first = 0, i = metrics.begin(), prev = 0; i != metrics.end(); ++i) {
 
                if ((*i)->frame() > where) {
                        break;
@@ -662,7 +678,7 @@ TempoMap::first_meter () const
 {
        const MeterSection *m = 0;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if ((m = dynamic_cast<const MeterSection *> (*i)) != 0) {
                        return *m;
                }
@@ -678,7 +694,7 @@ TempoMap::first_tempo () const
 {
        const TempoSection *t = 0;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if ((t = dynamic_cast<const TempoSection *> (*i)) != 0) {
                        return *t;
                }
@@ -689,138 +705,78 @@ TempoMap::first_tempo () const
        return *t;
 }
 
-void
-TempoMap::timestamp_metrics_from_audio_time ()
-{
-       Metrics::iterator i;
-       const MeterSection* meter;
-       const TempoSection* tempo;
-       MeterSection *m;
-       TempoSection *t;
-
-       meter = &first_meter ();
-       tempo = &first_tempo ();
-
-       BBT_Time start;
-       BBT_Time end;
-       
-       // cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl;
-
-       bool first = true;
-       MetricSection* prev = 0;
-
-       for (i = metrics->begin(); i != metrics->end(); ++i) {
-
-               BBT_Time bbt;
-               TempoMetric metric (*meter, *tempo);
-
-               if (prev) {
-                       metric.set_start (prev->start());
-                       metric.set_frame (prev->frame());
-               } else {
-                       // metric will be at frames=0 bbt=1|1|0 by default
-                       // which is correct for our purpose
-               }
-
-               BBTPointList::const_iterator bi = bbt_before_or_at ((*i)->frame());
-               bbt_time_unlocked ((*i)->frame(), bbt, bi);
-               
-               // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
-
-               if (first) {
-                       first = false;
-               } else {
-
-                       if (bbt.ticks > BBT_Time::ticks_per_bar_division/2) {
-                               /* round up to next beat */
-                               bbt.beats += 1;
-                       }
-
-                       bbt.ticks = 0;
-
-                       if (bbt.beats != 1) {
-                               /* round up to next bar */
-                               bbt.bars += 1;
-                               bbt.beats = 1;
-                       }
-               }
-
-               // cerr << bbt << endl;
-
-               (*i)->set_start (bbt);
-                       
-               if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
-                       tempo = t;
-                       // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
-               } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
-                       meter = m;
-                       // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
-               } else {
-                       fatal << _("programming error: unhandled MetricSection type") << endmsg;
-                       /*NOTREACHED*/
-               }
-
-               prev = (*i);
-       }
-
-#ifndef NDEBUG
-       if (DEBUG_ENABLED(DEBUG::TempoMap)) {
-               dump (cerr);
-       }
-#endif
-
-}
-
 void
 TempoMap::require_map_to (framepos_t pos)
 {
+       Glib::RWLock::WriterLock lm (lock);
+
        if (_map.empty() || _map.back().frame < pos) {
-               recompute_map (false, pos);
+               extend_map (pos);
        }
 }
 
 void
 TempoMap::require_map_to (const BBT_Time& bbt)
 {
-       if (_map.empty() || _map.back().bbt() < bbt) {
-               recompute_map (false, 99);
+       Glib::RWLock::WriterLock lm (lock);
+
+       /* since we have no idea where BBT is if its off the map, see the last
+        * point in the map is past BBT, and if not add an arbitrary amount of
+        * time until it is.
+        */
+
+       int additional_minutes = 1;
+       
+       while (1) {
+               if (!_map.empty() && _map.back().bar >= (bbt.bars + 1)) {
+                       break;
+               }
+               /* add some more distance, using bigger steps each time */
+               extend_map (_map.back().frame + (_frame_rate * 60 * additional_minutes));
+               additional_minutes *= 2;
        }
 }
 
 void
 TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
 {
-       MeterSection* meter;
-       TempoSection* tempo;
-       TempoSection* ts;
-       MeterSection* ms;
-       double divisions_per_bar;
-       double beat_frames;
+       /* CALLER MUST HOLD WRITE LOCK */
+
+       MeterSection* meter = 0;
+       TempoSection* tempo = 0;
        double current_frame;
        BBT_Time current;
        Metrics::iterator next_metric;
 
        if (end < 0) {
+
                if (_map.empty()) {
                        /* compute 1 mins worth */
                        end = _frame_rate * 60;
                } else {
                        end = _map.back().frame;
                }
+       } else {
+               if (!_map.empty ()) {
+                       /* never allow the map to be shortened */
+                       end = max (end, _map.back().frame);
+               }
        }
 
        DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
-       
-       _map.clear ();
 
-       for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               MeterSection* ms;
+
                if ((ms = dynamic_cast<MeterSection *> (*i)) != 0) {
                        meter = ms;
                        break;
                }
        }
 
-       for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+               TempoSection* ts;
+
                if ((ts = dynamic_cast<TempoSection *> (*i)) != 0) {
                        tempo = ts;
                        break;
@@ -837,17 +793,17 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
        current.beats = 1;
        current.ticks = 0;
 
-       divisions_per_bar = meter->divisions_per_bar ();
-       beat_frames = meter->frames_per_division (*tempo,_frame_rate);
-       
        if (reassign_tempo_bbt) {
 
                MeterSection* rmeter = meter;
 
                DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n");
 
-               for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
-                       
+               for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+
+                       TempoSection* ts;
+                       MeterSection* ms;
+       
                        if ((ts = dynamic_cast<TempoSection*>(*i)) != 0) {
 
                                /* reassign the BBT time of this tempo section
@@ -865,18 +821,92 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
                }
        }
 
-       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2 dpb %3 fpb %4\n", 
-                                                      *((Meter*)meter), *((Tempo*)tempo), divisions_per_bar, beat_frames));
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2\n", *((Meter*)meter), *((Tempo*)tempo)));
 
-       next_metric = metrics->begin();
+       next_metric = metrics.begin();
        ++next_metric; // skip meter (or tempo)
        ++next_metric; // skip tempo (or meter)
 
+       _map.clear ();
+
        DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add first bar at 1|1 @ %2\n", current.bars, current_frame));
        _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), 1, 1));
 
+       if (end == 0) {
+               /* silly call from Session::process() during startup
+                */
+               return;
+       }
+
+       _extend_map (tempo, meter, next_metric, current, current_frame, end);
+}
+
+void
+TempoMap::extend_map (framepos_t end)
+{
+       /* CALLER MUST HOLD WRITE LOCK */
+
+       if (_map.empty()) {
+               recompute_map (false, end);
+               return;
+       }
+
+       BBTPointList::const_iterator i = _map.end();    
+       Metrics::iterator next_metric;
+
+       --i;
+
+       BBT_Time last_metric_start;
+
+       if ((*i).tempo->frame() > (*i).meter->frame()) {
+               last_metric_start = (*i).tempo->start();
+       } else {
+               last_metric_start = (*i).meter->start();
+       }
+
+       /* find the metric immediately after the tempo + meter sections for the
+        * last point in the map 
+        */
+
+       for (next_metric = metrics.begin(); next_metric != metrics.end(); ++next_metric) {
+               if ((*next_metric)->start() > last_metric_start) {
+                       break;
+               }
+       }
+
+       /* we cast away const here because this is the one place where we need
+        * to actually modify the frame time of each metric section. 
+        */
+
+       _extend_map (const_cast<TempoSection*> ((*i).tempo), 
+                    const_cast<MeterSection*> ((*i).meter),
+                    next_metric, BBT_Time ((*i).bar, (*i).beat, 0), (*i).frame, end);
+}
+
+void
+TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter, 
+                      Metrics::iterator next_metric,
+                      BBT_Time current, framepos_t current_frame, framepos_t end)
+{
+       /* CALLER MUST HOLD WRITE LOCK */
+
+       TempoSection* ts;
+       MeterSection* ms;
+       double divisions_per_bar;
+       double beat_frames;
+       framepos_t bar_start_frame;
+
+       if (current.beats == 1) {
+               bar_start_frame = current_frame;
+       } else {
+               bar_start_frame = 0;
+       }
+
+       divisions_per_bar = meter->divisions_per_bar ();
+       beat_frames = meter->frames_per_grid (*tempo,_frame_rate);
+
        while (current_frame < end) {
-               
+
                current.beats++;
                current_frame += beat_frames;
 
@@ -885,7 +915,7 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
                        current.beats = 1;
                }
 
-               if (next_metric != metrics->end()) {
+               if (next_metric != metrics.end()) {
 
                        /* no operator >= so invert operator < */
 
@@ -915,17 +945,35 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
 
                                        if (tempo->start().ticks != 0) {
                                                
-                                               double next_beat_frames = meter->frames_per_division (*tempo,_frame_rate);                                      
+                                               double next_beat_frames = tempo->frames_per_beat (_frame_rate);                                 
                                                
                                                DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into non-beat-aligned tempo metric at %1 = %2, adjust next beat using %3\n",
                                                                                               tempo->start(), current_frame, tempo->bar_offset()));
                                                
                                                /* back up to previous beat */
                                                current_frame -= beat_frames;
-                                               /* set tempo section location based on offset from last beat */
-                                               tempo->set_frame (current_frame + (ts->bar_offset() * beat_frames));
-                                               /* advance to the location of the new (adjusted) beat */
-                                               current_frame += (ts->bar_offset() * beat_frames) + ((1.0 - ts->bar_offset()) * next_beat_frames);
+
+                                               /* set tempo section location
+                                                * based on offset from last
+                                                * bar start 
+                                                */
+                                               tempo->set_frame (bar_start_frame + 
+                                                                 llrint ((ts->bar_offset() * meter->divisions_per_bar() * beat_frames)));
+                                               
+                                               /* advance to the location of
+                                                * the new (adjusted) beat. do
+                                                * this by figuring out the
+                                                * offset within the beat that
+                                                * would have been there
+                                                * without the tempo
+                                                * change. then stretch the
+                                                * beat accordingly.
+                                                */
+
+                                               double offset_within_old_beat = (tempo->frame() - current_frame) / beat_frames;
+
+                                               current_frame += (offset_within_old_beat * beat_frames) + ((1.0 - offset_within_old_beat) * next_beat_frames);
+
                                                /* next metric doesn't have to
                                                 * match this precisely to
                                                 * merit a reloop ...
@@ -956,14 +1004,14 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
                                }
                                
                                divisions_per_bar = meter->divisions_per_bar ();
-                               beat_frames = meter->frames_per_division (*tempo, _frame_rate);
+                               beat_frames = meter->frames_per_grid (*tempo, _frame_rate);
                                
                                DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n", 
                                                                               beat_frames, divisions_per_bar, *((Meter*)meter), *((Tempo*)tempo)));
                        
                                ++next_metric;
 
-                               if (next_metric != metrics->end() && ((*next_metric)->start() == current)) {
+                               if (next_metric != metrics.end() && ((*next_metric)->start() == current)) {
                                        /* same position so go back and set this one up before advancing
                                        */
                                        goto set_metrics;
@@ -974,6 +1022,7 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
                if (current.beats == 1) {
                        DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Bar at %1|1 @ %2\n", current.bars, current_frame));
                        _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), current.bars, 1));
+                       bar_start_frame = current_frame;
                } else {
                        DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Beat at %1|%2 @ %3\n", current.bars, current.beats, current_frame));
                        _map.push_back (BBTPoint (*meter, *tempo, (framepos_t) llrint(current_frame), current.bars, current.beats));
@@ -996,9 +1045,7 @@ TempoMap::metric_at (framepos_t frame) const
           now see if we can find better candidates.
        */
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
-
-               // cerr << "Looking at a metric section " << **i << endl;
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
                if ((*i)->frame() > frame) {
                        break;
@@ -1014,7 +1061,6 @@ TempoMap::metric_at (framepos_t frame) const
                m.set_start ((*i)->start ());
        }
        
-       // cerr << "for framepos " << frame << " returning " << m.meter() << " @ " << m.tempo() << " location " << m.frame() << " = " << m.start() << endl;
        return m;
 }
 
@@ -1033,7 +1079,7 @@ TempoMap::metric_at (BBT_Time bbt) const
           now see if we can find better candidates.
        */
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
                BBT_Time section_start ((*i)->start());
 
@@ -1057,38 +1103,57 @@ TempoMap::metric_at (BBT_Time bbt) const
 void
 TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
 {
-       {
-               Glib::RWLock::ReaderLock lm (lock);
-               BBTPointList::const_iterator i = bbt_before_or_at (frame);
-               bbt_time_unlocked (frame, bbt, i);
+       require_map_to (frame);
+
+       Glib::RWLock::ReaderLock lm (lock);
+       return bbt_time (frame, bbt, bbt_before_or_at (frame));
+}
+
+void
+TempoMap::bbt_time_rt (framepos_t frame, BBT_Time& bbt)
+{
+       Glib::RWLock::ReaderLock lm (lock, Glib::TRY_LOCK);
+
+       if (!lm.locked()) {
+               throw std::logic_error ("TempoMap::bbt_time_rt() could not lock tempo map");
        }
+       
+       if (_map.empty() || _map.back().frame < frame) {
+               throw std::logic_error (string_compose ("map not long enough to reach %1", frame));
+       }
+
+       return bbt_time (frame, bbt, bbt_before_or_at (frame));
 }
 
 void
-TempoMap::bbt_time_unlocked (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_iterator& i)
+TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_iterator& i)
 {
+       /* CALLER MUST HOLD READ LOCK */
+
        bbt.bars = (*i).bar;
        bbt.beats = (*i).beat;
 
        if ((*i).frame == frame) {
                bbt.ticks = 0;
        } else {
-               bbt.ticks = llrint (((frame - (*i).frame) / (*i).meter->frames_per_division(*((*i).tempo), _frame_rate)) *
-                                   BBT_Time::ticks_per_bar_division);
+               bbt.ticks = llrint (((frame - (*i).frame) / (*i).tempo->frames_per_beat(_frame_rate)) *
+                                   BBT_Time::ticks_per_beat);
        }
 }
 
 framepos_t
 TempoMap::frame_time (const BBT_Time& bbt)
 {
+       require_map_to (bbt);
+
        Glib::RWLock::ReaderLock lm (lock);
 
-       BBTPointList::const_iterator s = bbt_point_for (BBT_Time (1, 1, 0));
-       BBTPointList::const_iterator e = bbt_point_for (BBT_Time (bbt.bars, bbt.beats, 0));
+       BBTPointList::const_iterator s = bbt_before_or_at (BBT_Time (1, 1, 0));
+       BBTPointList::const_iterator e = bbt_before_or_at (BBT_Time (bbt.bars, bbt.beats, 0));
 
        if (bbt.ticks != 0) {
                return ((*e).frame - (*s).frame) + 
-                       llrint ((*e).meter->frames_per_division (*(*e).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division));
+                       llrint ((*e).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat));
        } else {
                return ((*e).frame - (*s).frame);
        }
@@ -1115,7 +1180,7 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
        }
 
        /* round back to the previous precise beat */
-       BBTPointList::const_iterator wi = bbt_point_for (BBT_Time (when.bars, when.beats, 0));
+       BBTPointList::const_iterator wi = bbt_before_or_at (BBT_Time (when.bars, when.beats, 0));
        BBTPointList::const_iterator start (wi);
        double tick_frames = 0;
 
@@ -1124,7 +1189,7 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
        /* compute how much rounding we did because of non-zero ticks */
 
        if (when.ticks != 0) {
-               tick_frames = (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (when.ticks/BBT_Time::ticks_per_bar_division);
+               tick_frames = (*wi).tempo->frames_per_beat (_frame_rate) * (when.ticks/BBT_Time::ticks_per_beat);
        }
        
        uint32_t bars = 0;
@@ -1147,7 +1212,7 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
        /* add any additional frames related to ticks in the added value */
 
        if (bbt.ticks != 0) {
-               tick_frames += (*wi).meter->frames_per_division (*(*wi).tempo, _frame_rate) * (bbt.ticks/BBT_Time::ticks_per_bar_division);
+               tick_frames += (*wi).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat);
        }
 
        return ((*wi).frame - (*start).frame) + llrint (tick_frames);
@@ -1156,36 +1221,32 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
 framepos_t
 TempoMap::round_to_bar (framepos_t fr, int dir)
 {
-       {
-               Glib::RWLock::ReaderLock lm (lock);
-               return round_to_type (fr, dir, Bar);
-       }
+       return round_to_type (fr, dir, Bar);
 }
 
 framepos_t
 TempoMap::round_to_beat (framepos_t fr, int dir)
 {
-       {
-               Glib::RWLock::ReaderLock lm (lock);
-               return round_to_type (fr, dir, Beat);
-       }
+       return round_to_type (fr, dir, Beat);
 }
 
 framepos_t
 TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
 {
+       require_map_to (fr);
+
        Glib::RWLock::ReaderLock lm (lock);
        BBTPointList::const_iterator i = bbt_before_or_at (fr);
        BBT_Time the_beat;
        uint32_t ticks_one_subdivisions_worth;
        uint32_t difference;
 
-       bbt_time_unlocked (fr, the_beat, i);
+       bbt_time (fr, the_beat, i);
 
        DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round %1 to nearest 1/%2 beat, before-or-at = %3 @ %4|%5 precise = %6\n",
                                                     fr, sub_num, (*i).frame, (*i).bar, (*i).beat, the_beat));
 
-       ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_bar_division / sub_num;
+       ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_beat / sub_num;
 
        if (dir > 0) {
 
@@ -1203,11 +1264,11 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
                        the_beat.ticks += ticks_one_subdivisions_worth - mod;
                }
 
-               if (the_beat.ticks > BBT_Time::ticks_per_bar_division) {
+               if (the_beat.ticks > BBT_Time::ticks_per_beat) {
                        assert (i != _map.end());
                        ++i;
                        assert (i != _map.end());
-                       the_beat.ticks -= BBT_Time::ticks_per_bar_division;
+                       the_beat.ticks -= BBT_Time::ticks_per_beat;
                } 
 
 
@@ -1234,7 +1295,7 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
                                return fr;
                        }
                        --i;
-                       the_beat.ticks = BBT_Time::ticks_per_bar_division - the_beat.ticks;
+                       the_beat.ticks = BBT_Time::ticks_per_beat - the_beat.ticks;
                } else {
                        the_beat.ticks -= difference;
                }
@@ -1254,11 +1315,11 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
 
                        DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", the_beat.ticks));
 
-                       if (the_beat.ticks > BBT_Time::ticks_per_bar_division) {
+                       if (the_beat.ticks > BBT_Time::ticks_per_beat) {
                                assert (i != _map.end());
                                ++i;
                                assert (i != _map.end());
-                               the_beat.ticks -= BBT_Time::ticks_per_bar_division;
+                               the_beat.ticks -= BBT_Time::ticks_per_beat;
                                DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", the_beat));
                        } 
 
@@ -1273,7 +1334,7 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
                                }
                                /* step back to previous beat */
                                --i;
-                               the_beat.ticks = lrint (BBT_Time::ticks_per_bar_division - rem);
+                               the_beat.ticks = lrint (BBT_Time::ticks_per_beat - rem);
                                DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", the_beat));
                        } else {
                                the_beat.ticks = lrint (the_beat.ticks - rem);
@@ -1284,13 +1345,16 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
                }
        }
 
-       return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_bar_division) * 
-               (*i).meter->frames_per_division (*((*i).tempo), _frame_rate);
+       return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_beat) * 
+               (*i).tempo->frames_per_beat (_frame_rate);
 }
 
 framepos_t
 TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
 {
+       require_map_to (frame);
+
+       Glib::RWLock::ReaderLock lm (lock);
        BBTPointList::const_iterator fi;
 
        if (dir > 0) {
@@ -1301,13 +1365,18 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
 
        assert (fi != _map.end());
 
-       DEBUG_TRACE(DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to bars in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame));
+       DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to %6 in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame,
+                                                    (type == Bar ? "bar" : "beat")));
                
        switch (type) {
        case Bar:
                if (dir < 0) {
                        /* find bar previous to 'frame' */
 
+                       if (fi == _map.begin()) {
+                               return 0;
+                       }
+
                        if ((*fi).is_bar() && (*fi).frame == frame) {
                                --fi;
                        }
@@ -1360,15 +1429,11 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
                                prev--;
                        }
 
-                       while ((*next).beat != 1) {
+                       while ((next != _map.end()) && (*next).beat != 1) {
                                next++;
-                               if (next == _map.end()) {
-                                       --next;
-                                       break;
-                               }
                        }
 
-                       if ((frame - (*prev).frame) < ((*next).frame - frame)) {
+                       if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) {
                                return (*prev).frame;
                        } else {
                                return (*next).frame;
@@ -1380,6 +1445,11 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
 
        case Beat:
                if (dir < 0) {
+
+                       if (fi == _map.begin()) {
+                               return 0;
+                       }
+
                        if ((*fi).frame >= frame) {
                                DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step back\n");
                                --fi;
@@ -1403,10 +1473,14 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
 
                        BBTPointList::const_iterator prev = fi;
                        BBTPointList::const_iterator next = fi;
-                       --prev;
+
+                       /* fi is already the beat before_or_at frame, and
+                          we've just established that its not at frame, so its
+                          the beat before frame.
+                       */
                        ++next;
                        
-                       if ((frame - (*prev).frame) < ((*next).frame - frame)) {
+                       if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) {
                                return (*prev).frame;
                        } else {
                                return (*next).frame;
@@ -1421,12 +1495,15 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
 }
 
 void
-TempoMap::map (TempoMap::BBTPointList::const_iterator& begin, 
-              TempoMap::BBTPointList::const_iterator& end, 
-              framepos_t lower, framepos_t upper) 
+TempoMap::get_grid (TempoMap::BBTPointList::const_iterator& begin, 
+                   TempoMap::BBTPointList::const_iterator& end, 
+                   framepos_t lower, framepos_t upper) 
 {
-       if (_map.empty() || upper >= _map.back().frame) {
-               recompute_map (false, upper);
+       { 
+               Glib::RWLock::WriterLock lm (lock);
+               if (_map.empty() || (_map.back().frame < upper)) {
+                       recompute_map (false, upper);
+               }
        }
 
        begin = lower_bound (_map.begin(), _map.end(), lower);
@@ -1440,7 +1517,7 @@ TempoMap::tempo_section_at (framepos_t frame) const
        Metrics::const_iterator i;
        TempoSection* prev = 0;
 
-       for (i = metrics->begin(); i != metrics->end(); ++i) {
+       for (i = metrics.begin(); i != metrics.end(); ++i) {
                TempoSection* t;
 
                if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
@@ -1483,7 +1560,7 @@ TempoMap::get_state ()
 
        {
                Glib::RWLock::ReaderLock lm (lock);
-               for (i = metrics->begin(); i != metrics->end(); ++i) {
+               for (i = metrics.begin(); i != metrics.end(); ++i) {
                        root->add_child_nocopy ((*i)->get_state());
                }
        }
@@ -1499,10 +1576,10 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 
                XMLNodeList nlist;
                XMLNodeConstIterator niter;
-               Metrics old_metrics (*metrics);
+               Metrics old_metrics (metrics);
                MeterSection* last_meter = 0;
 
-               metrics->clear();
+               metrics.clear();
 
                nlist = node.children();
                
@@ -1513,7 +1590,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 
                                try {
                                        TempoSection* ts = new TempoSection (*child);
-                                       metrics->push_back (ts);
+                                       metrics.push_back (ts);
 
                                        if (ts->bar_offset() < 0.0) {
                                                if (last_meter) {
@@ -1524,7 +1601,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 
                                catch (failed_constructor& err){
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
-                                       *metrics = old_metrics;
+                                       metrics = old_metrics;
                                        break;
                                }
 
@@ -1532,24 +1609,24 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 
                                try {
                                        MeterSection* ms = new MeterSection (*child);
-                                       metrics->push_back (ms);
+                                       metrics.push_back (ms);
                                        last_meter = ms;
                                }
 
                                catch (failed_constructor& err) {
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
-                                       *metrics = old_metrics;
+                                       metrics = old_metrics;
                                        break;
                                }
                        }
                }
 
                if (niter == nlist.end()) {
-
                        MetricSectionSorter cmp;
-                       metrics->sort (cmp);
-                       recompute_map (true);
+                       metrics.sort (cmp);
                }
+
+               recompute_map (true);
        }
 
        PropertyChanged (PropertyChange ());
@@ -1560,10 +1637,11 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
 void
 TempoMap::dump (std::ostream& o) const
 {
+       Glib::RWLock::ReaderLock lm (lock, Glib::TRY_LOCK);
        const MeterSection* m;
        const TempoSection* t;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
                if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
                        o << "Tempo @ " << *i << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (movable? "
@@ -1581,7 +1659,7 @@ TempoMap::n_tempos() const
        Glib::RWLock::ReaderLock lm (lock);
        int cnt = 0;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if (dynamic_cast<const TempoSection*>(*i) != 0) {
                        cnt++;
                }
@@ -1596,7 +1674,7 @@ TempoMap::n_meters() const
        Glib::RWLock::ReaderLock lm (lock);
        int cnt = 0;
 
-       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+       for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
                if (dynamic_cast<const MeterSection*>(*i) != 0) {
                        cnt++;
                }
@@ -1608,13 +1686,93 @@ TempoMap::n_meters() const
 void
 TempoMap::insert_time (framepos_t where, framecnt_t amount)
 {
-       for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
-               if ((*i)->frame() >= where && (*i)->movable ()) {
-                       (*i)->set_frame ((*i)->frame() + amount);
+       {
+               Glib::RWLock::WriterLock lm (lock);
+               for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
+                       if ((*i)->frame() >= where && (*i)->movable ()) {
+                               (*i)->set_frame ((*i)->frame() + amount);
+                       }
+               }
+
+               /* now reset the BBT time of all metrics, based on their new
+                * audio time. This is the only place where we do this reverse
+                * timestamp.
+                */
+
+               Metrics::iterator i;
+               const MeterSection* meter;
+               const TempoSection* tempo;
+               MeterSection *m;
+               TempoSection *t;
+               
+               meter = &first_meter ();
+               tempo = &first_tempo ();
+               
+               BBT_Time start;
+               BBT_Time end;
+               
+               // cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl;
+               
+               bool first = true;
+               MetricSection* prev = 0;
+               
+               for (i = metrics.begin(); i != metrics.end(); ++i) {
+                       
+                       BBT_Time bbt;
+                       TempoMetric metric (*meter, *tempo);
+                       
+                       if (prev) {
+                               metric.set_start (prev->start());
+                               metric.set_frame (prev->frame());
+                       } else {
+                               // metric will be at frames=0 bbt=1|1|0 by default
+                               // which is correct for our purpose
+                       }
+                       
+                       BBTPointList::const_iterator bi = bbt_before_or_at ((*i)->frame());
+                       bbt_time ((*i)->frame(), bbt, bi);
+                       
+                       // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
+                       
+                       if (first) {
+                               first = false;
+                       } else {
+                               
+                               if (bbt.ticks > BBT_Time::ticks_per_beat/2) {
+                                       /* round up to next beat */
+                                       bbt.beats += 1;
+                               }
+                               
+                               bbt.ticks = 0;
+                               
+                               if (bbt.beats != 1) {
+                                       /* round up to next bar */
+                                       bbt.bars += 1;
+                                       bbt.beats = 1;
+                               }
+                       }
+                       
+                       // cerr << bbt << endl;
+                       
+                       (*i)->set_start (bbt);
+                       
+                       if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
+                               tempo = t;
+                               // cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
+                       } else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
+                               meter = m;
+                               // cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
+                       } else {
+                               fatal << _("programming error: unhandled MetricSection type") << endmsg;
+                               /*NOTREACHED*/
+                       }
+                       
+                       prev = (*i);
                }
+               
+               recompute_map (true);
        }
 
-       timestamp_metrics_from_audio_time ();
 
        PropertyChanged (PropertyChange ());
 }
@@ -1623,150 +1781,406 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount)
  *  pos can be -ve, if required.
  */
 framepos_t
-TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats)
+TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const
 {
-       return framepos_plus_bbt (pos, BBT_Time (beats));
+       Glib::RWLock::ReaderLock lm (lock);
+       Metrics::const_iterator next_tempo;
+       const TempoSection* tempo;
+
+       /* Find the starting tempo metric */
+
+       for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
+
+               const TempoSection* t;
+
+               if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
+
+                       /* This is a bit of a hack, but pos could be -ve, and if it is,
+                          we consider the initial metric changes (at time 0) to actually
+                          be in effect at pos.
+                       */
+
+                       framepos_t f = (*next_tempo)->frame ();
+
+                       if (pos < 0 && f == 0) {
+                               f = pos;
+                       }
+                       
+                       if (f > pos) {
+                               break;
+                       }
+                       
+                       tempo = t;
+               }
+       }
+
+       /* We now have:
+
+          tempo       -> the Tempo for "pos"
+          next_tempo  -> first tempo after "pos", possibly metrics.end()
+       */
+
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("frame %1 plus %2 beats, start with tempo = %3 @ %4\n",
+                                                      pos, beats, *((Tempo*)tempo), tempo->frame()));
+
+       while (beats) {
+
+               /* Distance to the end of this section in frames */
+               framecnt_t distance_frames = (next_tempo == metrics.end() ? max_framepos : ((*next_tempo)->frame() - pos));
+
+               /* Distance to the end in beats */
+               Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate);
+
+               /* Amount to subtract this time */
+               double const delta = min (distance_beats, beats);
+
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n",
+                                                              (next_tempo == metrics.end() ? max_framepos : (*next_tempo)->frame()),
+                                                              distance_frames, distance_beats));
+
+               /* Update */
+               beats -= delta;
+               pos += delta * tempo->frames_per_beat (_frame_rate);
+
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left\n", pos, beats));
+
+               /* step forwards to next tempo section */
+
+               if (next_tempo != metrics.end()) {
+
+                       tempo = dynamic_cast<const TempoSection*>(*next_tempo);
+
+                       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n",
+                                                                      *((Tempo*)tempo), tempo->frame(),
+                                                                      tempo->frames_per_beat (_frame_rate)));
+
+                       while (next_tempo != metrics.end ()) {
+
+                               ++next_tempo;
+                               
+                               if (next_tempo != metrics.end() && dynamic_cast<const TempoSection*>(*next_tempo)) {
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       return pos;
 }
 
 /** Subtract some (fractional) beats to a frame position, and return the result in frames */
 framepos_t
-TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats)
+TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const
 {
-       return framepos_minus_bbt (pos, BBT_Time (beats));
-}
+       Glib::RWLock::ReaderLock lm (lock);
+       Metrics::const_reverse_iterator prev_tempo;
+       const TempoSection* tempo = 0;
 
-framepos_t
-TempoMap::framepos_minus_bbt (framepos_t pos, BBT_Time op)
-{
-       BBTPointList::const_iterator i;
-       framecnt_t extra_frames = 0;
+       /* Find the starting tempo metric */
 
-       /* start from the bar|beat right before (or at) pos */
+       for (prev_tempo = metrics.rbegin(); prev_tempo != metrics.rend(); ++prev_tempo) {
 
-       i = bbt_before_or_at (pos);
-       
-       /* we know that (*i).frame is less than or equal to pos */
-       extra_frames = pos - (*i).frame;
-       
-       /* walk backwards */
+               const TempoSection* t;
 
-       while (i != _map.begin() && (op.bars || op.beats)) {
-               --i;
-               if ((*i).is_bar()) {
-                       if (op.bars) {
-                               op.bars--;
+               if ((t = dynamic_cast<const TempoSection*>(*prev_tempo)) != 0) {
+
+                       /* This is a bit of a hack, but pos could be -ve, and if it is,
+                          we consider the initial metric changes (at time 0) to actually
+                          be in effect at pos.
+                       */
+
+                       framepos_t f = (*prev_tempo)->frame ();
+
+                       if (pos < 0 && f == 0) {
+                               f = pos;
                        }
-               } else {
-                       if (op.beats) {
-                               op.beats--;
+
+                       /* this is slightly more complex than the forward case
+                          because we reach the tempo in effect at pos after
+                          passing through pos (rather before, as in the
+                          forward case). having done that, we then need to
+                          keep going to get the previous tempo (or
+                          metrics.rend())
+                       */
+                       
+                       if (f <= pos) {
+                               if (tempo == 0) {
+                                       /* first tempo with position at or
+                                          before pos
+                                       */
+                                       tempo = t;
+                               } else if (f < pos) {
+                                       /* some other tempo section that
+                                          is even earlier than 'tempo'
+                                       */
+                                       break;
+                               }
                        }
                }
        }
-       
-       /* handle ticks (assumed to be less than
-        * BBT_Time::ticks_per_bar_division, as always.
-        */
 
-       if (op.ticks) {
-               frameoffset_t tick_frames = llrint ((*i).meter->frames_per_division (*(*i).tempo, _frame_rate) * (op.ticks/BBT_Time::ticks_per_bar_division));
-               framepos_t pre_tick_frames = (*i).frame + extra_frames;
-               if (tick_frames < pre_tick_frames) {
-                       return pre_tick_frames - tick_frames;
-               } 
-               return 0;
-       } else {
-               return (*i).frame + extra_frames;
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("frame %1 minus %2 beats, start with tempo = %3 @ %4 prev at beg? %5\n",
+                                                      pos, beats, *((Tempo*)tempo), tempo->frame(),
+                                                      prev_tempo == metrics.rend()));
+
+       /* We now have:
+
+          tempo       -> the Tempo for "pos"
+          prev_tempo  -> the first metric before "pos", possibly metrics.rend()
+       */
+
+       while (beats) {
+               
+               /* Distance to the start of this section in frames */
+               framecnt_t distance_frames = (pos - tempo->frame());
+
+               /* Distance to the start in beats */
+               Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate);
+
+               /* Amount to subtract this time */
+               double const sub = min (distance_beats, beats);
+
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n",
+                                                              tempo->frame(), distance_frames, distance_beats));
+               /* Update */
+
+               beats -= sub;
+               pos -= sub * tempo->frames_per_beat (_frame_rate);
+
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left, prev at end ? %3\n", pos, beats,
+                                                              (prev_tempo == metrics.rend())));
+
+               /* step backwards to prior TempoSection */
+
+               if (prev_tempo != metrics.rend()) {
+
+                       tempo = dynamic_cast<const TempoSection*>(*prev_tempo);
+
+                       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n",
+                                                                      *((Tempo*)tempo), tempo->frame(),
+                                                                      tempo->frames_per_beat (_frame_rate)));
+
+                       while (prev_tempo != metrics.rend ()) {
+
+                               ++prev_tempo;
+
+                               if (prev_tempo != metrics.rend() && dynamic_cast<const TempoSection*>(*prev_tempo) != 0) {
+                                       break;
+                               }
+                       }
+               } else {
+                       pos -= llrint (beats * tempo->frames_per_beat (_frame_rate));
+                       beats = 0;
+               }
        }
+
+       return pos;
 }
 
 /** Add the BBT interval op to pos and return the result */
 framepos_t
-TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op)
+TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 {
-       BBT_Time op_copy (op);
-       int additional_minutes = 1;
-       BBTPointList::const_iterator i;
-       framecnt_t backup_frames = 0;
+       Glib::RWLock::ReaderLock lm (lock);
+       Metrics::const_iterator i;
+       const MeterSection* meter;
+       const MeterSection* m;
+       const TempoSection* tempo;
+       const TempoSection* t;
+       double frames_per_beat;
 
-       while (true) {
+       meter = &first_meter ();
+       tempo = &first_tempo ();
 
-               i = bbt_before_or_at (pos);
-               
-               op = op_copy;
+       assert (meter);
+       assert (tempo);
 
-               /* we know that (*i).frame is before or equal to pos */
-               backup_frames = pos - (*i).frame;
+       /* find the starting metrics for tempo & meter */
 
-               while (i != _map.end() && (op.bars || op.beats)) {
-                       ++i;
-                       if ((*i).is_bar()) {
-                               if (op.bars) {
-                                       op.bars--;
-                               }
-                       } else {
-                               if (op.beats) {
-                                       op.beats--;
+       for (i = metrics.begin(); i != metrics.end(); ++i) {
+
+               if ((*i)->frame() > pos) {
+                       break;
+               }
+
+               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+                       tempo = t;
+               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+                       meter = m;
+               }
+       }
+
+       /* We now have:
+
+          meter -> the Meter for "pos"
+          tempo -> the Tempo for "pos"
+          i     -> for first new metric after "pos", possibly metrics.end()
+       */
+
+       /* now comes the complicated part. we have to add one beat a time,
+          checking for a new metric on every beat.
+       */
+
+       frames_per_beat = tempo->frames_per_beat (_frame_rate);
+
+       uint64_t bars = 0;
+
+       while (op.bars) {
+
+               bars++;
+               op.bars--;
+
+               /* check if we need to use a new metric section: has adding frames moved us
+                  to or after the start of the next metric section? in which case, use it.
+               */
+
+               if (i != metrics.end()) {
+                       if ((*i)->frame() <= pos) {
+
+                               /* about to change tempo or meter, so add the
+                                * number of frames for the bars we've just
+                                * traversed before we change the
+                                * frames_per_beat value.
+                                */
+                               
+                               pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
+                               bars = 0;
+
+                               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+                                       tempo = t;
+                               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+                                       meter = m;
                                }
+                               ++i;
+                               frames_per_beat = tempo->frames_per_beat (_frame_rate);
+
                        }
                }
-               
-               if (i != _map.end()) {
-                       break;
-               }
 
-               /* we hit the end of the map before finish the bbt walk.
-                */
+       }
 
-               require_map_to (pos + (_frame_rate * 60 * additional_minutes));
-               additional_minutes *= 2;
+       pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
 
-               /* go back and try again */
-               warning << "reached end of map with op now at " << op << " end = " 
-                       << _map.back().frame << ' ' << _map.back().bar << '|' << _map.back().beat << ", trying to walk " 
-                       << op_copy << " ... retry" 
-                       << endmsg;
+       uint64_t beats = 0;
+
+       while (op.beats) {
+
+               /* given the current meter, have we gone past the end of the bar ? */
+
+               beats++;
+               op.beats--;
+
+               /* check if we need to use a new metric section: has adding frames moved us
+                  to or after the start of the next metric section? in which case, use it.
+               */
+
+               if (i != metrics.end()) {
+                       if ((*i)->frame() <= pos) {
+
+                               /* about to change tempo or meter, so add the
+                                * number of frames for the beats we've just
+                                * traversed before we change the
+                                * frames_per_beat value.
+                                */
+
+                               pos += llrint (beats * frames_per_beat);
+                               beats = 0;
+
+                               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+                                       tempo = t;
+                               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+                                       meter = m;
+                               }
+                               ++i;
+                               frames_per_beat = tempo->frames_per_beat (_frame_rate);
+                       }
+               }
        }
-       
+
+       pos += llrint (beats * frames_per_beat);
+
        if (op.ticks) {
-               return (*i).frame - backup_frames + 
-                       llrint ((*i).meter->frames_per_division (*(*i).tempo, _frame_rate) * (op.ticks/BBT_Time::ticks_per_bar_division));
-       } else {
-               return (*i).frame - backup_frames;
+               if (op.ticks >= BBT_Time::ticks_per_beat) {
+                       pos += llrint (frames_per_beat + /* extra beat */
+                                      (frames_per_beat * ((op.ticks % (uint32_t) BBT_Time::ticks_per_beat) / 
+                                                          (double) BBT_Time::ticks_per_beat)));
+               } else {
+                       pos += llrint (frames_per_beat * (op.ticks / (double) BBT_Time::ticks_per_beat));
+               }
        }
+
+       return pos;
 }
 
 /** Count the number of beats that are equivalent to distance when going forward,
     starting at pos.
 */
 Evoral::MusicalTime
-TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance)
+TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 {
-       BBTPointList::const_iterator i = bbt_after_or_at (pos);
-       Evoral::MusicalTime beats = 0;
-       framepos_t end = pos + distance;
+       Glib::RWLock::ReaderLock lm (lock);
+       Metrics::const_iterator next_tempo;
+       const TempoSection* tempo;
+       
+       /* Find the relevant initial tempo metric  */
 
-       require_map_to (end);
+       for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
 
-       /* if our starting BBTPoint is after pos, add a fractional beat
-          to represent that distance.
-       */
+               const TempoSection* t;
 
-       if ((*i).frame != pos) {
-               beats += ((*i).frame - pos) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate);
-       }
+               if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
 
-       while (i != _map.end() && (*i).frame < end) {
-               ++i;
-               beats++;
+                       if ((*next_tempo)->frame() > pos) {
+                               break;
+                       }
+
+                       tempo = t;
+               }
        }
-       assert (i != _map.end());
-       
-       /* if our ending BBTPoint is after the end, subtract a fractional beat
-          to represent that distance.
+
+       /* We now have:
+
+          tempo -> the Tempo for "pos"
+          next_tempo -> the next tempo after "pos", possibly metrics.end()
        */
 
-       if ((*i).frame > end) {
-               beats -= ((*i).frame - end) / (*i).meter->frames_per_division (*(*i).tempo, _frame_rate);
+       Evoral::MusicalTime beats = 0;
+
+       while (distance) {
+
+               /* End of this section */
+               framepos_t const end = ((next_tempo == metrics.end()) ? max_framepos : (*next_tempo)->frame ());
+
+               /* Distance to the end in frames */
+               framecnt_t const distance_to_end = end - pos;
+
+               /* Amount to subtract this time */
+               double const sub = min (distance, distance_to_end);
+
+               /* Update */
+               pos += sub;
+               distance -= sub;
+               beats += sub / tempo->frames_per_beat (_frame_rate);
+               
+               /* Move on if there's anything to move to */
+
+               if (next_tempo != metrics.end()) {
+
+                       tempo = dynamic_cast<const TempoSection*>(*next_tempo);
+
+                       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n",
+                                                                      *((Tempo*)tempo), tempo->frame(),
+                                                                      tempo->frames_per_beat (_frame_rate)));
+
+                       while (next_tempo != metrics.end ()) {
+
+                               ++next_tempo;
+                               
+                               if (next_tempo != metrics.end() && dynamic_cast<const TempoSection*>(*next_tempo)) {
+                                       break;
+                               }
+                       }
+               }
        }
 
        return beats;
@@ -1775,25 +2189,21 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance)
 TempoMap::BBTPointList::const_iterator
 TempoMap::bbt_before_or_at (framepos_t pos)
 {
-       require_map_to (pos);
-       BBTPointList::const_iterator i = lower_bound (_map.begin(), _map.end(), pos);
+       /* CALLER MUST HOLD READ LOCK */
+
+       BBTPointList::const_iterator i;
+
+       i = lower_bound (_map.begin(), _map.end(), pos);
        assert (i != _map.end());
        if ((*i).frame > pos) {
+               cerr << "lower bound was found at " << (*i).frame << " for " << pos;
+               dump (cerr);
                assert (i != _map.begin());
                --i;
        }
        return i;
 }
 
-TempoMap::BBTPointList::const_iterator
-TempoMap::bbt_after_or_at (framepos_t pos)
-{
-       require_map_to (pos);
-       BBTPointList::const_iterator i = upper_bound (_map.begin(), _map.end(), pos);
-       assert (i != _map.end());
-       return i;
-}
-
 struct bbtcmp {
     bool operator() (const BBT_Time& a, const BBT_Time& b) {
            return a < b;
@@ -1801,53 +2211,37 @@ struct bbtcmp {
 };
 
 TempoMap::BBTPointList::const_iterator
-TempoMap::bbt_point_for (const BBT_Time& bbt)
+TempoMap::bbt_before_or_at (const BBT_Time& bbt)
 {
+       BBTPointList::const_iterator i;
        bbtcmp cmp;
-       int additional_minutes = 1;
-
-       while (_map.empty() || _map.back().bar < (bbt.bars + 1)) {
-               /* add some more distance, using bigger steps each time */
-               require_map_to (_map.back().frame + (_frame_rate * 60 * additional_minutes));
-               additional_minutes *= 2;
-       }
 
-       BBTPointList::const_iterator i = lower_bound (_map.begin(), _map.end(), bbt, cmp);
+       i = lower_bound (_map.begin(), _map.end(), bbt, cmp);
        assert (i != _map.end());
+       if ((*i).bar > bbt.bars || (*i).beat > bbt.beats) {
+               assert (i != _map.begin());
+               --i;
+       }
        return i;
 }
 
-
-/** Compare the time of this with that of another MetricSection.
- *  @param with_bbt True to compare using start(), false to use frame().
- *  @return -1 for less than, 0 for equal, 1 for greater than.
- */
-
-int
-MetricSection::compare (const MetricSection& other) const
+TempoMap::BBTPointList::const_iterator
+TempoMap::bbt_after_or_at (framepos_t pos) 
 {
-       if (start() == other.start()) {
-               return 0;
-       } else if (start() < other.start()) {
-               return -1;
-       } else {
-               return 1;
-       }
+       /* CALLER MUST HOLD READ LOCK */
 
-       /* NOTREACHED */
-       return 0;
-}
+       BBTPointList::const_iterator i;
 
-bool
-MetricSection::operator== (const MetricSection& other) const
-{
-       return compare (other) == 0;
-}
+       if (_map.back().frame == pos) {
+               i = _map.end();
+               assert (i != _map.begin());
+               --i;
+               return i;
+       }
 
-bool
-MetricSection::operator!= (const MetricSection& other) const
-{
-       return compare (other) != 0;
+       i = upper_bound (_map.begin(), _map.end(), pos);
+       assert (i != _map.end());
+       return i;
 }
 
 std::ostream&