interpolation.cc/.h: Spline-Bugfixes: Crash bug at tempos close to 0, wrong calculati...
[ardour.git] / libs / ardour / tempo.cc
index b2865fc399128f11356e47cb87460fe3cc851611..30560167523b55e7ac9f098a8f91b36de384a812 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>
@@ -246,29 +246,49 @@ TempoMap::~TempoMap ()
 int
 TempoMap::move_metric_section (MetricSection& section, const BBT_Time& when)
 {
-       if (when == section.start()) {
+       if (when == section.start() || !section.movable()) {
                return -1;
        }
 
-       if (!section.movable()) {
-               return 1;
-       }
-
        Glib::RWLock::WriterLock  lm (lock);
        MetricSectionSorter cmp;
-       BBT_Time corrected (when);
-       
-       if (dynamic_cast<MeterSection*>(&section) != 0) {
-               if (corrected.beats > 1) {
-                       corrected.beats = 1;
-                       corrected.bars++;
+
+       if (when.beats != 1) {
+
+               /* position by audio frame, then recompute BBT timestamps from the audio ones */
+
+               nframes_t frame = frame_time (when);
+               // cerr << "nominal frame time = " << frame << endl;
+
+               nframes_t prev_frame = round_to_type (frame, -1, Beat);
+               nframes_t next_frame = round_to_type (frame, 1, Beat);
+               
+               // cerr << "previous beat at " << prev_frame << " next at " << next_frame << endl;
+
+               /* use the closest beat */
+
+               if ((frame - prev_frame) < (next_frame - frame)) {
+                       frame = prev_frame;
+               } else {
+                       frame = next_frame;
                }
+               
+               // cerr << "actual frame time = " << frame << endl;
+               section.set_frame (frame);
+               // cerr << "frame time = " << section.frame() << endl;
+               timestamp_metrics (false);
+               // cerr << "new BBT time = " << section.start() << endl;
+               metrics->sort (cmp);
+
+       } else {
+
+               /* positioned at bar start already, so just put it there */
+
+               section.set_start (when);
+               metrics->sort (cmp);
+               timestamp_metrics (true);
        }
-       corrected.ticks = 0;
 
-       section.set_start (corrected);
-       metrics->sort (cmp);
-       timestamp_metrics (true);
 
        return 0;
 }
@@ -288,7 +308,6 @@ TempoMap::move_meter (MeterSection& meter, const BBT_Time& when)
                StateChanged (Change (0));
        }
 }
-               
 
 void
 TempoMap::remove_tempo (const TempoSection& tempo)
@@ -412,11 +431,12 @@ TempoMap::replace_tempo (TempoSection& existing, const Tempo& replacement)
                        TempoSection *ts;
 
                        if ((ts = dynamic_cast<TempoSection*>(*i)) != 0 && ts == &existing) {
-                               
-                               *((Tempo *) ts) = replacement;
+
+                                *((Tempo *) ts) = replacement;
 
                                replaced = true;
                                timestamp_metrics (true);
+
                                break;
                        }
                }
@@ -493,6 +513,21 @@ TempoMap::replace_meter (MeterSection& existing, const Meter& replacement)
        }
 }
 
+void
+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) {
+               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                       *((Tempo*) t) = newtempo;
+                       StateChanged (Change (0));
+                       break;
+               }
+       }
+}
+
 void
 TempoMap::change_existing_tempo_at (nframes_t where, double beats_per_minute, double note_type)
 {
@@ -582,6 +617,8 @@ TempoMap::timestamp_metrics (bool use_bbt)
 
        if (use_bbt) {
 
+               // cerr << "\n\n\n ######################\nTIMESTAMP via BBT ##############\n" << endl;
+
                nframes_t current = 0;
                nframes_t section_frames;
                BBT_Time start;
@@ -611,42 +648,69 @@ TempoMap::timestamp_metrics (bool use_bbt)
 
        } else {
 
+               // cerr << "\n\n\n ######################\nTIMESTAMP via AUDIO ##############\n" << endl;
+
                bool first = true;
+               MetricSection* prev = 0;
 
                for (i = metrics->begin(); i != metrics->end(); ++i) {
 
                        BBT_Time bbt;
-
-                       bbt_time_with_metric ((*i)->frame(), bbt, Metric (*meter, *tempo));
+                       Metric 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
+                       }
+               
+                       bbt_time_with_metric ((*i)->frame(), bbt, metric);
 
                        // cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
+                       
 
                        if (first) {
                                first = false;
                        } else {
-                               if (bbt.beats != 1 || bbt.ticks != 0) {
+                               
+                               if (bbt.ticks > Meter::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;
-                                       bbt.ticks = 0;
                                }
                        }
-
-                       // cerr << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << endl;
-
+                       
+                       //s cerr << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << 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);
                }
        }
 
        // dump (cerr);
+       // cerr << "###############################################\n\n\n" << endl;
+
 }
 
 TempoMap::Metric
@@ -720,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);
        }
 }
@@ -737,49 +801,52 @@ 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();
 
-       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;
-
-
-       /* 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);
-
+        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.
+
+       // cerr << "-----\t RETURN " << bbt << endl;
 }
 
-
 nframes_t 
 TempoMap::count_frames_between ( const BBT_Time& start, const BBT_Time& end) const
 {
         /* for this to work with fractional measure types, start and end have to be "legal" BBT types, 
-        that means that the beats and ticks should be inside a bar
+          that means that the beats and ticks should be inside a bar
        */
 
        nframes_t frames = 0;
