fix a number of errors with BBT time computations, mostly notably ones related to...
authorPaul Davis <paul@linuxaudiosystems.com>
Mon, 19 Dec 2011 18:32:06 +0000 (18:32 +0000)
committerPaul Davis <paul@linuxaudiosystems.com>
Mon, 19 Dec 2011 18:32:06 +0000 (18:32 +0000)
git-svn-id: svn://localhost/ardour2/branches/3.0@11020 d708f5d6-7413-0410-9779-e7cbd77b26cf

libs/ardour/tempo.cc

index 24b395db0a0480793670e6e41e574d584f589cbf..3bb55f76f3f9ea956fc3f8c1ee85effb5c3af9de 100644 (file)
@@ -53,16 +53,16 @@ Tempo::frames_per_beat (framecnt_t sr) const
 
 /***********************************************************************/
 
-double
-Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
+double 
+Meter::frames_per_division (const Tempo& tempo, framecnt_t sr) const
 {
-       return (60.0 * sr * _divisions_per_bar) / tempo.beats_per_minute();
+       return (60.0 * sr) / (tempo.beats_per_minute() * (_note_type/tempo.note_type()));
 }
 
-double 
-Meter::frames_per_division (const Tempo& tempo, framecnt_t sr) const
+double
+Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
 {
-       return (60.0 * sr) / (tempo.beats_per_minute() * _note_type/tempo.note_type());
+       return frames_per_division (tempo, sr) * _divisions_per_bar;
 }
 
 /***********************************************************************/
@@ -862,52 +862,54 @@ TempoMap::bbt_time_unlocked (framepos_t frame, BBT_Time& bbt) const
 void
 TempoMap::bbt_time_with_metric (framepos_t frame, BBT_Time& bbt, const TempoMetric& metric) const
 {
-       framecnt_t frame_diff;
-
        const double divisions_per_bar = metric.meter().divisions_per_bar();
-       const double ticks_per_frame = metric.meter().frames_per_division (metric.tempo(),_frame_rate) / BBT_Time::ticks_per_beat;
-
-       // cerr << "*** Compute BBT time for " << frame 
-       // <<  " from metric at " << metric.frame() << " tempo = " << metric.tempo().beats_per_minute () << " meter " 
-       // << metric.meter().divisions_per_bar() << '/' << metric.meter().note_divisor() 
-       // << endl; 
-
-       /* now compute how far beyond that point we actually are. */
+       const double frames_per_tick = metric.meter().frames_per_division (metric.tempo(),_frame_rate) / BBT_Time::ticks_per_beat;
 
-       frame_diff = frame - metric.frame();
-
-       bbt.ticks = metric.start().ticks + (uint32_t)round((double)frame_diff / ticks_per_frame);
-       uint32_t xtra_beats = bbt.ticks / (uint32_t)BBT_Time::ticks_per_beat;
-       bbt.ticks %= (uint32_t)BBT_Time::ticks_per_beat;
-
-       bbt.beats = metric.start().beats + xtra_beats - 1; // correction for 1-based counting, see below for matching operation.
-       bbt.bars = metric.start().bars + (uint32_t)floor((double)bbt.beats / divisions_per_bar);
-       bbt.beats = (uint32_t)fmod((double)bbt.beats, divisions_per_bar);
+       /* now compute how far beyond the metric we actually are, and add the
+        * relevant number of ticks to the metric's BBT time
+        */
 
-       /* if we have a fractional number of beats per bar, we see if
-          we're in the last beat (the fractional one).  if so, we
-          round ticks appropriately and bump to the next bar. 
+       framecnt_t frame_diff = frame - metric.frame();
+       uint32_t tick_diff = (uint32_t) lrint ((double) frame_diff / frames_per_tick);
+
+       bbt.ticks = metric.start().ticks + tick_diff;
+       uint32_t beat_overflow = bbt.ticks / (uint32_t) BBT_Time::ticks_per_beat;
+       bbt.ticks = bbt.ticks % (uint32_t) BBT_Time::ticks_per_beat;
+       bbt.beats = metric.start().beats + beat_overflow;
+       /* bbt.beats uses 1-based counting, so adjust to get the right answer */
+       uint32_t bar_overflow = (bbt.beats - 1) / (uint32_t) divisions_per_bar;
+       bbt.bars = metric.start().bars + bar_overflow;
+
+       /* fmod will map bbt.beats as follows:
+
+          Beats      divisions per bar   Normalized beat
+          0          N                   => 0
+           1          N                   => 1
+           2          N                   => 2
+          3          N                   => 3
+           .   
+          .
+          .
+          N-1        N                   => N-1
+          N          N                   => 0
+          N+1        N                   => 1
+          .
+          .
+          .
+          2N-1       N                   => N-1
+          2N         N                   => 0
+   
+          so, the only special cases are 0, N, 2N etc. however bbt.beats is
+          never zero, so the only actual special cases are N, 2N and so on,
+          allowing us to use a special case check for fmod () == 0 and
+          changing the value to divisions per bar
        */
-       double beat_fraction = divisions_per_bar - floor(divisions_per_bar);
-
-       /* XXX one problem here is that I'm not sure how to handle
-          fractional beats that don't evenly divide ticks_per_beat.
-          If they aren't handled consistently, I would guess we'll
-          continue to have strange discrepancies occuring.  Perhaps
-          this will also behave badly in the case of meters like
-          0.1/4, but I can't be bothered to test that.
-       */
-       uint32_t ticks_on_last_beat = (uint32_t)floor(BBT_Time::ticks_per_beat * beat_fraction);
-
-       if (bbt.beats > (uint32_t)floor(divisions_per_bar) && bbt.ticks >= ticks_on_last_beat) {
-               bbt.ticks -= ticks_on_last_beat;
-               bbt.beats = 0;
-               bbt.bars++;
-       }
 
-       bbt.beats++; // correction for 1-based counting, see above for matching operation.
+       bbt.beats = (uint32_t) fmod (bbt.beats, divisions_per_bar);
 
-       // cerr << "-----\t RETURN " << bbt << endl;
+       if (bbt.beats == 0) {
+               bbt.beats = divisions_per_bar;
+       }
 }
 
 framecnt_t
