Fix crash on relayering.
[ardour.git] / libs / ardour / tempo.cc
index ab7b7c096eeb619a0f7bc5fbf117c0b035a72e03..a2307901bcd59c0917944217f349b2f677957ea8 100644 (file)
@@ -25,9 +25,9 @@
 #include <sigc++/bind.h>
 
 #include <glibmm/thread.h>
-#include <pbd/xml++.h>
-#include <ardour/tempo.h>
-#include <ardour/utils.h>
+#include "pbd/xml++.h"
+#include "ardour/tempo.h"
+#include "ardour/utils.h"
 
 #include "i18n.h"
 #include <locale.h>
@@ -660,6 +660,7 @@ TempoMap::timestamp_metrics (bool use_bbt)
                        
                        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
@@ -783,8 +784,8 @@ TempoMap::metric_at (BBT_Time bbt) const
 void
 TempoMap::bbt_time (nframes_t frame, BBT_Time& bbt) const
 {
-        {
-               Glib::RWLock::ReaderLock lm (lock);
+       {
+               Glib::RWLock::ReaderLock lm (lock);
                bbt_time_unlocked (frame, bbt);
        }
 }
@@ -800,46 +801,44 @@ TempoMap::bbt_time_with_metric (nframes_t frame, BBT_Time& bbt, const Metric& me
 {
        nframes_t frame_diff;
 
-       uint32_t xtra_bars = 0;
-       double xtra_beats = 0;
-       double beats = 0;
-
        // cerr << "---- BBT time for " << frame << " using metric @ " << metric.frame() << " BBT " << metric.start() << endl;
 
        const double beats_per_bar = metric.meter().beats_per_bar();
-       const double frames_per_bar = metric.meter().frames_per_bar (metric.tempo(), _frame_rate);
-       const double beat_frames = metric.tempo().frames_per_beat (_frame_rate, metric.meter());
+       const double ticks_per_frame = metric.tempo().frames_per_beat (_frame_rate, metric.meter()) / Meter::ticks_per_beat;
 
        /* now compute how far beyond that point we actually are. */
 
        frame_diff = frame - metric.frame();
-       
-       // cerr << "----\tdelta = " << frame_diff << endl;
-
-       xtra_bars = (uint32_t) floor (frame_diff / frames_per_bar);
-       frame_diff -= (uint32_t) floor (xtra_bars * frames_per_bar);
-       xtra_beats = (double) frame_diff / beat_frames;
 
-       // cerr << "---\tmeaning " << xtra_bars << " xtra bars and " << xtra_beats << " xtra beats\n";
+        bbt.ticks = metric.start().ticks + (uint32_t)round((double)frame_diff / ticks_per_frame);
+        uint32_t xtra_beats = bbt.ticks / (uint32_t)Meter::ticks_per_beat;
+        bbt.ticks %= (uint32_t)Meter::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 / beats_per_bar);
+        bbt.beats = (uint32_t)fmod((double)bbt.beats, beats_per_bar);
+
+        /* 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. */
+        double beat_fraction = beats_per_bar - floor(beats_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(Meter::ticks_per_beat * beat_fraction);
+        if(bbt.beats > (uint32_t)floor(beats_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.
 
-       /* and set the returned value */
-
-       /* and correct beat/bar shifts to match the meter.
-         remember: beat and bar counting is 1-based, 
-         not zero-based 
-         also the meter may contain a fraction
-       */
-       
-       bbt.bars = metric.start().bars + xtra_bars; 
-
-       beats = (double) metric.start().beats + xtra_beats;
-       
-       bbt.bars += (uint32_t) floor(beats/ (beats_per_bar+1) );
-
-       beats = fmod(beats - 1, beats_per_bar )+ 1.0;
-       bbt.ticks = (uint32_t)( round((beats - floor(beats)) *(double) Meter::ticks_per_beat));
-       bbt.beats = (uint32_t) floor(beats);
-       
        // cerr << "-----\t RETURN " << bbt << endl;
 }
 
@@ -943,7 +942,7 @@ TempoMap::bbt_duration_at (nframes_t pos, const BBT_Time& bbt, int dir) const
        nframes_t frames = 0;
 
        BBT_Time when;
-       bbt_time(pos,when);
+       bbt_time(pos, when);
 
        {
                Glib::RWLock::ReaderLock lm (lock);
@@ -962,7 +961,7 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
        double beats_per_bar;
        BBT_Time result;
        
-       result.bars = max(1U,when.bars + dir * bbt.bars) ;
+       result.bars = max(1U, when.bars + dir * bbt.bars) ;
        result.beats = 1;
        result.ticks = 0;
 
@@ -982,7 +981,7 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
                result.beats = when.beats +  bbt.beats;
                result.ticks = when.ticks +  bbt.ticks;
 
-               while (result.beats >= (beats_per_bar+1)) {
+               while (result.beats >= (beats_per_bar + 1)) {
                        result.bars++;
                        result.beats -=  (uint32_t) ceil(beats_per_bar);
                        metric = metric_at(result); // maybe there is a meter change
@@ -1004,14 +1003,14 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
                while (result.ticks >= ticks_at_beat) {
                        result.beats++;
                        result.ticks -= ticks_at_beat;
-                       if  (result.beats >= (beats_per_bar+1)) {
+                       if  (result.beats >= (beats_per_bar + 1)) {
                                result.bars++;
                                result.beats = 1;
                                metric = metric_at(result); // maybe there is a meter change
                                beats_per_bar = metric.meter().beats_per_bar();
                        }
                        ticks_at_beat= (uint32_t) ( result.beats == ceil(beats_per_bar) ?
-                                      (1 - (ceil(beats_per_bar) - beats_per_bar) )* Meter::ticks_per_beat 
+                                      (1 - (ceil(beats_per_bar) - beats_per_bar) ) * Meter::ticks_per_beat 
                                       : Meter::ticks_per_beat);
 
                }
@@ -1030,7 +1029,7 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
                                
                                b -= (uint32_t) ceil(beats_per_bar);
                        } else {
-                               b = (uint32_t) ceil(beats_per_bar)- b + when.beats ;
+                               b = (uint32_t) ceil(beats_per_bar) - b + when.beats ;
                        }
                }
                result.beats = when.beats - b;
@@ -1047,11 +1046,11 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
                        do {
 
                                if (result.beats == 1) {
-                                       result.bars = max(1U,result.bars-- ) ;
+                                       result.bars = max(1U, result.bars-- ) ;
                                        metric = metric_at(result); // maybe there is a meter change
                                        beats_per_bar = metric.meter().beats_per_bar();
                                        result.beats = (uint32_t) ceil(beats_per_bar);
-                                       ticks_at_beat = (uint32_t) ((1 - (ceil(beats_per_bar) - beats_per_bar))* Meter::ticks_per_beat) ;
+                                       ticks_at_beat = (uint32_t) ((1 - (ceil(beats_per_bar) - beats_per_bar)) * Meter::ticks_per_beat) ;
                                } else {
                                        result.beats --;
                                        ticks_at_beat = (uint32_t) Meter::ticks_per_beat;
@@ -1100,30 +1099,91 @@ TempoMap::round_to_beat (nframes_t fr, int dir)
 }
 
 nframes_t
-
-TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num)
+TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num, int dir)
 {
 
        BBT_Time the_beat;
        uint32_t ticks_one_half_subdivisions_worth;
        uint32_t ticks_one_subdivisions_worth;
+       uint32_t difference;
 
        bbt_time(fr, the_beat);
 
        ticks_one_subdivisions_worth = (uint32_t)Meter::ticks_per_beat / sub_num;
        ticks_one_half_subdivisions_worth = ticks_one_subdivisions_worth / 2;
+       
+       if (dir > 0) {
+               
+               /* round to next */
 
-       if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) {
-         uint32_t difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth);
-         if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) {
-           the_beat.beats++;
-           the_beat.ticks += difference;
-           the_beat.ticks -= (uint32_t)Meter::ticks_per_beat;
-         } else {  
-           the_beat.ticks += difference;
-         }
-       } else {
-         the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth;
+               uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
+
+               if (mod == 0) { 
+                       /* right on the subdivision, so the difference is just the subdivision ticks */
+                       difference = ticks_one_subdivisions_worth;
+
+               } else {
+                       /* not on subdivision, compute distance to next subdivision */
+
+                       difference = ticks_one_subdivisions_worth - mod;
+               }
+
+               if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) {
+                       the_beat.beats++;
+                       the_beat.ticks += difference;
+                       the_beat.ticks -= (uint32_t)Meter::ticks_per_beat;
+               } else {  
+                       the_beat.ticks += difference;
+               }
+
+       } else if (dir < 0) {
+
+               /* round to previous */
+
+               uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
+
+               if (mod == 0) { 
+                       /* right on the subdivision, so the difference is just the subdivision ticks */
+                       difference = ticks_one_subdivisions_worth;
+                       cerr << "On the sub, move by 1 sub = " << difference << endl;
+               } else {
+                       /* not on subdivision, compute distance to previous subdivision, which
+                          is just the modulus.
+                       */
+
+                       difference = mod;
+                       cerr << "off the sub, move by 1 sub = " << difference << endl;
+               }
+
+
+               cerr << "ticks = " << the_beat.ticks << endl;
+
+               if (the_beat.ticks < difference) {
+                       cerr << "backup beats, set ticks to "
+                            << (uint32_t)Meter::ticks_per_beat - difference << endl;
+                       the_beat.beats--;
+                       the_beat.ticks = (uint32_t)Meter::ticks_per_beat - difference;
+               } else {  
+                       cerr << " reduce ticks\n";
+                       the_beat.ticks -= difference;
+               }
+
+       } else { 
+               /* round to nearest */
+
+               if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) {
+                       difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth);
+                       if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) {
+                               the_beat.beats++;
+                               the_beat.ticks += difference;
+                               the_beat.ticks -= (uint32_t)Meter::ticks_per_beat;
+                       } else {  
+                               the_beat.ticks += difference;
+                       }
+               } else {
+                       // difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth);
+                       the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth;
+               }
        }
 
        return frame_time (the_beat);
