Round to bar correctly in TempoMap::exact_beat_at_frame_locked().
[ardour.git] / libs / ardour / tempo.cc
index 11da2fc81d3892768dbc2efbd2c17bc439228465..35a19b13db1119fa1dc0fe1909e13800a8739333 100644 (file)
@@ -994,7 +994,6 @@ TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, framepos_t frame
        }
 
        if (!solved && recompute) {
-               warning << "Adding tempo may have left the tempo map unsolved." << endmsg;
                recompute_map (_metrics);
        }
 
@@ -1454,6 +1453,14 @@ TempoMap::metric_at (BBT_Time bbt) const
        return m;
 }
 
+/** Returns the beat duration corresponding to the supplied frame, possibly returning a negative value.
+ * @param frame The session frame position.
+ * @return The beat duration according to the tempo map at the supplied frame.
+ * If the supplied frame lies before the first meter, the returned beat duration will be negative.
+ * The returned beat is obtained using the first meter and the continuation of the tempo curve (backwards).
+ *
+ * This function uses both tempo and meter.
+ */
 double
 TempoMap::beat_at_frame (const framecnt_t& frame) const
 {
@@ -1461,7 +1468,7 @@ TempoMap::beat_at_frame (const framecnt_t& frame) const
        return beat_at_frame_locked (_metrics, frame);
 }
 
-/* meter / tempo section based */
+/* This function uses both tempo and meter.*/
 double
 TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
 {
@@ -1478,9 +1485,7 @@ TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame)
                        prev_m = static_cast<MeterSection*> (*i);
                }
        }
-       if (frame < prev_m->frame()) {
-               return 0.0;
-       }
+
        const double beat = prev_m->beat() + (ts.pulse_at_frame (frame, _frame_rate) - prev_m->pulse()) * prev_m->note_divisor();
 
        /* audio locked meters fake their beat */
@@ -1856,6 +1861,18 @@ TempoMap::pulse_at_bbt (const Timecode::BBT_Time& bbt)
        return pulse_at_bbt_locked (_metrics, bbt);
 }
 
+double
+TempoMap::pulse_at_bbt_rt (const Timecode::BBT_Time& bbt)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock);
+
+       if (!lm.locked()) {
+               throw std::logic_error ("TempoMap::pulse_at_bbt_rt() could not lock tempo map");
+       }
+
+       return pulse_at_bbt_locked (_metrics, bbt);
+}
+
 double
 TempoMap::pulse_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
 {
@@ -1970,7 +1987,7 @@ TempoMap::bbt_at_frame_rt (framepos_t frame)
        Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
 
        if (!lm.locked()) {
-               throw std::logic_error ("TempoMap::bbt_time_rt() could not lock tempo map");
+               throw std::logic_error ("TempoMap::bbt_at_frame_rt() could not lock tempo map");
        }
 
        return bbt_at_frame_locked (_metrics, frame);
@@ -1984,7 +2001,6 @@ TempoMap::bbt_at_frame_locked (const Metrics& metrics, const framepos_t& frame)
                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 bbt;
        }
 
@@ -2073,6 +2089,52 @@ TempoMap::frame_at_bbt_locked (const Metrics& metrics, const BBT_Time& bbt) cons
        return ret;
 }
 
+/**
+ * Returns the distance from 0 in quarter pulses at the supplied frame.
+ *
+ * Plugin APIs don't count ticks in the same way PROGRAM_NAME does.
+ * We use ticks per beat whereas the rest of the world uses ticks per quarter note.
+ * This is more or less the VST's ppqPos (a scalar you use to obtain tick position
+ * in whatever ppqn you're using).
+ *
+ * @param frame The distance in frames relative to session 0 whose quarter note distance you would like.
+ * @return The quarter note (quarter pulse) distance from session 0 to the supplied frame. Ignores meter.
+*/
+
+double
+TempoMap::quarter_note_at_frame (const framepos_t frame)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
+
+       const double ret = pulse_at_frame_locked (_metrics, frame) * 4.0;
+
+       return ret;
+}
+
+double
+TempoMap::quarter_note_at_frame_rt (const framepos_t frame)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
+
+       if (!lm.locked()) {
+               throw std::logic_error ("TempoMap::quarter_note_at_frame_rt() could not lock tempo map");
+       }
+
+       const double ret = pulse_at_frame_locked (_metrics, frame) * 4.0;
+
+       return ret;
+}
+
+framepos_t
+TempoMap::frame_at_quarter_note (const double quarter_note)
+{
+       Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
+
+       const framepos_t ret = frame_at_pulse_locked (_metrics, quarter_note / 4.0);
+
+       return ret;
+}
+
 bool
 TempoMap::check_solved (const Metrics& metrics) const
 {
@@ -2977,6 +3039,15 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
        MetricPositionChanged (); // Emit Signal
 }
 
