X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Faudio_playlist.cc;h=76d0228547d01f1aae99a151cda1976cc61636c2;hb=204baa31d842d4c2f833d319b6fa55e402a1bfb8;hp=acb44e43c477b9841c4799a0db54c916d6c26c5a;hpb=f6fdd8dcbf41f864e9f0cc32dabe81fe3533ddfe;p=ardour.git diff --git a/libs/ardour/audio_playlist.cc b/libs/ardour/audio_playlist.cc index acb44e43c4..76d0228547 100644 --- a/libs/ardour/audio_playlist.cc +++ b/libs/ardour/audio_playlist.cc @@ -21,13 +21,13 @@ #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/crossfade_compare.h" +#include "ardour/region_sorters.h" #include "ardour/session.h" #include "pbd/enumwriter.h" @@ -37,25 +37,99 @@ 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"); assert(!prop || DataType(prop->value()) == DataType::AUDIO); +#endif + + add_property (_crossfades); in_set_state++; - set_state (node, Stateful::loading_state_version); + if (set_state (node, Stateful::loading_state_version)) { + throw failed_constructor(); + } in_set_state--; + + relayer (); } AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden) : Playlist (session, name, DataType::AUDIO, hidden) + , _crossfades (*this) { + add_property (_crossfades); } AudioPlaylist::AudioPlaylist (boost::shared_ptr 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(); @@ -94,18 +168,86 @@ 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) { + RegionLock 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 + will currently have the same fade in/out as the regions that they + were created from. This is wrong, so reset the fades here. + */ + + RegionList::iterator ours = regions.begin (); + + for (RegionList::const_iterator i = other->regions.begin(); i != other->regions.end(); ++i) { + boost::shared_ptr region = boost::dynamic_pointer_cast (*i); + assert (region); + + framecnt_t fade_in = 64; + framecnt_t fade_out = 64; + + switch (region->coverage (start, end)) { + case OverlapNone: + continue; + + case OverlapInternal: + { + framecnt_t const offset = start - region->position (); + framecnt_t const trim = region->last_frame() - end; + if (region->fade_in()->back()->when > offset) { + fade_in = region->fade_in()->back()->when - offset; + } + if (region->fade_out()->back()->when > trim) { + fade_out = region->fade_out()->back()->when - trim; + } + break; + } + + case OverlapStart: { + if (end > region->position() + region->fade_in()->back()->when) + fade_in = region->fade_in()->back()->when; //end is after fade-in, preserve the fade-in + if (end > region->last_frame() - region->fade_out()->back()->when) + fade_out = region->fade_out()->back()->when - ( region->last_frame() - end ); //end is inside the fadeout, preserve the fades endpoint + break; + } + + case OverlapEnd: { + if (start < region->last_frame() - region->fade_out()->back()->when) //start is before fade-out, preserve the fadeout + fade_out = region->fade_out()->back()->when; + + if (start < region->position() + region->fade_in()->back()->when) + fade_in = region->fade_in()->back()->when - (start - region->position()); //end is inside the fade-in, preserve the fade-in endpoint + break; + } + + case OverlapExternal: + fade_in = region->fade_in()->back()->when; + fade_out = region->fade_out()->back()->when; + break; + } + + boost::shared_ptr 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 () { - drop_references (); - - /* drop connections to signals */ - _crossfades.clear (); } @@ -115,14 +257,14 @@ struct RegionSortByLayer { } }; -ARDOUR::nframes_t -AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nframes_t start, - nframes_t cnt, unsigned chan_n) +ARDOUR::framecnt_t +AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, framepos_t start, + framecnt_t cnt, unsigned chan_n) { - nframes_t ret = cnt; - nframes_t end; - 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 @@ -145,17 +287,11 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nf Glib::RecMutex::Lock rm (region_lock); - end = start + cnt - 1; - read_frames = 0; - skip_frames = 0; - _read_data_count = 0; - - _read_data_count = 0; + framepos_t const end = start + cnt - 1; - RegionList* rlist = regions_to_read (start, start+cnt); + boost::shared_ptr rlist = regions_to_read (start, start+cnt); if (rlist->empty()) { - delete rlist; return cnt; } @@ -167,12 +303,20 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nf 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)))); } } @@ -189,26 +333,25 @@ 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]); + 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) { + DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("read from xfade between %1 & %2\n", (*i)->out()->name(), (*i)->in()->name())); (*i)->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n); - - /* don't JACK up _read_data_count, since its the same data as we just - read from the regions, and the OS should handle that for us. - */ } } - delete rlist; return ret; } @@ -240,9 +383,9 @@ AudioPlaylist::remove_dependents (boost::shared_ptr region) void -AudioPlaylist::flush_notifications () +AudioPlaylist::flush_notifications (bool from_undo) { - Playlist::flush_notifications(); + Playlist::flush_notifications (from_undo); if (in_flush) { return; @@ -347,7 +490,7 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) boost::shared_ptr top; boost::shared_ptr bottom; boost::shared_ptr xfade; - RegionList* touched_regions = 0; + boost::shared_ptr touched_regions; if (in_set_state || in_partition) { return; @@ -369,9 +512,6 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) } for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { - - nframes_t xfade_length; - other = boost::dynamic_pointer_cast (*i); if (other == region) { @@ -382,6 +522,10 @@ 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; @@ -397,10 +541,10 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) OverlapType c = top->coverage (bottom->position(), bottom->last_frame()); - delete touched_regions; - touched_regions = 0; + touched_regions.reset (); try { + framecnt_t xfade_length; switch (c) { case OverlapNone: break; @@ -424,11 +568,12 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) audio engineering. */ - xfade_length = min ((nframes_t) 720, top->length()); + 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, top->first_frame(), StartOfIn)); + xfade = boost::shared_ptr (new Crossfade (top, bottom, xfade_length, StartOfIn)); + xfade->set_position (top->first_frame()); add_crossfade (xfade); } @@ -439,7 +584,8 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) would cover it). */ - xfade = boost::shared_ptr (new Crossfade (bottom, top, xfade_length, top->last_frame() - xfade_length, EndOfOut)); + xfade = boost::shared_ptr (new Crossfade (bottom, top, xfade_length, EndOfOut)); + xfade->set_position (top->last_frame() - xfade_length); add_crossfade (xfade); } break; @@ -458,7 +604,7 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) } else { touched_regions = regions_touched (top->first_frame(), - top->first_frame() + min ((nframes_t)_session.config.get_short_xfade_seconds() * _session.frame_rate(), + 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())); @@ -484,7 +630,7 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) } else { touched_regions = regions_touched (bottom->first_frame(), - bottom->first_frame() + min ((nframes_t)_session.config.get_short_xfade_seconds() * _session.frame_rate(), + 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())); @@ -508,8 +654,6 @@ AudioPlaylist::check_dependents (boost::shared_ptr r, bool norefresh) } } - - delete touched_regions; } void @@ -528,8 +672,8 @@ AudioPlaylist::add_crossfade (boost::shared_ptr xfade) } else { _crossfades.push_back (xfade); - scoped_connect (xfade->Invalidated, boost::bind (&AudioPlaylist::crossfade_invalidated, this, _1)); - scoped_connect (xfade->StateChanged, boost::bind (&AudioPlaylist::crossfade_changed, this, _1)); + 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); } @@ -567,7 +711,9 @@ AudioPlaylist::set_state (const XMLNode& node, int version) in_set_state++; - Playlist::set_state (node, version); + if (Playlist::set_state (node, version)) { + return -1; + } freeze (); @@ -584,8 +730,8 @@ AudioPlaylist::set_state (const XMLNode& node, int version) try { boost::shared_ptr xfade = boost::shared_ptr (new Crossfade (*((const Playlist *)this), *child)); _crossfades.push_back (xfade); - scoped_connect (xfade->Invalidated, boost::bind (&AudioPlaylist::crossfade_invalidated, this, _1)); - scoped_connect (xfade->StateChanged, boost::bind (&AudioPlaylist::crossfade_changed, this, _1)); + 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); } @@ -666,17 +812,15 @@ 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; - if (r == 0) { - fatal << _("programming error: non-audio Region passed to remove_overlap in audio playlist") - << endmsg; - /*NOTREACHED*/ - return false; - } - { RegionLock rlock (this); @@ -730,7 +874,7 @@ AudioPlaylist::destroy_region (boost::shared_ptr region) } void -AudioPlaylist::crossfade_changed (Change) +AudioPlaylist::crossfade_changed (const PropertyChange&) { if (in_flush || in_set_state) { return; @@ -742,44 +886,45 @@ AudioPlaylist::crossfade_changed (Change) 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; } 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); @@ -795,3 +940,267 @@ AudioPlaylist::foreach_crossfade (boost::function +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)); + } +} + +void +AudioPlaylist::get_equivalent_crossfades (boost::shared_ptr c, vector > & results) +{ + for (list >::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { + if ((*i)->equivalent (c)) { + results.push_back (*i); + } + } +}