fix crash when copy'ing latent plugins
[ardour.git] / libs / ardour / audio_playlist.cc
index bcb0219e2a2f9286853e3637c9e87cafd531aabb..b00252df7412de461b073557d4bc974462ebde2f 100644 (file)
 
 #include "ardour/types.h"
 #include "ardour/debug.h"
-#include "ardour/configuration.h"
 #include "ardour/audioplaylist.h"
 #include "ardour/audioregion.h"
 #include "ardour/region_sorters.h"
 #include "ardour/session.h"
-#include "pbd/enumwriter.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace ARDOUR;
 using namespace std;
@@ -40,7 +38,7 @@ AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden
        : Playlist (session, node, DataType::AUDIO, hidden)
 {
 #ifndef NDEBUG
-       const XMLProperty* prop = node.property("type");
+       XMLProperty const * prop = node.property("type");
        assert(!prop || DataType(prop->value()) == DataType::AUDIO);
 #endif
 
@@ -51,6 +49,8 @@ AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden
        in_set_state--;
 
        relayer ();
+
+       load_legacy_crossfades (node, Stateful::loading_state_version);
 }
 
 AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden)
@@ -66,7 +66,7 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, stri
 AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, framepos_t start, framecnt_t cnt, string name, bool hidden)
        : Playlist (other, start, cnt, name, hidden)
 {
-       RegionLock rlock2 (const_cast<AudioPlaylist*> (other.get()));
+       RegionReadLock rlock2 (const_cast<AudioPlaylist*> (other.get()));
        in_set_state++;
 
        framepos_t const end = start + cnt - 1;
@@ -149,12 +149,23 @@ struct ReadSorter {
     }
 };
 
+/** A segment of region that needs to be read */
+struct Segment {
+       Segment (boost::shared_ptr<AudioRegion> r, Evoral::Range<framepos_t> a) : region (r), range (a) {}
+
+       boost::shared_ptr<AudioRegion> region; ///< the region
+       Evoral::Range<framepos_t> range;       ///< range of the region to read, in session frames
+};
+
+/** @param start Start position in session frames.
+ *  @param cnt Number of frames to read.
+ */
 ARDOUR::framecnt_t
 AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, framepos_t start,
                     framecnt_t cnt, unsigned chan_n)
 {
-       DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Playlist %1 read @ %2 for %3, channel %4, regions %5\n",
-                                                          name(), start, cnt, chan_n, regions.size()));
+       DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Playlist %1 read @ %2 for %3, channel %4, regions %5 mixdown @ %6 gain @ %7\n",
+                                                          name(), start, cnt, chan_n, regions.size(), mixdown_buffer, gain_buffer));
 
        /* optimizing this memset() away involves a lot of conditionals
           that may well cause more of a hit due to cache misses
@@ -175,24 +186,30 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, fr
           its OK to block (for short intervals).
        */
 
-       Glib::RecMutex::Lock rm (region_lock);
+       Playlist::RegionReadLock rl (this);
 
        /* Find all the regions that are involved in the bit we are reading,
           and sort them by descending layer and ascending position.
        */
-       boost::shared_ptr<RegionList> all = regions_touched (start, start + cnt - 1);
+       boost::shared_ptr<RegionList> all = regions_touched_locked (start, start + cnt - 1);
        all->sort (ReadSorter ());
 
        /* This will be a list of the bits of our read range that we have
-          read completely (ie for which no more regions need to be read).
+          handled completely (ie for which no more regions need to be read).
           It is a list of ranges in session frames.
        */
        Evoral::RangeList<framepos_t> done;
