fix up escaping of strings in TOC files (suggested by andreas ruge)
[ardour.git] / libs / ardour / audio_playlist.cc
index 3eda5b57cfc4e6d8450e59683003afb6d29d2baf..56f2c4fe83bf3a0cd0c76eda6d1be3da3d427b30 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2003 Paul Davis 
+    Copyright (C) 2003 Paul Davis
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 
 #include <cstdlib>
 
-#include <sigc++/bind.h>
-
-#include <ardour/types.h>
-#include <ardour/configuration.h>
-#include <ardour/audioplaylist.h>
-#include <ardour/audioregion.h>
-#include <ardour/crossfade.h>
-#include <ardour/crossfade_compare.h>
-#include <ardour/session.h>
-#include <pbd/enumwriter.h>
+#include "ardour/types.h"
+#include "ardour/debug.h"
+#include "ardour/configuration.h"
+#include "ardour/audioplaylist.h"
+#include "ardour/audioregion.h"
+#include "ardour/crossfade.h"
+#include "ardour/region_sorters.h"
+#include "ardour/session.h"
+#include "pbd/enumwriter.h"
 
 #include "i18n.h"
 
 using namespace ARDOUR;
-using namespace sigc;
 using namespace std;
 using namespace PBD;
 
+namespace ARDOUR {
+       namespace Properties {
+               PBD::PropertyDescriptor<bool> crossfades;
+       }
+}
+
+void
+AudioPlaylist::make_property_quarks ()
+{
+        Properties::crossfades.property_id = g_quark_from_static_string (X_("crossfades"));
+        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for crossfades = %1\n", Properties::crossfades.property_id));
+}
+
+CrossfadeListProperty::CrossfadeListProperty (AudioPlaylist& pl)
+        : SequenceProperty<std::list<boost::shared_ptr<Crossfade> > > (Properties::crossfades.property_id, boost::bind (&AudioPlaylist::update, &pl, _1))
+        , _playlist (pl)
+{
+
+}
+
+CrossfadeListProperty::CrossfadeListProperty (CrossfadeListProperty const & p)
+       : PBD::SequenceProperty<std::list<boost::shared_ptr<Crossfade> > > (p)
+       , _playlist (p._playlist)
+{
+
+}
+
+
+CrossfadeListProperty *
+CrossfadeListProperty::create () const
+{
+       return new CrossfadeListProperty (_playlist);
+}
+
+CrossfadeListProperty *
+CrossfadeListProperty::clone () const
+{
+       return new CrossfadeListProperty (*this);
+}
+
+void
+CrossfadeListProperty::get_content_as_xml (boost::shared_ptr<Crossfade> xfade, XMLNode & node) const
+{
+       /* Crossfades are not written to any state when they are no
+          longer in use, so we must write their state here.
+       */
+
+       XMLNode& c = xfade->get_state ();
+       node.add_child_nocopy (c);
+}
+
+boost::shared_ptr<Crossfade>
+CrossfadeListProperty::get_content_from_xml (XMLNode const & node) const
+{
+       XMLNodeList const c = node.children ();
+       assert (c.size() == 1);
+       return boost::shared_ptr<Crossfade> (new Crossfade (_playlist, *c.front()));
+}
+
+
 AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden)
        : Playlist (session, node, DataType::AUDIO, hidden)
+       , _crossfades (*this)
 {
+#ifndef NDEBUG
        const XMLProperty* prop = node.property("type");
        assert(!prop || DataType(prop->value()) == DataType::AUDIO);
+#endif
+
+       add_property (_crossfades);
 
        in_set_state++;
-       set_state (node);
+       if (set_state (node, Stateful::loading_state_version)) {
+               throw failed_constructor();
+       }
        in_set_state--;
+
+       relayer ();
 }
 
 AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden)
        : Playlist (session, name, DataType::AUDIO, hidden)
+       , _crossfades (*this)
 {
+       add_property (_crossfades);
 }
 
 AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, string name, bool hidden)
        : Playlist (other, name, hidden)
+       , _crossfades (*this)
 {
+       add_property (_crossfades);
+
        RegionList::const_iterator in_o  = other->regions.begin();
        RegionList::iterator in_n = regions.begin();
 
@@ -65,7 +137,6 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, stri
                boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(*in_o);
 
                // We look only for crossfades which begin with the current region, so we don't get doubles
-
                for (Crossfades::const_iterator xfades = other->_crossfades.begin(); xfades != other->_crossfades.end(); ++xfades) {
                        if ((*xfades)->in() == ar) {
                                // We found one! Now copy it!
@@ -74,9 +145,9 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, stri
                                RegionList::const_iterator out_n = regions.begin();
 
                                while (out_o != other->regions.end()) {
-                                       
+
                                        boost::shared_ptr<AudioRegion>ar2 = boost::dynamic_pointer_cast<AudioRegion>(*out_o);
-                                       
+
                                        if ((*xfades)->out() == ar2) {
                                                boost::shared_ptr<AudioRegion>in  = boost::dynamic_pointer_cast<AudioRegion>(*in_n);
                                                boost::shared_ptr<AudioRegion>out = boost::dynamic_pointer_cast<AudioRegion>(*out_n);
@@ -84,7 +155,7 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, stri
                                                add_crossfade(new_fade);
                                                break;
                                        }
-                                       
+
                                        out_o++;
                                        out_n++;
                                }
@@ -97,37 +168,88 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, stri
        }
 }
 
-AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, nframes_t start, nframes_t cnt, string name, bool hidden)
+AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, framepos_t start, framecnt_t cnt, string name, bool hidden)
        : Playlist (other, start, cnt, name, hidden)
