fixes for 98% of all the warnings/errors reported by OS X gcc on tiger
[ardour.git] / libs / ardour / playlist.cc
index bcc4c6061d52d31e11d6fe02d5faf65b7434c74a..6fad2084e4e267ae370f0fba2ee562f19179c666 100644 (file)
@@ -17,6 +17,7 @@
 
 */
 
+#include <stdint.h>
 #include <set>
 #include <fstream>
 #include <algorithm>
 #include <string>
 #include <climits>
 
-#include <sigc++/bind.h>
+#include <boost/lexical_cast.hpp>
 
+#include "pbd/convert.h"
 #include "pbd/failed_constructor.h"
-#include "pbd/stl_delete.h"
+#include "pbd/stateful_diff_command.h"
 #include "pbd/xml++.h"
-#include "pbd/stacktrace.h"
 
 #include "ardour/debug.h"
 #include "ardour/playlist.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"
 
@@ -46,6 +51,12 @@ using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 
+namespace ARDOUR {
+namespace Properties {
+PBD::PropertyDescriptor<bool> regions;
+}
+}
+
 struct ShowMeTheList {
     ShowMeTheList (boost::shared_ptr<Playlist> pl, const string& n) : playlist (pl), name (n) {}
     ~ShowMeTheList () {
@@ -55,67 +66,102 @@ 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;
-               }
+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));
+}
 
-               double q = b->layer ();
-               if (b->pending_explicit_relayer()) {
-                       q += 0.5;
-               }
+RegionListProperty::RegionListProperty (Playlist& pl)
+       : SequenceProperty<std::list<boost::shared_ptr<Region> > > (Properties::regions.property_id, boost::bind (&Playlist::update, &pl, _1))
+       , _playlist (pl)
+{
 
-               return p < q;
-       }
-};
+}
 
-struct RegionSortByPosition {
-    bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
-           return a->position() < b->position();
-    }
-};
+RegionListProperty::RegionListProperty (RegionListProperty const & p)
+       : PBD::SequenceProperty<std::list<boost::shared_ptr<Region> > > (p)
+       , _playlist (p._playlist)
+{
 
-struct RegionSortByLastLayerOp {
-    bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
-           return a->last_layer_op() < b->last_layer_op();
-    }
-};
+}
+
+RegionListProperty *
+RegionListProperty::clone () const
+{
+       return new RegionListProperty (*this);
+}
+
+RegionListProperty *
+RegionListProperty::create () const
+{
+       return new RegionListProperty (_playlist);
+}
+
+void
+RegionListProperty::get_content_as_xml (boost::shared_ptr<Region> region, XMLNode & node) const
+{
+       /* 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 ());
+}
+
+boost::shared_ptr<Region>
+RegionListProperty::get_content_from_xml (XMLNode const & node) const
+{
+       XMLProperty const * prop = node.property ("id");
+       assert (prop);
 
+       PBD::ID id (prop->value ());
+
+       boost::shared_ptr<Region> ret = _playlist.region_by_id (id);
+
+       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)
        , _type(type)
 {
        init (hide);
        first_set_state = false;
        _name = nom;
-
+       _set_sort_id ();
 }
 
 Playlist::Playlist (Session& sess, const XMLNode& node, DataType type, bool hide)
        : SessionObject(sess, "unnamed playlist")
+       , regions (*this)
        , _type(type)
 {
+#ifndef NDEBUG
        const XMLProperty* prop = node.property("type");
        assert(!prop || DataType(prop->value()) == _type);
+#endif
 
        init (hide);
        _name = "unnamed"; /* reset by set_state */
+       _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), _type(other->_type), _orig_diskstream_id(other->_orig_diskstream_id)
+       : SessionObject(other->_session, namestr)
+       , regions (*this)
+       , _type(other->_type)
+       , _orig_diskstream_id (other->_orig_diskstream_id)
 {
        init (hide);
 
@@ -146,24 +192,27 @@ Playlist::Playlist (boost::shared_ptr<const Playlist> other, string namestr, boo
        freeze_length = other->freeze_length;
 }
 
-Playlist::Playlist (boost::shared_ptr<const Playlist> other, nframes_t start, nframes_t cnt, string str, bool hide)
-       : SessionObject(other->_session, str), _type(other->_type), _orig_diskstream_id(other->_orig_diskstream_id)
+Playlist::Playlist (boost::shared_ptr<const Playlist> other, framepos_t start, framecnt_t cnt, string str, bool hide)
+       : SessionObject(other->_session, str)
+       , regions (*this)
+       , _type(other->_type)
+       , _orig_diskstream_id (other->_orig_diskstream_id)
 {
        RegionLock rlock2 (const_cast<Playlist*> (other.get()));
 
-       nframes_t end = start + cnt - 1;
+       framepos_t end = start + cnt - 1;
 
        init (hide);
 
        in_set_state++;
 
-       for (RegionList::const_iterator i = other->regions.begin(); i != other->regions.end(); i++) {
+       for (RegionList::const_iterator i = other->regions.begin(); i != other->regions.end(); ++i) {
 
                boost::shared_ptr<Region> region;
                boost::shared_ptr<Region> new_region;
-               nframes_t offset = 0;
-               nframes_t position = 0;
-               nframes_t len = 0;
+               frameoffset_t offset = 0;
+               framepos_t position = 0;
+               framecnt_t len = 0;
                string    new_name;
                OverlapType overlap;
 
@@ -200,17 +249,22 @@ Playlist::Playlist (boost::shared_ptr<const Playlist> other, nframes_t start, nf
                        break;
                }
 
-               _session.region_name (new_name, region->name(), false);
+               RegionFactory::region_name (new_name, region->name(), false);
 
-               new_region = RegionFactory::RegionFactory::create (region, offset, len, new_name, region->layer(), region->flags());
+               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());
+
+               new_region = RegionFactory::RegionFactory::create (region, plist);
 
                add_region_internal (new_region, position);
        }
 
        in_set_state--;
        first_set_state = false;
-
-       /* this constructor does NOT notify others (session) */
 }
 
 void
@@ -238,17 +292,21 @@ Playlist::copy_regions (RegionList& newlist) const
        RegionLock 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::RegionFactory::create (*i, true));
        }
 }
 
 void
 Playlist::init (bool hide)
 {
+       add_property (regions);
+       _xml_node_name = X_("Playlist");
+
        g_atomic_int_set (&block_notifications, 0);
        g_atomic_int_set (&ignore_state_changes, 0);
-       pending_modified = false;
+       pending_contents_change = false;
        pending_length = false;
+       pending_layering = false;
        first_set_state = true;
        _refcnt = 0;
        _hidden = hide;
@@ -256,6 +314,7 @@ Playlist::init (bool hide)
        _shuffling = false;
        _nudging = false;
        in_set_state = 0;
+       in_update = false;
        _edit_mode = Config->get_edit_mode();
        in_flush = false;
        in_partition = false;
@@ -265,13 +324,18 @@ Playlist::init (bool hide)
        layer_op_counter = 0;
        freeze_length = 0;
        _explicit_relayering = false;
+       _combine_ops = 0;
 
-       Modified.connect (mem_fun (*this, &Playlist::mark_session_dirty));
+       _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));
 }
 
 Playlist::~Playlist ()
 {
        DEBUG_TRACE (DEBUG::Destruction, string_compose ("Playlist %1 destructor\n", _name));
+
        {
                RegionLock rl (this);
 
@@ -283,6 +347,32 @@ Playlist::~Playlist ()
        /* GoingAway must be emitted by derived classes */
 }
 
+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(".");
+
+       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;
+               }
+       }
+}
+
 bool
 Playlist::set_name (const string& str)
 {
@@ -294,9 +384,13 @@ Playlist::set_name (const string& str)
 
        if (_refcnt > 2) {
                return false;
-       } else {
-               return SessionObject::set_name(str);
        }
+
+       bool ret =  SessionObject::set_name(str);
+       if (ret) {
+               _set_sort_id ();
+       }
+       return ret;
 }
 
 /***********************************************************************
@@ -307,6 +401,20 @@ Playlist::set_name (const string& str)
  the lock (e.g. to read from the playlist).
  ***********************************************************************/
 
+void
+Playlist::begin_undo ()
+{
+       in_update = true;
+       freeze ();
+}
+
+void
+Playlist::end_undo ()
+{
+       thaw (true);
+       in_update = false;
+}
+
 void
 Playlist::freeze ()
 {
@@ -314,11 +422,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);
 }
 
 
