set sidechain port pretty name
[ardour.git] / libs / ardour / playlist.cc
index 525749181530b18556b1f23400233cb70bb74e98..239622a38c3252e3242dcd9e2ddbd37ff81f5edb 100644 (file)
 
 */
 
-#define __STDC_LIMIT_MACROS
 #include <stdint.h>
-
 #include <set>
-#include <fstream>
 #include <algorithm>
-#include <unistd.h>
-#include <cerrno>
 #include <string>
-#include <climits>
 
 #include <boost/lexical_cast.hpp>
 
-#include "pbd/failed_constructor.h"
+#include "pbd/convert.h"
 #include "pbd/stateful_diff_command.h"
 #include "pbd/xml++.h"
 
 #include "ardour/session.h"
 #include "ardour/region.h"
 #include "ardour/region_factory.h"
+#include "ardour/region_sorters.h"
 #include "ardour/playlist_factory.h"
+#include "ardour/playlist_source.h"
 #include "ardour/transient_detector.h"
 #include "ardour/session_playlists.h"
+#include "ardour/source_factory.h"
 
 #include "i18n.h"
 
@@ -50,9 +47,9 @@ using namespace ARDOUR;
 using namespace PBD;
 
 namespace ARDOUR {
-namespace Properties {
-PBD::PropertyDescriptor<bool> regions;
-}
+       namespace Properties {
+               PBD::PropertyDescriptor<bool> regions;
+       }
 }
 
 struct ShowMeTheList {
@@ -64,120 +61,84 @@ struct ShowMeTheList {
     string name;
 };
 
-struct RegionSortByLayer {
-    bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
-           return a->layer() < b->layer();
-    }
-};
-
-struct RegionSortByLayerWithPending {
-       bool operator () (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
-
-               double p = a->layer ();
-               if (a->pending_explicit_relayer()) {
-                       p += 0.5;
-               }
-
-               double q = b->layer ();
-               if (b->pending_explicit_relayer()) {
-                       q += 0.5;
-               }
-
-               return p < q;
-       }
-};
-
-struct RegionSortByPosition {
-    bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
-           return a->position() < b->position();
-    }
-};
 
-struct RegionSortByLastLayerOp {
-    bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
-           return a->last_layer_op() < b->last_layer_op();
-    }
-};
 
 void
 Playlist::make_property_quarks ()
 {
-        Properties::regions.property_id = g_quark_from_static_string (X_("regions"));
-        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for regions = %1\n",         Properties::regions.property_id));
+       Properties::regions.property_id = g_quark_from_static_string (X_("regions"));
+       DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for regions = %1\n",
+                                                       Properties::regions.property_id));
 }
 
 RegionListProperty::RegionListProperty (Playlist& pl)
-        : SequenceProperty<std::list<boost::shared_ptr<Region> > > (Properties::regions.property_id, boost::bind (&Playlist::update, &pl, _1))
-        , _playlist (pl)
+       : SequenceProperty<std::list<boost::shared_ptr<Region> > > (Properties::regions.property_id, boost::bind (&Playlist::update, &pl, _1))
+       , _playlist (pl)
 {
+
 }
 
-boost::shared_ptr<Region>
-RegionListProperty::lookup_id (const ID& id)
+RegionListProperty::RegionListProperty (RegionListProperty const & p)
+       : PBD::SequenceProperty<std::list<boost::shared_ptr<Region> > > (p)
+       , _playlist (p._playlist)
+{
+
+}
+
+RegionListProperty *
+RegionListProperty::clone () const
 {
-        boost::shared_ptr<Region> ret =  _playlist.region_by_id (id);
-        
-        if (!ret) {
-                ret = RegionFactory::region_by_id (id);
-        }
+       return new RegionListProperty (*this);
+}
 
-        return ret;
+RegionListProperty *
+RegionListProperty::create () const
+{
+       return new RegionListProperty (_playlist);
 }
 
-RegionListProperty*
-RegionListProperty::copy_for_history () const
+void
+RegionListProperty::get_content_as_xml (boost::shared_ptr<Region> region, XMLNode & node) const
 {
-        RegionListProperty* copy = new RegionListProperty (_playlist);
-        /* this is all we need */
-        copy->_change = _change;
-        return copy;
+       /* All regions (even those which are deleted) have their state saved by other
+          code, so we can just store ID here.
+       */
+
+       node.add_property ("id", region->id().to_s ());
 }
 
-void 
-RegionListProperty::diff (PropertyList& undo, PropertyList& redo, Command* cmd) const
+boost::shared_ptr<Region>
+RegionListProperty::get_content_from_xml (XMLNode const & node) const
 {
-        if (changed()) {
-               /* list of the removed/added regions since clear_history() was last called */
-                RegionListProperty* a = copy_for_history ();
+       XMLProperty const * prop = node.property ("id");
+       assert (prop);
 
-               /* the same list, but with removed/added lists swapped (for undo purposes) */
-                RegionListProperty* b = copy_for_history ();
-                b->invert_changes ();
+       PBD::ID id (prop->value ());
 
-                if (cmd) {
-                        /* whenever one of the regions emits DropReferences, make sure
-                           that the Destructible we've been told to notify hears about
-                           it. the Destructible is likely to be the Command being built
-                           with this diff().
-                        */
-                        
-                        for (set<boost::shared_ptr<Region> >::iterator i = a->change().added.begin(); i != a->change().added.end(); ++i) {
-                                (*i)->DropReferences.connect_same_thread (*cmd, boost::bind (&Destructible::drop_references, cmd));
-                        }
-                }
+       boost::shared_ptr<Region> ret = _playlist.region_by_id (id);
 
-                undo.add (b);
-                redo.add (a);
-        }
+       if (!ret) {
+               ret = RegionFactory::region_by_id (id);
+       }
+
+       return ret;
 }
 
 Playlist::Playlist (Session& sess, string nom, DataType type, bool hide)
        : SessionObject(sess, nom)
-        , regions (*this)
+       , regions (*this)
        , _type(type)
 {
        init (hide);
        first_set_state = false;
        _name = nom;
-        _set_sort_id ();
-
+       _set_sort_id ();
 }
 
 Playlist::Playlist (Session& sess, const XMLNode& node, DataType type, bool hide)
        : SessionObject(sess, "unnamed playlist")