+       , _crossfades (*this)
 {
-       /* this constructor does NOT notify others (session) */
-}
+       RegionLock rlock2 (const_cast<AudioPlaylist*> (other.get()));
+       in_set_state++;
 
-AudioPlaylist::~AudioPlaylist ()
-{
-       GoingAway (); /* EMIT SIGNAL */
+       add_property (_crossfades);
 
-       /* drop connections to signals */
+       framepos_t const end = start + cnt - 1;
 
-       notify_callbacks ();
-       
-       _crossfades.clear ();
-}
+       /* Audio regions that have been created by the Playlist constructor
+          will currently have the same fade in/out as the regions that they
+          were created from.  This is wrong, so reset the fades here.
+       */
 
-void
-AudioPlaylist::copy_regions (RegionList& newlist) const
-{
-       RegionLock rlock (const_cast<Playlist *> (this));
+       RegionList::iterator ours = regions.begin ();
 
-       Playlist::copy_regions (newlist, false);
+       for (RegionList::const_iterator i = other->regions.begin(); i != other->regions.end(); ++i) {
+               boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (*i);
+               assert (region);
 
-       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
-               if (!(*i)->is_dependent()) {
-                       newlist.push_back (RegionFactory::RegionFactory::create (*i));
+               framecnt_t fade_in = 64;
+               framecnt_t fade_out = 64;
+
+               switch (region->coverage (start, end)) {
+               case OverlapNone:
+                       continue;
+
+               case OverlapInternal:
+               {
+                       framecnt_t const offset = start - region->position ();
+                       framecnt_t const trim = region->last_frame() - end;
+                       if (region->fade_in()->back()->when > offset) {
+                               fade_in = region->fade_in()->back()->when - offset;
+                       }
+                       if (region->fade_out()->back()->when > trim) {
+                               fade_out = region->fade_out()->back()->when - trim;
+                       }
+                       break;
+               }
+
+               case OverlapStart: {
+                       if (end > region->position() + region->fade_in()->back()->when)
+                               fade_in = region->fade_in()->back()->when;  //end is after fade-in, preserve the fade-in
+                       if (end > region->last_frame() - region->fade_out()->back()->when)
+                               fade_out = region->fade_out()->back()->when - ( region->last_frame() - end );  //end is inside the fadeout, preserve the fades endpoint
+                       break;
+               }
+
+               case OverlapEnd: {
+                       if (start < region->last_frame() - region->fade_out()->back()->when)  //start is before fade-out, preserve the fadeout
+                               fade_out = region->fade_out()->back()->when;
+
+                       if (start < region->position() + region->fade_in()->back()->when)
+                               fade_in = region->fade_in()->back()->when - (start - region->position());  //end is inside the fade-in, preserve the fade-in endpoint
+                       break;
                }
+
+               case OverlapExternal:
+                       fade_in = region->fade_in()->back()->when;
+                       fade_out = region->fade_out()->back()->when;
+                       break;
+               }
+
+               boost::shared_ptr<AudioRegion> our_region = boost::dynamic_pointer_cast<AudioRegion> (*ours);
+               assert (our_region);
+
+               our_region->set_fade_in_length (fade_in);
+               our_region->set_fade_out_length (fade_out);
+               ++ours;
        }
+
+       in_set_state--;
+
+       /* this constructor does NOT notify others (session) */
 }
 
+AudioPlaylist::~AudioPlaylist ()
+{
+       _crossfades.clear ();
+}
 
 struct RegionSortByLayer {
     bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
@@ -135,17 +257,19 @@ struct RegionSortByLayer {
     }
 };
 
-ARDOUR::nframes_t
-AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nframes_t start,
-                    nframes_t cnt, unsigned chan_n)
+ARDOUR::framecnt_t
+AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, framepos_t start,
+                    framecnt_t cnt, unsigned chan_n)
 {
-       nframes_t ret = cnt;
-       nframes_t end;
+       framecnt_t ret = cnt;
+
+       DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Playlist %1 read @ %2 for %3, channel %4, regions %5 xfades %6\n",
+                                                          name(), start, cnt, chan_n, regions.size(), _crossfades.size()));
 
        /* optimizing this memset() away involves a lot of conditionals
-          that may well cause more of a hit due to cache misses 
+          that may well cause more of a hit due to cache misses
           and related stuff than just doing this here.
-          
+
           it would be great if someone could measure this
           at some point.
 
@@ -157,30 +281,42 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nf
 
        memset (buf, 0, sizeof (Sample) * cnt);
 
-       /* this function is never called from a realtime thread, so 
+       /* this function is never called from a realtime thread, so
           its OK to block (for short intervals).
        */
 
-       Glib::Mutex::Lock rm (region_lock);
+       Glib::RecMutex::Lock rm (region_lock);
+
+       framepos_t const end = start + cnt - 1;
 