@@ -326,25 +435,37 @@ void
 Playlist::delay_notifications ()
 {
        g_atomic_int_inc (&block_notifications);
-       freeze_length = _get_maximum_extent();
+       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
-Playlist::notify_modified ()
+Playlist::notify_contents_changed ()
 {
        if (holding_state ()) {
-               pending_modified = true;
+               pending_contents_change = true;
        } else {
-               pending_modified = false;
-               Modified(); /* EMIT SIGNAL */
+               pending_contents_change = false;
+               ContentsChanged(); /* EMIT SIGNAL */
+       }
+}
+
+void
+Playlist::notify_layering_changed ()
+{
+       if (holding_state ()) {
+               pending_layering = true;
+       } else {
+               pending_layering = false;
+               LayeringChanged(); /* EMIT SIGNAL */
        }
 }
 
@@ -353,7 +474,7 @@ Playlist::notify_region_removed (boost::shared_ptr<Region> r)
 {
        if (holding_state ()) {
                pending_removes.insert (r);
-               pending_modified = true;
+               pending_contents_change = true;
                pending_length = true;
        } else {
                /* this might not be true, but we have to act
@@ -361,16 +482,16 @@ Playlist::notify_region_removed (boost::shared_ptr<Region> r)
                */
                pending_length = false;
                LengthChanged (); /* EMIT SIGNAL */
-               pending_modified = false;
+               pending_contents_change = false;
                RegionRemoved (boost::weak_ptr<Region> (r)); /* EMIT SIGNAL */
-               Modified (); /* EMIT SIGNAL */
+               ContentsChanged (); /* EMIT SIGNAL */
        }
 }
 
 void
 Playlist::notify_region_moved (boost::shared_ptr<Region> r)
 {
-       Evoral::RangeMove<nframes_t> const move (r->last_position (), r->length (), r->position ());
+       Evoral::RangeMove<framepos_t> const move (r->last_position (), r->length (), r->position ());
 
        if (holding_state ()) {
 
@@ -378,13 +499,58 @@ Playlist::notify_region_moved (boost::shared_ptr<Region> r)
 
        } else {
 
-               list< Evoral::RangeMove<nframes_t> > m;
+               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)
 {
@@ -394,14 +560,15 @@ Playlist::notify_region_added (boost::shared_ptr<Region> r)
 
        if (holding_state()) {
                pending_adds.insert (r);
-               pending_modified = true;
+               pending_contents_change = true;
                pending_length = true;
        } else {
+               r->clear_changes ();
                pending_length = false;
                LengthChanged (); /* EMIT SIGNAL */
-               pending_modified = false;
+               pending_contents_change = false;
                RegionAdded (boost::weak_ptr<Region> (r)); /* EMIT SIGNAL */
-               Modified (); /* EMIT SIGNAL */
+               ContentsChanged (); /* EMIT SIGNAL */
        }
 }
 
@@ -413,17 +580,20 @@ Playlist::notify_length_changed ()
        } else {
                pending_length = false;
                LengthChanged(); /* EMIT SIGNAL */
-               pending_modified = false;
-               Modified (); /* 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 n = 0;
+       uint32_t regions_changed = false;
+       bool check_length = false;
+       framecnt_t old_length = 0;
 
        if (in_flush) {
                return;
@@ -431,6 +601,14 @@ Playlist::flush_notifications ()
 
        in_flush = true;
 
+       if (!pending_bounds.empty() || !pending_removes.empty() || !pending_adds.empty()) {
+               regions_changed = true;
+               if (!pending_length) {
+                       old_length = _get_extent ().second;
+                       check_length = true;
+               }
+       }
+
        /* 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"
@@ -444,1442 +622,1678 @@ Playlist::flush_notifications ()
                if (_session.config.get_layer_model() == MoveAddHigher) {
                        timestamp_layer_op (*r);
                }
-
-               pending_length = true;
                dependent_checks_needed.insert (*r);
-
-               n++;
        }
 
        for (s = pending_removes.begin(); s != pending_removes.end(); ++s) {
                remove_dependents (*s);
+               // cerr << _name << " sends RegionRemoved\n";
                RegionRemoved (boost::weak_ptr<Region> (*s)); /* EMIT SIGNAL */
-               n++;
        }
 
        for (s = pending_adds.begin(); s != pending_adds.end(); ++s) {
-               RegionAdded (boost::weak_ptr<Region> (*s)); /* EMIT SIGNAL */
-               dependent_checks_needed.insert (*s);
-               n++;
-       }
+                // 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";
+                }
+        }
+
+        if (pending_length || (freeze_length != _get_extent().second)) {
+                pending_length = false;
+                // cerr << _name << " sends LengthChanged\n";
+                LengthChanged(); /* EMIT SIGNAL */
+        }
+
+        if (regions_changed || pending_contents_change) {
+                if (!in_set_state) {
+                        relayer ();
+                }
+                pending_contents_change = false;
+                // cerr << _name << " sends 5 contents change @ " << get_microseconds() << endl;
+                ContentsChanged (); /* EMIT SIGNAL */
+                // cerr << _name << "done contents change @ " << get_microseconds() << endl;
+        }
+
+        for (s = pending_adds.begin(); s != pending_adds.end(); ++s) {
+                (*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 (!pending_range_moves.empty ()) {
+                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_region_extensions.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;
+
+        if (times == 1 && auto_partition){
+                partition(pos - 1, (pos + region->length()), true);
+        }
+
+        if (itimes >= 1) {
+                add_region_internal (region, pos);
+                pos += region->length();
+                --itimes;
+        }
+
+
+        /* note that itimes can be zero if we being asked to just
+           insert a single fraction of the region.
+        */
 
-       if ((freeze_length != _get_maximum_extent()) || pending_length) {
-               pending_length = 0;
-               LengthChanged(); /* EMIT SIGNAL */
-               n++;
-       }
+        for (int i = 0; i < itimes; ++i) {
+                boost::shared_ptr<Region> copy = RegionFactory::create (region, true);
+                add_region_internal (copy, pos);
+                pos += region->length();
+        }
 
-       if (n || pending_modified) {
-               if (!in_set_state) {
-                       relayer ();
-               }
-               pending_modified = false;
-               Modified (); /* EMIT SIGNAL */
-       }
+        framecnt_t length = 0;
 
-       for (s = dependent_checks_needed.begin(); s != dependent_checks_needed.end(); ++s) {
-               check_dependents (*s, false);
-       }
+        if (floor (times) != times) {
+                length = (framecnt_t) floor (region->length() * (times - floor (times)));
+                string name;
+                RegionFactory::region_name (name, region->name(), false);
 
-       if (!pending_range_moves.empty ()) {
-               RangesMoved (pending_range_moves);
-       }
+                {
+                        PropertyList plist;
 
-       pending_adds.clear ();
-       pending_removes.clear ();
-       pending_bounds.clear ();
-       pending_range_moves.clear ();
+                        plist.add (Properties::start, region->start());
+                        plist.add (Properties::length, length);
+                        plist.add (Properties::name, name);
+                        plist.add (Properties::layer, region->layer());
 
-       in_flush = false;
-}
+                        boost::shared_ptr<Region> sub = RegionFactory::create (region, plist);
+                        add_region_internal (sub, pos);
+                }
+        }
 
-/*************************************************************
-  PLAYLIST OPERATIONS
- *************************************************************/
+        possibly_splice_unlocked (position, (pos + length) - position, boost::shared_ptr<Region>());
+ }
 
-void
-Playlist::add_region (boost::shared_ptr<Region> region, nframes_t position, float times, bool auto_partition)
-{
-       RegionLock rlock (this);
-       times = fabs (times);
+ void
+ Playlist::set_region_ownership ()
+ {
+        RegionLock rl (this);
+        RegionList::iterator i;
+        boost::weak_ptr<Playlist> pl (shared_from_this());
 
-       int itimes = (int) floor (times);
+        for (i = regions.begin(); i != regions.end(); ++i) {
+                (*i)->set_playlist (pl);
+        }
+ }
 
-       nframes_t pos = position;
+ bool
+ Playlist::add_region_internal (boost::shared_ptr<Region> region, framepos_t position)
+ {
+        if (region->data_type() != _type){
+                return false;
+        }
 
-       if (times == 1 && auto_partition){
-               partition(pos, (nframes_t) (pos + region->length()), true);
-       }
+        RegionSortByPosition cmp;
 
-       if (itimes >= 1) {
-               add_region_internal (region, pos);
-               pos += region->length();
-               --itimes;
-       }
+        framecnt_t old_length = 0;
 
+        if (!holding_state()) {
+                 old_length = _get_extent().second;
+        }
 
-       /* note that itimes can be zero if we being asked to just
-          insert a single fraction of the region.
-       */
+        if (!first_set_state) {
+                boost::shared_ptr<Playlist> foo (shared_from_this());
+                region->set_playlist (boost::weak_ptr<Playlist>(foo));
+        }
 
-       for (int i = 0; i < itimes; ++i) {
-               boost::shared_ptr<Region> copy = RegionFactory::create (region);
-               add_region_internal (copy, pos);
-               pos += region->length();
-       }
+        region->set_position (position);
 
-       nframes_t length = 0;
+        timestamp_layer_op (region);
 
-       if (floor (times) != times) {
-               length = (nframes_t) floor (region->length() * (times - floor (times)));
-               string name;
-               _session.region_name (name, region->name(), false);
-               boost::shared_ptr<Region> sub = RegionFactory::create (region, 0, length, name, region->layer(), region->flags());
-               add_region_internal (sub, pos);
-       }
+        regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region);
+        all_regions.insert (region);
 
-       possibly_splice_unlocked (position, (pos + length) - position, boost::shared_ptr<Region>());
-}
+        possibly_splice_unlocked (position, region->length(), region);
 
-void
-Playlist::set_region_ownership ()
-{
-       RegionLock rl (this);
-       RegionList::iterator i;
-       boost::weak_ptr<Playlist> pl (shared_from_this());
+        if (!holding_state ()) {
+                /* layers get assigned from XML state, and are not reset during undo/redo */
+                relayer ();
+        }
 
-       for (i = regions.begin(); i != regions.end(); ++i) {
-               (*i)->set_playlist (pl);
-       }
-}
+        /* we need to notify the existence of new region before checking dependents. Ick. */
 
-bool
-Playlist::add_region_internal (boost::shared_ptr<Region> region, nframes_t position)
-{
-       if (region->data_type() != _type){
-               return false;
-       }
+        notify_region_added (region);
 
-       RegionSortByPosition cmp;
 
-       nframes_t old_length = 0;
+        if (!holding_state ()) {
 
-       if (!holding_state()) {
-                old_length = _get_maximum_extent();
-       }
+                check_dependents (region, false);
 
-       if (!first_set_state) {
-               boost::shared_ptr<Playlist> foo (shared_from_this());
-               region->set_playlist (boost::weak_ptr<Playlist>(foo));
-       }
+                if (old_length != _get_extent().second) {
+                        notify_length_changed ();
+                }
+        }
 
-       region->set_position (position, this);
+        region->PropertyChanged.connect_same_thread (region_state_changed_connections, boost::bind (&Playlist::region_changed_proxy, this, _1, boost::weak_ptr<Region> (region)));
 
-       timestamp_layer_op (region);
+        return true;
+ }
 
-       regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region);
-       all_regions.insert (region);
+ void
+ Playlist::replace_region (boost::shared_ptr<Region> old, boost::shared_ptr<Region> newr, framepos_t pos)
+ {
+        RegionLock rlock (this);
 
-       possibly_splice_unlocked (position, region->length(), region);
+        bool old_sp = _splicing;
+        _splicing = true;
 
-       if (!holding_state () && !in_set_state) {
-               /* layers get assigned from XML state */
-               relayer ();
-       }
+        remove_region_internal (old);
+        add_region_internal (newr, pos);
 
-       /* we need to notify the existence of new region before checking dependents. Ick. */
+        _splicing = old_sp;
 
-       notify_region_added (region);
+        possibly_splice_unlocked (pos, old->length() - newr->length());
+ }
 
