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