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