X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Faudio_playlist.cc;h=b00252df7412de461b073557d4bc974462ebde2f;hb=d17f58e5314ad826fc2473c587341facc285399d;hp=be5c9ab5d95c563002bf818bb2802e0c33fa0214;hpb=fe13d08874f08b723df53116e5655c3d229a657e;p=ardour.git diff --git a/libs/ardour/audio_playlist.cc b/libs/ardour/audio_playlist.cc index be5c9ab5d9..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 @@ -15,180 +15,162 @@ 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 "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 "i18n.h" +#include "pbd/i18n.h" using namespace ARDOUR; -using namespace sigc; using namespace std; using namespace PBD; -AudioPlaylist::State::~State () -{ -} - AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden) - : Playlist (session, node, hidden) + : Playlist (session, node, DataType::AUDIO, hidden) { - in_set_state = true; - set_state (node); - in_set_state = false; - - save_state (_("initial state")); +#ifndef NDEBUG + XMLProperty const * prop = node.property("type"); + assert(!prop || DataType(prop->value()) == DataType::AUDIO); +#endif - if (!hidden) { - PlaylistCreated (this); /* EMIT SIGNAL */ + in_set_state++; + if (set_state (node, Stateful::loading_state_version)) { + throw failed_constructor(); } + in_set_state--; + + relayer (); + + load_legacy_crossfades (node, Stateful::loading_state_version); } AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden) - : Playlist (session, name, hidden) + : Playlist (session, name, DataType::AUDIO, hidden) { - save_state (_("initial state")); - - if (!hidden) { - PlaylistCreated (this); /* EMIT SIGNAL */ - } - } -AudioPlaylist::AudioPlaylist (const AudioPlaylist& other, string name, bool hidden) +AudioPlaylist::AudioPlaylist (boost::shared_ptr other, string name, bool hidden) : Playlist (other, name, hidden) { - save_state (_("initial state")); - - list::const_iterator in_o = other.regions.begin(); - list::iterator in_n = regions.begin(); - - while (in_o != other.regions.end()) { - AudioRegion *ar = dynamic_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) { - if ( &(*xfades)->in() == ar) { - // We found one! Now copy it! - - list::const_iterator out_o = other.regions.begin(); - list::const_iterator out_n = regions.begin(); - - while (out_o != other.regions.end()) { - - AudioRegion *ar2 = dynamic_cast( (*out_o) ); - - if ( &(*xfades)->out() == ar2) { - AudioRegion *in = dynamic_cast( (*in_n) ); - AudioRegion *out = dynamic_cast( (*out_n) ); - Crossfade *new_fade = 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++; - } - - if (!hidden) { - PlaylistCreated (this); /* EMIT SIGNAL */ - } } -AudioPlaylist::AudioPlaylist (const AudioPlaylist& other, jack_nframes_t start, jack_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) { - save_state (_("initial state")); + RegionReadLock rlock2 (const_cast (other.get())); + in_set_state++; - /* this constructor does NOT notify others (session) */ -} + framepos_t const end = start + cnt - 1; -AudioPlaylist::~AudioPlaylist () -{ - set all_xfades; - set all_regions; + /* 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. + */ - GoingAway (this); + RegionList::iterator ours = regions.begin (); - /* find every region we've ever used, and add it to the set of - all regions. same for xfades; - */ + for (RegionList::const_iterator i = other->regions.begin(); i != other->regions.end(); ++i) { + boost::shared_ptr region = boost::dynamic_pointer_cast (*i); + assert (region); - for (RegionList::iterator x = regions.begin(); x != regions.end(); ++x) { - all_regions.insert (*x); - } + framecnt_t fade_in = 64; + framecnt_t fade_out = 64; - for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end(); ++x) { - all_xfades.insert (*x); - } + switch (region->coverage (start, end)) { + case Evoral::OverlapNone: + continue; - for (StateMap::iterator i = states.begin(); i != states.end(); ++i) { - - AudioPlaylist::State* apstate = dynamic_cast (*i); + 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; + } - for (RegionList::iterator r = apstate->regions.begin(); r != apstate->regions.end(); ++r) { - all_regions.insert (*r); + 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; } - for (Crossfades::iterator xf = apstate->crossfades.begin(); xf != apstate->crossfades.end(); ++xf) { - all_xfades.insert (*xf); + + 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; } - delete apstate; - } + case Evoral::OverlapExternal: + fade_in = region->fade_in()->back()->when; + fade_out = region->fade_out()->back()->when; + break; + } - /* delete every region */ + boost::shared_ptr our_region = boost::dynamic_pointer_cast (*ours); + assert (our_region); - for (set::iterator ar = all_regions.begin(); ar != all_regions.end(); ++ar) { - (*ar)->unlock_sources (); - delete *ar; + our_region->set_fade_in_length (fade_in); + our_region->set_fade_out_length (fade_out); + ++ours; } - /* delete every crossfade */ + in_set_state--; - for (set::iterator axf = all_xfades.begin(); axf != all_xfades.end(); ++axf) { - delete *axf; - } + /* this constructor does NOT notify others (session) */ } -struct RegionSortByLayer { - bool operator() (Region *a, Region *b) { - return a->layer() < b->layer(); +/** Sort by descending layer and then by ascending position */ +struct ReadSorter { + bool operator() (boost::shared_ptr a, boost::shared_ptr b) { + if (a->layer() != b->layer()) { + return a->layer() > b->layer(); + } + + return a->position() < b->position(); } }; -jack_nframes_t -AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, char * workbuf, jack_nframes_t start, - jack_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) { - jack_nframes_t ret = cnt; - jack_nframes_t end; - jack_nframes_t read_frames; - jack_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. @@ -200,760 +182,395 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, ch 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); + Playlist::RegionReadLock rl (this); - end = start + cnt - 1; + /* 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 ()); - read_frames = 0; - skip_frames = 0; - _read_data_count = 0; + /* 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 = regions.begin(); i != regions.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. + */ - // FIXME: Should be vector - 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) { - AudioRegion* const ar = dynamic_cast(*i); - assert(ar); - ar->read_at (buf, mixdown_buffer, gain_buffer, workbuf, 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, workbuf, 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); + } + } } } - return ret; -} - - -void -AudioPlaylist::remove_dependents (Region& region) -{ - Crossfades::iterator i, tmp; - AudioRegion* r = dynamic_cast (®ion); - - 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 (i = _crossfades.begin(); i != _crossfades.end(); ) { - tmp = i; - tmp++; - - if ((*i)->involves (*r)) { - /* do not delete crossfades */ - _crossfades.erase (i); - } - - i = tmp; - } + return cnt; } - void -AudioPlaylist::flush_notifications () +AudioPlaylist::dump () const { - Playlist::flush_notifications(); + boost::shared_ptrr; - if (in_flush) { - return; - } - - 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 (Region& r) +bool +AudioPlaylist::destroy_region (boost::shared_ptr region) { - AudioRegion* ar = dynamic_cast(&r); - set updated; + boost::shared_ptr r = boost::dynamic_pointer_cast (region); - if (ar == 0) { - return; - } + if (!r) { + return false; + } - for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) { + bool changed = false; - Crossfades::iterator tmp; - - tmp = x; - ++tmp; + { + RegionWriteLock rlock (this); - /* only update them once */ + for (RegionList::iterator i = regions.begin(); i != regions.end(); ) { - if ((*x)->involves (*ar)) { + RegionList::iterator tmp = i; + ++tmp; - if (find (updated.begin(), updated.end(), *x) == updated.end()) { - if ((*x)->refresh ()) { - /* not invalidated by the refresh */ - updated.insert (*x); - } + 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 (Region *o, Region *l, Region *r) -{ - AudioRegion *orig = dynamic_cast(o); - AudioRegion *left = dynamic_cast(l); - AudioRegion *right = dynamic_cast(r); - - for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) { - Crossfades::iterator tmp; - tmp = x; - ++tmp; - - Crossfade *fade = 0; - - if ((*x)->_in == orig) { - if (! (*x)->covers(right->position())) { - fade = 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); - } - } - - if ((*x)->_out == orig) { - if (! (*x)->covers(right->position())) { - fade = 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); + 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 (Region& r, bool norefresh) -{ - AudioRegion* other; - AudioRegion* region; - AudioRegion* top; - AudioRegion* bottom; - Crossfade* xfade; + x = xtmp; + } - if (in_set_state || in_partition) { - return; + region->set_playlist (boost::shared_ptr()); } - if ((region = dynamic_cast (&r)) == 0) { - fatal << _("programming error: non-audio Region tested for overlap in audio playlist") - << endmsg; - return; + if (changed) { + /* overload this, it normally means "removed", not destroyed */ + notify_region_removed (region); } - if (!norefresh) { - refresh_dependents (r); - } + 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) { + PropertyChange bounds; + bounds.add (Properties::start); + bounds.add (Properties::position); + bounds.add (Properties::length); - other = dynamic_cast (*i); + PropertyChange our_interests; - if (other == region) { - continue; - } + 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 (other->muted() || region->muted()) { - continue; - } - - if (other->layer() < region->layer()) { - top = region; - bottom = other; - } else { - top = other; - bottom = region; - } + bool parent_wants_notify; - 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. - */ - - jack_nframes_t xfade_length = min ((jack_nframes_t) 720, top->length()); - - /* in, out */ - xfade = new Crossfade (*top, *bottom, xfade_length, top->first_frame(), StartOfIn); - add_crossfade (*xfade); - xfade = new Crossfade (*bottom, *top, xfade_length, top->last_frame() - xfade_length, EndOfOut); - add_crossfade (*xfade); - - } else { - - xfade = new Crossfade (*other, *region, _session.get_xfade_model(), _session.get_crossfades_active()); - add_crossfade (*xfade); - } - } - } - - catch (failed_constructor& err) { - continue; - } - - catch (Crossfade::NoCrossfadeHere& err) { - continue; - } - + 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 (); } + + return true; } void -AudioPlaylist::add_crossfade (Crossfade& xfade) +AudioPlaylist::pre_combine (vector >& copies) { - Crossfades::iterator ci; + RegionSortByPosition cmp; + boost::shared_ptr ar; - for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) { - if (*(*ci) == xfade) { // Crossfade::operator==() - break; - } - } - - if (ci != _crossfades.end()) { - delete &xfade; - } else { - _crossfades.push_back (&xfade); + sort (copies.begin(), copies.end(), cmp); - xfade.Invalidated.connect (mem_fun (*this, &AudioPlaylist::crossfade_invalidated)); - xfade.StateChanged.connect (mem_fun (*this, &AudioPlaylist::crossfade_changed)); + ar = boost::dynamic_pointer_cast (copies.front()); - notify_crossfade_added (&xfade); - } -} - -void AudioPlaylist::notify_crossfade_added (Crossfade *x) -{ - if (g_atomic_int_get(&block_notifications)) { - _pending_xfade_adds.insert (_pending_xfade_adds.end(), x); - } else { - NewCrossfade (x); /* EMIT SIGNAL */ + /* disable fade in of the first region */ + + if (ar) { + ar->set_fade_in_active (false); } -} -void -AudioPlaylist::crossfade_invalidated (Crossfade* xfade) -{ - Crossfades::iterator i; + ar = boost::dynamic_pointer_cast (copies.back()); - xfade->in().resume_fade_in (); - xfade->out().resume_fade_out (); + /* disable fade out of the last region */ - if ((i = find (_crossfades.begin(), _crossfades.end(), xfade)) != _crossfades.end()) { - _crossfades.erase (i); + if (ar) { + ar->set_fade_out_active (false); } } -int -AudioPlaylist::set_state (const XMLNode& node) +void +AudioPlaylist::post_combine (vector >& originals, boost::shared_ptr compound_region) { - XMLNode *child; - XMLNodeList nlist; - XMLNodeConstIterator niter; + RegionSortByPosition cmp; + boost::shared_ptr ar; + boost::shared_ptr cr; - if (!in_set_state) { - Playlist::set_state (node); + if ((cr = boost::dynamic_pointer_cast (compound_region)) == 0) { + return; } - nlist = node.children(); - - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - - child = *niter; - - if (child->name() == "Crossfade") { + sort (originals.begin(), originals.end(), cmp); - Crossfade *xfade; - - try { - xfade = new Crossfade (*((const Playlist *)this), *child); - } + ar = boost::dynamic_pointer_cast (originals.front()); - catch (failed_constructor& err) { - // cout << string_compose (_("could not create crossfade object in playlist %1"), - // _name) - // << endl; - continue; - } + /* copy the fade in of the first into the compound region */ - Crossfades::iterator ci; - - for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) { - if (*(*ci) == *xfade) { - break; - } - } + if (ar) { + cr->set_fade_in (ar->fade_in()); + } - if (ci == _crossfades.end()) { - _crossfades.push_back (xfade); - xfade->Invalidated.connect (mem_fun (*this, &AudioPlaylist::crossfade_invalidated)); - xfade->StateChanged.connect (mem_fun (*this, &AudioPlaylist::crossfade_changed)); - /* no need to notify here */ - } else { - delete xfade; - } - } + 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()); } - - return 0; } void -AudioPlaylist::drop_all_states () +AudioPlaylist::pre_uncombine (vector >& originals, boost::shared_ptr compound_region) { - set all_xfades; - set all_regions; - - /* find every region we've ever used, and add it to the set of - all regions. same for xfades; - */ + RegionSortByPosition cmp; + boost::shared_ptr ar; + boost::shared_ptr cr = boost::dynamic_pointer_cast(compound_region); - for (StateMap::iterator i = states.begin(); i != states.end(); ++i) { - - AudioPlaylist::State* apstate = dynamic_cast (*i); - - for (RegionList::iterator r = apstate->regions.begin(); r != apstate->regions.end(); ++r) { - all_regions.insert (*r); - } - for (Crossfades::iterator xf = apstate->crossfades.begin(); xf != apstate->crossfades.end(); ++xf) { - all_xfades.insert (*xf); - } + if (!cr) { + return; } - /* now remove from the "all" lists every region that is in the current list. */ + sort (originals.begin(), originals.end(), cmp); - for (list::iterator i = regions.begin(); i != regions.end(); ++i) { - set::iterator x = all_regions.find (*i); - if (x != all_regions.end()) { - all_regions.erase (x); - } - } + /* no need to call clear_changes() on the originals because that is + * done within Playlist::uncombine () + */ - /* ditto for every crossfade */ + for (vector >::iterator i = originals.begin(); i != originals.end(); ++i) { - for (list::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { - set::iterator x = all_xfades.find (*i); - if (x != all_xfades.end()) { - all_xfades.erase (x); + if ((ar = boost::dynamic_pointer_cast (*i)) == 0) { + continue; } - } - - /* delete every region that is left - these are all things that are part of our "history" */ - - for (set::iterator ar = all_regions.begin(); ar != all_regions.end(); ++ar) { - (*ar)->unlock_sources (); - delete *ar; - } - - /* delete every crossfade that is left (ditto as per regions) */ - for (set::iterator axf = all_xfades.begin(); axf != all_xfades.end(); ++axf) { - delete *axf; - } + /* scale the uncombined regions by any gain setting for the + * compound one. + */ - /* Now do the generic thing ... */ + ar->set_scale_amplitude (ar->scale_amplitude() * cr->scale_amplitude()); - StateManager::drop_all_states (); -} + if (i == originals.begin()) { -StateManager::State* -AudioPlaylist::state_factory (std::string why) const -{ - State* state = new State (why); - - state->regions = regions; - state->region_states.clear (); - for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) { - state->region_states.push_back ((*i)->get_memento()); - } + /* copy the compound region's fade in back into the first + original region. + */ - state->crossfades = _crossfades; - state->crossfade_states.clear (); - for (Crossfades::const_iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { - state->crossfade_states.push_back ((*i)->get_memento()); - } - return 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()); + } -Change -AudioPlaylist::restore_state (StateManager::State& state) -{ - { - RegionLock rlock (this); - State* apstate = dynamic_cast (&state); - in_set_state = true; + } else if (*i == originals.back()) { - regions = apstate->regions; + /* copy the compound region's fade out back into the last + original region. + */ - for (list::iterator s = apstate->region_states.begin(); s != apstate->region_states.end(); ++s) { - (*s) (); - } + 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()); + } - _crossfades = apstate->crossfades; - - for (list::iterator s = apstate->crossfade_states.begin(); s != apstate->crossfade_states.end(); ++s) { - (*s) (); } - in_set_state = false; + _session.add_command (new StatefulDiffCommand (*i)); } - - notify_length_changed (); - return Change (~0); } -UndoAction -AudioPlaylist::get_memento () const -{ - return sigc::bind (mem_fun (*(const_cast (this)), &StateManager::use_state), _current_state_id); -} - -void -AudioPlaylist::clear (bool with_delete, bool with_save) -{ - if (with_delete) { - for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { - delete *i; - } - } - - _crossfades.clear (); - - Playlist::clear (with_delete, with_save); -} - -XMLNode& -AudioPlaylist::state (bool full_state) +int +AudioPlaylist::set_state (const XMLNode& node, int version) { - XMLNode& node = Playlist::state (full_state); - - if (full_state) { - for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { - node.add_child_nocopy ((*i)->get_state()); - } - } - - return node; + return Playlist::set_state (node, version); } void -AudioPlaylist::dump () const -{ - Region *r; - Crossfade *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; - } -} - -bool -AudioPlaylist::destroy_region (Region* region) +AudioPlaylist::load_legacy_crossfades (const XMLNode& node, int version) { - AudioRegion* r = dynamic_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); - RegionList::iterator i; - RegionList::iterator tmp; + XMLProperty const * p = (*i)->property (X_("active")); + assert (p); - for (i = regions.begin(); i != regions.end(); ) { - - tmp = i; - ++tmp; - - if ((*i) == region) { - (*i)->unlock_sources (); - regions.erase (i); - changed = true; + if (!string_is_affirmative (p->value())) { + continue; } - - i = tmp; - } - } - - for (c = _crossfades.begin(); c != _crossfades.end(); ) { - ctmp = c; - ++ctmp; - if ((*c)->involves (*r)) { - unique_xfades.insert (*c); - _crossfades.erase (c); - } - - c = ctmp; - } - - for (StateMap::iterator s = states.begin(); s != states.end(); ) { - StateMap::iterator tmp; - - tmp = s; - ++tmp; - - State* astate = dynamic_cast (*s); - - for (c = astate->crossfades.begin(); c != astate->crossfades.end(); ) { - - ctmp = c; - ++ctmp; - - if ((*c)->involves (*r)) { - unique_xfades.insert (*c); - _crossfades.erase (c); + if ((p = (*i)->property (X_("in"))) == 0) { + continue; } - c = ctmp; - } - - list::iterator rsi, rsitmp; - RegionList::iterator ri, ritmp; + boost::shared_ptr in = region_by_id (PBD::ID (p->value ())); - for (ri = astate->regions.begin(), rsi = astate->region_states.begin(); - ri != astate->regions.end() && rsi != astate->region_states.end();) { + if (!in) { + warning << string_compose (_("Legacy crossfade involved an incoming region not present in playlist \"%1\" - crossfade discarded"), + name()) + << endmsg; + continue; + } + boost::shared_ptr in_a = boost::dynamic_pointer_cast (in); + assert (in_a); - ritmp = ri; - ++ritmp; + if ((p = (*i)->property (X_("out"))) == 0) { + continue; + } - rsitmp = rsi; - ++rsitmp; + boost::shared_ptr out = region_by_id (PBD::ID (p->value ())); - if (region == (*ri)) { - astate->regions.erase (ri); - astate->region_states.erase (rsi); + if (!out) { + warning << string_compose (_("Legacy crossfade involved an outgoing region not present in playlist \"%1\" - crossfade discarded"), + name()) + << endmsg; + continue; } - ri = ritmp; - rsi = rsitmp; - } - - s = tmp; - } + boost::shared_ptr out_a = boost::dynamic_pointer_cast (out); + assert (out_a); - for (set::iterator c = unique_xfades.begin(); c != unique_xfades.end(); ++c) { - delete *c; - } + /* now decide whether to add a fade in or fade out + * xfade and to which region + */ - if (changed) { - /* overload this, it normally means "removed", not destroyed */ - notify_region_removed (region); - } + if (in->layer() <= out->layer()) { - return changed; -} + /* incoming region is below the outgoing one, + * so apply a fade out to the outgoing one + */ -void -AudioPlaylist::crossfade_changed (Change ignored) -{ - if (in_flush || in_set_state) { - return; - } + const XMLNodeList c = (*i)->children (); - /* 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. - */ + 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); + } + } - maybe_save_state (_("xfade change")); + out_a->set_fade_out_active (true); - notify_modified (); -} + } else { -void -AudioPlaylist::get_equivalent_regions (const AudioRegion& other, vector& results) -{ - for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { + /* apply a fade in to the incoming region, + * since its above the outgoing one + */ - AudioRegion* ar = dynamic_cast (*i); + const XMLNodeList c = (*i)->children (); - if (ar) { - if (Config->get_use_overlap_equivalency()) { - if (ar->overlap_equivalent (other)) { - results.push_back (ar); - } else if (ar->equivalent (other)) { - results.push_back (ar); + 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); + } } - } - } - } -} -void -AudioPlaylist::get_region_list_equivalent_regions (const AudioRegion& other, vector& results) -{ - for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { - - AudioRegion* ar = dynamic_cast (*i); - - if (ar && ar->region_list_equivalent (other)) { - results.push_back (ar); + in_a->set_fade_in_active (true); + } } } } - -bool -AudioPlaylist::region_changed (Change what_changed, Region* 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); - bool parent_wants_notify; - - parent_wants_notify = Playlist::region_changed (what_changed, region); - - maybe_save_state (_("region modified")); - - if ((parent_wants_notify || (what_changed & our_interests))) { - notify_modified (); - } - - return true; -} - -void -AudioPlaylist::crossfades_at (jack_nframes_t frame, Crossfades& clist) -{ - RegionLock rlock (this); - - for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { - jack_nframes_t start, end; - - start = (*i)->position(); - end = start + (*i)->overlap_length(); // not length(), important difference - - if (frame >= start && frame <= end) { - clist.push_back (*i); - } - } -}