@@ -814,7 +881,7 @@ TempoMap::count_frames_between ( const BBT_Time& start, const BBT_Time& end) con
 nframes_t 
 TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, const BBT_Time& start, const BBT_Time& end) const
 {
-        /*this is used in timestamping the metrics by actually counting the beats */ 
+        /* this is used in timestamping the metrics by actually counting the beats */ 
 
        nframes_t frames = 0;
        uint32_t bar = start.bars;
@@ -834,16 +901,26 @@ TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo,
                        beat = 1;
                        ++bar;
                        ++beats_counted;
-               } else {
-                       ++beat;
-                       ++beats_counted;
+
                        if (beat > beats_per_bar) {
+
                                /* this is a fractional beat at the end of a fractional bar
-                                  so it should only count for the fraction */
+                                  so it should only count for the fraction 
+                               */
+
                                beats_counted -= (ceil(beats_per_bar) - beats_per_bar);
                        }
+
+               } else {
+                       ++beat;
+                       ++beats_counted;
                }
        }
+
+       // cerr << "Counted " << beats_counted << " from " << start << " to " << end 
+       // << " bpb were " << beats_per_bar 
+       // << " fpb was " << beat_frames
+       // << endl;
        
        frames = (nframes_t) floor (beats_counted * beat_frames);
 
@@ -865,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);
@@ -884,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;
 
@@ -904,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
@@ -926,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);
 
                }
@@ -952,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;
@@ -969,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;
@@ -1052,7 +1129,6 @@ TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num)
 }
 
 nframes_t
-
 TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type)
 {
        Metric metric = metric_at (frame);
@@ -1064,16 +1140,16 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type)
        case Bar:
                if (dir < 0) {
                        /* relax */
-
                } else if (dir > 0) {
                        if (bbt.beats > 0) {
                                bbt.bars++;
+                       } else if (metric.frame() < frame) {
+                               bbt.bars++;
                        }
                } else {
                        if (bbt.beats > metric.meter().beats_per_bar()/2) {
                                bbt.bars++;
                        }
-
                }
                bbt.beats = 1;
                bbt.ticks = 0;
@@ -1085,6 +1161,8 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type)
                } else if (dir > 0) {
                        if (bbt.ticks > 0) {
                                bbt.beats++;
+                       } else if (metric.frame() < frame) {
+                               bbt.beats++;
                        }
                } else {
                        if (bbt.ticks >= (Meter::ticks_per_beat/2)) {
@@ -1099,13 +1177,12 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type)
                break;
        
        }
-
+       
        /* 
-          cerr << "for " << frame << " round to " << bbt << " using "
-          << metric.start()
-          << endl;
+       cerr << "for " << frame << " round to " << bbt << " using "
+            << metric.start()
+            << endl;
        */
-
        return metric.frame() + count_frames_between (metric.start(), bbt);
 }
 
@@ -1191,7 +1268,9 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const
 
                if (i == metrics->end()) {
                        limit = upper;
+                       // cerr << "== limit set to end of request @ " << limit << endl;
                } else {
+                       // cerr << "== limit set to next metric @ " << (*i)->frame() << endl;
                        limit = (*i)->frame();
                }
 
@@ -1224,6 +1303,10 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const
                                beat++;
                        }
 
+                       //  cerr << "out of beats, @ end ? " << (i == metrics->end()) << " out of bpb ? "
+                       // << (beat > ceil(beats_per_bar))
+                       // << endl;
+
                        if (beat > ceil(beats_per_bar) || i != metrics->end()) {
 
                                /* we walked an entire bar. its
@@ -1245,9 +1328,11 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const
                                if (beat > ceil (beats_per_bar)) {
                                        /* next bar goes where the numbers suggest */
                                        current -=  beat_frames * (ceil(beats_per_bar)-beats_per_bar);
+                                       // cerr << "++ next bar from numbers\n";
                                } else {
                                        /* next bar goes where the next metric is */
                                        current = limit;
+                                       // cerr << "++ next bar at next metric\n";
                                }
                                bar++;
                                beat = 1;
@@ -1275,6 +1360,9 @@ TempoMap::get_points (nframes_t lower, nframes_t upper) const
                                beat = 1;
                        }
 
+                       current = (*i)->frame ();
+                       // cerr << "loop around with current @ " << current << endl;
+
                        beats_per_bar = meter->beats_per_bar ();
                        frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
                        beat_frames = tempo->frames_per_beat (_frame_rate, *meter);
@@ -1419,3 +1507,46 @@ TempoMap::dump (std::ostream& o) const
        }
 }
 
+int
+TempoMap::n_tempos() const
+{
+       Glib::RWLock::ReaderLock lm (lock);
+       int cnt = 0;
+
+       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+               if (dynamic_cast<const TempoSection*>(*i) != 0) {
+                       cnt++;
+               }
+       }
+
+       return cnt;
+}
+
+int
+TempoMap::n_meters() const
+{
+       Glib::RWLock::ReaderLock lm (lock);
+       int cnt = 0;
+
+       for (Metrics::const_iterator i = metrics->begin(); i != metrics->end(); ++i) {
+               if (dynamic_cast<const MeterSection*>(*i) != 0) {
+                       cnt++;
+               }
+       }
+
+       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));
+}