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