X-Git-Url: https://main.carlh.net/gitweb/?p=ardour.git;a=blobdiff_plain;f=libs%2Fardour%2Faudio_playlist.cc;h=b00252df7412de461b073557d4bc974462ebde2f;hp=1506d204f15f6400d49e6db7f5811780ce051455;hb=c8c6bca6587450ff64303dbc994a4cd28d6ce7aa;hpb=bb457bb960c5bd7ed538f9d31477293415739f68 diff --git a/libs/ardour/audio_playlist.cc b/libs/ardour/audio_playlist.cc index 1506d204f1..b00252df74 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 @@ -21,33 +21,36 @@ #include -#include +#include "ardour/types.h" +#include "ardour/debug.h" +#include "ardour/audioplaylist.h" +#include "ardour/audioregion.h" +#include "ardour/region_sorters.h" +#include "ardour/session.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include "i18n.h" +#include "pbd/i18n.h" using namespace ARDOUR; -using namespace sigc; using namespace std; using namespace PBD; AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden) : Playlist (session, node, DataType::AUDIO, hidden) { - const XMLProperty* prop = node.property("type"); +#ifndef NDEBUG + XMLProperty const * prop = node.property("type"); assert(!prop || DataType(prop->value()) == DataType::AUDIO); +#endif in_set_state++; - set_state (node); + 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) @@ -58,80 +61,116 @@ AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden) AudioPlaylist::AudioPlaylist (boost::shared_ptr other, string name, bool hidden) : Playlist (other, name, hidden) { - 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, 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) { - /* this constructor does NOT notify others (session) */ -} + RegionReadLock rlock2 (const_cast (other.get())); + in_set_state++; -AudioPlaylist::~AudioPlaylist () -{ - GoingAway (); /* EMIT SIGNAL */ + 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 Evoral::OverlapNone: + continue; + + case Evoral::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 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) + fade_out = region->fade_out()->back()->when - ( region->last_frame() - end ); //end is inside the fadeout, preserve the fades endpoint + break; + } + + 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; + + 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 Evoral::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; + } - /* drop connections to signals */ + in_set_state--; - notify_callbacks (); - - _crossfades.clear (); + /* this constructor does NOT notify others (session) */ } -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(); } }; -ARDOUR::nframes_t -AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nframes_t start, - nframes_t cnt, unsigned chan_n) +/** 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) { - nframes_t ret = cnt; - nframes_t end; - nframes_t read_frames; - nframes_t skip_frames; + 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 + 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. @@ -143,646 +182,395 @@ 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); - - end = start + cnt - 1; - read_frames = 0; - skip_frames = 0; - _read_data_count = 0; - - _read_data_count = 0; + Playlist::RegionReadLock rl (this); - RegionList* rlist = regions_to_read (start, start+cnt); + /* 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 ()); - if (rlist->empty()) { - delete rlist; - return cnt; - } + /* 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; - map > > relevant_regions; - map > > relevant_xfades; - vector relevant_layers; + /* This will be a list of the bits of regions that we need to read */ + list to_do; - 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()); - } - } + /* 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); - for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { - if ((*i)->coverage (start, end) != OverlapNone) { - relevant_xfades[(*i)->upper_layer()].push_back (*i); - } - } + /* muted regions don't figure into it at all */ + if ( ar->muted() ) + continue; -// RegionSortByLayer layer_cmp; -// relevant_regions.sort (layer_cmp); + /* 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); - /* 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. - */ + /* ... and then remove the bits that are already done */ - sort (relevant_layers.begin(), relevant_layers.end()); + Evoral::RangeList region_to_do = Evoral::subtract (region_range, done); - for (vector::iterator l = relevant_layers.begin(); l != relevant_layers.end(); ++l) { + /* 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. + */ - vector > r (relevant_regions[*l]); - vector >& x (relevant_xfades[*l]); + Evoral::RangeList::List t = region_to_do.get (); - for (vector >::iterator i = r.begin(); i != r.end(); ++i) { - boost::shared_ptr ar = boost::dynamic_pointer_cast(*i); - 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(); - } - - for (vector >::iterator i = x.begin(); i != x.end(); ++i) { - (*i)->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n); + for (Evoral::RangeList::List::iterator j = t.begin(); j != t.end(); ++j) { + Evoral::Range d = *j; + to_do.push_back (Segment (ar, d)); - /* 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. - */ + 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); + } + } } } - 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; + /* 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); } - for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ) { - - if ((*i)->involves (r)) { - i = _crossfades.erase (i); - } else { - ++i; - } - } + return cnt; } - void -AudioPlaylist::flush_notifications () +AudioPlaylist::dump () const { - Playlist::flush_notifications(); - - if (in_flush) { - return; - } + boost::shared_ptrr; - in_flush = true; + cerr << "Playlist \"" << _name << "\" " << endl + << regions.size() << " regions " + << endl; - Crossfades::iterator a; - for (a = _pending_xfade_adds.begin(); a != _pending_xfade_adds.end(); ++a) { - NewCrossfade (*a); /* EMIT SIGNAL */ + for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) { + r = *i; + cerr << " " << r->name() << " @ " << r << " [" + << r->start() << "+" << r->length() + << "] at " + << r->position() + << " on layer " + << r->layer () + << endl; } - - _pending_xfade_adds.clear (); - - in_flush = false; } -void -AudioPlaylist::refresh_dependents (boost::shared_ptr r) +bool +AudioPlaylist::destroy_region (boost::shared_ptr region) { - boost::shared_ptr ar = boost::dynamic_pointer_cast(r); - set > updated; - - if (ar == 0) { - return; - } + boost::shared_ptr r = boost::dynamic_pointer_cast (region); - for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) { + if (!r) { + return false; + } - Crossfades::iterator tmp; - - tmp = x; - ++tmp; + bool changed = false; - /* only update them once */ + { + RegionWriteLock rlock (this); - if ((*x)->involves (ar)) { + for (RegionList::iterator i = regions.begin(); i != regions.end(); ) { - 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 (); - } + RegionList::iterator tmp = i; + ++tmp; - catch (Crossfade::NoCrossfadeHere& err) { - // relax, Invalidated during refresh - } + if ((*i) == region) { + regions.erase (i); + changed = true; } + + i = tmp; } - x = tmp; - } -} + for (set >::iterator x = all_regions.begin(); x != all_regions.end(); ) { -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)); + set >::iterator xtmp = x; + ++xtmp; + + if ((*x) == region) { + all_regions.erase (x); + changed = true; } - } - - 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; - - if (in_set_state || in_partition) { - return; - } + x = xtmp; + } - if ((region = boost::dynamic_pointer_cast (r)) == 0) { - fatal << _("programming error: non-audio Region tested for overlap in audio playlist") - << endmsg; - return; + region->set_playlist (boost::shared_ptr()); } - if (!norefresh) { - refresh_dependents (r); + if (changed) { + /* overload this, it normally means "removed", not destroyed */ + notify_region_removed (region); } + return changed; +} - if (!Config->get_auto_xfade()) { - return; +bool +AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr region) +{ + if (in_flush || in_set_state) { + return false; } - for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { - - nframes_t xfade_length; + PropertyChange bounds; + bounds.add (Properties::start); + bounds.add (Properties::position); + bounds.add (Properties::length); - other = boost::dynamic_pointer_cast (*i); + PropertyChange our_interests; - if (other == region) { - continue; - } - - if (other->muted() || region->muted()) { - continue; - } - - - if (other->layer() < region->layer()) { - top = region; - bottom = other; - } else { - top = other; - bottom = region; - } + 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); - if (!top->opaque()) { - continue; - } - - OverlapType c = top->coverage (bottom->position(), bottom->last_frame()); - - try { - 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 ((nframes_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)); - add_crossfade (xfade); - } + bool parent_wants_notify; - if (top_region_at (top->last_frame() - 1) == top) { + parent_wants_notify = Playlist::region_changed (what_changed, region); + /* 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 (); + } - /* - 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, top->last_frame() - xfade_length, EndOfOut)); - add_crossfade (xfade); - } - break; - case OverlapStart: + return true; +} - /* { ==== top ============ } - * [---- bottom -------------------] - */ +void +AudioPlaylist::pre_combine (vector >& copies) +{ + RegionSortByPosition cmp; + boost::shared_ptr ar; - if (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, Config->get_xfade_model(), Config->get_xfades_active())); - add_crossfade (xfade); - } - } else { - - touched_regions = regions_touched (top->first_frame(), - top->first_frame() + min ((nframes_t)Config->get_short_xfade_seconds() * _session.frame_rate(), - top->length())); - if (touched_regions->size() <= 2) { - xfade = boost::shared_ptr (new Crossfade (region, other, Config->get_xfade_model(), Config->get_xfades_active())); - add_crossfade (xfade); - } - } - break; - case OverlapEnd: + sort (copies.begin(), copies.end(), cmp); + ar = boost::dynamic_pointer_cast (copies.front()); - /* [---- top ------------------------] - * { ==== bottom ============ } - */ + /* disable fade in of the first region */ - if (Config->get_xfade_model() == FullCrossfade) { + if (ar) { + ar->set_fade_in_active (false); + } - touched_regions = regions_touched (bottom->first_frame(), top->last_frame()); - if (touched_regions->size() <= 2) { - xfade = boost::shared_ptr (new Crossfade (region, other, - Config->get_xfade_model(), Config->get_xfades_active())); - add_crossfade (xfade); - } + ar = boost::dynamic_pointer_cast (copies.back()); - } else { - touched_regions = regions_touched (bottom->first_frame(), - bottom->first_frame() + min ((nframes_t)Config->get_short_xfade_seconds() * _session.frame_rate(), - bottom->length())); - if (touched_regions->size() <= 2) { - xfade = boost::shared_ptr (new Crossfade (region, other, Config->get_xfade_model(), Config->get_xfades_active())); - add_crossfade (xfade); - } - } - break; - default: - xfade = boost::shared_ptr (new Crossfade (region, other, - Config->get_xfade_model(), Config->get_xfades_active())); - add_crossfade (xfade); - } - } + /* disable fade out of the last region */ - catch (failed_constructor& err) { - continue; - } - - catch (Crossfade::NoCrossfadeHere& err) { - continue; - } - + if (ar) { + ar->set_fade_out_active (false); } } void -AudioPlaylist::add_crossfade (boost::shared_ptr xfade) +AudioPlaylist::post_combine (vector >& originals, boost::shared_ptr compound_region) { - Crossfades::iterator ci; + RegionSortByPosition cmp; + boost::shared_ptr ar; + boost::shared_ptr cr; - for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) { - if (*(*ci) == *xfade) { // Crossfade::operator==() - break; - } + if ((cr = boost::dynamic_pointer_cast (compound_region)) == 0) { + return; } - - if (ci != _crossfades.end()) { - // it will just go away - } else { - _crossfades.push_back (xfade); - xfade->Invalidated.connect (mem_fun (*this, &AudioPlaylist::crossfade_invalidated)); - xfade->StateChanged.connect (mem_fun (*this, &AudioPlaylist::crossfade_changed)); + sort (originals.begin(), originals.end(), cmp); + + ar = boost::dynamic_pointer_cast (originals.front()); + + /* copy the fade in of the first into the compound region */ - notify_crossfade_added (xfade); + if (ar) { + cr->set_fade_in (ar->fade_in()); } -} - -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 */ + 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::crossfade_invalidated (boost::shared_ptr r) +AudioPlaylist::pre_uncombine (vector >& originals, boost::shared_ptr compound_region) { - Crossfades::iterator i; - boost::shared_ptr xfade = boost::dynamic_pointer_cast (r); - - xfade->in()->resume_fade_in (); - xfade->out()->resume_fade_out (); + RegionSortByPosition cmp; + boost::shared_ptr ar; + boost::shared_ptr cr = boost::dynamic_pointer_cast(compound_region); - if ((i = find (_crossfades.begin(), _crossfades.end(), xfade)) != _crossfades.end()) { - _crossfades.erase (i); + if (!cr) { + return; } -} -int -AudioPlaylist::set_state (const XMLNode& node) -{ - XMLNode *child; - XMLNodeList nlist; - XMLNodeConstIterator niter; + sort (originals.begin(), originals.end(), cmp); - in_set_state++; - freeze (); + /* no need to call clear_changes() on the originals because that is + * done within Playlist::uncombine () + */ - Playlist::set_state (node); + for (vector >::iterator i = originals.begin(); i != originals.end(); ++i) { - nlist = node.children(); + if ((ar = boost::dynamic_pointer_cast (*i)) == 0) { + continue; + } - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { + /* scale the uncombined regions by any gain setting for the + * compound one. + */ - child = *niter; + ar->set_scale_amplitude (ar->scale_amplitude() * cr->scale_amplitude()); - if (child->name() != "Crossfade") { - continue; - } + if (i == originals.begin()) { - try { - 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)); - NewCrossfade(xfade); - } - - catch (failed_constructor& err) { - // cout << string_compose (_("could not create crossfade object in playlist %1"), - // _name) - // << endl; - continue; - } - } + /* copy the compound region's fade in back into the first + original region. + */ - thaw (); - in_set_state--; + 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()); + } - return 0; -} -void -AudioPlaylist::clear (bool with_signals) -{ - _crossfades.clear (); - Playlist::clear (with_signals); -} + } else if (*i == originals.back()) { -XMLNode& -AudioPlaylist::state (bool full_state) -{ - XMLNode& node = Playlist::state (full_state); + /* 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()); + } - if (full_state) { - for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { - node.add_child_nocopy ((*i)->get_state()); } + + _session.add_command (new StatefulDiffCommand (*i)); } - - return node; } -void -AudioPlaylist::dump () const +int +AudioPlaylist::set_state (const XMLNode& node, int version) { - 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) { - r = *i; - cerr << " " << r->name() << " @ " << r << " [" - << r->start() << "+" << r->length() - << "] at " - << r->position() - << " on layer " - << 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; - } + return Playlist::set_state (node, version); } -bool -AudioPlaylist::destroy_region (boost::shared_ptr region) +void +AudioPlaylist::load_legacy_crossfades (const XMLNode& node, int version) { - boost::shared_ptr r = boost::dynamic_pointer_cast (region); - bool changed = false; - Crossfades::iterator c, ctmp; - set > unique_xfades; + /* Read legacy Crossfade nodes and set up region fades accordingly */ - if (r == 0) { - fatal << _("programming error: non-audio Region passed to remove_overlap in audio playlist") - << endmsg; - /*NOTREACHED*/ - return false; - } + XMLNodeList children = node.children (); + for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) { + if ((*i)->name() == X_("Crossfade")) { - { - RegionLock rlock (this); + XMLProperty const * p = (*i)->property (X_("active")); + assert (p); - for (RegionList::iterator i = regions.begin(); i != regions.end(); ) { - - RegionList::iterator tmp = i; - ++tmp; - - if ((*i) == region) { - regions.erase (i); - changed = true; + if (!string_is_affirmative (p->value())) { + continue; } - - i = tmp; - } - for (set >::iterator x = all_regions.begin(); x != all_regions.end(); ) { - - set >::iterator xtmp = x; - ++xtmp; - - if ((*x) == region) { - all_regions.erase (x); - changed = true; + if ((p = (*i)->property (X_("in"))) == 0) { + continue; } - - x = xtmp; - } - region->set_playlist (boost::shared_ptr()); - } + boost::shared_ptr in = region_by_id (PBD::ID (p->value ())); - for (c = _crossfades.begin(); c != _crossfades.end(); ) { - ctmp = c; - ++ctmp; + if (!in) { + warning << string_compose (_("Legacy crossfade involved an incoming region not present in playlist \"%1\" - crossfade discarded"), + name()) + << endmsg; + continue; + } - if ((*c)->involves (r)) { - unique_xfades.insert (*c); - _crossfades.erase (c); - } - - c = ctmp; - } + boost::shared_ptr in_a = boost::dynamic_pointer_cast (in); + assert (in_a); - if (changed) { - /* overload this, it normally means "removed", not destroyed */ - notify_region_removed (region); - } + if ((p = (*i)->property (X_("out"))) == 0) { + continue; + } - return changed; -} + boost::shared_ptr out = region_by_id (PBD::ID (p->value ())); -void -AudioPlaylist::crossfade_changed (Change ignored) -{ - if (in_flush || in_set_state) { - return; - } + if (!out) { + warning << string_compose (_("Legacy crossfade involved an outgoing region not present in playlist \"%1\" - crossfade discarded"), + name()) + << endmsg; + continue; + } - /* 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. - */ + boost::shared_ptr out_a = boost::dynamic_pointer_cast (out); + assert (out_a); - notify_modified (); -} + /* now decide whether to add a fade in or fade out + * xfade and to which region + */ -bool -AudioPlaylist::region_changed (Change what_changed, boost::shared_ptr region) -{ - if (in_flush || in_set_state) { - return false; - } + if (in->layer() <= out->layer()) { - Change our_interests = Change (AudioRegion::FadeInChanged| - AudioRegion::FadeOutChanged| - AudioRegion::FadeInActiveChanged| - AudioRegion::FadeOutActiveChanged| - AudioRegion::EnvelopeActiveChanged| - AudioRegion::ScaleAmplitudeChanged| - AudioRegion::EnvelopeChanged); - bool parent_wants_notify; + /* incoming region is below the outgoing one, + * so apply a fade out to the outgoing one + */ - parent_wants_notify = Playlist::region_changed (what_changed, region); + const XMLNodeList c = (*i)->children (); - if ((parent_wants_notify || (what_changed & our_interests))) { - notify_modified (); - } + 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); + } + } - return true; -} + out_a->set_fade_out_active (true); -void -AudioPlaylist::crossfades_at (nframes_t frame, Crossfades& clist) -{ - RegionLock rlock (this); + } else { - for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { - nframes_t start, end; + /* apply a fade in to the incoming region, + * since its above the outgoing one + */ - start = (*i)->position(); - end = start + (*i)->overlap_length(); // not length(), important difference + const XMLNodeList c = (*i)->children (); - if (frame >= start && frame <= end) { - clist.push_back (*i); - } + 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); + } + } } } -