@@ -1140,7 +1200,9 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type)
        switch (type) {
        case Bar:
                if (dir < 0) {
-                       /* relax */
+                       if (bbt.bars > 1) {
+                               bbt.bars--;
+                       }
                } else if (dir > 0) {
                        if (bbt.beats > 0) {
                                bbt.bars++;
@@ -1158,7 +1220,9 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type)
        
        case Beat:
                if (dir < 0) {
-                       /* relax */
+                       if (bbt.beats > 1) {
+                               bbt.beats--;
+                       }
                } else if (dir > 0) {
                        if (bbt.ticks > 0) {
                                bbt.beats++;
@@ -1179,11 +1243,12 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type)
        
        }
        
-       /* 
+       /*
        cerr << "for " << frame << " round to " << bbt << " using "
             << metric.start()
             << endl;
        */
+       
        return metric.frame() + count_frames_between (metric.start(), bbt);
 }
 
@@ -1537,3 +1602,17 @@ TempoMap::n_meters() const
 
        return cnt;
 }
+
+void
+TempoMap::insert_time (nframes_t where, nframes_t amount)
+{
+       for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
+               if ((*i)->frame() >= where) {
+                       (*i)->set_frame ((*i)->frame() + amount);
+               }
+       }
+
+       timestamp_metrics (false);
+       
+       StateChanged (Change (0));
+}