Fix unit-test data (XML attributes changed)
[ardour.git] / libs / ardour / region.cc
index eb320c5cef8353f14129cd33427cdb318d8d1b36..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,6 +38,7 @@
 #include "ardour/source.h"
 #include "ardour/tempo.h"
 #include "ardour/transient_detector.h"
+#include "ardour/types_convert.h"
 
 #include "pbd/i18n.h"
 
@@ -329,7 +331,7 @@ 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)
@@ -343,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;
@@ -351,9 +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);
-       _quarter_note = _session.tempo_map().exact_qn_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.
@@ -589,74 +600,93 @@ Region::set_position (framepos_t pos, int32_t sub_num)
                return;
        }
 
+       /* 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);
+
        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);
                }
 
-               /* will set pulse accordingly */
                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).
-       */
-       p_and_l.add (Properties::length);
+       if (position_lock_style() == MusicTime) {
+               p_and_l.add (Properties::length);
+       }
 
        send_change (p_and_l);
 
 }
 
-/** 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
@@ -664,26 +694,58 @@ Region::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const i
        */
        _last_position = _position;
 
-       if (_position != pos) {
-               _position = pos;
+       if (_quarter_note != qn) {
+               _position = _session.tempo_map().frame_at_quarter_note (qn);
+               _quarter_note = qn;
 
-               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;
                }
+       }
+}
+
+/** 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;
 
                /* 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
@@ -1195,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
@@ -1213,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:
@@ -1232,20 +1292,18 @@ Region::state ()
                break;
        }
 
-       node->add_property ("first-edit", fe);
+       node->set_property ("first-edit", fe);
 
        /* note: flags are stored by derived classes */
 
        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
@@ -1293,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);
@@ -1303,8 +1360,9 @@ 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 (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) {
@@ -1313,11 +1371,11 @@ Region::_set_state (const XMLNode& node, int /*version*/, PropertyChange& what_c
                        } 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);
                }
        }
 
-       _quarter_note = _session.tempo_map().quarter_note_at_beat (_beat);
-
        /* fix problems with old sessions corrupted by impossible
           values for _stretch or _shift
        */
@@ -1334,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);
                }
        }
@@ -1850,9 +1909,7 @@ Region::is_compound () const
 void
 Region::post_set (const PropertyChange& pc)
 {
-       if (pc.contains (Properties::position)) {
-               _quarter_note = _session.tempo_map().quarter_note_at_beat (_beat);
-       }
+       _quarter_note = _session.tempo_map().quarter_note_at_beat (_beat);
 }
 
 void