Round to bar correctly in TempoMap::exact_beat_at_frame_locked().
[ardour.git] / libs / ardour / tempo.cc
index cc423ef4a5b71488931c9c3e12a0aa272c9c4efb..35a19b13db1119fa1dc0fe1909e13800a8739333 100644 (file)
@@ -33,7 +33,7 @@
 #include "ardour/lmath.h"
 #include "ardour/tempo.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 #include <locale.h>
 
 using namespace std;
@@ -179,13 +179,13 @@ TempoSection::get_state() const
        char buf[256];
        LocaleGuard lg;
 
-       snprintf (buf, sizeof (buf), "%f", pulse());
+       snprintf (buf, sizeof (buf), "%lf", pulse());
        root->add_property ("pulse", buf);
        snprintf (buf, sizeof (buf), "%li", frame());
        root->add_property ("frame", buf);
-       snprintf (buf, sizeof (buf), "%f", _beats_per_minute);
+       snprintf (buf, sizeof (buf), "%lf", _beats_per_minute);
        root->add_property ("beats-per-minute", buf);
-       snprintf (buf, sizeof (buf), "%f", _note_type);
+       snprintf (buf, sizeof (buf), "%lf", _note_type);
        root->add_property ("note-type", buf);
        snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
        root->add_property ("movable", buf);
@@ -569,12 +569,12 @@ MeterSection::get_state() const
        root->add_property ("bbt", buf);
        snprintf (buf, sizeof (buf), "%lf", beat());
        root->add_property ("beat", buf);
-       snprintf (buf, sizeof (buf), "%f", _note_type);
+       snprintf (buf, sizeof (buf), "%lf", _note_type);
        root->add_property ("note-type", buf);
        snprintf (buf, sizeof (buf), "%li", frame());
        root->add_property ("frame", buf);
        root->add_property ("lock-style", enum_2_string (position_lock_style()));
-       snprintf (buf, sizeof (buf), "%f", _divisions_per_bar);
+       snprintf (buf, sizeof (buf), "%lf", _divisions_per_bar);
        root->add_property ("divisions-per-bar", buf);
        snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
        root->add_property ("movable", buf);