-       
+
+       /* This will be a list of the bits of regions that we need to read */
+       list<Segment> to_do;
+
+       /* Now go through the `all' list filling in `to_do' and `done' */
        for (RegionList::iterator i = all->begin(); i != all->end(); ++i) {
                boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (*i);
 
-               /* Trim region range to the bit we are reading */
+               /* muted regions don't figure into it at all */
+               if ( ar->muted() )
+                       continue;
 
                /* Work out which bits of this region need to be read;
                   first, trim to the range we are reading...
@@ -202,19 +219,18 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, fr
                region_range.to = min (region_range.to, start + cnt - 1);
 
                /* ... and then remove the bits that are already done */
-               Evoral::RangeList<framepos_t> to_do = Evoral::subtract (region_range, done);
 
-               /* Read those bits, adding their bodies (the parts between end-of-fade-in
+               Evoral::RangeList<framepos_t> region_to_do = Evoral::subtract (region_range, done);
+
+               /* Make a note to read those bits, adding their bodies (the parts between end-of-fade-in
                   and start-of-fade-out) to the `done' list.
                */
 
-               Evoral::RangeList<framepos_t>::List t = to_do.get ();
-
-               for (Evoral::RangeList<framepos_t>::List::iterator i = t.begin(); i != t.end(); ++i) {
-                       Evoral::Range<framepos_t> d = *i;
+               Evoral::RangeList<framepos_t>::List t = region_to_do.get ();
 
-                       /* Read the whole range, possibly including fades */
-                       ar->read_at (buf + d.from - start, mixdown_buffer, gain_buffer, d.from, d.to - d.from + 1, chan_n);
+               for (Evoral::RangeList<framepos_t>::List::iterator j = t.begin(); j != t.end(); ++j) {
+                       Evoral::Range<framepos_t> d = *j;
+                       to_do.push_back (Segment (ar, d));
 
                        if (ar->opaque ()) {
                                /* Cut this range down to just the body and mark it done */
@@ -228,118 +244,16 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, fr
                }
        }
 
-       return cnt;
-}
-
-void
-AudioPlaylist::check_crossfades (Evoral::Range<framepos_t> range)
-{
-       if (in_set_state || in_partition || !_session.config.get_auto_xfade ()) {
-               return;
-       }
-       
-       boost::shared_ptr<RegionList> starts = regions_with_start_within (range);
-       boost::shared_ptr<RegionList> ends = regions_with_end_within (range);
-
-       RegionList all = *starts;
-       std::copy (ends->begin(), ends->end(), back_inserter (all));
-
-       all.sort (RegionSortByLayer ());
-
-       set<boost::shared_ptr<Region> > done_start;
-       set<boost::shared_ptr<Region> > done_end;
-
-       for (RegionList::reverse_iterator i = all.rbegin(); i != all.rend(); ++i) {
-               for (RegionList::reverse_iterator j = all.rbegin(); j != all.rend(); ++j) {
-
-                       if (i == j) {
-                               continue;
-                       }
-
-                       if ((*i)->muted() || (*j)->muted()) {
-                               continue;
-                       }
-
-                       if ((*i)->position() == (*j)->position() && ((*i)->length() == (*j)->length())) {
-                               /* precise overlay: no xfade */
-                               continue;
-                       }
-
-                       if ((*i)->position() == (*j)->position() || ((*i)->last_frame() == (*j)->last_frame())) {
-                               /* starts or ends match: no xfade */
-                               continue;
-                       }
-                       
-
-                       boost::shared_ptr<AudioRegion> top;
-                       boost::shared_ptr<AudioRegion> bottom;
-               
-                       if ((*i)->layer() < (*j)->layer()) {
-                               top = boost::dynamic_pointer_cast<AudioRegion> (*j);
-                               bottom = boost::dynamic_pointer_cast<AudioRegion> (*i);
-                       } else {
-                               top = boost::dynamic_pointer_cast<AudioRegion> (*i);
-                               bottom = boost::dynamic_pointer_cast<AudioRegion> (*j);
-                       }
-                       
-                       if (!top->opaque ()) {
-                               continue;
-                       }
-
-                       Evoral::OverlapType const c = top->coverage (bottom->position(), bottom->last_frame());
-                       
-                       if (c == Evoral::OverlapStart) {
-                               
-                               /* top starts within bottom but covers bottom's end */
-                               
-                               /*                   { ==== top ============ } 
-                                *   [---- bottom -------------------] 
-                                */
-
-                               if (done_start.find (top) == done_start.end() && done_end.find (bottom) == done_end.end ()) {
-                                       framecnt_t const len = bottom->last_frame () - top->first_frame ();
-                                       top->set_fade_in_length (len);
-                                       top->set_fade_in_active (true);
-                                       done_start.insert (top);
-                                       bottom->set_fade_out_length (len);
-                                       bottom->set_fade_out_active (true);
-                                       done_end.insert (bottom);
-                               }
-
-                       } else if (c == Evoral::OverlapEnd) {
-                               
-                               /* top covers start of bottom but ends within it */
-                               
-                               /* [---- top ------------------------] 
-                                *                { ==== bottom ============ } 
-                                */
-
-                               if (done_end.find (top) == done_end.end() && done_start.find (bottom) == done_start.end ()) {
-                                       framecnt_t const len = top->last_frame () - bottom->first_frame ();
-                                       top->set_fade_out_length (len);
-                                       top->set_fade_out_active (true);
-                                       done_end.insert (top);
-                                       bottom->set_fade_in_length (len);
-                                       bottom->set_fade_in_active (true);
-                                       done_start.insert (bottom);
-                               }
-                       }
-               }
-       }
-
-       for (RegionList::iterator i = starts->begin(); i != starts->end(); ++i) {
-               if (done_start.find (*i) == done_start.end()) {
-                       boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (*i);
-                       r->set_default_fade_in ();
-               }
+       /* Now go backwards through the to_do list doing the actual reads */
+       for (list<Segment>::reverse_iterator i = to_do.rbegin(); i != to_do.rend(); ++i) {
+               DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\tPlaylist %1 read %2 @ %3 for %4, channel %5, buf @ %6 offset %7\n",
+                                                                  name(), i->region->name(), i->range.from,
+                                                                  i->range.to - i->range.from + 1, (int) chan_n,
+                                                                  buf, i->range.from - start));
+               i->region->read_at (buf + i->range.from - start, mixdown_buffer, gain_buffer, i->range.from, i->range.to - i->range.from + 1, chan_n);
        }
 
-       for (RegionList::iterator i = ends->begin(); i != ends->end(); ++i) {
-               if (done_end.find (*i) == done_end.end()) {
-                       boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (*i);
-                       r->set_default_fade_out ();
-               }
-       }
+       return cnt;
 }
 
 void