-       if (!holding_state ()) {
+ void
+ Playlist::remove_region (boost::shared_ptr<Region> region)
+ {
+        RegionLock rlock (this);
+        remove_region_internal (region);
+ }
 
-               check_dependents (region, false);
+ int
+ Playlist::remove_region_internal (boost::shared_ptr<Region> region)
+ {
+        RegionList::iterator i;
+        framecnt_t old_length = 0;
 
-               if (old_length != _get_maximum_extent()) {
-                       notify_length_changed ();
-               }
-       }
+        if (!holding_state()) {
+                old_length = _get_extent().second;
+        }
 
-       region_state_changed_connections.push_back (
-               region->StateChanged.connect (sigc::bind (mem_fun (this, &Playlist::region_changed_proxy),
-                                                         boost::weak_ptr<Region> (region)))
-               );
+        if (!in_set_state) {
+                /* unset playlist */
+                region->set_playlist (boost::weak_ptr<Playlist>());
+        }
 
-       return true;
-}
+        /* XXX should probably freeze here .... */
 
-void
-Playlist::replace_region (boost::shared_ptr<Region> old, boost::shared_ptr<Region> newr, nframes_t pos)
-{
-       RegionLock rlock (this);
+        for (i = regions.begin(); i != regions.end(); ++i) {
+                if (*i == region) {
 
-       bool old_sp = _splicing;
-       _splicing = true;
+                        framepos_t pos = (*i)->position();
+                        framecnt_t distance = (*i)->length();
 
-       remove_region_internal (old);
-       add_region_internal (newr, pos);
+                        regions.erase (i);
 
-       _splicing = old_sp;
+                        possibly_splice_unlocked (pos, -distance);
 
-       possibly_splice_unlocked (pos, (nframes64_t) old->length() - (nframes64_t) newr->length());
-}
+                        if (!holding_state ()) {
+                                relayer ();
+                                remove_dependents (region);
 
-void
-Playlist::remove_region (boost::shared_ptr<Region> region)
-{
-       RegionLock rlock (this);
-       remove_region_internal (region);
-}
+                                if (old_length != _get_extent().second) {
+                                        notify_length_changed ();
+                                }
+                        }
 
-int
-Playlist::remove_region_internal (boost::shared_ptr<Region> region)
-{
-       RegionList::iterator i;
-       nframes_t old_length = 0;
+                        notify_region_removed (region);
+                        break;
+                }
+        }
 
-       if (!holding_state()) {
-               old_length = _get_maximum_extent();
-       }
+        return -1;
+ }
 
-       if (!in_set_state) {
-               /* unset playlist */
-               region->set_playlist (boost::weak_ptr<Playlist>());
-       }
+ 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));
+                        }
+                }
+        }
+ }
 
-       for (i = regions.begin(); i != regions.end(); ++i) {
-               if (*i == region) {
+ 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) {
 
-                       nframes_t pos = (*i)->position();
-                       nframes64_t distance = (*i)->length();
+                if ((*i) && (*i)->region_list_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 ();
+        }
+ }
 
-                       regions.erase (i);
+ void
+ Playlist::partition_internal (framepos_t start, framepos_t end, bool cutting, RegionList& thawlist)
+ {
+        RegionList new_regions;
+
+        {
+                RegionLock rlock (this);
+
+                boost::shared_ptr<Region> region;
+                boost::shared_ptr<Region> current;
+                string new_name;
+                RegionList::iterator tmp;
+                OverlapType overlap;
+                framepos_t pos1, pos2, pos3, pos4;
 
-                       possibly_splice_unlocked (pos, -distance);
+                in_partition = true;
 
-                       if (!holding_state ()) {
-                               relayer ();
-                               remove_dependents (region);
+                /* need to work from a copy, because otherwise the regions we add during the process
+                   get operated on as well.
+                */
 
-                               if (old_length != _get_maximum_extent()) {
-                                       notify_length_changed ();
-                               }
-                       }
+                RegionList copy = regions.rlist();
 
-                       notify_region_removed (region);
-                       return 0;
-               }
-       }
+                for (RegionList::iterator i = copy.begin(); i != copy.end(); i = tmp) {
 
+                        tmp = i;
+                        ++tmp;
+
+                        current = *i;
 
+                        if (current->first_frame() >= start && current->last_frame() < end) {
 
-       return -1;
-}
+                                if (cutting) {
+                                        remove_region_internal (current);
+                                }
 
-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));
-                       }
-               }
-       }
-}
+                                continue;
+                        }
 
-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) {
+                        /* coverage will return OverlapStart if the start coincides
+                           with the end point. we do not partition such a region,
+                           so catch this special case.
+                        */
+
+                        if (current->first_frame() >= end) {
+                                continue;
+                        }
+
+                        if ((overlap = current->coverage (start, end)) == OverlapNone) {
+                                continue;
+                        }
 
-               if ((*i) && (*i)->region_list_equivalent (other)) {
-                       results.push_back (*i);
-               }
-       }
-}
+                        pos1 = current->position();
+                        pos2 = start;
+                        pos3 = end;
+                        pos4 = current->last_frame();
 
-void
-Playlist::partition (nframes_t start, nframes_t end, bool cut)
-{
-       RegionList thawlist;
+                        if (overlap == OverlapInternal) {
+                                /* split: we need 3 new regions, the front, middle and end.
+                                   cut:   we need 2 regions, the front and end.
+                                */
 
-       partition_internal (start, end, cut, thawlist);
+                                /*
+                                         start                 end
+                          ---------------*************************------------
+                                         P1  P2              P3  P4
+                          SPLIT:
+                          ---------------*****++++++++++++++++====------------
+                          CUT
+                          ---------------*****----------------====------------
 
-       for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
-               (*i)->thaw ("separation");
-       }
-}
+                                */
 
-void
-Playlist::partition_internal (nframes_t start, nframes_t end, bool cutting, RegionList& thawlist)
-{
-       RegionList new_regions;
+                                if (!cutting) {
+                                        /* "middle" ++++++ */
 
-       {
-               RegionLock rlock (this);
+                                        RegionFactory::region_name (new_name, current->name(), false);
 
-               boost::shared_ptr<Region> region;
-               boost::shared_ptr<Region> current;
-               string new_name;
-               RegionList::iterator tmp;
-               OverlapType overlap;
-               nframes_t pos1, pos2, pos3, pos4;
+                                        PropertyList plist;
 
-               in_partition = true;
+                                        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);
 
-               /* need to work from a copy, because otherwise the regions we add during the process
-                  get operated on as well.
-               */
+                                        region = RegionFactory::create (current, plist);
+                                        add_region_internal (region, start);
+                                        new_regions.push_back (region);
+                                }
 
-               RegionList copy = regions;
+                                /* "end" ====== */
 
-               for (RegionList::iterator i = copy.begin(); i != copy.end(); i = tmp) {
+                                RegionFactory::region_name (new_name, current->name(), false);
 
-                       tmp = i;
-                       ++tmp;
+                                PropertyList plist;
 
-                       current = *i;
+                                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);
 
-                       if (current->first_frame() >= start && current->last_frame() < end) {
+                                region = RegionFactory::create (current, plist);
 
-                               if (cutting) {
-                                       remove_region_internal (current);
-                               }
+                                add_region_internal (region, end);
+                                new_regions.push_back (region);
 
-                               continue;
-                       }
+                                /* "front" ***** */
 
-                       /* coverage will return OverlapStart if the start coincides
-                          with the end point. we do not partition such a region,
-                          so catch this special case.
-                       */
+                                current->suspend_property_changes ();
+                                thawlist.push_back (current);
+                                current->cut_end (pos2 - 1);
 
-                       if (current->first_frame() >= end) {
-                               continue;
-                       }
+                        } else if (overlap == OverlapEnd) {
 
-                       if ((overlap = current->coverage (start, end)) == OverlapNone) {
-                               continue;
-                       }
+                                /*
+                                                              start           end
+                                    ---------------*************************------------
+                                                   P1           P2         P4   P3
+                                    SPLIT:
+                                    ---------------**************+++++++++++------------
+                                    CUT:
+                                    ---------------**************-----------------------
+                                */
 
-                       pos1 = current->position();
-                       pos2 = start;
-                       pos3 = end;
-                       pos4 = current->last_frame();
+                                if (!cutting) {
 
-                       if (overlap == OverlapInternal) {
-                               /* split: we need 3 new regions, the front, middle and end.
-                                  cut:   we need 2 regions, the front and end.
-                               */
+                                        /* end +++++ */
 
-                               /*
-                                        start                 end
-                         ---------------*************************------------
-                                        P1  P2              P3  P4
-                         SPLIT:
-                         ---------------*****++++++++++++++++====------------
-                         CUT
-                         ---------------*****----------------====------------
+                                        RegionFactory::region_name (new_name, current->name(), false);
 
-                               */
+                                        PropertyList plist;
 
-                               if (!cutting) {
-                                       /* "middle" ++++++ */
+                                        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);
 
-                                       _session.region_name (new_name, current->name(), false);
-                                       region = RegionFactory::create (current, pos2 - pos1, pos3 - pos2, new_name,
-                                                                       regions.size(), Region::Flag(current->flags()|Region::Automatic|Region::LeftOfSplit|Region::RightOfSplit));
-                                       add_region_internal (region, start);
-                                       new_regions.push_back (region);
-                               }
+                                        region = RegionFactory::create (current, plist);
 
-                               /* "end" ====== */
+                                        add_region_internal (region, start);
+                                        new_regions.push_back (region);
+                                }
 
-                               _session.region_name (new_name, current->name(), false);
-                               region = RegionFactory::create (current, pos3 - pos1, pos4 - pos3, new_name,
-                                                               regions.size(), Region::Flag(current->flags()|Region::Automatic|Region::RightOfSplit));
+                                /* front ****** */
 
-                               add_region_internal (region, end);
-                               new_regions.push_back (region);
+                                current->suspend_property_changes ();
+                                thawlist.push_back (current);
+                                current->cut_end (pos2 - 1);
 
-                               /* "front" ***** */
+                        } else if (overlap == OverlapStart) {
 
-                               current->freeze ();
-                               thawlist.push_back (current);
-                               current->trim_end (pos2, this);
+                                /* split: we need 2 regions: the front and the end.
+                                   cut: just trim current to skip the cut area
+                                */
 
-                       } else if (overlap == OverlapEnd) {
+                                /*
+                                                        start           end
+                                    ---------------*************************------------
+                                       P2          P1 P3                   P4
 
-                               /*
-                                                             start           end
-                                   ---------------*************************------------
-                                                  P1           P2         P4   P3
-                                    SPLIT:
-                                   ---------------**************+++++++++++------------
-                                    CUT:
-                                   ---------------**************-----------------------
-                               */
+                                    SPLIT:
+                                    ---------------****+++++++++++++++++++++------------
+                                    CUT:
+                                    -------------------*********************------------
 
-                               if (!cutting) {
+                                */
 
-                                       /* end +++++ */
+                                if (!cutting) {
+                                        /* front **** */
+                                        RegionFactory::region_name (new_name, current->name(), false);
 
-                                       _session.region_name (new_name, current->name(), false);
-                                       region = RegionFactory::create (current, pos2 - pos1, pos4 - pos2, new_name, (layer_t) regions.size(),
-                                                                       Region::Flag(current->flags()|Region::Automatic|Region::LeftOfSplit));
+                                        PropertyList 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, regions.size());
+                                        plist.add (Properties::automatic, true);
+                                        plist.add (Properties::right_of_split, true);
 
-                               /* front ****** */
+                                        region = RegionFactory::create (current, plist);
 
-                               current->freeze ();
-                               thawlist.push_back (current);
-                               current->trim_end (pos2, 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 == 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 **** */
-                                       _session.region_name (new_name, current->name(), false);
-                                       region = RegionFactory::create (current, 0, pos3 - pos1, new_name,
-                                                                       regions.size(), Region::Flag(current->flags()|Region::Automatic|Region::RightOfSplit));
+                                */
 
-                                       add_region_internal (region, pos1);
-                                       new_regions.push_back (region);
-                               }
+                                if (cutting) {
+                                        remove_region_internal (current);
+                                }
 
-                               /* end */
+                                new_regions.push_back (current);
+                        }
+                }
 
-                               current->freeze ();
-                               thawlist.push_back (current);
-                               current->trim_front (pos3, this);
-                       } else if (overlap == OverlapExternal) {
+                in_partition = false;
+        }
 
-                               /* split: no split required.
-                                  cut: remove the region.
-                               */
+        for (RegionList::iterator i = new_regions.begin(); i != new_regions.end(); ++i) {
+                check_dependents (*i, false);
+        }
+ }
 
