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