Crossfades: default xfades are now constant-power, -3dB rule, other options are const...
[ardour.git] / libs / ardour / playlist.cc
index 7ef59645d136202f4ffd898df8af43411a398391..29be9ec49da120632cd57ff5593f471d114d47d6 100644 (file)
@@ -210,35 +210,35 @@ Playlist::Playlist (boost::shared_ptr<const Playlist> other, framepos_t start, f
                framepos_t position = 0;
                framecnt_t len = 0;
                string    new_name;
-               OverlapType overlap;
+               Evoral::OverlapType overlap;
 
                region = *i;
 
                overlap = region->coverage (start, end);
 
                switch (overlap) {
-               case OverlapNone:
+               case Evoral::OverlapNone:
                        continue;
 
-               case OverlapInternal:
+               case Evoral::OverlapInternal:
                        offset = start - region->position();
                        position = 0;
                        len = cnt;
                        break;
 
-               case OverlapStart:
+               case Evoral::OverlapStart:
                        offset = 0;
                        position = region->position() - start;
                        len = end - region->position();
                        break;
 
-               case OverlapEnd:
+               case Evoral::OverlapEnd:
                        offset = start - region->position();
                        position = 0;
                        len = region->length() - offset;
                        break;
 
-               case OverlapExternal:
+               case Evoral::OverlapExternal:
                        offset = 0;
                        position = region->position() - start;
                        len = region->length();
@@ -310,7 +310,7 @@ Playlist::init (bool hide)
        _shuffling = false;
        _nudging = false;
        in_set_state = 0;
-       in_update = false;
+       in_undo = false;
        _edit_mode = Config->get_edit_mode();
        in_flush = false;
        in_partition = false;
@@ -396,7 +396,7 @@ Playlist::set_name (const string& str)
 void
 Playlist::begin_undo ()
 {
-       in_update = true;
+       in_undo = true;
        freeze ();
 }
 
@@ -404,7 +404,7 @@ void
 Playlist::end_undo ()
 {
        thaw (true);
-       in_update = false;
+       in_undo = false;
 }
 
 void
@@ -561,9 +561,8 @@ Playlist::notify_region_added (boost::shared_ptr<Region> r)
 void
 Playlist::flush_notifications (bool from_undo)
 {
-       set<boost::shared_ptr<Region> > dependent_checks_needed;
        set<boost::shared_ptr<Region> >::iterator s;
-       uint32_t regions_changed = false;
+       bool regions_changed = false;
 
        if (in_flush) {
                return;
@@ -575,6 +574,10 @@ Playlist::flush_notifications (bool from_undo)
                regions_changed = true;
        }
 
+       /* XXX: it'd be nice if we could use pending_bounds for
+          RegionsExtended and RegionsMoved.
+       */
+
        /* we have no idea what order the regions ended up in pending
           bounds (it could be based on selection order, for example).
           so, to preserve layering in the "most recently moved is higher"
@@ -584,33 +587,34 @@ Playlist::flush_notifications (bool from_undo)
        // RegionSortByLayer cmp;
        // pending_bounds.sort (cmp);
 
+       list<Evoral::Range<framepos_t> > crossfade_ranges;
+
        for (RegionList::iterator r = pending_bounds.begin(); r != pending_bounds.end(); ++r) {
-               dependent_checks_needed.insert (*r);
+               crossfade_ranges.push_back ((*r)->last_range ());
+               crossfade_ranges.push_back ((*r)->range ());
        }
 
        for (s = pending_removes.begin(); s != pending_removes.end(); ++s) {
+               crossfade_ranges.push_back ((*s)->range ());
                remove_dependents (*s);
-               // cerr << _name << " sends RegionRemoved\n";
                RegionRemoved (boost::weak_ptr<Region> (*s)); /* EMIT SIGNAL */
        }
-
+       
        for (s = pending_adds.begin(); s != pending_adds.end(); ++s) {
-                // cerr << _name << " sends RegionAdded\n";
-                /* don't emit RegionAdded signal until relayering is done,
-                   so that the region is fully setup by the time
-                   anyone hear's that its been added
-                */
-                dependent_checks_needed.insert (*s);
-        }
+               crossfade_ranges.push_back ((*s)->range ());
+               /* don't emit RegionAdded signal until relayering is done,
+                  so that the region is fully setup by the time
+                  anyone hears that its been added
+               */
+       }
+
+       if (((regions_changed || pending_contents_change) && !in_set_state) || pending_layering) {
+               relayer ();
+       }
 
         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) {
@@ -618,11 +622,12 @@ Playlist::flush_notifications (bool from_undo)
                 RegionAdded (boost::weak_ptr<Region> (*s)); /* EMIT SIGNAL */
         }
 
