2 Copyright (C) 2003 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include "ardour/types.h"
25 #include "ardour/debug.h"
26 #include "ardour/configuration.h"
27 #include "ardour/audioplaylist.h"
28 #include "ardour/audioregion.h"
29 #include "ardour/crossfade.h"
30 #include "ardour/session.h"
31 #include "pbd/enumwriter.h"
35 using namespace ARDOUR;
40 namespace Properties {
41 PBD::PropertyDescriptor<bool> crossfades;
46 AudioPlaylist::make_property_quarks ()
48 Properties::crossfades.property_id = g_quark_from_static_string (X_("crossfades"));
49 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for crossfades = %1\n", Properties::crossfades.property_id));
52 CrossfadeListProperty::CrossfadeListProperty (AudioPlaylist& pl)
53 : SequenceProperty<std::list<boost::shared_ptr<Crossfade> > > (Properties::crossfades.property_id, boost::bind (&AudioPlaylist::update, &pl, _1))
59 CrossfadeListProperty::CrossfadeListProperty (CrossfadeListProperty const & p)
60 : PBD::SequenceProperty<std::list<boost::shared_ptr<Crossfade> > > (p)
61 , _playlist (p._playlist)
67 CrossfadeListProperty *
68 CrossfadeListProperty::create () const
70 return new CrossfadeListProperty (_playlist);
73 CrossfadeListProperty *
74 CrossfadeListProperty::clone () const
76 return new CrossfadeListProperty (*this);
80 CrossfadeListProperty::get_content_as_xml (boost::shared_ptr<Crossfade> xfade, XMLNode & node) const
82 /* Crossfades are not written to any state when they are no
83 longer in use, so we must write their state here.
86 XMLNode& c = xfade->get_state ();
87 node.add_child_nocopy (c);
90 boost::shared_ptr<Crossfade>
91 CrossfadeListProperty::get_content_from_xml (XMLNode const & node) const
93 XMLNodeList const c = node.children ();
94 assert (c.size() == 1);
95 return boost::shared_ptr<Crossfade> (new Crossfade (_playlist, *c.front()));
99 AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden)
100 : Playlist (session, node, DataType::AUDIO, hidden)
101 , _crossfades (*this)
104 const XMLProperty* prop = node.property("type");
105 assert(!prop || DataType(prop->value()) == DataType::AUDIO);
108 add_property (_crossfades);
111 set_state (node, Stateful::loading_state_version);
115 AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden)
116 : Playlist (session, name, DataType::AUDIO, hidden)
117 , _crossfades (*this)
119 add_property (_crossfades);
122 AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, string name, bool hidden)
123 : Playlist (other, name, hidden)
124 , _crossfades (*this)
126 add_property (_crossfades);
128 RegionList::const_iterator in_o = other->regions.begin();
129 RegionList::iterator in_n = regions.begin();
131 while (in_o != other->regions.end()) {
132 boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(*in_o);
134 // We look only for crossfades which begin with the current region, so we don't get doubles
135 for (Crossfades::const_iterator xfades = other->_crossfades.begin(); xfades != other->_crossfades.end(); ++xfades) {
136 if ((*xfades)->in() == ar) {
137 // We found one! Now copy it!
139 RegionList::const_iterator out_o = other->regions.begin();
140 RegionList::const_iterator out_n = regions.begin();
142 while (out_o != other->regions.end()) {
144 boost::shared_ptr<AudioRegion>ar2 = boost::dynamic_pointer_cast<AudioRegion>(*out_o);
146 if ((*xfades)->out() == ar2) {
147 boost::shared_ptr<AudioRegion>in = boost::dynamic_pointer_cast<AudioRegion>(*in_n);
148 boost::shared_ptr<AudioRegion>out = boost::dynamic_pointer_cast<AudioRegion>(*out_n);
149 boost::shared_ptr<Crossfade> new_fade = boost::shared_ptr<Crossfade> (new Crossfade (*xfades, in, out));
150 add_crossfade(new_fade);
157 // cerr << "HUH!? second region in the crossfade not found!" << endl;
166 AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, framepos_t start, framecnt_t cnt, string name, bool hidden)
167 : Playlist (other, start, cnt, name, hidden)
168 , _crossfades (*this)
170 RegionLock rlock2 (const_cast<AudioPlaylist*> (other.get()));
173 add_property (_crossfades);
175 framepos_t const end = start + cnt - 1;
177 /* Audio regions that have been created by the Playlist constructor
178 will currently have the same fade in/out as the regions that they
179 were created from. This is wrong, so reset the fades here.
182 RegionList::iterator ours = regions.begin ();
184 for (RegionList::const_iterator i = other->regions.begin(); i != other->regions.end(); ++i) {
185 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (*i);
188 framecnt_t fade_in = 64;
189 framecnt_t fade_out = 64;
191 switch (region->coverage (start, end)) {
195 case OverlapInternal:
197 framecnt_t const offset = start - region->position ();
198 framecnt_t const trim = region->last_frame() - end;
199 if (region->fade_in()->back()->when > offset) {
200 fade_in = region->fade_in()->back()->when - offset;
202 if (region->fade_out()->back()->when > trim) {
203 fade_out = region->fade_out()->back()->when - trim;
209 position = region->position() - start;
210 len = end - region->position();
212 if (end > region->position() + region->fade_in().back()->when)
213 fade_in_len = region->fade_in().back()->when; //end is after fade-in, preserve the fade-in
214 if (end > region->last_frame() - region->fade_out().back()->when)
215 fade_out_len = region->fade_out().back()->when - ( region->last_frame() - end ); //end is inside the fadeout, preserve the fades endpoint
221 offset = start - region->position();
222 len = region->length() - offset;
224 if (start < region->last_frame() - region->fade_out().back()->when) //start is before fade-out, preserve the fadeout
225 fade_out_len = region->fade_out().back()->when;
227 if (start < region->position() + region->fade_in().back()->when)
228 fade_in_len = region->fade_in().back()->when - (start - region->position()); //end is inside the fade-in, preserve the fade-in endpoint
232 case OverlapExternal:
233 fade_in = region->fade_in()->back()->when;
234 fade_out = region->fade_out()->back()->when;
238 boost::shared_ptr<AudioRegion> our_region = boost::dynamic_pointer_cast<AudioRegion> (*ours);
241 our_region->set_fade_in_length (fade_in);
242 our_region->set_fade_out_length (fade_out);
248 /* this constructor does NOT notify others (session) */
251 AudioPlaylist::~AudioPlaylist ()
253 _crossfades.clear ();
256 struct RegionSortByLayer {
257 bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
258 return a->layer() < b->layer();
263 AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, framepos_t start,
264 framecnt_t cnt, unsigned chan_n)
266 framecnt_t ret = cnt;
268 /* optimizing this memset() away involves a lot of conditionals
269 that may well cause more of a hit due to cache misses
270 and related stuff than just doing this here.
272 it would be great if someone could measure this
275 one way or another, parts of the requested area
276 that are not written to by Region::region_at()
277 for all Regions that cover the area need to be
281 memset (buf, 0, sizeof (Sample) * cnt);
283 /* this function is never called from a realtime thread, so
284 its OK to block (for short intervals).
287 Glib::RecMutex::Lock rm (region_lock);
289 framepos_t const end = start + cnt - 1;
290 framecnt_t read_frames = 0;
291 framecnt_t skip_frames = 0;
292 _read_data_count = 0;
294 _read_data_count = 0;
296 RegionList* rlist = regions_to_read (start, start+cnt);
298 if (rlist->empty()) {
303 map<uint32_t,vector<boost::shared_ptr<Region> > > relevant_regions;
304 map<uint32_t,vector<boost::shared_ptr<Crossfade> > > relevant_xfades;
305 vector<uint32_t> relevant_layers;
307 for (RegionList::iterator i = rlist->begin(); i != rlist->end(); ++i) {
308 if ((*i)->coverage (start, end) != OverlapNone) {
309 relevant_regions[(*i)->layer()].push_back (*i);
310 relevant_layers.push_back ((*i)->layer());
314 for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
315 if ((*i)->coverage (start, end) != OverlapNone) {
316 relevant_xfades[(*i)->upper_layer()].push_back (*i);
320 // RegionSortByLayer layer_cmp;
321 // relevant_regions.sort (layer_cmp);
323 /* XXX this whole per-layer approach is a hack that
324 should be removed once Crossfades become
325 CrossfadeRegions and we just grab a list of relevant
326 regions and call read_at() on all of them.
329 sort (relevant_layers.begin(), relevant_layers.end());
331 for (vector<uint32_t>::iterator l = relevant_layers.begin(); l != relevant_layers.end(); ++l) {
333 vector<boost::shared_ptr<Region> > r (relevant_regions[*l]);
334 vector<boost::shared_ptr<Crossfade> >& x (relevant_xfades[*l]);
337 for (vector<boost::shared_ptr<Region> >::iterator i = r.begin(); i != r.end(); ++i) {
338 boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(*i);
339 DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("read from region %1\n", ar->name()));
341 ar->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n, read_frames, skip_frames);
342 _read_data_count += ar->read_data_count();
345 for (vector<boost::shared_ptr<Crossfade> >::iterator i = x.begin(); i != x.end(); ++i) {
346 (*i)->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n);
348 /* don't JACK up _read_data_count, since its the same data as we just
349 read from the regions, and the OS should handle that for us.
360 AudioPlaylist::remove_dependents (boost::shared_ptr<Region> region)
362 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
369 fatal << _("programming error: non-audio Region passed to remove_overlap in audio playlist")
374 for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ) {
376 if ((*i)->involves (r)) {
377 i = _crossfades.erase (i);
386 AudioPlaylist::flush_notifications (bool from_undo)
388 Playlist::flush_notifications (from_undo);
396 Crossfades::iterator a;
397 for (a = _pending_xfade_adds.begin(); a != _pending_xfade_adds.end(); ++a) {
398 NewCrossfade (*a); /* EMIT SIGNAL */
401 _pending_xfade_adds.clear ();
407 AudioPlaylist::refresh_dependents (boost::shared_ptr<Region> r)
409 boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(r);
410 set<boost::shared_ptr<Crossfade> > updated;
416 for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) {
418 Crossfades::iterator tmp;
423 /* only update them once */
425 if ((*x)->involves (ar)) {
427 pair<set<boost::shared_ptr<Crossfade> >::iterator, bool> const u = updated.insert (*x);
430 /* x was successfully inserted into the set, so it has not already been updated */
435 catch (Crossfade::NoCrossfadeHere& err) {
436 // relax, Invalidated during refresh
446 AudioPlaylist::finalize_split_region (boost::shared_ptr<Region> o, boost::shared_ptr<Region> l, boost::shared_ptr<Region> r)
448 boost::shared_ptr<AudioRegion> orig = boost::dynamic_pointer_cast<AudioRegion>(o);
449 boost::shared_ptr<AudioRegion> left = boost::dynamic_pointer_cast<AudioRegion>(l);
450 boost::shared_ptr<AudioRegion> right = boost::dynamic_pointer_cast<AudioRegion>(r);
452 for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) {
453 Crossfades::iterator tmp;
457 boost::shared_ptr<Crossfade> fade;
459 if ((*x)->_in == orig) {
460 if (! (*x)->covers(right->position())) {
461 fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, left, (*x)->_out));
463 // Overlap, the crossfade is copied on the left side of the right region instead
464 fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, right, (*x)->_out));
468 if ((*x)->_out == orig) {
469 if (! (*x)->covers(right->position())) {
470 fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, (*x)->_in, right));
472 // Overlap, the crossfade is copied on the right side of the left region instead
473 fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, (*x)->_in, left));
478 _crossfades.remove (*x);
479 add_crossfade (fade);
486 AudioPlaylist::check_dependents (boost::shared_ptr<Region> r, bool norefresh)
488 boost::shared_ptr<AudioRegion> other;
489 boost::shared_ptr<AudioRegion> region;
490 boost::shared_ptr<AudioRegion> top;
491 boost::shared_ptr<AudioRegion> bottom;
492 boost::shared_ptr<Crossfade> xfade;
493 RegionList* touched_regions = 0;
495 if (in_set_state || in_partition) {
499 if ((region = boost::dynamic_pointer_cast<AudioRegion> (r)) == 0) {
500 fatal << _("programming error: non-audio Region tested for overlap in audio playlist")
506 refresh_dependents (r);
510 if (!_session.config.get_auto_xfade()) {
514 for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
515 other = boost::dynamic_pointer_cast<AudioRegion> (*i);
517 if (other == region) {
521 if (other->muted() || region->muted()) {
525 if (other->position() == r->position() && other->length() == r->length()) {
526 /* precise overlay of two regions - no xfade */
530 if (other->layer() < region->layer()) {
538 if (!top->opaque()) {
542 OverlapType c = top->coverage (bottom->position(), bottom->last_frame());
544 delete touched_regions;
548 framecnt_t xfade_length;
553 case OverlapInternal:
554 /* {=============== top =============}
555 * [ ----- bottom ------- ]
559 case OverlapExternal:
561 /* [ -------- top ------- ]
562 * {=========== bottom =============}
565 /* to avoid discontinuities at the region boundaries of an internal
566 overlap (this region is completely within another), we create
567 two hidden crossfades at each boundary. this is not dependent
568 on the auto-xfade option, because we require it as basic
572 xfade_length = min ((framecnt_t) 720, top->length());
574 if (top_region_at (top->first_frame()) == top) {
576 xfade = boost::shared_ptr<Crossfade> (new Crossfade (top, bottom, xfade_length, top->first_frame(), StartOfIn));
577 add_crossfade (xfade);
580 if (top_region_at (top->last_frame() - 1) == top) {
583 only add a fade out if there is no region on top of the end of 'top' (which
587 xfade = boost::shared_ptr<Crossfade> (new Crossfade (bottom, top, xfade_length, top->last_frame() - xfade_length, EndOfOut));
588 add_crossfade (xfade);
593 /* { ==== top ============ }
594 * [---- bottom -------------------]
597 if (_session.config.get_xfade_model() == FullCrossfade) {
598 touched_regions = regions_touched (top->first_frame(), bottom->last_frame());
599 if (touched_regions->size() <= 2) {
600 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active()));
601 add_crossfade (xfade);
605 touched_regions = regions_touched (top->first_frame(),
606 top->first_frame() + min ((framecnt_t) _session.config.get_short_xfade_seconds() * _session.frame_rate(),
608 if (touched_regions->size() <= 2) {
609 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active()));
610 add_crossfade (xfade);
617 /* [---- top ------------------------]
618 * { ==== bottom ============ }
621 if (_session.config.get_xfade_model() == FullCrossfade) {
623 touched_regions = regions_touched (bottom->first_frame(), top->last_frame());
624 if (touched_regions->size() <= 2) {
625 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other,
626 _session.config.get_xfade_model(), _session.config.get_xfades_active()));
627 add_crossfade (xfade);
631 touched_regions = regions_touched (bottom->first_frame(),
632 bottom->first_frame() + min ((framecnt_t)_session.config.get_short_xfade_seconds() * _session.frame_rate(),
634 if (touched_regions->size() <= 2) {
635 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active()));
636 add_crossfade (xfade);
641 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other,
642 _session.config.get_xfade_model(), _session.config.get_xfades_active()));
643 add_crossfade (xfade);
647 catch (failed_constructor& err) {
651 catch (Crossfade::NoCrossfadeHere& err) {
657 delete touched_regions;
661 AudioPlaylist::add_crossfade (boost::shared_ptr<Crossfade> xfade)
663 Crossfades::iterator ci;
665 for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) {
666 if (*(*ci) == *xfade) { // Crossfade::operator==()
671 if (ci != _crossfades.end()) {
672 // it will just go away
674 _crossfades.push_back (xfade);
676 xfade->Invalidated.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_invalidated, this, _1));
677 xfade->PropertyChanged.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_changed, this, _1));
679 notify_crossfade_added (xfade);
683 void AudioPlaylist::notify_crossfade_added (boost::shared_ptr<Crossfade> x)
685 if (g_atomic_int_get(&block_notifications)) {
686 _pending_xfade_adds.insert (_pending_xfade_adds.end(), x);
688 NewCrossfade (x); /* EMIT SIGNAL */
693 AudioPlaylist::crossfade_invalidated (boost::shared_ptr<Region> r)
695 Crossfades::iterator i;
696 boost::shared_ptr<Crossfade> xfade = boost::dynamic_pointer_cast<Crossfade> (r);
698 xfade->in()->resume_fade_in ();
699 xfade->out()->resume_fade_out ();
701 if ((i = find (_crossfades.begin(), _crossfades.end(), xfade)) != _crossfades.end()) {
702 _crossfades.erase (i);
707 AudioPlaylist::set_state (const XMLNode& node, int version)
711 XMLNodeConstIterator niter;
715 Playlist::set_state (node, version);
719 nlist = node.children();
721 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
725 if (child->name() != "Crossfade") {
730 boost::shared_ptr<Crossfade> xfade = boost::shared_ptr<Crossfade> (new Crossfade (*((const Playlist *)this), *child));
731 _crossfades.push_back (xfade);
732 xfade->Invalidated.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_invalidated, this, _1));
733 xfade->PropertyChanged.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_changed, this, _1));
737 catch (failed_constructor& err) {
738 // cout << string_compose (_("could not create crossfade object in playlist %1"),
752 AudioPlaylist::clear (bool with_signals)
754 _crossfades.clear ();
755 Playlist::clear (with_signals);
759 AudioPlaylist::state (bool full_state)
761 XMLNode& node = Playlist::state (full_state);
764 for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
765 node.add_child_nocopy ((*i)->get_state());
773 AudioPlaylist::dump () const
775 boost::shared_ptr<Region>r;
776 boost::shared_ptr<Crossfade> x;
778 cerr << "Playlist \"" << _name << "\" " << endl
779 << regions.size() << " regions "
780 << _crossfades.size() << " crossfades"
783 for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
785 cerr << " " << r->name() << " @ " << r << " ["
786 << r->start() << "+" << r->length()
794 for (Crossfades::const_iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
805 << (x->active() ? "yes" : "no")
811 AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
813 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
819 bool changed = false;
820 Crossfades::iterator c, ctmp;
821 set<boost::shared_ptr<Crossfade> > unique_xfades;
824 RegionLock rlock (this);
826 for (RegionList::iterator i = regions.begin(); i != regions.end(); ) {
828 RegionList::iterator tmp = i;
831 if ((*i) == region) {
839 for (set<boost::shared_ptr<Region> >::iterator x = all_regions.begin(); x != all_regions.end(); ) {
841 set<boost::shared_ptr<Region> >::iterator xtmp = x;
844 if ((*x) == region) {
845 all_regions.erase (x);
852 region->set_playlist (boost::shared_ptr<Playlist>());
855 for (c = _crossfades.begin(); c != _crossfades.end(); ) {
859 if ((*c)->involves (r)) {
860 unique_xfades.insert (*c);
861 _crossfades.erase (c);
868 /* overload this, it normally means "removed", not destroyed */
869 notify_region_removed (region);
876 AudioPlaylist::crossfade_changed (const PropertyChange&)
878 if (in_flush || in_set_state) {
882 /* XXX is there a loop here? can an xfade change not happen
883 due to a playlist change? well, sure activation would
884 be an example. maybe we should check the type of change
888 notify_contents_changed ();
892 AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
894 if (in_flush || in_set_state) {
898 PropertyChange our_interests;
900 our_interests.add (Properties::fade_in_active);
901 our_interests.add (Properties::fade_out_active);
902 our_interests.add (Properties::scale_amplitude);
903 our_interests.add (Properties::envelope_active);
904 our_interests.add (Properties::envelope);
905 our_interests.add (Properties::fade_in);
906 our_interests.add (Properties::fade_out);
908 bool parent_wants_notify;
910 parent_wants_notify = Playlist::region_changed (what_changed, region);
912 if (parent_wants_notify || (what_changed.contains (our_interests))) {
913 notify_contents_changed ();
920 AudioPlaylist::crossfades_at (framepos_t frame, Crossfades& clist)
922 RegionLock rlock (this);
924 for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
925 framepos_t const start = (*i)->position ();
926 framepos_t const end = start + (*i)->overlap_length(); // not length(), important difference
928 if (frame >= start && frame <= end) {
929 clist.push_back (*i);
935 AudioPlaylist::foreach_crossfade (boost::function<void (boost::shared_ptr<Crossfade>)> s)
937 RegionLock rl (this, false);
938 for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
944 AudioPlaylist::update (const CrossfadeListProperty::ChangeRecord& change)
946 for (CrossfadeListProperty::ChangeContainer::const_iterator i = change.added.begin(); i != change.added.end(); ++i) {
950 /* don't remove crossfades here; they will be dealt with by the dependency code */
953 boost::shared_ptr<Crossfade>
954 AudioPlaylist::find_crossfade (const PBD::ID& id) const
956 Crossfades::const_iterator i = _crossfades.begin ();
957 while (i != _crossfades.end() && (*i)->id() != id) {
961 if (i == _crossfades.end()) {
962 return boost::shared_ptr<Crossfade> ();