-       end =  start + cnt - 1;
+       boost::shared_ptr<RegionList> rlist = regions_to_read (start, start+cnt);
 
-       _read_data_count = 0;
+       if (rlist->empty()) {
+               return cnt;
+       }
 
        map<uint32_t,vector<boost::shared_ptr<Region> > > relevant_regions;
        map<uint32_t,vector<boost::shared_ptr<Crossfade> > > relevant_xfades;
        vector<uint32_t> relevant_layers;
 
-       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+       for (RegionList::iterator i = rlist->begin(); i != rlist->end(); ++i) {
                if ((*i)->coverage (start, end) != OverlapNone) {
                        relevant_regions[(*i)->layer()].push_back (*i);
                        relevant_layers.push_back ((*i)->layer());
-               }
+                }
        }
 
+       DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Checking %1 xfades\n", _crossfades.size()));
+
        for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+               DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("%1 check xfade between %2 and %3 ... [ %4 ... %5 | %6 ... %7]\n",
+                                                                  name(), (*i)->out()->name(), (*i)->in()->name(), 
+                                                                  (*i)->first_frame(), (*i)->last_frame(),
+                                                                  start, end));
                if ((*i)->coverage (start, end) != OverlapNone) {
                        relevant_xfades[(*i)->upper_layer()].push_back (*i);
+                       DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\t\txfade is relevant (coverage = %2), place on layer %1\n",
+                                                                          (*i)->upper_layer(), enum_2_string ((*i)->coverage (start, end))));
                }
        }
 
@@ -197,22 +333,22 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nf
 
        for (vector<uint32_t>::iterator l = relevant_layers.begin(); l != relevant_layers.end(); ++l) {
 
+               DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("read for layer %1\n", *l));
+
                vector<boost::shared_ptr<Region> > r (relevant_regions[*l]);
                vector<boost::shared_ptr<Crossfade> >& x (relevant_xfades[*l]);
 
+
                for (vector<boost::shared_ptr<Region> >::iterator i = r.begin(); i != r.end(); ++i) {
                        boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(*i);
+                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("read from region %1\n", ar->name()));
                        assert(ar);
                        ar->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n);
-                       _read_data_count += ar->read_data_count();
                }
-               
+
                for (vector<boost::shared_ptr<Crossfade> >::iterator i = x.begin(); i != x.end(); ++i) {
+                        DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("read from xfade between %1 & %2\n", (*i)->out()->name(), (*i)->in()->name()));
                        (*i)->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n);
-
-                       /* don't JACK up _read_data_count, since its the same data as we just
-                          read from the regions, and the OS should handle that for us.
-                       */
                }
        }
 
@@ -228,17 +364,17 @@ AudioPlaylist::remove_dependents (boost::shared_ptr<Region> region)
        if (in_set_state) {
                return;
        }
-       
+
        if (r == 0) {
                fatal << _("programming error: non-audio Region passed to remove_overlap in audio playlist")
                      << endmsg;
                return;
        }