-        for (s = dependent_checks_needed.begin(); s != dependent_checks_needed.end(); ++s) {
-                check_dependents (*s, false);
-        }
+        coalesce_and_check_crossfades (crossfade_ranges);
 
         if (!pending_range_moves.empty ()) {
+                /* We don't need to check crossfades for these as pending_bounds has
+                   already covered it.
+                */
                 RangesMoved (pending_range_moves, from_undo);
         }
 
@@ -650,6 +655,7 @@ Playlist::flush_notifications (bool from_undo)
    PLAYLIST OPERATIONS
   *************************************************************/
 
+/** Note: this calls set_layer (..., DBL_MAX) so it will reset the layering index of region */
  void
  Playlist::add_region (boost::shared_ptr<Region> region, framepos_t position, float times, bool auto_partition)
  {
@@ -665,8 +671,8 @@ Playlist::flush_notifications (bool from_undo)
         }
 
         if (itimes >= 1) {
-                region->set_pending_layer (DBL_MAX);
                 add_region_internal (region, pos);
+                set_layer (region, DBL_MAX);
                 pos += region->length();
                 --itimes;
         }
@@ -678,8 +684,8 @@ Playlist::flush_notifications (bool from_undo)
 
         for (int i = 0; i < itimes; ++i) {
                 boost::shared_ptr<Region> copy = RegionFactory::create (region, true);
-                copy->set_pending_layer (DBL_MAX);
                 add_region_internal (copy, pos);
+                set_layer (copy, DBL_MAX);
                 pos += region->length();
         }
 
@@ -699,8 +705,8 @@ Playlist::flush_notifications (bool from_undo)
                         plist.add (Properties::layer, region->layer());
 
                         boost::shared_ptr<Region> sub = RegionFactory::create (region, plist);
-                        sub->set_pending_layer (DBL_MAX);
                         add_region_internal (sub, pos);
+                        set_layer (sub, DBL_MAX);
                 }
         }
 
@@ -750,7 +756,7 @@ Playlist::flush_notifications (bool from_undo)
         notify_region_added (region);
 
         if (!holding_state ()) {
-                check_dependents (region, false);
+                check_crossfades (region->range ());
         }
 
         region->PropertyChanged.connect_same_thread (region_state_changed_connections, boost::bind (&Playlist::region_changed_proxy, this, _1, boost::weak_ptr<Region> (region)));
@@ -767,8 +773,8 @@ Playlist::flush_notifications (bool from_undo)
         _splicing = true;
 
         remove_region_internal (old);
-        newr->set_pending_layer (newr->layer ());
         add_region_internal (newr, pos);
+        set_layer (newr, old->layer ());
 
         _splicing = old_sp;
 
@@ -823,13 +829,13 @@ Playlist::flush_notifications (bool from_undo)
         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));
+                                results.push_back (*i);
                         }
                 }
         } else {
                 for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
                         if ((*i)->equivalent (other)) {
-                                results.push_back ((*i));
+                                results.push_back (*i);
                         }
                 }
         }
