Add fixed-up framepos_plus_beats() and use it for the BeatsFramesConverter, since...
[ardour.git] / libs / ardour / tempo.cc
index e1ef6f2685f2b1335c7e527eddab1b5a7218bac3..97ec1224f0101501ae902569cd05389a7885c500 100644 (file)
@@ -24,9 +24,9 @@
 
 #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"
@@ -368,6 +368,42 @@ TempoMap::remove_meter (const MeterSection& tempo)
 void
 TempoMap::do_insert (MetricSection* section, bool with_bbt)
 {
+       /* First of all, check to see if the new MetricSection is in the
+          middle of a bar.  If so, we need to fix the bar that we are in
+          to have a different meter.
+       */
+
+       assert (section->start().ticks == 0);
+
+       if (section->start().beats != 1) {
+
+               /* Here's the tempo and metric where we are proposing to insert `section' */
+               TempoMetric tm = metric_at (section->start ());
+
+               /* This is where we will put the `corrective' new meter; at the start of
+                  the bar that we are inserting into the middle of.
+               */
+               BBT_Time where_correction = section->start();
+               where_correction.beats = 1;
+               where_correction.ticks = 0;
+
+               /* Put in the meter change to make the bar before our `section' the right
+                  length.
+               */
+               do_insert (new MeterSection (where_correction, section->start().beats, tm.meter().note_divisor ()), true);
+
+               /* This is where the new stuff will now go; the start of the next bar
+                  (after the one whose meter we just fixed).
+               */
+               BBT_Time where_new (where_correction.bars + 1, 1, 0);
+
+               /* Change back to the original meter */
+               do_insert (new MeterSection (where_new, tm.meter().beats_per_bar(), tm.meter().note_divisor()), true);
+
+               /* And set up `section' for where it should be, ready to be inserted */
+               section->set_start (where_new);
+       }
+
        Metrics::iterator i;
 
        /* Look for any existing MetricSection that is of the same type and
@@ -831,8 +867,6 @@ TempoMap::bbt_time_with_metric (framepos_t frame, BBT_Time& bbt, const TempoMetr
 {
        framecnt_t frame_diff;
 
-       // 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 ticks_per_frame = metric.tempo().frames_per_beat (_frame_rate, metric.meter()) / BBT_Time::ticks_per_beat;
 
@@ -892,7 +926,7 @@ TempoMap::count_frames_between (const BBT_Time& start, const BBT_Time& end) cons
                + start.ticks/BBT_Time::ticks_per_beat;
 
 
-       start_frame = m.frame() + (framepos_t) rint( beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter()));
+       start_frame = m.frame() + (framepos_t) rint(beat_offset * m.tempo().frames_per_beat(_frame_rate, m.meter()));
 
        m =  metric_at(end);
 
@@ -964,7 +998,7 @@ TempoMap::frame_time (const BBT_Time& bbt) const
 {
        BBT_Time start ; /* 1|1|0 */
 
-       return  count_frames_between ( start, bbt);
+       return count_frames_between (start, bbt);
 }
 
 framecnt_t
@@ -1015,7 +1049,7 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
                        beats_per_bar = metric.meter().beats_per_bar();
 
                }
-               
+
                /* We now counted the beats and landed in the target measure, now deal
                  with ticks this seems complicated, but we want to deal with the
                  corner case of a sequence of time signatures like 0.2/4-0.7/4 and
@@ -1026,7 +1060,7 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
                /* of course gtk_ardour only allows bar with at least 1.0 beats .....
                 */
 
-               uint32_t ticks_at_beat = (uint32_t) ( result.beats == ceil(beats_per_bar) ?
+               uint32_t ticks_at_beat = (uint32_t) (result.beats == ceil(beats_per_bar) ?
                                        (1 - (ceil(beats_per_bar) - beats_per_bar))* BBT_Time::ticks_per_beat
                                           : BBT_Time::ticks_per_beat );
 
@@ -1039,10 +1073,9 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
                                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) ?