-       
-       for (RegionList::iterator i = regions.begin(); i != regions.end(); ) {
-               
-               if ((*i)->depends_on (r)) {
-                       i = regions.erase (i);
+
+       for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ) {
+
+               if ((*i)->involves (r)) {
+                       i = _crossfades.erase (i);
                } else {
                        ++i;
                }
@@ -247,9 +383,9 @@ AudioPlaylist::remove_dependents (boost::shared_ptr<Region> region)
 
 
 void
-AudioPlaylist::flush_notifications ()
+AudioPlaylist::flush_notifications (bool from_undo)
 {
-       Playlist::flush_notifications();
+       Playlist::flush_notifications (from_undo);
 
        if (in_flush) {
                return;
@@ -263,7 +399,7 @@ AudioPlaylist::flush_notifications ()
        }
 
        _pending_xfade_adds.clear ();
-       
+
        in_flush = false;
 }
 
@@ -277,22 +413,23 @@ AudioPlaylist::refresh_dependents (boost::shared_ptr<Region> r)
                return;
        }
 
-       for (RegionList::iterator x = regions.begin(); x != regions.end();) {
-               
-               RegionList::iterator tmp;
-               
+       for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) {
+
+               Crossfades::iterator tmp;
+
                tmp = x;
                ++tmp;
 
                /* only update them once */
 
-               if ((*x)->depends_on (ar)) {
+               if ((*x)->involves (ar)) {
 
-                       if (find (updated.begin(), updated.end(), *x) == updated.end()) {
-                               try { 
-                                       if ((*x)->refresh ()) {
-                                               updated.insert (*x);
-                                       }
+                       pair<set<boost::shared_ptr<Crossfade> >::iterator, bool> const u = updated.insert (*x);
+
+                       if (u.second) {
+                               /* x was successfully inserted into the set, so it has not already been updated */
+                               try {
+                                       (*x)->refresh ();
                                }
 
                                catch (Crossfade::NoCrossfadeHere& err) {
@@ -312,43 +449,35 @@ AudioPlaylist::finalize_split_region (boost::shared_ptr<Region> o, boost::shared
        boost::shared_ptr<AudioRegion> left  = boost::dynamic_pointer_cast<AudioRegion>(l);
        boost::shared_ptr<AudioRegion> right = boost::dynamic_pointer_cast<AudioRegion>(r);
 
-       for (RegionList::iterator x = regions.begin(); x !=regions.end();) {
+       for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) {
                Crossfades::iterator tmp;
-               boost::shared_ptr<Crossfade> xfade = boost::dynamic_pointer_cast<Crossfade>(*x);
-
-               if (!xfade) {
-                       ++x;
-                       continue;
-               }
-
                tmp = x;
                ++tmp;
 
                boost::shared_ptr<Crossfade> fade;
-               
-               if (xfade->_in == orig) {
-                       if (! xfade->covers(right->position())) {
-                               fade = boost::shared_ptr<Crossfade> (new Crossfade (*xfade, left, xfade->_out));
+
+               if ((*x)->_in == orig) {
+                       if (! (*x)->covers(right->position())) {
+                               fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, left, (*x)->_out));
                        } else {
                                // Overlap, the crossfade is copied on the left side of the right region instead
-                               fade = boost::shared_ptr<Crossfade> (new Crossfade (*xfade, right, xfade->_out));
+                               fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, right, (*x)->_out));
                        }
                }
-               
-               if (xfade->_out == orig) {
-                       if (! xfade->covers(right->position())) {
-                               fade = boost::shared_ptr<Crossfade> (new Crossfade (*xfade, xfade->_in, right));
+
+               if ((*x)->_out == orig) {
+                       if (! (*x)->covers(right->position())) {
+                               fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, (*x)->_in, right));
                        } else {
                                // Overlap, the crossfade is copied on the right side of the left region instead
-                               fade = boost::shared_ptr<Crossfade> (new Crossfade (*xfade, xfade->_in, left));
+                               fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, (*x)->_in, left));
                        }
                }
-               
+
                if (fade) {
-                       regions.erase (*x);
+                       _crossfades.remove (*x);
                        add_crossfade (fade);
                }
-
                x = tmp;
        }
 }
@@ -361,6 +490,7 @@ AudioPlaylist::check_dependents (boost::shared_ptr<Region> r, bool norefresh)
        boost::shared_ptr<AudioRegion> top;
        boost::shared_ptr<AudioRegion> bottom;
        boost::shared_ptr<Crossfade>   xfade;
+       boost::shared_ptr<RegionList> touched_regions;
 
        if (in_set_state || in_partition) {
                return;
@@ -377,14 +507,11 @@ AudioPlaylist::check_dependents (boost::shared_ptr<Region> r, bool norefresh)
        }
 
 
-       if (!Config->get_auto_xfade()) {
+       if (!_session.config.get_auto_xfade()) {
                return;
        }
 
        for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
-
-               nframes_t xfade_length;
-
                other = boost::dynamic_pointer_cast<AudioRegion> (*i);
 
                if (other == region) {
@@ -394,7 +521,11 @@ AudioPlaylist::check_dependents (boost::shared_ptr<Region> r, bool norefresh)
                if (other->muted() || region->muted()) {
                        continue;
                }
-               
+
+                if (other->position() == r->position() && other->length() == r->length()) {
+                        /* precise overlay of two regions - no xfade */
+                        continue;
+                }
 
                if (other->layer() < region->layer()) {
                        top = region;
@@ -404,11 +535,16 @@ AudioPlaylist::check_dependents (boost::shared_ptr<Region> r, bool norefresh)
                        bottom = region;
                }
 
-
+               if (!top->opaque()) {
+                       continue;
+               }
 
                OverlapType c = top->coverage (bottom->position(), bottom->last_frame());
 
+               touched_regions.reset ();
+
                try {
+                       framecnt_t xfade_length;
                        switch (c) {
                        case OverlapNone:
                                break;
@@ -424,32 +560,87 @@ AudioPlaylist::check_dependents (boost::shared_ptr<Region> r, bool norefresh)
                                /*     [ -------- top ------- ]
                                 * {=========== bottom =============}
                                 */
-                               
+
                                /* to avoid discontinuities at the region boundaries of an internal
                                   overlap (this region is completely within another), we create
                                   two hidden crossfades at each boundary. this is not dependent
                                   on the auto-xfade option, because we require it as basic
                                   audio engineering.
                                */
-                               
-                               xfade_length = min ((nframes_t) 720, top->length());
-                               
-                               xfade = boost::shared_ptr<Crossfade> (new Crossfade (top, bottom, xfade_length, top->first_frame(), StartOfIn));
-                               add_crossfade (xfade);
+
+                               xfade_length = min ((framecnt_t) 720, top->length());
+
+                               if (top_region_at (top->first_frame()) == top) {
+
+                                       xfade = boost::shared_ptr<Crossfade> (new Crossfade (top, bottom, xfade_length, StartOfIn));
+                                       xfade->set_position (top->first_frame());
+                                       add_crossfade (xfade);
+                               }
 
                                if (top_region_at (top->last_frame() - 1) == top) {
-                                       /* 
-                                          only add a fade out if there is no region on top of the end of 'top' (which 
+
+                                       /*
+                                          only add a fade out if there is no region on top of the end of 'top' (which
                                           would cover it).
                                        */
-                                       
-                                       xfade = boost::shared_ptr<Crossfade> (new Crossfade (bottom, top, xfade_length, top->last_frame() - xfade_length, EndOfOut));
+
+                                       xfade = boost::shared_ptr<Crossfade> (new Crossfade (bottom, top, xfade_length, EndOfOut));
+                                       xfade->set_position (top->last_frame() - xfade_length);
                                        add_crossfade (xfade);
                                }
                                break;
-                               
+                       case OverlapStart:
+
+                               /*                   { ==== top ============ }
+                                *   [---- bottom -------------------]
+                                */
+
+                               if (_session.config.get_xfade_model() == FullCrossfade) {
+                                       touched_regions = regions_touched (top->first_frame(), bottom->last_frame());
+                                       if (touched_regions->size() <= 2) {
+                                               xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active()));
+                                               add_crossfade (xfade);
+                                       }
+                               } else {
+
+                                       touched_regions = regions_touched (top->first_frame(),
+                                                                          top->first_frame() + min ((framecnt_t) _session.config.get_short_xfade_seconds() * _session.frame_rate(),
+                                                                                                    top->length()));
+                                       if (touched_regions->size() <= 2) {
+                                               xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active()));
+                                               add_crossfade (xfade);
+                                       }
+                               }
+                               break;
+                       case OverlapEnd:
+
+
+                               /* [---- top ------------------------]
+                                *                { ==== bottom ============ }
+                                */
+
+                               if (_session.config.get_xfade_model() == FullCrossfade) {
+
+                                       touched_regions = regions_touched (bottom->first_frame(), top->last_frame());
+                                       if (touched_regions->size() <= 2) {
+                                               xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other,
+                                                                                                    _session.config.get_xfade_model(), _session.config.get_xfades_active()));
+                                               add_crossfade (xfade);
+                                       }
+
+                               } else {
+                                       touched_regions = regions_touched (bottom->first_frame(),
+                                                                          bottom->first_frame() + min ((framecnt_t)_session.config.get_short_xfade_seconds() * _session.frame_rate(),
+                                                                                                       bottom->length()));
+                                       if (touched_regions->size() <= 2) {
+                                               xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active()));
+                                               add_crossfade (xfade);
+                                       }
+                               }
+                               break;
                        default:
