don't try to backup sessions from older versions of ardour that are also read-only
[ardour.git] / libs / ardour / tempo.cc
index a521f44bd78d93cacf4b1da75b0faaa468972056..e64a1169333023bf72021c3f86b144cbe549d6d4 100644 (file)
 
 #include <algorithm>
 #include <stdexcept>
+#include <cmath>
 
 #include <unistd.h>
 
-#include <cmath>
-
 #include <glibmm/thread.h>
 #include "pbd/xml++.h"
 #include "evoral/types.hpp"
 #include "ardour/debug.h"
 #include "ardour/tempo.h"
-#include "ardour/utils.h"
 
 #include "i18n.h"
 #include <locale.h>
@@ -182,8 +180,8 @@ TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
        new_start.bars = start().bars;
        
        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);
+       new_start.beats = (uint32_t) floor (ticks/BBT_Time::ticks_per_beat);
+       new_start.ticks = 0; /* (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat); */
 
        /* remember the 1-based counting properties of beats */
        new_start.beats += 1;
@@ -399,75 +397,84 @@ 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
-                                * its data content (but not its properties as
-                                * a section
-                                */
+                       if ((*i)->start().bars == section->start().bars) {
 
-                               if (!iter_is_tempo) {
+                               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<Tempo*>(*i)) = *(dynamic_cast<Tempo*>(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) {
+
+               Metrics::iterator i;
+
                for (i = metrics.begin(); i != metrics.end(); ++i) {
-                       
-                       if ((*i)->compare (*section) < 0) {
-                               continue;
+                       if ((*i)->start() > section->start()) {
+                               break;
                        }
-                       
-                       metrics.insert (i, section);
-                       break;
-               }
-
-               if (i == metrics.end()) {
-                       metrics.insert (metrics.end(), section);
                }
+               
+               metrics.insert (i, section);
        }
 }
 
@@ -476,7 +483,7 @@ 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 {
@@ -546,7 +553,7 @@ 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 {
@@ -741,12 +748,11 @@ TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
 
        if (end < 0) {
 
-               if (_map.empty()) {
-                       /* compute 1 mins worth */
-                       end = _frame_rate * 60;
-               } else {
-                       end = _map.back().frame;
-               }
+               /* we will actually stop once we hit
+                  the last metric.
+               */
+               end = max_framepos;
+
        } else {
                if (!_map.empty ()) {
                        /* never allow the map to be shortened */
@@ -883,14 +889,21 @@ TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter,
 
        TempoSection* ts;
        MeterSection* ms;
-       double divisions_per_bar;
        double beat_frames;
+       framepos_t bar_start_frame;
+
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Extend map to %1 from %2 = %3\n", end, current, current_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;
 
@@ -937,21 +950,27 @@ TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter,
                                                /* back up to previous beat */
                                                current_frame -= beat_frames;
 
-                                               /* set tempo section location based on offset from last beat */
-
-                                               double bar_offset_in_beats = 1 + (ts->bar_offset() * meter->divisions_per_bar());
-
-                                               /* we've already advanced
-                                                * current.beats, but we want
-                                                * the previous beat's value
+                                               /* 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.
                                                 */
 
-                                               bar_offset_in_beats -= current.beats - 1;
+                                               double offset_within_old_beat = (tempo->frame() - current_frame) / beat_frames;
 
-                                               tempo->set_frame (current_frame + (bar_offset_in_beats * beat_frames));
+                                               current_frame += (offset_within_old_beat * beat_frames) + ((1.0 - offset_within_old_beat) * next_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);
                                                /* next metric doesn't have to
                                                 * match this precisely to
                                                 * merit a reloop ...
@@ -981,11 +1000,10 @@ TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter,
                                        meter->set_frame (current_frame);
                                }
                                
-                               divisions_per_bar = meter->divisions_per_bar ();
                                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)));
+                                                                              beat_frames, meter->divisions_per_bar(), *((Meter*)meter), *((Tempo*)tempo)));
                        
                                ++next_metric;
 
@@ -995,15 +1013,25 @@ TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter,
                                        goto set_metrics;
                                }
                        }
-               }
+               } 
 
                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));
                }
+
+               if (next_metric == metrics.end()) {
+                       /* no more metrics - we've timestamped them all, stop here */
+                       if (end == max_framepos) {
+                               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("stop extending map now that we've reach the end @ %1|%2 = %3\n",
+                                                                              current.bars, current.beats, current_frame));
+                               break;
+                       }
+               }
        }
 }
 
@@ -1024,8 +1052,6 @@ TempoMap::metric_at (framepos_t frame) const
 
        for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
 
-               // cerr << "Looking at a metric section " << **i << endl;
-
                if ((*i)->frame() > frame) {
                        break;
                }
@@ -1040,7 +1066,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;
 }
 
@@ -1086,6 +1111,15 @@ TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
        require_map_to (frame);
 
        Glib::RWLock::ReaderLock lm (lock);
+
+       if (frame < 0) {
+               bbt.bars = 1;
+               bbt.beats = 1;
+               bbt.ticks = 0;
+               warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg;
+               return;
+       }
+
        return bbt_time (frame, bbt, bbt_before_or_at (frame));
 }
 
@@ -1124,6 +1158,15 @@ TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_i
 framepos_t
 TempoMap::frame_time (const BBT_Time& bbt)
 {
+       if (bbt.bars < 1) {
+               warning << string_compose (_("tempo map asked for frame time at bar < 1  (%1)\n"), bbt) << endmsg;
+               return 0;
+       }
+       
+       if (bbt.beats < 1) {
+               throw std::logic_error ("beats are counted from one");
+       }
+
        require_map_to (bbt);
 
        Glib::RWLock::ReaderLock lm (lock);
@@ -1142,18 +1185,15 @@ TempoMap::frame_time (const BBT_Time& bbt)
 framecnt_t
 TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
 {
-       Glib::RWLock::ReaderLock lm (lock);
-       framecnt_t frames = 0;
        BBT_Time when;
-
        bbt_time (pos, when);
-       frames = bbt_duration_at_unlocked (when, bbt,dir);
-
-       return frames;
+       
+       Glib::RWLock::ReaderLock lm (lock);
+       return bbt_duration_at_unlocked (when, bbt, dir);
 }
 
 framecnt_t
-TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int dir
+TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int /*dir*/
 {
        if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) {
                return 0;
@@ -1345,7 +1385,8 @@ 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:
@@ -1452,9 +1493,11 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
 
                        BBTPointList::const_iterator prev = fi;
                        BBTPointList::const_iterator next = fi;
-                       if (prev != _map.begin()) {
-                               --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 ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) {
@@ -1555,7 +1598,6 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                XMLNodeConstIterator niter;
                Metrics old_metrics (metrics);
                MeterSection* last_meter = 0;
-
                metrics.clear();
 
                nlist = node.children();
@@ -1603,7 +1645,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                        metrics.sort (cmp);
                }
 
-               recompute_map (true);
+               recompute_map (true, -1);
        }
 
        PropertyChanged (PropertyChange ());
@@ -1762,7 +1804,7 @@ TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const
 {
        Glib::RWLock::ReaderLock lm (lock);
        Metrics::const_iterator next_tempo;
-       const TempoSection* tempo;
+       const TempoSection* tempo = 0;
 
        /* Find the starting tempo metric */
 
@@ -1845,7 +1887,7 @@ TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const
        return pos;
 }
 
-/** Subtract some (fractional) beats to a frame position, and return the result in frames */
+/** Subtract some (fractional) beats from a frame position, and return the result in frames */
 framepos_t
 TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const
 {
@@ -1965,6 +2007,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
        const TempoSection* tempo;
        const TempoSection* t;
        double frames_per_beat;
+       framepos_t effective_pos = max (pos, (framepos_t) 0);
 
        meter = &first_meter ();
        tempo = &first_tempo ();
@@ -1976,7 +2019,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 
        for (i = metrics.begin(); i != metrics.end(); ++i) {
 
-               if ((*i)->frame() > pos) {
+               if ((*i)->frame() > effective_pos) {
                        break;
                }
 
@@ -2097,8 +2140,9 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 {
        Glib::RWLock::ReaderLock lm (lock);
        Metrics::const_iterator next_tempo;
-       const TempoSection* tempo;
-       
+       const TempoSection* tempo = 0;
+       framepos_t effective_pos = max (pos, (framepos_t) 0);
+
        /* Find the relevant initial tempo metric  */
 
        for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
@@ -2107,7 +2151,7 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 
                if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
 
-                       if ((*next_tempo)->frame() > pos) {
+                       if ((*next_tempo)->frame() > effective_pos) {
                                break;
                        }
 
@@ -2121,24 +2165,44 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
           next_tempo -> the next tempo after "pos", possibly metrics.end()
        */
 
+       assert (tempo);
+
+       DEBUG_TRACE (DEBUG::TempoMath, string_compose ("frame %1 walk by %2 frames, start with tempo = %3 @ %4\n",
+                                                      pos, distance, *((Tempo*)tempo), tempo->frame()));
+       
        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;
+               framepos_t end;
+               /* Distance to `end' in frames */
+               framepos_t distance_to_end;
+
+               if (next_tempo == metrics.end ()) {
+                       /* We can't do (end - pos) if end is max_framepos, as it will overflow if pos is -ve */
+                       end = max_framepos;
+                       distance_to_end = max_framepos;
+               } else {
+                       end = (*next_tempo)->frame ();
+                       distance_to_end = end - pos;
+               }
 
                /* Amount to subtract this time */
                double const sub = min (distance, distance_to_end);
 
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("to reach end at %1 (end ? %2), distance= %3 sub=%4\n", end, (next_tempo == metrics.end()),
+                                                              distance_to_end, sub));
+
                /* Update */
                pos += sub;
                distance -= sub;
+               assert (tempo);
                beats += sub / tempo->frames_per_beat (_frame_rate);
-               
+
+               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1, beats = %2 distance left %3\n",
+                                                              pos, beats, distance));
+
                /* Move on if there's anything to move to */
 
                if (next_tempo != metrics.end()) {
@@ -2157,7 +2221,16 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
                                        break;
                                }
                        }
+
+                       if (next_tempo == metrics.end()) {
+                               DEBUG_TRACE (DEBUG::TempoMath, "no more tempo sections\n");
+                       } else {
+                               DEBUG_TRACE (DEBUG::TempoMath, string_compose ("next tempo section is %1 @ %2\n",
+                                                                              **next_tempo, (*next_tempo)->frame()));
+                       }
+
                }
+               assert (tempo);
        }
 
        return beats;
@@ -2170,6 +2243,13 @@ TempoMap::bbt_before_or_at (framepos_t pos)
 
        BBTPointList::const_iterator i;
 
+       if (pos < 0) {
+               /* not really correct, but we should catch pos < 0 at a higher
+                  level 
+               */
+               return _map.begin();
+       }
+
        i = lower_bound (_map.begin(), _map.end(), pos);
        assert (i != _map.end());
        if ((*i).frame > pos) {
@@ -2219,38 +2299,6 @@ TempoMap::bbt_after_or_at (framepos_t pos)
        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
-{
-       if (start() == other.start()) {
-               return 0;
-       } else if (start() < other.start()) {
-               return -1;
-       } else {
-               return 1;
-       }
-
-       /* NOTREACHED */
-       return 0;
-}
-
-bool
-MetricSection::operator== (const MetricSection& other) const
-{
-       return compare (other) == 0;
-}
-
-bool
-MetricSection::operator!= (const MetricSection& other) const
-{
-       return compare (other) != 0;
-}
-
 std::ostream& 
 operator<< (std::ostream& o, const Meter& m) {
        return o << m.divisions_per_bar() << '/' << m.note_divisor();