Fix unit-test data (XML attributes changed)
[ardour.git] / libs / ardour / region.cc
index 039ecbe691d129670346b2b043dc098f30a90170..9d82ff93956c943deb9892e9b52b19234fbad04c 100644 (file)
@@ -25,6 +25,7 @@
 
 #include <glibmm/threads.h>
 #include "pbd/xml++.h"
+#include "pbd/types_convert.h"
 
 #include "ardour/debug.h"
 #include "ardour/filter.h"
@@ -37,8 +38,9 @@
 #include "ardour/source.h"
 #include "ardour/tempo.h"
 #include "ardour/transient_detector.h"
+#include "ardour/types_convert.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace ARDOUR;
@@ -64,6 +66,7 @@ namespace ARDOUR {
                PBD::PropertyDescriptor<framepos_t> start;
                PBD::PropertyDescriptor<framecnt_t> length;
                PBD::PropertyDescriptor<framepos_t> position;
+               PBD::PropertyDescriptor<double> beat;
                PBD::PropertyDescriptor<framecnt_t> sync_position;
                PBD::PropertyDescriptor<layer_t> layer;
                PBD::PropertyDescriptor<framepos_t> ancestral_start;
@@ -114,6 +117,8 @@ Region::make_property_quarks ()
        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length = %1\n",      Properties::length.property_id));
        Properties::position.property_id = g_quark_from_static_string (X_("position"));
        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for position = %1\n",    Properties::position.property_id));
+       Properties::beat.property_id = g_quark_from_static_string (X_("beat"));
+       DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for beat = %1\n",        Properties::beat.property_id));
        Properties::sync_position.property_id = g_quark_from_static_string (X_("sync-position"));
        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for sync-position = %1\n",       Properties::sync_position.property_id));
        Properties::layer.property_id = g_quark_from_static_string (X_("layer"));
@@ -154,6 +159,7 @@ Region::register_properties ()
        add_property (_start);
        add_property (_length);
        add_property (_position);
+       add_property (_beat);
        add_property (_sync_position);
        add_property (_ancestral_start);
        add_property (_ancestral_length);
@@ -171,7 +177,9 @@ Region::register_properties ()
        , _start (Properties::start, (s))       \
        , _length (Properties::length, (l))     \
        , _position (Properties::position, 0) \
+       , _beat (Properties::beat, 0.0) \
        , _sync_position (Properties::sync_position, (s)) \
+       , _quarter_note (0.0) \
        , _transient_user_start (0) \
        , _transient_analysis_start (0) \
        , _transient_analysis_end (0) \
@@ -200,7 +208,9 @@ Region::register_properties ()
        , _start(Properties::start, other->_start)              \
        , _length(Properties::length, other->_length)           \
        , _position(Properties::position, other->_position)     \
+       , _beat (Properties::beat, other->_beat)                \
        , _sync_position(Properties::sync_position, other->_sync_position) \
+       , _quarter_note (other->_quarter_note)                                \
        , _user_transients (other->_user_transients) \
        , _transient_user_start (other->_transient_user_start) \
        , _transients (other->_transients) \
@@ -286,6 +296,7 @@ Region::Region (boost::shared_ptr<const Region> other)
 
        _start = other->_start;
        _beat = other->_beat;
+       _quarter_note = other->_quarter_note;
 
        /* sync pos is relative to start of file. our start-in-file is now zero,
           so set our sync position to whatever the the difference between
@@ -320,14 +331,13 @@ Region::Region (boost::shared_ptr<const Region> other)
     the start within \a other is given by \a offset
     (i.e. relative to the start of \a other's sources, the start is \a offset + \a other.start()
 */
-Region::Region (boost::shared_ptr<const Region> other, frameoffset_t offset, const int32_t sub_num)
+Region::Region (boost::shared_ptr<const Region> other, MusicFrame offset)
        : SessionObject(other->session(), other->name())
        , _type (other->data_type())
        , REGION_COPY_STATE (other)
        , _last_length (other->_last_length)
        , _last_position(other->_last_position) \
        , _first_edit (EditChangesNothing)
-       , _beat (0.0)
        , _layer (other->_layer)
 {
        register_properties ();
@@ -335,7 +345,6 @@ Region::Region (boost::shared_ptr<const Region> other, frameoffset_t offset, con
        /* override state that may have been incorrectly inherited from the other region
         */
 
-       _position = other->_position + offset;
        _locked = false;
        _whole_file = false;
        _hidden = false;
@@ -343,8 +352,19 @@ Region::Region (boost::shared_ptr<const Region> other, frameoffset_t offset, con
        use_sources (other->_sources);
        set_master_sources (other->_master_sources);
 
-       _start = other->_start + offset;
-       _beat = _session.tempo_map().exact_beat_at_frame (_position, sub_num);
+       _position = other->_position + offset.frame;
+       _start = other->_start + offset.frame;
+
+       /* prevent offset of 0 from altering musical position */
+       if (offset.frame != 0) {
+               const double offset_qn = _session.tempo_map().exact_qn_at_frame (other->_position + offset.frame, offset.division)
+                       - other->_quarter_note;
+
+               _quarter_note = other->_quarter_note + offset_qn;
+               _beat = _session.tempo_map().beat_at_quarter_note (_quarter_note);
+       } else {
+               _quarter_note = _session.tempo_map().quarter_note_at_beat (_beat);
+       }
 
        /* if the other region had a distinct sync point
           set, then continue to use it as best we can.
@@ -540,10 +560,6 @@ Region::set_position_lock_style (PositionLockStyle ps)
 
                _position_lock_style = ps;
 
-               if (_position_lock_style == MusicTime) {
-                       _beat = _session.tempo_map().beat_at_frame (_position);
-               }
-
                send_change (Properties::position_lock_style);
        }
 }
@@ -553,11 +569,18 @@ Region::update_after_tempo_map_change (bool send)
 {
        boost::shared_ptr<Playlist> pl (playlist());
 
-       if (!pl || _position_lock_style != MusicTime) {
+       if (!pl) {
                return;
        }
 
-       const framepos_t pos = _session.tempo_map().frame_at_beat (_beat);
+       if (_position_lock_style == AudioTime) {
+               /* don't signal as the actual position has not chnged */
+               recompute_position_from_lock_style (0);
+               return;
+       }
+
+       /* prevent movement before 0 */
+       const framepos_t pos = max ((framepos_t) 0, _session.tempo_map().frame_at_beat (_beat));
        /* we have _beat. update frame position non-musically */
        set_position_internal (pos, false, 0);
 
@@ -577,25 +600,24 @@ Region::set_position (framepos_t pos, int32_t sub_num)
                return;
        }
 
-       if (sub_num == 0) {
-               set_position_internal (pos, true, 0);
-       } else {
-               double beat = _session.tempo_map().exact_beat_at_frame (pos, sub_num);
-               _beat = beat;
-               set_position_internal (pos, false, sub_num);
-       }
-
        /* do this even if the position is the same. this helps out
           a GUI that has moved its representation already.
        */
        PropertyChange p_and_l;
 
        p_and_l.add (Properties::position);
-       /* Currently length change due to position change is only implemented
-          for MidiRegion (Region has no length in beats).
-          Notify a length change regardless (its more efficient for MidiRegions),
-          and when Region has a _length_beats we will need it here anyway).
-       */
+
+       if (position_lock_style() == AudioTime) {
+               set_position_internal (pos, true, sub_num);
+       } else {
+               if (!_session.loading()) {
+                       _beat = _session.tempo_map().exact_beat_at_frame (pos, sub_num);
+                       _quarter_note = _session.tempo_map().quarter_note_at_beat (_beat);
+               }
+
+               set_position_internal (pos, false, sub_num);
+       }
+
        if (position_lock_style() == MusicTime) {
                p_and_l.add (Properties::length);
        }
@@ -604,46 +626,67 @@ Region::set_position (framepos_t pos, int32_t sub_num)
 
 }
 
-/** A gui may need to create a region, then place it in an initial
- *  position determined by the user.
- *  When this takes place within one gui operation, we have to reset
- *  _last_position to prevent an implied move.
- */
 void
-Region::set_initial_position (framepos_t pos)
+Region::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
 {
-       if (!can_move()) {
-               return;
-       }
+       /* We emit a change of Properties::position even if the position hasn't changed
+          (see Region::set_position), so we must always set this up so that
+          e.g. Playlist::notify_region_moved doesn't use an out-of-date last_position.
+       */
+       _last_position = _position;
 
        if (_position != pos) {
                _position = pos;
 
+               if (allow_bbt_recompute) {
+                       recompute_position_from_lock_style (sub_num);
+               } else {
+                       /* MusicTime dictates that we glue to ardour beats. the pulse may have changed.*/
+                       _quarter_note = _session.tempo_map().quarter_note_at_beat (_beat);
+               }
+
                /* check that the new _position wouldn't make the current
                   length impossible - if so, change the length.
 
                   XXX is this the right thing to do?
                */
-
                if (max_framepos - _length < _position) {
                        _last_length = _length;
                        _length = max_framepos - _position;
                }
-
-               recompute_position_from_lock_style (0);
-               /* ensure that this move doesn't cause a range move */
-               _last_position = _position;
        }
+}
 
+void
+Region::set_position_music (double qn)
+{
+       if (!can_move()) {
+               return;
+       }
 
        /* do this even if the position is the same. this helps out
           a GUI that has moved its representation already.
        */
-       send_change (Properties::position);
+       PropertyChange p_and_l;
+
+       p_and_l.add (Properties::position);
+
+       if (!_session.loading()) {
+               _beat = _session.tempo_map().beat_at_quarter_note (qn);
+       }
+
+       /* will set frame accordingly */
+       set_position_music_internal (qn);
+
+       if (position_lock_style() == MusicTime) {
+               p_and_l.add (Properties::length);
+       }
+
+       send_change (p_and_l);
 }
 
 void
-Region::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
+Region::set_position_music_internal (double qn)
 {
        /* We emit a change of Properties::position even if the position hasn't changed
           (see Region::set_position), so we must always set this up so that
@@ -651,28 +694,65 @@ Region::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const i
        */
        _last_position = _position;
 
+       if (_quarter_note != qn) {
+               _position = _session.tempo_map().frame_at_quarter_note (qn);
+               _quarter_note = qn;
+
+               /* check that the new _position wouldn't make the current
+                  length impossible - if so, change the length.
+
+                  XXX is this the right thing to do?
+               */
+               if (max_framepos - _length < _position) {
+                       _last_length = _length;
+                       _length = max_framepos - _position;
+               }
+       }
+}
+
+/** A gui may need to create a region, then place it in an initial
+ *  position determined by the user.
+ *  When this takes place within one gui operation, we have to reset
+ *  _last_position to prevent an implied move.
+ */
+void
+Region::set_initial_position (framepos_t pos)
+{
+       if (!can_move()) {
+               return;
+       }
+
        if (_position != pos) {
                _position = pos;
 
-               if (allow_bbt_recompute) {
-                       recompute_position_from_lock_style (sub_num);
-               }
                /* check that the new _position wouldn't make the current
                   length impossible - if so, change the length.
 
                   XXX is this the right thing to do?
                */
+
                if (max_framepos - _length < _position) {
                        _last_length = _length;
                        _length = max_framepos - _position;
                }
+
+               recompute_position_from_lock_style (0);
+               /* ensure that this move doesn't cause a range move */
+               _last_position = _position;
        }
+
+
+       /* do this even if the position is the same. this helps out
+          a GUI that has moved its representation already.
+       */
+       send_change (Properties::position);
 }
 
 void
 Region::recompute_position_from_lock_style (const int32_t sub_num)
 {
        _beat = _session.tempo_map().exact_beat_at_frame (_position, sub_num);
+       _quarter_note = _session.tempo_map().exact_qn_at_frame (_position, sub_num);
 }
 
 void
@@ -1177,10 +1257,7 @@ XMLNode&
 Region::state ()
 {
        XMLNode *node = new XMLNode ("Region");
-       char buf[64];
        char buf2[64];
-       LocaleGuard lg;
-       const char* fe = NULL;
 
        /* custom version of 'add_properties (*node);'
         * skip values that have have dedicated save functions
@@ -1195,9 +1272,10 @@ Region::state ()
                i->second->get_value (*node);
        }
 
-       id().print (buf, sizeof (buf));
-       node->add_property ("id", buf);
-       node->add_property ("type", _type.to_string());
+       node->set_property ("id", id ());
+       node->set_property ("type", _type);
+
+       std::string fe;
 
        switch (_first_edit) {
        case EditChangesNothing:
@@ -1214,25 +1292,18 @@ Region::state ()
                break;
        }
 
-       node->add_property ("first-edit", fe);
+       node->set_property ("first-edit", fe);
 
        /* note: flags are stored by derived classes */
 
-       if (_position_lock_style != AudioTime) {
-               snprintf (buf, sizeof(buf), "%lf", _beat);
-               node->add_property ("beat", buf);
-       }
-
        for (uint32_t n=0; n < _sources.size(); ++n) {
                snprintf (buf2, sizeof(buf2), "source-%d", n);
-               _sources[n]->id().print (buf, sizeof(buf));
-               node->add_property (buf2, buf);
+               node->set_property (buf2, _sources[n]->id());
        }
 
        for (uint32_t n=0; n < _master_sources.size(); ++n) {
                snprintf (buf2, sizeof(buf2), "master-source-%d", n);
-               _master_sources[n]->id().print (buf, sizeof (buf));
-               node->add_property (buf2, buf);
+               node->set_property (buf2, _master_sources[n]->id ());
        }
 
        /* Only store nested sources for the whole-file region that acts
@@ -1280,7 +1351,6 @@ Region::set_state (const XMLNode& node, int version)
 int
 Region::_set_state (const XMLNode& node, int /*version*/, PropertyChange& what_changed, bool send)
 {
-       XMLProperty const * prop;
        Timecode::BBT_Time bbt_time;
 
        Stateful::save_extra_xml (node);
@@ -1290,25 +1360,19 @@ Region::_set_state (const XMLNode& node, int /*version*/, PropertyChange& what_c
        set_id (node);
 
        if (_position_lock_style == MusicTime) {
-               if ((prop = node.property ("bbt-position")) == 0) {
-                       if ((prop = node.property ("beat")) == 0) {
-                               /* missing BBT info, revert to audio time locking */
-                               _position_lock_style = AudioTime;
-                       } else {
-                               if (sscanf (prop->value().c_str(), "%lf", &_beat) != 1) {
-                                       _position_lock_style = AudioTime;
-                               }
-                       }
-
-               } else {
-                       if (sscanf (prop->value().c_str(), "%d|%d|%d",
+               std::string bbt_str;
+               if (node.get_property ("bbt-position", bbt_str)) {
+                       if (sscanf (bbt_str.c_str(), "%d|%d|%d",
                                    &bbt_time.bars,
                                    &bbt_time.beats,
                                    &bbt_time.ticks) != 3) {
                                _position_lock_style = AudioTime;
+                               _beat = _session.tempo_map().beat_at_frame (_position);
                        } else {
                                _beat = _session.tempo_map().beat_at_bbt (bbt_time);
                        }
+                       /* no position property change for legacy Property, so we do this here */
+                       _quarter_note = _session.tempo_map().quarter_note_at_beat (_beat);
                }
        }
 
@@ -1328,8 +1392,9 @@ Region::_set_state (const XMLNode& node, int /*version*/, PropertyChange& what_c
        }
 
        /* Quick fix for 2.x sessions when region is muted */
-       if ((prop = node.property (X_("flags")))) {
-               if (string::npos != prop->value().find("Muted")){
+       std::string flags;
+       if (node.get_property (X_("flags"), flags)) {
+               if (string::npos != flags.find("Muted")){
                        set_muted (true);
                }
        }
@@ -1844,9 +1909,7 @@ Region::is_compound () const
 void
 Region::post_set (const PropertyChange& pc)
 {
-       if (pc.contains (Properties::position)) {
-               recompute_position_from_lock_style (0);
-       }
+       _quarter_note = _session.tempo_map().quarter_note_at_beat (_beat);
 }
 
 void