+/** Returns the exact beat subdivision closest to the supplied frame, possibly returning a negative value.
+ * @param frame  The session frame position.
+ * @param sub_num The requested beat subdivision to use when rounding the frame position.
+ * @return The beat position of the supplied frame.
+ * If the supplied frame lies before the first meter, the return will be negative.
+ * The returned beat is obtained using the first meter and the continuation of the tempo curve (backwards).
+ *
+ * This function uses both tempo and meter.
+ */
 double
 TempoMap::exact_beat_at_frame (const framepos_t& frame, const int32_t sub_num)
 {
@@ -2989,6 +3060,7 @@ double
 TempoMap::exact_beat_at_frame_locked (const Metrics& metrics, const framepos_t& frame, const int32_t sub_num)
 {
        double beat = beat_at_frame_locked (metrics, frame);
+
        if (sub_num > 1) {
                beat = floor (beat) + (floor (((beat - floor (beat)) * (double) sub_num) + 0.5) / sub_num);
        } else if (sub_num == 1) {
@@ -2999,8 +3071,18 @@ TempoMap::exact_beat_at_frame_locked (const Metrics& metrics, const framepos_t&
                Timecode::BBT_Time bbt = bbt_at_beat_locked (metrics, beat);
                bbt.beats = 1;
                bbt.ticks = 0;
-               beat = beat_at_bbt_locked (metrics, bbt);
+
+               const double prev_b = beat_at_bbt_locked (_metrics, bbt);
+               ++bbt.bars;
+               const double next_b = beat_at_bbt_locked (_metrics, bbt);
+
+               if ((beat - prev_b) > (next_b - prev_b) / 2.0) {
+                       beat = next_b;
+               } else {
+                       beat = prev_b;
+               }
        }
+
        return beat;
 }
 
@@ -3009,7 +3091,7 @@ TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       const double tick_at_time = beat_at_frame_locked (_metrics, pos) * BBT_Time::ticks_per_beat;
+       const double tick_at_time = max (0.0, beat_at_frame_locked (_metrics, pos)) * BBT_Time::ticks_per_beat;
        const double bbt_ticks = bbt.ticks + (bbt.beats * BBT_Time::ticks_per_beat);
        const double total_beats = (tick_at_time + bbt_ticks) / BBT_Time::ticks_per_beat;
 
@@ -3032,7 +3114,7 @@ framepos_t
 TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
-       uint32_t ticks = (uint32_t) floor (beat_at_frame_locked (_metrics, fr) * BBT_Time::ticks_per_beat);
+       uint32_t ticks = (uint32_t) floor (max (0.0, beat_at_frame_locked (_metrics, fr)) * BBT_Time::ticks_per_beat);
        uint32_t beats = (uint32_t) floor (ticks / BBT_Time::ticks_per_beat);
        uint32_t ticks_one_subdivisions_worth = (uint32_t) BBT_Time::ticks_per_beat / sub_num;
 
@@ -3129,7 +3211,7 @@ TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       const double beat_at_framepos = beat_at_frame_locked (_metrics, frame);
+       const double beat_at_framepos = max (0.0, beat_at_frame_locked (_metrics, frame));
        BBT_Time bbt (bbt_at_beat_locked (_metrics, beat_at_framepos));
 
        switch (type) {
@@ -3715,7 +3797,7 @@ TempoMap::insert_time (framepos_t where, framecnt_t amount)
                                                bbt.beats = 1;
                                        }
                                }
-                               pair<double, BBT_Time> start = make_pair (beat_at_frame_locked (_metrics, m->frame()), bbt);
+                               pair<double, BBT_Time> start = make_pair (max (0.0, beat_at_frame_locked (_metrics, m->frame())), bbt);
                                m->set_beat (start);
                                m->set_pulse (pulse_at_frame_locked (_metrics, m->frame()));
                                meter = m;
@@ -3803,28 +3885,7 @@ TempoMap::framepos_plus_beats (framepos_t frame, Evoral::Beats beats) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       const TempoSection& ts = tempo_section_at_frame_locked (_metrics, frame);
-       MeterSection* prev_m = 0;
-       MeterSection* next_m = 0;
-
-       for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
-               if (!(*i)->is_tempo()) {
-                       if (prev_m && (*i)->frame() > frame) {
-                               next_m = static_cast<MeterSection*> (*i);
-                               break;
-                       }
-                       prev_m = static_cast<MeterSection*> (*i);
-               }
-       }
-
-       double pos_beat = prev_m->beat() + (ts.pulse_at_frame (frame, _frame_rate) - prev_m->pulse()) * prev_m->note_divisor();
-
-       /* audio locked meters fake their beat */
-       if (next_m && next_m->beat() < pos_beat) {
-               pos_beat = next_m->beat();
-       }
-
-       return frame_at_beat_locked (_metrics, pos_beat + beats.to_double());
+       return frame_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, frame) + beats.to_double());
 }
 
 /** Subtract some (fractional) beats from a frame position, and return the result in frames */