-        , regions (*this)      
-        , _type(type)
-
+       , regions (*this)
+       , _type(type)
 {
 #ifndef NDEBUG
        const XMLProperty* prop = node.property("type");
@@ -186,16 +147,16 @@ Playlist::Playlist (Session& sess, const XMLNode& node, DataType type, bool hide
 
        init (hide);
        _name = "unnamed"; /* reset by set_state */
-        _set_sort_id ();
+       _set_sort_id ();
 
        /* set state called by derived class */
 }
 
 Playlist::Playlist (boost::shared_ptr<const Playlist> other, string namestr, bool hide)
        : SessionObject(other->_session, namestr)
-        , regions (*this)
+       , regions (*this)
        , _type(other->_type)
-       , _orig_diskstream_id (other->_orig_diskstream_id)
+       , _orig_track_id (other->_orig_track_id)
 {
        init (hide);
 
@@ -211,6 +172,7 @@ Playlist::Playlist (boost::shared_ptr<const Playlist> other, string namestr, boo
        in_set_state--;
 
        _splicing  = other->_splicing;
+       _rippling  = other->_rippling;
        _nudging   = other->_nudging;
        _edit_mode = other->_edit_mode;
 
@@ -219,20 +181,16 @@ Playlist::Playlist (boost::shared_ptr<const Playlist> other, string namestr, boo
        in_flush = false;
        in_partition = false;
        subcnt = 0;
-       _read_data_count = 0;
        _frozen = other->_frozen;
-
-       layer_op_counter = other->layer_op_counter;
-       freeze_length = other->freeze_length;
 }
 
 Playlist::Playlist (boost::shared_ptr<const Playlist> other, framepos_t start, framecnt_t cnt, string str, bool hide)
        : SessionObject(other->_session, str)
-        , regions (*this)
+       , regions (*this)
        , _type(other->_type)
-       , _orig_diskstream_id (other->_orig_diskstream_id)
+       , _orig_track_id (other->_orig_track_id)
 {
-       RegionLock rlock2 (const_cast<Playlist*> (other.get()));
+       RegionReadLock rlock2 (const_cast<Playlist*> (other.get()));
 
        framepos_t end = start + cnt - 1;
 
@@ -248,35 +206,35 @@ Playlist::Playlist (boost::shared_ptr<const Playlist> other, framepos_t start, f
                framepos_t position = 0;
                framecnt_t len = 0;
                string    new_name;
-               OverlapType overlap;
+               Evoral::OverlapType overlap;
 
                region = *i;
 
                overlap = region->coverage (start, end);
 
                switch (overlap) {
-               case OverlapNone:
+               case Evoral::OverlapNone:
                        continue;
 
-               case OverlapInternal:
+               case Evoral::OverlapInternal:
                        offset = start - region->position();
                        position = 0;
                        len = cnt;
                        break;
 
-               case OverlapStart:
+               case Evoral::OverlapStart:
                        offset = 0;
                        position = region->position() - start;
                        len = end - region->position();
                        break;
 
-               case OverlapEnd:
+               case Evoral::OverlapEnd:
                        offset = start - region->position();
                        position = 0;
                        len = region->length() - offset;
                        break;
 
-               case OverlapExternal:
+               case Evoral::OverlapExternal:
                        offset = 0;
                        position = region->position() - start;
                        len = region->length();
@@ -285,22 +243,25 @@ Playlist::Playlist (boost::shared_ptr<const Playlist> other, framepos_t start, f
 
                RegionFactory::region_name (new_name, region->name(), false);
 
-               PropertyList plist; 
+               PropertyList plist;
 
                plist.add (Properties::start, region->start() + offset);
                plist.add (Properties::length, len);
                plist.add (Properties::name, new_name);
                plist.add (Properties::layer, region->layer());
+               plist.add (Properties::layering_index, region->layering_index());
 
-               new_region = RegionFactory::RegionFactory::create (region, plist);
+               new_region = RegionFactory::create (region, plist);
 
                add_region_internal (new_region, position);
        }
 
+       //keep track of any dead space at end (for pasting into Ripple or Splice mode)
+       //at the end of construction, any length of cnt beyond the extents of the regions is end_space
+       _end_space = cnt - (get_extent().second - get_extent().first);
+
        in_set_state--;
        first_set_state = false;
-
-       /* this constructor does NOT notify others (session) */
 }
 
 void
@@ -325,45 +286,44 @@ Playlist::release ()
 void
 Playlist::copy_regions (RegionList& newlist) const
 {
-       RegionLock rlock (const_cast<Playlist *> (this));
+       RegionReadLock rlock (const_cast<Playlist *> (this));
 
        for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
-               newlist.push_back (RegionFactory::RegionFactory::create (*i));
+               newlist.push_back (RegionFactory::create (*i, true));
        }
 }
 
 void
 Playlist::init (bool hide)
 {
-        add_property (regions);
-        _xml_node_name = X_("Playlist");
+       add_property (regions);
+       _xml_node_name = X_("Playlist");
 
        g_atomic_int_set (&block_notifications, 0);
        g_atomic_int_set (&ignore_state_changes, 0);
        pending_contents_change = false;
-       pending_length = false;
        pending_layering = false;
        first_set_state = true;
        _refcnt = 0;
        _hidden = hide;
        _splicing = false;
+       _rippling = false;
        _shuffling = false;
        _nudging = false;
        in_set_state = 0;
-        in_update = false;
+       in_undo = false;
        _edit_mode = Config->get_edit_mode();
        in_flush = false;
        in_partition = false;
        subcnt = 0;
-       _read_data_count = 0;
        _frozen = false;
-       layer_op_counter = 0;
-       freeze_length = 0;
-       _explicit_relayering = false;
+       _capture_insertion_underway = false;
+       _combine_ops = 0;
+       _end_space = 0;
 
        _session.history().BeginUndoRedo.connect_same_thread (*this, boost::bind (&Playlist::begin_undo, this));
        _session.history().EndUndoRedo.connect_same_thread (*this, boost::bind (&Playlist::end_undo, this));
-       
+
        ContentsChanged.connect_same_thread (*this, boost::bind (&Playlist::mark_session_dirty, this));
 }
 
@@ -372,7 +332,7 @@ Playlist::~Playlist ()
        DEBUG_TRACE (DEBUG::Destruction, string_compose ("Playlist %1 destructor\n", _name));
 
        {
-               RegionLock rl (this);
+               RegionReadLock rl (this);
 
                for (set<boost::shared_ptr<Region> >::iterator i = all_regions.begin(); i != all_regions.end(); ++i) {
                        (*i)->set_playlist (boost::shared_ptr<Playlist>());
@@ -385,27 +345,27 @@ Playlist::~Playlist ()
 void
 Playlist::_set_sort_id ()
 {
-        /*
-          Playlists are given names like <track name>.<id>
-          or <track name>.<edit group name>.<id> where id
-          is an integer. We extract the id and sort by that.
-        */
-        
-        size_t dot_position = _name.val().find_last_of(".");
+       /*
+         Playlists are given names like <track name>.<id>
+         or <track name>.<edit group name>.<id> where id
+         is an integer. We extract the id and sort by that.
+       */
 
-        if (dot_position == string::npos) {
-                _sort_id = 0;
-        } else {
-                string t = _name.val().substr(dot_position + 1);
+       size_t dot_position = _name.val().find_last_of(".");
 
-                try {
-                        _sort_id = boost::lexical_cast<int>(t);
-                }
+       if (dot_position == string::npos) {
+               _sort_id = 0;
+       } else {
+               string t = _name.val().substr(dot_position + 1);
+
+               try {
+                       _sort_id = boost::lexical_cast<int>(t);
+               }
 
-                catch (boost::bad_lexical_cast e) {
-                        _sort_id = 0;
-                }
-        }
+               catch (boost::bad_lexical_cast e) {
+                       _sort_id = 0;
+               }
+       }
 }
 
 bool
@@ -419,13 +379,13 @@ Playlist::set_name (const string& str)
 
        if (_refcnt > 2) {
                return false;
-       } 
+       }
 
-        bool ret =  SessionObject::set_name(str);
-        if (ret) {
-                _set_sort_id ();
-        }
-        return ret;
+       bool ret =  SessionObject::set_name(str);
+       if (ret) {
+               _set_sort_id ();
+       }
+       return ret;
 }
 
 /***********************************************************************
@@ -439,15 +399,15 @@ Playlist::set_name (const string& str)
 void
 Playlist::begin_undo ()
 {
-        in_update = true;
+       in_undo = true;
        freeze ();
 }
 
 void
 Playlist::end_undo ()
 {
-       thaw ();
-        in_update = false;
+       thaw (true);
+       in_undo = false;
 }
 
 void
@@ -457,11 +417,12 @@ Playlist::freeze ()
        g_atomic_int_inc (&ignore_state_changes);
 }
 
+/** @param from_undo true if this thaw is triggered by the end of an undo on this playlist */
 void
-Playlist::thaw ()
+Playlist::thaw (bool from_undo)
 {
        g_atomic_int_dec_and_test (&ignore_state_changes);
-       release_notifications ();
+       release_notifications (from_undo);
 }
 
 
@@ -469,16 +430,15 @@ void
 Playlist::delay_notifications ()
 {
        g_atomic_int_inc (&block_notifications);
-       freeze_length = _get_extent().second;
 }
 
+/** @param from_undo true if this release is triggered by the end of an undo on this playlist */
 void
-Playlist::release_notifications ()
+Playlist::release_notifications (bool from_undo)
 {
        if (g_atomic_int_dec_and_test (&block_notifications)) {
-               flush_notifications ();
-        }
-
+               flush_notifications (from_undo);
+       }
 }
 
 void
@@ -509,13 +469,10 @@ Playlist::notify_region_removed (boost::shared_ptr<Region> r)
        if (holding_state ()) {
                pending_removes.insert (r);
                pending_contents_change = true;
-               pending_length = true;
        } else {
                /* this might not be true, but we have to act
                   as though it could be.
                */
-               pending_length = false;
-               LengthChanged (); /* EMIT SIGNAL */
                pending_contents_change = false;
                RegionRemoved (boost::weak_ptr<Region> (r)); /* EMIT SIGNAL */
                ContentsChanged (); /* EMIT SIGNAL */
@@ -535,53 +492,81 @@ Playlist::notify_region_moved (boost::shared_ptr<Region> r)
 
                list< Evoral::RangeMove<framepos_t> > m;
                m.push_back (move);
-               RangesMoved (m);
+               RangesMoved (m, false);
+       }
+
+}
+
+void
+Playlist::notify_region_start_trimmed (boost::shared_ptr<Region> r)
+{
+       if (r->position() >= r->last_position()) {
+               /* trimmed shorter */
+               return;
+       }
+
+       Evoral::Range<framepos_t> const extra (r->position(), r->last_position());
+
+       if (holding_state ()) {
+
+               pending_region_extensions.push_back (extra);
+
+       } else {
+
+               list<Evoral::Range<framepos_t> > r;
+               r.push_back (extra);
+               RegionsExtended (r);
+
+       }
+}
+
+void
+Playlist::notify_region_end_trimmed (boost::shared_ptr<Region> r)
+{
+       if (r->length() < r->last_length()) {
+               /* trimmed shorter */
        }
 
+       Evoral::Range<framepos_t> const extra (r->position() + r->last_length(), r->position() + r->length());
+
+       if (holding_state ()) {
+
+               pending_region_extensions.push_back (extra);
+
+       } else {
+
+               list<Evoral::Range<framepos_t> > r;
+               r.push_back (extra);
+               RegionsExtended (r);
+       }
 }
 
+
 void
 Playlist::notify_region_added (boost::shared_ptr<Region> r)
 {
        /* the length change might not be true, but we have to act
           as though it could be.
        */
-        
+
        if (holding_state()) {
                pending_adds.insert (r);
                pending_contents_change = true;
-               pending_length = true;
        } else {
-                r->clear_history ();
-               pending_length = false;
-               LengthChanged (); /* EMIT SIGNAL */
+               r->clear_changes ();
                pending_contents_change = false;
                RegionAdded (boost::weak_ptr<Region> (r)); /* EMIT SIGNAL */
                ContentsChanged (); /* EMIT SIGNAL */
-       }
-}
 
-void
-Playlist::notify_length_changed ()
-{
-       if (holding_state ()) {
-               pending_length = true;
-       } else {
-               pending_length = false;
-               LengthChanged(); /* EMIT SIGNAL */
-               pending_contents_change = false;
-               ContentsChanged (); /* EMIT SIGNAL */
        }
 }
 
+/** @param from_undo true if this flush is triggered by the end of an undo on this playlist */
 void
-Playlist::flush_notifications ()
+Playlist::flush_notifications (bool from_undo)
 {
-       set<boost::shared_ptr<Region> > dependent_checks_needed;
        set<boost::shared_ptr<Region> >::iterator s;
-       uint32_t regions_changed = false;
-       bool check_length = false;
-       framecnt_t old_length = 0;
+       bool regions_changed = false;
 
        if (in_flush) {
                return;
@@ -591,12 +576,12 @@ Playlist::flush_notifications ()
 
        if (!pending_bounds.empty() || !pending_removes.empty() || !pending_adds.empty()) {
                regions_changed = true;
-               if (!pending_length) {
-                       old_length = _get_extent ().second;
-                       check_length = true;
-               }
        }
 
+       /* XXX: it'd be nice if we could use pending_bounds for
+          RegionsExtended and RegionsMoved.
+       */
+
        /* we have no idea what order the regions ended up in pending
           bounds (it could be based on selection order, for example).
           so, to preserve layering in the "most recently moved is higher"
@@ -606,1280 +591,1296 @@ Playlist::flush_notifications ()
        // RegionSortByLayer cmp;
        // pending_bounds.sort (cmp);
 
+       list<Evoral::Range<framepos_t> > crossfade_ranges;
+
        for (RegionList::iterator r = pending_bounds.begin(); r != pending_bounds.end(); ++r) {
-               if (_session.config.get_layer_model() == MoveAddHigher) {
-                       timestamp_layer_op (*r);
-               }
-               dependent_checks_needed.insert (*r);
+               crossfade_ranges.push_back ((*r)->last_range ());
+               crossfade_ranges.push_back ((*r)->range ());
        }
 
        for (s = pending_removes.begin(); s != pending_removes.end(); ++s) {
+               crossfade_ranges.push_back ((*s)->range ());
                remove_dependents (*s);
-               // cerr << _name << " sends RegionRemoved\n";
                RegionRemoved (boost::weak_ptr<Region> (*s)); /* EMIT SIGNAL */
        }
 
        for (s = pending_adds.begin(); s != pending_adds.end(); ++s) {
-               // cerr << _name << " sends RegionAdded\n";
-                /* don't emit RegionAdded signal until relayering is done,
-                   so that the region is fully setup by the time
-                   anyone hear's that its been added
-                */
-               dependent_checks_needed.insert (*s);
-       }
-
-       if (check_length) {
-               if (old_length != _get_extent().second) {
-                       pending_length = true;
-                       // cerr << _name << " length has changed\n";
-               }
+               crossfade_ranges.push_back ((*s)->range ());
+               /* don't emit RegionAdded signal until relayering is done,
+                  so that the region is fully setup by the time
+                  anyone hears that its been added
+               */
        }
 
-       if (pending_length || (freeze_length != _get_extent().second)) {
-               pending_length = false;
-               // cerr << _name << " sends LengthChanged\n";
-               LengthChanged(); /* EMIT SIGNAL */
-       }
+       /* notify about contents/region changes first so that layering changes
+        * in a UI will take place on the new contents.
+        */
 
        if (regions_changed || pending_contents_change) {
-               if (!in_set_state) {
-                       relayer ();
-               }
-               pending_contents_change = false;
-               // cerr << _name << " sends 5 contents change @ " << get_microseconds() << endl;
+               pending_layering = true;
                ContentsChanged (); /* EMIT SIGNAL */
-               // cerr << _name << "done contents change @ " << get_microseconds() << endl;
        }
 
        for (s = pending_adds.begin(); s != pending_adds.end(); ++s) {
-                (*s)->clear_history ();
+               (*s)->clear_changes ();
                RegionAdded (boost::weak_ptr<Region> (*s)); /* EMIT SIGNAL */
-        }
+       }
 
-       for (s = dependent_checks_needed.begin(); s != dependent_checks_needed.end(); ++s) {
-               check_dependents (*s, false);
+       if ((regions_changed && !in_set_state) || pending_layering) {
+               relayer ();
        }
 
+       coalesce_and_check_crossfades (crossfade_ranges);
+
        if (!pending_range_moves.empty ()) {
-               // cerr << _name << " sends RangesMoved\n";
-               RangesMoved (pending_range_moves);
+               /* We don't need to check crossfades for these as pending_bounds has
+                  already covered it.
+               */
+               RangesMoved (pending_range_moves, from_undo);
+       }
+
+       if (!pending_region_extensions.empty ()) {
+               RegionsExtended (pending_region_extensions);
        }
-       
+
        clear_pending ();
 
        in_flush = false;
 }
 
-void
-Playlist::clear_pending ()
-{
-       pending_adds.clear ();
-       pending_removes.clear ();
-       pending_bounds.clear ();
-       pending_range_moves.clear ();
-       pending_contents_change = false;
-       pending_length = false;
-}
-
-/*************************************************************
-  PLAYLIST OPERATIONS
- *************************************************************/
-
-void
-Playlist::add_region (boost::shared_ptr<Region> region, framepos_t position, float times, bool auto_partition)
-{
-       RegionLock rlock (this);
-       times = fabs (times);
-
-       int itimes = (int) floor (times);
-
-       framepos_t pos = position;
+ void
+ Playlist::clear_pending ()
+ {
+        pending_adds.clear ();
+        pending_removes.clear ();
+        pending_bounds.clear ();
+        pending_range_moves.clear ();
+        pending_region_extensions.clear ();
+        pending_contents_change = false;
+ }
 
-       if (times == 1 && auto_partition){
-               partition(pos - 1, (pos + region->length()), true);
-       }
+ /*************************************************************
+   PLAYLIST OPERATIONS
+  *************************************************************/
 
-       if (itimes >= 1) {
-               add_region_internal (region, pos);
-               pos += region->length();
-               --itimes;
-       }
+/** Note: this calls set_layer (..., DBL_MAX) so it will reset the layering index of region */
+ void
+ Playlist::add_region (boost::shared_ptr<Region> region, framepos_t position, float times, bool auto_partition)
+ {
+        RegionWriteLock rlock (this);
+        times = fabs (times);
 
+        int itimes = (int) floor (times);
 
-       /* note that itimes can be zero if we being asked to just
-          insert a single fraction of the region.
-       */
+        framepos_t pos = position;
 
-       for (int i = 0; i < itimes; ++i) {
-               boost::shared_ptr<Region> copy = RegionFactory::create (region);
-               add_region_internal (copy, pos);
-               pos += region->length();
-       }
+        if (times == 1 && auto_partition){
+                partition(pos - 1, (pos + region->length()), true);
+        }
 
-       framecnt_t length = 0;
+        if (itimes >= 1) {
+                add_region_internal (region, pos);
+                set_layer (region, DBL_MAX);
+                pos += region->length();
+                --itimes;
+        }
 
-       if (floor (times) != times) {
-               length = (framecnt_t) floor (region->length() * (times - floor (times)));
-               string name;
-               RegionFactory::region_name (name, region->name(), false);
 
-               {
-                       PropertyList plist;
-                       
-                       plist.add (Properties::start, region->start());
-                       plist.add (Properties::length, length);
-                       plist.add (Properties::name, name);
-                       plist.add (Properties::layer, region->layer());
+        /* note that itimes can be zero if we being asked to just
+           insert a single fraction of the region.
+        */
 
-                       boost::shared_ptr<Region> sub = RegionFactory::create (region, plist);
-                       add_region_internal (sub, pos);
-               }
-       }
+        for (int i = 0; i < itimes; ++i) {
+                boost::shared_ptr<Region> copy = RegionFactory::create (region, true);
+                add_region_internal (copy, pos);
+                set_layer (copy, DBL_MAX);
+                pos += region->length();
+        }
 
-       possibly_splice_unlocked (position, (pos + length) - position, boost::shared_ptr<Region>());
-}
+        framecnt_t length = 0;
 
-void
-Playlist::set_region_ownership ()
-{
-       RegionLock rl (this);
-       RegionList::iterator i;
-       boost::weak_ptr<Playlist> pl (shared_from_this());
+        if (floor (times) != times) {
+                length = (framecnt_t) floor (region->length() * (times - floor (times)));
+                string name;
+                RegionFactory::region_name (name, region->name(), false);
 
-       for (i = regions.begin(); i != regions.end(); ++i) {
-               (*i)->set_playlist (pl);
-       }
-}
+                {
+                        PropertyList plist;
 
-bool
-Playlist::add_region_internal (boost::shared_ptr<Region> region, framepos_t position)
-{
-       if (region->data_type() != _type){
-               return false;
-       }
+                        plist.add (Properties::start, region->start());
+                        plist.add (Properties::length, length);
+                        plist.add (Properties::name, name);
+                        plist.add (Properties::layer, region->layer());
 
-       RegionSortByPosition cmp;
+                        boost::shared_ptr<Region> sub = RegionFactory::create (region, plist);
+                        add_region_internal (sub, pos);
+                        set_layer (sub, DBL_MAX);
+                }
+        }
 
-       framecnt_t old_length = 0;
+        possibly_splice_unlocked (position, (pos + length) - position, region);
+ }
 
-       if (!holding_state()) {
-                old_length = _get_extent().second;
-       }
+ void
+ Playlist::set_region_ownership ()
+ {
+        RegionWriteLock rl (this);
+        RegionList::iterator i;
+        boost::weak_ptr<Playlist> pl (shared_from_this());
 
-       if (!first_set_state) {
-               boost::shared_ptr<Playlist> foo (shared_from_this());
-               region->set_playlist (boost::weak_ptr<Playlist>(foo));
      }
+        for (i = regions.begin(); i != regions.end(); ++i) {
+                (*i)->set_playlist (pl);
+        }
+ }
 
-       region->set_position (position, this);
+ bool
+ Playlist::add_region_internal (boost::shared_ptr<Region> region, framepos_t position)
+ {
+        if (region->data_type() != _type) {
+                return false;
+        }
 
-       timestamp_layer_op (region);
+        RegionSortByPosition cmp;
+
+        if (!first_set_state) {
+                boost::shared_ptr<Playlist> foo (shared_from_this());
+                region->set_playlist (boost::weak_ptr<Playlist>(foo));
+        }
+
+        region->set_position (position);
+
+        regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region);
+        all_regions.insert (region);
+
+        possibly_splice_unlocked (position, region->length(), region);
+
+        if (!holding_state ()) {
+                /* layers get assigned from XML state, and are not reset during undo/redo */
+                relayer ();
+        }
+
+        /* we need to notify the existence of new region before checking dependents. Ick. */
+
+        notify_region_added (region);
+
+        region->PropertyChanged.connect_same_thread (region_state_changed_connections, boost::bind (&Playlist::region_changed_proxy, this, _1, boost::weak_ptr<Region> (region)));
+
+        return true;
+ }
+
+ void
+ Playlist::replace_region (boost::shared_ptr<Region> old, boost::shared_ptr<Region> newr, framepos_t pos)
+ {
+        RegionWriteLock rlock (this);
+
+        bool old_sp = _splicing;
+        _splicing = true;
+
+        remove_region_internal (old);
+        add_region_internal (newr, pos);
+        set_layer (newr, old->layer ());
+
+        _splicing = old_sp;
+
+        possibly_splice_unlocked (pos, old->length() - newr->length());
+ }
+
+ void
+ Playlist::remove_region (boost::shared_ptr<Region> region)
+ {
+        RegionWriteLock rlock (this);
+        remove_region_internal (region);
+ }
+
+ int
+ Playlist::remove_region_internal (boost::shared_ptr<Region> region)
+ {
+        RegionList::iterator i;
+
+        if (!in_set_state) {
+                /* unset playlist */
+                region->set_playlist (boost::weak_ptr<Playlist>());
+        }
+
+        /* XXX should probably freeze here .... */
+
+        for (i = regions.begin(); i != regions.end(); ++i) {
+                if (*i == region) {
+
+                        framepos_t pos = (*i)->position();
+                        framecnt_t distance = (*i)->length();
+
+                        regions.erase (i);
+
+                        possibly_splice_unlocked (pos, -distance);
+
+                        if (!holding_state ()) {
+                                relayer ();
+                                remove_dependents (region);
+                        }
+
+                        notify_region_removed (region);
+                        break;
+                }
+        }
+
+        return -1;
+ }
+
+ void
+ Playlist::get_equivalent_regions (boost::shared_ptr<Region> other, vector<boost::shared_ptr<Region> >& results)
+ {
+        if (Config->get_use_overlap_equivalency()) {
+                for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                        if ((*i)->overlap_equivalent (other)) {
+                                results.push_back (*i);
+                        }
+                }
+        } else {
+                for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                        if ((*i)->equivalent (other)) {
+                                results.push_back (*i);
+                        }
+                }
+        }
+ }
+
+ void
+ Playlist::get_region_list_equivalent_regions (boost::shared_ptr<Region> other, vector<boost::shared_ptr<Region> >& results)
+ {
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+                if ((*i) && (*i)->region_list_equivalent (other)) {
+                        results.push_back (*i);
+                }
+        }
+ }
+
+ void
+ Playlist::get_source_equivalent_regions (boost::shared_ptr<Region> other, vector<boost::shared_ptr<Region> >& results)
+ {
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+                if ((*i) && (*i)->any_source_equivalent (other)) {
+                        results.push_back (*i);
+                }
+        }
+ }
+
+ void
+ Playlist::partition (framepos_t start, framepos_t end, bool cut)
+ {
+        RegionList thawlist;
+
+        partition_internal (start, end, cut, thawlist);
+
+        for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
+                (*i)->resume_property_changes ();
+        }
+ }
+
+/** Go through each region on the playlist and cut them at start and end, removing the section between
+ *  start and end if cutting == true.  Regions that lie entirely within start and end are always
+ *  removed.
+ */
 
