/* 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 the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "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, hidden) { in_set_state++; set_state (node); in_set_state--; } AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden) : Playlist (session, name, 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) : Playlist (other, start, cnt, name, hidden) { clear(); //we need to remove any regions that the parent constructor added RegionLock rlock2 (const_cast (other.get())); nframes_t end = start + cnt - 1; Playlist::init (hidden); in_set_state++; //now re-add the regions with the correct fade in/out for (RegionList::const_iterator i = other->regions.begin(); i != other->regions.end(); i++) { boost::shared_ptr region = boost::dynamic_pointer_cast (*i); boost::shared_ptr new_region; nframes64_t offset = 0; nframes64_t trim = 0; nframes64_t position = 0; nframes64_t len = 0; string new_name; OverlapType overlap; nframes_t fade_in_len = 64; nframes_t fade_out_len = 64; overlap = region->coverage (start, end+1); switch (overlap) { case OverlapNone: continue; case OverlapInternal: offset = start - region->position(); trim = region->last_frame() -end; position = 0; len = cnt; if (region->fade_in().back()->when > offset) fade_in_len = region->fade_in().back()->when - offset; if (region->fade_out().back()->when > trim) fade_out_len = region->fade_out().back()->when - trim; break; case OverlapStart: offset = 0; position = region->position() - start; len = end - region->position(); if (region->fade_in().back()->when > offset) fade_in_len = region->fade_in().back()->when - offset; if (start > region->last_frame() - region->fade_out().back()->when) fade_out_len = region->last_frame() - start; break; case OverlapEnd: offset = start - region->position(); trim = region->last_frame() -end; position = 0; len = region->length() - offset; if (region->fade_in().back()->when > offset) fade_in_len = region->fade_in().back()->when - offset; if (start > region->last_frame() - region->fade_out().back()->when) fade_out_len = region->last_frame() - start; else fade_out_len = region->fade_out().back()->when; break; case OverlapExternal: offset = 0; trim = region->last_frame() -end; position = region->position() - start; len = region->length(); fade_in_len = region->fade_in().back()->when; fade_out_len = region->fade_out().back()->when; break; } _session.region_name (new_name, region->name(), false); new_region = boost::dynamic_pointer_cast (RegionFactory::RegionFactory::create (region, offset, len, new_name, region->layer(), region->flags())); new_region->set_fade_in_length(fade_in_len > 64 ? fade_in_len : 64); new_region->set_fade_out_length(fade_out_len > 64 ? fade_out_len : 64); add_region_internal (new_region, position); } in_set_state--; first_set_state = false; /* this constructor does NOT notify others (session) */ } AudioPlaylist::~AudioPlaylist () { GoingAway (); /* EMIT SIGNAL */ /* drop connections to signals */ notify_callbacks (); _crossfades.clear (); } struct RegionSortByLayer { bool operator() (boost::shared_ptra, boost::shared_ptrb) { return a->layer() < b->layer(); } }; nframes_t AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nframes_t start, nframes_t cnt, unsigned chan_n) { nframes_t ret = cnt; nframes_t end; nframes_t read_frames; nframes_t skip_frames; /* optimizing this memset() away involves a lot of conditionals 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. one way or another, parts of the requested area that are not written to by Region::region_at() for all Regions that cover the area need to be zeroed. */ memset (buf, 0, sizeof (Sample) * cnt); /* 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; RegionList* rlist = regions_to_read (start, start+cnt); if (rlist->empty()) { delete rlist; return cnt; } map > > relevant_regions; map > > relevant_xfades; vector relevant_layers; 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()); } } for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) { if ((*i)->coverage (start, end) != OverlapNone) { relevant_xfades[(*i)->upper_layer()].push_back (*i); } } // RegionSortByLayer layer_cmp; // relevant_regions.sort (layer_cmp); /* 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. */ sort (relevant_layers.begin(), relevant_layers.end()); for (vector::iterator l = relevant_layers.begin(); l != relevant_layers.end(); ++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); 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); /* 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; } 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; } for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ) { if ((*i)->involves (r)) { i = _crossfades.erase (i); } else { ++i; } } } void AudioPlaylist::flush_notifications () { Playlist::flush_notifications(); if (in_flush) { return; } in_flush = true; Crossfades::iterator a; for (a = _pending_xfade_adds.begin(); a != _pending_xfade_adds.end(); ++a) { NewCrossfade (*a); /* EMIT SIGNAL */ } _pending_xfade_adds.clear (); in_flush = false; } void AudioPlaylist::refresh_dependents (boost::shared_ptr r) { boost::shared_ptr ar = boost::dynamic_pointer_cast(r); set > updated; if (ar == 0) { return; } for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) { Crossfades::iterator tmp; tmp = x; ++tmp; /* only update them once */ if ((*x)->involves (ar)) { pair >::iterator, bool> const u = updated.insert (*x); if (u.second) { /* x was succesfully inserted into the set, so it has not already been updated */ try { (*x)->refresh (); } catch (Crossfade::NoCrossfadeHere& err) { // relax, Invalidated during refresh } } } x = tmp; } } 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)); } } 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; if (in_set_state || in_partition) { return; } if ((region = boost::dynamic_pointer_cast (r)) == 0) { fatal << _("programming error: non-audio Region tested for overlap in audio playlist") << endmsg; return; } if (!norefresh) { refresh_dependents (r); } if (!Config->get_auto_xfade()) { return; } for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { nframes_t xfade_length; other = boost::dynamic_pointer_cast (*i); if (other == region) { continue; } if (other->muted() || region->muted()) { continue; } if (other->layer() < region->layer()) { top = region; bottom = other; } else { top = other; bottom = region; } 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()); xfade = boost::shared_ptr (new Crossfade (top, bottom, xfade_length, top->first_frame(), StartOfIn)); add_crossfade (xfade); if (top_region_at (top->last_frame() - 1) == top) { /* only add a fade out if there is no region on top of the end of 'top' (which would cover it). */ xfade = boost::shared_ptr (new Crossfade (bottom, top, xfade_length, top->last_frame() - xfade_length, EndOfOut)); add_crossfade (xfade); } break; default: xfade = boost::shared_ptr (new Crossfade (region, other, Config->get_xfade_model(), Config->get_xfades_active())); add_crossfade (xfade); } } catch (failed_constructor& err) { continue; } catch (Crossfade::NoCrossfadeHere& err) { continue; } } } void AudioPlaylist::add_crossfade (boost::shared_ptr xfade) { Crossfades::iterator ci; for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) { if (*(*ci) == *xfade) { // Crossfade::operator==() break; } } 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)); notify_crossfade_added (xfade); } } 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 */ } } void AudioPlaylist::crossfade_invalidated (boost::shared_ptr xfade) { Crossfades::iterator i; xfade->in()->resume_fade_in (); xfade->out()->resume_fade_out (); if ((i = find (_crossfades.begin(), _crossfades.end(), xfade)) != _crossfades.end()) { _crossfades.erase (i); } } int AudioPlaylist::set_state (const XMLNode& node) { XMLNode *child; XMLNodeList nlist; XMLNodeConstIterator niter; in_set_state++; freeze (); Playlist::set_state (node); nlist = node.children(); for (niter = nlist.begin(); niter != nlist.end(); ++niter) { child = *niter; if (child->name() != "Crossfade") { continue; } try { boost::shared_ptr xfade = boost::shared_ptr (new Crossfade (*((const Playlist *)this), *child)); _crossfades.push_back (xfade); /* we should not need to do this here, since all values were set via XML. however some older sessions may have bugs and this allows us to fix them. */ xfade->update (); /* we care about these signals ... */ 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; } } thaw (); in_set_state--; return 0; } void AudioPlaylist::clear (bool with_signals) { _crossfades.clear (); Playlist::clear (with_signals); } XMLNode& AudioPlaylist::state (bool full_state) { 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; } void AudioPlaylist::dump () const { 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; } } bool AudioPlaylist::destroy_region (boost::shared_ptr region) { boost::shared_ptr r = boost::dynamic_pointer_cast (region); 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); for (RegionList::iterator i = regions.begin(); i != regions.end(); ) { RegionList::iterator tmp = i; ++tmp; if ((*i) == region) { regions.erase (i); changed = true; } 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; } x = xtmp; } region->set_playlist (boost::shared_ptr()); } for (c = _crossfades.begin(); c != _crossfades.end(); ) { ctmp = c; ++ctmp; if ((*c)->involves (r)) { unique_xfades.insert (*c); _crossfades.erase (c); } c = ctmp; } if (changed) { /* overload this, it normally means "removed", not destroyed */ notify_region_removed (region); } return changed; } void AudioPlaylist::crossfade_changed (Change ignored) { if (in_flush || in_set_state) { return; } /* 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. */ notify_modified (); } bool AudioPlaylist::region_changed (Change 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); bool parent_wants_notify; parent_wants_notify = Playlist::region_changed (what_changed, region); if ((parent_wants_notify || (what_changed & our_interests))) { notify_modified (); } return true; } void AudioPlaylist::crossfades_at (nframes_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 if (frame >= start && frame <= end) { clist.push_back (*i); } } }