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