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