@@ -375,7 +289,7 @@ AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
        bool changed = false;
 
        {
-               RegionLock rlock (this);
+               RegionWriteLock rlock (this);
 
                for (RegionList::iterator i = regions.begin(); i != regions.end(); ) {
 
@@ -421,6 +335,11 @@ AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared
                return false;
        }
 
+       PropertyChange bounds;
+       bounds.add (Properties::start);
+       bounds.add (Properties::position);
+       bounds.add (Properties::length);
+
        PropertyChange our_interests;
 
        our_interests.add (Properties::fade_in_active);
@@ -434,8 +353,8 @@ AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared
        bool parent_wants_notify;
 
        parent_wants_notify = Playlist::region_changed (what_changed, region);
-
-       if (parent_wants_notify || (what_changed.contains (our_interests))) {
+       /* if bounds changed, we have already done notify_contents_changed ()*/
+       if ((parent_wants_notify || what_changed.contains (our_interests)) && !what_changed.contains (bounds)) {
                notify_contents_changed ();
        }
 
@@ -561,49 +480,97 @@ AudioPlaylist::pre_uncombine (vector<boost::shared_ptr<Region> >& originals, boo
 int
 AudioPlaylist::set_state (const XMLNode& node, int version)
 {
-       int const r = Playlist::set_state (node, version);
-       if (r) {
-               return r;
-       }
+       return Playlist::set_state (node, version);
+}
 
+void
+AudioPlaylist::load_legacy_crossfades (const XMLNode& node, int version)
+{
        /* Read legacy Crossfade nodes and set up region fades accordingly */
 
        XMLNodeList children = node.children ();
        for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
                if ((*i)->name() == X_("Crossfade")) {
 
-                       XMLProperty* p = (*i)->property (X_("active"));
+                       XMLProperty const * p = (*i)->property (X_("active"));
                        assert (p);
+
                        if (!string_is_affirmative (p->value())) {
                                continue;
                        }
 
-                       p = (*i)->property (X_("in"));
-                       assert (p);
+                       if ((p = (*i)->property (X_("in"))) == 0) {
+                               continue;
+                       }
+
                        boost::shared_ptr<Region> in = region_by_id (PBD::ID (p->value ()));
-                       assert (in);
+
+                       if (!in) {
+                               warning << string_compose (_("Legacy crossfade involved an incoming region not present in playlist \"%1\" - crossfade discarded"),
+                                                          name())
+                                       << endmsg;
+                               continue;
+                       }
+
                        boost::shared_ptr<AudioRegion> in_a = boost::dynamic_pointer_cast<AudioRegion> (in);
                        assert (in_a);
 
-                       p = (*i)->property (X_("out"));
-                       assert (p);
+                       if ((p = (*i)->property (X_("out"))) == 0) {
+                               continue;
+                       }
+
                        boost::shared_ptr<Region> out = region_by_id (PBD::ID (p->value ()));
-                       assert (out);
+
+                       if (!out) {
+                               warning << string_compose (_("Legacy crossfade involved an outgoing region not present in playlist \"%1\" - crossfade discarded"),
+                                                          name())
+                                       << endmsg;
+                               continue;
+                       }
+
                        boost::shared_ptr<AudioRegion> out_a = boost::dynamic_pointer_cast<AudioRegion> (out);
                        assert (out_a);
 
-                       XMLNodeList c = (*i)->children ();
-                       for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
-                               if ((*j)->name() == X_("FadeIn")) {
-                                       in_a->fade_in()->set_state (**j, version);
-                                       in_a->set_fade_in_active (true);
-                               } else if ((*j)->name() == X_("FadeOut")) {
-                                       out_a->fade_out()->set_state (**j, version);
-                                       out_a->set_fade_out_active (true);
+                       /* now decide whether to add a fade in or fade out
+                        * xfade and to which region
+                        */
+
+                       if (in->layer() <= out->layer()) {
+
+                               /* incoming region is below the outgoing one,
+                                * so apply a fade out to the outgoing one
+                                */
+
+                               const XMLNodeList c = (*i)->children ();
+
+                               for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
+                                       if ((*j)->name() == X_("FadeOut")) {
+                                               out_a->fade_out()->set_state (**j, version);
+                                       } else if ((*j)->name() == X_("FadeIn")) {
+                                               out_a->inverse_fade_out()->set_state (**j, version);
+                                       }
                                }
+
+                               out_a->set_fade_out_active (true);
+
+                       } else {
+
+                               /* apply a fade in to the incoming region,
+                                * since its above the outgoing one
+                                */
+
+                               const XMLNodeList c = (*i)->children ();
+
+                               for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
+                                       if ((*j)->name() == X_("FadeIn")) {
+                                               in_a->fade_in()->set_state (**j, version);
+                                       } else if ((*j)->name() == X_("FadeOut")) {
+                                               in_a->inverse_fade_in()->set_state (**j, version);
+                                       }
+                               }
+
+                               in_a->set_fade_in_active (true);
                        }
                }
        }
-
-       return 0;
 }