@@ -875,7 +881,7 @@ Playlist::flush_notifications (bool from_undo)
                 boost::shared_ptr<Region> current;
                 string new_name;
                 RegionList::iterator tmp;
-                OverlapType overlap;
+                Evoral::OverlapType overlap;
                 framepos_t pos1, pos2, pos3, pos4;
 
                 in_partition = true;
@@ -911,7 +917,7 @@ Playlist::flush_notifications (bool from_undo)
                                 continue;
                         }
 
-                        if ((overlap = current->coverage (start, end)) == OverlapNone) {
+                        if ((overlap = current->coverage (start, end)) == Evoral::OverlapNone) {
                                 continue;
                         }
 
@@ -920,7 +926,7 @@ Playlist::flush_notifications (bool from_undo)
                         pos3 = end;
                         pos4 = current->last_frame();
 
-                        if (overlap == OverlapInternal) {
+                        if (overlap == Evoral::OverlapInternal) {
                                 /* split: we need 3 new regions, the front, middle and end.
                                    cut:   we need 2 regions, the front and end.
                                 */
@@ -982,7 +988,7 @@ Playlist::flush_notifications (bool from_undo)
                                 thawlist.push_back (current);
                                 current->cut_end (pos2 - 1);
 
-                        } else if (overlap == OverlapEnd) {
+                        } else if (overlap == Evoral::OverlapEnd) {
 
                                 /*
                                                               start           end
@@ -1022,7 +1028,7 @@ Playlist::flush_notifications (bool from_undo)
                                 thawlist.push_back (current);
                                 current->cut_end (pos2 - 1);
 
-                        } else if (overlap == OverlapStart) {
+                        } else if (overlap == Evoral::OverlapStart) {
 
                                 /* split: we need 2 regions: the front and the end.
                                    cut: just trim current to skip the cut area
@@ -1065,7 +1071,7 @@ Playlist::flush_notifications (bool from_undo)
                                 current->suspend_property_changes ();
                                 thawlist.push_back (current);
                                 current->trim_front (pos3);
-                        } else if (overlap == OverlapExternal) {
+                        } else if (overlap == Evoral::OverlapExternal) {
 
                                 /* split: no split required.
                                    cut: remove the region.
@@ -1094,9 +1100,7 @@ Playlist::flush_notifications (bool from_undo)
                 in_partition = false;
         }
 
-        for (RegionList::iterator i = new_regions.begin(); i != new_regions.end(); ++i) {
-                check_dependents (*i, false);
-        }
+        check_crossfades (Evoral::Range<framepos_t> (start, end));
  }
 
  boost::shared_ptr<Playlist>
@@ -1207,8 +1211,8 @@ Playlist::flush_notifications (bool from_undo)
                                    the ordering they had in the original playlist.
                                 */
 
-                                copy_of_region->set_pending_layer (copy_of_region->layer() + top);
                                 add_region_internal (copy_of_region, (*i)->position() + pos);
+                                set_layer (copy_of_region, copy_of_region->layer() + top);
                         }
                         pos += shift;
                 }
@@ -1229,8 +1233,8 @@ Playlist::flush_notifications (bool from_undo)
 
         while (itimes--) {
                 boost::shared_ptr<Region> copy = RegionFactory::create (region, true);
-                copy->set_pending_layer (DBL_MAX);
                 add_region_internal (copy, pos);
+                set_layer (copy, DBL_MAX);
                 pos += region->length();
         }
 
@@ -1247,8 +1251,8 @@ Playlist::flush_notifications (bool from_undo)
                         plist.add (Properties::name, name);
 
                         boost::shared_ptr<Region> sub = RegionFactory::create (region, plist);
-                        sub->set_pending_layer (DBL_MAX);
                         add_region_internal (sub, pos);
+                        set_layer (sub, DBL_MAX);
                 }
         }
  }
@@ -1377,9 +1381,6 @@ Playlist::flush_notifications (bool from_undo)
 
         add_region_internal (left, region->position());
         add_region_internal (right, region->position() + before);