-                               xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, Config->get_xfade_model(), Config->get_xfades_active()));
+                               xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other,
+                                                                                    _session.config.get_xfade_model(), _session.config.get_xfades_active()));
                                add_crossfade (xfade);
                        }
                }
@@ -457,41 +648,37 @@ AudioPlaylist::check_dependents (boost::shared_ptr<Region> r, bool norefresh)
                catch (failed_constructor& err) {
                        continue;
                }
-               
+
                catch (Crossfade::NoCrossfadeHere& err) {
                        continue;
                }
-               
+
        }
 }
 
 void
-AudioPlaylist::add_crossfade (boost::shared_ptr<Crossfade> new_xfade)
+AudioPlaylist::add_crossfade (boost::shared_ptr<Crossfade> xfade)
 {
-       RegionList::iterator r;
-       boost::shared_ptr<Crossfade> xfade;
+       Crossfades::iterator ci;
 
-       for (r = regions.begin(); r != regions.end(); ++r) {
-               
-               if ((xfade = boost::dynamic_pointer_cast<Crossfade>(*r))) {
-                       if (*xfade == *new_xfade) { // Crossfade::operator==()
-                               break;
-                       }
+       for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) {
+               if (*(*ci) == *xfade) { // Crossfade::operator==()
+                       break;
                }
        }
-       
-       if (r != regions.end()) {
+
+       if (ci != _crossfades.end()) {
                // it will just go away
        } else {
-               add_region_internal (new_xfade);
+               _crossfades.push_back (xfade);
 
-               new_xfade->Invalidated.connect (mem_fun (*this, &AudioPlaylist::crossfade_invalidated));
-               new_xfade->StateChanged.connect (mem_fun (*this, &AudioPlaylist::crossfade_changed));
+               xfade->Invalidated.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_invalidated, this, _1));
+               xfade->PropertyChanged.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_changed, this, _1));
 
-               notify_crossfade_added (new_xfade);
+               notify_crossfade_added (xfade);
        }
 }
