When a region movement is undone, prevent the resulting movement from triggering...
[ardour.git] / libs / ardour / audio_playlist.cc
1 /*
2     Copyright (C) 2003 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #include <algorithm>
21
22 #include <cstdlib>
23
24
25 #include "ardour/types.h"
26 #include "ardour/debug.h"
27 #include "ardour/configuration.h"
28 #include "ardour/audioplaylist.h"
29 #include "ardour/audioregion.h"
30 #include "ardour/crossfade.h"
31 #include "ardour/crossfade_compare.h"
32 #include "ardour/session.h"
33 #include "pbd/enumwriter.h"
34
35 #include "i18n.h"
36
37 using namespace ARDOUR;
38 using namespace std;
39 using namespace PBD;
40
41 AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden)
42         : Playlist (session, node, DataType::AUDIO, hidden)
43 {
44 #ifndef NDEBUG
45         const XMLProperty* prop = node.property("type");
46         assert(!prop || DataType(prop->value()) == DataType::AUDIO);
47 #endif
48
49         in_set_state++;
50         set_state (node, Stateful::loading_state_version);
51         in_set_state--;
52 }
53
54 AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden)
55         : Playlist (session, name, DataType::AUDIO, hidden)
56 {
57 }
58
59 AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, string name, bool hidden)
60         : Playlist (other, name, hidden)
61 {
62         RegionList::const_iterator in_o  = other->regions.begin();
63         RegionList::iterator in_n = regions.begin();
64
65         while (in_o != other->regions.end()) {
66                 boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(*in_o);
67
68                 // We look only for crossfades which begin with the current region, so we don't get doubles
69                 for (Crossfades::const_iterator xfades = other->_crossfades.begin(); xfades != other->_crossfades.end(); ++xfades) {
70                         if ((*xfades)->in() == ar) {
71                                 // We found one! Now copy it!
72
73                                 RegionList::const_iterator out_o = other->regions.begin();
74                                 RegionList::const_iterator out_n = regions.begin();
75
76                                 while (out_o != other->regions.end()) {
77
78                                         boost::shared_ptr<AudioRegion>ar2 = boost::dynamic_pointer_cast<AudioRegion>(*out_o);
79
80                                         if ((*xfades)->out() == ar2) {
81                                                 boost::shared_ptr<AudioRegion>in  = boost::dynamic_pointer_cast<AudioRegion>(*in_n);
82                                                 boost::shared_ptr<AudioRegion>out = boost::dynamic_pointer_cast<AudioRegion>(*out_n);
83                                                 boost::shared_ptr<Crossfade> new_fade = boost::shared_ptr<Crossfade> (new Crossfade (*xfades, in, out));
84                                                 add_crossfade(new_fade);
85                                                 break;
86                                         }
87
88                                         out_o++;
89                                         out_n++;
90                                 }
91 //                              cerr << "HUH!? second region in the crossfade not found!" << endl;
92                         }
93                 }
94
95                 in_o++;
96                 in_n++;
97         }
98 }
99
100 AudioPlaylist::AudioPlaylist (boost::shared_ptr<const AudioPlaylist> other, nframes_t start, nframes_t cnt, string name, bool hidden)
101         : Playlist (other, start, cnt, name, hidden)
102 {
103         /* this constructor does NOT notify others (session) */
104 }
105
106 AudioPlaylist::~AudioPlaylist ()
107 {
108         _crossfades.clear ();
109 }
110
111 struct RegionSortByLayer {
112     bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) {
113             return a->layer() < b->layer();
114     }
115 };
116
117 ARDOUR::nframes_t
118 AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, nframes_t start,
119                      nframes_t cnt, unsigned chan_n)
120 {
121         nframes_t ret = cnt;
122         nframes_t end;
123         nframes_t read_frames;
124         nframes_t skip_frames;
125
126         /* optimizing this memset() away involves a lot of conditionals
127            that may well cause more of a hit due to cache misses
128            and related stuff than just doing this here.
129
130            it would be great if someone could measure this
131            at some point.
132
133            one way or another, parts of the requested area
134            that are not written to by Region::region_at()
135            for all Regions that cover the area need to be
136            zeroed.
137         */
138
139         memset (buf, 0, sizeof (Sample) * cnt);
140
141         /* this function is never called from a realtime thread, so
142            its OK to block (for short intervals).
143         */
144
145         Glib::RecMutex::Lock rm (region_lock);
146
147         end =  start + cnt - 1;
148         read_frames = 0;
149         skip_frames = 0;
150         _read_data_count = 0;
151
152         _read_data_count = 0;
153
154         RegionList* rlist = regions_to_read (start, start+cnt);
155
156         if (rlist->empty()) {
157                 delete rlist;
158                 return cnt;
159         }
160
161         map<uint32_t,vector<boost::shared_ptr<Region> > > relevant_regions;
162         map<uint32_t,vector<boost::shared_ptr<Crossfade> > > relevant_xfades;
163         vector<uint32_t> relevant_layers;
164
165         for (RegionList::iterator i = rlist->begin(); i != rlist->end(); ++i) {
166                 if ((*i)->coverage (start, end) != OverlapNone) {
167                         relevant_regions[(*i)->layer()].push_back (*i);
168                         relevant_layers.push_back ((*i)->layer());
169                 }
170         }
171
172         for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
173                 if ((*i)->coverage (start, end) != OverlapNone) {
174                         relevant_xfades[(*i)->upper_layer()].push_back (*i);
175                 }
176         }
177
178 //      RegionSortByLayer layer_cmp;
179 //      relevant_regions.sort (layer_cmp);
180
181         /* XXX this whole per-layer approach is a hack that
182            should be removed once Crossfades become
183            CrossfadeRegions and we just grab a list of relevant
184            regions and call read_at() on all of them.
185         */
186
187         sort (relevant_layers.begin(), relevant_layers.end());
188
189         for (vector<uint32_t>::iterator l = relevant_layers.begin(); l != relevant_layers.end(); ++l) {
190
191                 vector<boost::shared_ptr<Region> > r (relevant_regions[*l]);
192                 vector<boost::shared_ptr<Crossfade> >& x (relevant_xfades[*l]);
193
194
195                 for (vector<boost::shared_ptr<Region> >::iterator i = r.begin(); i != r.end(); ++i) {
196                         boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(*i);
197                         DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("read from region %1\n", ar->name()));
198                         assert(ar);
199                         ar->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n, read_frames, skip_frames);
200                         _read_data_count += ar->read_data_count();
201                 }
202
203                 for (vector<boost::shared_ptr<Crossfade> >::iterator i = x.begin(); i != x.end(); ++i) {
204                         (*i)->read_at (buf, mixdown_buffer, gain_buffer, start, cnt, chan_n);
205
206                         /* don't JACK up _read_data_count, since its the same data as we just
207                            read from the regions, and the OS should handle that for us.
208                         */
209                 }
210         }
211
212         delete rlist;
213         return ret;
214 }
215
216
217 void
218 AudioPlaylist::remove_dependents (boost::shared_ptr<Region> region)
219 {
220         boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
221
222         if (in_set_state) {
223                 return;
224         }
225
226         if (r == 0) {
227                 fatal << _("programming error: non-audio Region passed to remove_overlap in audio playlist")
228                       << endmsg;
229                 return;
230         }
231
232         for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ) {
233
234                 if ((*i)->involves (r)) {
235                         i = _crossfades.erase (i);
236                 } else {
237                         ++i;
238                 }
239         }
240 }
241
242
243 void
244 AudioPlaylist::flush_notifications (bool from_undo)
245 {
246         Playlist::flush_notifications (from_undo);
247
248         if (in_flush) {
249                 return;
250         }
251
252         in_flush = true;
253
254         Crossfades::iterator a;
255         for (a = _pending_xfade_adds.begin(); a != _pending_xfade_adds.end(); ++a) {
256                 NewCrossfade (*a); /* EMIT SIGNAL */
257         }
258
259         _pending_xfade_adds.clear ();
260
261         in_flush = false;
262 }
263
264 void
265 AudioPlaylist::refresh_dependents (boost::shared_ptr<Region> r)
266 {
267         boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(r);
268         set<boost::shared_ptr<Crossfade> > updated;
269
270         if (ar == 0) {
271                 return;
272         }
273
274         for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) {
275
276                 Crossfades::iterator tmp;
277
278                 tmp = x;
279                 ++tmp;
280
281                 /* only update them once */
282
283                 if ((*x)->involves (ar)) {
284
285                         pair<set<boost::shared_ptr<Crossfade> >::iterator, bool> const u = updated.insert (*x);
286
287                         if (u.second) {
288                                 /* x was successfully inserted into the set, so it has not already been updated */
289                                 try {
290                                         (*x)->refresh ();
291                                 }
292
293                                 catch (Crossfade::NoCrossfadeHere& err) {
294                                         // relax, Invalidated during refresh
295                                 }
296                         }
297                 }
298
299                 x = tmp;
300         }
301 }
302
303 void
304 AudioPlaylist::finalize_split_region (boost::shared_ptr<Region> o, boost::shared_ptr<Region> l, boost::shared_ptr<Region> r)
305 {
306         boost::shared_ptr<AudioRegion> orig  = boost::dynamic_pointer_cast<AudioRegion>(o);
307         boost::shared_ptr<AudioRegion> left  = boost::dynamic_pointer_cast<AudioRegion>(l);
308         boost::shared_ptr<AudioRegion> right = boost::dynamic_pointer_cast<AudioRegion>(r);
309
310         for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) {
311                 Crossfades::iterator tmp;
312                 tmp = x;
313                 ++tmp;
314
315                 boost::shared_ptr<Crossfade> fade;
316
317                 if ((*x)->_in == orig) {
318                         if (! (*x)->covers(right->position())) {
319                                 fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, left, (*x)->_out));
320                         } else {
321                                 // Overlap, the crossfade is copied on the left side of the right region instead
322                                 fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, right, (*x)->_out));
323                         }
324                 }
325
326                 if ((*x)->_out == orig) {
327                         if (! (*x)->covers(right->position())) {
328                                 fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, (*x)->_in, right));
329                         } else {
330                                 // Overlap, the crossfade is copied on the right side of the left region instead
331                                 fade = boost::shared_ptr<Crossfade> (new Crossfade (*x, (*x)->_in, left));
332                         }
333                 }
334
335                 if (fade) {
336                         _crossfades.remove (*x);
337                         add_crossfade (fade);
338                 }
339                 x = tmp;
340         }
341 }
342
343 void
344 AudioPlaylist::check_dependents (boost::shared_ptr<Region> r, bool norefresh)
345 {
346         boost::shared_ptr<AudioRegion> other;
347         boost::shared_ptr<AudioRegion> region;
348         boost::shared_ptr<AudioRegion> top;
349         boost::shared_ptr<AudioRegion> bottom;
350         boost::shared_ptr<Crossfade>   xfade;
351         RegionList*  touched_regions = 0;
352
353         if (in_set_state || in_partition) {
354                 return;
355         }
356
357         if ((region = boost::dynamic_pointer_cast<AudioRegion> (r)) == 0) {
358                 fatal << _("programming error: non-audio Region tested for overlap in audio playlist")
359                       << endmsg;
360                 return;
361         }
362
363         if (!norefresh) {
364                 refresh_dependents (r);
365         }
366
367
368         if (!_session.config.get_auto_xfade()) {
369                 return;
370         }
371
372         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
373                 other = boost::dynamic_pointer_cast<AudioRegion> (*i);
374
375                 if (other == region) {
376                         continue;
377                 }
378
379                 if (other->muted() || region->muted()) {
380                         continue;
381                 }
382
383
384                 if (other->layer() < region->layer()) {
385                         top = region;
386                         bottom = other;
387                 } else {
388                         top = other;
389                         bottom = region;
390                 }
391
392                 if (!top->opaque()) {
393                         continue;
394                 }
395
396                 OverlapType c = top->coverage (bottom->position(), bottom->last_frame());
397
398                 delete touched_regions;
399                 touched_regions = 0;
400
401                 try {
402                         nframes_t xfade_length;
403                         switch (c) {
404                         case OverlapNone:
405                                 break;
406
407                         case OverlapInternal:
408                                  /* {=============== top  =============}
409                                   *     [ ----- bottom  ------- ]
410                                   */
411                                 break;
412
413                         case OverlapExternal:
414
415                                 /*     [ -------- top ------- ]
416                                  * {=========== bottom =============}
417                                  */
418
419                                 /* to avoid discontinuities at the region boundaries of an internal
420                                    overlap (this region is completely within another), we create
421                                    two hidden crossfades at each boundary. this is not dependent
422                                    on the auto-xfade option, because we require it as basic
423                                    audio engineering.
424                                 */
425
426                                 xfade_length = min ((framecnt_t) 720, top->length());
427
428                                 if (top_region_at (top->first_frame()) == top) {
429
430                                         xfade = boost::shared_ptr<Crossfade> (new Crossfade (top, bottom, xfade_length, top->first_frame(), StartOfIn));
431                                         add_crossfade (xfade);
432                                 }
433
434                                 if (top_region_at (top->last_frame() - 1) == top) {
435
436                                         /*
437                                            only add a fade out if there is no region on top of the end of 'top' (which
438                                            would cover it).
439                                         */
440
441                                         xfade = boost::shared_ptr<Crossfade> (new Crossfade (bottom, top, xfade_length, top->last_frame() - xfade_length, EndOfOut));
442                                         add_crossfade (xfade);
443                                 }
444                                 break;
445                         case OverlapStart:
446
447                                 /*                   { ==== top ============ }
448                                  *   [---- bottom -------------------]
449                                  */
450
451                                 if (_session.config.get_xfade_model() == FullCrossfade) {
452                                         touched_regions = regions_touched (top->first_frame(), bottom->last_frame());
453                                         if (touched_regions->size() <= 2) {
454                                                 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active()));
455                                                 add_crossfade (xfade);
456                                         }
457                                 } else {
458
459                                         touched_regions = regions_touched (top->first_frame(),
460                                                                            top->first_frame() + min ((framecnt_t) _session.config.get_short_xfade_seconds() * _session.frame_rate(),
461                                                                                                      top->length()));
462                                         if (touched_regions->size() <= 2) {
463                                                 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active()));
464                                                 add_crossfade (xfade);
465                                         }
466                                 }
467                                 break;
468                         case OverlapEnd:
469
470
471                                 /* [---- top ------------------------]
472                                  *                { ==== bottom ============ }
473                                  */
474
475                                 if (_session.config.get_xfade_model() == FullCrossfade) {
476
477                                         touched_regions = regions_touched (bottom->first_frame(), top->last_frame());
478                                         if (touched_regions->size() <= 2) {
479                                                 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other,
480                                                                                                      _session.config.get_xfade_model(), _session.config.get_xfades_active()));
481                                                 add_crossfade (xfade);
482                                         }
483
484                                 } else {
485                                         touched_regions = regions_touched (bottom->first_frame(),
486                                                                            bottom->first_frame() + min ((framecnt_t)_session.config.get_short_xfade_seconds() * _session.frame_rate(),
487                                                                                                         bottom->length()));
488                                         if (touched_regions->size() <= 2) {
489                                                 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other, _session.config.get_xfade_model(), _session.config.get_xfades_active()));
490                                                 add_crossfade (xfade);
491                                         }
492                                 }
493                                 break;
494                         default:
495                                 xfade = boost::shared_ptr<Crossfade> (new Crossfade (region, other,
496                                                                                      _session.config.get_xfade_model(), _session.config.get_xfades_active()));
497                                 add_crossfade (xfade);
498                         }
499                 }
500
501                 catch (failed_constructor& err) {
502                         continue;
503                 }
504
505                 catch (Crossfade::NoCrossfadeHere& err) {
506                         continue;
507                 }
508
509         }
510
511         delete touched_regions;
512 }
513
514 void
515 AudioPlaylist::add_crossfade (boost::shared_ptr<Crossfade> xfade)
516 {
517         Crossfades::iterator ci;
518
519         for (ci = _crossfades.begin(); ci != _crossfades.end(); ++ci) {
520                 if (*(*ci) == *xfade) { // Crossfade::operator==()
521                         break;
522                 }
523         }
524
525         if (ci != _crossfades.end()) {
526                 // it will just go away
527         } else {
528                 _crossfades.push_back (xfade);
529
530                 xfade->Invalidated.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_invalidated, this, _1));
531                 xfade->PropertyChanged.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_changed, this, _1));
532
533                 notify_crossfade_added (xfade);
534         }
535 }
536
537 void AudioPlaylist::notify_crossfade_added (boost::shared_ptr<Crossfade> x)
538 {
539         if (g_atomic_int_get(&block_notifications)) {
540                 _pending_xfade_adds.insert (_pending_xfade_adds.end(), x);
541         } else {
542                 NewCrossfade (x); /* EMIT SIGNAL */
543         }
544 }
545
546 void
547 AudioPlaylist::crossfade_invalidated (boost::shared_ptr<Region> r)
548 {
549         Crossfades::iterator i;
550         boost::shared_ptr<Crossfade> xfade = boost::dynamic_pointer_cast<Crossfade> (r);
551
552         xfade->in()->resume_fade_in ();
553         xfade->out()->resume_fade_out ();
554
555         if ((i = find (_crossfades.begin(), _crossfades.end(), xfade)) != _crossfades.end()) {
556                 _crossfades.erase (i);
557         }
558 }
559
560 int
561 AudioPlaylist::set_state (const XMLNode& node, int version)
562 {
563         XMLNode *child;
564         XMLNodeList nlist;
565         XMLNodeConstIterator niter;
566
567         in_set_state++;
568
569         Playlist::set_state (node, version);
570
571         freeze ();
572
573         nlist = node.children();
574
575         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
576
577                 child = *niter;
578
579                 if (child->name() != "Crossfade") {
580                         continue;
581                 }
582
583                 try {
584                         boost::shared_ptr<Crossfade> xfade = boost::shared_ptr<Crossfade> (new Crossfade (*((const Playlist *)this), *child));
585                         _crossfades.push_back (xfade);
586                         xfade->Invalidated.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_invalidated, this, _1));
587                         xfade->PropertyChanged.connect_same_thread (*this, boost::bind (&AudioPlaylist::crossfade_changed, this, _1));
588                         NewCrossfade(xfade);
589                 }
590
591                 catch (failed_constructor& err) {
592                         //      cout << string_compose (_("could not create crossfade object in playlist %1"),
593                         //        _name)
594                         //    << endl;
595                         continue;
596                 }
597         }
598
599         thaw ();
600         in_set_state--;
601
602         return 0;
603 }
604
605 void
606 AudioPlaylist::clear (bool with_signals)
607 {
608         _crossfades.clear ();
609         Playlist::clear (with_signals);
610 }
611
612 XMLNode&
613 AudioPlaylist::state (bool full_state)
614 {
615         XMLNode& node = Playlist::state (full_state);
616
617         if (full_state) {
618                 for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
619                         node.add_child_nocopy ((*i)->get_state());
620                 }
621         }
622
623         return node;
624 }
625
626 void
627 AudioPlaylist::dump () const
628 {
629         boost::shared_ptr<Region>r;
630         boost::shared_ptr<Crossfade> x;
631
632         cerr << "Playlist \"" << _name << "\" " << endl
633              << regions.size() << " regions "
634              << _crossfades.size() << " crossfades"
635              << endl;
636
637         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
638                 r = *i;
639                 cerr << "  " << r->name() << " @ " << r << " ["
640                      << r->start() << "+" << r->length()
641                      << "] at "
642                      << r->position()
643                      << " on layer "
644                      << r->layer ()
645                      << endl;
646         }
647
648         for (Crossfades::const_iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
649                 x = *i;
650                 cerr << "  xfade ["
651                      << x->out()->name()
652                      << ','
653                      << x->in()->name()
654                      << " @ "
655                      << x->position()
656                      << " length = "
657                      << x->length ()
658                      << " active ? "
659                      << (x->active() ? "yes" : "no")
660                      << endl;
661         }
662 }
663
664 bool
665 AudioPlaylist::destroy_region (boost::shared_ptr<Region> region)
666 {
667         boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
668
669         if (!r) {
670                 return false;
671         }
672
673         bool changed = false;
674         Crossfades::iterator c, ctmp;
675         set<boost::shared_ptr<Crossfade> > unique_xfades;
676
677         {
678                 RegionLock rlock (this);
679
680                 for (RegionList::iterator i = regions.begin(); i != regions.end(); ) {
681
682                         RegionList::iterator tmp = i;
683                         ++tmp;
684
685                         if ((*i) == region) {
686                                 regions.erase (i);
687                                 changed = true;
688                         }
689
690                         i = tmp;
691                 }
692
693                 for (set<boost::shared_ptr<Region> >::iterator x = all_regions.begin(); x != all_regions.end(); ) {
694
695                         set<boost::shared_ptr<Region> >::iterator xtmp = x;
696                         ++xtmp;
697
698                         if ((*x) == region) {
699                                 all_regions.erase (x);
700                                 changed = true;
701                         }
702
703                         x = xtmp;
704                 }
705
706                 region->set_playlist (boost::shared_ptr<Playlist>());
707         }
708
709         for (c = _crossfades.begin(); c != _crossfades.end(); ) {
710                 ctmp = c;
711                 ++ctmp;
712
713                 if ((*c)->involves (r)) {
714                         unique_xfades.insert (*c);
715                         _crossfades.erase (c);
716                 }
717
718                 c = ctmp;
719         }
720
721         if (changed) {
722                 /* overload this, it normally means "removed", not destroyed */
723                 notify_region_removed (region);
724         }
725
726         return changed;
727 }
728
729 void
730 AudioPlaylist::crossfade_changed (const PropertyChange&)
731 {
732         if (in_flush || in_set_state) {
733                 return;
734         }
735
736         /* XXX is there a loop here? can an xfade change not happen
737            due to a playlist change? well, sure activation would
738            be an example. maybe we should check the type of change
739            that occured.
740         */
741
742         notify_contents_changed ();
743 }
744
745 bool
746 AudioPlaylist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
747 {
748         if (in_flush || in_set_state) {
749                 return false;
750         }
751
752         PropertyChange our_interests;
753
754         our_interests.add (Properties::fade_in_active);
755         our_interests.add (Properties::fade_out_active);
756         our_interests.add (Properties::scale_amplitude);
757         our_interests.add (Properties::envelope_active);
758         our_interests.add (Properties::envelope);
759         our_interests.add (Properties::fade_in);
760         our_interests.add (Properties::fade_out);
761
762         bool parent_wants_notify;
763
764         parent_wants_notify = Playlist::region_changed (what_changed, region);
765
766         if (parent_wants_notify || (what_changed.contains (our_interests))) {
767                 notify_contents_changed ();
768         }
769
770         return true;
771 }
772
773 void
774 AudioPlaylist::crossfades_at (nframes_t frame, Crossfades& clist)
775 {
776         RegionLock rlock (this);
777
778         for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
779                 nframes_t start, end;
780
781                 start = (*i)->position();
782                 end = start + (*i)->overlap_length(); // not length(), important difference
783
784                 if (frame >= start && frame <= end) {
785                         clist.push_back (*i);
786                 }
787         }
788 }
789
790 void
791 AudioPlaylist::foreach_crossfade (boost::function<void (boost::shared_ptr<Crossfade>)> s)
792 {
793         RegionLock rl (this, false);
794         for (Crossfades::iterator i = _crossfades.begin(); i != _crossfades.end(); ++i) {
795                 s (*i);
796         }
797 }