-       regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region);
-       all_regions.insert (region);
+ void
+ Playlist::partition_internal (framepos_t start, framepos_t end, bool cutting, RegionList& thawlist)
+ {
+        RegionList new_regions;
 
-       possibly_splice_unlocked (position, region->length(), region);
+        {
+                RegionWriteLock rlock (this);
 
-       if (!holding_state ()) {
-               /* layers get assigned from XML state, and are not reset during undo/redo */
-               relayer ();
-       }
+                boost::shared_ptr<Region> region;
+                boost::shared_ptr<Region> current;
+                string new_name;
+                RegionList::iterator tmp;
+                Evoral::OverlapType overlap;
+                framepos_t pos1, pos2, pos3, pos4;
 
-       /* we need to notify the existence of new region before checking dependents. Ick. */
+                in_partition = true;
 
-       notify_region_added (region);
+                /* need to work from a copy, because otherwise the regions we add during the process
+                   get operated on as well.
+                */
 
-       if (!holding_state ()) {
+                RegionList copy = regions.rlist();
 
-               check_dependents (region, false);
+                for (RegionList::iterator i = copy.begin(); i != copy.end(); i = tmp) {
 
-               if (old_length != _get_extent().second) {
-                       notify_length_changed ();
-               }
-       }
+                        tmp = i;
+                        ++tmp;
 
-       region->PropertyChanged.connect_same_thread (region_state_changed_connections, boost::bind (&Playlist::region_changed_proxy, this, _1, boost::weak_ptr<Region> (region)));
+                        current = *i;
 
-       return true;
-}
+                        if (current->first_frame() >= start && current->last_frame() < end) {
 
-void
-Playlist::replace_region (boost::shared_ptr<Region> old, boost::shared_ptr<Region> newr, framepos_t pos)
-{
-       RegionLock rlock (this);
+                                if (cutting) {
+                                        remove_region_internal (current);
+                                }
 
-       bool old_sp = _splicing;
-       _splicing = true;
+                                continue;
+                        }
 
-       remove_region_internal (old);
-       add_region_internal (newr, pos);
+                        /* coverage will return OverlapStart if the start coincides
+                           with the end point. we do not partition such a region,
+                           so catch this special case.
+                        */
 
-       _splicing = old_sp;
+                        if (current->first_frame() >= end) {
+                                continue;
+                        }
 
-       possibly_splice_unlocked (pos, old->length() - newr->length());
-}
+                        if ((overlap = current->coverage (start, end)) == Evoral::OverlapNone) {
+                                continue;
+                        }
 
-void
-Playlist::remove_region (boost::shared_ptr<Region> region)
-{
-       RegionLock rlock (this);
-       remove_region_internal (region);
-}
+                        pos1 = current->position();
+                        pos2 = start;
+                        pos3 = end;
+                        pos4 = current->last_frame();
 
-int
-Playlist::remove_region_internal (boost::shared_ptr<Region> region)
-{
-       RegionList::iterator i;
-       framecnt_t old_length = 0;
-       int ret = -1;
+                        if (overlap == Evoral::OverlapInternal) {
+                                /* split: we need 3 new regions, the front, middle and end.
+                                   cut:   we need 2 regions, the front and end.
+                                */
 
-       if (!holding_state()) {
-               old_length = _get_extent().second;
-       }
+                                /*
+                                         start                 end
+                          ---------------*************************------------
+                                         P1  P2              P3  P4
+                          SPLIT:
+                          ---------------*****++++++++++++++++====------------
+                          CUT
+                          ---------------*****----------------====------------
 
-       if (!in_set_state) {
-               /* unset playlist */
-               region->set_playlist (boost::weak_ptr<Playlist>());
-       }
+                                */
 
-       /* XXX should probably freeze here .... */
+                                if (!cutting) {
+                                        /* "middle" ++++++ */
 
-       for (i = regions.begin(); i != regions.end(); ++i) {
-               if (*i == region) {
+                                        RegionFactory::region_name (new_name, current->name(), false);
 
-                       framepos_t pos = (*i)->position();
-                       framecnt_t distance = (*i)->length();
+                                        PropertyList plist;
 
-                       regions.erase (i);
+                                        plist.add (Properties::start, current->start() + (pos2 - pos1));
+                                        plist.add (Properties::length, pos3 - pos2);
+                                        plist.add (Properties::name, new_name);
+                                        plist.add (Properties::layer, current->layer ());
+                                        plist.add (Properties::layering_index, current->layering_index ());
+                                        plist.add (Properties::automatic, true);
+                                        plist.add (Properties::left_of_split, true);
+                                        plist.add (Properties::right_of_split, true);
 
-                       possibly_splice_unlocked (pos, -distance);
+                                        region = RegionFactory::create (current, plist);
+                                        add_region_internal (region, start);
+                                        new_regions.push_back (region);
+                                }
 
-                       if (!holding_state ()) {
-                                relayer ();
-                               remove_dependents (region);
+                                /* "end" ====== */
 
-                               if (old_length != _get_extent().second) {
-                                       notify_length_changed ();
-                               }
-                       }
+                                RegionFactory::region_name (new_name, current->name(), false);
 
-                       notify_region_removed (region);
-                       ret = 0;
-                       break;
-               }
-       }
+                                PropertyList plist;
 
-       return -1;
-}
+                                plist.add (Properties::start, current->start() + (pos3 - pos1));
+                                plist.add (Properties::length, pos4 - pos3);
+                                plist.add (Properties::name, new_name);
+                                plist.add (Properties::layer, current->layer ());
+                                plist.add (Properties::layering_index, current->layering_index ());
+                                plist.add (Properties::automatic, true);
+                                plist.add (Properties::right_of_split, true);
 
-void
-Playlist::get_equivalent_regions (boost::shared_ptr<Region> other, vector<boost::shared_ptr<Region> >& results)
-{
-       if (Config->get_use_overlap_equivalency()) {
-               for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-                       if ((*i)->overlap_equivalent (other)) {
-                               results.push_back ((*i));
-                       }
-               }
-       } else {
-               for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-                       if ((*i)->equivalent (other)) {
-                               results.push_back ((*i));
-                       }
-               }
-       }
-}
+                                region = RegionFactory::create (current, plist);
 
-void
-Playlist::get_region_list_equivalent_regions (boost::shared_ptr<Region> other, vector<boost::shared_ptr<Region> >& results)
-{
-       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                                add_region_internal (region, end);
+                                new_regions.push_back (region);
 
-               if ((*i) && (*i)->region_list_equivalent (other)) {
-                       results.push_back (*i);
-               }
-       }
-}
+                                /* "front" ***** */
 
-void
-Playlist::partition (framepos_t start, framepos_t end, bool cut)
-{
-       RegionList thawlist;
+                                current->suspend_property_changes ();
+                                thawlist.push_back (current);
+                                current->cut_end (pos2 - 1);
 
-       partition_internal (start, end, cut, thawlist);
+                        } else if (overlap == Evoral::OverlapEnd) {
 
-       for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
-               (*i)->resume_property_changes ();
-       }
-}
+                                /*
+                                                              start           end
+                                    ---------------*************************------------
+                                                   P1           P2         P4   P3
+                                    SPLIT:
+                                    ---------------**************+++++++++++------------
+                                    CUT:
+                                    ---------------**************-----------------------
+                                */
 
-void
-Playlist::partition_internal (framepos_t start, framepos_t end, bool cutting, RegionList& thawlist)
-{
-       RegionList new_regions;
+                                if (!cutting) {
 
-       {
-               RegionLock rlock (this);
+                                        /* end +++++ */
 
-               boost::shared_ptr<Region> region;
-               boost::shared_ptr<Region> current;
-               string new_name;
-               RegionList::iterator tmp;
-               OverlapType overlap;
-               framepos_t pos1, pos2, pos3, pos4;
+                                        RegionFactory::region_name (new_name, current->name(), false);
 
-               in_partition = true;
+                                        PropertyList plist;
 
-               /* need to work from a copy, because otherwise the regions we add during the process
-                  get operated on as well.
-               */
+                                        plist.add (Properties::start, current->start() + (pos2 - pos1));
+                                        plist.add (Properties::length, pos4 - pos2);
+                                        plist.add (Properties::name, new_name);
+                                        plist.add (Properties::layer, current->layer ());
+                                        plist.add (Properties::layering_index, current->layering_index ());
+                                        plist.add (Properties::automatic, true);
+                                        plist.add (Properties::left_of_split, true);
 
-               RegionList copy = regions.rlist();
+                                        region = RegionFactory::create (current, plist);
 
-               for (RegionList::iterator i = copy.begin(); i != copy.end(); i = tmp) {
+                                        add_region_internal (region, start);
+                                        new_regions.push_back (region);
+                                }
 
-                       tmp = i;
-                       ++tmp;
+                                /* front ****** */
 
-                       current = *i;
+                                current->suspend_property_changes ();
+                                thawlist.push_back (current);
+                                current->cut_end (pos2 - 1);
 
-                       if (current->first_frame() >= start && current->last_frame() < end) {
+                        } else if (overlap == Evoral::OverlapStart) {
 
-                               if (cutting) {
-                                       remove_region_internal (current);
-                               }
+                                /* split: we need 2 regions: the front and the end.
+                                   cut: just trim current to skip the cut area
+                                */
 
-                               continue;
-                       }
+                                /*
+                                                        start           end
+                                    ---------------*************************------------
+                                       P2          P1 P3                   P4
 
-                       /* coverage will return OverlapStart if the start coincides
-                          with the end point. we do not partition such a region,
-                          so catch this special case.
-                       */
+                                    SPLIT:
+                                    ---------------****+++++++++++++++++++++------------
+                                    CUT:
+                                    -------------------*********************------------
 
-                       if (current->first_frame() >= end) {
-                               continue;
-                       }
+                                */
 
-                       if ((overlap = current->coverage (start, end)) == OverlapNone) {
-                               continue;
-                       }
+                                if (!cutting) {
+                                        /* front **** */
+                                        RegionFactory::region_name (new_name, current->name(), false);
 
-                       pos1 = current->position();
-                       pos2 = start;
-                       pos3 = end;
-                       pos4 = current->last_frame();
-
-                       if (overlap == OverlapInternal) {
-                               /* split: we need 3 new regions, the front, middle and end.
-                                  cut:   we need 2 regions, the front and end.
-                               */
-
-                               /*
-                                        start                 end
-                         ---------------*************************------------
-                                        P1  P2              P3  P4
-                         SPLIT:
-                         ---------------*****++++++++++++++++====------------
-                         CUT
-                         ---------------*****----------------====------------
-
-                               */
-
-                               if (!cutting) {
-                                       /* "middle" ++++++ */
-
-                                       RegionFactory::region_name (new_name, current->name(), false);
-
-                                       PropertyList plist;
-                                       
-                                       plist.add (Properties::start, current->start() + (pos2 - pos1));
-                                       plist.add (Properties::length, pos3 - pos2);
-                                       plist.add (Properties::name, new_name);
-                                       plist.add (Properties::layer, regions.size());
-                                       plist.add (Properties::automatic, true);
-                                       plist.add (Properties::left_of_split, true);
-                                       plist.add (Properties::right_of_split, true);
-
-                                       region = RegionFactory::create (current, plist);
-                                       add_region_internal (region, start);
-                                       new_regions.push_back (region);
-                               }
+                                        PropertyList plist;
 
-                               /* "end" ====== */
-
-                               RegionFactory::region_name (new_name, current->name(), false);
-
-                               PropertyList plist;
-                               
-                               plist.add (Properties::start, current->start() + (pos3 - pos1));
-                               plist.add (Properties::length, pos4 - pos3);
-                               plist.add (Properties::name, new_name);
-                               plist.add (Properties::layer, regions.size());
-                               plist.add (Properties::automatic, true);
-                               plist.add (Properties::right_of_split, true);
-                               
-                               region = RegionFactory::create (current, plist);
-
-                               add_region_internal (region, end);
-                               new_regions.push_back (region);
-
-                               /* "front" ***** */
-
-                               current->suspend_property_changes ();
-                               thawlist.push_back (current);
-                               current->cut_end (pos2 - 1, this);
-
-                       } else if (overlap == OverlapEnd) {
-
-                               /*
-                                                             start           end
-                                   ---------------*************************------------
-                                                  P1           P2         P4   P3
-                                    SPLIT:
-                                   ---------------**************+++++++++++------------
-                                    CUT:
-                                   ---------------**************-----------------------
-                               */
-
-                               if (!cutting) {
-
-                                       /* end +++++ */
-
-                                       RegionFactory::region_name (new_name, current->name(), false);
-                                       
-                                       PropertyList plist;
-                                       
-                                       plist.add (Properties::start, current->start() + (pos2 - pos1));
-                                       plist.add (Properties::length, pos4 - pos2);
-                                       plist.add (Properties::name, new_name);
-                                       plist.add (Properties::layer, regions.size());
-                                       plist.add (Properties::automatic, true);
-                                       plist.add (Properties::left_of_split, true);
-
-                                       region = RegionFactory::create (current, plist);
-
-                                       add_region_internal (region, start);
-                                       new_regions.push_back (region);
-                               }
+                                        plist.add (Properties::start, current->start());
+                                        plist.add (Properties::length, pos3 - pos1);
+                                        plist.add (Properties::name, new_name);
+                                        plist.add (Properties::layer, current->layer ());
+                                        plist.add (Properties::layering_index, current->layering_index ());
+                                        plist.add (Properties::automatic, true);
+                                        plist.add (Properties::right_of_split, true);
 
-                               /* front ****** */
+                                        region = RegionFactory::create (current, plist);
 
-                               current->suspend_property_changes ();
-                               thawlist.push_back (current);
-                               current->cut_end (pos2 - 1, this);
+                                        add_region_internal (region, pos1);
+                                        new_regions.push_back (region);
+                                }
 
-                       } else if (overlap == OverlapStart) {
+                                /* end */
 
-                               /* split: we need 2 regions: the front and the end.
-                                  cut: just trim current to skip the cut area
-                               */
+                                current->suspend_property_changes ();
+                                thawlist.push_back (current);
+                                current->trim_front (pos3);
+                        } else if (overlap == Evoral::OverlapExternal) {
 
-                               /*
-                                                       start           end
-                                   ---------------*************************------------
-                                      P2          P1 P3                   P4
+                                /* split: no split required.
+                                   cut: remove the region.
+                                */
 
-                                   SPLIT:
-                                   ---------------****+++++++++++++++++++++------------
-                                   CUT:
-                                   -------------------*********************------------
+                                /*
+                                       start                                      end
+                                    ---------------*************************------------
+                                       P2          P1 P3                   P4
 
-                               */
+                                    SPLIT:
+                                    ---------------*************************------------
+                                    CUT:
+                                    ----------------------------------------------------
 
-                               if (!cutting) {
-                                       /* front **** */
-                                       RegionFactory::region_name (new_name, current->name(), false);
+                                */
 
-                                       PropertyList plist;
-                                       
-                                       plist.add (Properties::start, current->start());
-                                       plist.add (Properties::length, pos3 - pos1);
-                                       plist.add (Properties::name, new_name);
-                                       plist.add (Properties::layer, regions.size());
-                                       plist.add (Properties::automatic, true);
-                                       plist.add (Properties::right_of_split, true);
+                                if (cutting) {
+                                        remove_region_internal (current);
+                                }
 
-                                       region = RegionFactory::create (current, plist);
+                                new_regions.push_back (current);
+                        }
+                }
 
-                                       add_region_internal (region, pos1);
-                                       new_regions.push_back (region);
-                               }
+                in_partition = false;
+        }
 
-                               /* end */
+       //keep track of any dead space at end (for pasting into Ripple or Splice mode)
+       framepos_t wanted_length = end-start;
+       _end_space = wanted_length - get_extent().second-get_extent().first;
+ }
 