-                               /*
-                                      start                                      end
-                                   ---------------*************************------------
-                                      P2          P1 P3                   P4
+ 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:
-                                   ---------------*************************------------
-                                   CUT:
-                                   ----------------------------------------------------
+        if (ranges.empty()) {
+                return boost::shared_ptr<Playlist>();
+        }
 
-                               */
+        start = ranges.front().start;
 
-                               if (cutting) {
-                                       remove_region_internal (current);
-                               }
+        for (list<AudioRange>::iterator i = ranges.begin(); i != ranges.end(); ++i) {
+
+                pl = (this->*pmf)((*i).start, (*i).length(), result_is_hidden);
 
-                               new_regions.push_back (current);
-                       }
-               }
+                if (i == ranges.begin()) {
+                        ret = pl;
+                } else {
 
-               in_partition = false;
-       }
+                        /* paste the next section into the nascent playlist,
+                           offset to reflect the start of the first range we
+                           chopped.
+                        */
 
-       for (RegionList::iterator i = new_regions.begin(); i != new_regions.end(); ++i) {
-               check_dependents (*i, false);
-       }
-}
+                        ret->paste (pl, (*i).start - start, 1.0f);
+                }
+        }
 
-boost::shared_ptr<Playlist>
-Playlist::cut_copy (boost::shared_ptr<Playlist> (Playlist::*pmf)(nframes_t, nframes_t,bool), list<AudioRange>& ranges, bool result_is_hidden)
-{
-       boost::shared_ptr<Playlist> ret;
-       boost::shared_ptr<Playlist> pl;
-       nframes_t start;
+        return ret;
+ }
 
-       if (ranges.empty()) {
-               return boost::shared_ptr<Playlist>();
-       }
+ 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);
+ }
 
-       start = ranges.front().start;
+ 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);
+ }
 
-       for (list<AudioRange>::iterator i = ranges.begin(); i != ranges.end(); ++i) {
+ 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];
 
-               pl = (this->*pmf)((*i).start, (*i).length(), result_is_hidden);
+        snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
+        string new_name = _name;
+        new_name += '.';
+        new_name += buf;
 
-               if (i == ranges.begin()) {
-                       ret = pl;
-               } else {
+        if ((the_copy = PlaylistFactory::create (shared_from_this(), start, cnt, new_name, result_is_hidden)) == 0) {
+                return boost::shared_ptr<Playlist>();
+        }
 
-                       /* paste the next section into the nascent playlist,
-                          offset to reflect the start of the first range we
-                          chopped.
-                       */
+        partition_internal (start, start+cnt-1, true, thawlist);
 
-                       ret->paste (pl, (*i).start - start, 1.0f);
-               }
-       }
+        for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
+                (*i)->resume_property_changes();
+        }
+
+        return the_copy;
+ }
 
-       return ret;
-}
+ boost::shared_ptr<Playlist>
+ Playlist::copy (framepos_t start, framecnt_t cnt, bool result_is_hidden)
+ {
+        char buf[32];
 
-boost::shared_ptr<Playlist>
-Playlist::cut (list<AudioRange>& ranges, bool result_is_hidden)
-{
-       boost::shared_ptr<Playlist> (Playlist::*pmf)(nframes_t,nframes_t,bool) = &Playlist::cut;
-       return cut_copy (pmf, ranges, result_is_hidden);
-}
+        snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
+        string new_name = _name;
+        new_name += '.';
+        new_name += buf;
 
-boost::shared_ptr<Playlist>
-Playlist::copy (list<AudioRange>& ranges, bool result_is_hidden)
-{
-       boost::shared_ptr<Playlist> (Playlist::*pmf)(nframes_t,nframes_t,bool) = &Playlist::copy;
-       return cut_copy (pmf, ranges, result_is_hidden);
-}
+        cnt = min (_get_extent().second - start, cnt);
+        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);
+
+        {
+                RegionLock rl1 (this);
+                RegionLock rl2 (other.get());
+
+                framecnt_t const old_length = _get_extent().second;
+
+                int itimes = (int) floor (times);
+                framepos_t pos = position;
+                framecnt_t const shift = other->_get_extent().second;
+                layer_t top_layer = regions.size();
+
+                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.
+                                */
 
-boost::shared_ptr<Playlist>
-Playlist::cut (nframes_t start, nframes_t cnt, bool result_is_hidden)
-{
-       boost::shared_ptr<Playlist> the_copy;
-       RegionList thawlist;
-       char buf[32];
+                                copy_of_region->set_layer (copy_of_region->layer() + top_layer);
+                                add_region_internal (copy_of_region, (*i)->position() + pos);
+                        }
+                        pos += shift;
+                }
+
+
+                /* XXX shall we handle fractional cases at some point? */
+
+                if (old_length != _get_extent().second) {
+                        notify_length_changed ();
+                }
+
+
+        }
+
+        return 0;
+ }
+
+
+ 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, true);
+                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);
+                }
+        }
+ }
+
+ 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);
+        }
+
+        for (RegionList::iterator r = fixup.begin(); r != fixup.end(); ++r) {
+                (*r)->recompute_position_from_lock_style ();
+        }
+ }
 
-       snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
-       string new_name = _name;
-       new_name += '.';
-       new_name += buf;
+ void
+ Playlist::split (framepos_t at)
+ {
+        RegionLock rlock (this);
+        RegionList copy (regions.rlist());
+
+        /* use a copy since this operation can modify the region list
+         */
 
-       if ((the_copy = PlaylistFactory::create (shared_from_this(), start, cnt, new_name, result_is_hidden)) == 0) {
-               return boost::shared_ptr<Playlist>();
-       }
+        for (RegionList::iterator r = copy.begin(); r != copy.end(); ++r) {
+                _split_region (*r, at);
+        }
+ }
 
