/* * Copyright (C) 2005-2017 Paul Davis * Copyright (C) 2006-2012 David Robillard * Copyright (C) 2006 Jesse Chappell * Copyright (C) 2006 Sampo Savolainen * Copyright (C) 2007-2012 Carl Hetherington * Copyright (C) 2011-2012 Ben Loftis * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #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 "pbd/i18n.h" using namespace ARDOUR; using namespace std; using namespace PBD; AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden) : Playlist (session, node, DataType::AUDIO, hidden) { #ifndef NDEBUG XMLProperty const * prop = node.property("type"); assert(!prop || DataType(prop->value()) == DataType::AUDIO); #endif 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, DataType::AUDIO, hidden) { } AudioPlaylist::AudioPlaylist (boost::shared_ptr other, string name, bool hidden) : Playlist (other, name, hidden) { } AudioPlaylist::AudioPlaylist (boost::shared_ptr other, samplepos_t start, samplecnt_t cnt, string name, bool hidden) : Playlist (other, start, cnt, name, hidden) { RegionReadLock rlock2 (const_cast (other.get())); in_set_state++; samplepos_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); samplecnt_t fade_in = 64; samplecnt_t fade_out = 64; switch (region->coverage (start, end)) { case Evoral::OverlapNone: continue; case Evoral::OverlapInternal: { samplecnt_t const offset = start - region->position (); samplecnt_t const trim = region->last_sample() - 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_sample() - region->fade_out()->back()->when) fade_out = region->fade_out()->back()->when - ( region->last_sample() - end ); //end is inside the fadeout, preserve the fades endpoint break; } case Evoral::OverlapEnd: { if (start < region->last_sample() - 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; } in_set_state--; /* this constructor does NOT notify others (session) */ } /** 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(); } }; /** 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 samples }; /** @param start Start position in session samples. * @param cnt Number of samples to read. */ ARDOUR::samplecnt_t AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, samplepos_t start, samplecnt_t cnt, unsigned chan_n) { 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 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). */ Playlist::RegionReadLock rl (this); /* 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 ()); /* 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 samples. */ Evoral::RangeList done; /* This will be a list of the bits of regions that we need to read */ list to_do; /* 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); /* muted regions don't figure into it at all */ if ( ar->muted() ) continue; /* check for the case of solo_selection */ bool force_transparent = ( _session.solo_selection_active() && SoloSelectedActive() && !SoloSelectedListIncludes( (const Region*) &(**i) ) ); if ( force_transparent ) continue; /* 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); /* ... and then remove the bits that are already done */ Evoral::RangeList region_to_do = Evoral::subtract (region_range, done); /* 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. */ Evoral::RangeList::List t = region_to_do.get (); for (Evoral::RangeList::List::iterator j = t.begin(); j != t.end(); ++j) { Evoral::Range d = *j; to_do.push_back (Segment (ar, d)); 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); } } } } /* 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); } return cnt; } void AudioPlaylist::dump () const { boost::shared_ptrr; cerr << "Playlist \"" << _name << "\" " << endl << regions.size() << " regions " << 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; } } bool AudioPlaylist::destroy_region (boost::shared_ptr region) { boost::shared_ptr r = boost::dynamic_pointer_cast (region); if (!r) { return false; } bool changed = false; { RegionWriteLock 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()); } if (changed) { /* overload this, it normally means "removed", not destroyed */ notify_region_removed (region); } return changed; } bool AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr region) { if (in_flush || in_set_state) { return false; } PropertyChange bounds; bounds.add (Properties::start); bounds.add (Properties::position); bounds.add (Properties::length); 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 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::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)); } } int AudioPlaylist::set_state (const XMLNode& node, int version) { return Playlist::set_state (node, version); } void AudioPlaylist::load_legacy_crossfades (const XMLNode& node, int version) { /* Read legacy Crossfade nodes and set up region fades accordingly */ XMLNodeList children = node.children (); for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) { if ((*i)->name() == X_("Crossfade")) { XMLProperty const * p = (*i)->property (X_("active")); assert (p); if (!string_to (p->value())) { continue; } if ((p = (*i)->property (X_("in"))) == 0) { continue; } boost::shared_ptr in = region_by_id (PBD::ID (p->value ())); 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); if ((p = (*i)->property (X_("out"))) == 0) { continue; } boost::shared_ptr out = region_by_id (PBD::ID (p->value ())); if (!out) { warning << string_compose (_("Legacy crossfade involved an outgoing region not present in playlist \"%1\" - crossfade discarded"), name()) << endmsg; continue; } boost::shared_ptr out_a = boost::dynamic_pointer_cast (out); assert (out_a); /* now decide whether to add a fade in or fade out * xfade and to which region */ if (in->layer() <= out->layer()) { /* incoming region is below the outgoing one, * so apply a fade out to the outgoing one */ const XMLNodeList c = (*i)->children (); 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); } } out_a->set_fade_out_active (true); } else { /* apply a fade in to the incoming region, * since its above the outgoing one */ const XMLNodeList c = (*i)->children (); 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); } } } }