X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Faudio_playlist.cc;h=b00252df7412de461b073557d4bc974462ebde2f;hb=cf52d6e4b40111eb04b244ec054055a4ec15dbe0;hp=b62aeb3ee7ddadb9bf6a5d0fe70a70303565a45d;hpb=75e6adcf03078c24d13ddc7c9c714582a6cf7891;p=ardour.git diff --git a/libs/ardour/audio_playlist.cc b/libs/ardour/audio_playlist.cc index b62aeb3ee7..b00252df74 100644 --- a/libs/ardour/audio_playlist.cc +++ b/libs/ardour/audio_playlist.cc @@ -23,158 +23,52 @@ #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" +#include "pbd/i18n.h" using namespace ARDOUR; using namespace std; using namespace PBD; -namespace ARDOUR { - namespace Properties { - PBD::PropertyDescriptor 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 > > (Properties::crossfades.property_id, boost::bind (&AudioPlaylist::update, &pl, _1)) - , _playlist (pl) -{ - -} - -CrossfadeListProperty::CrossfadeListProperty (CrossfadeListProperty const & p) - : PBD::SequenceProperty > > (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 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 -CrossfadeListProperty::get_content_from_xml (XMLNode const & node) const -{ - XMLNodeList const c = node.children (); - assert (c.size() == 1); - return boost::shared_ptr (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"); + XMLProperty const * prop = node.property("type"); assert(!prop || DataType(prop->value()) == DataType::AUDIO); #endif - add_property (_crossfades); - in_set_state++; if (set_state (node, Stateful::loading_state_version)) { throw failed_constructor(); } in_set_state--; + + relayer (); + + load_legacy_crossfades (node, Stateful::loading_state_version); } AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden) : Playlist (session, name, DataType::AUDIO, hidden) - , _crossfades (*this) { - add_property (_crossfades); } AudioPlaylist::AudioPlaylist (boost::shared_ptr 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(); - - while (in_o != other->regions.end()) { - boost::shared_ptr ar = boost::dynamic_pointer_cast(*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! - - RegionList::const_iterator out_o = other->regions.begin(); - RegionList::const_iterator out_n = regions.begin(); - - while (out_o != other->regions.end()) { - - boost::shared_ptrar2 = boost::dynamic_pointer_cast(*out_o); - - if ((*xfades)->out() == ar2) { - boost::shared_ptrin = boost::dynamic_pointer_cast(*in_n); - boost::shared_ptrout = boost::dynamic_pointer_cast(*out_n); - boost::shared_ptr new_fade = boost::shared_ptr (new Crossfade (*xfades, in, out)); - add_crossfade(new_fade); - break; - } - - out_o++; - out_n++; - } -// cerr << "HUH!? second region in the crossfade not found!" << endl; - } - } - - in_o++; - in_n++; - } } AudioPlaylist::AudioPlaylist (boost::shared_ptr other, framepos_t start, framecnt_t cnt, string name, bool hidden) : Playlist (other, start, cnt, name, hidden) - , _crossfades (*this) { - RegionLock rlock2 (const_cast (other.get())); + RegionReadLock rlock2 (const_cast (other.get())); in_set_state++; - add_property (_crossfades); - framepos_t const end = start + cnt - 1; /* Audio regions that have been created by the Playlist constructor @@ -192,10 +86,10 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr other, fram framecnt_t fade_out = 64; switch (region->coverage (start, end)) { - case OverlapNone: + case Evoral::OverlapNone: continue; - case OverlapInternal: + case Evoral::OverlapInternal: { framecnt_t const offset = start - region->position (); framecnt_t const trim = region->last_frame() - end; @@ -208,7 +102,7 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr other, fram break; } - case OverlapStart: { + case Evoral::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) @@ -216,7 +110,7 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr other, fram break; } - case OverlapEnd: { + case Evoral::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; @@ -225,7 +119,7 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr other, fram break; } - case OverlapExternal: + case Evoral::OverlapExternal: fade_in = region->fade_in()->back()->when; fade_out = region->fade_out()->back()->when; break; @@ -244,25 +138,34 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr other, fram /* this constructor does NOT notify others (session) */ } -AudioPlaylist::~AudioPlaylist () -{ - _crossfades.clear (); -} - -struct RegionSortByLayer { +/** Sort by descending layer and then by ascending position */ +struct ReadSorter { bool operator() (boost::shared_ptr a, boost::shared_ptr b) { - return a->layer() < b->layer(); + if (a->layer() != b->layer()) { + return a->layer() > b->layer(); + } + + return a->position() < b->position(); } }; +/** A segment of region that needs to be read */ +struct Segment { + Segment (boost::shared_ptr r, Evoral::Range a) : region (r), range (a) {} + + boost::shared_ptr region; ///< the region + Evoral::Range 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) { - 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())); + 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 @@ -283,505 +186,83 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, fr its OK to block (for short intervals). */ - Glib::RecMutex::Lock rm (region_lock); - - framepos_t const end = start + cnt - 1; - - RegionList* rlist = regions_to_read (start, start+cnt); - - if (rlist->empty()) { - delete rlist; - return cnt; - } - - map > > relevant_regions; - map > > relevant_xfades; - vector relevant_layers; + Playlist::RegionReadLock rl (this); - 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)))); - } - } - -// RegionSortByLayer layer_cmp; -// relevant_regions.sort (layer_cmp); - - /* XXX this whole per-layer approach is a hack that - should be removed once Crossfades become - CrossfadeRegions and we just grab a list of relevant - regions and call read_at() on all of them. + /* 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 all = regions_touched_locked (start, start + cnt - 1); + all->sort (ReadSorter ()); - sort (relevant_layers.begin(), relevant_layers.end()); - - for (vector::iterator l = relevant_layers.begin(); l != relevant_layers.end(); ++l) { - - DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("read for layer %1\n", *l)); - - vector > r (relevant_regions[*l]); - vector >& x (relevant_xfades[*l]); - - - for (vector >::iterator i = r.begin(); i != r.end(); ++i) { - boost::shared_ptr ar = boost::dynamic_pointer_cast(*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); - } - - for (vector >::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); - } - } - - delete rlist; - return ret; -} - - -void -AudioPlaylist::remove_dependents (boost::shared_ptr region) -{ - boost::shared_ptr r = boost::dynamic_pointer_cast (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 (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ) { - - if ((*i)->involves (r)) { - i = _crossfades.erase (i); - } else { - ++i; - } - } -} - - -void -AudioPlaylist::flush_notifications (bool from_undo) -{ - Playlist::flush_notifications (from_undo); - - if (in_flush) { - return; - } - - in_flush = true; - - Crossfades::iterator a; - for (a = _pending_xfade_adds.begin(); a != _pending_xfade_adds.end(); ++a) { - NewCrossfade (*a); /* EMIT SIGNAL */ - } - - _pending_xfade_adds.clear (); - - in_flush = false; -} - -void -AudioPlaylist::refresh_dependents (boost::shared_ptr r) -{ - boost::shared_ptr ar = boost::dynamic_pointer_cast(r); - set > updated; - - if (ar == 0) { - return; - } - - for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) { - - Crossfades::iterator tmp; - - tmp = x; - ++tmp; - - /* only update them once */ - - if ((*x)->involves (ar)) { - - pair >::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) { - // relax, Invalidated during refresh - } - } - } - - x = tmp; - } -} - -void -AudioPlaylist::finalize_split_region (boost::shared_ptr o, boost::shared_ptr l, boost::shared_ptr r) -{ - boost::shared_ptr orig = boost::dynamic_pointer_cast(o); - boost::shared_ptr left = boost::dynamic_pointer_cast(l); - boost::shared_ptr right = boost::dynamic_pointer_cast(r); - - for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) { - Crossfades::iterator tmp; - tmp = x; - ++tmp; - - boost::shared_ptr fade; - - if ((*x)->_in == orig) { - if (! (*x)->covers(right->position())) { - fade = boost::shared_ptr (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 (new Crossfade (*x, right, (*x)->_out)); - } - } - - if ((*x)->_out == orig) { - if (! (*x)->covers(right->position())) { - fade = boost::shared_ptr (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 (new Crossfade (*x, (*x)->_in, left)); - } - } - - if (fade) { - _crossfades.remove (*x); - add_crossfade (fade); - } - x = tmp; - } -} - -void -AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) -{ - boost::shared_ptr other; - boost::shared_ptr region; - boost::shared_ptr top; - boost::shared_ptr bottom; - boost::shared_ptr xfade; - RegionList* touched_regions = 0; - - if (in_set_state || in_partition) { - return; - } - - if ((region = boost::dynamic_pointer_cast (r)) == 0) { - fatal << _("programming error: non-audio Region tested for overlap in audio playlist") - << endmsg; - return; - } - - if (!norefresh) { - refresh_dependents (r); - } - - - if (!_session.config.get_auto_xfade()) { - return; - } - - for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { - other = boost::dynamic_pointer_cast (*i); - - if (other == region) { - continue; - } + /* This will be a list of the bits of our read range that we have + handled completely (ie for which no more regions need to be read). + It is a list of ranges in session frames. + */ + Evoral::RangeList done; - if (other->muted() || region->muted()) { - continue; - } + /* This will be a list of the bits of regions that we need to read */ + list to_do; - if (other->position() == r->position() && other->length() == r->length()) { - /* precise overlay of two regions - no xfade */ - continue; - } - - if (other->layer() < region->layer()) { - top = region; - bottom = other; - } else { - top = other; - bottom = region; - } + /* 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 ar = boost::dynamic_pointer_cast (*i); - if (!top->opaque()) { + /* muted regions don't figure into it at all */ + if ( ar->muted() ) continue; - } - - OverlapType c = top->coverage (bottom->position(), bottom->last_frame()); - - delete touched_regions; - touched_regions = 0; - - try { - framecnt_t xfade_length; - switch (c) { - case OverlapNone: - break; - - case OverlapInternal: - /* {=============== top =============} - * [ ----- bottom ------- ] - */ - break; - - case OverlapExternal: - - /* [ -------- 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 ((framecnt_t) 720, top->length()); - - if (top_region_at (top->first_frame()) == top) { - - xfade = boost::shared_ptr (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 - would cover it). - */ - - xfade = boost::shared_ptr (new Crossfade (bottom, top, xfade_length, EndOfOut)); - xfade->set_position (top->last_frame() - xfade_length); - add_crossfade (xfade); - } - break; - case OverlapStart: + /* Work out which bits of this region need to be read; + first, trim to the range we are reading... + */ + Evoral::Range region_range = ar->range (); + region_range.from = max (region_range.from, start); + region_range.to = min (region_range.to, start + cnt - 1); - /* { ==== top ============ } - * [---- bottom -------------------] - */ + /* ... and then remove the bits that are already done */ - 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 (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 (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active())); - add_crossfade (xfade); - } - } - break; - case OverlapEnd: + Evoral::RangeList 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. + */ - /* [---- top ------------------------] - * { ==== bottom ============ } - */ - - if (_session.config.get_xfade_model() == FullCrossfade) { + Evoral::RangeList::List t = region_to_do.get (); - touched_regions = regions_touched (bottom->first_frame(), top->last_frame()); - if (touched_regions->size() <= 2) { - xfade = boost::shared_ptr (new Crossfade (region, other, - _session.config.get_xfade_model(), _session.config.get_xfades_active())); - add_crossfade (xfade); - } + for (Evoral::RangeList::List::iterator j = t.begin(); j != t.end(); ++j) { + Evoral::Range d = *j; + to_do.push_back (Segment (ar, d)); - } 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 (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active())); - add_crossfade (xfade); - } + if (ar->opaque ()) { + /* Cut this range down to just the body and mark it done */ + Evoral::Range body = ar->body_range (); + if (body.from < d.to && body.to > d.from) { + d.from = max (d.from, body.from); + d.to = min (d.to, body.to); + done.add (d); } - break; - default: - xfade = boost::shared_ptr (new Crossfade (region, other, - _session.config.get_xfade_model(), _session.config.get_xfades_active())); - add_crossfade (xfade); } } - - catch (failed_constructor& err) { - continue; - } - - catch (Crossfade::NoCrossfadeHere& err) { - continue; - } - - } - - delete touched_regions; -} - -void -AudioPlaylist::add_crossfade (boost::shared_ptr xfade) -{ - Crossfades::iterator ci; - - for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) { - if (*(*ci) == *xfade) { // Crossfade::operator==() - break; - } - } - - if (ci != _crossfades.end()) { - // it will just go away - } else { - _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)); - - notify_crossfade_added (xfade); } -} - -void AudioPlaylist::notify_crossfade_added (boost::shared_ptr x) -{ - if (g_atomic_int_get(&block_notifications)) { - _pending_xfade_adds.insert (_pending_xfade_adds.end(), x); - } else { - NewCrossfade (x); /* EMIT SIGNAL */ - } -} - -void -AudioPlaylist::crossfade_invalidated (boost::shared_ptr r) -{ - Crossfades::iterator i; - boost::shared_ptr xfade = boost::dynamic_pointer_cast (r); - - xfade->in()->resume_fade_in (); - xfade->out()->resume_fade_out (); - - if ((i = find (_crossfades.begin(), _crossfades.end(), xfade)) != _crossfades.end()) { - _crossfades.erase (i); - } -} - -int -AudioPlaylist::set_state (const XMLNode& node, int version) -{ - XMLNode *child; - XMLNodeList nlist; - XMLNodeConstIterator niter; - - in_set_state++; - - if (Playlist::set_state (node, version)) { - return -1; - } - - freeze (); - - nlist = node.children(); - - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - - child = *niter; - - if (child->name() != "Crossfade") { - continue; - } - - try { - boost::shared_ptr xfade = boost::shared_ptr (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) - // << endl; - continue; - } - } - - thaw (); - in_set_state--; - - return 0; -} - -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()); - } + /* Now go backwards through the to_do list doing the actual reads */ + for (list::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); } - return node; + return cnt; } void AudioPlaylist::dump () const { boost::shared_ptrr; - boost::shared_ptr x; cerr << "Playlist \"" << _name << "\" " << endl << regions.size() << " regions " - << _crossfades.size() << " crossfades" << endl; for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) { @@ -794,21 +275,6 @@ AudioPlaylist::dump () const << 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; - } } bool @@ -821,11 +287,9 @@ AudioPlaylist::destroy_region (boost::shared_ptr region) } bool changed = false; - Crossfades::iterator c, ctmp; - set > unique_xfades; { - RegionLock rlock (this); + RegionWriteLock rlock (this); for (RegionList::iterator i = regions.begin(); i != regions.end(); ) { @@ -856,18 +320,6 @@ AudioPlaylist::destroy_region (boost::shared_ptr region) region->set_playlist (boost::shared_ptr()); } - 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); @@ -876,22 +328,6 @@ AudioPlaylist::destroy_region (boost::shared_ptr region) return changed; } -void -AudioPlaylist::crossfade_changed (const PropertyChange&) -{ - if (in_flush || in_set_state) { - return; - } - - /* XXX is there a loop here? can an xfade change not happen - due to a playlist change? well, sure activation would - be an example. maybe we should check the type of change - that occured. - */ - - notify_contents_changed (); -} - bool AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr region) { @@ -899,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); @@ -912,176 +353,14 @@ 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 (); } 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)> 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 -AudioPlaylist::find_crossfade (const PBD::ID& id) const -{ - Crossfades::const_iterator i = _crossfades.begin (); - while (i != _crossfades.end() && (*i)->id() != id) { - ++i; - } - - if (i == _crossfades.end()) { - return boost::shared_ptr (); - } - - return *i; -} - -struct crossfade_triple { - boost::shared_ptr old_in; - boost::shared_ptr new_in; - boost::shared_ptr new_out; -}; - -void -AudioPlaylist::copy_dependents (const vector& old_and_new, Playlist* other) const -{ - AudioPlaylist* other_audio = dynamic_cast(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,crossfade_triple> CrossfadeInfo; - CrossfadeInfo crossfade_info; - - /* build up a record that links crossfades, old regions and new regions - */ - - for (vector::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 new_xfade (new Crossfade (ci->first, - boost::dynamic_pointer_cast(ci->second.new_in), - boost::dynamic_pointer_cast(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 >& copies) { @@ -1197,3 +476,101 @@ AudioPlaylist::pre_uncombine (vector >& originals, boo _session.add_command (new StatefulDiffCommand (*i)); } } + +int +AudioPlaylist::set_state (const XMLNode& node, int version) +{ + 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 const * p = (*i)->property (X_("active")); + assert (p); + + if (!string_is_affirmative (p->value())) { + continue; + } + + if ((p = (*i)->property (X_("in"))) == 0) { + continue; + } + + boost::shared_ptr in = region_by_id (PBD::ID (p->value ())); + + if (!in) { + warning << string_compose (_("Legacy crossfade involved an incoming region not present in playlist \"%1\" - crossfade discarded"), + name()) + << endmsg; + continue; + } + + boost::shared_ptr in_a = boost::dynamic_pointer_cast (in); + assert (in_a); + + if ((p = (*i)->property (X_("out"))) == 0) { + continue; + } + + boost::shared_ptr out = region_by_id (PBD::ID (p->value ())); + + if (!out) { + warning << string_compose (_("Legacy crossfade involved an outgoing region not present in playlist \"%1\" - crossfade discarded"), + name()) + << endmsg; + continue; + } + + boost::shared_ptr out_a = boost::dynamic_pointer_cast (out); + assert (out_a); + + /* 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); + } + } + } +}