-       partition_internal (start, start+cnt-1, true, thawlist);
+ void
+ Playlist::split_region (boost::shared_ptr<Region> region, framepos_t playlist_position)
+ {
+        RegionLock 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::position, region->position ());
+                plist.add (Properties::length, before);
+                plist.add (Properties::name, before_name);
+                plist.add (Properties::left_of_split, true);
+
+                /* 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::position, region->position() + before);
+                plist.add (Properties::length, after);
+                plist.add (Properties::name, after_name);
+                plist.add (Properties::right_of_split, true);
+
+                /* same note as above */
+                right = RegionFactory::create (region, before, plist);
+        }
+
+        add_region_internal (left, region->position());
+        add_region_internal (right, region->position() + before);
+
+        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);
+
+        layer_op_counter++;
+
+        finalize_split_region (region, left, right);
+
+        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)
+ {
+        {
+                RegionLock 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_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) && !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()
+        */
 
-       for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
-               (*i)->thaw ("playlist cut");
-       }
+        if (what_changed.contains (our_interests)) {
+                save = true;
+        }
+
+        return save;
+ }
+
+ void
+ Playlist::drop_regions ()
+ {
+        RegionLock rl (this);
+        regions.clear ();
+        all_regions.clear ();
+ }
 
-       return the_copy;
-}
+ void
+ Playlist::sync_all_regions_with_regions ()
+ {
+        RegionLock rl (this);
 
-boost::shared_ptr<Playlist>
-Playlist::copy (nframes_t start, nframes_t cnt, bool result_is_hidden)
-{
-       char buf[32];
+        all_regions.clear ();
 
-       snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
-       string new_name = _name;
-       new_name += '.';
-       new_name += buf;
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                all_regions.insert (*i);
+        }
+ }
 
-       cnt = min (_get_maximum_extent() - start, cnt);
-       return PlaylistFactory::create (shared_from_this(), start, cnt, new_name, result_is_hidden);
-}
+ void
+ Playlist::clear (bool with_signals)
+ {
+        {
+                RegionLock rl (this);
 
-int
-Playlist::paste (boost::shared_ptr<Playlist> other, nframes_t position, float times)
-{
-       times = fabs (times);
-       nframes_t old_length;
+                region_state_changed_connections.drop_connections ();
 
-       {
-               RegionLock rl1 (this);
-               RegionLock rl2 (other.get());
+                for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                        pending_removes.insert (*i);
+                }
 
-               old_length = _get_maximum_extent();
+                regions.clear ();
 
-               int itimes = (int) floor (times);
-               nframes_t pos = position;
-               nframes_t shift = other->_get_maximum_extent();
-               layer_t top_layer = regions.size();
+                for (set<boost::shared_ptr<Region> >::iterator s = pending_removes.begin(); s != pending_removes.end(); ++s) {
+                        remove_dependents (*s);
+                }
+        }
 
-               while (itimes--) {
-                       for (RegionList::iterator i = other->regions.begin(); i != other->regions.end(); ++i) {
-                               boost::shared_ptr<Region> copy_of_region = RegionFactory::create (*i);
+        if (with_signals) {
 
-                               /* put these new regions on top of all existing ones, but preserve
-                                  the ordering they had in the original playlist.
-                               */
+                for (set<boost::shared_ptr<Region> >::iterator s = pending_removes.begin(); s != pending_removes.end(); ++s) {
+                        RegionRemoved (boost::weak_ptr<Region> (*s)); /* EMIT SIGNAL */
+                }
 
-                               copy_of_region->set_layer (copy_of_region->layer() + top_layer);
-                               add_region_internal (copy_of_region, copy_of_region->position() + pos);
-                       }
-                       pos += shift;
-               }
+                pending_removes.clear ();
+                pending_length = false;
+                LengthChanged ();
+                pending_contents_change = false;
+                ContentsChanged ();
+        }
 
+ }
 
-               /* XXX shall we handle fractional cases at some point? */
+ /***********************************************************************
+  FINDING THINGS
+  **********************************************************************/
 
-               if (old_length != _get_maximum_extent()) {
-                       notify_length_changed ();
-               }
+ Playlist::RegionList *
+ Playlist::regions_at (framepos_t frame)
 
+ {
+        RegionLock rlock (this);
+        return find_regions_at (frame);
+ }
 
-       }
+ uint32_t
+ Playlist::count_regions_at (framepos_t frame) const
+ {
+        RegionLock rlock (const_cast<Playlist*>(this));
+        uint32_t cnt = 0;
+
+        for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+                if ((*i)->covers (frame)) {
+                        cnt++;
+                }
+        }
+
+        return cnt;
+ }
+
+ 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(); ) {
 
-       return 0;
-}
+                RegionList::iterator tmp = i;
+                ++tmp;
 
+                if ((*i)->muted()) {
+                        rlist->erase (i);
+                }
 
-void
-Playlist::duplicate (boost::shared_ptr<Region> region, nframes_t position, float times)
-{
-       times = fabs (times);
+                i = tmp;
+        }
 
-       RegionLock rl (this);
-       int itimes = (int) floor (times);
-       nframes_t pos = position;
+        boost::shared_ptr<Region> region;
 
-       while (itimes--) {
-               boost::shared_ptr<Region> copy = RegionFactory::create (region);
-               add_region_internal (copy, pos);
-               pos += region->length();
-       }
+        if (rlist->size()) {
+                RegionSortByLayer cmp;
+                rlist->sort (cmp);
+                region = rlist->back();
+        }
 
-       if (floor (times) != times) {
-               nframes_t length = (nframes_t) floor (region->length() * (times - floor (times)));
-               string name;
-               _session.region_name (name, region->name(), false);
-               boost::shared_ptr<Region> sub = RegionFactory::create (region, 0, length, name, region->layer(), region->flags());
-               add_region_internal (sub, pos);
-       }
-}
+        delete rlist;
+        return region;
+ }
 
-void
-Playlist::shift (nframes64_t at, nframes64_t distance, bool move_intersected, bool ignore_music_glue)
-{
-       RegionLock rlock (this);
-       RegionList copy (regions);
-       RegionList fixup;
+ Playlist::RegionList*
+ Playlist::regions_to_read (framepos_t start, framepos_t end)
+ {
+        /* Caller must hold lock */
 
-       for (RegionList::iterator r = copy.begin(); r != copy.end(); ++r) {
+        RegionList covering;
+        set<framepos_t> to_check;
+        set<boost::shared_ptr<Region> > unique;
 
-               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)->positional_lock_style() != Region::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 (nframes64_t at)
-{
-       RegionLock rlock (this);
-       RegionList copy (regions);
-
-       /* 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, nframes_t playlist_position)
-{
-       RegionLock rl (this);
-       _split_region (region, playlist_position);
-}
-
-void
-Playlist::_split_region (boost::shared_ptr<Region> region, nframes_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;
-       nframes_t before;
-       nframes_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;
-
-       _session.region_name (before_name, region->name(), false);
-       left = RegionFactory::create (region, 0, before, before_name, region->layer(), Region::Flag (region->flags()|Region::LeftOfSplit));
-
-       _session.region_name (after_name, region->name(), false);
-       right = RegionFactory::create (region, before, after, after_name, region->layer(), Region::Flag (region->flags()|Region::RightOfSplit));
-
-       add_region_internal (left, region->position());
-       add_region_internal (right, region->position() + before);
-
-       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);
-
-       layer_op_counter++;
-
-       finalize_split_region (region, left, right);
-
-       remove_region_internal (region);
-
-       _splicing = old_sp;
-}
-
-void
-Playlist::possibly_splice (nframes_t at, nframes64_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 (nframes_t at, nframes64_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 (nframes_t at, nframes64_t distance, boost::shared_ptr<Region> exclude)
-{
-       {
-               RegionLock rl (this);
-               core_splice (at, distance, exclude);
-       }
-}
-
-void
-Playlist::splice_unlocked (nframes_t at, nframes64_t distance, boost::shared_ptr<Region> exclude)
-{
-       core_splice (at, distance, exclude);
-}
-
-void
-Playlist::core_splice (nframes_t at, nframes64_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) {
-                       nframes64_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 (Change what_changed, boost::shared_ptr<Region> region)
-{
-       if (in_set_state || _splicing || _nudging || _shuffling) {
-               return;
-       }
-
-       if (what_changed & ARDOUR::PositionChanged) {
-
-               /* 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()) {
-                       warning << string_compose (_("%1: bounds changed received for region (%2)not in playlist"),
-                                           _name, region->name())
-                               << endmsg;
-                       return;
-               }
-
-               regions.erase (i);
-               regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region);
-       }
-
-       if (what_changed & Change (ARDOUR::PositionChanged|ARDOUR::LengthChanged)) {
-
-               nframes64_t delta = 0;
-
-               if (what_changed & ARDOUR::PositionChanged) {
-                       delta = (nframes64_t) region->position() - (nframes64_t) region->last_position();
-               }
-
-               if (what_changed & ARDOUR::LengthChanged) {
-                       delta += (nframes64_t) region->length() - (nframes64_t) 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 (Change 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 (Change what_changed, boost::shared_ptr<Region> region)
-{
-       Change our_interests = Change (Region::MuteChanged|Region::LayerChanged|Region::OpacityChanged);
-       bool save = false;
-
-       if (in_set_state || in_flush) {
-               return false;
-       }
-
-       if (what_changed & BoundsChanged) {
-               region_bounds_changed (what_changed, region);
-               save = !(_splicing || _nudging);
-       }
-
-       if ((what_changed & our_interests) &&
-           !(what_changed & Change (ARDOUR::PositionChanged|ARDOUR::LengthChanged))) {
-               check_dependents (region, false);
-       }
-
-       if (what_changed & Change (ARDOUR::PositionChanged)) {
-               notify_region_moved (region);
-       }
-
-       if (what_changed & 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);
-
-               for (
-                       std::list<sigc::connection>::iterator i = region_state_changed_connections.begin ();
-                       i != region_state_changed_connections.end ();
-                       ++i
-               ) {
-                       i->disconnect ();
-               }
-
-               for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-                       pending_removes.insert (*i);
-               }
-
-               regions.clear ();
-       }
-
-       if (with_signals) {
-               pending_length = false;
-               LengthChanged ();
-               pending_modified = false;
-               Modified ();
-       }
-
-}
-
-/***********************************************************************
- FINDING THINGS
- **********************************************************************/
-
-Playlist::RegionList *
-Playlist::regions_at (nframes_t frame)
-
-{
-       RegionLock rlock (this);
-       return find_regions_at (frame);
-}
-
-boost::shared_ptr<Region>
-Playlist::top_region_at (nframes_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 (nframes_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);
-               }
-
-               i = tmp;
-       }
-
-       boost::shared_ptr<Region> region;
-
-       if (rlist->size()) {
-               RegionSortByLayer cmp;
-               rlist->sort (cmp);
-               region = rlist->back();
-       }
-
-       delete rlist;
-       return region;
-}
-
-Playlist::RegionList*
-Playlist::regions_to_read (nframes_t start, nframes_t end)
-{
-       /* Caller must hold lock */
-
-       RegionList covering;
-       set<nframes_t> to_check;
-       set<boost::shared_ptr<Region> > unique;
-       RegionList here;
-
-       to_check.insert (start);
-       to_check.insert (end);
-
-       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);
-                       break;
-
-               case OverlapStart:
-                       to_check.insert ((*i)->position());
-                       covering.push_back (*i);
-                       break;
-
-               case OverlapEnd:
-                       to_check.insert ((*i)->last_frame());
-                       covering.push_back (*i);
-                       break;
-
-               case OverlapExternal:
-                       covering.push_back (*i);
-                       to_check.insert ((*i)->position());
-                       to_check.insert ((*i)->last_frame());
-                       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());
-
-       } else {
-
-               for (set<nframes_t>::iterator t = to_check.begin(); t != to_check.end(); ++t) {
-
-                       here.clear ();
-
-                       for (RegionList::iterator x = covering.begin(); x != covering.end(); ++x) {
-
-                               if ((*x)->covers (*t)) {
-                                       here.push_back (*x);
-                               }
-                       }
-
-                       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 */
-
-                                       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);
-               }
-       }
-
-       return rlist;
-}
-
-Playlist::RegionList *
-Playlist::find_regions_at (nframes_t frame)
-{
-       /* Caller must hold lock */
-
-       RegionList *rlist = new RegionList;
-
-       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-               if ((*i)->covers (frame)) {
-                       rlist->push_back (*i);
-               }
-       }
-
-       return rlist;
-}
-
-Playlist::RegionList *
-Playlist::regions_touched (nframes_t start, nframes_t end)
-{
-       RegionLock rlock (this);
-       RegionList *rlist = new RegionList;
-
-       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-               if ((*i)->coverage (start, end) != OverlapNone) {
-                       rlist->push_back (*i);
-               }
-       }
-
-       return rlist;
-}
-
-nframes64_t
-Playlist::find_next_transient (nframes64_t from, int dir)
-{
-       RegionLock rlock (this);
-       AnalysisFeatureList points;
-       AnalysisFeatureList these_points;
-
-       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-               if (dir > 0) {
-                       if ((*i)->last_frame() < from) {
-                               continue;
-                       }
-               } else {
-                       if ((*i)->first_frame() > from) {
-                               continue;
-                       }
-               }
-
-               (*i)->get_transients (these_points);
-
-               /* add first frame, just, err, because */
-
-               these_points.push_back ((*i)->first_frame());
-
-               points.insert (points.end(), these_points.begin(), these_points.end());
-               these_points.clear ();
-       }
-
-       if (points.empty()) {
-               return -1;
-       }
-
-       TransientDetector::cleanup_transients (points, _session.frame_rate(), 3.0);
-       bool reached = false;
-
-       if (dir > 0) {
-               for (AnalysisFeatureList::iterator x = points.begin(); x != points.end(); ++x) {
-                       if ((*x) >= from) {
-                               reached = true;
-                       }
+        to_check.insert (start);
+        to_check.insert (end);
 
-                       if (reached && (*x) > from) {
-                               return *x;
-                       }
-               }
-       } else {
-               for (AnalysisFeatureList::reverse_iterator x = points.rbegin(); x != points.rend(); ++x) {
-                       if ((*x) <= from) {
-                               reached = true;
-                       }
-
-                       if (reached && (*x) < from) {
-                               return *x;
-                       }
-               }
-       }
+        DEBUG_TRACE (DEBUG::AudioPlayback, ">>>>> REGIONS TO READ\n");
 