@@ -1417,6 +1419,8 @@ TempoMap::get_points (framepos_t lower, framepos_t upper) const
        frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
        beat_frames = meter->frames_per_division (*tempo,_frame_rate);
 
+       // cerr << "Start with beat frames = " << beat_frames <<  " bar = " << frames_per_bar << endl;
+
        if (meter->frame() > tempo->frame()) {
                bar = meter->start().bars;
                beat = meter->start().beats;
@@ -1448,7 +1452,7 @@ TempoMap::get_points (framepos_t lower, framepos_t upper) const
                        limit = upper;
                        // cerr << "== limit set to end of request @ " << limit << endl;
                } else {
-                       // cerr << "== limit set to next metric @ " << (*i)->frame() << endl;
+                       // cerr << "== limit set to next meter section @ " << (*i)->frame() << endl;
                        limit = (*i)->frame();
                }
 
@@ -1486,22 +1490,22 @@ TempoMap::get_points (framepos_t lower, framepos_t upper) const
                        // << " beat frame @ " << beat_frame << " vs. " << limit
                        // << endl;
 
-                       if (beat > ceil(divisions_per_bar) || i != metrics->end()) {
-
-                               /* we walked an entire bar. its
-                                  important to move `current' forward
-                                  by the actual frames_per_bar, not move it to
-                                  an integral beat_frame, so that metrics with
-                                  non-integral beats-per-bar have
-                                  their bar positions set
-                                  correctly. consider a metric with
-                                  9-1/2 beats-per-bar. the bar we
-                                  just filled had  10 beat marks,
-                                  but the bar end is 1/2 beat before
-                                  the last beat mark.
-                                  And it is also possible that a tempo
-                                  change occured in the middle of a bar,
-                                  so we subtract the possible extra fraction from the current
+                        if (beat > ceil(divisions_per_bar) || (i != metrics->end() && dynamic_cast<MeterSection*>(*i))) {
+
+                               /* we've arrived at either the end of a bar or
+                                  a new meter marker.
+
+                                  its important to move `current' forward by
+                                  the actual frames_per_bar, not move it to an
+                                  integral beat_frame, so that metrics with
+                                  non-integral beats-per-bar have their bar
+                                  positions set correctly. consider a metric
+                                  with 9-1/2 beats-per-bar. the bar we just
+                                  filled had 10 beat marks, but the bar end is
+                                  1/2 beat before the last beat mark.  And it
+                                  is also possible that a tempo change occured
+                                  in the middle of a bar, so we subtract the
+                                  possible extra fraction from the current
                                */
 
                                if (beat > ceil (divisions_per_bar)) {
@@ -1516,7 +1520,6 @@ TempoMap::get_points (framepos_t lower, framepos_t upper) const
                                bar++;
                                beat = 1;
                        }
-
                }
 
                /* if we're done, then we're done */
@@ -1546,6 +1549,8 @@ TempoMap::get_points (framepos_t lower, framepos_t upper) const
                        frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
                        beat_frames = meter->frames_per_division (*tempo, _frame_rate);
 
+                       // cerr << "New metric with beat frames = " << beat_frames <<  " bar = " << frames_per_bar << endl;
+
                        ++i;
                }