Paul Davis
b35518e212
This is mostly a simple lexical search+replace but the absence of operator< for std::weak_ptr<T> leads to some complications, particularly with Evoral::Sequence and ExportPortChannel.
614 lines
18 KiB
C++
614 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2005-2017 Paul Davis <paul@linuxaudiosystems.com>
|
|
* Copyright (C) 2006-2012 David Robillard <d@drobilla.net>
|
|
* Copyright (C) 2006 Jesse Chappell <jesse@essej.net>
|
|
* Copyright (C) 2006 Sampo Savolainen <v2@iki.fi>
|
|
* Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
|
|
* Copyright (C) 2011-2012 Ben Loftis <ben@harrisonconsoles.com>
|
|
*
|
|
* 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 <algorithm>
|
|
|
|
#include <cstdlib>
|
|
|
|
#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;
|
|
|
|
#include "ardour/audioengine.h"
|
|
#define S2SC(s) Temporal::samples_to_superclock (s, TEMPORAL_SAMPLE_RATE)
|
|
#define SC2S(s) Temporal::superclock_to_samples (s, TEMPORAL_SAMPLE_RATE)
|
|
|
|
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 (std::shared_ptr<const AudioPlaylist> other, string name, bool hidden)
|
|
: Playlist (other, name, hidden)
|
|
{
|
|
}
|
|
|
|
AudioPlaylist::AudioPlaylist (std::shared_ptr<const AudioPlaylist> other, timepos_t const & start, timepos_t const & cnt, string name, bool hidden)
|
|
: Playlist (other, start, cnt, name, hidden)
|
|
{
|
|
RegionReadLock rlock2 (const_cast<AudioPlaylist*> (other.get()));
|
|
in_set_state++;
|
|
|
|
const timepos_t tend = start + cnt;
|
|
samplepos_t end = tend.samples();
|
|
|
|
/* 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 (auto const & r : other->regions) {
|
|
std::shared_ptr<AudioRegion> region = std::dynamic_pointer_cast<AudioRegion> (r);
|
|
assert (region);
|
|
|
|
samplecnt_t fade_in = 64;
|
|
samplecnt_t fade_out = 64;
|
|
|
|
switch (region->coverage (start, tend)) {
|
|
case Temporal::OverlapNone:
|
|
continue;
|
|
|
|
case Temporal::OverlapInternal:
|
|
{
|
|
samplecnt_t const offset = start.samples() - region->position_sample ();
|
|
samplecnt_t const trim = region->last_sample() - end;
|
|
if (region->fade_in()->back()->when > offset) {
|
|
fade_in = region->fade_in()->back()->when.earlier (timepos_t (offset)).samples();
|
|
}
|
|
if (region->fade_out()->back()->when > trim) {
|
|
fade_out = region->fade_out()->back()->when.earlier (timepos_t (trim)).samples();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Temporal::OverlapStart: {
|
|
if (timepos_t (end) > region->position() + region->fade_in()->back()->when) {
|
|
fade_in = region->fade_in()->back()->when.samples(); //end is after fade-in, preserve the fade-in
|
|
}
|
|
if (timepos_t (end) >= region->end().earlier (region->fade_out()->back()->when)) {
|
|
fade_out = region->fade_out()->back()->when.earlier (timepos_t (region->last_sample() - end)).samples(); //end is inside the fadeout, preserve the fades endpoint
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Temporal::OverlapEnd: {
|
|
if (start < region->end().earlier (region->fade_out()->back()->when)) { //start is before fade-out, preserve the fadeout
|
|
fade_out = region->fade_out()->back()->when.samples();
|
|
}
|
|
if (start < region->position() + region->fade_in()->back()->when) {
|
|
fade_in = region->fade_in()->back()->when.earlier (start.distance (region->position())).samples(); //end is inside the fade-in, preserve the fade-in endpoint
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Temporal::OverlapExternal:
|
|
fade_in = region->fade_in()->back()->when.samples();
|
|
fade_out = region->fade_out()->back()->when.samples();
|
|
break;
|
|
}
|
|
|
|
std::shared_ptr<AudioRegion> our_region = std::dynamic_pointer_cast<AudioRegion> (*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() (std::shared_ptr<Region> a, std::shared_ptr<Region> 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 (std::shared_ptr<AudioRegion> r, Temporal::Range a) : region (r), range (a) {}
|
|
|
|
std::shared_ptr<AudioRegion> region; ///< the region
|
|
Temporal::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::timecnt_t
|
|
AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, timepos_t const & start, timecnt_t const & cnt, uint32_t 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));
|
|
|
|
samplecnt_t const scnt (cnt.samples ());
|
|
|
|
/* 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) * scnt);
|
|
|
|
/* 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.
|
|
*/
|
|
std::shared_ptr<RegionList> all = regions_touched_locked (start, start + cnt);
|
|
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.
|
|
*/
|
|
Temporal::RangeList done;
|
|
|
|
/* This will be a list of the bits of regions that we need to read */
|
|
list<Segment> to_do;
|
|
|
|
/* Now go through the `all' list filling in `to_do' and `done' */
|
|
for (RegionList::iterator i = all->begin(); i != all->end(); ++i) {
|
|
std::shared_ptr<AudioRegion> ar = std::dynamic_pointer_cast<AudioRegion> (*i);
|
|
|
|
/* muted regions don't figure into it at all */
|
|
if (ar->muted()) {
|
|
continue;
|
|
}
|
|
|
|
/* check for the case of solo_selection */
|
|
const 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...
|
|
*/
|
|
Temporal::Range rrange = ar->range_samples ();
|
|
Temporal::Range region_range (max (rrange.start(), start),
|
|
min (rrange.end(), start + cnt));
|
|
|
|
/* ... and then remove the bits that are already done */
|
|
|
|
Temporal::RangeList region_to_do = region_range.subtract (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.
|
|
*/
|
|
|
|
Temporal::RangeList::List t = region_to_do.get ();
|
|
|
|
for (Temporal::RangeList::List::iterator j = t.begin(); j != t.end(); ++j) {
|
|
Temporal::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 */
|
|
Temporal::Range body = ar->body_range ();
|
|
|
|
if (body.start() < d.end() && body.end() > d.start()) {
|
|
d.set_start (max (d.start(), body.start()));
|
|
d.set_end (min (d.end(), body.end()));
|
|
done.add (d);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now go backwards through the to_do list doing the actual reads */
|
|
|
|
for (list<Segment>::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.start(),
|
|
i->range.length(), (int) chan_n,
|
|
buf, i->range.start().earlier (start)));
|
|
|
|
samplepos_t read_pos (i->range.start().samples());
|
|
samplecnt_t read_cnt (i->range.start().distance (i->range.end()).samples());
|
|
assert (start.distance (i->range.start()).samples() < scnt);
|
|
assert (start.distance (i->range.start()).samples() + read_cnt <= scnt);
|
|
samplecnt_t nread = i->region->read_at (buf + start.distance (i->range.start()).samples(), mixdown_buffer, gain_buffer, read_pos, read_cnt, chan_n);
|
|
if (nread != read_cnt) {
|
|
#ifndef NDEBUG
|
|
/* forward error to DiskReader::audio_read. This does 2 things:
|
|
* - error "DiskReader %1: when refilling, cannot read ..."
|
|
* - emit Underrun() - "Disk is too slow"
|
|
* (ideally only the first would happen)
|
|
* Since the buffer is zero'ed above, failed reads are not an issue.
|
|
*/
|
|
return timecnt_t (0);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
void
|
|
AudioPlaylist::dump () const
|
|
{
|
|
std::shared_ptr<Region>r;
|
|
|
|
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 (std::shared_ptr<Region> region)
|
|
{
|
|
std::shared_ptr<AudioRegion> r = std::dynamic_pointer_cast<AudioRegion> (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<std::shared_ptr<Region> >::iterator x = all_regions.begin(); x != all_regions.end(); ) {
|
|
|
|
set<std::shared_ptr<Region> >::iterator xtmp = x;
|
|
++xtmp;
|
|
|
|
if ((*x) == region) {
|
|
all_regions.erase (x);
|
|
changed = true;
|
|
}
|
|
|
|
x = xtmp;
|
|
}
|
|
|
|
region->set_playlist (std::shared_ptr<Playlist>());
|
|
}
|
|
|
|
if (changed) {
|
|
/* overload this, it normally means "removed", not destroyed */
|
|
notify_region_removed (region);
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
bool
|
|
AudioPlaylist::region_changed (const PropertyChange& what_changed, std::shared_ptr<Region> region)
|
|
{
|
|
if (in_flush || in_set_state) {
|
|
return false;
|
|
}
|
|
|
|
PropertyChange bounds;
|
|
bounds.add (Properties::start);
|
|
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<std::shared_ptr<Region> >& copies)
|
|
{
|
|
RegionSortByPosition cmp;
|
|
std::shared_ptr<AudioRegion> ar;
|
|
|
|
sort (copies.begin(), copies.end(), cmp);
|
|
|
|
ar = std::dynamic_pointer_cast<AudioRegion> (copies.front());
|
|
|
|
/* disable fade in of the first region */
|
|
|
|
if (ar) {
|
|
ar->set_fade_in_active (false);
|
|
}
|
|
|
|
ar = std::dynamic_pointer_cast<AudioRegion> (copies.back());
|
|
|
|
/* disable fade out of the last region */
|
|
|
|
if (ar) {
|
|
ar->set_fade_out_active (false);
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioPlaylist::post_combine (vector<std::shared_ptr<Region> >& originals, std::shared_ptr<Region> compound_region)
|
|
{
|
|
RegionSortByPosition cmp;
|
|
std::shared_ptr<AudioRegion> ar;
|
|
std::shared_ptr<AudioRegion> cr;
|
|
|
|
if ((cr = std::dynamic_pointer_cast<AudioRegion> (compound_region)) == 0) {
|
|
return;
|
|
}
|
|
|
|
sort (originals.begin(), originals.end(), cmp);
|
|
|
|
ar = std::dynamic_pointer_cast<AudioRegion> (originals.front());
|
|
|
|
/* copy the fade in of the first into the compound region */
|
|
|
|
if (ar) {
|
|
cr->set_fade_in (ar->fade_in());
|
|
}
|
|
|
|
ar = std::dynamic_pointer_cast<AudioRegion> (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<std::shared_ptr<Region> >& originals, std::shared_ptr<Region> compound_region)
|
|
{
|
|
RegionSortByPosition cmp;
|
|
std::shared_ptr<AudioRegion> ar;
|
|
std::shared_ptr<AudioRegion> cr = std::dynamic_pointer_cast<AudioRegion>(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<std::shared_ptr<Region> >::iterator i = originals.begin(); i != originals.end(); ++i) {
|
|
|
|
if ((ar = std::dynamic_pointer_cast<AudioRegion> (*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<bool> (p->value())) {
|
|
continue;
|
|
}
|
|
|
|
if ((p = (*i)->property (X_("in"))) == 0) {
|
|
continue;
|
|
}
|
|
|
|
std::shared_ptr<Region> 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;
|
|
}
|
|
|
|
std::shared_ptr<AudioRegion> in_a = std::dynamic_pointer_cast<AudioRegion> (in);
|
|
assert (in_a);
|
|
|
|
if ((p = (*i)->property (X_("out"))) == 0) {
|
|
continue;
|
|
}
|
|
|
|
std::shared_ptr<Region> 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;
|
|
}
|
|
|
|
std::shared_ptr<AudioRegion> out_a = std::dynamic_pointer_cast<AudioRegion> (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);
|
|
}
|
|
}
|
|
}
|
|
}
|