-       return -1;
-}
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
 
-boost::shared_ptr<Region>
-Playlist::find_next_region (nframes_t frame, RegionPoint point, int dir)
-{
-       RegionLock rlock (this);
-       boost::shared_ptr<Region> ret;
-       nframes_t closest = max_frames;
+                /* find all/any regions that span start+end */
 
-       bool end_iter = false;
+                switch ((*i)->coverage (start, end)) {
+                case OverlapNone:
+                        break;
 
-       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                case OverlapInternal:
+                        covering.push_back (*i);
+                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("toread: will cover %1 (OInternal)\n", (*i)->name()));
+                        break;
 
-               if(end_iter) 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;
 
-               nframes_t distance;
-               boost::shared_ptr<Region> r = (*i);
-               nframes_t pos = 0;
+                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;
 
-               switch (point) {
-               case Start:
-                       pos = r->first_frame ();
-                       break;
-               case End:
-                       pos = r->last_frame ();
-                       break;
-               case SyncPoint:
-                       pos = r->sync_position ();
-                       // r->adjust_to_sync (r->first_frame());
-                       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;
+                }
 
-               switch (dir) {
-               case 1: /* forwards */
+                /* don't go too far */
 
-                       if (pos > frame) {
-                               if ((distance = pos - frame) < closest) {
-                                       closest = distance;
-                                       ret = r;
-                                       end_iter = true;
-                               }
-                       }
+                if ((*i)->position() > end) {
+                        break;
+                }
+        }
 
-                       break;
+        RegionList* rlist = new RegionList;
 
-               default: /* backwards */
+        /* find all the regions that cover each position .... */
 
-                       if (pos < frame) {
-                               if ((distance = frame - pos) < closest) {
-                                       closest = distance;
-                                       ret = r;
-                               }
-                       }
-                       else {
-                               end_iter = true;
-                       }
+        if (covering.size() == 1) {
 
-                       break;
-               }
-       }
+                rlist->push_back (covering.front());
+                DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Just one covering region (%1)\n", covering.front()->name()));
 
-       return ret;
-}
+        } else {
 
-nframes64_t
-Playlist::find_next_region_boundary (nframes64_t frame, int dir)
-{
-       RegionLock rlock (this);
+                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)));
+                                }
+
+                        }
 
-       nframes64_t closest = max_frames;
-       nframes64_t ret = -1;
+                        RegionSortByLayer cmp;
+                        here.sort (cmp);
 
-       if (dir > 0) {
+                        /* ... and get the top/transparent regions at "here" */
 
-               for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                        for (RegionList::reverse_iterator c = here.rbegin(); c != here.rend(); ++c) {
+
+                                unique.insert (*c);
 
-                       boost::shared_ptr<Region> r = (*i);
-                       nframes64_t distance;
+                                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);
+                }
+        }
+
+        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)
+ {
+        /* Caller must hold lock */
+
+        RegionList *rlist = new RegionList;
+
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                if ((*i)->covers (frame)) {
+                        rlist->push_back (*i);
+                }
+        }
+
+        return rlist;
+ }
+
+ Playlist::RegionList *
+ Playlist::regions_touched (framepos_t start, framepos_t end)
+ {
+        RegionLock rlock (this);
+        RegionList *rlist = new RegionList;
 
-                       if (r->first_frame() > frame) {
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                if ((*i)->coverage (start, end) != OverlapNone) {
+                        rlist->push_back (*i);
+                }
+        }
+
+        return rlist;
+ }
 
-                               distance = r->first_frame() - frame;
+ framepos_t
+ Playlist::find_next_transient (framepos_t from, int dir)
+ {
+        RegionLock rlock (this);
+        AnalysisFeatureList points;
+        AnalysisFeatureList these_points;
+
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+                if (dir > 0) {
+                        if ((*i)->last_frame() < from) {
+                                continue;
+                        }
+                } else {
+                        if ((*i)->first_frame() > from) {
+                                continue;
+                        }
+                }
+
+                (*i)->get_transients (these_points);
+
+                /* add first frame, just, err, because */
+
+                these_points.push_back ((*i)->first_frame());
+
+                points.insert (points.end(), these_points.begin(), these_points.end());
+                these_points.clear ();
+        }
+
+        if (points.empty()) {
+                return -1;
+        }
+
+        TransientDetector::cleanup_transients (points, _session.frame_rate(), 3.0);
+        bool reached = false;
+
+        if (dir > 0) {
+                for (AnalysisFeatureList::iterator x = points.begin(); x != points.end(); ++x) {
+                        if ((*x) >= from) {
+                                reached = true;
+                        }
+
+                        if (reached && (*x) > from) {
+                                return *x;
+                        }
+                }
+        } else {
+                for (AnalysisFeatureList::reverse_iterator x = points.rbegin(); x != points.rend(); ++x) {
+                        if ((*x) <= from) {
+                                reached = true;
+                        }
+
+                        if (reached && (*x) < from) {
+                                return *x;
+                        }
+                }
+        }
+
+        return -1;
+ }
+
+ boost::shared_ptr<Region>
+ Playlist::find_next_region (framepos_t frame, RegionPoint point, int dir)
+ {
+        RegionLock rlock (this);
+        boost::shared_ptr<Region> ret;
+        framepos_t closest = max_framepos;
+
+        bool end_iter = false;
+
+        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+                if(end_iter) break;
+
+                frameoffset_t distance;
+                boost::shared_ptr<Region> r = (*i);
+                framepos_t pos = 0;
+
+                switch (point) {
+                case Start:
+                        pos = r->first_frame ();
+                        break;
+                case End:
+                        pos = r->last_frame ();
+                        break;
+                case SyncPoint:
+                        pos = r->sync_position ();
+                        break;
+                }
+
+                switch (dir) {
+                case 1: /* forwards */
 
-                               if (distance < closest) {
-                                       ret = r->first_frame();
-                                       closest = distance;
-                               }
-                       }
+                        if (pos > frame) {
+                                if ((distance = pos - frame) < closest) {
+                                        closest = distance;
+                                        ret = r;
+                                        end_iter = true;
+                                }
+                        }
 
-                       if (r->last_frame () > frame) {
+                        break;
 
-                               distance = r->last_frame () - frame;
+                default: /* backwards */
 
-                               if (distance < closest) {
-                                       ret = r->last_frame ();
-                                       closest = distance;
-                               }
-                       }
-               }
+                        if (pos < frame) {
+                                if ((distance = frame - pos) < closest) {
+                                        closest = distance;
+                                        ret = r;
+                                }
+                        }
+                        else {
+                                end_iter = true;
+                        }
 
