Optimize automation-event process splitting
[ardour.git] / nutemp / t.cc
index 6e4ee6a6e68259b905dea277a168fd1777ce40ad..296628445bb0601e1c354c7252e5dac8817afa67 100644 (file)
@@ -6,7 +6,7 @@ using std::cout;
 using std::endl;
 
 /* overloaded operator* that avoids floating point math when multiplying a superclock position by a number of quarter notes */
-superclock_t operator*(superclock_t sc, Evoral::Beats const & b) { return (sc * ((b.get_beats() * Evoral::Beats::PPQN) + b.get_ticks())) / Evoral::Beats::PPQN; }
+superclock_t operator*(superclock_t sc, Temporal::Beats const & b) { return (sc * ((b.get_beats() * Temporal::Beats::PPQN) + b.get_ticks())) / Temporal::Beats::PPQN; }
 
 Timecode::BBT_Time
 Meter::bbt_add (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & add) const
@@ -43,9 +43,9 @@ Meter::bbt_add (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & add
 
        Timecode::BBT_Offset r (bars + add.bars, beats + add.beats, ticks + add.ticks);
 
-       if (r.ticks >= Evoral::Beats::PPQN) {
-               r.beats += r.ticks / Evoral::Beats::PPQN;
-               r.ticks %= Evoral::Beats::PPQN;
+       if (r.ticks >= Temporal::Beats::PPQN) {
+               r.beats += r.ticks / Temporal::Beats::PPQN;
+               r.ticks %= Temporal::Beats::PPQN;
        }
 
        if (r.beats > _divisions_per_bar) {
@@ -98,8 +98,8 @@ Meter::bbt_subtract (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const
        Timecode::BBT_Offset r (bars - sub.bars, beats - sub.beats, ticks - sub.ticks);
 
        if (r.ticks < 0) {
-               r.beats -= 1 - (r.ticks / Evoral::Beats::PPQN);
-               r.ticks = Evoral::Beats::PPQN + (r.ticks % Evoral::Beats::PPQN);
+               r.beats -= 1 - (r.ticks / Temporal::Beats::PPQN);
+               r.ticks = Temporal::Beats::PPQN + (r.ticks % Temporal::Beats::PPQN);
        }
 
        if (r.beats <= 0) {
@@ -135,16 +135,14 @@ Meter::round_up_to_bar (Timecode::BBT_Time const & bbt) const
        return b;
 }
 
-Evoral::Beats
+Temporal::Beats
 Meter::to_quarters (Timecode::BBT_Offset const & offset) const
 {
-       Evoral::Beats b;
+       Temporal::Beats b;
 
        b += (offset.bars * _divisions_per_bar * 4) / _note_value;
-       cerr << offset.bars << " bars as quarters : " << b << " nv = " << (int) _note_value << endl;
        b += (offset.beats * 4) / _note_value;
-       cerr << offset.beats << " beats as quarters : " << (offset.beats * 4) / _note_value << " nv = " << (int) _note_value << endl;
-       b += Evoral::Beats::ticks (offset.ticks);
+       b += Temporal::Beats::ticks (offset.ticks);
 
        return b;
 }
@@ -156,13 +154,13 @@ TempoMetric::superclock_per_note_type_at_superclock (superclock_t sc) const
 }
 
 superclock_t
-TempoMetric::superclocks_per_grid (framecnt_t sr) const
+TempoMetric::superclocks_per_grid (samplecnt_t sr) const
 {
        return (superclock_ticks_per_second * Meter::note_value()) / (note_types_per_minute() / Tempo::note_type());
 }
 
 superclock_t
-TempoMetric::superclocks_per_bar (framecnt_t sr) const
+TempoMetric::superclocks_per_bar (samplecnt_t sr) const
 {
        return superclocks_per_grid (sr) * _divisions_per_bar;
 }
@@ -277,7 +275,7 @@ It would be more accurate to substitute the work 'pulse' for 'beat' above.
  */
 
 void
-TempoMetric::compute_c_superclock (framecnt_t sr, superclock_t end_scpqn, superclock_t superclock_duration)
+TempoMetric::compute_c_superclock (samplecnt_t sr, superclock_t end_scpqn, superclock_t superclock_duration)
 {
        if ((superclocks_per_quarter_note() == end_scpqn) || !ramped()) {
                _c_per_superclock = 0.0;
@@ -287,7 +285,7 @@ TempoMetric::compute_c_superclock (framecnt_t sr, superclock_t end_scpqn, superc
        _c_per_superclock = log ((double) superclocks_per_quarter_note () / end_scpqn) / superclock_duration;
 }
 void
-TempoMetric::compute_c_quarters (framecnt_t sr, superclock_t end_scpqn, Evoral::Beats const & quarter_duration)
+TempoMetric::compute_c_quarters (samplecnt_t sr, superclock_t end_scpqn, Temporal::Beats const & quarter_duration)
 {
        if ((superclocks_per_quarter_note () == end_scpqn) || !ramped()) {
                _c_per_quarter = 0.0;
@@ -298,7 +296,7 @@ TempoMetric::compute_c_quarters (framecnt_t sr, superclock_t end_scpqn, Evoral::
 }
 
 superclock_t
-TempoMetric::superclock_at_qn (Evoral::Beats const & qn) const
+TempoMetric::superclock_at_qn (Temporal::Beats const & qn) const
 {
        if (_c_per_quarter == 0.0) {
                /* not ramped, use linear */
@@ -308,45 +306,68 @@ TempoMetric::superclock_at_qn (Evoral::Beats const & qn) const
        return llrint (superclocks_per_quarter_note() * (log1p (_c_per_quarter * qn.to_double()) / _c_per_quarter));
 }
 
-Evoral::Beats
+void
+TempoMapPoint::set_map (TempoMap* m)
+{
+       _map = m;
+}
+
+void
+TempoMapPoint::set_dirty (bool yn)
+{
+       if (yn != _dirty) {
+               _dirty = yn;
+               if (yn && _map) {
+                       _map->set_dirty (true);
+               }
+       }
+}
+
+Temporal::Beats
 TempoMapPoint::quarters_at (superclock_t sc) const
 {
        /* This TempoMapPoint must already have a fully computed metric and position */
 
        if (!ramped()) {
-               return _quarters + Evoral::Beats ((sc - _sclock) / (double) (metric().superclocks_per_quarter_note ()));
+               return _quarters + Temporal::Beats ((sc - _sclock) / (double) (metric().superclocks_per_quarter_note ()));
        }
 
-       return _quarters + Evoral::Beats (expm1 (metric().c_per_superclock() * (sc - _sclock)) / (metric().c_per_superclock() * metric().superclocks_per_quarter_note ()));
+       return _quarters + Temporal::Beats (expm1 (metric().c_per_superclock() * (sc - _sclock)) / (metric().c_per_superclock() * metric().superclocks_per_quarter_note ()));
 }
 
-Evoral::Beats
+Temporal::Beats
 TempoMapPoint::quarters_at (Timecode::BBT_Time const & bbt) const
 {
        /* This TempoMapPoint must already have a fully computed metric and position */
 
        Timecode::BBT_Offset offset = metric().bbt_delta (bbt, _bbt);
-       cerr << "QA BBT DELTA between " << bbt << " and " << _bbt << " = " << offset << " as quarters for " << static_cast<Meter> (metric()) << " = " << metric().to_quarters (offset) << endl;
        return _quarters + metric().to_quarters (offset);
 }
 
 Timecode::BBT_Time
-TempoMapPoint::bbt_at (Evoral::Beats const & qn) const
+TempoMapPoint::bbt_at (Temporal::Beats const & qn) const
 {
        /* This TempoMapPoint must already have a fully computed metric and position */
 
-       Evoral::Beats quarters_delta = qn - _quarters;
-       int32_t ticks_delta = quarters_delta.to_ticks (Evoral::Beats::PPQN);
+       Temporal::Beats quarters_delta = qn - _quarters;
+       int32_t ticks_delta = quarters_delta.to_ticks (Temporal::Beats::PPQN);
        return metric().bbt_add (_bbt, Timecode::BBT_Offset (0, 0,  ticks_delta));
 }
 
-TempoMap::TempoMap (Tempo const & initial_tempo, Meter const & initial_meter, framecnt_t sr)
+TempoMap::TempoMap (Tempo const & initial_tempo, Meter const & initial_meter, samplecnt_t sr)
        : _sample_rate (sr)
+       , _dirty (false)
 {
-       TempoMapPoint tmp (TempoMapPoint::Flag (TempoMapPoint::ExplicitMeter|TempoMapPoint::ExplicitTempo), initial_tempo, initial_meter, 0, Evoral::Beats(), Timecode::BBT_Time(), AudioTime);
+       TempoMapPoint tmp (TempoMapPoint::Flag (TempoMapPoint::ExplicitMeter|TempoMapPoint::ExplicitTempo), initial_tempo, initial_meter, 0, Temporal::Beats(), Timecode::BBT_Time(), AudioTime);
        _points.push_back (tmp);
 }
 
+void
+TempoMap::set_dirty (bool yn)
+{
+       _dirty = yn;
+}
+
 Meter const &
 TempoMap::meter_at (superclock_t sc) const
 {
@@ -355,7 +376,7 @@ TempoMap::meter_at (superclock_t sc) const
 }
 
 Meter const &
-TempoMap::meter_at (Evoral::Beats const & b) const
+TempoMap::meter_at (Temporal::Beats const & b) const
 {
        Glib::Threads::RWLock::ReaderLock lm (_lock);
        return meter_at_locked (b);
@@ -376,7 +397,7 @@ TempoMap::tempo_at (superclock_t sc) const
 }
 
 Tempo const &
-TempoMap::tempo_at (Evoral::Beats const &b) const
+TempoMap::tempo_at (Temporal::Beats const &b) const
 {
        Glib::Threads::RWLock::ReaderLock lm (_lock);
        return tempo_at_locked (b);
@@ -393,12 +414,7 @@ void
 TempoMap::rebuild (superclock_t limit)
 {
        Glib::Threads::RWLock::WriterLock lm (_lock);
-       rebuild_locked (limit);
-}
 
-void
-TempoMap::rebuild_locked (superclock_t limit)
-{
        /* step one: remove all implicit points after a dirty explicit point */
 
   restart:
@@ -411,6 +427,11 @@ TempoMap::rebuild_locked (superclock_t limit)
 
        first_explicit_dirty = tmp;
 
+       if (first_explicit_dirty == _points.end()) {
+               /* nothing is dirty */
+               return;
+       }
+
        /* remove all implicit points, because we're going to recalculate them all */
 
        while (tmp != _points.end()) {
@@ -424,14 +445,14 @@ TempoMap::rebuild_locked (superclock_t limit)
                tmp = next;
        }
 
-       /* compute C for all ramped sections */
+       /* compute C-by-quarters for all ramped sections, because we need it shortly */
 
        for (tmp = first_explicit_dirty; tmp != _points.end(); ) {
                TempoMapPoints::iterator nxt = tmp;
                ++nxt;
 
                if (tmp->ramped() && (nxt != _points.end())) {
-                       tmp->metric().compute_c_quarters (_sample_rate, nxt->metric().superclocks_per_quarter_note (), nxt->quarters() - tmp->quarters());
+                       tmp->compute_c_quarters (_sample_rate, nxt->metric().superclocks_per_quarter_note (), nxt->quarters() - tmp->quarters());
                }
 
                tmp = nxt;
@@ -450,22 +471,13 @@ TempoMap::rebuild_locked (superclock_t limit)
                        if ((tmp->lock_style() == MusicTime)) {
                                /* determine superclock and quarter note time for this (music-time) locked point */
 
-                               cerr << "MT-lock, prev = " << *prev << endl;
-
-                               Evoral::Beats qn = prev->quarters_at (tmp->bbt());
-                               cerr << "MT-lock @ " << tmp->bbt() << " => " << qn << endl;
+                               Temporal::Beats qn = prev->quarters_at (tmp->bbt());
                                superclock_t sc = prev->sclock() + prev->metric().superclock_at_qn (qn - prev->quarters());
-                               cerr << "MT-lock sc is " << prev->metric().superclock_at_qn (qn - prev->quarters()) << " after " << prev->sclock() << " = " << sc
-                                    << " secs = " << prev->metric().superclock_at_qn (qn - prev->quarters()) / (double) superclock_ticks_per_second
-                                    << endl;
 
                                if (qn != tmp->quarters() || tmp->sclock() != sc) {
-                                       cerr << "Ned to move " << *tmp << endl;
                                        tmp->set_quarters (qn);
                                        tmp->set_sclock (sc);
-                                       cerr << "using " << *prev << " moved music-time-locked @ " << tmp->bbt() << " to " << sc << " aka " << qn << endl;
                                        _points.sort (TempoMapPoint::SuperClockComparator());
-                                       cerr << "Restart\n";
                                        goto restart;
                                }
                        }
@@ -479,32 +491,34 @@ TempoMap::rebuild_locked (superclock_t limit)
         * of music-time locked points.
         */
 
-       cerr << "POST-SORT\n";
-       dump_locked (cerr);
-
        prev = _points.end();
 
        /* step two: add new implicit points between each pair of explicit
         * points, after the dirty explicit point
         */
 
+       bool hit_dirty = false;
+       superclock_t first_dirty = 0;
+
        for (tmp = _points.begin(); tmp != _points.end(); ) {
 
-               if (!tmp->dirty()) {
-                       ++tmp;
-                       continue;
+               if (!hit_dirty) {
+                       if (!tmp->dirty()) {
+                               ++tmp;
+                               continue;
+                       }
+                       hit_dirty = true;
+                       first_dirty = tmp->sclock();
                }
 
                TempoMapPoints::iterator next = tmp;
                ++next;
 
+
                if (prev != _points.end()) {
                        if ((tmp->lock_style() == AudioTime)) {
-                               cerr << "AT: check " << *tmp << endl
-                                    << "\t\tusing " << *prev << endl;
                                /* audio-locked explicit point: recompute it's BBT and quarter-note position since this may have changed */
                                tmp->set_quarters (prev->quarters_at (tmp->sclock()));
-                               cerr << "AT - recompute quarters at " << tmp->quarters () << endl;
                                if (static_cast<Meter>(tmp->metric()) != static_cast<Meter>(prev->metric())) {
                                        /* new meter, must be on bar/measure start */
                                        tmp->set_bbt (prev->metric().round_up_to_bar (prev->bbt_at (tmp->quarters())));
@@ -512,19 +526,18 @@ TempoMap::rebuild_locked (superclock_t limit)
                                        /* no meter change, tempo change required to be on beat */
                                        tmp->set_bbt (prev->bbt_at (tmp->quarters()).round_up_to_beat());
                                }
-                               cerr << "AT - recompute bbt at " << tmp->bbt () << endl;
                        }
                }
 
                superclock_t sc = tmp->sclock();
-               Evoral::Beats qn (tmp->quarters ());
+               Temporal::Beats qn (tmp->quarters ());
                Timecode::BBT_Time bbt (tmp->bbt());
                const bool ramped = tmp->ramped () && next != _points.end();
 
-               /* Evoral::Beats are really quarter notes. This counts how many quarter notes
+               /* Temporal::Beats are really quarter notes. This counts how many quarter notes
                   there are between grid points in this section of the tempo map.
                 */
-               const Evoral::Beats qn_step = (Evoral::Beats (1) * 4) / tmp->metric().note_value();
+               const Temporal::Beats qn_step = (Temporal::Beats (1) * 4) / tmp->metric().note_value();
 
                /* compute implicit points as far as the next explicit point, or limit,
                   whichever comes first.
@@ -552,10 +565,103 @@ TempoMap::rebuild_locked (superclock_t limit)
                        _points.insert (next, TempoMapPoint (*tmp, sc, qn, bbt));
                }
 
-               (*tmp).set_dirty (false);
+               tmp->set_dirty (false);
                prev = tmp;
                tmp = next;
        }
+
+       Changed (first_dirty, _points.back().sclock()); /* EMIT SIGNAL */
+}
+
+bool
+TempoMap::set_tempo_and_meter (Tempo const & tempo, Meter const & meter, superclock_t sc, bool ramp, bool flexible)
+{
+       /* CALLER MUST HOLD LOCK */
+
+       assert (!_points.empty());
+
+       /* special case: first map entry is later than the new point */
+
+       if (_points.front().sclock() > sc) {
+               /* first point is later than sc. There's no iterator to reference a point at or before sc */
+
+               /* determine beats and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+                  even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+                  fractional.
+               */
+
+               Temporal::Beats b = _points.front().quarters_at (sc).round_to_beat();
+               Timecode::BBT_Time bbt = _points.front().bbt_at (b).round_to_beat ();
+
+               _points.insert (_points.begin(), TempoMapPoint (TempoMapPoint::ExplicitTempo, tempo, meter, sc, b, bbt, AudioTime, ramp));
+               return true;
+       }
+
+       /* special case #3: only one map entry, at the same time as the new point.
+          This is the common case when editing tempo/meter in a session with a single tempo/meter
+       */
+
+       if (_points.size() == 1 && _points.front().sclock() == sc) {
+               /* change metrics */
+               *((Tempo*) &_points.front().metric()) = tempo;
+               *((Meter*) &_points.front().metric()) = meter;
+               _points.front().make_explicit (TempoMapPoint::Flag (TempoMapPoint::ExplicitTempo|TempoMapPoint::ExplicitMeter));
+               return true;
+       }
+
+       /* Remember: iterator_at() returns an iterator that references the TempoMapPoint at or BEFORE sc */
+
+       TempoMapPoints::iterator i = iterator_at (sc);
+       TempoMapPoints::iterator nxt = i;
+       ++nxt;
+
+       if (i->sclock() == sc) {
+               /* change  metrics */
+               *((Tempo*) &i->metric()) = tempo;
+               *((Meter*) &i->metric()) = meter;
+               i->make_explicit (TempoMapPoint::Flag (TempoMapPoint::ExplicitTempo|TempoMapPoint::ExplicitMeter));
+               /* done */
+               return true;
+       }
+
+       if (!flexible && (sc - i->sclock() < i->metric().superclocks_per_note_type())) {
+               cerr << "new tempo too close to previous ...\n";
+               return false;
+       }
+
+       TempoMapPoints::iterator e (i);
+       while (!e->is_explicit() && e != _points.begin()) {
+               --e;
+       }
+
+       if (e->metric().ramped()) {
+               /* need to adjust ramp constants for preceding explict point, since the new point will be positioned right after it
+                  and thus defines the new ramp distance.
+               */
+               e->compute_c_superclock (_sample_rate, tempo.superclocks_per_quarter_note (), sc);
+       }
+
+       /* determine beats and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+          even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+          fractional.
+        */
+
+       Temporal::Beats qn = i->quarters_at (sc).round_to_beat();
+
+       /* rule: all Tempo changes must be on-beat. So determine the nearest later beat to "sc"
+        */
+
+       Timecode::BBT_Time bbt = i->bbt_at (qn).round_up_to_beat ();
+
+       /* Modify the iterator to reference the point AFTER this new one, because STL insert is always "insert-before"
+        */
+
+       if (i != _points.end()) {
+               ++i;
+       }
+
+       _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitTempo, tempo, meter, sc, qn, bbt, AudioTime, ramp));
+       return true;
 }
 
 bool
@@ -575,7 +681,7 @@ TempoMap::set_tempo (Tempo const & t, superclock_t sc, bool ramp)
                   fractional.
                */
 
-               Evoral::Beats b = _points.front().quarters_at (sc).round_to_beat();
+               Temporal::Beats b = _points.front().quarters_at (sc).round_to_beat();
                Timecode::BBT_Time bbt = _points.front().bbt_at (b).round_to_beat ();
 
                _points.insert (_points.begin(), TempoMapPoint (TempoMapPoint::ExplicitTempo, t, _points.front().metric(), sc, b, bbt, AudioTime, ramp));
@@ -614,11 +720,16 @@ TempoMap::set_tempo (Tempo const & t, superclock_t sc, bool ramp)
 
        Meter const & meter (i->metric());
 
-       if (i->metric().ramped()) {
+       TempoMapPoints::iterator e (i);
+       while (!e->is_explicit() && e != _points.begin()) {
+               --e;
+       }
+
+       if (e->metric().ramped()) {
                /* need to adjust ramp constants for preceding explict point, since the new point will be positioned right after it
                   and thus defines the new ramp distance.
                */
-               i->metric().compute_c_superclock (_sample_rate, t.superclocks_per_quarter_note (), sc);
+               e->compute_c_superclock (_sample_rate, t.superclocks_per_quarter_note (), sc);
        }
 
        /* determine beats and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
@@ -626,7 +737,7 @@ TempoMap::set_tempo (Tempo const & t, superclock_t sc, bool ramp)
           fractional.
         */
 
-       Evoral::Beats qn = i->quarters_at (sc).round_to_beat();
+       Temporal::Beats qn = i->quarters_at (sc).round_to_beat();
 
        /* rule: all Tempo changes must be on-beat. So determine the nearest later beat to "sc"
         */
@@ -652,8 +763,6 @@ TempoMap::set_tempo (Tempo const & t, Timecode::BBT_Time const & bbt, bool ramp)
 
        Timecode::BBT_Time on_beat = bbt.round_up_to_beat();
 
-       cerr << "Try to set tempo @ " << on_beat << " to " << t << endl;
-
        assert (!_points.empty());
 
        if (_points.front().bbt() > on_beat) {
@@ -680,7 +789,7 @@ TempoMap::set_tempo (Tempo const & t, Timecode::BBT_Time const & bbt, bool ramp)
        ++i;
 
        /* stick a prototype music-locked point up front and let ::rebuild figure out the superclock and quarter time */
-       _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitTempo, t, meter, 0, Evoral::Beats(), on_beat, MusicTime, ramp));
+       _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitTempo, t, meter, 0, Temporal::Beats(), on_beat, MusicTime, ramp));
        return true;
 }
 
@@ -690,8 +799,6 @@ TempoMap::set_meter (Meter const & m, Timecode::BBT_Time const & bbt)
        Glib::Threads::RWLock::WriterLock lm (_lock);
        Timecode::BBT_Time measure_start (m.round_up_to_bar (bbt));
 
-       cerr << "Try to set meter @ " << measure_start << " to " << m << endl;
-
        assert (!_points.empty());
 
        if (_points.front().bbt() > measure_start) {
@@ -701,9 +808,7 @@ TempoMap::set_meter (Meter const & m, Timecode::BBT_Time const & bbt)
 
        if (_points.size() == 1 && _points.front().bbt() == measure_start) {
                /* change Meter */
-               cerr << "Found the single point\n";
                *((Meter*) &_points.front().metric()) = m;
-               cerr << "Updated meter to " << m << endl;
                _points.front().make_explicit (TempoMapPoint::ExplicitMeter);
                return true;
        }
@@ -712,21 +817,16 @@ TempoMap::set_meter (Meter const & m, Timecode::BBT_Time const & bbt)
 
        if (i->bbt() == measure_start) {
                *((Meter*) &i->metric()) = m;
-               cerr << "Updated meter to " << m << endl;
                i->make_explicit (TempoMapPoint::ExplicitMeter);
                return true;
        }
 
-       Evoral::Beats qn = i->quarters_at (measure_start);
+       Temporal::Beats qn = i->quarters_at (measure_start);
        superclock_t sc = i->sclock() + i->metric().superclock_at_qn (qn);
 
        Tempo const & tempo (i->metric());
        ++i;
 
-       cerr << "NEW METER, provisionally @ "
-            << TempoMapPoint (TempoMapPoint::ExplicitMeter, tempo, m, sc, qn, measure_start, MusicTime)
-            << endl;
-
        _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitMeter, tempo, m, sc, qn, measure_start, MusicTime));
        return true;
 }
@@ -745,7 +845,7 @@ TempoMap::set_meter (Meter const & m, superclock_t sc)
                   fractional.
                */
 
-               Evoral::Beats b = _points.front().quarters_at (sc).round_to_beat();
+               Temporal::Beats b = _points.front().quarters_at (sc).round_to_beat();
                Timecode::BBT_Time bbt = _points.front().bbt_at (b).round_to_beat ();
 
                _points.insert (_points.begin(), TempoMapPoint (TempoMapPoint::ExplicitMeter, _points.front().metric(), m, sc, b, bbt, AudioTime));
@@ -791,7 +891,7 @@ TempoMap::set_meter (Meter const & m, superclock_t sc)
           fractional.
        */
 
-       Evoral::Beats b = i->quarters_at (sc).round_to_beat();
+       Temporal::Beats b = i->quarters_at (sc).round_to_beat();
 
        /* rule: all Meter changes must start a new measure. So determine the nearest, lower beat to "sc". If this is not
           the first division of the measure, move to the next measure.
@@ -830,7 +930,7 @@ TempoMap::iterator_at (superclock_t sc)
        */
 
        TempoMetric const & metric (_points.front().metric());
-       const TempoMapPoint tp (TempoMapPoint::Flag (0), metric, metric, sc, Evoral::Beats(), Timecode::BBT_Time(), AudioTime);
+       const TempoMapPoint tp (TempoMapPoint::Flag (0), metric, metric, sc, Temporal::Beats(), Timecode::BBT_Time(), AudioTime);
        TempoMapPoint::SuperClockComparator scmp;
 
        TempoMapPoints::iterator tmp = upper_bound (_points.begin(), _points.end(), tp, scmp);
@@ -843,7 +943,7 @@ TempoMap::iterator_at (superclock_t sc)
 }
 
 TempoMapPoints::iterator
-TempoMap::iterator_at (Evoral::Beats const & qn)
+TempoMap::iterator_at (Temporal::Beats const & qn)
 {
        /* CALLER MUST HOLD LOCK */
 
@@ -890,7 +990,7 @@ TempoMap::iterator_at (Timecode::BBT_Time const & bbt)
        */
 
        TempoMetric const & metric (_points.front().metric());
-       const TempoMapPoint tp (TempoMapPoint::Flag(0), metric, metric, 0, Evoral::Beats(), bbt, MusicTime);
+       const TempoMapPoint tp (TempoMapPoint::Flag(0), metric, metric, 0, Temporal::Beats(), bbt, MusicTime);
        TempoMapPoint::BBTComparator bcmp;
 
        TempoMapPoints::iterator tmp = upper_bound (_points.begin(), _points.end(), tp, bcmp);
@@ -913,66 +1013,35 @@ Timecode::BBT_Time
 TempoMap::bbt_at_locked (superclock_t sc) const
 {
        TempoMapPoint point (const_point_at (sc));
-       Evoral::Beats b ((sc - point.sclock()) / (double) point.metric().superclocks_per_quarter_note());
+       Temporal::Beats b ((sc - point.sclock()) / (double) point.metric().superclocks_per_quarter_note());
        return point.metric().bbt_add (point.bbt(), Timecode::BBT_Offset (0, b.get_beats(), b.get_ticks()));
 }
 
 Timecode::BBT_Time
-TempoMap::bbt_at (Evoral::Beats const & qn) const
+TempoMap::bbt_at (Temporal::Beats const & qn) const
 {
        Glib::Threads::RWLock::ReaderLock lm (_lock);
        return bbt_at_locked (qn);
 }
 
 Timecode::BBT_Time
-TempoMap::bbt_at_locked (Evoral::Beats const & qn) const
+TempoMap::bbt_at_locked (Temporal::Beats const & qn) const
 {
        //Glib::Threads::RWLock::ReaderLock lm (_lock);
        TempoMapPoint const & point (const_point_at (qn));
-       Evoral::Beats delta (qn - point.quarters());
+       Temporal::Beats delta (qn - point.quarters());
        return point.metric().bbt_add (point.bbt(), Timecode::BBT_Offset (0, delta.get_beats(), delta.get_ticks()));
 }
 
-Evoral::Beats
-TempoMap::quarter_note_at (superclock_t sc) const
-{
-       Glib::Threads::RWLock::ReaderLock lm (_lock);
-       return quarter_note_at_locked (sc);
-}
-
-Evoral::Beats
-TempoMap::quarter_note_at_locked (superclock_t sc) const
-{
-       return const_point_at (sc).quarters_at (sc);
-}
-
-Evoral::Beats
-TempoMap::quarter_note_at (Timecode::BBT_Time const & bbt) const
-{
-       Glib::Threads::RWLock::ReaderLock lm (_lock);
-       return quarter_note_at_locked (bbt);
-}
-
-Evoral::Beats
-TempoMap::quarter_note_at_locked (Timecode::BBT_Time const & bbt) const
-{
-       //Glib::Threads::RWLock::ReaderLock lm (_lock);
-       TempoMapPoint const & point (const_point_at (bbt));
-
-       Timecode::BBT_Time bbt_delta (point.metric().bbt_subtract (bbt, point.bbt()));
-       /* XXX need to convert the metric division to quarters to match Evoral::Beats == Evoral::Quarters */
-       return point.quarters() + Evoral::Beats ((point.metric().divisions_per_bar() * bbt_delta.bars) + bbt_delta.beats, bbt_delta.ticks);
-}
-
 superclock_t
-TempoMap::superclock_at (Evoral::Beats const & qn) const
+TempoMap::superclock_at (Temporal::Beats const & qn) const
 {
        Glib::Threads::RWLock::ReaderLock lm (_lock);
        return superclock_at_locked (qn);
 }
 
 superclock_t
-TempoMap::superclock_at_locked (Evoral::Beats const & qn) const
+TempoMap::superclock_at_locked (Temporal::Beats const & qn) const
 {
        assert (!_points.empty());
 
@@ -986,10 +1055,10 @@ TempoMap::superclock_at_locked (Evoral::Beats const & qn) const
                --i;
        }
 
-       /* compute distance from reference point to b. Remember that Evoral::Beats is always interpreted as quarter notes */
+       /* compute distance from reference point to b. Remember that Temporal::Beats is always interpreted as quarter notes */
 
-       const Evoral::Beats q_delta = qn - i->quarters();
-       superclock_t sclock_delta = i->metric().superclocks_per_quarter_note () * q_delta;
+       const Temporal::Beats q_delta = qn - i->quarters();
+       superclock_t sclock_delta = i->metric().superclock_at_qn (q_delta);
        return i->sclock() + sclock_delta;
 }
 
@@ -1007,7 +1076,7 @@ TempoMap::superclock_at_locked (Timecode::BBT_Time const & bbt) const
        assert (!_points.empty());
 
        /* only the bbt property matters, since we're just using it to compare */
-       const TempoMapPoint tmp (TempoMapPoint::Flag (0), Tempo (120), Meter (4, 4), 0, Evoral::Beats(), bbt, MusicTime);
+       const TempoMapPoint tmp (TempoMapPoint::Flag (0), Tempo (120), Meter (4, 4), 0, Temporal::Beats(), bbt, MusicTime);
        TempoMapPoint::BBTComparator bcmp;
 
        TempoMapPoints::const_iterator i = upper_bound (_points.begin(), _points.end(), tmp, bcmp);
@@ -1016,15 +1085,21 @@ TempoMap::superclock_at_locked (Timecode::BBT_Time const & bbt) const
                --i;
        }
 
-       /* compute distance from reference point to b. Remember that Evoral::Beats is always interpreted as quarter notes */
+       /* this computes the distance from the point, in beats whose size is
+          determined by the meter.
+       */
 
-       //const Evoral::Beats delta = b - i->beats();
-       //return i->sclock() + samples_to_superclock ((delta / i->metric().quarter_notes_per_minute ()).to_double() * _sample_rate, _sample_rate);
-       return 0;
+       const Timecode::BBT_Offset delta = i->metric().bbt_delta (bbt, i->bbt());
+
+       /* convert to quarter notes */
+       const int32_t ticks = delta.ticks + (Temporal::Beats::PPQN * delta.beats * 4) / i->metric().note_value();
+
+       /* get distance in superclocks */
+       return i->sclock() + i->metric().superclock_at_qn (Temporal::Beats::ticks (ticks));
 }
 
 void
-TempoMap::set_sample_rate (framecnt_t new_sr)
+TempoMap::set_sample_rate (samplecnt_t new_sr)
 {
        //Glib::Threads::RWLock::ReaderLock lm (_lock);
        double ratio = new_sr / (double) _sample_rate;
@@ -1042,7 +1117,7 @@ TempoMap::dump (std::ostream& ostr)
 }
 
 void
-TempoMap::dump (std::ostream& ostr)
+TempoMap::dump_locked (std::ostream& ostr)
 {
        ostr << "\n\n------------\n";
        for (TempoMapPoints::iterator i = _points.begin(); i != _points.end(); ++i) {
@@ -1053,7 +1128,7 @@ TempoMap::dump (std::ostream& ostr)
 void
 TempoMap::remove_explicit_point (superclock_t sc)
 {
-       //Glib::Threads::RWLock::WriterLock lm (_lock);
+       Glib::Threads::RWLock::WriterLock lm (_lock);
        TempoMapPoints::iterator p = iterator_at (sc);
 
        if (p->sclock() == sc) {
@@ -1061,64 +1136,160 @@ TempoMap::remove_explicit_point (superclock_t sc)
        }
 }
 
-void
-TempoMap::move_explicit (superclock_t current, superclock_t destination)
+bool
+TempoMap::move_to (superclock_t current, superclock_t destination, bool push)
 {
-       //Glib::Threads::RWLock::WriterLock lm (_lock);
+       Glib::Threads::RWLock::WriterLock lm (_lock);
        TempoMapPoints::iterator p = iterator_at (current);
 
        if (p->sclock() != current) {
-               return;
+               cerr << "No point @ " << current << endl;
+               return false;
+       }
+
+       /* put a "dirty" flag in at the nearest explicit point to the removal point
+        */
+
+       if (p != _points.begin()) {
+               TempoMapPoints::iterator prev (p);
+               --prev;
+               while (prev != _points.begin() && !prev->is_explicit()) {
+                       --prev;
+               }
+               prev->set_dirty (true);
+       } else {
+               TempoMapPoints::iterator nxt (p);
+               ++nxt;
+               while (nxt != _points.end() && !nxt->is_explicit()) {
+                       ++nxt;
+               }
+               if (nxt != _points.end()) {
+                       nxt->set_dirty (true);
+               }
+       }
+
+       if (!set_tempo_and_meter (p->metric(), p->metric(), destination, p->ramped(), true)) {
+               return false;
+       }
+
+       if (push) {
+
+               p = iterator_at (destination);
+               ++p;
+
+               const superclock_t delta = destination - current;
+
+               while (p != _points.end()) {
+                       p->set_sclock (p->sclock() + delta);
+                       ++p;
+               }
        }
 
-       move_explicit_to (p, destination);
+       _points.erase (p);
+
+       return true;
 }
 
 void
-TempoMap::move_explicit_to (TempoMapPoints::iterator p, superclock_t destination)
+TempoMap::get_grid (TempoMapPoints& ret, superclock_t start, superclock_t end, Temporal::Beats const & resolution)
 {
-       /* CALLER MUST HOLD LOCK */
-
-       TempoMapPoint point (*p);
-       point.set_sclock (destination);
+       Glib::Threads::RWLock::ReaderLock lm (_lock);
+       TempoMapPoints::iterator p = iterator_at (start);
 
-       TempoMapPoints::iterator prev;
+       while (p != _points.end() && p->sclock() < start) {
+               ++p;
+       }
 
-       prev = p;
-       if (p != _points.begin()) {
-               --prev;
+       if (resolution == Temporal::Beats()) {
+               /* just hand over the points as-is */
+               while (p != _points.end() && p->sclock() < end) {
+                       ret.push_back (*p);
+                       ++p;
+               }
+               return;
        }
 
-       /* remove existing */
+       superclock_t pos = p->sclock();
+       Temporal::Beats qpos;
+       TempoMapPoints::iterator nxt = p;
+       ++nxt;
 
-       _points.erase (p);
-       prev->set_dirty (true);
+       while ((p != _points.end()) && (pos < end)) {
+               /* recompute grid down to @param resolution level
+                */
 
-       /* find insertion point */
+               superclock_t sclock_delta = p->metric().superclock_at_qn (qpos);
 
-       p = iterator_at (destination);
+               ret.push_back (TempoMapPoint (TempoMapPoint::Flag (TempoMapPoint::ExplicitMeter|TempoMapPoint::ExplicitTempo),
+                                             p->metric(), p->metric(),
+                                             p->sclock() + sclock_delta,
+                                             p->quarters() + qpos,
+                                             p->metric().bbt_add (p->bbt(), Timecode::BBT_Offset (0, qpos.get_beats(), qpos.get_ticks())),
+                                             AudioTime,
+                                             p->ramped()));
 
-       /* STL insert semantics are always "insert-before", whereas ::iterator_at() returns iterator-at-or-before */
-       ++p;
+               qpos += resolution;
+               pos += sclock_delta;
 
-       _points.insert (p, point);
+               if (pos >= nxt->sclock()) {
+                       p = nxt;
+                       ++nxt;
+               }
+       }
 }
 
+
 void
-TempoMap::move_implicit (superclock_t current, superclock_t destination)
+TempoMap::get_bar_grid (TempoMapPoints& ret, superclock_t start, superclock_t end, int32_t bar_gap)
 {
-       //Glib::Threads::RWLock::WriterLock lm (_lock);
-       TempoMapPoints::iterator p = iterator_at (current);
+       Glib::Threads::RWLock::ReaderLock lm (_lock);
 
-       if (p->sclock() != current) {
-               return;
+       for (TempoMapPoints::iterator p = iterator_at (start); (p != _points.end()) && (p->sclock() < end); ++p) {
+
+               if ((p->sclock() >= start) && (p->bbt().beats == 1) && ((p->bbt().bars == 1) || (p->bbt().bars % bar_gap == 0))) {
+                       ret.push_back (TempoMapPoint (TempoMapPoint::Flag (TempoMapPoint::ExplicitMeter|TempoMapPoint::ExplicitTempo),
+                                                     p->metric(), p->metric(),
+                                                     p->sclock(),
+                                                     p->quarters(),
+                                                     p->bbt(),
+                                                     AudioTime,
+                                                     p->ramped()));
+               }
        }
+}
+
+std::ostream&
+operator<<(std::ostream& str, Meter const & m)
+{
+       return str << m.divisions_per_bar() << '/' << m.note_value();
+}
+
+std::ostream&
+operator<<(std::ostream& str, Tempo const & t)
+{
+       return str << t.note_types_per_minute() << " 1/" << t.note_type() << " notes per minute (" << t.superclocks_per_note_type() << " sc-per-1/" << t.note_type() << ')';
+}
+
+std::ostream&
+operator<<(std::ostream& str, TempoMapPoint const & tmp)
+{
+       str << '@' << std::setw (12) << tmp.sclock() << ' ' << tmp.sclock() / (double) superclock_ticks_per_second
+           << (tmp.is_explicit() ? " EXP" : " imp")
+           << " qn " << tmp.quarters ()
+           << " bbt " << tmp.bbt()
+           << " lock to " << tmp.lock_style()
+               ;
 
-       if (p->is_implicit()) {
-               p->make_explicit (TempoMapPoint::Flag (TempoMapPoint::ExplicitMeter|TempoMapPoint::ExplicitTempo));
+       if (tmp.is_explicit()) {
+               str << " tempo " << *((Tempo*) &tmp.metric())
+                   << " meter " << *((Meter*) &tmp.metric())
+                       ;
        }
 
-       move_explicit_to (p, destination);
+       if (tmp.is_explicit() && tmp.ramped()) {
+               str << " ramp c/sc = " << tmp.metric().c_per_superclock() << " c/qn " << tmp.metric().c_per_quarter();
+       }
+       return str;
 }
 
 /*******/
@@ -1152,24 +1323,24 @@ test_bbt_math ()
                        for (int b = 1; b < 13; ++b) {
                                PRINT_RESULT((*m), "+", bbt_add, a, B, b, 0);
                                PRINT_RESULT((*m), "+", bbt_add, a, B, b, 1);
-                               PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN/2);
-                               PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN);
-                               PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN - 1);
-                               PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN - 2);
+                               PRINT_RESULT((*m), "+", bbt_add, a, B, b, Temporal::Beats::PPQN/2);
+                               PRINT_RESULT((*m), "+", bbt_add, a, B, b, Temporal::Beats::PPQN);
+                               PRINT_RESULT((*m), "+", bbt_add, a, B, b, Temporal::Beats::PPQN - 1);
+                               PRINT_RESULT((*m), "+", bbt_add, a, B, b, Temporal::Beats::PPQN - 2);
 
                                PRINT_RESULT((*m), "+", bbt_add, b1, B, b, 0);
                                PRINT_RESULT((*m), "+", bbt_add, b1, B, b, 1);
-                               PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN/2);
-                               PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN);
-                               PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN - 1);
-                               PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN - 2);
+                               PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Temporal::Beats::PPQN/2);
+                               PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Temporal::Beats::PPQN);
+                               PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Temporal::Beats::PPQN - 1);
+                               PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Temporal::Beats::PPQN - 2);
 
                                PRINT_RESULT((*m), "+", bbt_add, n1, B, b, 0);
                                PRINT_RESULT((*m), "+", bbt_add, n1, B, b, 1);
