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 /* Work out which bits of this region need to be read;
215 first, trim to the range we are reading...
217 Evoral::Range<samplepos_t> region_range = ar->range ();
218 region_range.from = max (region_range.from, start);
219 region_range.to = min (region_range.to, start + cnt - 1);
221 /* ... and then remove the bits that are already done */
223 Evoral::RangeList<samplepos_t> region_to_do = Evoral::subtract (region_range, done);
225 /* Make a note to read those bits, adding their bodies (the parts between end-of-fade-in
226 and start-of-fade-out) to the `done' list.
229 Evoral::RangeList<samplepos_t>::List t = region_to_do.get ();
231 for (Evoral::RangeList<samplepos_t>::List::iterator j = t.begin(); j != t.end(); ++j) {
232 Evoral::Range<samplepos_t> d = *j;
233 to_do.push_back (Segment (ar, d));
236 /* Cut this range down to just the body and mark it done */
237 Evoral::Range<samplepos_t> body = ar->body_range ();
238 if (body.from < d.to && body.to > d.from) {
239 d.from = max (d.from, body.from);
240 d.to = min (d.to, body.to);
247 /* Now go backwards through the to_do list doing the actual reads */
248 for (list<Segment>::reverse_iterator i = to_do.rbegin(); i != to_do.rend(); ++i) {
249 DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\tPlaylist %1 read %2 @ %3 for %4, channel %5, buf @ %6 offset %7\n",
250 name(), i->region->name(), i->range.from,
251 i->range.to - i->range.from + 1, (int) chan_n,
252 buf, i->range.from - start));
253 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);
260 AudioPlaylist::dump () const
262 boost::shared_ptr<Region>r;
264 cerr << "Playlist \"" << _name << "\" " << endl
265 << regions.size() << " regions "
268 for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
270 cerr << " " << r->name() << " @ " << r << " ["
271 << r->start() << "+" << r->length()
281 AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
283 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
289 bool changed = false;
292 RegionWriteLock rlock (this);
294 for (RegionList::iterator i = regions.begin(); i != regions.end(); ) {
296 RegionList::iterator tmp = i;
299 if ((*i) == region) {
307 for (set<boost::shared_ptr<Region> >::iterator x = all_regions.begin(); x != all_regions.end(); ) {
309 set<boost::shared_ptr<Region> >::iterator xtmp = x;
312 if ((*x) == region) {
313 all_regions.erase (x);
320 region->set_playlist (boost::shared_ptr<Playlist>());
324 /* overload this, it normally means "removed", not destroyed */
325 notify_region_removed (region);
332 AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
334 if (in_flush || in_set_state) {
338 PropertyChange bounds;
339 bounds.add (Properties::start);
340 bounds.add (Properties::position);
341 bounds.add (Properties::length);
343 PropertyChange our_interests;
345 our_interests.add (Properties::fade_in_active);
346 our_interests.add (Properties::fade_out_active);
347 our_interests.add (Properties::scale_amplitude);
348 our_interests.add (Properties::envelope_active);
349 our_interests.add (Properties::envelope);
350 our_interests.add (Properties::fade_in);
351 our_interests.add (Properties::fade_out);
353 bool parent_wants_notify;
355 parent_wants_notify = Playlist::region_changed (what_changed, region);
356 /* if bounds changed, we have already done notify_contents_changed ()*/
357 if ((parent_wants_notify || what_changed.contains (our_interests)) && !what_changed.contains (bounds)) {
358 notify_contents_changed ();
365 AudioPlaylist::pre_combine (vector<boost::shared_ptr<Region> >& copies)
367 RegionSortByPosition cmp;
368 boost::shared_ptr<AudioRegion> ar;
370 sort (copies.begin(), copies.end(), cmp);
372 ar = boost::dynamic_pointer_cast<AudioRegion> (copies.front());
374 /* disable fade in of the first region */
377 ar->set_fade_in_active (false);
380 ar = boost::dynamic_pointer_cast<AudioRegion> (copies.back());
382 /* disable fade out of the last region */
385 ar->set_fade_out_active (false);
390 AudioPlaylist::post_combine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
392 RegionSortByPosition cmp;
393 boost::shared_ptr<AudioRegion> ar;
394 boost::shared_ptr<AudioRegion> cr;
396 if ((cr = boost::dynamic_pointer_cast<AudioRegion> (compound_region)) == 0) {
400 sort (originals.begin(), originals.end(), cmp);
402 ar = boost::dynamic_pointer_cast<AudioRegion> (originals.front());
404 /* copy the fade in of the first into the compound region */
407 cr->set_fade_in (ar->fade_in());
410 ar = boost::dynamic_pointer_cast<AudioRegion> (originals.back());
413 /* copy the fade out of the last into the compound region */
414 cr->set_fade_out (ar->fade_out());
419 AudioPlaylist::pre_uncombine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
421 RegionSortByPosition cmp;
422 boost::shared_ptr<AudioRegion> ar;
423 boost::shared_ptr<AudioRegion> cr = boost::dynamic_pointer_cast<AudioRegion>(compound_region);
429 sort (originals.begin(), originals.end(), cmp);
431 /* no need to call clear_changes() on the originals because that is
432 * done within Playlist::uncombine ()
435 for (vector<boost::shared_ptr<Region> >::iterator i = originals.begin(); i != originals.end(); ++i) {
437 if ((ar = boost::dynamic_pointer_cast<AudioRegion> (*i)) == 0) {
441 /* scale the uncombined regions by any gain setting for the
445 ar->set_scale_amplitude (ar->scale_amplitude() * cr->scale_amplitude());
447 if (i == originals.begin()) {
449 /* copy the compound region's fade in back into the first
453 if (cr->fade_in()->back()->when <= ar->length()) {
454 /* don't do this if the fade is longer than the
457 ar->set_fade_in (cr->fade_in());
461 } else if (*i == originals.back()) {
463 /* copy the compound region's fade out back into the last
467 if (cr->fade_out()->back()->when <= ar->length()) {
468 /* don't do this if the fade is longer than the
471 ar->set_fade_out (cr->fade_out());
476 _session.add_command (new StatefulDiffCommand (*i));
481 AudioPlaylist::set_state (const XMLNode& node, int version)
483 return Playlist::set_state (node, version);
487 AudioPlaylist::load_legacy_crossfades (const XMLNode& node, int version)
489 /* Read legacy Crossfade nodes and set up region fades accordingly */
491 XMLNodeList children = node.children ();
492 for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
493 if ((*i)->name() == X_("Crossfade")) {
495 XMLProperty const * p = (*i)->property (X_("active"));
498 if (!string_to<bool> (p->value())) {
502 if ((p = (*i)->property (X_("in"))) == 0) {
506 boost::shared_ptr<Region> in = region_by_id (PBD::ID (p->value ()));
509 warning << string_compose (_("Legacy crossfade involved an incoming region not present in playlist \"%1\" - crossfade discarded"),
515 boost::shared_ptr<AudioRegion> in_a = boost::dynamic_pointer_cast<AudioRegion> (in);
518 if ((p = (*i)->property (X_("out"))) == 0) {
522 boost::shared_ptr<Region> out = region_by_id (PBD::ID (p->value ()));
525 warning << string_compose (_("Legacy crossfade involved an outgoing region not present in playlist \"%1\" - crossfade discarded"),
531 boost::shared_ptr<AudioRegion> out_a = boost::dynamic_pointer_cast<AudioRegion> (out);
534 /* now decide whether to add a fade in or fade out
535 * xfade and to which region
538 if (in->layer() <= out->layer()) {
540 /* incoming region is below the outgoing one,
541 * so apply a fade out to the outgoing one
544 const XMLNodeList c = (*i)->children ();
546 for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
547 if ((*j)->name() == X_("FadeOut")) {
548 out_a->fade_out()->set_state (**j, version);
549 } else if ((*j)->name() == X_("FadeIn")) {
550 out_a->inverse_fade_out()->set_state (**j, version);
554 out_a->set_fade_out_active (true);
558 /* apply a fade in to the incoming region,
559 * since its above the outgoing one
562 const XMLNodeList c = (*i)->children ();
564 for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
565 if ((*j)->name() == X_("FadeIn")) {
566 in_a->fade_in()->set_state (**j, version);
567 } else if ((*j)->name() == X_("FadeOut")) {
568 in_a->inverse_fade_in()->set_state (**j, version);
572 in_a->set_fade_in_active (true);