-                               current->suspend_property_changes ();
-                               thawlist.push_back (current);
-                               current->trim_front (pos3, this);
-                       } else if (overlap == OverlapExternal) {
+ boost::shared_ptr<Playlist>
+ Playlist::cut_copy (boost::shared_ptr<Playlist> (Playlist::*pmf)(framepos_t, framecnt_t,bool), list<AudioRange>& ranges, bool result_is_hidden)
+ {
+        boost::shared_ptr<Playlist> ret;
+        boost::shared_ptr<Playlist> pl;
+        framepos_t start;
 
-                               /* split: no split required.
-                                  cut: remove the region.
-                               */
+        if (ranges.empty()) {
+                return boost::shared_ptr<Playlist>();
+        }
 
-                               /*
-                                      start                                      end
-                                   ---------------*************************------------
-                                      P2          P1 P3                   P4
+        start = ranges.front().start;
 
-                                   SPLIT:
-                                   ---------------*************************------------
-                                   CUT:
-                                   ----------------------------------------------------
+        for (list<AudioRange>::iterator i = ranges.begin(); i != ranges.end(); ++i) {
+
+                pl = (this->*pmf)((*i).start, (*i).length(), result_is_hidden);
+
+                if (i == ranges.begin()) {
+                        ret = pl;
+                } else {
+
+                        /* paste the next section into the nascent playlist,
+                           offset to reflect the start of the first range we
+                           chopped.
+                        */
+
+                        ret->paste (pl, (*i).start - start, 1.0f);
+                }
+        }
+
+        return ret;
+ }
+
+ boost::shared_ptr<Playlist>
+ Playlist::cut (list<AudioRange>& ranges, bool result_is_hidden)
+ {
+        boost::shared_ptr<Playlist> (Playlist::*pmf)(framepos_t,framecnt_t,bool) = &Playlist::cut;
+        return cut_copy (pmf, ranges, result_is_hidden);
+ }
+
+ boost::shared_ptr<Playlist>
+ Playlist::copy (list<AudioRange>& ranges, bool result_is_hidden)
+ {
+        boost::shared_ptr<Playlist> (Playlist::*pmf)(framepos_t,framecnt_t,bool) = &Playlist::copy;
+        return cut_copy (pmf, ranges, result_is_hidden);
+ }
+
+ boost::shared_ptr<Playlist>
+ Playlist::cut (framepos_t start, framecnt_t cnt, bool result_is_hidden)
+ {
+        boost::shared_ptr<Playlist> the_copy;
+        RegionList thawlist;
+        char buf[32];
+
+        snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
+        string new_name = _name;
+        new_name += '.';
+        new_name += buf;
+
+        if ((the_copy = PlaylistFactory::create (shared_from_this(), start, cnt, new_name, result_is_hidden)) == 0) {
+                return boost::shared_ptr<Playlist>();
+        }
+
+        partition_internal (start, start+cnt-1, true, thawlist);
+
+        for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
+                (*i)->resume_property_changes();
+        }
+
+        return the_copy;
+ }
+
+ boost::shared_ptr<Playlist>
+ Playlist::copy (framepos_t start, framecnt_t cnt, bool result_is_hidden)
+ {
+        char buf[32];
+
+        snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
+        string new_name = _name;
+        new_name += '.';
+        new_name += buf;
+
+       // cnt = min (_get_extent().second - start, cnt);  (We need the full range length when copy/pasting in Ripple.  Why was this limit here?  It's not in CUT... )
+
+        return PlaylistFactory::create (shared_from_this(), start, cnt, new_name, result_is_hidden);
+ }
+
+ int
+ Playlist::paste (boost::shared_ptr<Playlist> other, framepos_t position, float times)
+ {
+        times = fabs (times);
+
+        {
+                RegionReadLock rl2 (other.get());
+
+                int itimes = (int) floor (times);
+                framepos_t pos = position;
+                framecnt_t const shift = other->_get_extent().second;
+                layer_t top = top_layer ();
+
+                {
+                        RegionWriteLock rl1 (this);
+                        while (itimes--) {
+                                for (RegionList::iterator i = other->regions.begin(); i != other->regions.end(); ++i) {
+                                        boost::shared_ptr<Region> copy_of_region = RegionFactory::create (*i, true);
+
+                                        /* put these new regions on top of all existing ones, but preserve
+                                           the ordering they had in the original playlist.
+                                        */
+
+                                        add_region_internal (copy_of_region, (*i)->position() + pos);
+                                        set_layer (copy_of_region, copy_of_region->layer() + top);
+                                }
+                                pos += shift;
+                        }
+                }
+        }
+
+        return 0;
+ }
+
+
+ void
+ Playlist::duplicate (boost::shared_ptr<Region> region, framepos_t position, float times)
+ {
+        duplicate(region, position, region->length(), times);
+ }
+
+/** @param gap from the beginning of the region to the next beginning */
+ void
+ Playlist::duplicate (boost::shared_ptr<Region> region, framepos_t position, framecnt_t gap, float times)
+ {
+        times = fabs (times);
+
+        RegionWriteLock rl (this);
+        int itimes = (int) floor (times);
+
+        while (itimes--) {
+                boost::shared_ptr<Region> copy = RegionFactory::create (region, true);
+                add_region_internal (copy, position);
+                set_layer (copy, DBL_MAX);
+                position += gap;
+        }
+
+        if (floor (times) != times) {
+                framecnt_t length = (framecnt_t) floor (region->length() * (times - floor (times)));
+                string name;
+                RegionFactory::region_name (name, region->name(), false);
+
+                {
+                        PropertyList plist;
+
+                        plist.add (Properties::start, region->start());
+                        plist.add (Properties::length, length);
+                        plist.add (Properties::name, name);
+
+                        boost::shared_ptr<Region> sub = RegionFactory::create (region, plist);
+                        add_region_internal (sub, position);
+                        set_layer (sub, DBL_MAX);
+                }
+        }
+ }
+
+/** @param gap from the beginning of the region to the next beginning */
+/** @param end the first frame that does _not_ contain a duplicated frame */
+void
+Playlist::duplicate_until (boost::shared_ptr<Region> region, framepos_t position, framecnt_t gap, framepos_t end)
+{
+        RegionWriteLock rl (this);
 
-                               */
+        while (position + region->length() - 1 < end) {
+                boost::shared_ptr<Region> copy = RegionFactory::create (region, true);
+                add_region_internal (copy, position);
+                set_layer (copy, DBL_MAX);
+                position += gap;
+        }
 
-                               if (cutting) {
-                                       remove_region_internal (current);
-                               }
+        if (position < end) {
+                framecnt_t length = min (region->length(), end - position);
+                string name;
+                RegionFactory::region_name (name, region->name(), false);
 
-                               new_regions.push_back (current);
-                       }
-               }
+                {
+                        PropertyList plist;
 
-               in_partition = false;
-       }
+                        plist.add (Properties::start, region->start());
+                        plist.add (Properties::length, length);
+                        plist.add (Properties::name, name);
 
-       for (RegionList::iterator i = new_regions.begin(); i != new_regions.end(); ++i) {
-               check_dependents (*i, false);
-       }
+                        boost::shared_ptr<Region> sub = RegionFactory::create (region, plist);
+                        add_region_internal (sub, position);
+                        set_layer (sub, DBL_MAX);
+                }
+        }
 }
 
-boost::shared_ptr<Playlist>
-Playlist::cut_copy (boost::shared_ptr<Playlist> (Playlist::*pmf)(framepos_t, framecnt_t,bool), list<AudioRange>& ranges, bool result_is_hidden)
+void
+Playlist::duplicate_range (AudioRange& range, float times)
 {
-       boost::shared_ptr<Playlist> ret;
-       boost::shared_ptr<Playlist> pl;
-       framepos_t start;
+       boost::shared_ptr<Playlist> pl = copy (range.start, range.length(), true);
+       framecnt_t offset = range.end - range.start;
+       paste (pl, range.start + offset, times);
+}
 
+void
+Playlist::duplicate_ranges (std::list<AudioRange>& ranges, float /* times */)
+{
        if (ranges.empty()) {
-               return boost::shared_ptr<Playlist>();
+               return;
        }
 
-       start = ranges.front().start;
-
-       for (list<AudioRange>::iterator i = ranges.begin(); i != ranges.end(); ++i) {
-
-               pl = (this->*pmf)((*i).start, (*i).length(), result_is_hidden);
-
-               if (i == ranges.begin()) {
-                       ret = pl;
-               } else {
-
-                       /* paste the next section into the nascent playlist,
-                          offset to reflect the start of the first range we
-                          chopped.
-                       */
+       framepos_t min_pos = max_framepos;
+       framepos_t max_pos = 0;
 
-                       ret->paste (pl, (*i).start - start, 1.0f);
-               }
+       for (std::list<AudioRange>::const_iterator i = ranges.begin();
+            i != ranges.end();
+            ++i) {
+               min_pos = min (min_pos, (*i).start);
+               max_pos = max (max_pos, (*i).end);
        }
 
-       return ret;
+       framecnt_t offset = max_pos - min_pos;
+
+       for (list<AudioRange>::iterator i = ranges.begin(); i != ranges.end(); ++i) {
+               boost::shared_ptr<Playlist> pl = copy ((*i).start, (*i).length(), true);
+               paste (pl, (*i).start + offset, 1.0f); // times ??
+       }
+}
+
+ void
+ Playlist::shift (framepos_t at, frameoffset_t distance, bool move_intersected, bool ignore_music_glue)
+ {
+        RegionWriteLock rlock (this);
+        RegionList copy (regions.rlist());
+        RegionList fixup;
+
+        for (RegionList::iterator r = copy.begin(); r != copy.end(); ++r) {
+
+                if ((*r)->last_frame() < at) {
+                        /* too early */
+                        continue;
+                }
+
+                if (at > (*r)->first_frame() && at < (*r)->last_frame()) {
+                        /* intersected region */
+                        if (!move_intersected) {
+                                continue;
+                        }
+                }
+
+                /* do not move regions glued to music time - that
+                   has to be done separately.
+                */
+
+                if (!ignore_music_glue && (*r)->position_lock_style() != AudioTime) {
+                        fixup.push_back (*r);
+                        continue;
+                }
+
+                (*r)->set_position ((*r)->position() + distance);
+        }
+
+        /* XXX: may not be necessary; Region::post_set should do this, I think */
+        for (RegionList::iterator r = fixup.begin(); r != fixup.end(); ++r) {
+                (*r)->recompute_position_from_lock_style ();
+        }
+ }
+
+ void
+ Playlist::split (framepos_t at)
+ {
+        RegionWriteLock rlock (this);
+        RegionList copy (regions.rlist());
+
+        /* use a copy since this operation can modify the region list
+         */
+
+        for (RegionList::iterator r = copy.begin(); r != copy.end(); ++r) {
+                _split_region (*r, at);
+        }
+ }
+
+ void
+ Playlist::split_region (boost::shared_ptr<Region> region, framepos_t playlist_position)
+ {
+        RegionWriteLock rl (this);
+        _split_region (region, playlist_position);
+ }
+
+ void
+ Playlist::_split_region (boost::shared_ptr<Region> region, framepos_t playlist_position)
+ {
+        if (!region->covers (playlist_position)) {
+                return;
+        }
+
+        if (region->position() == playlist_position ||
+            region->last_frame() == playlist_position) {
+                return;
+        }
+
+        boost::shared_ptr<Region> left;
+        boost::shared_ptr<Region> right;
+        frameoffset_t before;
+        frameoffset_t after;
+        string before_name;
+        string after_name;
+
+        /* split doesn't change anything about length, so don't try to splice */
+
+        bool old_sp = _splicing;
+        _splicing = true;
+
+        before = playlist_position - region->position();
+        after = region->length() - before;
+
+        RegionFactory::region_name (before_name, region->name(), false);
+
+        {
+                PropertyList plist;
+
+                plist.add (Properties::length, before);
+                plist.add (Properties::name, before_name);
+                plist.add (Properties::left_of_split, true);
+                plist.add (Properties::layering_index, region->layering_index ());
+                plist.add (Properties::layer, region->layer ());
+
+                /* note: we must use the version of ::create with an offset here,
+                   since it supplies that offset to the Region constructor, which
+                   is necessary to get audio region gain envelopes right.
+                */
+                left = RegionFactory::create (region, 0, plist);
+        }
+
+        RegionFactory::region_name (after_name, region->name(), false);
+
+        {
+                PropertyList plist;
+
+                plist.add (Properties::length, after);
+                plist.add (Properties::name, after_name);
+                plist.add (Properties::right_of_split, true);
+                plist.add (Properties::layering_index, region->layering_index ());
+                plist.add (Properties::layer, region->layer ());
+
+                /* same note as above */
+                right = RegionFactory::create (region, before, plist);
+        }
+
+        add_region_internal (left, region->position());
+        add_region_internal (right, region->position() + before);
+        remove_region_internal (region);
+
+        _splicing = old_sp;
+ }
+
+ void
+ Playlist::possibly_splice (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
+ {
+        if (_splicing || in_set_state) {
+                /* don't respond to splicing moves or state setting */
+                return;
+        }
+
+        if (_edit_mode == Splice) {
+                splice_locked (at, distance, exclude);
+        }
+ }
+
+ void
+ Playlist::possibly_splice_unlocked (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
+ {
+        if (_splicing || in_set_state) {
+                /* don't respond to splicing moves or state setting */
+                return;
+        }
+
+        if (_edit_mode == Splice) {
+                splice_unlocked (at, distance, exclude);
+        }
+ }
+
+ void
+ Playlist::splice_locked (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
+ {
+        {
+                RegionWriteLock rl (this);
+                core_splice (at, distance, exclude);
+        }
+ }
+
+ void
+ Playlist::splice_unlocked (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
+ {
+        core_splice (at, distance, exclude);
+ }
+
+ void
+ Playlist::core_splice (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
+ {
+        _splicing = true;
+
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+                if (exclude && (*i) == exclude) {
+                        continue;
+                }
+
+                if ((*i)->position() >= at) {
+                        framepos_t new_pos = (*i)->position() + distance;
+                        if (new_pos < 0) {
+                                new_pos = 0;
+                        } else if (new_pos >= max_framepos - (*i)->length()) {
+                                new_pos = max_framepos - (*i)->length();
+                        }
+
+                        (*i)->set_position (new_pos);
+                }
+        }
+
+        _splicing = false;
+
+        notify_contents_changed ();
 }
 
-boost::shared_ptr<Playlist>
-Playlist::cut (list<AudioRange>& ranges, bool result_is_hidden)
+void
+Playlist::ripple_locked (framepos_t at, framecnt_t distance, RegionList *exclude)
 {
-       boost::shared_ptr<Playlist> (Playlist::*pmf)(framepos_t,framecnt_t,bool) = &Playlist::cut;
-       return cut_copy (pmf, ranges, result_is_hidden);
+       {
+               RegionWriteLock rl (this);
+               core_ripple (at, distance, exclude);
+       }
 }
 
-boost::shared_ptr<Playlist>
-Playlist::copy (list<AudioRange>& ranges, bool result_is_hidden)
+void
+Playlist::ripple_unlocked (framepos_t at, framecnt_t distance, RegionList *exclude)
 {
-       boost::shared_ptr<Playlist> (Playlist::*pmf)(framepos_t,framecnt_t,bool) = &Playlist::copy;
-       return cut_copy (pmf, ranges, result_is_hidden);
+       core_ripple (at, distance, exclude);
 }
 
-boost::shared_ptr<Playlist>
-Playlist::cut (framepos_t start, framecnt_t cnt, bool result_is_hidden)
+void
+Playlist::core_ripple (framepos_t at, framecnt_t distance, RegionList *exclude)
 {
-       boost::shared_ptr<Playlist> the_copy;
-       RegionList thawlist;
-       char buf[32];
+       if (distance == 0) {
+               return;
+       }
 
-       snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
-       string new_name = _name;
-       new_name += '.';
-       new_name += buf;
+       _rippling = true;
+       RegionListProperty copy = regions;
+       for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) {
+               assert (i != copy.end());
 
-       if ((the_copy = PlaylistFactory::create (shared_from_this(), start, cnt, new_name, result_is_hidden)) == 0) {
-               return boost::shared_ptr<Playlist>();
-       }
+               if (exclude) {
+                       if (std::find(exclude->begin(), exclude->end(), (*i)) != exclude->end()) {
+                               continue;
+                       }
+               }
 
-       partition_internal (start, start+cnt-1, true, thawlist);
+               if ((*i)->position() >= at) {
+                       framepos_t new_pos = (*i)->position() + distance;
+                       framepos_t limit = max_framepos - (*i)->length();
+                       if (new_pos < 0) {
+                               new_pos = 0;
+                       } else if (new_pos >= limit ) {
+                               new_pos = limit;
+                       }
 
-       for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
-               (*i)->resume_property_changes();
+                       (*i)->set_position (new_pos);
+               }
        }
 
-       return the_copy;
+       _rippling = false;
+       notify_contents_changed ();
 }
 
-boost::shared_ptr<Playlist>
-Playlist::copy (framepos_t start, framecnt_t cnt, bool result_is_hidden)
+
+void
+Playlist::region_bounds_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
 {
-       char buf[32];
+        if (in_set_state || _splicing || _rippling || _nudging || _shuffling) {
+                return;
+        }
+
+        if (what_changed.contains (Properties::position)) {
+
+                /* remove it from the list then add it back in
+                   the right place again.
+                */
+
+                RegionSortByPosition cmp;
+
+                RegionList::iterator i = find (regions.begin(), regions.end(), region);
+
+                if (i == regions.end()) {
+                        /* the region bounds are being modified but its not currently
+                           in the region list. we will use its bounds correctly when/if
+                           it is added
+                        */
+                        return;
+                }
+
+                regions.erase (i);
+                regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region);
+        }
+
+        if (what_changed.contains (Properties::position) || what_changed.contains (Properties::length)) {
+
+                frameoffset_t delta = 0;
+
+                if (what_changed.contains (Properties::position)) {
+                        delta = region->position() - region->last_position();
+                }
+
+                if (what_changed.contains (Properties::length)) {
+                        delta += region->length() - region->last_length();
+                }
+
+                if (delta) {
+                        possibly_splice (region->last_position() + region->last_length(), delta, region);
+                }
+
+                if (holding_state ()) {
+                        pending_bounds.push_back (region);
+                } else {
+                        notify_contents_changed ();
+                        relayer ();
+                        list<Evoral::Range<framepos_t> > xf;
+                        xf.push_back (Evoral::Range<framepos_t> (region->last_range()));
+                        xf.push_back (Evoral::Range<framepos_t> (region->range()));
+                        coalesce_and_check_crossfades (xf);
+                }
+        }
+ }
+
+ void
+ Playlist::region_changed_proxy (const PropertyChange& what_changed, boost::weak_ptr<Region> weak_region)
+ {
+        boost::shared_ptr<Region> region (weak_region.lock());
+
+        if (!region) {
+                return;
+        }
+
+        /* this makes a virtual call to the right kind of playlist ... */
+
+        region_changed (what_changed, region);
+ }
+
+ bool
+ Playlist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
+ {
+        PropertyChange our_interests;
+        PropertyChange bounds;
+        PropertyChange pos_and_length;
+        bool save = false;
+
+        if (in_set_state || in_flush) {
+                return false;
+        }
+
+        our_interests.add (Properties::muted);
+        our_interests.add (Properties::layer);
+        our_interests.add (Properties::opaque);
+
+        bounds.add (Properties::start);
+        bounds.add (Properties::position);
+        bounds.add (Properties::length);
+
+        pos_and_length.add (Properties::position);
+        pos_and_length.add (Properties::length);
+
+        if (what_changed.contains (bounds)) {
+                region_bounds_changed (what_changed, region);
+                save = !(_splicing || _nudging);
+        }
+
+        if (what_changed.contains (Properties::position) && !what_changed.contains (Properties::length)) {
+                notify_region_moved (region);
+        } else if (!what_changed.contains (Properties::position) && what_changed.contains (Properties::length)) {
+                notify_region_end_trimmed (region);
+        } else if (what_changed.contains (Properties::position) && what_changed.contains (Properties::length)) {
+                notify_region_start_trimmed (region);
+        }
+
+        /* don't notify about layer changes, since we are the only object that can initiate
+           them, and we notify in ::relayer()
+        */
 
-       snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
-       string new_name = _name;
-       new_name += '.';
-       new_name += buf;
+        if (what_changed.contains (our_interests)) {
+                save = true;
+        }
 
-       cnt = min (_get_extent().second - start, cnt);
-       return PlaylistFactory::create (shared_from_this(), start, cnt, new_name, result_is_hidden);
-}
+        mark_session_dirty ();
 
-int
-Playlist::paste (boost::shared_ptr<Playlist> other, framepos_t position, float times)
-{
-       times = fabs (times);
+        return save;
+ }
 
-       {
-               RegionLock rl1 (this);
-               RegionLock rl2 (other.get());
+ void
+ Playlist::drop_regions ()
+ {
+        RegionWriteLock rl (this);
+        regions.clear ();
+        all_regions.clear ();
+ }
 
-               framecnt_t const old_length = _get_extent().second;
+ void
+ Playlist::sync_all_regions_with_regions ()
+ {
+        RegionWriteLock rl (this);
 
-               int itimes = (int) floor (times);
-               framepos_t pos = position;
-               framecnt_t const shift = other->_get_extent().second;
-               layer_t top_layer = regions.size();
+        all_regions.clear ();
 
-               while (itimes--) {
-                       for (RegionList::iterator i = other->regions.begin(); i != other->regions.end(); ++i) {
-                               boost::shared_ptr<Region> copy_of_region = RegionFactory::create (*i);
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                all_regions.insert (*i);
+        }
+ }
 
-                               /* put these new regions on top of all existing ones, but preserve
-                                  the ordering they had in the original playlist.
-                               */
+ void
+ Playlist::clear (bool with_signals)
+ {
+        {
+                RegionWriteLock rl (this);
 
-                               copy_of_region->set_layer (copy_of_region->layer() + top_layer);
-                               add_region_internal (copy_of_region, copy_of_region->position() + pos);
-                       }
-                       pos += shift;
-               }
+                region_state_changed_connections.drop_connections ();
 
+                for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                        pending_removes.insert (*i);
+                }
 
-               /* XXX shall we handle fractional cases at some point? */
+                regions.clear ();
 
-               if (old_length != _get_extent().second) {
-                       notify_length_changed ();
-               }
+                for (set<boost::shared_ptr<Region> >::iterator s = pending_removes.begin(); s != pending_removes.end(); ++s) {
+                        remove_dependents (*s);
+                }
+        }
 
+        if (with_signals) {
 
-       }
+                for (set<boost::shared_ptr<Region> >::iterator s = pending_removes.begin(); s != pending_removes.end(); ++s) {
+                        RegionRemoved (boost::weak_ptr<Region> (*s)); /* EMIT SIGNAL */
+                }
 
-       return 0;
-}
+                pending_removes.clear ();
+                pending_contents_change = false;
+                ContentsChanged ();
+        }
 
+ }
 