-       } else {
+                        break;
+                }
+        }
 
-               for (RegionList::reverse_iterator i = regions.rbegin(); i != regions.rend(); ++i) {
+        return ret;
+ }
 
-                       boost::shared_ptr<Region> r = (*i);
-                       nframes64_t distance;
+ framepos_t
+ Playlist::find_next_region_boundary (framepos_t frame, int dir)
+ {
+        RegionLock rlock (this);
 
-                       if (r->last_frame() < frame) {
+        framepos_t closest = max_framepos;
+        framepos_t ret = -1;
+
+        if (dir > 0) {
+
+                for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+                        boost::shared_ptr<Region> r = (*i);
+                        frameoffset_t distance;
+
+                        if (r->first_frame() > frame) {
+
+                                distance = r->first_frame() - frame;
+
+                                if (distance < closest) {
+                                        ret = r->first_frame();
+                                        closest = distance;
+                                }
+                        }
+
+                        if (r->last_frame () > frame) {
+
+                                distance = r->last_frame () - frame;
+
+                                if (distance < closest) {
+                                        ret = r->last_frame ();
+                                        closest = distance;
+                                }
+                        }
+                }
 
-                               distance = frame - r->last_frame();
+        } else {
 
-                               if (distance < closest) {
-                                       ret = r->last_frame();
-                                       closest = distance;
-                               }
-                       }
+                for (RegionList::reverse_iterator i = regions.rbegin(); i != regions.rend(); ++i) {
 
-                       if (r->first_frame() < frame) {
+                        boost::shared_ptr<Region> r = (*i);
+                        frameoffset_t distance;
 
-                               distance = frame - r->first_frame();
+                        if (r->last_frame() < frame) {
 
-                               if (distance < closest) {
-                                       ret = r->first_frame();
-                                       closest = distance;
-                               }
-                       }
-               }
-       }
+                                distance = frame - r->last_frame();
 
-       return ret;
-}
+                                if (distance < closest) {
+                                        ret = r->last_frame();
+                                        closest = distance;
+                                }
+                        }
+
+                        if (r->first_frame() < frame) {
+
+                                distance = frame - r->first_frame();
+
+                                if (distance < closest) {
+                                        ret = r->first_frame();
+                                        closest = distance;
+                                }
+                        }
+                }
+        }
 
-/***********************************************************************/
+        return ret;
+ }
 
 
+ /***********************************************************************/
 
 
-void
-Playlist::mark_session_dirty ()
-{
-       if (!in_set_state && !holding_state ()) {
-               _session.set_dirty();
-       }
-}
 
-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++;
+ void
+ Playlist::mark_session_dirty ()
+ {
+        if (!in_set_state && !holding_state ()) {
+                _session.set_dirty();
+        }
+ }
 
-       if (node.name() != "Playlist") {
-               in_set_state--;
-               return -1;
-       }
+ void
+ Playlist::rdiff (vector<Command*>& cmds) const
+ {
+        RegionLock rlock (const_cast<Playlist *> (this));
+        Stateful::rdiff (cmds);
+ }
 
-       freeze ();
+ void
+ Playlist::clear_owned_changes ()
+ {
+        RegionLock rlock (this);
+        Stateful::clear_owned_changes ();
+ }
 
-       plist = node.properties();
+ 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()));
 
-       for (piter = plist.begin(); piter != plist.end(); ++piter) {
+        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);
+        }
 
-               prop = *piter;
+        thaw ();
+ }
 
-               if (prop->name() == X_("name")) {
-                       _name = 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());
-               }
-       }
+ 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;
 
-       clear (false);
+        in_set_state++;
 
-       nlist = node.children();
+        if (node.name() != "Playlist") {
+                in_set_state--;
+                return -1;
+        }
 
-       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+        freeze ();
 
-               child = *niter;
+        plist = node.properties();
 
-               if (child->name() == "Region") {
+        for (piter = plist.begin(); piter != plist.end(); ++piter) {
 
-                       if ((prop = child->property ("id")) == 0) {
-                               error << _("region state node has no ID, ignored") << endmsg;
-                               continue;
-                       }
+                prop = *piter;
 
-                       ID id = prop->value ();
+                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());
+                } else if (prop->name() == X_("combine-ops")) {
+                        _combine_ops = atoi (prop->value());
+                }
+        }
+
+        clear (true);
+
+        nlist = node.children();
 
-                       if ((region = region_by_id (id))) {
+        for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
 
-                               Change what_changed = Change (0);
+                child = *niter;
 
-                               if (region->set_live_state (*child, version, what_changed, true)) {
-                                       error << _("Playlist: cannot reset region state from XML") << endmsg;
-                                       continue;
-                               }
+                if (child->name() == "Region") {
 
-                       } else if ((region = RegionFactory::create (_session, *child, true)) == 0) {
-                               error << _("Playlist: cannot create region from XML") << endmsg;
-                               continue;
+                        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;
                        }
 
                        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 ();
 
                }
        }
 
-       notify_modified ();
-
-       thaw ();
-
-       /* update dependents, which was not done during add_region_internal
-          due to in_set_state being true
-       */
+       if (seen_region_nodes && regions.empty()) {
+               ret = -1;
+       } else {
 
-       for (RegionList::iterator r = regions.begin(); r != regions.end(); ++r) {
-               check_dependents (*r, false);
+               /* 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);
+               }
        }
+               
+       thaw ();
+       notify_contents_changed ();
 
        in_set_state--;
        first_set_state = false;
-       return 0;
+       return ret;
 }
 
 XMLNode&
@@ -1902,15 +2316,20 @@ Playlist::state (bool full_state)
        XMLNode *node = new XMLNode (X_("Playlist"));
        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);
+       node->add_property (X_("orig-diskstream-id"), buf);
        node->add_property (X_("frozen"), _frozen ? "yes" : "no");
 
        if (full_state) {
                RegionLock rlock (this, false);
+
+               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());
                }
@@ -1937,27 +2356,34 @@ Playlist::n_regions() const
        return regions.size();
 }
 
-nframes_t
-Playlist::get_maximum_extent () const
+pair<framepos_t, framepos_t>
+Playlist::get_extent () const
 {
        RegionLock rlock (const_cast<Playlist *>(this), false);
-       return _get_maximum_extent ();
+       return _get_extent ();
 }
 
-ARDOUR::nframes_t
-Playlist::_get_maximum_extent () const
+pair<framepos_t, framepos_t>
+Playlist::_get_extent () const
 {
-       RegionList::const_iterator i;
-       nframes_t max_extent = 0;
-       nframes_t end = 0;
+       pair<framepos_t, framepos_t> ext (max_framepos, 0);
+
+       if (regions.empty()) {
+               ext.first = 0;
+               return ext;
+       }
 
-       for (i = regions.begin(); i != regions.end(); ++i) {
-               if ((end = (*i)->position() + (*i)->length()) > max_extent) {
-                       max_extent = end;
+       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+               pair<framepos_t, framepos_t> const e ((*i)->position(), (*i)->position() + (*i)->length());
+               if (e.first < ext.first) {
+                       ext.first = e.first;
+               }
+               if (e.second > ext.second) {
+                       ext.second = e.second;
                }
        }
 
-       return max_extent;
+       return ext;
 }
 
 string
@@ -1966,8 +2392,8 @@ Playlist::bump_name (string name, Session &session)
        string newname = name;
 
        do {
-               newname = bump_name_once (newname);
-       } while (session.playlist_by_name (newname)!=NULL);
+               newname = bump_name_once (newname, '.');
+       } while (session.playlists->by_name (newname)!=NULL);
 
        return newname;
 }
@@ -1998,11 +2424,13 @@ Playlist::set_edit_mode (EditMode mode)
 void
 Playlist::relayer ()
 {
-       /* don't send multiple Modified notifications
-          when multiple regions are relayered.
-       */
+       /* never compute layers when changing state for undo/redo or setting from XML */
 
-       freeze ();
+       if (in_update || in_set_state) {
+               return;
+       }
+
+       bool changed = false;
 
        /* 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
@@ -2013,8 +2441,8 @@ Playlist::relayer ()
        int const divisions = 512;
 
        /* find the start and end positions of the regions on this playlist */
-       nframes_t start = UINT_MAX;
-       nframes_t end = 0;
+       framepos_t start = INT64_MAX;
+       framepos_t end = 0;
        for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
                start = min (start, (*i)->position());
                end = max (end, (*i)->position() + (*i)->length());
@@ -2030,7 +2458,7 @@ Playlist::relayer ()
           which depends on the layer model
        */
 
-       RegionList copy = regions;
+       RegionList copy = regions.rlist();
 
        /* sort according to the model and the layering mode that we're in */
 
@@ -2044,19 +2472,28 @@ Playlist::relayer ()
 
        }
 
+
        for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) {
 
                /* reset the pending explicit relayer flag for every region, now that we're relayering */
                (*i)->set_pending_explicit_relayer (false);
 
-               /* find the time divisions that this region covers */
-               int const start_division = floor ( ((*i)->position() - start) / division_size);
-               int end_division = floor ( ((*i)->position() + (*i)->length() - start) / division_size );
-               if (end_division == divisions) {
-                       end_division--;
+               /* 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
+                  start_division = end_division = 0.
+               */
+               int start_division = 0;
+               int end_division = 0;
+
+               if (division_size > 0) {
+                       start_division = floor ( ((*i)->position() - start) / division_size);
+                       end_division = floor ( ((*i)->position() + (*i)->length() - start) / division_size );
+                       if (end_division == divisions) {
+                               end_division--;
+                       }
                }
 
-               assert (end_division < divisions);
+               assert (divisions == 0 || end_division < divisions);
 
                /* find the lowest layer that this region can go on */
                size_t j = layers.size();
@@ -2099,20 +2536,16 @@ Playlist::relayer ()
                        layers[j][k].push_back (*i);
                }
 
+               if ((*i)->layer() != j) {
+                       changed = true;
+               }
+
                (*i)->set_layer (j);
        }
 