-
-        finalize_split_region (region, left, right);
-
         remove_region_internal (region);
 
         _splicing = old_sp;
@@ -1504,7 +1505,10 @@ Playlist::flush_notifications (bool from_undo)
                 } else {
                         notify_contents_changed ();
                         relayer ();
-                        check_dependents (region, false);
+                        list<Evoral::Range<framepos_t> > xf;
+                        xf.push_back (Evoral::Range<framepos_t> (region->last_range()));
+                        xf.push_back (Evoral::Range<framepos_t> (region->range()));
+                        coalesce_and_check_crossfades (xf);
                 }
         }
  }
@@ -1552,7 +1556,7 @@ Playlist::flush_notifications (bool from_undo)
         }
 
         if (what_changed.contains (our_interests) && !what_changed.contains (pos_and_length)) {
-                check_dependents (region, false);
+                check_crossfades (region->range ());
         }
 
         if (what_changed.contains (Properties::position) && !what_changed.contains (Properties::length)) {
@@ -1630,13 +1634,12 @@ Playlist::flush_notifications (bool from_undo)
   FINDING THINGS
   **********************************************************************/
 
- Playlist::RegionList *
- Playlist::regions_at (framepos_t frame)
-
- {
-        RegionLock rlock (this);
-        return find_regions_at (frame);
- }
+boost::shared_ptr<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
@@ -1658,7 +1661,7 @@ Playlist::flush_notifications (bool from_undo)
 
  {
         RegionLock rlock (this);
-        RegionList *rlist = find_regions_at (frame);
+        boost::shared_ptr<RegionList> rlist = find_regions_at (frame);
         boost::shared_ptr<Region> region;
 
         if (rlist->size()) {
@@ -1667,7 +1670,6 @@ Playlist::flush_notifications (bool from_undo)
                 region = rlist->back();
         }
 
-        delete rlist;
         return region;
  }
 
@@ -1676,7 +1678,7 @@ Playlist::flush_notifications (bool from_undo)
 
  {
         RegionLock rlock (this);
-        RegionList *rlist = find_regions_at (frame);
+        boost::shared_ptr<RegionList> rlist = find_regions_at (frame);
 
         for (RegionList::iterator i = rlist->begin(); i != rlist->end(); ) {
 
@@ -1698,175 +1700,73 @@ Playlist::flush_notifications (bool from_undo)
                 region = rlist->back();
         }
 
-        delete rlist;
         return region;
  }
 
- Playlist::RegionList*
- Playlist::regions_to_read (framepos_t start, framepos_t end)
- {
-        /* Caller must hold lock */
-
-        RegionList covering;
-        set<framepos_t> to_check;
-        set<boost::shared_ptr<Region> > unique;
-
-        to_check.insert (start);
-        to_check.insert (end);
-
-        DEBUG_TRACE (DEBUG::AudioPlayback, ">>>>> REGIONS TO READ\n");
-
-        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-
-                /* find all/any regions that span start+end */
-
-                switch ((*i)->coverage (start, end)) {
-                case OverlapNone:
-                        break;
-
-                case OverlapInternal:
-                        covering.push_back (*i);
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("toread: will cover %1 (OInternal)\n", (*i)->name()));
-                        break;
-
-                case OverlapStart:
-                        to_check.insert ((*i)->position());
-                        if ((*i)->position() != 0) {
-                                to_check.insert ((*i)->position()-1);
-                        }
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("toread: will check %1 for %2\n", (*i)->position(), (*i)->name()));
-                        covering.push_back (*i);
-                        break;
-
-                case OverlapEnd:
-                        to_check.insert ((*i)->last_frame());
-                        to_check.insert ((*i)->last_frame()+1);
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("toread: will cover %1 (OEnd)\n", (*i)->name()));
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\ttoread: will check %1 for %2\n", (*i)->last_frame(), (*i)->name()));
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\ttoread: will check %1 for %2\n", (*i)->last_frame(), (*i)->name()));
-                        covering.push_back (*i);
-                        break;
-
-                case OverlapExternal:
-                        covering.push_back (*i);
-                        to_check.insert ((*i)->position());
-                        if ((*i)->position() != 0) {
-                                to_check.insert ((*i)->position()-1);
-                        }
-                        to_check.insert ((*i)->last_frame());
-                        to_check.insert ((*i)->last_frame()+1);
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("toread: will cover %1 (OExt)\n", (*i)->name()));
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\ttoread: will check %1 for %2\n", (*i)->position(), (*i)->name()));
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\ttoread: will check %1 for %2\n", (*i)->last_frame(), (*i)->name()));
-                        break;
-                }
-
-                /* don't go too far */
-
-                if ((*i)->position() > end) {
-                        break;
-                }
-        }
-
-        RegionList* rlist = new RegionList;
-
-        /* find all the regions that cover each position .... */
-
-        if (covering.size() == 1) {
-
-                rlist->push_back (covering.front());
-                DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Just one covering region (%1)\n", covering.front()->name()));
-
-        } else {
-
-                RegionList here;
-                for (set<framepos_t>::iterator t = to_check.begin(); t != to_check.end(); ++t) {
-
-                        here.clear ();
-
-                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("++++ Considering %1\n", *t));
-
-                        for (RegionList::iterator x = covering.begin(); x != covering.end(); ++x) {
-
-                                if ((*x)->covers (*t)) {
-                                        here.push_back (*x);
-                                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("region %1 covers %2\n",
-                                                                                           (*x)->name(),
-                                                                                           (*t)));
-                                } else {
-                                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("region %1 does NOT covers %2\n",
-                                                                                           (*x)->name(),
-                                                                                           (*t)));
-                                }
-
-                        }
-
-                        RegionSortByLayer cmp;
-                        here.sort (cmp);
-
-                        /* ... and get the top/transparent regions at "here" */
-
-                        for (RegionList::reverse_iterator c = here.rbegin(); c != here.rend(); ++c) {
-
-                                unique.insert (*c);
-
-                                if ((*c)->opaque()) {
-
-                                        /* the other regions at this position are hidden by this one */
-                                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("%1 is opaque, ignore all others\n",
-                                                                                           (*c)->name()));
-                                        break;
-                                }
-                        }
-                }
-
-                for (set<boost::shared_ptr<Region> >::iterator s = unique.begin(); s != unique.end(); ++s) {
-                        rlist->push_back (*s);
-                }
-
-                if (rlist->size() > 1) {
-                        /* now sort by time order */
-
-                        RegionSortByPosition cmp;
-                        rlist->sort (cmp);
-                }
-        }
-
-        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("<<<<< REGIONS TO READ returns %1\n", rlist->size()));
-
-        return rlist;
- }
+boost::shared_ptr<RegionList>
+Playlist::find_regions_at (framepos_t frame)
+{
+       /* Caller must hold lock */
+       
+       boost::shared_ptr<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::find_regions_at (framepos_t frame)
- {
-        /* Caller must hold lock */
+boost::shared_ptr<RegionList>
+Playlist::regions_with_start_within (Evoral::Range<framepos_t> range)
+{
+       RegionLock rlock (this);
+       boost::shared_ptr<RegionList> rlist (new RegionList);
 
-        RegionList *rlist = new RegionList;
+       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+               if ((*i)->first_frame() >= range.from && (*i)->first_frame() <= range.to) {
+                       rlist->push_back (*i);
+               }
+       }
 
-        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-                if ((*i)->covers (frame)) {
-                        rlist->push_back (*i);
-                }
-        }
+       return rlist;
+}
 