-       
+
 void AudioPlaylist::notify_crossfade_added (boost::shared_ptr<Crossfade> x)
 {
        if (g_atomic_int_get(&block_notifications)) {
@@ -504,25 +691,31 @@ void AudioPlaylist::notify_crossfade_added (boost::shared_ptr<Crossfade> x)
 void
 AudioPlaylist::crossfade_invalidated (boost::shared_ptr<Region> r)
 {
+       Crossfades::iterator i;
        boost::shared_ptr<Crossfade> xfade = boost::dynamic_pointer_cast<Crossfade> (r);
-       
+
        xfade->in()->resume_fade_in ();
        xfade->out()->resume_fade_out ();
 
-       remove_region_internal (r);
+       if ((i = find (_crossfades.begin(), _crossfades.end(), xfade)) != _crossfades.end()) {
+               _crossfades.erase (i);
+       }
 }
 
 int
-AudioPlaylist::set_state (const XMLNode& node)
+AudioPlaylist::set_state (const XMLNode& node, int version)
 {
        XMLNode *child;
        XMLNodeList nlist;
        XMLNodeConstIterator niter;
 
        in_set_state++;
-       freeze ();
 
-       Playlist::set_state (node);
+       if (Playlist::set_state (node, version)) {
+               return -1;
+       }
+
+       freeze ();
 
        nlist = node.children();
 
@@ -530,22 +723,21 @@ AudioPlaylist::set_state (const XMLNode& node)
 
                child = *niter;
 
-               if (child->name() != Crossfade::node_name()) {
+               if (child->name() != "Crossfade") {
                        continue;
                }
-               
+
                try {
-                       boost::shared_ptr<Crossfade> xfade = boost::dynamic_pointer_cast<Crossfade> (RegionFactory::create (*((const Playlist *) this), *child));
-                       assert (xfade);
-                       add_region_internal (xfade);
-                       xfade->Invalidated.connect (mem_fun (*this, &AudioPlaylist::crossfade_invalidated));
-                       xfade->StateChanged.connect (mem_fun (*this, &AudioPlaylist::crossfade_changed));
+                       boost::shared_ptr<Crossfade> xfade = boost::shared_ptr<Crossfade> (new Crossfade (*((const Playlist *)this), *child));
+                       _crossfades.push_back (xfade);
+                       xfade->Invalidated.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_invalidated, this, _1));
+                       xfade->PropertyChanged.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_changed, this, _1));
                        NewCrossfade(xfade);
                }
-               
+
                catch (failed_constructor& err) {
                        //      cout << string_compose (_("could not create crossfade object in playlist %1"),
-                       //        _name) 
+                       //        _name)
                        //    << endl;
                        continue;
                }
@@ -560,9 +752,24 @@ AudioPlaylist::set_state (const XMLNode& node)
 void
 AudioPlaylist::clear (bool with_signals)
 {
+       _crossfades.clear ();
        Playlist::clear (with_signals);
 }
 
+XMLNode&
+AudioPlaylist::state (bool full_state)
+{
+       XMLNode& node = Playlist::state (full_state);
+
+       if (full_state) {
+               for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+                       node.add_child_nocopy ((*i)->get_state());
+               }
+       }
+
+       return node;
+}
+
 void
 AudioPlaylist::dump () const
 {
@@ -571,32 +778,33 @@ AudioPlaylist::dump () const
 
        cerr << "Playlist \"" << _name << "\" " << endl
             << regions.size() << " regions "
+            << _crossfades.size() << " crossfades"
             << endl;
 
        for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
                r = *i;
+               cerr << "  " << r->name() << " @ " << r << " ["
+                    << r->start() << "+" << r->length()
+                    << "] at "
+                    << r->position()
+                    << " on layer "
+                    << r->layer ()
+                    << endl;
+       }
 
-               if ((x = boost::dynamic_pointer_cast<Crossfade> (r))) {
-                       cerr << "  xfade [" 
-                            << x->out()->name()
-                            << ','
-                            << x->in()->name()
-                            << " @ "
-                            << x->position()
-                            << " length = " 
-                            << x->length ()
-                            << " active ? "
-                            << (x->active() ? "yes" : "no")
-                            << endl;
-               } else {
-                       cerr << "  " << r->name() << " @ " << r << " [" 
-                            << r->start() << "+" << r->length() 
-                            << "] at " 
-                            << r->position()
-                            << " on layer "
-                            << r->layer ()
-                            << endl;
-               }
+       for (Crossfades::const_iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+               x = *i;
+               cerr << "  xfade ["
+                    << x->out()->name()
+                    << ','
+                    << x->in()->name()
+                    << " @ "
+                    << x->position()
+                    << " length = "
+                    << x->length ()
+                    << " active ? "
+                    << (x->active() ? "yes" : "no")
+                    << endl;
        }
 }
 