-void
-Playlist::duplicate (boost::shared_ptr<Region> region, framepos_t position, float times)
-{
-       times = fabs (times);
-
-       RegionLock rl (this);
-       int itimes = (int) floor (times);
-       framepos_t pos = position + 1;
-
-       while (itimes--) {
-               boost::shared_ptr<Region> copy = RegionFactory::create (region);
-               add_region_internal (copy, pos);
-               pos += region->length();
-       }
-
-       if (floor (times) != times) {
-               framecnt_t length = (framecnt_t) floor (region->length() * (times - floor (times)));
-               string name;
-               RegionFactory::region_name (name, region->name(), false);
-               
-               {
-                       PropertyList plist;
-                       
-                       plist.add (Properties::start, region->start());
-                       plist.add (Properties::length, length);
-                       plist.add (Properties::name, name);
-                       
-                       boost::shared_ptr<Region> sub = RegionFactory::create (region, plist);
-                       add_region_internal (sub, pos);
-               }
-       }
-}
+ /* *********************************************************************
+  FINDING THINGS
+  **********************************************************************/
 
-void
-Playlist::shift (framepos_t at, frameoffset_t distance, bool move_intersected, bool ignore_music_glue)
-{
-       RegionLock rlock (this);
-       RegionList copy (regions.rlist());
-       RegionList fixup;
-
-       for (RegionList::iterator r = copy.begin(); r != copy.end(); ++r) {
-
-               if ((*r)->last_frame() < at) {
-                       /* too early */
-                       continue;
-               }
-
-               if (at > (*r)->first_frame() && at < (*r)->last_frame()) {
-                       /* intersected region */
-                       if (!move_intersected) {
-                               continue;
-                       }
-               }
-
-               /* do not move regions glued to music time - that
-                  has to be done separately.
-               */
-
-               if (!ignore_music_glue && (*r)->position_lock_style() != AudioTime) {
-                       fixup.push_back (*r);
-                       continue;
-               }
-
-               (*r)->set_position ((*r)->position() + distance, this);
-       }
-
-       for (RegionList::iterator r = fixup.begin(); r != fixup.end(); ++r) {
-               (*r)->recompute_position_from_lock_style ();
-       }
-}
-
-void
-Playlist::split (framepos_t at)
-{
-       RegionLock rlock (this);
-       RegionList copy (regions.rlist());
-
-       /* use a copy since this operation can modify the region list
-        */
-
-       for (RegionList::iterator r = copy.begin(); r != copy.end(); ++r) {
-               _split_region (*r, at);
-       }
-}
-
-void
-Playlist::split_region (boost::shared_ptr<Region> region, framepos_t playlist_position)
+boost::shared_ptr<RegionList>
+Playlist::regions_at (framepos_t frame)
 {
-       RegionLock rl (this);
-       _split_region (region, playlist_position);
+       RegionReadLock rlock (this);
+       return find_regions_at (frame);
 }
 
-void
-Playlist::_split_region (boost::shared_ptr<Region> region, framepos_t playlist_position)
-{
-       if (!region->covers (playlist_position)) {
-               return;
-       }
-
-       if (region->position() == playlist_position ||
-           region->last_frame() == playlist_position) {
-               return;
-       }
-
-       boost::shared_ptr<Region> left;
-       boost::shared_ptr<Region> right;
-       frameoffset_t before;
-       frameoffset_t after;
-       string before_name;
-       string after_name;
-
-       /* split doesn't change anything about length, so don't try to splice */
-
-       bool old_sp = _splicing;
-       _splicing = true;
-
-       before = playlist_position - region->position();
-       after = region->length() - before;
-
-       RegionFactory::region_name (before_name, region->name(), false);
+ uint32_t
+ Playlist::count_regions_at (framepos_t frame) const
+ {
+        RegionReadLock rlock (const_cast<Playlist*>(this));
+        uint32_t cnt = 0;
 
-       {
-               PropertyList plist;
-               
-               plist.add (Properties::start, region->start());
-               plist.add (Properties::length, before);
-               plist.add (Properties::name, before_name);
-               plist.add (Properties::left_of_split, true);
-               
-               left = RegionFactory::create (region, plist);
-       }
+        for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+                if ((*i)->covers (frame)) {
+                        cnt++;
+                }
+        }
 
-       RegionFactory::region_name (after_name, region->name(), false);
+        return cnt;
+ }
 
-       {
-               PropertyList plist;
-               
-               plist.add (Properties::start, region->start() + before);
-               plist.add (Properties::length, after);
-               plist.add (Properties::name, after_name);
-               plist.add (Properties::right_of_split, true);
+ boost::shared_ptr<Region>
+ Playlist::top_region_at (framepos_t frame)
 
-               right = RegionFactory::create (region, plist);
-       }
+ {
+        RegionReadLock rlock (this);
+        boost::shared_ptr<RegionList> rlist = find_regions_at (frame);
+        boost::shared_ptr<Region> region;
 
-       add_region_internal (left, region->position());
-       add_region_internal (right, region->position() + before);
+        if (rlist->size()) {
+                RegionSortByLayer cmp;
+                rlist->sort (cmp);
+                region = rlist->back();
+        }
 
-       uint64_t orig_layer_op = region->last_layer_op();
-       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-               if ((*i)->last_layer_op() > orig_layer_op) {
-                       (*i)->set_last_layer_op( (*i)->last_layer_op() + 1 );
-               }
-       }
-
-       left->set_last_layer_op ( orig_layer_op );
-       right->set_last_layer_op ( orig_layer_op + 1);
+        return region;
+ }
 
-       layer_op_counter++;
+ boost::shared_ptr<Region>
+ Playlist::top_unmuted_region_at (framepos_t frame)
 
-       finalize_split_region (region, left, right);
+ {
+        RegionReadLock rlock (this);
+        boost::shared_ptr<RegionList> rlist = find_regions_at (frame);
 
-       remove_region_internal (region);
+        for (RegionList::iterator i = rlist->begin(); i != rlist->end(); ) {
 
-       _splicing = old_sp;
-}
+                RegionList::iterator tmp = i;
+                ++tmp;
 
-void
-Playlist::possibly_splice (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
-{
-       if (_splicing || in_set_state) {
-               /* don't respond to splicing moves or state setting */
-               return;
-       }
+                if ((*i)->muted()) {
+                        rlist->erase (i);
+                }
 
-       if (_edit_mode == Splice) {
-               splice_locked (at, distance, exclude);
-       }
-}
+                i = tmp;
+        }
 
-void
-Playlist::possibly_splice_unlocked (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
-{
-       if (_splicing || in_set_state) {
-               /* don't respond to splicing moves or state setting */
-               return;
-       }
+        boost::shared_ptr<Region> region;
 
-       if (_edit_mode == Splice) {
-               splice_unlocked (at, distance, exclude);
-       }
-}
+        if (rlist->size()) {
+                RegionSortByLayer cmp;
+                rlist->sort (cmp);
+                region = rlist->back();
+        }
 
-void
-Playlist::splice_locked (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
-{
-       {
-               RegionLock rl (this);
-               core_splice (at, distance, exclude);
-       }
-}
+        return region;
+ }
 
-void
-Playlist::splice_unlocked (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
+boost::shared_ptr<RegionList>
+Playlist::find_regions_at (framepos_t frame)
 {
-       core_splice (at, distance, exclude);
-}
+       /* Caller must hold lock */
 
-void
-Playlist::core_splice (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude)
-{
-       _splicing = true;
+       boost::shared_ptr<RegionList> rlist (new RegionList);
 
        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-
-               if (exclude && (*i) == exclude) {
-                       continue;
-               }
-
-               if ((*i)->position() >= at) {
-                       framepos_t new_pos = (*i)->position() + distance;
-                       if (new_pos < 0) {
-                               new_pos = 0;
-                       } else if (new_pos >= max_frames - (*i)->length()) {
-                               new_pos = max_frames - (*i)->length();
-                       }
-
-                       (*i)->set_position (new_pos, this);
-               }
-       }
-
-       _splicing = false;
-
-       notify_length_changed ();
-}
-
-void
-Playlist::region_bounds_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
-{
-       if (in_set_state || _splicing || _nudging || _shuffling) {
-               return;
-       }
-
-       if (what_changed.contains (Properties::position)) {
-
-               /* remove it from the list then add it back in
-                  the right place again.
-               */
-
-               RegionSortByPosition cmp;
-
-               RegionList::iterator i = find (regions.begin(), regions.end(), region);
-
-               if (i == regions.end()) {
-                        /* the region bounds are being modified but its not currently
-                           in the region list. we will use its bounds correctly when/if
-                           it is added
-                        */
-                       return;
-               }
-
-               regions.erase (i);
-               regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region);
-       }
-
-       if (what_changed.contains (Properties::position) || what_changed.contains (Properties::length)) {
-
-               frameoffset_t delta = 0;
-
-               if (what_changed.contains (Properties::position)) {
-                       delta = region->position() - region->last_position();
-               }
-
-               if (what_changed.contains (Properties::length)) {
-                       delta += region->length() - region->last_length();
-               }
-
-               if (delta) {
-                       possibly_splice (region->last_position() + region->last_length(), delta, region);
-               }
-
-               if (holding_state ()) {
-                       pending_bounds.push_back (region);
-               } else {
-                       if (_session.config.get_layer_model() == MoveAddHigher) {
-                               /* it moved or changed length, so change the timestamp */
-                               timestamp_layer_op (region);
-                       }
-
-                       notify_length_changed ();
-                       relayer ();
-                       check_dependents (region, false);
-               }
-       }
-}
-
-void
-Playlist::region_changed_proxy (const PropertyChange& what_changed, boost::weak_ptr<Region> weak_region)
-{
-       boost::shared_ptr<Region> region (weak_region.lock());
-
-       if (!region) {
-               return;
-       }
-
-       /* this makes a virtual call to the right kind of playlist ... */
-
-       region_changed (what_changed, region);
-}
-
-bool
-Playlist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
-{
-       PropertyChange our_interests;
-       PropertyChange bounds;
-       PropertyChange pos_and_length;
-       bool save = false;
-
-       if (in_set_state || in_flush) {
-               return false;
-       }
-
-       our_interests.add (Properties::muted);
-       our_interests.add (Properties::layer);
-       our_interests.add (Properties::opaque);
-
-       bounds.add (Properties::start);
-       bounds.add (Properties::position);
-       bounds.add (Properties::length);
-
-       pos_and_length.add (Properties::position);
-       pos_and_length.add (Properties::length);
-
-       if (what_changed.contains (bounds)) {
-               region_bounds_changed (what_changed, region);
-               save = !(_splicing || _nudging);
-       }
-
-       if (what_changed.contains (our_interests) && !what_changed.contains (pos_and_length)) {
-               check_dependents (region, false);
-       }
-
-       if (what_changed.contains (Properties::position)) {
-               notify_region_moved (region);
-       }
-
-
-       /* don't notify about layer changes, since we are the only object that can initiate
-          them, and we notify in ::relayer()
-       */
-
-       if (what_changed.contains (our_interests)) {
-               save = true;
-       }
-
-       return save;
-}
-
-void
-Playlist::drop_regions ()
-{
-       RegionLock rl (this);
-       regions.clear ();
-       all_regions.clear ();
-}
-
-void
-Playlist::clear (bool with_signals)
-{
-       {
-               RegionLock rl (this);
-
-               region_state_changed_connections.drop_connections ();
-
-               for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-                       pending_removes.insert (*i);
-               }
-
-               regions.clear ();
-
-                for (set<boost::shared_ptr<Region> >::iterator s = pending_removes.begin(); s != pending_removes.end(); ++s) {
-                        remove_dependents (*s);
-                }
-       }
-
-       if (with_signals) {
-
-                for (set<boost::shared_ptr<Region> >::iterator s = pending_removes.begin(); s != pending_removes.end(); ++s) {
-                        RegionRemoved (boost::weak_ptr<Region> (*s)); /* EMIT SIGNAL */
-                }
-
-                pending_removes.clear ();
-               pending_length = false;
-               LengthChanged ();
-               pending_contents_change = false;
-               ContentsChanged ();
-       }
-
-}
-
-/***********************************************************************
- FINDING THINGS
- **********************************************************************/
-
-Playlist::RegionList *
-Playlist::regions_at (framepos_t frame)
-
-{
-       RegionLock rlock (this);
-       return find_regions_at (frame);
-}
-
-boost::shared_ptr<Region>
-Playlist::top_region_at (framepos_t frame)
-
-{
-       RegionLock rlock (this);
-       RegionList *rlist = find_regions_at (frame);
-       boost::shared_ptr<Region> region;
-
-       if (rlist->size()) {
-               RegionSortByLayer cmp;
-               rlist->sort (cmp);
-               region = rlist->back();
-       }
-
-       delete rlist;
-       return region;
-}
-
-boost::shared_ptr<Region>
-Playlist::top_unmuted_region_at (framepos_t frame)
-
-{
-       RegionLock rlock (this);
-       RegionList *rlist = find_regions_at (frame);
-
-       for (RegionList::iterator i = rlist->begin(); i != rlist->end(); ) {
-
-               RegionList::iterator tmp = i;
-               ++tmp;
-
-               if ((*i)->muted()) {
-                       rlist->erase (i);
+               if ((*i)->covers (frame)) {
+                       rlist->push_back (*i);
                }
-
-               i = tmp;
-       }
-
-       boost::shared_ptr<Region> region;
-
-       if (rlist->size()) {
-               RegionSortByLayer cmp;
-               rlist->sort (cmp);
-               region = rlist->back();
        }
 
-       delete rlist;
-       return region;
+       return rlist;
 }
 
-Playlist::RegionList*
-Playlist::regions_to_read (framepos_t start, framepos_t end)
+boost::shared_ptr<RegionList>
+Playlist::regions_with_start_within (Evoral::Range<framepos_t> range)
 {
-       /* Caller must hold lock */
-
-       RegionList covering;
-       set<framepos_t> to_check;
-       set<boost::shared_ptr<Region> > unique;
-
-       to_check.insert (start);
-       to_check.insert (end);
-
-        DEBUG_TRACE (DEBUG::AudioPlayback, ">>>>> REGIONS TO READ\n");
+       RegionReadLock rlock (this);
+       boost::shared_ptr<RegionList> rlist (new RegionList);
 
        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-
-               /* find all/any regions that span start+end */
-
-               switch ((*i)->coverage (start, end)) {
-               case OverlapNone:
-                       break;
-
-               case OverlapInternal:
-                       covering.push_back (*i);
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("toread: will cover %1 (OInternal)\n", (*i)->name()));
-                       break;
-
-               case OverlapStart:
-                       to_check.insert ((*i)->position());
-                        if ((*i)->position() != 0) {
-                                to_check.insert ((*i)->position()-1);
-                        }
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("toread: will check %1 for %2\n", (*i)->position(), (*i)->name()));
-                       covering.push_back (*i);
-                       break;
-
-               case OverlapEnd:
-                       to_check.insert ((*i)->last_frame());
-                       to_check.insert ((*i)->last_frame()+1);
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("toread: will cover %1 (OEnd)\n", (*i)->name()));
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\ttoread: will check %1 for %2\n", (*i)->last_frame(), (*i)->name()));
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\ttoread: will check %1 for %2\n", (*i)->last_frame(), (*i)->name()));
-                       covering.push_back (*i);
-                       break;
-
-               case OverlapExternal:
-                       covering.push_back (*i);
-                       to_check.insert ((*i)->position());
-                        if ((*i)->position() != 0) {
-                                to_check.insert ((*i)->position()-1);
-                        }
-                       to_check.insert ((*i)->last_frame());
-                       to_check.insert ((*i)->last_frame()+1);
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("toread: will cover %1 (OExt)\n", (*i)->name()));
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\ttoread: will check %1 for %2\n", (*i)->position(), (*i)->name()));
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\ttoread: will check %1 for %2\n", (*i)->last_frame(), (*i)->name()));
-                       break;
-               }
-
-               /* don't go too far */
-
-               if ((*i)->position() > end) {
-                       break;
-               }
-       }
-
-       RegionList* rlist = new RegionList;
-
-       /* find all the regions that cover each position .... */
-
-       if (covering.size() == 1) {
-
-               rlist->push_back (covering.front());
-                DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Just one covering region (%1)\n", covering.front()->name()));
-
-       } else {
-
-               RegionList here;
-               for (set<framepos_t>::iterator t = to_check.begin(); t != to_check.end(); ++t) {
-
-                       here.clear ();
-
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("++++ Considering %1\n", *t));
-
-                       for (RegionList::iterator x = covering.begin(); x != covering.end(); ++x) {
-
-                               if ((*x)->covers (*t)) {
-                                       here.push_back (*x);
-                                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("region %1 covers %2\n",
-                                                                                           (*x)->name(),
-                                                                                           (*t)));
-                               } else {
-                                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("region %1 does NOT covers %2\n",
-                                                                                           (*x)->name(),
-                                                                                           (*t)));
-                                }
-                                        
-                       }
-
-                       RegionSortByLayer cmp;
-                       here.sort (cmp);
-
-                       /* ... and get the top/transparent regions at "here" */
-
-                       for (RegionList::reverse_iterator c = here.rbegin(); c != here.rend(); ++c) {
-
-                               unique.insert (*c);
-
-                               if ((*c)->opaque()) {
-
-                                       /* the other regions at this position are hidden by this one */
-                                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("%1 is opaque, ignore all others\n",
-                                                                                           (*c)->name()));
-                                       break;
-                               }
-                       }
-               }
-
-               for (set<boost::shared_ptr<Region> >::iterator s = unique.begin(); s != unique.end(); ++s) {
-                       rlist->push_back (*s);
-               }
-
-               if (rlist->size() > 1) {
-                       /* now sort by time order */
-
-                       RegionSortByPosition cmp;
-                       rlist->sort (cmp);
+               if ((*i)->first_frame() >= range.from && (*i)->first_frame() <= range.to) {
+                       rlist->push_back (*i);
                }
        }
 