-        return rlist;
- }
+boost::shared_ptr<RegionList>
+Playlist::regions_with_end_within (Evoral::Range<framepos_t> range)
+{
+       RegionLock rlock (this);
+       boost::shared_ptr<RegionList> rlist (new RegionList);
 
- Playlist::RegionList *
- Playlist::regions_touched (framepos_t start, framepos_t end)
- {
-        RegionLock rlock (this);
-        RegionList *rlist = new RegionList;
+       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+               if ((*i)->last_frame() >= range.from && (*i)->last_frame() <= range.to) {
+                       rlist->push_back (*i);
+               }
+       }
 
-        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-                if ((*i)->coverage (start, end) != OverlapNone) {
-                        rlist->push_back (*i);
-                }
-        }
+       return rlist;
+}
 
-        return rlist;
- }
+/** @param start Range start.
+ *  @param end Range end.
+ *  @return regions which have some part within this range.
+ */
+boost::shared_ptr<RegionList>
+Playlist::regions_touched (framepos_t start, framepos_t end)
+{
+       RegionLock rlock (this);
+       boost::shared_ptr<RegionList> rlist (new RegionList);
+       
+       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+               if ((*i)->coverage (start, end) != Evoral::OverlapNone) {
+                       rlist->push_back (*i);
+               }
+       }
+       
+       return rlist;
+}
 
  framepos_t
  Playlist::find_next_transient (framepos_t from, int dir)
