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