-        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("<<<<< REGIONS TO READ returns %1\n", rlist->size()));
-
        return rlist;
 }
 
-Playlist::RegionList *
-Playlist::find_regions_at (framepos_t frame)
+boost::shared_ptr<RegionList>
+Playlist::regions_with_end_within (Evoral::Range<framepos_t> range)
 {
-       /* Caller must hold lock */
-
-       RegionList *rlist = new RegionList;
+       RegionReadLock rlock (this);
+       boost::shared_ptr<RegionList> rlist (new RegionList);
 
        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-               if ((*i)->covers (frame)) {
+               if ((*i)->last_frame() >= range.from && (*i)->last_frame() <= range.to) {
                        rlist->push_back (*i);
                }
        }
@@ -1887,14 +1888,24 @@ Playlist::find_regions_at (framepos_t frame)
        return rlist;
 }
 
-Playlist::RegionList *
+/** @param start Range start.
+ *  @param end Range end.
+ *  @return regions which have some part within this range.
+ */
+boost::shared_ptr<RegionList>
 Playlist::regions_touched (framepos_t start, framepos_t end)
 {
-       RegionLock rlock (this);
-       RegionList *rlist = new RegionList;
+       RegionReadLock rlock (this);
+       return regions_touched_locked (start, end);
+}
+
+boost::shared_ptr<RegionList>
+Playlist::regions_touched_locked (framepos_t start, framepos_t end)
+{
+       boost::shared_ptr<RegionList> rlist (new RegionList);
 
        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-               if ((*i)->coverage (start, end) != OverlapNone) {
+               if ((*i)->coverage (start, end) != Evoral::OverlapNone) {
                        rlist->push_back (*i);
                }
        }
@@ -1905,7 +1916,7 @@ Playlist::regions_touched (framepos_t start, framepos_t end)
 framepos_t
 Playlist::find_next_transient (framepos_t from, int dir)
 {
-       RegionLock rlock (this);
+       RegionReadLock rlock (this);
        AnalysisFeatureList points;
        AnalysisFeatureList these_points;
 
@@ -1965,9 +1976,9 @@ Playlist::find_next_transient (framepos_t from, int dir)
 boost::shared_ptr<Region>
 Playlist::find_next_region (framepos_t frame, RegionPoint point, int dir)
 {
-       RegionLock rlock (this);
+       RegionReadLock rlock (this);
        boost::shared_ptr<Region> ret;
-       framepos_t closest = max_frames;
+       framepos_t closest = max_framepos;
 
        bool end_iter = false;
 
@@ -1988,7 +1999,6 @@ Playlist::find_next_region (framepos_t frame, RegionPoint point, int dir)
                        break;
                case SyncPoint:
                        pos = r->sync_position ();
-                       // r->adjust_to_sync (r->first_frame());
                        break;
                }
 
@@ -2012,8 +2022,7 @@ Playlist::find_next_region (framepos_t frame, RegionPoint point, int dir)
                                        closest = distance;
                                        ret = r;
                                }
-                       }
-                       else {
+                       } else {
                                end_iter = true;
                        }
 
@@ -2024,254 +2033,214 @@ Playlist::find_next_region (framepos_t frame, RegionPoint point, int dir)
        return ret;
 }
 
-framepos_t
-Playlist::find_next_region_boundary (framepos_t frame, int dir)
-{
-       RegionLock rlock (this);
+ framepos_t
+ Playlist::find_next_region_boundary (framepos_t frame, int dir)
+ {
+        RegionReadLock rlock (this);
 
-       framepos_t closest = max_frames;
-       framepos_t ret = -1;
+        framepos_t closest = max_framepos;
+        framepos_t ret = -1;
 
-       if (dir > 0) {
+        if (dir > 0) {
 
-               for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
 
-                       boost::shared_ptr<Region> r = (*i);
-                       frameoffset_t distance;
+                        boost::shared_ptr<Region> r = (*i);
+                        frameoffset_t distance;
 
-                       if (r->first_frame() > frame) {
+                        if (r->first_frame() > frame) {
 
-                               distance = r->first_frame() - frame;
+                                distance = r->first_frame() - frame;
 
-                               if (distance < closest) {
-                                       ret = r->first_frame();
-                                       closest = distance;
-                               }
-                       }
+                                if (distance < closest) {
+                                        ret = r->first_frame();
+                                        closest = distance;
+                                }
+                        }
 
-                       if (r->last_frame () > frame) {
+                        if (r->last_frame () > frame) {
 
-                               distance = r->last_frame () - frame;
+                                distance = r->last_frame () - frame;
 
-                               if (distance < closest) {
-                                       ret = r->last_frame ();
-                                       closest = distance;
-                               }
-                       }
-               }
+                                if (distance < closest) {
+                                        ret = r->last_frame ();
+                                        closest = distance;
+                                }
+                        }
+                }
 
-       } else {
+        } else {
 
-               for (RegionList::reverse_iterator i = regions.rbegin(); i != regions.rend(); ++i) {
+                for (RegionList::reverse_iterator i = regions.rbegin(); i != regions.rend(); ++i) {
 
-                       boost::shared_ptr<Region> r = (*i);
-                       frameoffset_t distance;
+                        boost::shared_ptr<Region> r = (*i);
+                        frameoffset_t distance;
 
-                       if (r->last_frame() < frame) {
+                        if (r->last_frame() < frame) {
 
-                               distance = frame - r->last_frame();
+                                distance = frame - r->last_frame();
 
-                               if (distance < closest) {
-                                       ret = r->last_frame();
-                                       closest = distance;
-                               }
-                       }
+                                if (distance < closest) {
+                                        ret = r->last_frame();
+                                        closest = distance;
+                                }
+                        }
 
-                       if (r->first_frame() < frame) {
+                        if (r->first_frame() < frame) {
 
-                               distance = frame - r->first_frame();
+                                distance = frame - r->first_frame();
 
-                               if (distance < closest) {
-                                       ret = r->first_frame();
-                                       closest = distance;
-                               }
-                       }
-               }
-       }
+                                if (distance < closest) {
+                                        ret = r->first_frame();
+                                        closest = distance;
+                                }
+                        }
+                }
+        }
 
-       return ret;
-}
+        return ret;
+ }
 
 
-/***********************************************************************/
+ /***********************************************************************/
 
 
 
 
-void
-Playlist::mark_session_dirty ()
-{
-       if (!in_set_state && !holding_state ()) {
-               _session.set_dirty();
-       }
-}
+ void
+ Playlist::mark_session_dirty ()
+ {
+        if (!in_set_state && !holding_state ()) {
+                _session.set_dirty();
+        }
+ }
 
-bool
-Playlist::set_property (const PropertyBase& prop)
-{
-        if (prop == Properties::regions.property_id) {
-                const RegionListProperty::ChangeRecord& change (dynamic_cast<const RegionListProperty*>(&prop)->change());
-                regions.update (change);
-                return (!change.added.empty() && !change.removed.empty());
-        }
-        return false;
-}
+ void
+ Playlist::rdiff (vector<Command*>& cmds) const
+ {
+        RegionReadLock rlock (const_cast<Playlist *> (this));
+        Stateful::rdiff (cmds);
+ }
 
-void
-Playlist::rdiff (vector<StatefulDiffCommand*>& cmds) const
-{
-       RegionLock rlock (const_cast<Playlist *> (this));
+ void
+ Playlist::clear_owned_changes ()
+ {
+        RegionReadLock rlock (this);
+        Stateful::clear_owned_changes ();
+ }
 
-       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
-               if ((*i)->changed ()) {
-                        StatefulDiffCommand* sdc = new StatefulDiffCommand (*i);
-                        cmds.push_back (sdc);
-                }
-       }
-}
+ void
+ Playlist::update (const RegionListProperty::ChangeRecord& change)
+ {
+        DEBUG_TRACE (DEBUG::Properties, string_compose ("Playlist %1 updates from a change record with %2 adds %3 removes\n",
+                                                        name(), change.added.size(), change.removed.size()));
 
-void
-Playlist::clear_owned_history ()
-{
-       RegionLock rlock (this);
+        freeze ();
+        /* add the added regions */
+        for (RegionListProperty::ChangeContainer::const_iterator i = change.added.begin(); i != change.added.end(); ++i) {
+                add_region_internal ((*i), (*i)->position());
+        }
+        /* remove the removed regions */
+        for (RegionListProperty::ChangeContainer::const_iterator i = change.removed.begin(); i != change.removed.end(); ++i) {
+                remove_region (*i);
+        }
 
-       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-                (*i)->clear_history ();
-        }
-}
+        thaw ();
+ }
 
