2 Copyright (C) 2003 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include "ardour/types.h"
25 #include "ardour/debug.h"
26 #include "ardour/audioplaylist.h"
27 #include "ardour/audioregion.h"
28 #include "ardour/region_sorters.h"
29 #include "ardour/session.h"
33 using namespace ARDOUR;
37 AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden)
38 : Playlist (session, node, DataType::AUDIO, hidden)
41 XMLProperty const * prop = node.property("type");
42 assert(!prop || DataType(prop->value()) == DataType::AUDIO);
46 if (set_state (node, Stateful::loading_state_version)) {
47 throw failed_constructor();
53 load_legacy_crossfades (node, Stateful::loading_state_version);
56 AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden)
57 : Playlist (session, name, DataType::AUDIO, hidden)
61 AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, string name, bool hidden)
62 : Playlist (other, name, hidden)
66 AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, samplepos_t start, samplecnt_t cnt, string name, bool hidden)
67 : Playlist (other, start, cnt, name, hidden)
69 RegionReadLock rlock2 (const_cast<AudioPlaylist*> (other.get()));
72 samplepos_t const end = start + cnt - 1;
74 /* Audio regions that have been created by the Playlist constructor
75 will currently have the same fade in/out as the regions that they
76 were created from. This is wrong, so reset the fades here.
79 RegionList::iterator ours = regions.begin ();
81 for (RegionList::const_iterator i = other->regions.begin(); i != other->regions.end(); ++i) {
82 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (*i);
85 samplecnt_t fade_in = 64;
86 samplecnt_t fade_out = 64;
88 switch (region->coverage (start, end)) {
89 case Evoral::OverlapNone:
92 case Evoral::OverlapInternal:
94 samplecnt_t const offset = start - region->position ();
95 samplecnt_t const trim = region->last_sample() - end;
96 if (region->fade_in()->back()->when > offset) {
97 fade_in = region->fade_in()->back()->when - offset;
99 if (region->fade_out()->back()->when > trim) {
100 fade_out = region->fade_out()->back()->when - trim;
105 case Evoral::OverlapStart: {
106 if (end > region->position() + region->fade_in()->back()->when)
107 fade_in = region->fade_in()->back()->when; //end is after fade-in, preserve the fade-in
108 if (end > region->last_sample() - region->fade_out()->back()->when)
109 fade_out = region->fade_out()->back()->when - ( region->last_sample() - end ); //end is inside the fadeout, preserve the fades endpoint
113 case Evoral::OverlapEnd: {
114 if (start < region->last_sample() - region->fade_out()->back()->when) //start is before fade-out, preserve the fadeout
115 fade_out = region->fade_out()->back()->when;
117 if (start < region->position() + region->fade_in()->back()->when)
118 fade_in = region->fade_in()->back()->when - (start - region->position()); //end is inside the fade-in, preserve the fade-in endpoint
122 case Evoral::OverlapExternal:
123 fade_in = region->fade_in()->back()->when;
124 fade_out = region->fade_out()->back()->when;
128 boost::shared_ptr<AudioRegion> our_region = boost::dynamic_pointer_cast<AudioRegion> (*ours);
131 our_region->set_fade_in_length (fade_in);
132 our_region->set_fade_out_length (fade_out);
138 /* this constructor does NOT notify others (session) */
141 /** Sort by descending layer and then by ascending position */
143 bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
144 if (a->layer() != b->layer()) {
145 return a->layer() > b->layer();
148 return a->position() < b->position();
152 /** A segment of region that needs to be read */
154 Segment (boost::shared_ptr<AudioRegion> r, Evoral::Range<samplepos_t> a) : region (r), range (a) {}
156 boost::shared_ptr<AudioRegion> region; ///< the region
157 Evoral::Range<samplepos_t> range; ///< range of the region to read, in session samples
160 /** @param start Start position in session samples.
161 * @param cnt Number of samples to read.
164 AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, samplepos_t start,
165 samplecnt_t cnt, unsigned chan_n)
167 DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Playlist %1 read @ %2 for %3, channel %4, regions %5 mixdown @ %6 gain @ %7\n",
168 name(), start, cnt, chan_n, regions.size(), mixdown_buffer, gain_buffer));
170 /* optimizing this memset() away involves a lot of conditionals
171 that may well cause more of a hit due to cache misses
172 and related stuff than just doing this here.
174 it would be great if someone could measure this
177 one way or another, parts of the requested area
178 that are not written to by Region::region_at()
179 for all Regions that cover the area need to be
183 memset (buf, 0, sizeof (Sample) * cnt);
185 /* this function is never called from a realtime thread, so
186 its OK to block (for short intervals).
189 Playlist::RegionReadLock rl (this);
191 /* Find all the regions that are involved in the bit we are reading,
192 and sort them by descending layer and ascending position.
194 boost::shared_ptr<RegionList> all = regions_touched_locked (start, start + cnt - 1);
195 all->sort (ReadSorter ());
197 /* This will be a list of the bits of our read range that we have
198 handled completely (ie for which no more regions need to be read).
199 It is a list of ranges in session samples.
201 Evoral::RangeList<samplepos_t> done;
203 /* This will be a list of the bits of regions that we need to read */
206 /* Now go through the `all' list filling in `to_do' and `done' */
207 for (RegionList::iterator i = all->begin(); i != all->end(); ++i) {
208 boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (*i);
210 /* muted regions don't figure into it at all */
214 /* check for the case of solo_selection */
215 bool force_transparent = ( _session.solo_selection_active() && SoloSelectedActive() && !SoloSelectedListIncludes( (const Region*) &(**i) ) );
216 if ( force_transparent )
219 /* Work out which bits of this region need to be read;
220 first, trim to the range we are reading...
222 Evoral::Range<samplepos_t> region_range = ar->range ();
223 region_range.from = max (region_range.from, start);
224 region_range.to = min (region_range.to, start + cnt - 1);
226 /* ... and then remove the bits that are already done */
228 Evoral::RangeList<samplepos_t> region_to_do = Evoral::subtract (region_range, done);
230 /* Make a note to read those bits, adding their bodies (the parts between end-of-fade-in
231 and start-of-fade-out) to the `done' list.
234 Evoral::RangeList<samplepos_t>::List t = region_to_do.get ();
236 for (Evoral::RangeList<samplepos_t>::List::iterator j = t.begin(); j != t.end(); ++j) {
237 Evoral::Range<samplepos_t> d = *j;
238 to_do.push_back (Segment (ar, d));
241 /* Cut this range down to just the body and mark it done */
242 Evoral::Range<samplepos_t> body = ar->body_range ();
243 if (body.from < d.to && body.to > d.from) {
244 d.from = max (d.from, body.from);
245 d.to = min (d.to, body.to);
252 /* Now go backwards through the to_do list doing the actual reads */
253 for (list<Segment>::reverse_iterator i = to_do.rbegin(); i != to_do.rend(); ++i) {
254 DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\tPlaylist %1 read %2 @ %3 for %4, channel %5, buf @ %6 offset %7\n",
255 name(), i->region->name(), i->range.from,
256 i->range.to - i->range.from + 1, (int) chan_n,
257 buf, i->range.from - start));
258 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);
265 AudioPlaylist::dump () const
267 boost::shared_ptr<Region>r;
269 cerr << "Playlist \"" << _name << "\" " << endl
270 << regions.size() << " regions "
273 for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
275 cerr << " " << r->name() << " @ " << r << " ["
276 << r->start() << "+" << r->length()
286 AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
288 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
294 bool changed = false;
297 RegionWriteLock rlock (this);
299 for (RegionList::iterator i = regions.begin(); i != regions.end(); ) {
301 RegionList::iterator tmp = i;
304 if ((*i) == region) {
312 for (set<boost::shared_ptr<Region> >::iterator x = all_regions.begin(); x != all_regions.end(); ) {
314 set<boost::shared_ptr<Region> >::iterator xtmp = x;
317 if ((*x) == region) {
318 all_regions.erase (x);
325 region->set_playlist (boost::shared_ptr<Playlist>());
329 /* overload this, it normally means "removed", not destroyed */
330 notify_region_removed (region);
337 AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
339 if (in_flush || in_set_state) {
343 PropertyChange bounds;
344 bounds.add (Properties::start);
345 bounds.add (Properties::position);
346 bounds.add (Properties::length);
348 PropertyChange our_interests;
350 our_interests.add (Properties::fade_in_active);
351 our_interests.add (Properties::fade_out_active);
352 our_interests.add (Properties::scale_amplitude);
353 our_interests.add (Properties::envelope_active);
354 our_interests.add (Properties::envelope);
355 our_interests.add (Properties::fade_in);
356 our_interests.add (Properties::fade_out);
358 bool parent_wants_notify;
360 parent_wants_notify = Playlist::region_changed (what_changed, region);
361 /* if bounds changed, we have already done notify_contents_changed ()*/
362 if ((parent_wants_notify || what_changed.contains (our_interests)) && !what_changed.contains (bounds)) {
363 notify_contents_changed ();
370 AudioPlaylist::pre_combine (vector<boost::shared_ptr<Region> >& copies)
372 RegionSortByPosition cmp;
373 boost::shared_ptr<AudioRegion> ar;
375 sort (copies.begin(), copies.end(), cmp);
377 ar = boost::dynamic_pointer_cast<AudioRegion> (copies.front());
379 /* disable fade in of the first region */
382 ar->set_fade_in_active (false);
385 ar = boost::dynamic_pointer_cast<AudioRegion> (copies.back());
387 /* disable fade out of the last region */
390 ar->set_fade_out_active (false);
395 AudioPlaylist::post_combine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
397 RegionSortByPosition cmp;
398 boost::shared_ptr<AudioRegion> ar;
399 boost::shared_ptr<AudioRegion> cr;
401 if ((cr = boost::dynamic_pointer_cast<AudioRegion> (compound_region)) == 0) {
405 sort (originals.begin(), originals.end(), cmp);
407 ar = boost::dynamic_pointer_cast<AudioRegion> (originals.front());
409 /* copy the fade in of the first into the compound region */
412 cr->set_fade_in (ar->fade_in());
415 ar = boost::dynamic_pointer_cast<AudioRegion> (originals.back());
418 /* copy the fade out of the last into the compound region */
419 cr->set_fade_out (ar->fade_out());
424 AudioPlaylist::pre_uncombine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
426 RegionSortByPosition cmp;
427 boost::shared_ptr<AudioRegion> ar;
428 boost::shared_ptr<AudioRegion> cr = boost::dynamic_pointer_cast<AudioRegion>(compound_region);
434 sort (originals.begin(), originals.end(), cmp);
436 /* no need to call clear_changes() on the originals because that is
437 * done within Playlist::uncombine ()
440 for (vector<boost::shared_ptr<Region> >::iterator i = originals.begin(); i != originals.end(); ++i) {
442 if ((ar = boost::dynamic_pointer_cast<AudioRegion> (*i)) == 0) {
446 /* scale the uncombined regions by any gain setting for the
450 ar->set_scale_amplitude (ar->scale_amplitude() * cr->scale_amplitude());
452 if (i == originals.begin()) {
454 /* copy the compound region's fade in back into the first
458 if (cr->fade_in()->back()->when <= ar->length()) {
459 /* don't do this if the fade is longer than the
462 ar->set_fade_in (cr->fade_in());
466 } else if (*i == originals.back()) {
468 /* copy the compound region's fade out back into the last
472 if (cr->fade_out()->back()->when <= ar->length()) {
473 /* don't do this if the fade is longer than the
476 ar->set_fade_out (cr->fade_out());
481 _session.add_command (new StatefulDiffCommand (*i));
486 AudioPlaylist::set_state (const XMLNode& node, int version)
488 return Playlist::set_state (node, version);
492 AudioPlaylist::load_legacy_crossfades (const XMLNode& node, int version)
494 /* Read legacy Crossfade nodes and set up region fades accordingly */
496 XMLNodeList children = node.children ();
497 for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
498 if ((*i)->name() == X_("Crossfade")) {
500 XMLProperty const * p = (*i)->property (X_("active"));
503 if (!string_to<bool> (p->value())) {
507 if ((p = (*i)->property (X_("in"))) == 0) {
511 boost::shared_ptr<Region> in = region_by_id (PBD::ID (p->value ()));
514 warning << string_compose (_("Legacy crossfade involved an incoming region not present in playlist \"%1\" - crossfade discarded"),
520 boost::shared_ptr<AudioRegion> in_a = boost::dynamic_pointer_cast<AudioRegion> (in);
523 if ((p = (*i)->property (X_("out"))) == 0) {
527 boost::shared_ptr<Region> out = region_by_id (PBD::ID (p->value ()));
530 warning << string_compose (_("Legacy crossfade involved an outgoing region not present in playlist \"%1\" - crossfade discarded"),
536 boost::shared_ptr<AudioRegion> out_a = boost::dynamic_pointer_cast<AudioRegion> (out);
539 /* now decide whether to add a fade in or fade out
540 * xfade and to which region
543 if (in->layer() <= out->layer()) {
545 /* incoming region is below the outgoing one,
546 * so apply a fade out to the outgoing one
549 const XMLNodeList c = (*i)->children ();
551 for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
552 if ((*j)->name() == X_("FadeOut")) {
553 out_a->fade_out()->set_state (**j, version);
554 } else if ((*j)->name() == X_("FadeIn")) {
555 out_a->inverse_fade_out()->set_state (**j, version);
559 out_a->set_fade_out_active (true);
563 /* apply a fade in to the incoming region,
564 * since its above the outgoing one
567 const XMLNodeList c = (*i)->children ();
569 for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
570 if ((*j)->name() == X_("FadeIn")) {
571 in_a->fade_in()->set_state (**j, version);
572 } else if ((*j)->name() == X_("FadeOut")) {
573 in_a->inverse_fade_in()->set_state (**j, version);
577 in_a->set_fade_in_active (true);