Fairly major change to the way in which crossfades are handled;
[ardour.git] / libs / ardour / region.cc
1 /*
2     Copyright (C) 2000-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 <iostream>
21 #include <cmath>
22 #include <climits>
23 #include <algorithm>
24 #include <sstream>
25
26 #include <glibmm/thread.h>
27 #include "pbd/xml++.h"
28 #include "pbd/stacktrace.h"
29 #include "pbd/enumwriter.h"
30
31 #include "ardour/debug.h"
32 #include "ardour/file_source.h"
33 #include "ardour/filter.h"
34 #include "ardour/playlist.h"
35 #include "ardour/playlist_source.h"
36 #include "ardour/profile.h"
37 #include "ardour/region.h"
38 #include "ardour/region_factory.h"
39 #include "ardour/session.h"
40 #include "ardour/source.h"
41 #include "ardour/source_factory.h"
42 #include "ardour/tempo.h"
43 #include "ardour/utils.h"
44
45 #include "i18n.h"
46
47 using namespace std;
48 using namespace ARDOUR;
49 using namespace PBD;
50
51 namespace ARDOUR {
52         namespace Properties {
53                 PBD::PropertyDescriptor<bool> muted;
54                 PBD::PropertyDescriptor<bool> opaque;
55                 PBD::PropertyDescriptor<bool> locked;
56                 PBD::PropertyDescriptor<bool> automatic;
57                 PBD::PropertyDescriptor<bool> whole_file;
58                 PBD::PropertyDescriptor<bool> import;
59                 PBD::PropertyDescriptor<bool> external;
60                 PBD::PropertyDescriptor<bool> sync_marked;
61                 PBD::PropertyDescriptor<bool> left_of_split;
62                 PBD::PropertyDescriptor<bool> right_of_split;
63                 PBD::PropertyDescriptor<bool> hidden;
64                 PBD::PropertyDescriptor<bool> position_locked;
65                 PBD::PropertyDescriptor<bool> valid_transients;
66                 PBD::PropertyDescriptor<framepos_t> start;
67                 PBD::PropertyDescriptor<framecnt_t> length;
68                 PBD::PropertyDescriptor<framepos_t> position;
69                 PBD::PropertyDescriptor<framecnt_t> sync_position;
70                 PBD::PropertyDescriptor<layer_t> layer;
71                 PBD::PropertyDescriptor<framepos_t> ancestral_start;
72                 PBD::PropertyDescriptor<framecnt_t> ancestral_length;
73                 PBD::PropertyDescriptor<float> stretch;
74                 PBD::PropertyDescriptor<float> shift;
75                 PBD::PropertyDescriptor<PositionLockStyle> position_lock_style;
76                 PBD::PropertyDescriptor<uint64_t> layering_index;
77         }
78 }
79
80 PBD::Signal2<void,boost::shared_ptr<ARDOUR::Region>,const PropertyChange&> Region::RegionPropertyChanged;
81
82 void
83 Region::make_property_quarks ()
84 {
85         Properties::muted.property_id = g_quark_from_static_string (X_("muted"));
86         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for muted = %1\n",       Properties::muted.property_id));
87         Properties::opaque.property_id = g_quark_from_static_string (X_("opaque"));
88         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for opaque = %1\n",      Properties::opaque.property_id));
89         Properties::locked.property_id = g_quark_from_static_string (X_("locked"));
90         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for locked = %1\n",      Properties::locked.property_id));
91         Properties::automatic.property_id = g_quark_from_static_string (X_("automatic"));
92         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for automatic = %1\n",   Properties::automatic.property_id));
93         Properties::whole_file.property_id = g_quark_from_static_string (X_("whole-file"));
94         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for whole-file = %1\n",  Properties::whole_file.property_id));
95         Properties::import.property_id = g_quark_from_static_string (X_("import"));
96         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for import = %1\n",      Properties::import.property_id));
97         Properties::external.property_id = g_quark_from_static_string (X_("external"));
98         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for external = %1\n",    Properties::external.property_id));
99         Properties::sync_marked.property_id = g_quark_from_static_string (X_("sync-marked"));
100         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for sync-marked = %1\n",         Properties::sync_marked.property_id));
101         Properties::left_of_split.property_id = g_quark_from_static_string (X_("left-of-split"));
102         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for left-of-split = %1\n",       Properties::left_of_split.property_id));
103         Properties::right_of_split.property_id = g_quark_from_static_string (X_("right-of-split"));
104         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for right-of-split = %1\n",      Properties::right_of_split.property_id));
105         Properties::hidden.property_id = g_quark_from_static_string (X_("hidden"));
106         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for hidden = %1\n",      Properties::hidden.property_id));
107         Properties::position_locked.property_id = g_quark_from_static_string (X_("position-locked"));
108         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for position-locked = %1\n",     Properties::position_locked.property_id));
109         Properties::valid_transients.property_id = g_quark_from_static_string (X_("valid-transients"));
110         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for valid-transients = %1\n",    Properties::valid_transients.property_id));
111         Properties::start.property_id = g_quark_from_static_string (X_("start"));
112         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start = %1\n",       Properties::start.property_id));
113         Properties::length.property_id = g_quark_from_static_string (X_("length"));
114         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length = %1\n",      Properties::length.property_id));
115         Properties::position.property_id = g_quark_from_static_string (X_("position"));
116         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for position = %1\n",    Properties::position.property_id));
117         Properties::sync_position.property_id = g_quark_from_static_string (X_("sync-position"));
118         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for sync-position = %1\n",       Properties::sync_position.property_id));
119         Properties::layer.property_id = g_quark_from_static_string (X_("layer"));
120         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for layer = %1\n",       Properties::layer.property_id));
121         Properties::ancestral_start.property_id = g_quark_from_static_string (X_("ancestral-start"));
122         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for ancestral-start = %1\n",     Properties::ancestral_start.property_id));
123         Properties::ancestral_length.property_id = g_quark_from_static_string (X_("ancestral-length"));
124         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for ancestral-length = %1\n",    Properties::ancestral_length.property_id));
125         Properties::stretch.property_id = g_quark_from_static_string (X_("stretch"));
126         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for stretch = %1\n",     Properties::stretch.property_id));
127         Properties::shift.property_id = g_quark_from_static_string (X_("shift"));
128         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for shift = %1\n",       Properties::shift.property_id));
129         Properties::position_lock_style.property_id = g_quark_from_static_string (X_("positional-lock-style"));
130         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for position_lock_style = %1\n",         Properties::position_lock_style.property_id));
131         Properties::layering_index.property_id = g_quark_from_static_string (X_("layering-index"));
132         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for layering_index = %1\n",      Properties::layering_index.property_id));
133 }
134
135 void
136 Region::register_properties ()
137 {
138         _xml_node_name = X_("Region");
139
140         add_property (_muted);
141         add_property (_opaque);
142         add_property (_locked);
143         add_property (_automatic);
144         add_property (_whole_file);
145         add_property (_import);
146         add_property (_external);
147         add_property (_sync_marked);
148         add_property (_left_of_split);
149         add_property (_right_of_split);
150         add_property (_hidden);
151         add_property (_position_locked);
152         add_property (_valid_transients);
153         add_property (_start);
154         add_property (_length);
155         add_property (_position);
156         add_property (_sync_position);
157         add_property (_ancestral_start);
158         add_property (_ancestral_length);
159         add_property (_stretch);
160         add_property (_shift);
161         add_property (_position_lock_style);
162         add_property (_layering_index);
163 }
164
165 #define REGION_DEFAULT_STATE(s,l) \
166         _sync_marked (Properties::sync_marked, false) \
167         , _left_of_split (Properties::left_of_split, false) \
168         , _right_of_split (Properties::right_of_split, false) \
169         , _valid_transients (Properties::valid_transients, false) \
170         , _start (Properties::start, (s))       \
171         , _length (Properties::length, (l))     \
172         , _position (Properties::position, 0) \
173         , _sync_position (Properties::sync_position, (s)) \
174         , _muted (Properties::muted, false) \
175         , _opaque (Properties::opaque, true) \
176         , _locked (Properties::locked, false) \
177         , _automatic (Properties::automatic, false) \
178         , _whole_file (Properties::whole_file, false) \
179         , _import (Properties::import, false) \
180         , _external (Properties::external, false) \
181         , _hidden (Properties::hidden, false) \
182         , _position_locked (Properties::position_locked, false) \
183         , _ancestral_start (Properties::ancestral_start, (s)) \
184         , _ancestral_length (Properties::ancestral_length, (l)) \
185         , _stretch (Properties::stretch, 1.0) \
186         , _shift (Properties::shift, 1.0) \
187         , _position_lock_style (Properties::position_lock_style, _type == DataType::AUDIO ? AudioTime : MusicTime) \
188         , _layering_index (Properties::layering_index, 0)
189
190 #define REGION_COPY_STATE(other) \
191           _sync_marked (Properties::sync_marked, other->_sync_marked) \
192         , _left_of_split (Properties::left_of_split, other->_left_of_split) \
193         , _right_of_split (Properties::right_of_split, other->_right_of_split) \
194         , _valid_transients (Properties::valid_transients, other->_valid_transients) \
195         , _start(Properties::start, other->_start)              \
196         , _length(Properties::length, other->_length)           \
197         , _position(Properties::position, other->_position)     \
198         , _sync_position(Properties::sync_position, other->_sync_position) \
199         , _muted (Properties::muted, other->_muted)             \
200         , _opaque (Properties::opaque, other->_opaque)          \
201         , _locked (Properties::locked, other->_locked)          \
202         , _automatic (Properties::automatic, other->_automatic) \
203         , _whole_file (Properties::whole_file, other->_whole_file) \
204         , _import (Properties::import, other->_import)          \
205         , _external (Properties::external, other->_external)    \
206         , _hidden (Properties::hidden, other->_hidden)          \
207         , _position_locked (Properties::position_locked, other->_position_locked) \
208         , _ancestral_start (Properties::ancestral_start, other->_ancestral_start) \
209         , _ancestral_length (Properties::ancestral_length, other->_ancestral_length) \
210         , _stretch (Properties::stretch, other->_stretch)       \
211         , _shift (Properties::shift, other->_shift)             \
212         , _position_lock_style (Properties::position_lock_style, other->_position_lock_style) \
213         , _layering_index (Properties::layering_index, other->_layering_index)
214
215 /* derived-from-derived constructor (no sources in constructor) */
216 Region::Region (Session& s, framepos_t start, framecnt_t length, const string& name, DataType type)
217         : SessionObject(s, name)
218         , _type(type)
219         , REGION_DEFAULT_STATE(start,length)
220         , _last_length (length)
221         , _last_position (0)
222         , _first_edit (EditChangesNothing)
223         , _layer (0)
224 {
225         register_properties ();
226
227         /* no sources at this point */
228 }
229
230 /** Basic Region constructor (many sources) */
231 Region::Region (const SourceList& srcs)
232         : SessionObject(srcs.front()->session(), "toBeRenamed")
233         , _type (srcs.front()->type())
234         , REGION_DEFAULT_STATE(0,0)
235         , _last_length (0)
236         , _last_position (0)
237         , _first_edit (EditChangesNothing)
238         , _layer (0)
239 {
240         register_properties ();
241
242         _type = srcs.front()->type();
243
244         use_sources (srcs);
245
246         assert(_sources.size() > 0);
247         assert (_type == srcs.front()->type());
248 }
249
250 /** Create a new Region from an existing one */
251 Region::Region (boost::shared_ptr<const Region> other)
252         : SessionObject(other->session(), other->name())
253         , _type (other->data_type())
254         , REGION_COPY_STATE (other)
255         , _last_length (other->_last_length)
256         , _last_position(other->_last_position) \
257         , _first_edit (EditChangesNothing)
258         , _layer (other->_layer)
259 {
260         register_properties ();
261
262         /* override state that may have been incorrectly inherited from the other region
263          */
264
265         _position = 0;
266         _locked = false;
267         _whole_file = false;
268         _hidden = false;
269
270         use_sources (other->_sources);
271
272         _position_lock_style = other->_position_lock_style;
273         _first_edit = other->_first_edit;
274
275         _start = 0; // It seems strange _start is not inherited here?
276
277         /* sync pos is relative to start of file. our start-in-file is now zero,
278            so set our sync position to whatever the the difference between
279            _start and _sync_pos was in the other region.
280
281            result is that our new sync pos points to the same point in our source(s)
282            as the sync in the other region did in its source(s).
283
284            since we start at zero in our source(s), it is not possible to use a sync point that
285            is before the start. reset it to _start if that was true in the other region.
286         */
287
288         if (other->sync_marked()) {
289                 if (other->_start < other->_sync_position) {
290                         /* sync pos was after the start point of the other region */
291                         _sync_position = other->_sync_position - other->_start;
292                 } else {
293                         /* sync pos was before the start point of the other region. not possible here. */
294                         _sync_marked = false;
295                         _sync_position = _start;
296                 }
297         } else {
298                 _sync_marked = false;
299                 _sync_position = _start;
300         }
301
302         if (Profile->get_sae()) {
303                 /* reset sync point to start if its ended up
304                    outside region bounds.
305                 */
306
307                 if (_sync_position < _start || _sync_position >= _start + _length) {
308                         _sync_marked = false;
309                         _sync_position = _start;
310                 }
311         }
312
313         assert (_type == other->data_type());
314 }
315
316 /** Create a new Region from part of an existing one.
317
318     the start within \a other is given by \a offset
319     (i.e. relative to the start of \a other's sources, the start is \a offset + \a other.start()
320 */
321 Region::Region (boost::shared_ptr<const Region> other, frameoffset_t offset)
322         : SessionObject(other->session(), other->name())
323         , _type (other->data_type())
324         , REGION_COPY_STATE (other)
325         , _last_length (other->_last_length)
326         , _last_position(other->_last_position) \
327         , _first_edit (EditChangesNothing)
328         , _layer (other->_layer)
329 {
330         register_properties ();
331
332         /* override state that may have been incorrectly inherited from the other region
333          */
334
335         _position = 0;
336         _locked = false;
337         _whole_file = false;
338         _hidden = false;
339
340         use_sources (other->_sources);
341
342         _start = other->_start + offset;
343
344         /* if the other region had a distinct sync point
345            set, then continue to use it as best we can.
346            otherwise, reset sync point back to start.
347         */
348
349         if (other->sync_marked()) {
350                 if (other->_sync_position < _start) {
351                         _sync_marked = false;
352                         _sync_position = _start;
353                 } else {
354                         _sync_position = other->_sync_position;
355                 }
356         } else {
357                 _sync_marked = false;
358                 _sync_position = _start;
359         }
360
361         if (Profile->get_sae()) {
362                 /* reset sync point to start if its ended up
363                    outside region bounds.
364                 */
365
366                 if (_sync_position < _start || _sync_position >= _start + _length) {
367                         _sync_marked = false;
368                         _sync_position = _start;
369                 }
370         }
371
372         assert (_type == other->data_type());
373 }
374
375 /** Create a copy of @param other but with different sources. Used by filters */
376 Region::Region (boost::shared_ptr<const Region> other, const SourceList& srcs)
377         : SessionObject (other->session(), other->name())
378         , _type (srcs.front()->type())
379         , REGION_COPY_STATE (other)
380         , _last_length (other->_last_length)
381         , _last_position (other->_last_position)
382         , _first_edit (EditChangesID)
383         , _layer (other->_layer)
384 {
385         register_properties ();
386
387         _locked = false;
388         _position_locked = false;
389
390         other->_first_edit = EditChangesName;
391
392         if (other->_extra_xml) {
393                 _extra_xml = new XMLNode (*other->_extra_xml);
394         } else {
395                 _extra_xml = 0;
396         }
397
398         use_sources (srcs);
399         assert(_sources.size() > 0);
400 }
401
402 Region::~Region ()
403 {
404         DEBUG_TRACE (DEBUG::Destruction, string_compose ("Region %1 destructor @ %2\n", _name, this));
405         drop_sources ();
406 }
407
408 void
409 Region::set_playlist (boost::weak_ptr<Playlist> wpl)
410 {
411         _playlist = wpl.lock();
412 }
413
414 bool
415 Region::set_name (const std::string& str)
416 {
417         if (_name != str) {
418                 SessionObject::set_name(str); // EMIT SIGNAL NameChanged()
419                 assert(_name == str);
420
421                 send_change (Properties::name);
422         }
423
424         return true;
425 }
426
427 void
428 Region::set_length (framecnt_t len)
429 {
430         //cerr << "Region::set_length() len = " << len << endl;
431         if (locked()) {
432                 return;
433         }
434
435         if (_length != len && len != 0) {
436
437                 /* check that the current _position wouldn't make the new
438                    length impossible.
439                 */
440
441                 if (max_framepos - len < _position) {
442                         return;
443                 }
444
445                 if (!verify_length (len)) {
446                         return;
447                 }
448
449
450                 _last_length = _length;
451                 set_length_internal (len);
452                 _whole_file = false;
453                 first_edit ();
454                 maybe_uncopy ();
455                 invalidate_transients ();
456
457                 if (!property_changes_suspended()) {
458                         recompute_at_end ();
459                 }
460
461                 send_change (Properties::length);
462         }
463 }
464
465 void
466 Region::set_length_internal (framecnt_t len)
467 {
468         _length = len;
469 }
470
471 void
472 Region::maybe_uncopy ()
473 {
474         /* this does nothing but marked a semantic moment once upon a time */
475 }
476
477 void
478 Region::first_edit ()
479 {
480         boost::shared_ptr<Playlist> pl (playlist());
481
482         if (_first_edit != EditChangesNothing && pl) {
483
484                 _name = RegionFactory::new_region_name (_name);
485                 _first_edit = EditChangesNothing;
486
487                 send_change (Properties::name);
488
489                 RegionFactory::CheckNewRegion (shared_from_this());
490         }
491 }
492
493 bool
494 Region::at_natural_position () const
495 {
496         boost::shared_ptr<Playlist> pl (playlist());
497
498         if (!pl) {
499                 return false;
500         }
501
502         boost::shared_ptr<Region> whole_file_region = get_parent();
503
504         if (whole_file_region) {
505                 if (_position == whole_file_region->position() + _start) {
506                         return true;
507                 }
508         }
509
510         return false;
511 }
512
513 void
514 Region::move_to_natural_position ()
515 {
516         boost::shared_ptr<Playlist> pl (playlist());
517
518         if (!pl) {
519                 return;
520         }
521
522         boost::shared_ptr<Region> whole_file_region = get_parent();
523
524         if (whole_file_region) {
525                 set_position (whole_file_region->position() + _start);
526         }
527 }
528
529 void
530 Region::special_set_position (framepos_t pos)
531 {
532         /* this is used when creating a whole file region as
533            a way to store its "natural" or "captured" position.
534         */
535
536         _position = _position;
537         _position = pos;
538 }
539
540 void
541 Region::set_position_lock_style (PositionLockStyle ps)
542 {
543         if (_position_lock_style != ps) {
544
545                 boost::shared_ptr<Playlist> pl (playlist());
546
547                 _position_lock_style = ps;
548
549                 if (_position_lock_style == MusicTime) {
550                         _session.bbt_time (_position, _bbt_time);
551                 }
552
553                 send_change (Properties::position_lock_style);
554         }
555 }
556
557 void
558 Region::update_after_tempo_map_change ()
559 {
560         boost::shared_ptr<Playlist> pl (playlist());
561
562         if (!pl || _position_lock_style != MusicTime) {
563                 return;
564         }
565
566         TempoMap& map (_session.tempo_map());
567         framepos_t pos = map.frame_time (_bbt_time);
568         set_position_internal (pos, false);
569
570         /* do this even if the position is the same. this helps out
571            a GUI that has moved its representation already.
572         */
573         send_change (Properties::position);
574 }
575
576 void
577 Region::set_position (framepos_t pos)
578 {
579         if (!can_move()) {
580                 return;
581         }
582
583         set_position_internal (pos, true);
584
585         /* do this even if the position is the same. this helps out
586            a GUI that has moved its representation already.
587         */
588         send_change (Properties::position);
589
590 }
591
592 void
593 Region::set_position_internal (framepos_t pos, bool allow_bbt_recompute)
594 {
595         /* We emit a change of Properties::position even if the position hasn't changed
596            (see Region::set_position), so we must always set this up so that
597            e.g. Playlist::notify_region_moved doesn't use an out-of-date last_position.
598         */
599         _last_position = _position;
600         
601         if (_position != pos) {
602                 _position = pos;
603
604                 /* check that the new _position wouldn't make the current
605                    length impossible - if so, change the length.
606
607                    XXX is this the right thing to do?
608                 */
609
610                 if (max_framepos - _length < _position) {
611                         _last_length = _length;
612                         _length = max_framepos - _position;
613                 }
614
615                 if (allow_bbt_recompute) {
616                         recompute_position_from_lock_style ();
617                 }
618
619                 //invalidate_transients ();
620         }
621 }
622
623 void
624 Region::recompute_position_from_lock_style ()
625 {
626         if (_position_lock_style == MusicTime) {
627                 _session.bbt_time (_position, _bbt_time);
628         }
629 }
630
631 void
632 Region::nudge_position (frameoffset_t n)
633 {
634         if (locked()) {
635                 return;
636         }
637
638         if (n == 0) {
639                 return;
640         }
641
642         framepos_t new_position = _position;
643
644         if (n > 0) {
645                 if (_position > max_framepos - n) {
646                         new_position = max_framepos;
647                 } else {
648                         new_position += n;
649                 }
650         } else {
651                 if (_position < -n) {
652                         new_position = 0;
653                 } else {
654                         new_position += n;
655                 }
656         }
657
658         set_position_internal (new_position, true);
659
660         send_change (Properties::position);
661 }
662
663 void
664 Region::set_ancestral_data (framepos_t s, framecnt_t l, float st, float sh)
665 {
666         _ancestral_length = l;
667         _ancestral_start = s;
668         _stretch = st;
669         _shift = sh;
670 }
671
672 void
673 Region::set_start (framepos_t pos)
674 {
675         if (locked() || position_locked()) {
676                 return;
677         }
678         /* This just sets the start, nothing else. It effectively shifts
679            the contents of the Region within the overall extent of the Source,
680            without changing the Region's position or length
681         */
682
683         if (_start != pos) {
684
685                 if (!verify_start (pos)) {
686                         return;
687                 }
688
689                 set_start_internal (pos);
690                 _whole_file = false;
691                 first_edit ();
692                 invalidate_transients ();
693
694                 send_change (Properties::start);
695         }
696 }
697
698 void
699 Region::trim_start (framepos_t new_position)
700 {
701         if (locked() || position_locked()) {
702                 return;
703         }
704
705         framepos_t new_start;
706         frameoffset_t const start_shift = new_position - _position;
707
708         if (start_shift > 0) {
709
710                 if (_start > max_framepos - start_shift) {
711                         new_start = max_framepos;
712                 } else {
713                         new_start = _start + start_shift;
714                 }
715
716                 if (!verify_start (new_start)) {
717                         return;
718                 }
719
720         } else if (start_shift < 0) {
721
722                 if (_start < -start_shift) {
723                         new_start = 0;
724                 } else {
725                         new_start = _start + start_shift;
726                 }
727
728         } else {
729                 return;
730         }
731
732         if (new_start == _start) {
733                 return;
734         }
735
736         set_start_internal (new_start);
737         _whole_file = false;
738         first_edit ();
739
740         send_change (Properties::start);
741 }
742
743 void
744 Region::trim_front (framepos_t new_position)
745 {
746         modify_front (new_position, false);
747 }
748
749 void
750 Region::cut_front (framepos_t new_position)
751 {
752         modify_front (new_position, true);
753 }
754
755 void
756 Region::cut_end (framepos_t new_endpoint)
757 {
758         modify_end (new_endpoint, true);
759 }
760
761 void
762 Region::modify_front (framepos_t new_position, bool reset_fade)
763 {
764         if (locked()) {
765                 return;
766         }
767
768         framepos_t end = last_frame();
769         framepos_t source_zero;
770
771         if (_position > _start) {
772                 source_zero = _position - _start;
773         } else {
774                 source_zero = 0; // its actually negative, but this will work for us
775         }
776
777         if (new_position < end) { /* can't trim it zero or negative length */
778
779                 framecnt_t newlen = 0;
780                 framepos_t delta = 0;
781
782                 if (!can_trim_start_before_source_start ()) {
783                         /* can't trim it back past where source position zero is located */
784                         new_position = max (new_position, source_zero);
785                 }
786
787                 if (new_position > _position) {
788                         newlen = _length - (new_position - _position);
789                         delta = -1 * (new_position - _position);
790                 } else {
791                         newlen = _length + (_position - new_position);
792                         delta = _position - new_position;
793                 }
794
795                 trim_to_internal (new_position, newlen);
796
797                 if (reset_fade) {
798                         _right_of_split = true;
799                 }
800
801                 if (!property_changes_suspended()) {
802                         recompute_at_start ();
803                 }
804
805                 if (_transients.size() > 0){
806                         adjust_transients(delta);
807                 }
808         }
809 }
810
811 void
812 Region::modify_end (framepos_t new_endpoint, bool reset_fade)
813 {
814         if (locked()) {
815                 return;
816         }
817
818         if (new_endpoint > _position) {
819                 trim_to_internal (_position, new_endpoint - _position);
820                 if (reset_fade) {
821                         _left_of_split = true;
822                 }
823                 if (!property_changes_suspended()) {
824                         recompute_at_end ();
825                 }
826         }
827 }
828
829 /** @param new_endpoint New region end point, such that, for example,
830  *  a region at 0 of length 10 has an endpoint of 9.
831  */
832
833 void
834 Region::trim_end (framepos_t new_endpoint)
835 {
836         modify_end (new_endpoint, false);
837 }
838
839 void
840 Region::trim_to (framepos_t position, framecnt_t length)
841 {
842         if (locked()) {
843                 return;
844         }
845
846         trim_to_internal (position, length);
847
848         if (!property_changes_suspended()) {
849                 recompute_at_start ();
850                 recompute_at_end ();
851         }
852 }
853
854 void
855 Region::trim_to_internal (framepos_t position, framecnt_t length)
856 {
857         framepos_t new_start;
858
859         if (locked()) {
860                 return;
861         }
862
863         frameoffset_t const start_shift = position - _position;
864
865         if (start_shift > 0) {
866
867                 if (_start > max_framepos - start_shift) {
868                         new_start = max_framepos;
869                 } else {
870                         new_start = _start + start_shift;
871                 }
872
873         } else if (start_shift < 0) {
874
875                 if (_start < -start_shift && !can_trim_start_before_source_start ()) {
876                         new_start = 0;
877                 } else {
878                         new_start = _start + start_shift;
879                 }
880
881         } else {
882                 new_start = _start;
883         }
884
885         if (!verify_start_and_length (new_start, length)) {
886                 return;
887         }
888
889         PropertyChange what_changed;
890
891         if (_start != new_start) {
892                 set_start_internal (new_start);
893                 what_changed.add (Properties::start);
894         }
895
896         /* Set position before length, otherwise for MIDI regions this bad thing happens:
897          * 1. we call set_length_internal; length in beats is computed using the region's current
898          *    (soon-to-be old) position
899          * 2. we call set_position_internal; position is set and length in frames re-computed using
900          *    length in beats from (1) but at the new position, which is wrong if the region
901          *    straddles a tempo/meter change.
902          */
903
904         if (_position != position) {
905                 if (!property_changes_suspended()) {
906                         _last_position = _position;
907                 }
908                 set_position_internal (position, true);
909                 what_changed.add (Properties::position);
910         }
911
912         if (_length != length) {
913                 if (!property_changes_suspended()) {
914                         _last_length = _length;
915                 }
916                 set_length_internal (length);
917                 what_changed.add (Properties::length);
918         }
919
920         _whole_file = false;
921
922         PropertyChange start_and_length;
923
924         start_and_length.add (Properties::start);
925         start_and_length.add (Properties::length);
926
927         if (what_changed.contains (start_and_length)) {
928                 first_edit ();
929         }
930
931         if (!what_changed.empty()) {
932                 send_change (what_changed);
933         }
934 }
935
936 void
937 Region::set_hidden (bool yn)
938 {
939         if (hidden() != yn) {
940                 _hidden = yn;
941                 send_change (Properties::hidden);
942         }
943 }
944
945 void
946 Region::set_whole_file (bool yn)
947 {
948         _whole_file = yn;
949         /* no change signal */
950 }
951
952 void
953 Region::set_automatic (bool yn)
954 {
955         _automatic = yn;
956         /* no change signal */
957 }
958
959 void
960 Region::set_muted (bool yn)
961 {
962         if (muted() != yn) {
963                 _muted = yn;
964                 send_change (Properties::muted);
965         }
966 }
967
968 void
969 Region::set_opaque (bool yn)
970 {
971         if (opaque() != yn) {
972                 _opaque = yn;
973                 send_change (Properties::opaque);
974         }
975 }
976
977 void
978 Region::set_locked (bool yn)
979 {
980         if (locked() != yn) {
981                 _locked = yn;
982                 send_change (Properties::locked);
983         }
984 }
985
986 void
987 Region::set_position_locked (bool yn)
988 {
989         if (position_locked() != yn) {
990                 _position_locked = yn;
991                 send_change (Properties::locked);
992         }
993 }
994
995 /** Set the region's sync point.
996  *  @param absolute_pos Session time.
997  */
998 void
999 Region::set_sync_position (framepos_t absolute_pos)
1000 {
1001         /* position within our file */
1002         framepos_t const file_pos = _start + (absolute_pos - _position);
1003
1004         if (file_pos != _sync_position) {
1005                 _sync_marked = true;
1006                 _sync_position = file_pos;
1007                 if (!property_changes_suspended()) {
1008                         maybe_uncopy ();
1009                 }
1010
1011                 send_change (Properties::sync_position);
1012         }
1013 }
1014
1015 void
1016 Region::clear_sync_position ()
1017 {
1018         if (sync_marked()) {
1019                 _sync_marked = false;
1020                 if (!property_changes_suspended()) {
1021                         maybe_uncopy ();
1022                 }
1023
1024                 send_change (Properties::sync_position);
1025         }
1026 }
1027
1028 /* @return the sync point relative the first frame of the region */
1029 frameoffset_t
1030 Region::sync_offset (int& dir) const
1031 {
1032         if (sync_marked()) {
1033                 if (_sync_position > _start) {
1034                         dir = 1;
1035                         return _sync_position - _start;
1036                 } else {
1037                         dir = -1;
1038                         return _start - _sync_position;
1039                 }
1040         } else {
1041                 dir = 0;
1042                 return 0;
1043         }
1044 }
1045
1046 framepos_t
1047 Region::adjust_to_sync (framepos_t pos) const
1048 {
1049         int sync_dir;
1050         frameoffset_t offset = sync_offset (sync_dir);
1051
1052         // cerr << "adjusting pos = " << pos << " to sync at " << _sync_position << " offset = " << offset << " with dir = " << sync_dir << endl;
1053
1054         if (sync_dir > 0) {
1055                 if (pos > offset) {
1056                         pos -= offset;
1057                 } else {
1058                         pos = 0;
1059                 }
1060         } else {
1061                 if (max_framepos - pos > offset) {
1062                         pos += offset;
1063                 }
1064         }
1065
1066         return pos;
1067 }
1068
1069 /** @return Sync position in session time */
1070 framepos_t
1071 Region::sync_position() const
1072 {
1073         if (sync_marked()) {
1074                 return _position - _start + _sync_position;
1075         } else {
1076                 /* if sync has not been marked, use the start of the region */
1077                 return _position;
1078         }
1079 }
1080
1081 void
1082 Region::raise ()
1083 {
1084         boost::shared_ptr<Playlist> pl (playlist());
1085         if (pl) {
1086                 pl->raise_region (shared_from_this ());
1087         }
1088 }
1089
1090 void
1091 Region::lower ()
1092 {
1093         boost::shared_ptr<Playlist> pl (playlist());
1094         if (pl) {
1095                 pl->lower_region (shared_from_this ());
1096         }
1097 }
1098
1099
1100 void
1101 Region::raise_to_top ()
1102 {
1103         boost::shared_ptr<Playlist> pl (playlist());
1104         if (pl) {
1105                 pl->raise_region_to_top (shared_from_this());
1106         }
1107 }
1108
1109 void
1110 Region::lower_to_bottom ()
1111 {
1112         boost::shared_ptr<Playlist> pl (playlist());
1113         if (pl) {
1114                 pl->lower_region_to_bottom (shared_from_this());
1115         }
1116 }
1117
1118 void
1119 Region::set_layer (layer_t l)
1120 {
1121         _layer = l;
1122 }
1123
1124 XMLNode&
1125 Region::state ()
1126 {
1127         XMLNode *node = new XMLNode ("Region");
1128         char buf[64];
1129         char buf2[64];
1130         LocaleGuard lg (X_("POSIX"));
1131         const char* fe = NULL;
1132
1133         add_properties (*node);
1134
1135         id().print (buf, sizeof (buf));
1136         node->add_property ("id", buf);
1137         node->add_property ("type", _type.to_string());
1138
1139         switch (_first_edit) {
1140         case EditChangesNothing:
1141                 fe = X_("nothing");
1142                 break;
1143         case EditChangesName:
1144                 fe = X_("name");
1145                 break;
1146         case EditChangesID:
1147                 fe = X_("id");
1148                 break;
1149         default: /* should be unreachable but makes g++ happy */
1150                 fe = X_("nothing");
1151                 break;
1152         }
1153
1154         node->add_property ("first-edit", fe);
1155
1156         /* note: flags are stored by derived classes */
1157
1158         if (_position_lock_style != AudioTime) {
1159                 stringstream str;
1160                 str << _bbt_time;
1161                 node->add_property ("bbt-position", str.str());
1162         }
1163
1164         for (uint32_t n=0; n < _sources.size(); ++n) {
1165                 snprintf (buf2, sizeof(buf2), "source-%d", n);
1166                 _sources[n]->id().print (buf, sizeof(buf));
1167                 node->add_property (buf2, buf);
1168         }
1169
1170         for (uint32_t n=0; n < _master_sources.size(); ++n) {
1171                 snprintf (buf2, sizeof(buf2), "master-source-%d", n);
1172                 _master_sources[n]->id().print (buf, sizeof (buf));
1173                 node->add_property (buf2, buf);
1174         }
1175
1176         /* Only store nested sources for the whole-file region that acts
1177            as the parent/root of all regions using it.
1178         */
1179
1180         if (_whole_file && max_source_level() > 0) {
1181
1182                 XMLNode* nested_node = new XMLNode (X_("NestedSource"));
1183
1184                 /* region is compound - get its playlist and
1185                    store that before we list the region that
1186                    needs it ...
1187                 */
1188
1189                 for (SourceList::const_iterator s = _sources.begin(); s != _sources.end(); ++s) {
1190                         nested_node->add_child_nocopy ((*s)->get_state ());
1191                 }
1192
1193                 if (nested_node) {
1194                         node->add_child_nocopy (*nested_node);
1195                 }
1196         }
1197
1198         if (_extra_xml) {
1199                 node->add_child_copy (*_extra_xml);
1200         }
1201
1202         return *node;
1203 }
1204
1205 XMLNode&
1206 Region::get_state ()
1207 {
1208         return state ();
1209 }
1210
1211 int
1212 Region::set_state (const XMLNode& node, int version)
1213 {
1214         PropertyChange what_changed;
1215         return _set_state (node, version, what_changed, true);
1216 }
1217
1218 int
1219 Region::_set_state (const XMLNode& node, int /*version*/, PropertyChange& what_changed, bool send)
1220 {
1221         const XMLProperty* prop;
1222
1223         Stateful::save_extra_xml (node);
1224
1225         what_changed = set_values (node);
1226
1227         set_id (node);
1228
1229         if (_position_lock_style == MusicTime) {
1230                 if ((prop = node.property ("bbt-position")) == 0) {
1231                         /* missing BBT info, revert to audio time locking */
1232                         _position_lock_style = AudioTime;
1233                 } else {
1234                         if (sscanf (prop->value().c_str(), "%d|%d|%d",
1235                                     &_bbt_time.bars,
1236                                     &_bbt_time.beats,
1237                                     &_bbt_time.ticks) != 3) {
1238                                 _position_lock_style = AudioTime;
1239                         }
1240                 }
1241         }
1242
1243         /* fix problems with old sessions corrupted by impossible
1244            values for _stretch or _shift
1245         */
1246         if (_stretch == 0.0f) {
1247                 _stretch = 1.0f;
1248         }
1249
1250         if (_shift == 0.0f) {
1251                 _shift = 1.0f;
1252         }
1253
1254         if (send) {
1255                 send_change (what_changed);
1256         }
1257
1258         /* Quick fix for 2.x sessions when region is muted */
1259         if ((prop = node.property (X_("flags")))) {
1260                 if (string::npos != prop->value().find("Muted")){
1261                         set_muted (true);
1262                 }
1263         }
1264
1265
1266         return 0;
1267 }
1268
1269 void
1270 Region::suspend_property_changes ()
1271 {
1272         Stateful::suspend_property_changes ();
1273         _last_length = _length;
1274         _last_position = _position;
1275 }
1276
1277 void
1278 Region::mid_thaw (const PropertyChange& what_changed)
1279 {
1280         if (what_changed.contains (Properties::length)) {
1281                 if (what_changed.contains (Properties::position)) {
1282                         recompute_at_start ();
1283                 }
1284                 recompute_at_end ();
1285         }
1286 }
1287
1288 void
1289 Region::send_change (const PropertyChange& what_changed)
1290 {
1291         if (what_changed.empty()) {
1292                 return;
1293         }
1294
1295         Stateful::send_change (what_changed);
1296
1297         if (!Stateful::property_changes_suspended()) {
1298
1299                 /* Try and send a shared_pointer unless this is part of the constructor.
1300                    If so, do nothing.
1301                 */
1302
1303                 try {
1304                         boost::shared_ptr<Region> rptr = shared_from_this();
1305                         RegionPropertyChanged (rptr, what_changed);
1306                 } catch (...) {
1307                         /* no shared_ptr available, relax; */
1308                 }
1309         }
1310 }
1311
1312 bool
1313 Region::overlap_equivalent (boost::shared_ptr<const Region> other) const
1314 {
1315         return coverage (other->first_frame(), other->last_frame()) != Evoral::OverlapNone;
1316 }
1317
1318 bool
1319 Region::equivalent (boost::shared_ptr<const Region> other) const
1320 {
1321         return _start == other->_start &&
1322                 _position == other->_position &&
1323                 _length == other->_length;
1324 }
1325
1326 bool
1327 Region::size_equivalent (boost::shared_ptr<const Region> other) const
1328 {
1329         return _start == other->_start &&
1330                 _length == other->_length;
1331 }
1332
1333 bool
1334 Region::region_list_equivalent (boost::shared_ptr<const Region> other) const
1335 {
1336         return size_equivalent (other) && source_equivalent (other) && _name == other->_name;
1337 }
1338
1339 void
1340 Region::source_deleted (boost::weak_ptr<Source>)
1341 {
1342         drop_sources ();
1343
1344         if (!_session.deletion_in_progress()) {
1345                 /* this is a very special case: at least one of the region's
1346                    sources has bee deleted, so invalidate all references to
1347                    ourselves. Do NOT do this during session deletion, because
1348                    then we run the risk that this will actually result
1349                    in this object being deleted (as refcnt goes to zero)
1350                    while emitting DropReferences.
1351                 */
1352
1353                 drop_references ();
1354         }
1355 }
1356
1357 vector<string>
1358 Region::master_source_names ()
1359 {
1360         SourceList::iterator i;
1361
1362         vector<string> names;
1363         for (i = _master_sources.begin(); i != _master_sources.end(); ++i) {
1364                 names.push_back((*i)->name());
1365         }
1366
1367         return names;
1368 }
1369
1370 void
1371 Region::set_master_sources (const SourceList& srcs)
1372 {
1373         for (SourceList::const_iterator i = _master_sources.begin (); i != _master_sources.end(); ++i) {
1374                 (*i)->dec_use_count ();
1375         }
1376
1377         _master_sources = srcs;
1378         assert (_sources.size() == _master_sources.size());
1379
1380         for (SourceList::const_iterator i = _master_sources.begin (); i != _master_sources.end(); ++i) {
1381                 (*i)->inc_use_count ();
1382         }
1383 }
1384
1385 bool
1386 Region::source_equivalent (boost::shared_ptr<const Region> other) const
1387 {
1388         if (!other)
1389                 return false;
1390
1391         if ((_sources.size() != other->_sources.size()) ||
1392             (_master_sources.size() != other->_master_sources.size())) {
1393                 return false;
1394         }
1395
1396         SourceList::const_iterator i;
1397         SourceList::const_iterator io;
1398
1399         for (i = _sources.begin(), io = other->_sources.begin(); i != _sources.end() && io != other->_sources.end(); ++i, ++io) {
1400                 if ((*i)->id() != (*io)->id()) {
1401                         return false;
1402                 }
1403         }
1404
1405         for (i = _master_sources.begin(), io = other->_master_sources.begin(); i != _master_sources.end() && io != other->_master_sources.end(); ++i, ++io) {
1406                 if ((*i)->id() != (*io)->id()) {
1407                         return false;
1408                 }
1409         }
1410
1411         return true;
1412 }
1413
1414 std::string
1415 Region::source_string () const
1416 {
1417         //string res = itos(_sources.size());
1418
1419         stringstream res;
1420         res << _sources.size() << ":";
1421
1422         SourceList::const_iterator i;
1423
1424         for (i = _sources.begin(); i != _sources.end(); ++i) {
1425                 res << (*i)->id() << ":";
1426         }
1427
1428         for (i = _master_sources.begin(); i != _master_sources.end(); ++i) {
1429                 res << (*i)->id() << ":";
1430         }
1431
1432         return res.str();
1433 }
1434
1435 bool
1436 Region::uses_source (boost::shared_ptr<const Source> source) const
1437 {
1438         for (SourceList::const_iterator i = _sources.begin(); i != _sources.end(); ++i) {
1439                 if (*i == source) {
1440                         return true;
1441                 }
1442
1443                 boost::shared_ptr<PlaylistSource> ps = boost::dynamic_pointer_cast<PlaylistSource> (*i);
1444
1445                 if (ps) {
1446                         if (ps->playlist()->uses_source (source)) {
1447                                 return true;
1448                         }
1449                 }
1450         }
1451
1452         return false;
1453 }
1454
1455 framecnt_t
1456 Region::source_length(uint32_t n) const
1457 {
1458         assert (n < _sources.size());
1459         return _sources[n]->length (_position - _start);
1460 }
1461
1462 bool
1463 Region::verify_length (framecnt_t len)
1464 {
1465         if (source() && (source()->destructive() || source()->length_mutable())) {
1466                 return true;
1467         }
1468
1469         framecnt_t maxlen = 0;
1470
1471         for (uint32_t n = 0; n < _sources.size(); ++n) {
1472                 maxlen = max (maxlen, source_length(n) - _start);
1473         }
1474
1475         len = min (len, maxlen);
1476
1477         return true;
1478 }
1479
1480 bool
1481 Region::verify_start_and_length (framepos_t new_start, framecnt_t& new_length)
1482 {
1483         if (source() && (source()->destructive() || source()->length_mutable())) {
1484                 return true;
1485         }
1486
1487         framecnt_t maxlen = 0;
1488
1489         for (uint32_t n = 0; n < _sources.size(); ++n) {
1490                 maxlen = max (maxlen, source_length(n) - new_start);
1491         }
1492
1493         new_length = min (new_length, maxlen);
1494
1495         return true;
1496 }
1497
1498 bool
1499 Region::verify_start (framepos_t pos)
1500 {
1501         if (source() && (source()->destructive() || source()->length_mutable())) {
1502                 return true;
1503         }
1504
1505         for (uint32_t n = 0; n < _sources.size(); ++n) {
1506                 if (pos > source_length(n) - _length) {
1507                         return false;
1508                 }
1509         }
1510         return true;
1511 }
1512
1513 bool
1514 Region::verify_start_mutable (framepos_t& new_start)
1515 {
1516         if (source() && (source()->destructive() || source()->length_mutable())) {
1517                 return true;
1518         }
1519
1520         for (uint32_t n = 0; n < _sources.size(); ++n) {
1521                 if (new_start > source_length(n) - _length) {
1522                         new_start = source_length(n) - _length;
1523                 }
1524         }
1525         return true;
1526 }
1527
1528 boost::shared_ptr<Region>
1529 Region::get_parent() const
1530 {
1531         boost::shared_ptr<Playlist> pl (playlist());
1532
1533         if (pl) {
1534                 boost::shared_ptr<Region> r;
1535                 boost::shared_ptr<Region const> grrr2 = boost::dynamic_pointer_cast<Region const> (shared_from_this());
1536
1537                 if (grrr2 && (r = _session.find_whole_file_parent (grrr2))) {
1538                         return boost::static_pointer_cast<Region> (r);
1539                 }
1540         }
1541
1542         return boost::shared_ptr<Region>();
1543 }
1544
1545 int
1546 Region::apply (Filter& filter, Progress* progress)
1547 {
1548         return filter.run (shared_from_this(), progress);
1549 }
1550
1551
1552 void
1553 Region::invalidate_transients ()
1554 {
1555         _valid_transients = false;
1556         _transients.clear ();
1557
1558         send_change (PropertyChange (Properties::valid_transients));
1559 }
1560
1561 void
1562 Region::drop_sources ()
1563 {
1564         for (SourceList::const_iterator i = _sources.begin (); i != _sources.end(); ++i) {
1565                 (*i)->dec_use_count ();
1566         }
1567
1568         _sources.clear ();
1569
1570         for (SourceList::const_iterator i = _master_sources.begin (); i != _master_sources.end(); ++i) {
1571                 (*i)->dec_use_count ();
1572         }
1573
1574         _master_sources.clear ();
1575 }
1576
1577 void
1578 Region::use_sources (SourceList const & s)
1579 {
1580         set<boost::shared_ptr<Source> > unique_srcs;
1581
1582         for (SourceList::const_iterator i = s.begin (); i != s.end(); ++i) {
1583
1584                 _sources.push_back (*i);
1585                 (*i)->inc_use_count ();
1586                 _master_sources.push_back (*i);
1587                 (*i)->inc_use_count ();
1588
1589                 /* connect only once to DropReferences, even if sources are replicated
1590                  */
1591
1592                 if (unique_srcs.find (*i) == unique_srcs.end ()) {
1593                         unique_srcs.insert (*i);
1594                         (*i)->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(*i)));
1595                 }
1596         }
1597 }
1598
1599 Trimmable::CanTrim
1600 Region::can_trim () const
1601 {
1602         CanTrim ct = CanTrim (0);
1603
1604         if (locked()) {
1605                 return ct;
1606         }
1607
1608         /* if not locked, we can always move the front later, and the end earlier
1609          */
1610
1611         ct = CanTrim (ct | FrontTrimLater | EndTrimEarlier);
1612
1613         if (start() != 0 || can_trim_start_before_source_start ()) {
1614                 ct = CanTrim (ct | FrontTrimEarlier);
1615         }
1616
1617         if (!_sources.empty()) {
1618                 if ((start() + length()) < _sources.front()->length (0)) {
1619                         ct = CanTrim (ct | EndTrimLater);
1620                 }
1621         }
1622
1623         return ct;
1624 }
1625
1626 uint32_t
1627 Region::max_source_level () const
1628 {
1629         uint32_t lvl = 0;
1630
1631         for (SourceList::const_iterator i = _sources.begin(); i != _sources.end(); ++i) {
1632                 lvl = max (lvl, (*i)->level());
1633         }
1634
1635         return lvl;
1636 }
1637
1638 bool
1639 Region::is_compound () const
1640 {
1641         return max_source_level() > 0;
1642 }
1643
1644 void
1645 Region::post_set (const PropertyChange& pc)
1646 {
1647         if (pc.contains (Properties::position)) {
1648                 recompute_position_from_lock_style ();
1649         }
1650 }
1651
1652 void
1653 Region::set_start_internal (framecnt_t s)
1654 {
1655         _start = s;
1656 }