-                               PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN/2);
-                               PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN);
-                               PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN - 1);
-                               PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN - 2);
+                               PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Temporal::Beats::PPQN/2);
+                               PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Temporal::Beats::PPQN);
+                               PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Temporal::Beats::PPQN - 1);
+                               PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Temporal::Beats::PPQN - 2);
                        }
                }
 
@@ -1177,10 +1348,10 @@ test_bbt_math ()
                        for (int b = 1; b < 13; ++b) {
                                PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, 0);
                                PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, 1);
-                               PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN/2);
-                               PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN);
-                               PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN - 1);
-                               PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN - 2);
+                               PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Temporal::Beats::PPQN/2);
+                               PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Temporal::Beats::PPQN);
+                               PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Temporal::Beats::PPQN - 1);
+                               PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Temporal::Beats::PPQN - 2);
                        }
                }
        }
@@ -1212,39 +1383,24 @@ main ()
        tmap.rebuild (SECONDS_TO_SUPERCLOCK (120));
        tmap.dump (std::cout);
 
-       return 0;
-}
-
-std::ostream&
-operator<<(std::ostream& str, Meter const & m)
-{
-       return str << m.divisions_per_bar() << '/' << m.note_value();
-}
-
-std::ostream&
-operator<<(std::ostream& str, Tempo const & t)
-{
-       return str << t.note_types_per_minute() << " 1/" << t.note_type() << " notes per minute (" << t.superclocks_per_note_type() << " sc-per-1/" << t.note_type() << ')';
-}
-
-std::ostream&
-operator<<(std::ostream& str, TempoMapPoint const & tmp)
-{
-       str << '@' << std::setw (12) << tmp.sclock() << ' ' << tmp.sclock() / (double) superclock_ticks_per_second
-           << (tmp.is_explicit() ? " EXP" : " imp")
-           << " qn " << tmp.quarters ()
-           << " bbt " << tmp.bbt()
-           << " lock to " << tmp.lock_style()
-               ;
+       if (tmap.move_to (SECONDS_TO_SUPERCLOCK(23), SECONDS_TO_SUPERCLOCK (72))) {
+               tmap.rebuild (SECONDS_TO_SUPERCLOCK (120));
+               tmap.dump (std::cout);
+       }
 
-       if (tmp.is_explicit()) {
-               str << " tempo " << *((Tempo*) &tmp.metric())
-                   << " meter " << *((Meter*) &tmp.metric())
-                       ;
+       TempoMapPoints grid;
+       tmap.get_grid (grid, SECONDS_TO_SUPERCLOCK (12.3), SECONDS_TO_SUPERCLOCK (44), Temporal::Beats::ticks ((Temporal::Beats::PPQN * 4) / 12));
+       cout << "grid contains " << grid.size() << endl;
+       for (TempoMapPoints::iterator p = grid.begin(); p != grid.end(); ++p) {
+               cout << *p << endl;
        }
 
-       if (tmp.is_explicit() && tmp.ramped()) {
-               str << " ramp c/sc = " << tmp.metric().c_per_superclock() << " c/qn " << tmp.metric().c_per_quarter();
+       TempoMapPoints bbt_grid;
+       tmap.get_bar_grid (bbt_grid, SECONDS_TO_SUPERCLOCK (0), SECONDS_TO_SUPERCLOCK (100), 4);
+       cout << "bbt_grid contains " << bbt_grid.size() << endl;
+       for (TempoMapPoints::iterator p = bbt_grid.begin(); p != bbt_grid.end(); ++p) {
+               cout << *p << endl;
        }
-       return str;
+
+       return 0;
 }