-void
-Playlist::update (const RegionListProperty::ChangeRecord& change)
-{
-        DEBUG_TRACE (DEBUG::Properties, string_compose ("Playlist %1 updates from a change record with %2 adds %3 removes\n", 
-                                                        name(), change.added.size(), change.removed.size()));
-        
-        freeze ();
-        /* add the added regions */
-        for (RegionListProperty::ChangeContainer::iterator i = change.added.begin(); i != change.added.end(); ++i) {
-                add_region ((*i), (*i)->position());
-        }
-        /* remove the removed regions */
-        for (RegionListProperty::ChangeContainer::iterator i = change.removed.begin(); i != change.removed.end(); ++i) {
-                remove_region (*i);
-        }
-
-        thaw ();
-}
-
-PropertyList*
-Playlist::property_factory (const XMLNode& history_node) const
-{
-        const XMLNodeList& children (history_node.children());
-        PropertyList* prop_list = 0;
-
-        for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
-
-                if ((*i)->name() == capitalize (regions.property_name())) {
-                        
-                        RegionListProperty* rlp = new RegionListProperty (*const_cast<Playlist*> (this));
-
-                        if (rlp->load_history_state (**i)) {
-                                if (!prop_list) {
-                                        prop_list = new PropertyList();
-                                }
-                                prop_list->add (rlp);
-                        } else {
-                                delete rlp;
-                        }
-                }
-        }
-
-        return prop_list;
-}
-
-int
-Playlist::set_state (const XMLNode& node, int version)
-{
-       XMLNode *child;
-       XMLNodeList nlist;
-       XMLNodeConstIterator niter;
-       XMLPropertyList plist;
-       XMLPropertyConstIterator piter;
-       XMLProperty *prop;
-       boost::shared_ptr<Region> region;
-       string region_name;
-
-       in_set_state++;
-
-       if (node.name() != "Playlist") {
-               in_set_state--;
-               return -1;
-       }
-
-       freeze ();
+ int
+ Playlist::set_state (const XMLNode& node, int version)
+ {
+        XMLNode *child;
+        XMLNodeList nlist;
+        XMLNodeConstIterator niter;
+        XMLPropertyList plist;
+        XMLPropertyConstIterator piter;
+        XMLProperty *prop;
+        boost::shared_ptr<Region> region;
+        string region_name;
+        bool seen_region_nodes = false;
+        int ret = 0;
 
-       plist = node.properties();
+        in_set_state++;
 
-       for (piter = plist.begin(); piter != plist.end(); ++piter) {
+        if (node.name() != "Playlist") {
+                in_set_state--;
+                return -1;
+        }
 
-               prop = *piter;
+        freeze ();
 
-               if (prop->name() == X_("name")) {
-                       _name = prop->value();
-                        _set_sort_id ();
-               } else if (prop->name() == X_("id")) {
-                        _id = prop->value();
-               } else if (prop->name() == X_("orig_diskstream_id")) {
-                       _orig_diskstream_id = prop->value ();
-               } else if (prop->name() == X_("frozen")) {
-                       _frozen = string_is_affirmative (prop->value());
-               }
-       }
+        plist = node.properties();
 
-       clear (true);
+        set_id (node);
 
-       nlist = node.children();
+        for (piter = plist.begin(); piter != plist.end(); ++piter) {
 
-       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+                prop = *piter;
 
-               child = *niter;
+                if (prop->name() == X_("name")) {
+                        _name = prop->value();
+                        _set_sort_id ();
+                } else if (prop->name() == X_("orig-diskstream-id")) {
+                        /* XXX legacy session: fix up later */
+                        _orig_track_id = prop->value ();
+                } else if (prop->name() == X_("orig-track-id")) {
+                        _orig_track_id = prop->value ();
+                } else if (prop->name() == X_("frozen")) {
+                        _frozen = string_is_affirmative (prop->value());
+                } else if (prop->name() == X_("combine-ops")) {
+                        _combine_ops = atoi (prop->value());
+                }
+        }
 
-               if (child->name() == "Region") {
+        clear (true);
 
-                       if ((prop = child->property ("id")) == 0) {
-                               error << _("region state node has no ID, ignored") << endmsg;
-                               continue;
-                       }
-                       
-                       ID id = prop->value ();
+        nlist = node.children();
 
-                       if ((region = region_by_id (id))) {
+        for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
 
-                               region->suspend_property_changes ();
+                child = *niter;
 
-                               if (region->set_state (*child, version)) {
-                                       region->resume_property_changes ();
-                                       continue;
-                               }
-                               
-                       } else if ((region = RegionFactory::create (_session, *child, true)) != 0) {
-                               region->suspend_property_changes ();
-                       } else {
-                               error << _("Playlist: cannot create region from XML") << endmsg;
-                               continue;
+                if (child->name() == "Region") {
+
+                        seen_region_nodes = true;
+
+                        if ((prop = child->property ("id")) == 0) {
+                                error << _("region state node has no ID, ignored") << endmsg;
+                                continue;
+                        }
+
+                        ID id = prop->value ();
+
+                        if ((region = region_by_id (id))) {
+
+                                region->suspend_property_changes ();
+
+                                if (region->set_state (*child, version)) {
+                                        region->resume_property_changes ();
+                                        continue;
+                                }
+
+                        } else if ((region = RegionFactory::create (_session, *child, true)) != 0) {
+                                region->suspend_property_changes ();
+                        } else {
+                                error << _("Playlist: cannot create region from XML") << endmsg;
+                               return -1;
                        }
 
+                        {
+                                RegionWriteLock rlock (this);
+                                add_region_internal (region, region->position());
+                        }
 
-                       add_region (region, region->position(), 1.0);
-
-                       // So that layer_op ordering doesn't get screwed up
-                       region->set_last_layer_op( region->layer());
                        region->resume_property_changes ();
+
                }
        }
 
-       /* update dependents, which was not done during add_region_internal
-          due to in_set_state being true
-       */
-
-       for (RegionList::iterator r = regions.begin(); r != regions.end(); ++r) {
-               check_dependents (*r, false);
+       if (seen_region_nodes && regions.empty()) {
+               ret = -1;
        }
 
        thaw ();
@@ -2279,7 +2248,8 @@ Playlist::set_state (const XMLNode& node, int version)
 
        in_set_state--;
        first_set_state = false;
-       return 0;
+
+       return ret;
 }
 
 XMLNode&
@@ -2300,18 +2270,21 @@ XMLNode&
 Playlist::state (bool full_state)
 {
        XMLNode *node = new XMLNode (X_("Playlist"));
-       char buf[64] = "";
+       char buf[64];
 
        node->add_property (X_("id"), id().to_s());
        node->add_property (X_("name"), _name);
        node->add_property (X_("type"), _type.to_string());
 
-       _orig_diskstream_id.print (buf, sizeof (buf));
-       node->add_property (X_("orig_diskstream_id"), buf);
+       _orig_track_id.print (buf, sizeof (buf));
+       node->add_property (X_("orig-track-id"), buf);
        node->add_property (X_("frozen"), _frozen ? "yes" : "no");
 
        if (full_state) {
-               RegionLock rlock (this, false);
+               RegionReadLock rlock (this);
+
+               snprintf (buf, sizeof (buf), "%u", _combine_ops);
+               node->add_property ("combine-ops", buf);
 
                for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
                        node->add_child_nocopy ((*i)->get_state());
@@ -2328,31 +2301,54 @@ Playlist::state (bool full_state)
 bool
 Playlist::empty() const
 {
-       RegionLock rlock (const_cast<Playlist *>(this), false);
+       RegionReadLock rlock (const_cast<Playlist *>(this));
        return regions.empty();
 }
 
 uint32_t
 Playlist::n_regions() const
 {
-       RegionLock rlock (const_cast<Playlist *>(this), false);
+       RegionReadLock rlock (const_cast<Playlist *>(this));
        return regions.size();
 }
 
-pair<framecnt_t, framecnt_t>
+/** @return true if the all_regions list is empty, ie this playlist
+ *  has never had a region added to it.
+ */
+bool
+Playlist::all_regions_empty() const
+{
+       RegionReadLock rl (const_cast<Playlist *> (this));
+       return all_regions.empty();
+}
+
+pair<framepos_t, framepos_t>
 Playlist::get_extent () const
 {
-       RegionLock rlock (const_cast<Playlist *>(this), false);
+       RegionReadLock rlock (const_cast<Playlist *>(this));
        return _get_extent ();
 }
 
-pair<framecnt_t, framecnt_t>
+pair<framepos_t, framepos_t>
+Playlist::get_extent_with_endspace () const
+{
+       pair<framepos_t, framepos_t> l = get_extent();
+       l.second += _end_space;
+       return l;
+}
+
+pair<framepos_t, framepos_t>
 Playlist::_get_extent () const
 {
-       pair<framecnt_t, framecnt_t> ext (max_frames, 0);
+       pair<framepos_t, framepos_t> ext (max_framepos, 0);
+
+       if (regions.empty()) {
+               ext.first = 0;
+               return ext;
+       }
 
        for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
-               pair<framecnt_t, framecnt_t> const e ((*i)->position(), (*i)->position() + (*i)->length());
+               pair<framepos_t, framepos_t> const e ((*i)->position(), (*i)->position() + (*i)->length());
                if (e.first < ext.first) {
                        ext.first = e.first;
                }
@@ -2380,7 +2376,7 @@ Playlist::bump_name (string name, Session &session)
 layer_t
 Playlist::top_layer() const
 {
-       RegionLock rlock (const_cast<Playlist *> (this));
+       RegionReadLock rlock (const_cast<Playlist *> (this));
        layer_t top = 0;
 
        for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
@@ -2395,20 +2391,69 @@ Playlist::set_edit_mode (EditMode mode)
        _edit_mode = mode;
 }
 
-/********************
- * Region Layering
- ********************/
+struct RelayerSort {
+       bool operator () (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
+               return a->layering_index() < b->layering_index();
+       }
+};
+
+/** Set a new layer for a region.  This adjusts the layering indices of all
+ *  regions in the playlist to put the specified region in the appropriate
+ *  place.  The actual layering will be fixed up when relayer() happens.
+ */
 
 void
-Playlist::relayer ()
+Playlist::set_layer (boost::shared_ptr<Region> region, double new_layer)
+{
+       /* Remove the layer we are setting from our region list, and sort it
+       *  using the layer indeces.
+       */
+
+       RegionList copy = regions.rlist();
+       copy.remove (region);
+       copy.sort (RelayerSort ());
+
+       /* Put region back in the right place */
+       RegionList::iterator i = copy.begin();
+       while (i != copy.end ()) {
+               if ((*i)->layer() > new_layer) {
+                       break;
+               }
+               ++i;
+       }
+
+       copy.insert (i, region);
+
+       setup_layering_indices (copy);
+}
+
+void
+Playlist::setup_layering_indices (RegionList const & regions)
 {
-        /* never compute layers when changing state for undo/redo or setting from XML */
+       uint64_t j = 0;
 
-        if (in_update || in_set_state) {
-                return;
-        }
+       for (RegionList::const_iterator k = regions.begin(); k != regions.end(); ++k) {
+               (*k)->set_layering_index (j++);
+       }
+}
 
-       bool changed = false;
+struct LaterHigherSort {
+       bool operator () (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
+               return a->position() < b->position();
+       }
+};
+
+/** Take the layering indices of each of our regions, compute the layers
+ *  that they should be on, and write the layers back to the regions.
+ */
+void
+Playlist::relayer ()
+{
+       /* never compute layers when setting from XML */
+
+       if (in_set_state) {
+               return;
+       }
 
        /* Build up a new list of regions on each layer, stored in a set of lists
           each of which represent some period of time on some layer.  The idea
@@ -2432,29 +2477,23 @@ Playlist::relayer ()
        vector<vector<RegionList> > layers;
        layers.push_back (vector<RegionList> (divisions));
 
-       /* we want to go through regions from desired lowest to desired highest layer,
-          which depends on the layer model
-       */
-
+       /* Sort our regions into layering index order (for manual layering) or position order (for later is higher)*/
        RegionList copy = regions.rlist();
-
-       /* sort according to the model and the layering mode that we're in */
-
-       if (_explicit_relayering) {
-
-               copy.sort (RegionSortByLayerWithPending ());
-
-       } else if (_session.config.get_layer_model() == MoveAddHigher || _session.config.get_layer_model() == AddHigher) {
-
-               copy.sort (RegionSortByLastLayerOp ());
-
+       switch (Config->get_layer_model()) {
+               case LaterHigher:
+                       copy.sort (LaterHigherSort ());
+                       break;
+               case Manual:
+                       copy.sort (RelayerSort ());
+                       break;
        }
 
-
+       DEBUG_TRACE (DEBUG::Layering, "relayer() using:\n");
        for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) {
+               DEBUG_TRACE (DEBUG::Layering, string_compose ("\t%1 %2\n", (*i)->name(), (*i)->layering_index()));
+       }
 
-               /* reset the pending explicit relayer flag for every region, now that we're relayering */
-               (*i)->set_pending_explicit_relayer (false);
+       for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) {
 
                /* find the time divisions that this region covers; if there are no regions on the list,
                   division_size will equal 0 and in this case we'll just say that
@@ -2491,190 +2530,73 @@ Playlist::relayer ()
                                        l++;
                                }
 
-                               if (overlap) {
-                                       break;
-                               }
-                       }
-
-                       if (overlap) {
-                               /* overlap, so we must use layer j */
-                               break;
-                       }
-
-                       --j;
-               }
-
-               if (j == layers.size()) {
-                       /* we need a new layer for this region */
-                       layers.push_back (vector<RegionList> (divisions));
-               }
-
-               /* put a reference to this region in each of the divisions that it exists in */
-               for (int k = start_division; k <= end_division; ++k) {
-                       layers[j][k].push_back (*i);
-               }
-               
-               if ((*i)->layer() != j) {
-                       changed = true;
-               }
-
-               (*i)->set_layer (j);
-       }
-
-       if (changed) {
-               notify_layering_changed ();
-       }
-}
-
-/* XXX these layer functions are all deprecated */
-
-void
-Playlist::raise_region (boost::shared_ptr<Region> region)
-{
-       uint32_t rsz = regions.size();
-       layer_t target = region->layer() + 1U;
-
-       if (target >= rsz) {
-               /* its already at the effective top */
-               return;
-       }
-
-       move_region_to_layer (target, region, 1);
-}
-
-void
-Playlist::lower_region (boost::shared_ptr<Region> region)
-{
-       if (region->layer() == 0) {
-               /* its already at the bottom */
-               return;
-       }
-
-       layer_t target = region->layer() - 1U;
-
-       move_region_to_layer (target, region, -1);
-}
-
-void
-Playlist::raise_region_to_top (boost::shared_ptr<Region> region)
-{
-       /* does nothing useful if layering mode is later=higher */
-       switch (_session.config.get_layer_model()) {
-       case LaterHigher:
-               return;
-       default:
-               break;
-       }
-
-       layer_t top = regions.size() - 1;
-
-       if (region->layer() >= top) {
-               /* already on the top */
-               return;
-       }
-
-       move_region_to_layer (top, region, 1);
-       /* mark the region's last_layer_op as now, so that it remains on top when
-          doing future relayers (until something else takes over)
-        */
-       timestamp_layer_op (region);
-}
-
-void
-Playlist::lower_region_to_bottom (boost::shared_ptr<Region> region)
-{
-       /* does nothing useful if layering mode is later=higher */
-       switch (_session.config.get_layer_model()) {
-       case LaterHigher:
-               return;
-       default:
-               break;
-       }
-
-       if (region->layer() == 0) {
-               /* already on the bottom */
-               return;
-       }
-
-       move_region_to_layer (0, region, -1);
-       /* force region's last layer op to zero so that it stays at the bottom
-          when doing future relayers
-       */
-       region->set_last_layer_op (0);
-}
-
-int
-Playlist::move_region_to_layer (layer_t target_layer, boost::shared_ptr<Region> region, int dir)
-{
-       RegionList::iterator i;
-       typedef pair<boost::shared_ptr<Region>,layer_t> LayerInfo;
-       list<LayerInfo> layerinfo;
-
-       {
-               RegionLock rlock (const_cast<Playlist *> (this));
-
-               for (i = regions.begin(); i != regions.end(); ++i) {
-
-                       if (region == *i) {
-                               continue;
-                       }
-
-                       layer_t dest;
-
-                       if (dir > 0) {
-
-                               /* region is moving up, move all regions on intermediate layers
-                                  down 1
-                               */
-
-                               if ((*i)->layer() > region->layer() && (*i)->layer() <= target_layer) {
-                                       dest = (*i)->layer() - 1;
-                               } else {
-                                       /* not affected */
-                                       continue;
-                               }
-                       } else {
-
-                               /* region is moving down, move all regions on intermediate layers
-                                  up 1
-                               */
-
-                               if ((*i)->layer() < region->layer() && (*i)->layer() >= target_layer) {
-                                       dest = (*i)->layer() + 1;
-                               } else {
-                                       /* not affected */
-                                       continue;
+                               if (overlap) {
+                                       break;
                                }
                        }
 
-                       LayerInfo newpair;
+                       if (overlap) {
+                               /* overlap, so we must use layer j */
+                               break;
+                       }
 
-                       newpair.first = *i;
-                       newpair.second = dest;
+                       --j;
+               }
 
-                       layerinfo.push_back (newpair);
+               if (j == layers.size()) {
+                       /* we need a new layer for this region */
+                       layers.push_back (vector<RegionList> (divisions));
                }
-       }
 
-       /* now reset the layers without holding the region lock */
+               /* put a reference to this region in each of the divisions that it exists in */
+               for (int k = start_division; k <= end_division; ++k) {
+                       layers[j][k].push_back (*i);
+               }
 
-       for (list<LayerInfo>::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) {
-               x->first->set_layer (x->second);
+               (*i)->set_layer (j);
        }
 
-       region->set_layer (target_layer);
+       /* It's a little tricky to know when we could avoid calling this; e.g. if we are
+          relayering because we just removed the only region on the top layer, nothing will
+          appear to have changed, but the StreamView must still sort itself out.  We could
+          probably keep a note of the top layer last time we relayered, and check that,
+          but premature optimisation &c...
+       */
+       notify_layering_changed ();
 
-#if 0
-       /* now check all dependents */
+       /* This relayer() may have been called as a result of a region removal, in which
+          case we need to setup layering indices to account for the one that has just
+          gone away.
+       */
+       setup_layering_indices (copy);
+}
 
-       for (list<LayerInfo>::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) {
-               check_dependents (x->first, false);
-       }
+void
+Playlist::raise_region (boost::shared_ptr<Region> region)
+{
+       set_layer (region, region->layer() + 1.5);
+       relayer ();
+}
 
-       check_dependents (region, false);
-#endif
+void
+Playlist::lower_region (boost::shared_ptr<Region> region)
+{
+       set_layer (region, region->layer() - 1.5);
+       relayer ();
+}
+
+void
+Playlist::raise_region_to_top (boost::shared_ptr<Region> region)
+{
+       set_layer (region, DBL_MAX);
+       relayer ();
+}
 
-       return 0;
+void
+Playlist::lower_region_to_bottom (boost::shared_ptr<Region> region)
+{
+       set_layer (region, -0.5);
+       relayer ();
 }
 
 void
@@ -2686,7 +2608,7 @@ Playlist::nudge_after (framepos_t start, framecnt_t distance, bool forwards)
        _nudging = true;
 
        {
-               RegionLock rlock (const_cast<Playlist *> (this));
+               RegionWriteLock rlock (const_cast<Playlist *> (this));
 
                for (i = regions.begin(); i != regions.end(); ++i) {
 
@@ -2696,8 +2618,8 @@ Playlist::nudge_after (framepos_t start, framecnt_t distance, bool forwards)
 
                                if (forwards) {
 
-                                       if ((*i)->last_frame() > max_frames - distance) {
-                                               new_pos = max_frames - (*i)->length();
+                                       if ((*i)->last_frame() > max_framepos - distance) {
+                                               new_pos = max_framepos - (*i)->length();
                                        } else {
                                                new_pos = (*i)->position() + distance;
                                        }
@@ -2711,7 +2633,7 @@ Playlist::nudge_after (framepos_t start, framecnt_t distance, bool forwards)
                                        }
                                }
 
-                               (*i)->set_position (new_pos, this);
+                               (*i)->set_position (new_pos);
                                moved = true;
                        }
                }
@@ -2719,15 +2641,29 @@ Playlist::nudge_after (framepos_t start, framecnt_t distance, bool forwards)
 
        if (moved) {
                _nudging = false;
-               notify_length_changed ();
+               notify_contents_changed ();
+       }
+
+}
+
+bool
+Playlist::uses_source (boost::shared_ptr<const Source> src) const
+{
+       RegionReadLock rlock (const_cast<Playlist*> (this));
+
+       for (set<boost::shared_ptr<Region> >::const_iterator r = all_regions.begin(); r != all_regions.end(); ++r) {
+               if ((*r)->uses_source (src)) {
+                       return true;
+               }
        }
 
+       return false;
 }
 
 boost::shared_ptr<Region>
 Playlist::find_region (const ID& id) const
 {
-       RegionLock rlock (const_cast<Playlist*> (this));
+       RegionReadLock rlock (const_cast<Playlist*> (this));
 
        /* searches all regions currently in use by the playlist */
 
@@ -2740,12 +2676,50 @@ Playlist::find_region (const ID& id) const
        return boost::shared_ptr<Region> ();
 }
 
+uint32_t
+Playlist::region_use_count (boost::shared_ptr<Region> r) const
+{
+       RegionReadLock rlock (const_cast<Playlist*> (this));
+       uint32_t cnt = 0;
+
+       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+               if ((*i) == r) {
+                       cnt++;
+               }
+       }
+
+       RegionFactory::CompoundAssociations& cassocs (RegionFactory::compound_associations());
+       for (RegionFactory::CompoundAssociations::iterator it = cassocs.begin(); it != cassocs.end(); ++it) {
+               /* check if region is used in a compound */
+               if (it->second == r) {
+                       /* region is referenced as 'original' of a compound */
+                       ++cnt;
+                       break;
+               }
+               if (r->whole_file() && r->max_source_level() > 0) {
+                       /* region itself ia a compound.
+                        * the compound regions are not referenced -> check regions inside compound
+                        */
+                       const SourceList& sl = r->sources();
+                       for (SourceList::const_iterator s = sl.begin(); s != sl.end(); ++s) {
+                               boost::shared_ptr<PlaylistSource> ps = boost::dynamic_pointer_cast<PlaylistSource>(*s);
+                               if (!ps) continue;
+                               if (ps->playlist()->region_use_count(it->first)) {
+                                       // break out of both loops
+                                       return ++cnt;
+                               }
+                       }
+               }
+       }
+       return cnt;
+}
+
 boost::shared_ptr<Region>
-Playlist::region_by_id (const ID& id)
+Playlist::region_by_id (const ID& id) const
 {
        /* searches all regions ever added to this playlist */
 
-       for (set<boost::shared_ptr<Region> >::iterator i = all_regions.begin(); i != all_regions.end(); ++i) {
+       for (set<boost::shared_ptr<Region> >::const_iterator i = all_regions.begin(); i != all_regions.end(); ++i) {
                if ((*i)->id() == id) {
                        return *i;
                }
@@ -2780,13 +2754,6 @@ Playlist::set_frozen (bool yn)
        _frozen = yn;
 }
 
-void
-Playlist::timestamp_layer_op (boost::shared_ptr<Region> region)
-{
-       region->set_last_layer_op (++layer_op_counter);
-}
-
-
 void
 Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
 {
@@ -2799,7 +2766,7 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
        _shuffling = true;
 
        {
-               RegionLock rlock (const_cast<Playlist*> (this));
+               RegionWriteLock rlock (const_cast<Playlist*> (this));
 
 
                if (dir > 0) {
@@ -2833,8 +2800,8 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
                                                        new_pos = region->position() + (*next)->length();
                                                }
 
-                                               (*next)->set_position (region->position(), this);
-                                               region->set_position (new_pos, this);
+                                               (*next)->set_position (region->position());
+                                               region->set_position (new_pos);
 
                                                /* avoid a full sort */
 
@@ -2874,8 +2841,8 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
                                                        new_pos = (*prev)->position() + region->length();
                                                }
 
-                                               region->set_position ((*prev)->position(), this);
-                                               (*prev)->set_position (new_pos, this);
+                                               region->set_position ((*prev)->position());
+                                               (*prev)->set_position (new_pos);
 
                                                /* avoid a full sort */
 
@@ -2896,8 +2863,6 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
        if (moved) {
 
                relayer ();
-               check_dependents (region, false);
-
                notify_contents_changed();
        }
 
@@ -2906,7 +2871,7 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
 bool
 Playlist::region_is_shuffle_constrained (boost::shared_ptr<Region>)
 {
-       RegionLock rlock (const_cast<Playlist*> (this));
+       RegionReadLock rlock (const_cast<Playlist*> (this));
 
        if (regions.size() > 1) {
                return true;
@@ -2915,16 +2880,22 @@ Playlist::region_is_shuffle_constrained (boost::shared_ptr<Region>)
        return false;
 }
 
+void
+Playlist::ripple (framepos_t at, framecnt_t distance, RegionList *exclude)
+{
+       ripple_locked (at, distance, exclude);
+}
+
 void
 Playlist::update_after_tempo_map_change ()
 {
-       RegionLock rlock (const_cast<Playlist*> (this));
+       RegionWriteLock rlock (const_cast<Playlist*> (this));
        RegionList copy (regions.rlist());
 
        freeze ();
 
        for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) {
-               (*i)->update_position_after_tempo_map_change ();
+               (*i)->update_after_tempo_map_change ();
        }
 
        thaw ();
@@ -2933,40 +2904,17 @@ Playlist::update_after_tempo_map_change ()
 void
 Playlist::foreach_region (boost::function<void(boost::shared_ptr<Region>)> s)
 {
-       RegionLock rl (this, false);
+       RegionReadLock rl (this);
        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
                s (*i);
        }
 }
 
-void
-Playlist::set_explicit_relayering (bool e)
-{
-       if (e == false && _explicit_relayering == true) {
-
-               /* We are changing from explicit to implicit relayering; layering may have been changed whilst
-                  we were in explicit mode, and we don't want that to be undone next time an implicit relayer
-                  occurs.  Hence now we'll set up region last_layer_op values so that an implicit relayer
-                  at this point would keep regions on the same layers.
-
-                  From then on in, it's just you and your towel.
-               */
-
-               RegionLock rl (this);
-               for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-                       (*i)->set_last_layer_op ((*i)->layer ());
-               }
-       }
-
-       _explicit_relayering = e;
-}
-
-
 bool
 Playlist::has_region_at (framepos_t const p) const
 {
-       RegionLock (const_cast<Playlist *> (this));
-       
+       RegionReadLock (const_cast<Playlist *> (this));
+
        RegionList::const_iterator i = regions.begin ();
        while (i != regions.end() && !(*i)->covers (p)) {
                ++i;
@@ -2979,13 +2927,13 @@ Playlist::has_region_at (framepos_t const p) const
 void
 Playlist::remove_region_by_source (boost::shared_ptr<Source> s)
 {
-       RegionLock rl (this);
-       
+       RegionWriteLock rl (this);
+
        RegionList::iterator i = regions.begin();
        while (i != regions.end()) {
                RegionList::iterator j = i;
                ++j;
-               
+
                if ((*i)->uses_source (s)) {
                        remove_region_internal (*i);
                }
@@ -2993,3 +2941,387 @@ Playlist::remove_region_by_source (boost::shared_ptr<Source> s)
                i = j;
        }
 }
+
+/** Look from a session frame time and find the start time of the next region
+ *  which is on the top layer of this playlist.
+ *  @param t Time to look from.
+ *  @return Position of next top-layered region, or max_framepos if there isn't one.
+ */
+framepos_t
+Playlist::find_next_top_layer_position (framepos_t t) const
+{
+       RegionReadLock rlock (const_cast<Playlist *> (this));
+
+       layer_t const top = top_layer ();
+
+       RegionList copy = regions.rlist ();
+       copy.sort (RegionSortByPosition ());
+
+       for (RegionList::const_iterator i = copy.begin(); i != copy.end(); ++i) {
+               if ((*i)->position() >= t && (*i)->layer() == top) {
+                       return (*i)->position();
+               }
+       }
+
+       return max_framepos;
+}
+
+boost::shared_ptr<Region>
+Playlist::combine (const RegionList& r)
+{
+       PropertyList plist;
+       uint32_t channels = 0;
+       uint32_t layer = 0;
+       framepos_t earliest_position = max_framepos;
+       vector<TwoRegions> old_and_new_regions;
+       vector<boost::shared_ptr<Region> > originals;
+       vector<boost::shared_ptr<Region> > copies;
+       string parent_name;
+       string child_name;
+       uint32_t max_level = 0;
+
+       /* find the maximum depth of all the regions we're combining */
+
+       for (RegionList::const_iterator i = r.begin(); i != r.end(); ++i) {
+               max_level = max (max_level, (*i)->max_source_level());
+       }
+
+       parent_name = RegionFactory::compound_region_name (name(), combine_ops(), max_level, true);
+       child_name = RegionFactory::compound_region_name (name(), combine_ops(), max_level, false);
+
+       boost::shared_ptr<Playlist> pl = PlaylistFactory::create (_type, _session, parent_name, true);
+
+       for (RegionList::const_iterator i = r.begin(); i != r.end(); ++i) {
+               earliest_position = min (earliest_position, (*i)->position());
+       }
+
+       /* enable this so that we do not try to create xfades etc. as we add
+        * regions
+        */
+
+       pl->in_partition = true;
+
+       /* sort by position then layer.
+        * route_time_axis passes 'selected_regions' - which is not sorted.
+        * here we need the top-most first, then every layer's region sorted by position.
+        */
+       RegionList sorted(r);
+       sorted.sort(RegionSortByLayerAndPosition());
+
+       for (RegionList::const_iterator i = sorted.begin(); i != sorted.end(); ++i) {
+
+               /* copy the region */
+
+               boost::shared_ptr<Region> original_region = (*i);
+               boost::shared_ptr<Region> copied_region = RegionFactory::create (original_region, false);
+
+               old_and_new_regions.push_back (TwoRegions (original_region,copied_region));
+               originals.push_back (original_region);
+               copies.push_back (copied_region);
+
+               RegionFactory::add_compound_association (original_region, copied_region);
+
+               /* make position relative to zero */
+
+               pl->add_region (copied_region, original_region->position() - earliest_position);
+               copied_region->set_layer (original_region->layer ());
+
+               /* use the maximum number of channels for any region */
+
+               channels = max (channels, original_region->n_channels());
+
+               /* it will go above the layer of the highest existing region */
+
+               layer = max (layer, original_region->layer());
+       }
+
+       pl->in_partition = false;
+
+       pre_combine (copies);
+
+       /* now create a new PlaylistSource for each channel in the new playlist */
+
+       SourceList sources;
+       pair<framepos_t,framepos_t> extent = pl->get_extent();
+
+       for (uint32_t chn = 0; chn < channels; ++chn) {
+               sources.push_back (SourceFactory::createFromPlaylist (_type, _session, pl, id(), parent_name, chn, 0, extent.second, false, false));
+
+       }
+
+       /* now a new whole-file region using the list of sources */
+
+       plist.add (Properties::start, 0);
+       plist.add (Properties::length, extent.second);
+       plist.add (Properties::name, parent_name);
+       plist.add (Properties::whole_file, true);
+
+       boost::shared_ptr<Region> parent_region = RegionFactory::create (sources, plist, true);
+
+       /* now the non-whole-file region that we will actually use in the
+        * playlist
+        */
+
+       plist.clear ();
+       plist.add (Properties::start, 0);
+       plist.add (Properties::length, extent.second);
+       plist.add (Properties::name, child_name);
+       plist.add (Properties::layer, layer+1);
+
+       boost::shared_ptr<Region> compound_region = RegionFactory::create (parent_region, plist, true);
+
+       /* remove all the selected regions from the current playlist
+        */
+
+       freeze ();
+
+       for (RegionList::const_iterator i = r.begin(); i != r.end(); ++i) {
+               remove_region (*i);
+       }
+
+       /* do type-specific stuff with the originals and the new compound
+          region
+       */
+
+       post_combine (originals, compound_region);
+
+       /* add the new region at the right location */
+
+       add_region (compound_region, earliest_position);
+
+       _combine_ops++;
+
+       thaw ();
+
+       return compound_region;
+}
+
+void
+Playlist::uncombine (boost::shared_ptr<Region> target)
+{
+       boost::shared_ptr<PlaylistSource> pls;
+       boost::shared_ptr<const Playlist> pl;
+       vector<boost::shared_ptr<Region> > originals;
+       vector<TwoRegions> old_and_new_regions;
+
+       // (1) check that its really a compound region
+
+       if ((pls = boost::dynamic_pointer_cast<PlaylistSource>(target->source (0))) == 0) {
+               return;
+       }
+
+       pl = pls->playlist();
+
+       framepos_t adjusted_start = 0; // gcc isn't smart enough
+       framepos_t adjusted_end = 0;   // gcc isn't smart enough
+
+       /* the leftmost (earliest) edge of the compound region
+          starts at zero in its source, or larger if it
+          has been trimmed or content-scrolled.
+
+          the rightmost (latest) edge of the compound region
+          relative to its source is the starting point plus
+          the length of the region.
+       */
+
+       // (2) get all the original regions
+
+       const RegionList& rl (pl->region_list().rlist());
+       RegionFactory::CompoundAssociations& cassocs (RegionFactory::compound_associations());
+       frameoffset_t move_offset = 0;
+
+       /* there are two possibilities here:
+          1) the playlist that the playlist source was based on
+          is us, so just add the originals (which belonged to
+          us anyway) back in the right place.
+
+          2) the playlist that the playlist source was based on
+          is NOT us, so we need to make copies of each of
+          the original regions that we find, and add them
+          instead.
+       */
+       bool same_playlist = (pls->original() == id());
+
+       for (RegionList::const_iterator i = rl.begin(); i != rl.end(); ++i) {
+
+               boost::shared_ptr<Region> current (*i);
+
+               RegionFactory::CompoundAssociations::iterator ca = cassocs.find (*i);
+
+               if (ca == cassocs.end()) {
+                       continue;
+               }
+
+               boost::shared_ptr<Region> original (ca->second);
+               cassocs.erase(ca);
+               bool modified_region;
+
+               if (i == rl.begin()) {
+                       move_offset = (target->position() - original->position()) - target->start();
+                       adjusted_start = original->position() + target->start();
+                       adjusted_end = adjusted_start + target->length();
+               }
+
+               if (!same_playlist) {
+                       framepos_t pos = original->position();
+                       /* make a copy, but don't announce it */
+                       original = RegionFactory::create (original, false);
+                       /* the pure copy constructor resets position() to zero,
+                          so fix that up.
+                       */
+                       original->set_position (pos);
+               }
+
+               /* check to see how the original region (in the
+                * playlist before compounding occurred) overlaps
+                * with the new state of the compound region.
+                */
+
+               original->clear_changes ();
+               modified_region = false;
+
+               switch (original->coverage (adjusted_start, adjusted_end)) {
+               case Evoral::OverlapNone:
+                       /* original region does not cover any part
+                          of the current state of the compound region
+                       */
+                       continue;
+
+               case Evoral::OverlapInternal:
+                       /* overlap is just a small piece inside the
+                        * original so trim both ends
+                        */
+                       original->trim_to (adjusted_start, adjusted_end - adjusted_start);
+                       modified_region = true;
+                       break;
+
+               case Evoral::OverlapExternal:
+                       /* overlap fully covers original, so leave it
+                          as is
+                       */
+                       break;
+
+               case Evoral::OverlapEnd:
+                       /* overlap starts within but covers end,
+                          so trim the front of the region
+                       */
+                       original->trim_front (adjusted_start);
+                       modified_region = true;
+                       break;
+
+               case Evoral::OverlapStart:
+                       /* overlap covers start but ends within, so
+                        * trim the end of the region.
+                        */
+                       original->trim_end (adjusted_end);
+                       modified_region = true;
+                       break;
+               }
+
+               if (move_offset) {
+                       /* fix the position to match any movement of the compound region.
+                        */
+                       original->set_position (original->position() + move_offset);
+                       modified_region = true;
+               }
+
+               if (modified_region) {
+                       _session.add_command (new StatefulDiffCommand (original));
+               }
+
+               /* and add to the list of regions waiting to be
+                * re-inserted
+                */
+
+               originals.push_back (original);
+               old_and_new_regions.push_back (TwoRegions (*i, original));
+       }
+
+       pre_uncombine (originals, target);
+
+       in_partition = true;
+       freeze ();
+
+       // (3) remove the compound region
+
+       remove_region (target);
+
+       // (4) add the constituent regions
+
+       for (vector<boost::shared_ptr<Region> >::iterator i = originals.begin(); i != originals.end(); ++i) {
+               add_region ((*i), (*i)->position());
+               set_layer((*i), (*i)->layer());
+               if (!RegionFactory::region_by_id((*i)->id())) {
+                       RegionFactory::map_add(*i);
+               }
+       }
+
+       in_partition = false;
+       thaw ();
+}
+
+void
+Playlist::fade_range (list<AudioRange>& ranges)
+{
+        for (list<AudioRange>::iterator r = ranges.begin(); r != ranges.end(); ++r) {
+                for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+                        (*i)->fade_range ((*r).start, (*r).end);
+                }
+        }
+}
+
+uint32_t
+Playlist::max_source_level () const
+{
+       RegionReadLock rlock (const_cast<Playlist *> (this));
+       uint32_t lvl = 0;
+
+       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+               lvl = max (lvl, (*i)->max_source_level());
+       }
+
+       return lvl;
+}
+
+void
+Playlist::set_orig_track_id (const PBD::ID& id)
+{
+       _orig_track_id = id;
+}
+
+/** Take a list of ranges, coalesce any that can be coalesced, then call
+ *  check_crossfades for each one.
+ */
+void
+Playlist::coalesce_and_check_crossfades (list<Evoral::Range<framepos_t> > ranges)
+{
+       /* XXX: it's a shame that this coalesce algorithm also exists in
+          TimeSelection::consolidate().
+       */
+
+       /* XXX: xfade: this is implemented in Evoral::RangeList */
+
+restart:
+       for (list<Evoral::Range<framepos_t> >::iterator i = ranges.begin(); i != ranges.end(); ++i) {
+               for (list<Evoral::Range<framepos_t> >::iterator j = ranges.begin(); j != ranges.end(); ++j) {
+
+                       if (i == j) {
+                               continue;
+                       }
+
+                       // XXX i->from can be > i->to - is this right? coverage() will return OverlapNone in this case
+                       if (Evoral::coverage (i->from, i->to, j->from, j->to) != Evoral::OverlapNone) {
+                               i->from = min (i->from, j->from);
+                               i->to = max (i->to, j->to);
+                               ranges.erase (j);
+                               goto restart;
+                       }
+               }
+       }
+}
+
+void
+Playlist::set_capture_insertion_in_progress (bool yn)
+{
+       _capture_insertion_underway = yn;
+}