@@ -772,13 +772,13 @@ TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
 bool
 TempoMap::remove_meter_locked (const MeterSection& meter)
 {
-       Metrics::iterator i;
 
-       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
-               TempoSection* t = 0;
-               if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
-                       if (meter.frame() == (*i)->frame()) {
-                               if (t->locked_to_meter()) {
+       if (meter.position_lock_style() == AudioTime) {
+               /* remove meter-locked tempo */
+               for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+                       TempoSection* t = 0;
+                       if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+                               if (t->locked_to_meter() && meter.frame() == (*i)->frame()) {
                                        delete (*i);
                                        _metrics.erase (i);
                                        break;
@@ -787,7 +787,7 @@ TempoMap::remove_meter_locked (const MeterSection& meter)
                }
        }
 
-       for (i = _metrics.begin(); i != _metrics.end(); ++i) {
+       for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
                if (dynamic_cast<MeterSection*> (*i) != 0) {
                        if (meter.frame() == (*i)->frame()) {
                                if ((*i)->movable()) {
@@ -980,18 +980,23 @@ TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, framepos_t frame
 {
        TempoSection* t = new TempoSection (pulse, frame, tempo.beats_per_minute(), tempo.note_type(), type, pls);
        t->set_locked_to_meter (locked_to_meter);
+       bool solved = false;
 
        do_insert (t);
 
        if (recompute) {
                if (pls == AudioTime) {
-                       solve_map_frame (_metrics, t, t->frame());
+                       solved = solve_map_frame (_metrics, t, t->frame());
                } else {
-                       solve_map_pulse (_metrics, t, t->pulse());
+                       solved = solve_map_pulse (_metrics, t, t->pulse());
                }
                recompute_meters (_metrics);
        }
 
+       if (!solved && recompute) {
+               recompute_map (_metrics);
+       }
+
        return t;
 }
 
@@ -1046,29 +1051,49 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T
 }
 
 MeterSection*
-TempoMap::add_meter_locked (const Meter& meter, double beat, const Timecode::BBT_Time& where, framepos_t frame, PositionLockStyle pls, bool recompute)
+TempoMap::add_meter_locked (const Meter& meter, double beat, const BBT_Time& where, framepos_t frame, PositionLockStyle pls, bool recompute)
 {
        const MeterSection& prev_m = meter_section_at_frame_locked  (_metrics, frame - 1);
        const double pulse = ((where.bars - prev_m.bbt().bars) * (prev_m.divisions_per_bar() / prev_m.note_divisor())) + prev_m.pulse();
+       TempoSection* mlt = 0;
 
        if (pls == AudioTime) {
                /* add meter-locked tempo */
-               add_tempo_locked (tempo_at_frame_locked (_metrics, frame), pulse,  frame, TempoSection::Ramp, AudioTime, true, true);
+               mlt = add_tempo_locked (tempo_at_frame_locked (_metrics, frame), pulse,  frame, TempoSection::Ramp, AudioTime, true, true);
+
+               if (!mlt) {
+                       return 0;
+               }
+
        }
 
        MeterSection* new_meter = new MeterSection (pulse, frame, beat, where, meter.divisions_per_bar(), meter.note_divisor(), pls);
+       bool solved = false;
 
        do_insert (new_meter);
 
        if (recompute) {
 
                if (pls == AudioTime) {
-                       solve_map_frame (_metrics, new_meter, frame);
+                       solved = solve_map_frame (_metrics, new_meter, frame);
                } else {
-                       solve_map_bbt (_metrics, new_meter, where);
+                       solved = solve_map_bbt (_metrics, new_meter, where);
+                       /* required due to resetting the pulse of meter-locked tempi above.
+                          Arguably  solve_map_bbt() should use solve_map_pulse (_metrics, TempoSection) instead,
+                          but afaict this cannot cause the map to be left unsolved (these tempi are all audio locked).
+                       */
+                       recompute_map (_metrics);
                }
        }
 
+       if (!solved && recompute) {
+               /* if this has failed to solve, there is little we can do other than to ensure that
+                  the new map is recalculated.
+               */
+               warning << "Adding meter may have left the tempo map unsolved." << endmsg;
+               recompute_map (_metrics);
+       }
+
        return new_meter;
 }
 
@@ -1224,7 +1249,7 @@ TempoMap::first_tempo ()
        return *t;
 }
 void
-TempoMap::recompute_tempos (Metrics& metrics)
+TempoMap::recompute_tempi (Metrics& metrics)
 {
        TempoSection* prev_t = 0;
 
@@ -1364,7 +1389,7 @@ TempoMap::recompute_map (Metrics& metrics, framepos_t end)
                return;
        }
 
-       recompute_tempos (metrics);
+       recompute_tempi (metrics);
        recompute_meters (metrics);
 }
 
@@ -1428,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
 {
@@ -1435,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
 {
@@ -1452,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 */
@@ -1830,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
 {
@@ -1944,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);
@@ -1958,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;
        }
 
@@ -2047,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
 {
@@ -2089,10 +2177,17 @@ TempoMap::check_solved (const Metrics& metrics) const
                        m = static_cast<MeterSection*> (*i);
                        if (prev_m && m->position_lock_style() == AudioTime) {
                                const TempoSection* t = &tempo_section_at_frame_locked (metrics, m->frame() - 1);
-                               const double nascent_m_pulse = ((m->beat() - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse();
-                               const framepos_t nascent_m_frame = t->frame_at_pulse (nascent_m_pulse, _frame_rate);
-
-                               if (t && (nascent_m_frame > m->frame() || nascent_m_frame < 0)) {
+                               const framepos_t nascent_m_frame = t->frame_at_pulse (m->pulse(), _frame_rate);
+                               /* Here we check that a preceding section of music doesn't overlap a subsequent one.
+                                  It is complicated by the fact that audio locked meters represent a discontinuity in the pulse
+                                  (they place an exact pulse at a particular time expressed only in frames).
+                                  This has the effect of shifting the calculated frame at the meter pulse (wrt the previous section of music)
+                                  away from its actual frame (which is now the frame location of the exact pulse).
+                                  This can result in the calculated frame (from the previous musical section)
+                                  differing from the exact frame by one sample.
+                                  Allow for that.
+                               */
+                               if (t && (nascent_m_frame > m->frame() + 1 || nascent_m_frame < 0)) {
                                        return false;
                                }
                        }
@@ -2192,7 +2287,7 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram
        }
 
 #if (0)
-       recompute_tempos (imaginary);
+       recompute_tempi (imaginary);
 
        if (check_solved (imaginary)) {
                return true;
@@ -2204,7 +2299,7 @@ TempoMap::solve_map_frame (Metrics& imaginary, TempoSection* section, const fram
        MetricSectionFrameSorter fcmp;
        imaginary.sort (fcmp);
 
-       recompute_tempos (imaginary);
+       recompute_tempi (imaginary);
 
        if (check_solved (imaginary)) {
                return true;
@@ -2258,7 +2353,7 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub
        }
 
 #if (0)
-       recompute_tempos (imaginary);
+       recompute_tempi (imaginary);
 
        if (check_solved (imaginary)) {
                return true;
@@ -2270,7 +2365,7 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub
        MetricSectionSorter cmp;
        imaginary.sort (cmp);
 
-       recompute_tempos (imaginary);
+       recompute_tempi (imaginary);
        /* Reordering
         * XX need a restriction here, but only for this case,
         * as audio locked tempos don't interact in the same way.
@@ -2474,7 +2569,7 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti
 
                                for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) {
                                        TempoSection* t;
-                                       if ((*i)->is_tempo()) {
+                                       if ((*ii)->is_tempo()) {
                                                t = static_cast<TempoSection*> (*ii);
                                                if ((t->locked_to_meter() || !t->movable()) && t->frame() == m->frame()) {
                                                        meter_locked_tempo = t;
@@ -2740,7 +2835,7 @@ TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame)
 
                        if (solve_map_frame (future_map, copy, frame)) {
                                solve_map_frame (_metrics, ms, frame);
-                               recompute_tempos (_metrics);
+                               recompute_tempi (_metrics);
                        }
                }
        } else {
@@ -2753,7 +2848,7 @@ TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame)
 
                        if (solve_map_bbt (future_map, copy, bbt)) {
                                solve_map_bbt (_metrics, ms, bbt);
-                               recompute_tempos (_metrics);
+                               recompute_tempi (_metrics);
                        }
                }
        }
@@ -2776,7 +2871,7 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm)
                Glib::Threads::RWLock::WriterLock lm (lock);
                TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
                tempo_copy->set_beats_per_minute (bpm.beats_per_minute());
-               recompute_tempos (future_map);
+               recompute_tempi (future_map);
 
                if (check_solved (future_map)) {
                        ts->set_beats_per_minute (bpm.beats_per_minute());
@@ -2925,12 +3020,12 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
                }
                new_bpm = min (new_bpm, (double) 1000.0);
                prev_t->set_beats_per_minute (new_bpm);
-               recompute_tempos (future_map);
+               recompute_tempi (future_map);
                recompute_meters (future_map);
 
                if (check_solved (future_map)) {
                        ts->set_beats_per_minute (new_bpm);
-                       recompute_tempos (_metrics);
+                       recompute_tempi (_metrics);
                        recompute_meters (_metrics);
                }
        }
@@ -2944,8 +3039,17 @@ 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)
+TempoMap::exact_beat_at_frame (const framepos_t& frame, const int32_t sub_num)
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
@@ -2953,9 +3057,10 @@ TempoMap::exact_beat_at_frame (const framepos_t& frame, const int32_t& sub_num)
 }
 
 double
-TempoMap::exact_beat_at_frame_locked (const Metrics& metrics, const framepos_t& frame, const int32_t& sub_num)
+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) {
@@ -2966,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;
 }
 
@@ -2976,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;
 
@@ -2999,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;
 
@@ -3096,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) {
@@ -3438,6 +3553,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                                catch (failed_constructor& err){
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
                                        _metrics = old_metrics;
+                                       old_metrics.clear();
                                        break;
                                }
 
@@ -3451,6 +3567,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                                catch (failed_constructor& err) {
                                        error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
                                        _metrics = old_metrics;
+                                       old_metrics.clear();
                                        break;
                                }
                        }
@@ -3502,6 +3619,13 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
                }
 
                recompute_map (_metrics);
+
+               Metrics::const_iterator d = old_metrics.begin();
+               while (d != old_metrics.end()) {
+                       delete (*d);
+                       ++d;
+               }
+               old_metrics.clear ();
        }
 
        PropertyChanged (PropertyChange ());
@@ -3673,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;
@@ -3757,11 +3881,11 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount)
  *  pos can be -ve, if required.
  */
 framepos_t
-TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const
+TempoMap::framepos_plus_beats (framepos_t frame, Evoral::Beats beats) const
 {
        Glib::Threads::RWLock::ReaderLock lm (lock);
 
-       return frame_at_beat_locked (_metrics, beat_at_frame_locked (_metrics, pos) + 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 */