-       /* sending Modified means that various kinds of layering
-          models operate correctly at the GUI
-          level. slightly inefficient, but only slightly.
-
-          We force a Modified signal here in case no layers actually
-          changed.
-       */
-
-       notify_modified ();
-
-       thaw ();
+       if (changed) {
+               notify_layering_changed ();
+       }
 }
 
 /* XXX these layer functions are all deprecated */
@@ -2120,10 +2553,10 @@ Playlist::relayer ()
 void
 Playlist::raise_region (boost::shared_ptr<Region> region)
 {
-       uint32_t rsz = regions.size();
+       uint32_t top = regions.size() - 1;
        layer_t target = region->layer() + 1U;
 
-       if (target >= rsz) {
+       if (target >= top) {
                /* its already at the effective top */
                return;
        }
@@ -2148,22 +2581,48 @@ void
 Playlist::raise_region_to_top (boost::shared_ptr<Region> region)
 {
        /* does nothing useful if layering mode is later=higher */
-       if ((_session.config.get_layer_model() == MoveAddHigher) ||
-           (_session.config.get_layer_model() == AddHigher)) {
-               timestamp_layer_op (region);
-               relayer ();
+       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 */
-       if ((_session.config.get_layer_model() == MoveAddHigher) ||
-           (_session.config.get_layer_model() == AddHigher)) {
-               region->set_last_layer_op (0);
-               relayer ();
+       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
@@ -2172,7 +2631,6 @@ Playlist::move_region_to_layer (layer_t target_layer, boost::shared_ptr<Region>
        RegionList::iterator i;
        typedef pair<boost::shared_ptr<Region>,layer_t> LayerInfo;
        list<LayerInfo> layerinfo;
-       layer_t dest;
 
        {
                RegionLock rlock (const_cast<Playlist *> (this));
@@ -2183,6 +2641,8 @@ Playlist::move_region_to_layer (layer_t target_layer, boost::shared_ptr<Region>
                                continue;
                        }
 
+                       layer_t dest;
+
                        if (dir > 0) {
 
                                /* region is moving up, move all regions on intermediate layers
@@ -2218,6 +2678,8 @@ Playlist::move_region_to_layer (layer_t target_layer, boost::shared_ptr<Region>
                }
        }
 
+       freeze ();
+
        /* now reset the layers without holding the region lock */
 
        for (list<LayerInfo>::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) {
@@ -2226,24 +2688,24 @@ Playlist::move_region_to_layer (layer_t target_layer, boost::shared_ptr<Region>
 
        region->set_layer (target_layer);
 
-#if 0
-       /* now check all dependents */
+       /* now check all dependents, since we changed the layering */
 
        for (list<LayerInfo>::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) {
                check_dependents (x->first, false);
        }
 
        check_dependents (region, false);
-#endif
+       notify_layering_changed ();
+
+       thaw ();
 
        return 0;
 }
 
 void
-Playlist::nudge_after (nframes_t start, nframes_t distance, bool forwards)
+Playlist::nudge_after (framepos_t start, framecnt_t distance, bool forwards)
 {
        RegionList::iterator i;
-       nframes_t new_pos;
        bool moved = false;
 
        _nudging = true;
@@ -2255,10 +2717,12 @@ Playlist::nudge_after (nframes_t start, nframes_t distance, bool forwards)
 
                        if ((*i)->position() >= start) {
 
+                               framepos_t new_pos;
+
                                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;
                                        }
@@ -2272,7 +2736,7 @@ Playlist::nudge_after (nframes_t start, nframes_t distance, bool forwards)
                                        }
                                }
 
-                               (*i)->set_position (new_pos, this);
+                               (*i)->set_position (new_pos);
                                moved = true;
                        }
                }
@@ -2285,6 +2749,20 @@ Playlist::nudge_after (nframes_t start, nframes_t distance, bool forwards)
 
 }
 
+bool
+Playlist::uses_source (boost::shared_ptr<const Source> src) const
+{
+       RegionLock rlock (const_cast<Playlist*> (this));
+
+       for (set<boost::shared_ptr<Region> >::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
 {
@@ -2301,8 +2779,23 @@ Playlist::find_region (const ID& id) const
        return boost::shared_ptr<Region> ();
 }
 
+uint32_t
+Playlist::region_use_count (boost::shared_ptr<Region> r) const
+{
+       RegionLock rlock (const_cast<Playlist*> (this));
+       uint32_t cnt = 0;
+
+       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+               if ((*i) == r) {
+                       cnt++;
+               }
+       }
+
+       return cnt;
+}
+
 boost::shared_ptr<Region>
-Playlist::region_by_id (ID id)
+Playlist::region_by_id (const ID& id) const
 {
        /* searches all regions ever added to this playlist */
 
@@ -2344,8 +2837,6 @@ Playlist::set_frozen (bool yn)
 void
 Playlist::timestamp_layer_op (boost::shared_ptr<Region> region)
 {
-//     struct timeval tv;
-//     gettimeofday (&tv, 0);
        region->set_last_layer_op (++layer_op_counter);
 }
 
@@ -2354,7 +2845,6 @@ void
 Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
 {
        bool moved = false;
-       nframes_t new_pos;
 
        if (region->locked()) {
                return;
@@ -2381,6 +2871,8 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
                                                        break;
                                                }
 
+                                               framepos_t new_pos;
+
                                                if ((*next)->position() != region->last_frame() + 1) {
                                                        /* they didn't used to touch, so after shuffle,
                                                           just have them swap positions.
@@ -2395,8 +2887,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 */
 
@@ -2422,6 +2914,7 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
                                                        break;
                                                }
 
+                                               framepos_t new_pos;
                                                if (region->position() != (*prev)->last_frame() + 1) {
                                                        /* they didn't used to touch, so after shuffle,
                                                           just have them swap positions.
@@ -2435,8 +2928,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 */
 
@@ -2459,7 +2952,7 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
                relayer ();
                check_dependents (region, false);
 
-               notify_modified();
+               notify_contents_changed();
        }
 
 }
@@ -2480,7 +2973,7 @@ void
 Playlist::update_after_tempo_map_change ()
 {
        RegionLock rlock (const_cast<Playlist*> (this));
-       RegionList copy (regions);
+       RegionList copy (regions.rlist());
 
        freeze ();
 
@@ -2492,7 +2985,7 @@ Playlist::update_after_tempo_map_change ()
 }
 
 void
-Playlist::foreach_region (sigc::slot<void, boost::shared_ptr<Region> > s)
+Playlist::foreach_region (boost::function<void(boost::shared_ptr<Region>)> s)
 {
        RegionLock rl (this, false);
        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
@@ -2524,10 +3017,10 @@ Playlist::set_explicit_relayering (bool e)
 
 
 bool
-Playlist::has_region_at (nframes64_t const p) const
+Playlist::has_region_at (framepos_t const p) const
 {
        RegionLock (const_cast<Playlist *> (this));
-       
+
        RegionList::const_iterator i = regions.begin ();
        while (i != regions.end() && !(*i)->covers (p)) {
                ++i;
@@ -2535,3 +3028,364 @@ Playlist::has_region_at (nframes64_t const p) const
 
        return (i != regions.end());
 }
+
+/** Remove any region that uses a given source */
+void
+Playlist::remove_region_by_source (boost::shared_ptr<Source> s)
+{
+       RegionLock 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);
+               }
+
+               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
+{
+       RegionLock 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;
+
+       for (RegionList::const_iterator i = r.begin(); i != r.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);
+
+               /* 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);
+
+       /* add any dependent regions to the new playlist */
+
+       copy_dependents (old_and_new_regions, pl.get());
+
+       /* 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);
+               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 occured) overlaps
+                * with the new state of the compound region.
+                */
+
+               original->clear_changes ();
+               modified_region = false;
+
+               switch (original->coverage (adjusted_start, adjusted_end)) {
+               case OverlapNone:
+                       /* original region does not cover any part
+                          of the current state of the compound region
+                       */
+                       continue;
+
+               case 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 OverlapExternal:
+                       /* overlap fully covers original, so leave it
+                          as is
+                       */
+                       break;
+
+               case OverlapEnd:
+                       /* overlap starts within but covers end,
+                          so trim the front of the region
+                       */
+                       original->trim_front (adjusted_start);
+                       modified_region = true;
+                       break;
+
+               case 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());
+       }
+
+       /* now move dependent regions back from the compound to this playlist */
+
+       pl->copy_dependents (old_and_new_regions, this);
+
+       in_partition = false;
+       thaw ();
+}
+
+uint32_t
+Playlist::max_source_level () const
+{
+       RegionLock 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;
+}
+
+
+uint32_t
+Playlist::count_joined_regions () const
+{
+       RegionLock rlock (const_cast<Playlist *> (this));
+       uint32_t cnt = 0;
+
+       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+               if ((*i)->max_source_level() > 0) {
+                       cnt++;
+               }
+       }
+
+       return cnt;
+}