change ControlProtocolManager protocol mutex into a RW lock.
[ardour.git] / libs / ardour / region.cc
index 5fb5b30014add2474a2f8ea6919c7c720f36688a..ac08aff7ee9c3203b5a47dacec27ffef731c97a3 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)
+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)
        /* 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)
        use_sources (other->_sources);
        set_master_sources (other->_master_sources);
 
-       _start = other->_start + offset;
-       _beat = _session.tempo_map().beat_at_frame (_position);
+       _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.
@@ -419,7 +439,7 @@ Region::set_name (const std::string& str)
 }
 
 void
-Region::set_length (framecnt_t len)
+Region::set_length (framecnt_t len, const int32_t sub_num)
 {
        //cerr << "Region::set_length() len = " << len << endl;
        if (locked()) {
@@ -441,7 +461,7 @@ Region::set_length (framecnt_t len)
                }
 
 
-               set_length_internal (len);
+               set_length_internal (len, sub_num);
                _whole_file = false;
                first_edit ();
                maybe_uncopy ();
@@ -456,7 +476,7 @@ Region::set_length (framecnt_t len)
 }
 
 void
-Region::set_length_internal (framecnt_t len)
+Region::set_length_internal (framecnt_t len, const int32_t sub_num)
 {
        _last_length = _length;
        _length = len;
@@ -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,12 +569,20 @@ Region::update_after_tempo_map_change (bool send)
 {
        boost::shared_ptr<Playlist> pl (playlist());
 
-       if (!pl || _position_lock_style != MusicTime) {
+       if (!pl) {
+               return;
+       }
+
+       if (_position_lock_style == AudioTime) {
+               /* don't signal as the actual position has not chnged */
+               recompute_position_from_lock_style (0);
                return;
        }
 
-       const framepos_t pos = _session.tempo_map().frame_at_beat (_beat);
-       set_position_internal (pos, false);
+       /* 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);
 
        /* do this even if the position is the same. this helps out
           a GUI that has moved its representation already.
@@ -576,25 +600,24 @@ Region::set_position (framepos_t pos, int32_t sub_num)
                return;
        }
 
-       if (sub_num == 0) {
-               set_position_internal (pos, true);
-       } else {
-               double beat = _session.tempo_map().exact_beat_at_frame (pos, sub_num);
-               _beat = beat;
-               set_position_internal (pos, false);
-       }
-
        /* 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);
        }
@@ -603,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 ();
-               /* 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)
+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
@@ -650,30 +694,65 @@ Region::set_position_internal (framepos_t pos, bool allow_bbt_recompute)
        */
        _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 ();
-               }
                /* 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 ()
+Region::recompute_position_from_lock_style (const int32_t sub_num)
 {
-       if (_position_lock_style == MusicTime) {
-               _beat = _session.tempo_map().beat_at_frame (_position);
-       }
+       _beat = _session.tempo_map().exact_beat_at_frame (_position, sub_num);
+       _quarter_note = _session.tempo_map().exact_qn_at_frame (_position, sub_num);
 }
 
 void
@@ -702,8 +781,8 @@ Region::nudge_position (frameoffset_t n)
                        new_position += n;
                }
        }
-
-       set_position_internal (new_position, true);
+       /* assumes non-musical nudge */
+       set_position_internal (new_position, true, 0);
 
        send_change (Properties::position);
 }
@@ -744,7 +823,7 @@ Region::set_start (framepos_t pos)
 }
 
 void
-Region::move_start (frameoffset_t distance, const int32_t& sub_num)
+Region::move_start (frameoffset_t distance, const int32_t sub_num)
 {
        if (locked() || position_locked() || video_locked()) {
                return;
@@ -789,25 +868,25 @@ Region::move_start (frameoffset_t distance, const int32_t& sub_num)
 }
 
 void
-Region::trim_front (framepos_t new_position, const int32_t& sub_num)
+Region::trim_front (framepos_t new_position, const int32_t sub_num)
 {
        modify_front (new_position, false, sub_num);
 }
 
 void
-Region::cut_front (framepos_t new_position, const int32_t& sub_num)
+Region::cut_front (framepos_t new_position, const int32_t sub_num)
 {
        modify_front (new_position, true, sub_num);
 }
 
 void
-Region::cut_end (framepos_t new_endpoint, const int32_t& sub_num)
+Region::cut_end (framepos_t new_endpoint, const int32_t sub_num)
 {
        modify_end (new_endpoint, true, sub_num);
 }
 
 void
-Region::modify_front (framepos_t new_position, bool reset_fade, const int32_t& sub_num)
+Region::modify_front (framepos_t new_position, bool reset_fade, const int32_t sub_num)
 {
        if (locked()) {
                return;
@@ -852,7 +931,7 @@ Region::modify_front (framepos_t new_position, bool reset_fade, const int32_t& s
 }
 
 void
-Region::modify_end (framepos_t new_endpoint, bool reset_fade, const int32_t& sub_num)
+Region::modify_end (framepos_t new_endpoint, bool reset_fade, const int32_t sub_num)
 {
        if (locked()) {
                return;
@@ -874,13 +953,13 @@ Region::modify_end (framepos_t new_endpoint, bool reset_fade, const int32_t& sub
  */
 
 void
-Region::trim_end (framepos_t new_endpoint, const int32_t& sub_num)
+Region::trim_end (framepos_t new_endpoint, const int32_t sub_num)
 {
        modify_end (new_endpoint, false, sub_num);
 }
 
 void
-Region::trim_to (framepos_t position, framecnt_t length, const int32_t& sub_num)
+Region::trim_to (framepos_t position, framecnt_t length, const int32_t sub_num)
 {
        if (locked()) {
                return;
@@ -895,7 +974,7 @@ Region::trim_to (framepos_t position, framecnt_t length, const int32_t& sub_num)
 }
 
 void
-Region::trim_to_internal (framepos_t position, framecnt_t length, const int32_t& sub_num)
+Region::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
 {
        framepos_t new_start;
 
@@ -949,7 +1028,7 @@ Region::trim_to_internal (framepos_t position, framecnt_t length, const int32_t&
                if (!property_changes_suspended()) {
                        _last_position = _position;
                }
-               set_position_internal (position, true);
+               set_position_internal (position, true, sub_num);
                what_changed.add (Properties::position);
        }
 
@@ -957,7 +1036,7 @@ Region::trim_to_internal (framepos_t position, framecnt_t length, const int32_t&
                if (!property_changes_suspended()) {
                        _last_length = _length;
                }
-               set_length_internal (length);
+               set_length_internal (length, sub_num);
                what_changed.add (Properties::length);
        }
 
@@ -1178,10 +1257,8 @@ 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
@@ -1196,9 +1273,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:
@@ -1215,25 +1293,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
@@ -1281,7 +1352,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);
@@ -1291,25 +1361,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);
                }
        }
 
@@ -1329,8 +1393,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);
                }
        }
@@ -1845,13 +1910,11 @@ Region::is_compound () const
 void
 Region::post_set (const PropertyChange& pc)
 {
-       if (pc.contains (Properties::position)) {
-               recompute_position_from_lock_style ();
-       }
+       _quarter_note = _session.tempo_map().quarter_note_at_beat (_beat);
 }
 
 void
-Region::set_start_internal (framecnt_t s, const int32_t& sub_num)
+Region::set_start_internal (framecnt_t s, const int32_t sub_num)
 {
        _start = s;
 }