@@ -2094,7 +1994,7 @@ Playlist::flush_notifications (bool from_undo)
         freeze ();
         /* add the added regions */
         for (RegionListProperty::ChangeContainer::iterator i = change.added.begin(); i != change.added.end(); ++i) {
-                add_region ((*i), (*i)->position());
+                add_region_internal ((*i), (*i)->position());
         }
         /* remove the removed regions */
         for (RegionListProperty::ChangeContainer::iterator i = change.removed.begin(); i != change.removed.end(); ++i) {
@@ -2185,7 +2085,10 @@ Playlist::flush_notifications (bool from_undo)
                                return -1;
                        }
 
-                       add_region (region, region->position(), 1.0);
+                        {
+                                RegionLock rlock (this);
+                                add_region_internal (region, region->position());
+                        }
                        
                        region->resume_property_changes ();
 
@@ -2201,7 +2104,7 @@ Playlist::flush_notifications (bool from_undo)
                */
                
                for (RegionList::iterator r = regions.begin(); r != regions.end(); ++r) {
-                       check_dependents (*r, false);
+                       check_crossfades ((*r)->range ());
                }
        }
                
@@ -2210,6 +2113,7 @@ Playlist::flush_notifications (bool from_undo)
 
        in_set_state--;
        first_set_state = false;
+
        return ret;
 }
 
@@ -2340,17 +2244,63 @@ struct RelayerSort {
        }
 };
 