+                       ticks_at_beat= (uint32_t) (result.beats == ceil(beats_per_bar) ?
                                       (1 - (ceil(beats_per_bar) - beats_per_bar) ) * BBT_Time::ticks_per_beat
                                       : BBT_Time::ticks_per_beat);
-
                }
 
 
@@ -1050,13 +1083,12 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
                uint32_t b = bbt.beats;
 
                /* count beats */
-               while( b > when.beats ) {
+               while (b > when.beats) {
                        --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();
                        if (b >= ceil(beats_per_bar)) {
-
                                b -= (uint32_t) ceil(beats_per_bar);
                        } else {
                                b = (uint32_t) ceil(beats_per_bar) - b + when.beats ;
@@ -1099,8 +1131,8 @@ TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, i
 
        }
 
-       if (dir < 0 ) {
-               frames = count_frames_between( result,when);
+       if (dir < 0) {
+               frames = count_frames_between(result, when);
        } else {
                frames = count_frames_between(when,result);
        }
@@ -1177,7 +1209,7 @@ TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, int dir)
                        difference = mod;
                }
 
-               try { 
+               try {
                        the_beat = bbt_subtract (the_beat, BBT_Time (0, 0, difference));
                } catch (...) {
                        /* can't go backwards from wherever pos is, so just return it */
@@ -1248,7 +1280,7 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
                        midbar_beats = metric.meter().beats_per_bar() / 2 + 1;
                        midbar_ticks = BBT_Time::ticks_per_beat * fmod (midbar_beats, 1.0f);
                        midbar_beats = floor (midbar_beats);
-                       
+
                        BBT_Time midbar (bbt.bars, lrintf (midbar_beats), lrintf (midbar_ticks));
 
                        if (bbt < midbar) {
@@ -1275,7 +1307,7 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
                        /* find beat position preceding frame */
 
                        try {
-                               bbt = bbt_subtract (bbt, one_beat); 
+                               bbt = bbt_subtract (bbt, one_beat);
                        }
 
                        catch (...) {
@@ -1425,7 +1457,7 @@ TempoMap::get_points (framepos_t lower, framepos_t upper) const
 
                        beat_frame = current;
 
-                       while (beat <= ceil( beats_per_bar) && beat_frame < limit) {
+                       while (beat <= ceil(beats_per_bar) && beat_frame < limit) {
                                if (beat_frame >= lower) {
                                        // cerr << "Add Beat at " << bar << '|' << beat << " @ " << beat_frame << endl;
                                        points->push_back (BBTPoint (*meter, *tempo, (framepos_t) rint(beat_frame), Beat, bar, beat));
@@ -1674,7 +1706,7 @@ void
 TempoMap::insert_time (framepos_t where, framecnt_t amount)
 {
        for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
-               if ((*i)->frame() >= where) {
+               if ((*i)->frame() >= where && (*i)->movable ()) {
                        (*i)->set_frame ((*i)->frame() + amount);
                }
        }
@@ -1711,30 +1743,30 @@ TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& increment, const Tempo
        /* now comes the complicated part. we have to add one beat a time,
           checking for a new metric on every beat.
        */
-       
+
        /* grab all meter sections */
-       
+
        list<const MeterSection*> meter_sections;
-       
+
        for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) {
                const MeterSection* ms;
                if ((ms = dynamic_cast<const MeterSection*>(*x)) != 0) {
                        meter_sections.push_back (ms);
                }
        }
-       
+
        assert (!meter_sections.empty());
-       
+
        list<const MeterSection*>::const_iterator next_meter;
        const Meter* meter = 0;
-       
+
        /* go forwards through the meter sections till we get to the one
-          covering the current value of result. this positions i to point to 
+          covering the current value of result. this positions i to point to
           the next meter section too, or the end.
        */
-       
+
        for (next_meter = meter_sections.begin(); next_meter != meter_sections.end(); ++next_meter) {
-               
+
                if (result < (*next_meter)->start()) {
                        /* this metric is past the result time. stop looking, we have what we need */
                        break;
@@ -1748,21 +1780,21 @@ TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& increment, const Tempo
                        ++next_meter;
                        break;
                }
-               
+
                meter = *next_meter;
        }
-       
+
        assert (meter != 0);
-               
-       /* OK, now have the meter for the bar start we are on, and i is an iterator 
-          that points to the metric after the one we are currently dealing with 
-          (or to metrics->end(), of course) 
+
+       /* OK, now have the meter for the bar start we are on, and i is an iterator
+          that points to the metric after the one we are currently dealing with
+          (or to metrics->end(), of course)
        */
-       
+
        while (op.beats) {
-               
+
                /* given the current meter, have we gone past the end of the bar ? */
-               
+
                if (result.beats >= meter->beats_per_bar()) {
                        /* move to next bar, first beat */
                        result.bars++;
@@ -1770,12 +1802,12 @@ TempoMap::bbt_add (const BBT_Time& start, const BBT_Time& increment, const Tempo
                } else {
                        result.beats++;
                }
-               
+
                /* one down ... */
-               
+
                op.beats--;
-               
-               /* check if we need to use a new meter section: has adding beats to result taken us 
+
+               /* check if we need to use a new meter section: has adding beats to result taken us
                   to or after the start of the next meter section? in which case, use it.
                */
 
@@ -1812,35 +1844,35 @@ TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const
        /* now comes the complicated part. we have to subtract one beat a time,
           checking for a new metric on every beat.
        */
-       
+
        /* grab all meter sections */
-       
+
        list<const MeterSection*> meter_sections;
-       
+
        for (Metrics::const_iterator x = metrics->begin(); x != metrics->end(); ++x) {
                const MeterSection* ms;
                if ((ms = dynamic_cast<const MeterSection*>(*x)) != 0) {
                        meter_sections.push_back (ms);
                }
                }
-       
+
        assert (!meter_sections.empty());
-       
+
        /* go backwards through the meter sections till we get to the one
-          covering the current value of result. this positions i to point to 
+          covering the current value of result. this positions i to point to
           the next (previous) meter section too, or the end.
        */
-       
+
        const MeterSection* meter = 0;
-       list<const MeterSection*>::reverse_iterator next_meter; // older versions of GCC don't 
+       list<const MeterSection*>::reverse_iterator next_meter; // older versions of GCC don't
                                                                // support const_reverse_iterator::operator!=()
-       
+
        for (next_meter = meter_sections.rbegin(); next_meter != meter_sections.rend(); ++next_meter) {
-               
+
                /* when we find the first meter section that is before or at result, use it,
-                  and set next_meter to the previous one 
+                  and set next_meter to the previous one
                */
-               
+
                if ((*next_meter)->start() < result || (*next_meter)->start() == result) {
                        meter = *next_meter;
                        ++next_meter;
@@ -1849,27 +1881,27 @@ TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const
        }
 
        assert (meter != 0);
-       
-       /* OK, now have the meter for the bar start we are on, and i is an iterator 
-          that points to the metric after the one we are currently dealing with 
-          (or to metrics->end(), of course) 
+
+       /* OK, now have the meter for the bar start we are on, and i is an iterator
+          that points to the metric after the one we are currently dealing with
+          (or to metrics->end(), of course)
        */
-       
+
        while (op.beats) {
 
                /* have we reached the start of the bar? if so, move to the last beat of the previous
                   bar. opwise, just step back 1 beat.
                */
-               
+
                if (result.beats == 1) {
-                       
+
                        /* move to previous bar, last beat */
-                       
+
                        if (result.bars <= 1) {
                                /* i'm sorry dave, i can't do that */
                                throw std::out_of_range ("illegal BBT subtraction");
                        }
-                       
+
                        result.bars--;
                        result.beats = meter->beats_per_bar();
                } else {
@@ -1878,11 +1910,11 @@ TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const
 
                        result.beats--;
                }
-               
+
                /* one down ... */
                op.beats--;
-               
-               /* check if we need to use a new meter section: has subtracting beats to result taken us 
+
+               /* check if we need to use a new meter section: has subtracting beats to result taken us
                   to before the start of the current meter section? in which case, use the prior one.
                */
 
@@ -1903,22 +1935,82 @@ TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const
        return result;
 }
 
+/** Add some (fractional) beats to a frame position, and return the result in frames */
+framepos_t
+TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const
+{
+       Metrics::const_iterator i;
+       const TempoSection* tempo;
+       const MeterSection* meter;
+       
+       /* Find the starting metrics for tempo & meter */
+
+       for (i = metrics->begin(); i != metrics->end(); ++i) {
+
+               if ((*i)->frame() > pos) {
+                       break;
+               }
+
+               const TempoSection* t;
+               const MeterSection* m;
+
+               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+                       tempo = t;
+               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+                       meter = m;
+               }
+       }
+
+       /* We now have:
+
+          meter -> the Meter for "pos"
+          tempo -> the Tempo for "pos"
+          i     -> for first new metric after "pos", possibly metrics->end()
+       */
+
+       while (beats) {
+
+               /* End of this section */
+               framepos_t end = i == metrics->end() ? max_framepos : (*i)->frame ();
+
+               /* Distance to the end in beats */
+               Evoral::MusicalTime distance_beats = (end - pos) / tempo->frames_per_beat (_frame_rate, *meter);
+
+               /* Amount to subtract this time */
+               double const sub = min (distance_beats, beats);
+
+               /* Update */
+               beats -= sub;
+               pos += sub * tempo->frames_per_beat (_frame_rate, *meter);
+
+               /* Move on if there's anything to move to */
+               if (i != metrics->end ()) {
+                       const TempoSection* t;
+                       const MeterSection* m;
+                       
+                       if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+                               tempo = t;
+                       } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+                               meter = m;
+                       }
+
+                       ++i;
+               }
+       }
+
+       return pos;
+}
+
 /** Add the BBT interval op to pos and return the result */
 framepos_t
 TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 {
-       /* XXX: this is a little inaccurate as small errors are introduced
-          every time a probably-fractional product of something and
-          frames_per_beat is rounded.  Other errors can be introduced
-          by op.ticks' integer nature.
-       */
-       
        Metrics::const_iterator i;
        const MeterSection* meter;
        const MeterSection* m;
        const TempoSection* tempo;
        const TempoSection* t;
-       framecnt_t frames_per_beat;
+       double frames_per_beat;
 
        meter = &first_meter ();
        tempo = &first_tempo ();
@@ -1951,14 +2043,16 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
        /* now comes the complicated part. we have to add one beat a time,
           checking for a new metric on every beat.
        */
-       
+
        frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter);
 
+       uint64_t bars = 0;
+
        while (op.bars) {
 
-               pos += llrint (frames_per_beat * meter->beats_per_bar());
+               bars++;
                op.bars--;
-               
+
                /* check if we need to use a new metric section: has adding frames moved us
                   to or after the start of the next metric section? in which case, use it.
                */
@@ -1966,6 +2060,15 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
                if (i != metrics->end()) {
                        if ((*i)->frame() <= pos) {
 
+                               /* about to change tempo or meter, so add the
+                                * number of frames for the bars we've just
+                                * traversed before we change the
+                                * frames_per_beat value.
+                                */
+                               
+                               pos += llrint (frames_per_beat * (bars * meter->beats_per_bar()));
+                               bars = 0;
+
                                if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
                                        tempo = t;
                                } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
@@ -1979,13 +2082,17 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
 
        }
 
+       pos += llrint (frames_per_beat * (bars * meter->beats_per_bar()));
+
+       uint64_t beats = 0;
+
        while (op.beats) {
-               
+
                /* given the current meter, have we gone past the end of the bar ? */
 
-               pos += frames_per_beat;
+               beats++;
                op.beats--;
-               
+
                /* check if we need to use a new metric section: has adding frames moved us
                   to or after the start of the next metric section? in which case, use it.
                */
@@ -1993,21 +2100,33 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
                if (i != metrics->end()) {
                        if ((*i)->frame() <= pos) {
 
+                               /* about to change tempo or meter, so add the
+                                * number of frames for the beats we've just
+                                * traversed before we change the
+                                * frames_per_beat value.
+                                */
+
+                               pos += llrint (beats * frames_per_beat);
+                               beats = 0;
+
                                if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
                                        tempo = t;
                                } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
                                        meter = m;
                                }
                                ++i;
-                               frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); 
+                               frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter);
                        }
                }
        }
 
+       pos += llrint (beats * frames_per_beat);
+
        if (op.ticks) {
                if (op.ticks >= BBT_Time::ticks_per_beat) {
-                       pos += frames_per_beat;
-                       pos += llrint (frames_per_beat * ((op.ticks % (uint32_t) BBT_Time::ticks_per_beat) / (double) BBT_Time::ticks_per_beat));
+                       pos += llrint (frames_per_beat + /* extra beat */
+                                      (frames_per_beat * ((op.ticks % (uint32_t) BBT_Time::ticks_per_beat) / 
+                                                          (double) BBT_Time::ticks_per_beat)));
                } else {
                        pos += llrint (frames_per_beat * (op.ticks / (double) BBT_Time::ticks_per_beat));
                }
@@ -2016,6 +2135,7 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
        return pos;
 }
 
+
 /** Count the number of beats that are equivalent to distance when starting at pos */
 double
 TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
@@ -2062,9 +2182,11 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
        /* now comes the complicated part. we have to add one beat a time,
           checking for a new metric on every beat.
        */
-       
+
        frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter);
 
+       double last_dpos = 0;
+
        while (ddist > 0) {
 
                /* if we're nearly at the end, but have a fractional beat left,
@@ -2078,6 +2200,7 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 
                /* walk one beat */
 
+               last_dpos = dpos;
                ddist -= frames_per_beat;
                dpos += frames_per_beat;
                beats += 1.0;
@@ -2087,7 +2210,34 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
                */
 
                if (i != metrics->end()) {
-                       if ((*i)->frame() <= (framepos_t) dpos) {
+
+                       double const f = (*i)->frame ();
+
+                       if (f <= (framepos_t) dpos) {
+
+                               /* We just went past a tempo/meter section start at (*i)->frame(),
+                                  which will be on a beat.
+
+                                  So what we have is
+
+                                                      (*i)->frame() [f]
+                                  beat      beat      beat                beat
+                                  |         |         |                   |
+                                  |         |         |                   |
+                                                ^         ^
+                                               |         |
+                                               |         new
+                                               |         dpos [q]
+                                               last
+                                               dpos [p]
+
+                                 We need to go back to last_dpos (1 beat ago) and re-add
+                                 (f - p) beats using the old frames per beat and (q - f) beats
+                                 using the new.
+                               */
+
+                               beats -= 1;
+                               beats += (f - last_dpos) / frames_per_beat;
 
                                if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
                                        tempo = t;
@@ -2096,9 +2246,10 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
                                }
                                ++i;
                                frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter);
+
+                               beats += (dpos - f) / frames_per_beat;
                        }
                }
-
        }
 
        return beats;
@@ -2106,7 +2257,7 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 
 
 /** Compare the time of this with that of another MetricSection.
- *  @param with_bbt True to compare using ::start(), false to use ::frame().
+ *  @param with_bbt True to compare using start(), false to use frame().
  *  @return -1 for less than, 0 for equal, 1 for greater than.
  */