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 const XMLProperty* 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, framepos_t start, framecnt_t cnt, string name, bool hidden)
67 : Playlist (other, start, cnt, name, hidden)
69 RegionReadLock rlock2 (const_cast<AudioPlaylist*> (other.get()));
72 framepos_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 framecnt_t fade_in = 64;
86 framecnt_t fade_out = 64;
88 switch (region->coverage (start, end)) {
89 case Evoral::OverlapNone:
92 case Evoral::OverlapInternal:
94 framecnt_t const offset = start - region->position ();
95 framecnt_t const trim = region->last_frame() - 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_frame() - region->fade_out()->back()->when)
109 fade_out = region->fade_out()->back()->when - ( region->last_frame() - end ); //end is inside the fadeout, preserve the fades endpoint
113 case Evoral::OverlapEnd: {
114 if (start < region->last_frame() - 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<framepos_t> a) : region (r), range (a) {}
156 boost::shared_ptr<AudioRegion> region; ///< the region
157 Evoral::Range<framepos_t> range; ///< range of the region to read, in session frames
160 /** @param start Start position in session frames.
161 * @param cnt Number of frames to read.
164 AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, framepos_t start,
165 framecnt_t cnt, unsigned chan_n)
167 DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Playlist %1 read @ %2 for %3, channel %4, regions %5\n",
168 name(), start, cnt, chan_n, regions.size()));
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 frames.
201 Evoral::RangeList<framepos_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<framepos_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<framepos_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<framepos_t>::List t = region_to_do.get ();
231 for (Evoral::RangeList<framepos_t>::List::iterator j = t.begin(); j != t.end(); ++j) {
232 Evoral::Range<framepos_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<framepos_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::check_crossfades (Evoral::Range<framepos_t> range)
262 if (in_set_state || in_partition || !_session.config.get_auto_xfade ()) {
266 boost::shared_ptr<RegionList> starts = regions_with_start_within (range);
267 boost::shared_ptr<RegionList> ends = regions_with_end_within (range);
269 RegionList all = *starts;
270 std::copy (ends->begin(), ends->end(), back_inserter (all));
272 all.sort (RegionSortByLayer ());
274 set<boost::shared_ptr<Region> > done_start;
275 set<boost::shared_ptr<Region> > done_end;
277 for (RegionList::reverse_iterator i = all.rbegin(); i != all.rend(); ++i) {
278 for (RegionList::reverse_iterator j = all.rbegin(); j != all.rend(); ++j) {
284 if ((*i)->muted() || (*j)->muted()) {
288 if ((*i)->position() == (*j)->position() && ((*i)->length() == (*j)->length())) {
289 /* precise overlay: no xfade */
293 if ((*i)->position() == (*j)->position() || ((*i)->last_frame() == (*j)->last_frame())) {
294 /* starts or ends match: no xfade */
298 boost::shared_ptr<AudioRegion> top;
299 boost::shared_ptr<AudioRegion> bottom;
301 if ((*i)->layer() < (*j)->layer()) {
302 top = boost::dynamic_pointer_cast<AudioRegion> (*j);
303 bottom = boost::dynamic_pointer_cast<AudioRegion> (*i);
305 top = boost::dynamic_pointer_cast<AudioRegion> (*i);
306 bottom = boost::dynamic_pointer_cast<AudioRegion> (*j);
309 if (!top->opaque ()) {
313 Evoral::OverlapType const c = top->coverage (bottom->position(), bottom->last_frame());
315 if (c == Evoral::OverlapStart) {
317 /* top starts within bottom but covers bottom's end */
319 /* { ==== top ============ }
320 * [---- bottom -------------------]
323 if (done_start.find (top) == done_start.end() && done_end.find (bottom) == done_end.end ()) {
325 /* Top's fade-in will cause an implicit fade-out of bottom */
327 if (top->fade_in_is_xfade() && top->fade_in_is_short()) {
329 /* its already an xfade. if its
330 * really short, leave it
337 if (_capture_insertion_underway) {
338 len = _session.config.get_short_xfade_seconds() * _session.frame_rate();
340 switch (_session.config.get_xfade_model()) {
342 len = bottom->last_frame () - top->first_frame () + 1;
343 top->set_fade_in_is_short (false);
346 len = _session.config.get_short_xfade_seconds() * _session.frame_rate();
347 top->set_fade_in_is_short (true);
352 top->set_fade_in_active (true);
353 top->set_fade_in_is_xfade (true);
355 /* XXX may 2012: -3dB and -6dB curves
356 * are the same right now
359 switch (_session.config.get_xfade_choice ()) {
360 case ConstantPowerMinus3dB:
361 top->set_fade_in (FadeConstantPower, len);
363 case ConstantPowerMinus6dB:
364 top->set_fade_in (FadeConstantPower, len);
367 top->set_fade_in_length (len);
372 done_start.insert (top);
375 } else if (c == Evoral::OverlapEnd) {
377 /* top covers start of bottom but ends within it */
379 /* [---- top ------------------------]
380 * { ==== bottom ============ }
383 if (done_end.find (top) == done_end.end() && done_start.find (bottom) == done_start.end ()) {
384 /* Top's fade-out will cause an implicit fade-in of bottom */
387 if (top->fade_out_is_xfade() && top->fade_out_is_short()) {
389 /* its already an xfade. if its
390 * really short, leave it
397 if (_capture_insertion_underway) {
398 len = _session.config.get_short_xfade_seconds() * _session.frame_rate();
400 switch (_session.config.get_xfade_model()) {
402 len = top->last_frame () - bottom->first_frame () + 1;
405 len = _session.config.get_short_xfade_seconds() * _session.frame_rate();
410 top->set_fade_out_active (true);
411 top->set_fade_out_is_xfade (true);
413 switch (_session.config.get_xfade_choice ()) {
414 case ConstantPowerMinus3dB:
415 top->set_fade_out (FadeConstantPower, len);
417 case ConstantPowerMinus6dB:
418 top->set_fade_out (FadeConstantPower, len);
421 top->set_fade_out_length (len);
426 done_end.insert (top);
432 for (RegionList::iterator i = starts->begin(); i != starts->end(); ++i) {
433 if (done_start.find (*i) == done_start.end()) {
434 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (*i);
435 if (r->fade_in_is_xfade()) {
436 r->set_default_fade_in ();
441 for (RegionList::iterator i = ends->begin(); i != ends->end(); ++i) {
442 if (done_end.find (*i) == done_end.end()) {
443 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (*i);
444 if (r->fade_out_is_xfade()) {
445 r->set_default_fade_out ();
452 AudioPlaylist::dump () const
454 boost::shared_ptr<Region>r;
456 cerr << "Playlist \"" << _name << "\" " << endl
457 << regions.size() << " regions "
460 for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
462 cerr << " " << r->name() << " @ " << r << " ["
463 << r->start() << "+" << r->length()
473 AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
475 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
481 bool changed = false;
484 RegionWriteLock rlock (this);
486 for (RegionList::iterator i = regions.begin(); i != regions.end(); ) {
488 RegionList::iterator tmp = i;
491 if ((*i) == region) {
499 for (set<boost::shared_ptr<Region> >::iterator x = all_regions.begin(); x != all_regions.end(); ) {
501 set<boost::shared_ptr<Region> >::iterator xtmp = x;
504 if ((*x) == region) {
505 all_regions.erase (x);
512 region->set_playlist (boost::shared_ptr<Playlist>());
516 /* overload this, it normally means "removed", not destroyed */
517 notify_region_removed (region);
524 AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
526 if (in_flush || in_set_state) {
530 PropertyChange our_interests;
532 our_interests.add (Properties::fade_in_active);
533 our_interests.add (Properties::fade_out_active);
534 our_interests.add (Properties::scale_amplitude);
535 our_interests.add (Properties::envelope_active);
536 our_interests.add (Properties::envelope);
537 our_interests.add (Properties::fade_in);
538 our_interests.add (Properties::fade_out);
540 bool parent_wants_notify;
542 parent_wants_notify = Playlist::region_changed (what_changed, region);
544 if (parent_wants_notify || (what_changed.contains (our_interests))) {
545 notify_contents_changed ();
552 AudioPlaylist::pre_combine (vector<boost::shared_ptr<Region> >& copies)
554 RegionSortByPosition cmp;
555 boost::shared_ptr<AudioRegion> ar;
557 sort (copies.begin(), copies.end(), cmp);
559 ar = boost::dynamic_pointer_cast<AudioRegion> (copies.front());
561 /* disable fade in of the first region */
564 ar->set_fade_in_active (false);
567 ar = boost::dynamic_pointer_cast<AudioRegion> (copies.back());
569 /* disable fade out of the last region */
572 ar->set_fade_out_active (false);
577 AudioPlaylist::post_combine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
579 RegionSortByPosition cmp;
580 boost::shared_ptr<AudioRegion> ar;
581 boost::shared_ptr<AudioRegion> cr;
583 if ((cr = boost::dynamic_pointer_cast<AudioRegion> (compound_region)) == 0) {
587 sort (originals.begin(), originals.end(), cmp);
589 ar = boost::dynamic_pointer_cast<AudioRegion> (originals.front());
591 /* copy the fade in of the first into the compound region */
594 cr->set_fade_in (ar->fade_in());
597 ar = boost::dynamic_pointer_cast<AudioRegion> (originals.back());
600 /* copy the fade out of the last into the compound region */
601 cr->set_fade_out (ar->fade_out());
606 AudioPlaylist::pre_uncombine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
608 RegionSortByPosition cmp;
609 boost::shared_ptr<AudioRegion> ar;
610 boost::shared_ptr<AudioRegion> cr = boost::dynamic_pointer_cast<AudioRegion>(compound_region);
616 sort (originals.begin(), originals.end(), cmp);
618 /* no need to call clear_changes() on the originals because that is
619 * done within Playlist::uncombine ()
622 for (vector<boost::shared_ptr<Region> >::iterator i = originals.begin(); i != originals.end(); ++i) {
624 if ((ar = boost::dynamic_pointer_cast<AudioRegion> (*i)) == 0) {
628 /* scale the uncombined regions by any gain setting for the
632 ar->set_scale_amplitude (ar->scale_amplitude() * cr->scale_amplitude());
634 if (i == originals.begin()) {
636 /* copy the compound region's fade in back into the first
640 if (cr->fade_in()->back()->when <= ar->length()) {
641 /* don't do this if the fade is longer than the
644 ar->set_fade_in (cr->fade_in());
648 } else if (*i == originals.back()) {
650 /* copy the compound region's fade out back into the last
654 if (cr->fade_out()->back()->when <= ar->length()) {
655 /* don't do this if the fade is longer than the
658 ar->set_fade_out (cr->fade_out());
663 _session.add_command (new StatefulDiffCommand (*i));
668 AudioPlaylist::set_state (const XMLNode& node, int version)
670 return Playlist::set_state (node, version);
674 AudioPlaylist::load_legacy_crossfades (const XMLNode& node, int version)
676 /* Read legacy Crossfade nodes and set up region fades accordingly */
678 XMLNodeList children = node.children ();
679 for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
680 if ((*i)->name() == X_("Crossfade")) {
682 XMLProperty* p = (*i)->property (X_("active"));
685 if (!string_is_affirmative (p->value())) {
689 if ((p = (*i)->property (X_("in"))) == 0) {
693 boost::shared_ptr<Region> in = region_by_id (PBD::ID (p->value ()));
696 warning << string_compose (_("Legacy crossfade involved an incoming region not present in playlist \"%1\" - crossfade discarded"),
702 boost::shared_ptr<AudioRegion> in_a = boost::dynamic_pointer_cast<AudioRegion> (in);
705 if ((p = (*i)->property (X_("out"))) == 0) {
709 boost::shared_ptr<Region> out = region_by_id (PBD::ID (p->value ()));
712 warning << string_compose (_("Legacy crossfade involved an outgoing region not present in playlist \"%1\" - crossfade discarded"),
718 boost::shared_ptr<AudioRegion> out_a = boost::dynamic_pointer_cast<AudioRegion> (out);
721 /* now decide whether to add a fade in or fade out
722 * xfade and to which region
725 if (in->layer() <= out->layer()) {
727 /* incoming region is below the outgoing one,
728 * so apply a fade out to the outgoing one
731 const XMLNodeList c = (*i)->children ();
733 for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
734 if ((*j)->name() == X_("FadeOut")) {
735 out_a->fade_out()->set_state (**j, version);
736 } else if ((*j)->name() == X_("FadeIn")) {
737 out_a->inverse_fade_out()->set_state (**j, version);
741 if ((p = (*i)->property ("follow-overlap")) != 0) {
742 out_a->set_fade_out_is_short (!string_is_affirmative (p->value()));
744 out_a->set_fade_out_is_short (false);
747 out_a->set_fade_out_is_xfade (true);
748 out_a->set_fade_out_active (true);
752 /* apply a fade in to the incoming region,
753 * since its above the outgoing one
756 const XMLNodeList c = (*i)->children ();
758 for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
759 if ((*j)->name() == X_("FadeIn")) {
760 in_a->fade_in()->set_state (**j, version);
761 } else if ((*j)->name() == X_("FadeOut")) {
762 in_a->inverse_fade_in()->set_state (**j, version);
766 if ((p = (*i)->property ("follow-overlap")) != 0) {
767 in_a->set_fade_in_is_short (!string_is_affirmative (p->value()));
769 in_a->set_fade_in_is_short (false);
772 in_a->set_fade_in_is_xfade (true);
773 in_a->set_fade_in_active (true);