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 /* Work out which bits of this region need to be read;
211 first, trim to the range we are reading...
213 Evoral::Range<framepos_t> region_range = ar->range ();
214 region_range.from = max (region_range.from, start);
215 region_range.to = min (region_range.to, start + cnt - 1);
217 /* ... and then remove the bits that are already done */
219 Evoral::RangeList<framepos_t> region_to_do = Evoral::subtract (region_range, done);
221 /* Read those bits, adding their bodies (the parts between end-of-fade-in
222 and start-of-fade-out) to the `done' list.
225 Evoral::RangeList<framepos_t>::List t = region_to_do.get ();
227 for (Evoral::RangeList<framepos_t>::List::iterator j = t.begin(); j != t.end(); ++j) {
228 Evoral::Range<framepos_t> d = *j;
229 to_do.push_back (Segment (ar, d));
232 /* Cut this range down to just the body and mark it done */
233 Evoral::Range<framepos_t> body = ar->body_range ();
234 if (body.from < d.to && body.to > d.from) {
235 d.from = max (d.from, body.from);
236 d.to = min (d.to, body.to);
243 /* Now go backwards through the to_do list doing the actual reads */
244 for (list<Segment>::reverse_iterator i = to_do.rbegin(); i != to_do.rend(); ++i) {
245 DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("\tPlaylist %1 read %2 @ %3 for %4, channel %5, buf @ %6 offset %7\n",
246 name(), i->region->name(), i->range.from,
247 i->range.to - i->range.from + 1, (int) chan_n,
248 buf, i->range.from - start));
249 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);
256 AudioPlaylist::check_crossfades (Evoral::Range<framepos_t> range)
258 if (in_set_state || in_partition || !_session.config.get_auto_xfade ()) {
262 boost::shared_ptr<RegionList> starts = regions_with_start_within (range);
263 boost::shared_ptr<RegionList> ends = regions_with_end_within (range);
265 RegionList all = *starts;
266 std::copy (ends->begin(), ends->end(), back_inserter (all));
268 all.sort (RegionSortByLayer ());
270 set<boost::shared_ptr<Region> > done_start;
271 set<boost::shared_ptr<Region> > done_end;
273 for (RegionList::reverse_iterator i = all.rbegin(); i != all.rend(); ++i) {
274 for (RegionList::reverse_iterator j = all.rbegin(); j != all.rend(); ++j) {
280 if ((*i)->muted() || (*j)->muted()) {
284 if ((*i)->position() == (*j)->position() && ((*i)->length() == (*j)->length())) {
285 /* precise overlay: no xfade */
289 if ((*i)->position() == (*j)->position() || ((*i)->last_frame() == (*j)->last_frame())) {
290 /* starts or ends match: no xfade */
294 boost::shared_ptr<AudioRegion> top;
295 boost::shared_ptr<AudioRegion> bottom;
297 if ((*i)->layer() < (*j)->layer()) {
298 top = boost::dynamic_pointer_cast<AudioRegion> (*j);
299 bottom = boost::dynamic_pointer_cast<AudioRegion> (*i);
301 top = boost::dynamic_pointer_cast<AudioRegion> (*i);
302 bottom = boost::dynamic_pointer_cast<AudioRegion> (*j);
305 if (!top->opaque ()) {
309 Evoral::OverlapType const c = top->coverage (bottom->position(), bottom->last_frame());
311 if (c == Evoral::OverlapStart) {
313 /* top starts within bottom but covers bottom's end */
315 /* { ==== top ============ }
316 * [---- bottom -------------------]
319 if (done_start.find (top) == done_start.end() && done_end.find (bottom) == done_end.end ()) {
321 /* Top's fade-in will cause an implicit fade-out of bottom */
323 if (top->fade_in_is_xfade() && top->fade_in_is_short()) {
325 /* its already an xfade. if its
326 * really short, leave it
333 if (_capture_insertion_underway) {
334 len = _session.config.get_short_xfade_seconds() * _session.frame_rate();
336 switch (_session.config.get_xfade_model()) {
338 len = bottom->last_frame () - top->first_frame ();
339 top->set_fade_in_is_short (false);
342 len = _session.config.get_short_xfade_seconds() * _session.frame_rate();
343 top->set_fade_in_is_short (true);
348 top->set_fade_in_active (true);
349 top->set_fade_in_is_xfade (true);
351 /* XXX may 2012: -3dB and -6dB curves
352 * are the same right now
355 switch (_session.config.get_xfade_choice ()) {
356 case ConstantPowerMinus3dB:
357 top->set_fade_in (FadeConstantPower, len);
359 case ConstantPowerMinus6dB:
360 top->set_fade_in (FadeConstantPower, len);
363 top->set_fade_in_length (len);
368 done_start.insert (top);
371 } else if (c == Evoral::OverlapEnd) {
373 /* top covers start of bottom but ends within it */
375 /* [---- top ------------------------]
376 * { ==== bottom ============ }
379 if (done_end.find (top) == done_end.end() && done_start.find (bottom) == done_start.end ()) {
380 /* Top's fade-out will cause an implicit fade-in of bottom */
383 if (top->fade_out_is_xfade() && top->fade_out_is_short()) {
385 /* its already an xfade. if its
386 * really short, leave it
393 if (_capture_insertion_underway) {
394 len = _session.config.get_short_xfade_seconds() * _session.frame_rate();
396 switch (_session.config.get_xfade_model()) {
398 len = top->last_frame () - bottom->first_frame ();
401 len = _session.config.get_short_xfade_seconds() * _session.frame_rate();
406 top->set_fade_out_active (true);
407 top->set_fade_out_is_xfade (true);
409 switch (_session.config.get_xfade_choice ()) {
410 case ConstantPowerMinus3dB:
411 top->set_fade_out (FadeConstantPower, len);
413 case ConstantPowerMinus6dB:
414 top->set_fade_out (FadeConstantPower, len);
417 top->set_fade_out_length (len);
422 done_end.insert (top);
428 for (RegionList::iterator i = starts->begin(); i != starts->end(); ++i) {
429 if (done_start.find (*i) == done_start.end()) {
430 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (*i);
431 if (r->fade_in_is_xfade()) {
432 r->set_default_fade_in ();
437 for (RegionList::iterator i = ends->begin(); i != ends->end(); ++i) {
438 if (done_end.find (*i) == done_end.end()) {
439 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (*i);
440 if (r->fade_out_is_xfade()) {
441 r->set_default_fade_out ();
448 AudioPlaylist::dump () const
450 boost::shared_ptr<Region>r;
452 cerr << "Playlist \"" << _name << "\" " << endl
453 << regions.size() << " regions "
456 for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
458 cerr << " " << r->name() << " @ " << r << " ["
459 << r->start() << "+" << r->length()
469 AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
471 boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
477 bool changed = false;
480 RegionWriteLock rlock (this);
482 for (RegionList::iterator i = regions.begin(); i != regions.end(); ) {
484 RegionList::iterator tmp = i;
487 if ((*i) == region) {
495 for (set<boost::shared_ptr<Region> >::iterator x = all_regions.begin(); x != all_regions.end(); ) {
497 set<boost::shared_ptr<Region> >::iterator xtmp = x;
500 if ((*x) == region) {
501 all_regions.erase (x);
508 region->set_playlist (boost::shared_ptr<Playlist>());
512 /* overload this, it normally means "removed", not destroyed */
513 notify_region_removed (region);
520 AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
522 if (in_flush || in_set_state) {
526 PropertyChange our_interests;
528 our_interests.add (Properties::fade_in_active);
529 our_interests.add (Properties::fade_out_active);
530 our_interests.add (Properties::scale_amplitude);
531 our_interests.add (Properties::envelope_active);
532 our_interests.add (Properties::envelope);
533 our_interests.add (Properties::fade_in);
534 our_interests.add (Properties::fade_out);
536 bool parent_wants_notify;
538 parent_wants_notify = Playlist::region_changed (what_changed, region);
540 if (parent_wants_notify || (what_changed.contains (our_interests))) {
541 notify_contents_changed ();
548 AudioPlaylist::pre_combine (vector<boost::shared_ptr<Region> >& copies)
550 RegionSortByPosition cmp;
551 boost::shared_ptr<AudioRegion> ar;
553 sort (copies.begin(), copies.end(), cmp);
555 ar = boost::dynamic_pointer_cast<AudioRegion> (copies.front());
557 /* disable fade in of the first region */
560 ar->set_fade_in_active (false);
563 ar = boost::dynamic_pointer_cast<AudioRegion> (copies.back());
565 /* disable fade out of the last region */
568 ar->set_fade_out_active (false);
573 AudioPlaylist::post_combine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
575 RegionSortByPosition cmp;
576 boost::shared_ptr<AudioRegion> ar;
577 boost::shared_ptr<AudioRegion> cr;
579 if ((cr = boost::dynamic_pointer_cast<AudioRegion> (compound_region)) == 0) {
583 sort (originals.begin(), originals.end(), cmp);
585 ar = boost::dynamic_pointer_cast<AudioRegion> (originals.front());
587 /* copy the fade in of the first into the compound region */
590 cr->set_fade_in (ar->fade_in());
593 ar = boost::dynamic_pointer_cast<AudioRegion> (originals.back());
596 /* copy the fade out of the last into the compound region */
597 cr->set_fade_out (ar->fade_out());
602 AudioPlaylist::pre_uncombine (vector<boost::shared_ptr<Region> >& originals, boost::shared_ptr<Region> compound_region)
604 RegionSortByPosition cmp;
605 boost::shared_ptr<AudioRegion> ar;
606 boost::shared_ptr<AudioRegion> cr = boost::dynamic_pointer_cast<AudioRegion>(compound_region);
612 sort (originals.begin(), originals.end(), cmp);
614 /* no need to call clear_changes() on the originals because that is
615 * done within Playlist::uncombine ()
618 for (vector<boost::shared_ptr<Region> >::iterator i = originals.begin(); i != originals.end(); ++i) {
620 if ((ar = boost::dynamic_pointer_cast<AudioRegion> (*i)) == 0) {
624 /* scale the uncombined regions by any gain setting for the
628 ar->set_scale_amplitude (ar->scale_amplitude() * cr->scale_amplitude());
630 if (i == originals.begin()) {
632 /* copy the compound region's fade in back into the first
636 if (cr->fade_in()->back()->when <= ar->length()) {
637 /* don't do this if the fade is longer than the
640 ar->set_fade_in (cr->fade_in());
644 } else if (*i == originals.back()) {
646 /* copy the compound region's fade out back into the last
650 if (cr->fade_out()->back()->when <= ar->length()) {
651 /* don't do this if the fade is longer than the
654 ar->set_fade_out (cr->fade_out());
659 _session.add_command (new StatefulDiffCommand (*i));
664 AudioPlaylist::set_state (const XMLNode& node, int version)
666 return Playlist::set_state (node, version);
670 AudioPlaylist::load_legacy_crossfades (const XMLNode& node, int version)
672 /* Read legacy Crossfade nodes and set up region fades accordingly */
674 XMLNodeList children = node.children ();
675 for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
676 if ((*i)->name() == X_("Crossfade")) {
678 XMLProperty* p = (*i)->property (X_("active"));
681 if (!string_is_affirmative (p->value())) {
685 if ((p = (*i)->property (X_("in"))) == 0) {
689 boost::shared_ptr<Region> in = region_by_id (PBD::ID (p->value ()));
692 warning << string_compose (_("Legacy crossfade involved an incoming region not present in playlist \"%1\" - crossfade discarded"),
698 boost::shared_ptr<AudioRegion> in_a = boost::dynamic_pointer_cast<AudioRegion> (in);
701 if ((p = (*i)->property (X_("out"))) == 0) {
705 boost::shared_ptr<Region> out = region_by_id (PBD::ID (p->value ()));
708 warning << string_compose (_("Legacy crossfade involved an outgoing region not present in playlist \"%1\" - crossfade discarded"),
714 boost::shared_ptr<AudioRegion> out_a = boost::dynamic_pointer_cast<AudioRegion> (out);
717 /* now decide whether to add a fade in or fade out
718 * xfade and to which region
721 if (in->layer() <= out->layer()) {
723 /* incoming region is below the outgoing one,
724 * so apply a fade out to the outgoing one
727 const XMLNodeList c = (*i)->children ();
729 for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
730 if ((*j)->name() == X_("FadeOut")) {
731 out_a->fade_out()->set_state (**j, version);
732 } else if ((*j)->name() == X_("FadeIn")) {
733 out_a->inverse_fade_out()->set_state (**j, version);
737 if ((p = (*i)->property ("follow-overlap")) != 0) {
738 out_a->set_fade_out_is_short (!string_is_affirmative (p->value()));
740 out_a->set_fade_out_is_short (false);
743 out_a->set_fade_out_is_xfade (true);
744 out_a->set_fade_out_active (true);
748 /* apply a fade in to the incoming region,
749 * since its above the outgoing one
752 const XMLNodeList c = (*i)->children ();
754 for (XMLNodeConstIterator j = c.begin(); j != c.end(); ++j) {
755 if ((*j)->name() == X_("FadeIn")) {
756 in_a->fade_in()->set_state (**j, version);
757 } else if ((*j)->name() == X_("FadeOut")) {
758 in_a->inverse_fade_in()->set_state (**j, version);
762 if ((p = (*i)->property ("follow-overlap")) != 0) {
763 in_a->set_fade_in_is_short (!string_is_affirmative (p->value()));
765 in_a->set_fade_in_is_short (false);
768 in_a->set_fade_in_is_xfade (true);
769 in_a->set_fade_in_active (true);