+/** Set a new layer for a region.  This adjusts the layering indices of all
+ *  regions in the playlist to put the specified region in the appropriate
+ *  place.  The actual layering will be fixed up when relayer() happens.
+ */
+
+void
+Playlist::set_layer (boost::shared_ptr<Region> region, double new_layer)
+{
+       /* Remove the layer we are setting from our region list, and sort it */
+       RegionList copy = regions.rlist();
+       copy.remove (region);
+       copy.sort (RelayerSort ());
+
+       /* Put region back in the right place */
+       RegionList::iterator i = copy.begin();
+       while (i != copy.end ()) {
+               if ((*i)->layer() > new_layer) {
+                       break;
+               }
+               ++i;
+       }
+       
+       copy.insert (i, region);
+
+       setup_layering_indices (copy);
+}
+
+void
+Playlist::setup_layering_indices (RegionList const & regions)
+{
+       uint64_t j = 0;
+       list<Evoral::Range<framepos_t> > xf;
+
+       for (RegionList::const_iterator k = regions.begin(); k != regions.end(); ++k) {
+               (*k)->set_layering_index (j++);
+
+               Evoral::Range<framepos_t> r ((*k)->first_frame(), (*k)->last_frame());
+               xf.push_back (r);
+       }
+
+       /* now recheck the entire playlist for crossfades */
+
+       coalesce_and_check_crossfades (xf);
+}
+
+/** Take the layering indices of each of our regions, compute the layers
+ *  that they should be on, and write the layers back to the regions.
+ */
 void
 Playlist::relayer ()
 {
-       /* never compute layers when changing state for undo/redo or setting from XML */
+       /* never compute layers when setting from XML */
 
-       if (in_update || in_set_state) {
+       if (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
           is to avoid having to search the entire region list to establish whether
@@ -2373,47 +2323,17 @@ Playlist::relayer ()
        vector<vector<RegionList> > layers;
        layers.push_back (vector<RegionList> (divisions));
 
+       /* Sort our regions into layering index order */
        RegionList copy = regions.rlist();
-       RegionList pending;
-
-       /* Remove regions with pending relayers */
-       for (RegionList::iterator i = copy.begin(); i != copy.end(); ) {
-
-               RegionList::iterator j = i;
-               ++j;
-               
-               if ((*i)->pending_layer()) {
-                       pending.push_back (*i);
-                       copy.erase (i);
-               }
-
-               i = j;
-       }
-
-       /* Sort the remainder */
        copy.sort (RelayerSort ());
 
-       /* Re-insert the pending layers in the right places */
-       for (RegionList::iterator i = pending.begin(); i != pending.end(); ++i) {
-               RegionList::iterator j = copy.begin();
-               while (j != copy.end ()) {
-                       if ((*j)->pending_layer().get_value_or ((*j)->layer ()) > (*i)->pending_layer().get ()) {
-                               break;
-                       }
-                       ++j;
-               }
-               copy.insert (j, *i);
+       DEBUG_TRACE (DEBUG::Layering, "relayer() using:\n");
+       for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) {
+               DEBUG_TRACE (DEBUG::Layering, string_compose ("\t%1 %2\n", (*i)->name(), (*i)->layering_index()));
        }
 
-       bool had_pending = false;
-
        for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) {
 
-               /* reset the pending layer for every region now that we're relayering */
-               if ((*i)->reset_pending_layer ()) {
-                       had_pending = true;
-               }
-
                /* 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.
@@ -2472,50 +2392,49 @@ Playlist::relayer ()
                        layers[j][k].push_back (*i);
                }
 
-               if ((*i)->layer() != j) {
-                       changed = true;
-               }
-
                (*i)->set_layer (j);
        }
 
-       if (changed) {
-               notify_layering_changed ();
-       }
+       /* It's a little tricky to know when we could avoid calling this; e.g. if we are
+          relayering because we just removed the only region on the top layer, nothing will
+          appear to have changed, but the StreamView must still sort itself out.  We could
+          probably keep a note of the top layer last time we relayered, and check that,
+          but premature optimisation &c...
+       */
+       notify_layering_changed ();
 
-       if (had_pending) {
-               uint64_t i = 0;
-               for (RegionList::iterator j = copy.begin(); j != copy.end(); ++j) {
-                       (*j)->set_layering_index (i++);
-               }
-       }
+       /* This relayer() may have been called as a result of a region removal, in which
+          case we need to setup layering indices so account for the one that has just
+          gone away.
+       */
+       setup_layering_indices (copy);
 }
 
 void
 Playlist::raise_region (boost::shared_ptr<Region> region)
 {
-       region->set_pending_layer (region->layer() + 1.5);
+       set_layer (region, region->layer() + 1.5);
        relayer ();
 }
 
 void
 Playlist::lower_region (boost::shared_ptr<Region> region)
 {
-       region->set_pending_layer (region->layer() - 1.5);
+       set_layer (region, region->layer() - 1.5);
        relayer ();
 }
 
 void
 Playlist::raise_region_to_top (boost::shared_ptr<Region> region)
 {
-       region->set_pending_layer (DBL_MAX);
+       set_layer (region, DBL_MAX);
        relayer ();
 }
 
 void
 Playlist::lower_region_to_bottom (boost::shared_ptr<Region> region)
 {
-       region->set_pending_layer (-0.5);
+       set_layer (region, -0.5);
        relayer ();
 }
 
@@ -2662,6 +2581,8 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
 
        _shuffling = true;
 
+       Evoral::Range<framepos_t> old_range = region->range ();
+
        {
                RegionLock rlock (const_cast<Playlist*> (this));
 
@@ -2760,8 +2681,6 @@ Playlist::shuffle (boost::shared_ptr<Region> region, int dir)
        if (moved) {
 
                relayer ();
-               check_dependents (region, false);
-
                notify_contents_changed();
        }
 
@@ -2924,10 +2843,6 @@ Playlist::combine (const RegionList& r)
 
        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;
@@ -3069,13 +2984,13 @@ Playlist::uncombine (boost::shared_ptr<Region> target)
                modified_region = false;
 
                switch (original->coverage (adjusted_start, adjusted_end)) {
-               case OverlapNone:
+               case Evoral::OverlapNone:
                        /* original region does not cover any part
                           of the current state of the compound region
                        */
                        continue;
 
-               case OverlapInternal:
+               case Evoral::OverlapInternal:
                        /* overlap is just a small piece inside the
                         * original so trim both ends
                         */
@@ -3083,13 +2998,13 @@ Playlist::uncombine (boost::shared_ptr<Region> target)
                        modified_region = true;
                        break;
 
-               case OverlapExternal:
+               case Evoral::OverlapExternal:
                        /* overlap fully covers original, so leave it
                           as is
                        */
                        break;
 
-               case OverlapEnd:
+               case Evoral::OverlapEnd:
                        /* overlap starts within but covers end,
                           so trim the front of the region
                        */
@@ -3097,7 +3012,7 @@ Playlist::uncombine (boost::shared_ptr<Region> target)
                        modified_region = true;
                        break;
 
-               case OverlapStart:
+               case Evoral::OverlapStart:
                        /* overlap covers start but ends within, so
                         * trim the end of the region.
                         */
@@ -3140,10 +3055,6 @@ Playlist::uncombine (boost::shared_ptr<Region> target)
                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 ();
 }
@@ -3161,38 +3072,39 @@ Playlist::max_source_level () const
        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;
-}
-
 void
 Playlist::set_orig_track_id (const PBD::ID& id)
 {
        _orig_track_id = id;
 }
 
-uint64_t
-Playlist::highest_layering_index () const
+void
+Playlist::coalesce_and_check_crossfades (list<Evoral::Range<framepos_t> > ranges)
 {
-       RegionLock rlock (const_cast<Playlist *> (this));
+       /* XXX: it's a shame that this coalesce algorithm also exists in
+          TimeSelection::consolidate().
+       */
 
-       uint64_t h = 0;
-       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
-               h = max (h, (*i)->layering_index ());
+       /* XXX: xfade: this is implemented in Evoral::RangeList */
+
+restart:
+       for (list<Evoral::Range<framepos_t> >::iterator i = ranges.begin(); i != ranges.end(); ++i) {
+               for (list<Evoral::Range<framepos_t> >::iterator j = ranges.begin(); j != ranges.end(); ++j) {
+
+                       if (i == j) {
+                               continue;
+                       }
+
+                       if (Evoral::coverage (i->from, i->to, j->from, j->to) != Evoral::OverlapNone) {
+                               i->from = min (i->from, j->from);
+                               i->to = max (i->to, j->to);
+                               ranges.erase (j);
+                               goto restart;
+                       }
+               }
        }
 
-       return h;
+       for (list<Evoral::Range<framepos_t> >::iterator i = ranges.begin(); i != ranges.end(); ++i) {
+               check_crossfades (*i);
+       }
 }
-