@@ -604,31 +812,28 @@ bool
 AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
 {
        boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
-       bool changed = false;
 
-       if (r == 0) {
-               fatal << _("programming error: non-audio Region passed to remove_overlap in audio playlist")
-                     << endmsg;
-               /*NOTREACHED*/
-               return false;
-       }
+        if (!r) {
+                return false;
+        }
 
-       { 
+       bool changed = false;
+       Crossfades::iterator c, ctmp;
+       set<boost::shared_ptr<Crossfade> > unique_xfades;
+
+       {
                RegionLock rlock (this);
 
                for (RegionList::iterator i = regions.begin(); i != regions.end(); ) {
-                       
+
                        RegionList::iterator tmp = i;
                        ++tmp;
-                       
+
                        if ((*i) == region) {
                                regions.erase (i);
                                changed = true;
-                       } else if ((*i)->depends_on (region)) {
-                               regions.erase (i);
-                               changed = true;
                        }
-                       
+
                        i = tmp;
                }
 
@@ -636,21 +841,30 @@ AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
 
                        set<boost::shared_ptr<Region> >::iterator xtmp = x;
                        ++xtmp;
-                       
+
                        if ((*x) == region) {
                                all_regions.erase (x);
                                changed = true;
-                       } else if ((*x)->depends_on (region)) {
-                               all_regions.erase (x);
-                               changed = true;
                        }
-                       
+
                        x = xtmp;
                }
 
                region->set_playlist (boost::shared_ptr<Playlist>());
        }
 
+       for (c = _crossfades.begin(); c != _crossfades.end(); ) {
+               ctmp = c;
+               ++ctmp;
+
+               if ((*c)->involves (r)) {
+                       unique_xfades.insert (*c);
+                       _crossfades.erase (c);
+               }
+
+               c = ctmp;
+       }
+
        if (changed) {
                /* overload this, it normally means "removed", not destroyed */
                notify_region_removed (region);
@@ -660,7 +874,7 @@ AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
 }
 
 void
-AudioPlaylist::crossfade_changed (Change ignored)
+AudioPlaylist::crossfade_changed (const PropertyChange&)
 {
        if (in_flush || in_set_state) {
                return;
@@ -672,31 +886,311 @@ AudioPlaylist::crossfade_changed (Change ignored)
           that occured.
        */
 
-       notify_modified ();
+       notify_contents_changed ();
 }
 
 bool
-AudioPlaylist::region_changed (Change what_changed, boost::shared_ptr<Region> region)
+AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
 {
        if (in_flush || in_set_state) {
                return false;
        }
 
-       Change our_interests = Change (AudioRegion::FadeInChanged|
-                                      AudioRegion::FadeOutChanged|
-                                      AudioRegion::FadeInActiveChanged|
-                                      AudioRegion::FadeOutActiveChanged|
-                                      AudioRegion::EnvelopeActiveChanged|
-                                      AudioRegion::ScaleAmplitudeChanged|
-                                      AudioRegion::EnvelopeChanged);
+       PropertyChange our_interests;
+
+       our_interests.add (Properties::fade_in_active);
+       our_interests.add (Properties::fade_out_active);
+       our_interests.add (Properties::scale_amplitude);
+       our_interests.add (Properties::envelope_active);
+       our_interests.add (Properties::envelope);
+       our_interests.add (Properties::fade_in);
+       our_interests.add (Properties::fade_out);
+
        bool parent_wants_notify;
 
        parent_wants_notify = Playlist::region_changed (what_changed, region);
 
-       if ((parent_wants_notify || (what_changed & our_interests))) {
-               notify_modified ();
+       if (parent_wants_notify || (what_changed.contains (our_interests))) {
+               notify_contents_changed ();
+       }
+
+       return true;
+}
+
+void
+AudioPlaylist::crossfades_at (framepos_t frame, Crossfades& clist)
+{
+       RegionLock rlock (this);
+
+       for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+               framepos_t const start = (*i)->position ();
+               framepos_t const end = start + (*i)->overlap_length(); // not length(), important difference
+
+               if (frame >= start && frame <= end) {
+                       clist.push_back (*i);
+               }
+       }
+}
+
+void
+AudioPlaylist::foreach_crossfade (boost::function<void (boost::shared_ptr<Crossfade>)> s)
+{
+       RegionLock rl (this, false);
+       for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+               s (*i);
+       }
+}
+
+void
+AudioPlaylist::update (const CrossfadeListProperty::ChangeRecord& change)
+{
+       for (CrossfadeListProperty::ChangeContainer::const_iterator i = change.added.begin(); i != change.added.end(); ++i) {
+               add_crossfade (*i);
+       }
+
+       /* don't remove crossfades here; they will be dealt with by the dependency code */
+}
+
+boost::shared_ptr<Crossfade>
+AudioPlaylist::find_crossfade (const PBD::ID& id) const
+{
+       Crossfades::const_iterator i = _crossfades.begin ();
+       while (i != _crossfades.end() && (*i)->id() != id) {
+               ++i;
        }
 
-       return true; 
+       if (i == _crossfades.end()) {
+               return boost::shared_ptr<Crossfade> ();
+       }
+
+       return *i;
 }
 
+struct crossfade_triple {
+    boost::shared_ptr<Region> old_in;
+    boost::shared_ptr<Region> new_in;
+    boost::shared_ptr<Region> new_out;
+};
+
+void
+AudioPlaylist::copy_dependents (const vector<TwoRegions>& old_and_new, Playlist* other) const
+{
+       AudioPlaylist* other_audio = dynamic_cast<AudioPlaylist*>(other);
+
+       if (!other_audio) {
+               return;
+       }
+
+       /* our argument is a vector of old and new regions. Each old region
+          might be participant in a crossfade that is already present. Each new
+          region is a copy of the old region, present in the other playlist.
+
+          our task is to find all the relevant xfades in our playlist (involving
+          the "old" regions) and place copies of them in the other playlist.
+       */
+
+       typedef map<boost::shared_ptr<Crossfade>,crossfade_triple> CrossfadeInfo;
+       CrossfadeInfo crossfade_info;
+
+       /* build up a record that links crossfades, old regions and new regions
+        */
+
+       for (vector<TwoRegions>::const_iterator on = old_and_new.begin(); on != old_and_new.end(); ++on) {
+
+               for (Crossfades::const_iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
+
+                       if ((*i)->in() == on->first) {
+
+                               CrossfadeInfo::iterator cf;
+
+                               if ((cf = crossfade_info.find (*i)) != crossfade_info.end()) {
+
+                                       /* already have a record for the old fade-in region,
+                                          so note the new fade-in region
+                                       */
+
+                                       cf->second.new_in = on->second;
+
+                               } else {
+
+                                       /* add a record of this crossfade, keeping an association
+                                          with the new fade-in region
+                                       */
+
+                                       crossfade_triple ct;
+
+                                       ct.old_in = on->first;
+                                       ct.new_in = on->second;
+
+                                       crossfade_info[*i] = ct;
+                               }
+
+                       } else if ((*i)->out() == on->first) {
+
+                               /* this old region is the fade-out region of this crossfade */
+
+                               CrossfadeInfo::iterator cf;
+
+                               if ((cf = crossfade_info.find (*i)) != crossfade_info.end()) {
+
+                                       /* already have a record for this crossfade, so just keep
+                                          an association for the new fade out region
+                                       */
+
+                                       cf->second.new_out = on->second;
+
+                               } else {
+
+                                       /* add a record of this crossfade, keeping an association
+                                          with the new fade-in region
+                                       */
+
+                                       crossfade_triple ct;
+
+                                       ct.old_in = on->first;
+                                       ct.new_out = on->second;
+
+                                       crossfade_info[*i] = ct;
+                               }
+                       }
+               }
+       }
+
+       for (CrossfadeInfo::iterator ci = crossfade_info.begin(); ci != crossfade_info.end(); ++ci) {
+
+               /* for each crossfade that involves at least two of the old regions,
+                  create a new identical crossfade with the new regions
+               */
+
+               if (!ci->second.new_in || !ci->second.new_out) {
+                       continue;
+               }
+
+               boost::shared_ptr<Crossfade> new_xfade (new Crossfade (ci->first,
+                                                                      boost::dynamic_pointer_cast<AudioRegion>(ci->second.new_in),
+                                                                      boost::dynamic_pointer_cast<AudioRegion>(ci->second.new_out)));
+
+               /* add it at the right position - which must be at the start
+                * of the fade-in region
+                */
+
+               new_xfade->set_position (ci->second.new_in->position());
+               other_audio->add_crossfade (new_xfade);
+       }
+}
+
+void
+AudioPlaylist::pre_combine (vector<boost::shared_ptr<Region> >& copies)
+{
+       RegionSortByPosition cmp;
+       boost::shared_ptr<AudioRegion> ar;
+
+       sort (copies.begin(), copies.end(), cmp);
+
+       ar = boost::dynamic_pointer_cast<AudioRegion> (copies.front());
+
+       /* disable fade in of the first region */
+
+       if (ar) {
+               ar->set_fade_in_active (false);
+       }
+
+       ar = boost::dynamic_pointer_cast<AudioRegion> (copies.back());
+
+       /* disable fade out of the last region */
+
+       if (ar) {
+               ar->set_fade_out_active (false);
+       }
+}
+
+void
+AudioPlaylist::post_combine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
+{
+       RegionSortByPosition cmp;
+       boost::shared_ptr<AudioRegion> ar;
+       boost::shared_ptr<AudioRegion> cr;
+
+       if ((cr = boost::dynamic_pointer_cast<AudioRegion> (compound_region)) == 0) {
+               return;
+       }
+
+       sort (originals.begin(), originals.end(), cmp);
+
+       ar = boost::dynamic_pointer_cast<AudioRegion> (originals.front());
+
+       /* copy the fade in of the first into the compound region */
+
+       if (ar) {
+               cr->set_fade_in (ar->fade_in());
+       }
+
+       ar = boost::dynamic_pointer_cast<AudioRegion> (originals.back());
+
+       if (ar) {
+               /* copy the fade out of the last into the compound region */
+               cr->set_fade_out (ar->fade_out());
+       }
+}
+
+void
+AudioPlaylist::pre_uncombine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
+{
+       RegionSortByPosition cmp;
+       boost::shared_ptr<AudioRegion> ar;
+       boost::shared_ptr<AudioRegion> cr = boost::dynamic_pointer_cast<AudioRegion>(compound_region);
+
+       if (!cr) {
+               return;
+       }
+
+       sort (originals.begin(), originals.end(), cmp);
+
+       /* no need to call clear_changes() on the originals because that is
+        * done within Playlist::uncombine ()
+        */
+
+       for (vector<boost::shared_ptr<Region> >::iterator i = originals.begin(); i != originals.end(); ++i) {
+
+               if ((ar = boost::dynamic_pointer_cast<AudioRegion> (*i)) == 0) {
+                       continue;
+               }
+
+               /* scale the uncombined regions by any gain setting for the
+                * compound one.
+                */
+
+               ar->set_scale_amplitude (ar->scale_amplitude() * cr->scale_amplitude());
+
+               if (i == originals.begin()) {
+
+                       /* copy the compound region's fade in back into the first
+                          original region.
+                       */
+
+                       if (cr->fade_in()->back()->when <= ar->length()) {
+                               /* don't do this if the fade is longer than the
+                                * region
+                                */
+                               ar->set_fade_in (cr->fade_in());
+                       }
+
+
+               } else if (*i == originals.back()) {
+
+                       /* copy the compound region's fade out back into the last
+                          original region.
+                       */
+
+                       if (cr->fade_out()->back()->when <= ar->length()) {
+                               /* don't do this if the fade is longer than the
+                                * region
+                                */
+                               ar->set_fade_out (cr->fade_out());
+                       }
+
+               }
+
+               _session.add_command (new StatefulDiffCommand (*i));
+       }
+}