X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Faudio_playlist.cc;h=56f2c4fe83bf3a0cd0c76eda6d1be3da3d427b30;hb=cbaa0d472e86840e7c93791b354468b2c0e75a7d;hp=0c35599332187b6d97daf3392f2d4464c128697d;hpb=af12adb34f62dc82f694a03ea3b2a6c99ba426ef;p=ardour.git diff --git a/libs/ardour/audio_playlist.cc b/libs/ardour/audio_playlist.cc index 0c35599332..56f2c4fe83 100644 --- a/libs/ardour/audio_playlist.cc +++ b/libs/ardour/audio_playlist.cc @@ -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 @@ -15,46 +15,121 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ #include #include -#include - -#include -#include -#include -#include -#include -#include -#include +#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 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, 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, 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(); @@ -62,7 +137,7 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr other, stri 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 (list::const_iterator xfades = other->_crossfades.begin(); xfades != other->_crossfades.end(); ++xfades) { + for (Crossfades::const_iterator xfades = other->_crossfades.begin(); xfades != other->_crossfades.end(); ++xfades) { if ((*xfades)->in() == ar) { // We found one! Now copy it! @@ -70,17 +145,17 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr other, stri 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); - Crossfade *new_fade = new Crossfade (*(*xfades), in, out); - add_crossfade(*new_fade); + boost::shared_ptr new_fade = boost::shared_ptr (new Crossfade (*xfades, in, out)); + add_crossfade(new_fade); break; } - + out_o++; out_n++; } @@ -93,53 +168,108 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr other, stri } } -AudioPlaylist::AudioPlaylist (boost::shared_ptr other, nframes_t start, nframes_t cnt, string name, bool hidden) +AudioPlaylist::AudioPlaylist (boost::shared_ptr 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 (other.get())); + in_set_state++; -AudioPlaylist::~AudioPlaylist () -{ - set all_xfades; + add_property (_crossfades); - GoingAway (); /* EMIT SIGNAL */ + framepos_t const end = start + cnt - 1; - /* drop connections to signals */ + /* 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. + */ - notify_callbacks (); + RegionList::iterator ours = regions.begin (); - for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end(); ) { - Crossfades::iterator tmp; + for (RegionList::const_iterator i = other->regions.begin(); i != other->regions.end(); ++i) { + boost::shared_ptr region = boost::dynamic_pointer_cast (*i); + assert (region); - tmp = x; - ++tmp; + framecnt_t fade_in = 64; + framecnt_t fade_out = 64; - delete *x; + switch (region->coverage (start, end)) { + case OverlapNone: + continue; - x = tmp; + 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 our_region = boost::dynamic_pointer_cast (*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_ptra, boost::shared_ptrb) { + bool operator() (boost::shared_ptr a, boost::shared_ptr b) { return a->layer() < b->layer(); } }; -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; - nframes_t read_frames; - nframes_t skip_frames; + 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. @@ -151,32 +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); - end = start + cnt - 1; + framepos_t const end = start + cnt - 1; - read_frames = 0; - skip_frames = 0; - _read_data_count = 0; + boost::shared_ptr rlist = regions_to_read (start, start+cnt); + + if (rlist->empty()) { + return cnt; + } map > > relevant_regions; - map > relevant_xfades; + map > > relevant_xfades; vector 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)))); } } @@ -193,22 +333,22 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nf 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]); + 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, read_frames, skip_frames); - _read_data_count += ar->read_data_count(); + ar->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n); } - - for (vector::iterator i = x.begin(); i != x.end(); ++i) { - (*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. - */ + 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); } } @@ -219,37 +359,33 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nf void AudioPlaylist::remove_dependents (boost::shared_ptr region) { - Crossfades::iterator i, tmp; 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 (i = _crossfades.begin(); i != _crossfades.end(); ) { - tmp = i; - tmp++; + for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ) { - if ((*i)->involves (r)) { - delete *i; + i = _crossfades.erase (i); + } else { + ++i; } - - i = tmp; } } 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; } @@ -271,7 +407,7 @@ void AudioPlaylist::refresh_dependents (boost::shared_ptr r) { boost::shared_ptr ar = boost::dynamic_pointer_cast(r); - set updated; + set > updated; if (ar == 0) { return; @@ -280,7 +416,7 @@ AudioPlaylist::refresh_dependents (boost::shared_ptr r) for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) { Crossfades::iterator tmp; - + tmp = x; ++tmp; @@ -288,10 +424,16 @@ AudioPlaylist::refresh_dependents (boost::shared_ptr r) if ((*x)->involves (ar)) { - if (find (updated.begin(), updated.end(), *x) == updated.end()) { - if ((*x)->refresh ()) { - /* not invalidated by the refresh */ - updated.insert (*x); + 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 } } } @@ -312,29 +454,29 @@ AudioPlaylist::finalize_split_region (boost::shared_ptr o, boost::shared tmp = x; ++tmp; - Crossfade *fade = 0; - + boost::shared_ptr fade; + if ((*x)->_in == orig) { if (! (*x)->covers(right->position())) { - fade = new Crossfade (**x, left, (*x)->_out); + 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 = new Crossfade (**x, right, (*x)->_out); + fade = boost::shared_ptr (new Crossfade (*x, right, (*x)->_out)); } } - + if ((*x)->_out == orig) { if (! (*x)->covers(right->position())) { - fade = new Crossfade (**x, (*x)->_in, right); + 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 = new Crossfade (**x, (*x)->_in, left); + fade = boost::shared_ptr (new Crossfade (*x, (*x)->_in, left)); } } - + if (fade) { _crossfades.remove (*x); - add_crossfade (*fade); + add_crossfade (fade); } x = tmp; } @@ -347,7 +489,8 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) boost::shared_ptr region; boost::shared_ptr top; boost::shared_ptr bottom; - Crossfade* xfade; + boost::shared_ptr xfade; + boost::shared_ptr touched_regions; if (in_set_state || in_partition) { return; @@ -363,12 +506,12 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) refresh_dependents (r); } - if (!Config->get_auto_xfade()) { + + 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) { @@ -379,6 +522,11 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) 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; bottom = other; @@ -387,85 +535,151 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) bottom = region; } + if (!top->opaque()) { + continue; + } + + OverlapType c = top->coverage (bottom->position(), bottom->last_frame()); + + touched_regions.reset (); + try { - - if (top->coverage (bottom->position(), bottom->last_frame()) != OverlapNone) { - - /* check if the upper region is within the lower region */ - - if (top->first_frame() > bottom->first_frame() && - top->last_frame() < bottom->last_frame()) { - - - /* [ -------- 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. + 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). */ - - nframes_t xfade_length = min ((nframes_t) 720, top->length()); - - /* in, out */ - xfade = new Crossfade (top, bottom, xfade_length, top->first_frame(), StartOfIn); - 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 = new Crossfade (bottom, top, xfade_length, top->last_frame() - xfade_length, EndOfOut); - add_crossfade (*xfade); + + 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: + + /* { ==== 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 (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active())); + add_crossfade (xfade); } - } else { - xfade = new Crossfade (other, region, Config->get_xfade_model(), Config->get_xfades_active()); - add_crossfade (*xfade); + 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: + + + /* [---- 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 (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 (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active())); + add_crossfade (xfade); + } + } + 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; } - + } } void -AudioPlaylist::add_crossfade (Crossfade& xfade) +AudioPlaylist::add_crossfade (boost::shared_ptr xfade) { Crossfades::iterator ci; for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) { - if (*(*ci) == xfade) { // Crossfade::operator==() + if (*(*ci) == *xfade) { // Crossfade::operator==() break; } } - + if (ci != _crossfades.end()) { - delete &xfade; + // it will just go away } else { - _crossfades.push_back (&xfade); + _crossfades.push_back (xfade); - xfade.Invalidated.connect (mem_fun (*this, &AudioPlaylist::crossfade_invalidated)); - 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 (&xfade); + notify_crossfade_added (xfade); } } - -void AudioPlaylist::notify_crossfade_added (Crossfade *x) + +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); @@ -475,9 +689,10 @@ void AudioPlaylist::notify_crossfade_added (Crossfade *x) } void -AudioPlaylist::crossfade_invalidated (Crossfade* xfade) +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 (); @@ -488,16 +703,19 @@ AudioPlaylist::crossfade_invalidated (Crossfade* xfade) } 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(); @@ -510,16 +728,16 @@ AudioPlaylist::set_state (const XMLNode& node) } try { - Crossfade* xfade = new Crossfade (*((const Playlist *)this), *child); + boost::shared_ptr xfade = boost::shared_ptr (new Crossfade (*((const Playlist *)this), *child)); _crossfades.push_back (xfade); - xfade->Invalidated.connect (mem_fun (*this, &AudioPlaylist::crossfade_invalidated)); - 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)); NewCrossfade(xfade); } - + catch (failed_constructor& err) { // cout << string_compose (_("could not create crossfade object in playlist %1"), - // _name) + // _name) // << endl; continue; } @@ -534,19 +752,7 @@ AudioPlaylist::set_state (const XMLNode& node) void AudioPlaylist::clear (bool with_signals) { - for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ) { - - Crossfades::iterator tmp; - tmp = i; - ++tmp; - - delete *i; - - i = tmp; - } - _crossfades.clear (); - Playlist::clear (with_signals); } @@ -560,7 +766,7 @@ AudioPlaylist::state (bool full_state) node.add_child_nocopy ((*i)->get_state()); } } - + return node; } @@ -568,7 +774,7 @@ void AudioPlaylist::dump () const { boost::shared_ptrr; - Crossfade *x; + boost::shared_ptr x; cerr << "Playlist \"" << _name << "\" " << endl << regions.size() << " regions " @@ -577,9 +783,9 @@ AudioPlaylist::dump () const for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) { r = *i; - cerr << " " << r->name() << " @ " << r << " [" - << r->start() << "+" << r->length() - << "] at " + cerr << " " << r->name() << " @ " << r << " [" + << r->start() << "+" << r->length() + << "] at " << r->position() << " on layer " << r->layer () @@ -588,13 +794,13 @@ AudioPlaylist::dump () const for (Crossfades::const_iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { x = *i; - cerr << " xfade [" + cerr << " xfade [" << x->out()->name() << ',' << x->in()->name() << " @ " << x->position() - << " length = " + << " length = " << x->length () << " active ? " << (x->active() ? "yes" : "no") @@ -606,30 +812,28 @@ bool AudioPlaylist::destroy_region (boost::shared_ptr region) { boost::shared_ptr r = boost::dynamic_pointer_cast (region); + + if (!r) { + return false; + } + bool changed = false; Crossfades::iterator c, ctmp; - set unique_xfades; + set > unique_xfades; - if (r == 0) { - fatal << _("programming error: non-audio Region passed to remove_overlap in audio playlist") - << endmsg; - /*NOTREACHED*/ - return false; - } - - { + { 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; } - + i = tmp; } @@ -637,12 +841,12 @@ AudioPlaylist::destroy_region (boost::shared_ptr region) set >::iterator xtmp = x; ++xtmp; - + if ((*x) == region) { all_regions.erase (x); changed = true; } - + x = xtmp; } @@ -657,12 +861,8 @@ AudioPlaylist::destroy_region (boost::shared_ptr region) unique_xfades.insert (*c); _crossfades.erase (c); } - - c = ctmp; - } - for (set::iterator c = unique_xfades.begin(); c != unique_xfades.end(); ++c) { - delete *c; + c = ctmp; } if (changed) { @@ -674,7 +874,7 @@ AudioPlaylist::destroy_region (boost::shared_ptr region) } void -AudioPlaylist::crossfade_changed (Change ignored) +AudioPlaylist::crossfade_changed (const PropertyChange&) { if (in_flush || in_set_state) { return; @@ -686,48 +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) +AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr 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; + return true; } void -AudioPlaylist::crossfades_at (nframes_t frame, Crossfades& clist) +AudioPlaylist::crossfades_at (framepos_t frame, Crossfades& clist) { RegionLock rlock (this); for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { - nframes_t start, end; - - start = (*i)->position(); - end = start + (*i)->overlap_length(); // not length(), important difference + 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) +{ + RegionSortByPosition cmp; + boost::shared_ptr ar; + + sort (copies.begin(), copies.end(), cmp); + + ar = boost::dynamic_pointer_cast (copies.front()); + + /* disable fade in of the first region */ + + if (ar) { + ar->set_fade_in_active (false); + } + + ar = boost::dynamic_pointer_cast (copies.back()); + + /* disable fade out of the last region */ + + if (ar) { + ar->set_fade_out_active (false); + } +} + +void +AudioPlaylist::post_combine (vector >& originals, boost::shared_ptr compound_region) +{ + RegionSortByPosition cmp; + boost::shared_ptr ar; + boost::shared_ptr cr; + + if ((cr = boost::dynamic_pointer_cast (compound_region)) == 0) { + return; + } + + sort (originals.begin(), originals.end(), cmp); + + ar = boost::dynamic_pointer_cast (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 (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 >& originals, boost::shared_ptr compound_region) +{ + RegionSortByPosition cmp; + boost::shared_ptr ar; + boost::shared_ptr cr = boost::dynamic_pointer_cast(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 >::iterator i = originals.begin(); i != originals.end(); ++i) { + + if ((ar = boost::dynamic_pointer_cast (*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)); + } +}