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