Completed playlist copy constructor. Also fixed issue with layering in
[ardour.git] / libs / ardour / playlist.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     $Id$
19 */
20
21 #include <set>
22 #include <fstream>
23 #include <algorithm>
24 #include <unistd.h>
25 #include <cerrno>
26 #include <string>
27 #include <climits>
28
29 #include <sigc++/bind.h>
30
31 #include <pbd/failed_constructor.h>
32 #include <pbd/stl_delete.h>
33 #include <pbd/xml++.h>
34
35 #include <ardour/playlist.h>
36 #include <ardour/session.h>
37 #include <ardour/region.h>
38 #include <ardour/region_factory.h>
39
40 #include "i18n.h"
41
42 using namespace std;
43 using namespace ARDOUR;
44 //using namespace sigc;
45
46 sigc::signal<void,Playlist*> Playlist::PlaylistCreated;
47
48 struct ShowMeTheList {
49     ShowMeTheList (Playlist *pl, const string& n) : playlist (pl), name (n) {}
50     ~ShowMeTheList () { 
51             cerr << ">>>>" << name << endl; playlist->dump(); cerr << "<<<<" << name << endl << endl; 
52     };
53     Playlist *playlist;
54     string name;
55 };
56
57 struct RegionSortByLayer {
58     bool operator() (Region *a, Region *b) {
59             return a->layer() < b->layer();
60     }
61 };
62
63 struct RegionSortByPosition {
64     bool operator() (Region *a, Region *b) {
65             return a->position() < b->position();
66     }
67 };
68
69 struct RegionSortByLastLayerOp {
70     bool operator() (Region *a, Region *b) {
71             return a->last_layer_op() < b->last_layer_op();
72     }
73 };
74
75 Playlist::Playlist (Session& sess, string nom, bool hide)
76         : _session (sess)
77 {
78         init (hide);
79         _name = nom;
80         _orig_diskstream_id = 0;
81         
82 }
83
84 Playlist::Playlist (Session& sess, const XMLNode& node, bool hide)
85         : _session (sess)
86 {
87         init (hide);
88         _name = "unnamed"; /* reset by set_state */
89         _orig_diskstream_id = 0;
90         
91         if (set_state (node)) {
92                 throw failed_constructor();
93         }
94 }
95
96 Playlist::Playlist (const Playlist& other, string namestr, bool hide)
97         : _name (namestr), _session (other._session), _orig_diskstream_id(other._orig_diskstream_id)
98 {
99         init (hide);
100
101         RegionList tmp;
102         other.copy_regions (tmp);
103         
104         in_set_state = true;
105
106         for (list<Region*>::iterator x = tmp.begin(); x != tmp.end(); ++x) {
107                 add_region_internal( (*x), (*x)->position() );
108         }
109
110         in_set_state = false;
111
112         _splicing  = other._splicing;
113         _nudging   = other._nudging;
114         _edit_mode = other._edit_mode;
115
116         in_set_state = false;
117         in_flush = false;
118         in_partition = false;
119         subcnt = 0;
120         _read_data_count = 0;
121         _frozen = other._frozen;
122         save_on_thaw = false;
123         
124         layer_op_counter = other.layer_op_counter;
125         freeze_length = other.freeze_length;
126         
127 }
128
129 Playlist::Playlist (const Playlist& other, jack_nframes_t start, jack_nframes_t cnt, string str, bool hide)
130         : _name (str), _session (other._session), _orig_diskstream_id(other._orig_diskstream_id)
131 {
132         RegionLock rlock2 (&((Playlist&)other));
133         
134         jack_nframes_t end = start + cnt - 1;
135
136         init (hide);
137
138         for (RegionList::const_iterator i = other.regions.begin(); i != other.regions.end(); i++) {
139
140                 Region   *region;
141                 Region   *new_region;
142                 jack_nframes_t offset = 0;
143                 jack_nframes_t position = 0;
144                 jack_nframes_t len = 0;
145                 string    new_name;
146                 OverlapType overlap;
147
148                 region = *i;
149
150                 overlap = region->coverage (start, end);
151
152                 switch (overlap) {
153                 case OverlapNone:
154                         continue;
155
156                 case OverlapInternal:
157                         offset = start - region->position();
158                         position = 0;
159                         len = cnt;
160                         break;
161
162                 case OverlapStart:
163                         offset = 0;
164                         position = region->position() - start;
165                         len = end - region->position();
166                         break;
167
168                 case OverlapEnd:
169                         offset = start - region->position();
170                         position = 0;
171                         len = region->length() - offset;
172                         break;
173
174                 case OverlapExternal:
175                         offset = 0;
176                         position = region->position() - start;
177                         len = region->length();
178                         break;
179                 }
180
181                 _session.region_name (new_name, region->name(), false);
182
183                 new_region = createRegion (*region, offset, len, new_name, region->layer(), region->flags());
184
185                 add_region_internal (new_region, position, true);
186         }
187         
188         /* this constructor does NOT notify others (session) */
189 }
190
191 void
192 Playlist::ref ()
193 {
194         ++_refcnt;
195         InUse (this, true); /* EMIT SIGNAL */
196 }
197
198 void
199 Playlist::unref ()
200 {
201         if (_refcnt > 0) {
202                 _refcnt--; 
203         }
204         if (_refcnt == 0) {
205                 InUse (this, false); /* EMIT SIGNAL */
206                 
207                 if (_hidden) {
208                         /* nobody knows we exist */
209                         delete this;
210                 }
211         }
212 }
213
214
215 void
216 Playlist::copy_regions (RegionList& newlist) const
217 {
218         RegionLock rlock (const_cast<Playlist *> (this));
219
220         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
221                 newlist.push_back (createRegion (**i));
222         }
223 }
224
225 void
226 Playlist::init (bool hide)
227 {
228         atomic_set (&block_notifications, 0);
229         atomic_set (&ignore_state_changes, 0);
230         pending_modified = false;
231         pending_length = false;
232         _refcnt = 0;
233         _hidden = hide;
234         _splicing = false;
235         _nudging = false;
236         in_set_state = false;
237         _edit_mode = _session.get_edit_mode();
238         in_flush = false;
239         in_partition = false;
240         subcnt = 0;
241         _read_data_count = 0;
242         _frozen = false;
243         save_on_thaw = false;
244         layer_op_counter = 0;
245         freeze_length = 0;
246
247         Modified.connect (mem_fun (*this, &Playlist::mark_session_dirty));
248 }
249
250 Playlist::Playlist (const Playlist& pl)
251         : _session (pl._session)
252 {
253         fatal << _("playlist const copy constructor called") << endmsg;
254 }
255
256 Playlist::Playlist (Playlist& pl)
257         : _session (pl._session)
258 {
259         fatal << _("playlist non-const copy constructor called") << endmsg;
260 }
261
262 Playlist::~Playlist ()
263 {
264 }
265
266 void
267 Playlist::set_name (const string& str)
268 {
269         /* in a typical situation, a playlist is being used
270            by one diskstream and also is referenced by the
271            Session. if there are more references than that,
272            then don't change the name.
273         */
274
275         if (_refcnt > 2) {
276                 return;
277         }
278
279         _name = str; 
280         NameChanged(); /* EMIT SIGNAL */
281 }
282
283 /***********************************************************************
284  CHANGE NOTIFICATION HANDLING
285  
286  Notifications must be delayed till the region_lock is released. This
287  is necessary because handlers for the signals may need to acquire
288  the lock (e.g. to read from the playlist).
289  ***********************************************************************/
290
291 void
292 Playlist::freeze ()
293 {
294         delay_notifications ();
295         atomic_inc (&ignore_state_changes);
296 }
297
298 void
299 Playlist::thaw ()
300 {
301         atomic_dec (&ignore_state_changes);
302         release_notifications ();
303 }
304
305
306 void
307 Playlist::delay_notifications ()
308 {
309         atomic_inc (&block_notifications);
310         freeze_length = _get_maximum_extent();
311 }
312
313 void
314 Playlist::release_notifications ()
315 {
316         if (atomic_dec_and_test(&block_notifications)) { 
317                 flush_notifications ();
318         } 
319 }
320
321
322 void
323 Playlist::notify_modified ()
324 {
325         if (holding_state ()) {
326                 pending_modified = true;
327         } else {
328                 pending_modified = false;
329                 Modified(); /* EMIT SIGNAL */
330         }
331 }
332
333 void
334 Playlist::notify_region_removed (Region *r)
335 {
336         if (holding_state ()) {
337                 pending_removals.insert (pending_removals.end(), r);
338         } else {
339                 RegionRemoved (r); /* EMIT SIGNAL */
340                 /* this might not be true, but we have to act
341                    as though it could be.
342                 */
343                 LengthChanged (); /* EMIT SIGNAL */
344                 Modified (); /* EMIT SIGNAL */
345         }
346 }
347
348 void
349 Playlist::notify_region_added (Region *r)
350 {
351         if (holding_state()) {
352                 pending_adds.insert (pending_adds.end(), r);
353         } else {
354                 RegionAdded (r); /* EMIT SIGNAL */
355                 /* this might not be true, but we have to act
356                    as though it could be.
357                 */
358                 LengthChanged (); /* EMIT SIGNAL */
359                 Modified (); /* EMIT SIGNAL */
360         }
361 }
362
363 void
364 Playlist::notify_length_changed ()
365 {
366         if (holding_state ()) {
367                 pending_length = true;
368         } else {
369                 LengthChanged(); /* EMIT SIGNAL */
370                 Modified (); /* EMIT SIGNAL */
371         }
372 }
373
374 void
375 Playlist::flush_notifications ()
376 {
377         RegionList::iterator r;
378         RegionList::iterator a;
379         set<Region*> dependent_checks_needed;
380         uint32_t n = 0;
381
382         if (in_flush) {
383                 return;
384         }
385
386         in_flush = true;
387
388         /* we have no idea what order the regions ended up in pending
389            bounds (it could be based on selection order, for example).
390            so, to preserve layering in the "most recently moved is higher" 
391            model, sort them by existing layer, then timestamp them.
392         */
393
394         // RegionSortByLayer cmp;
395         // pending_bounds.sort (cmp);
396
397         for (RegionList::iterator r = pending_bounds.begin(); r != pending_bounds.end(); ++r) {
398                 if (_session.get_layer_model() == Session::MoveAddHigher) {
399                         timestamp_layer_op (**r);
400                 }
401                 pending_length = true;
402                 n++;
403         }
404
405         for (RegionList::iterator r = pending_bounds.begin(); r != pending_bounds.end(); ++r) {
406                 dependent_checks_needed.insert (*r);
407                 /* don't increment n again - its the same list */
408         }
409
410         for (a = pending_adds.begin(); a != pending_adds.end(); ++a) {
411                 dependent_checks_needed.insert (*a);
412                 RegionAdded (*a); /* EMIT SIGNAL */
413                 n++;
414         }
415
416         for (set<Region*>::iterator x = dependent_checks_needed.begin(); x != dependent_checks_needed.end(); ++x) {
417                 check_dependents (**x, false);
418         }
419
420         for (r = pending_removals.begin(); r != pending_removals.end(); ++r) {
421                 remove_dependents (**r);
422                 RegionRemoved (*r); /* EMIT SIGNAL */
423                 n++;
424         }
425
426         if ((freeze_length != _get_maximum_extent()) || pending_length) {
427                 pending_length = 0;
428                 LengthChanged(); /* EMIT SIGNAL */
429                 n++;
430         }
431
432         if (n || pending_modified) {
433                 if (!in_set_state) {
434                         possibly_splice ();
435                         relayer ();
436                 }
437                 pending_modified = false;
438                 Modified (); /* EMIT SIGNAL */
439         }
440
441         pending_adds.clear ();
442         pending_removals.clear ();
443         pending_bounds.clear ();
444
445         if (save_on_thaw) {
446                 save_on_thaw = false;
447                 save_state (last_save_reason);
448         }
449         
450         in_flush = false;
451 }
452
453 /*************************************************************
454   PLAYLIST OPERATIONS
455  *************************************************************/
456
457 void
458 Playlist::add_region (const Region& region, jack_nframes_t position, float times, bool with_save) 
459
460         RegionLock rlock (this);
461         
462         times = fabs (times);
463         
464         int itimes = (int) floor (times);
465
466         jack_nframes_t pos = position;
467         
468         if (itimes >= 1) {
469                 add_region_internal (const_cast<Region*>(&region), pos, true);
470                 pos += region.length();
471                 --itimes;
472         }
473         
474         /* later regions will all be spliced anyway */
475         
476         if (!holding_state ()) {
477                 possibly_splice_unlocked ();
478         }
479
480         /* note that itimes can be zero if we being asked to just
481            insert a single fraction of the region.
482         */
483
484         for (int i = 0; i < itimes; ++i) {
485                 Region *copy = createRegion (region);
486                 add_region_internal (copy, pos, true);
487                 pos += region.length();
488         }
489         
490         if (floor (times) != times) {
491                 jack_nframes_t length = (jack_nframes_t) floor (region.length() * (times - floor (times)));
492                 string name;
493                 _session.region_name (name, region.name(), false);
494                 Region *sub = createRegion (region, 0, length, name, region.layer(), region.flags());
495                 add_region_internal (sub, pos, true);
496         }
497         
498         if (with_save) {
499                 maybe_save_state (_("add region"));
500         }
501 }
502
503 void
504 Playlist::add_region_internal (Region *region, jack_nframes_t position, bool delay_sort)
505 {
506         RegionSortByPosition cmp;
507         jack_nframes_t old_length = 0;
508
509         // cerr << "adding region " << region->name() << " at " << position << endl;
510
511         if (!holding_state()) {
512                  old_length = _get_maximum_extent();
513         }
514
515         region->set_playlist (this);
516         region->set_position (position, this);
517         region->lock_sources ();
518
519         timestamp_layer_op (*region);
520
521         regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region);
522
523         if (!holding_state () && !in_set_state) {
524                 /* layers get assigned from XML state */
525                 relayer ();
526         }
527
528         /* we need to notify the existence of new region before checking dependents. Ick. */
529
530         notify_region_added (region);
531         
532         if (!holding_state ()) {
533                 check_dependents (*region, false);
534                 if (old_length != _get_maximum_extent()) {
535                         notify_length_changed ();
536                 }
537         }
538
539         region->StateChanged.connect (sigc::bind (mem_fun (this, &Playlist::region_changed_proxy), region));
540 }
541
542 void
543 Playlist::replace_region (Region& old, Region& newr, jack_nframes_t pos)
544 {
545         RegionLock rlock (this);
546
547         remove_region_internal (&old);
548         add_region_internal (&newr, pos);
549
550         if (!holding_state ()) {
551                 possibly_splice_unlocked ();
552         }
553
554         maybe_save_state (_("replace region"));
555 }
556
557 void
558 Playlist::remove_region (Region *region)
559 {
560         RegionLock rlock (this);
561         remove_region_internal (region);
562
563         if (!holding_state ()) {
564                 possibly_splice_unlocked ();
565         }
566
567         maybe_save_state (_("remove region"));
568 }
569
570 int
571 Playlist::remove_region_internal (Region *region, bool delay_sort)
572 {
573         RegionList::iterator i;
574         jack_nframes_t old_length = 0;
575
576         // cerr << "removing region " << region->name() << endl;
577
578         if (!holding_state()) {
579                 old_length = _get_maximum_extent();
580         }
581
582         for (i = regions.begin(); i != regions.end(); ++i) {
583                 if (*i == region) {
584
585                         regions.erase (i);
586
587                         if (!holding_state ()) {
588                                 relayer ();
589                                 remove_dependents (*region);
590                                 
591                                 if (old_length != _get_maximum_extent()) {
592                                         notify_length_changed ();
593                                 }
594                         }
595
596                         notify_region_removed (region);
597                         return 0;
598                 }
599         }
600         return -1;
601 }
602
603 void
604 Playlist::partition (jack_nframes_t start, jack_nframes_t end, bool just_top_level)
605 {
606         RegionList thawlist;
607
608         partition_internal (start, end, false, thawlist);
609
610         for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
611                 (*i)->thaw ("separation");
612         }
613
614         maybe_save_state (_("separate"));
615 }
616
617 void
618 Playlist::partition_internal (jack_nframes_t start, jack_nframes_t end, bool cutting, RegionList& thawlist)
619 {
620         RegionLock rlock (this);
621         Region *region;
622         Region *current;
623         string new_name;
624         RegionList::iterator tmp;
625         OverlapType overlap;
626         jack_nframes_t pos1, pos2, pos3, pos4;
627         RegionList new_regions;
628
629         in_partition = true;
630
631         /* need to work from a copy, because otherwise the regions we add during the process
632            get operated on as well.
633         */
634
635         RegionList copy = regions;
636
637         for (RegionList::iterator i = copy.begin(); i != copy.end(); i = tmp) {
638                 
639                 tmp = i;
640                 ++tmp;
641
642                 current = *i;
643                 
644                 if (current->first_frame() == start && current->last_frame() == end) {
645                         if (cutting) {
646                                 remove_region_internal (current);
647                         }
648                         continue;
649                 }
650                 
651                 if ((overlap = current->coverage (start, end)) == OverlapNone) {
652                         continue;
653                 }
654                 
655                 pos1 = current->position();
656                 pos2 = start;
657                 pos3 = end;
658                 pos4 = current->last_frame();
659
660                 if (overlap == OverlapInternal) {
661                         
662                         /* split: we need 3 new regions, the front, middle and end.
663                            cut:   we need 2 regions, the front and end.
664                         */
665                         
666                         /*
667                                          start                 end
668                           ---------------*************************------------
669                                          P1  P2              P3  P4
670                           SPLIT:
671                           ---------------*****++++++++++++++++====------------
672                           CUT
673                           ---------------*****----------------====------------
674                           
675                         */
676
677                         if (!cutting) {
678                                 
679                                 /* "middle" ++++++ */
680                                 
681                                 _session.region_name (new_name, current->name(), false);
682                                 region = createRegion (*current, pos2 - pos1, pos3 - pos2, new_name,
683                                                        regions.size(), Region::Flag(current->flags()|Region::Automatic|Region::LeftOfSplit|Region::RightOfSplit));
684                                 add_region_internal (region, start, true);
685                                 new_regions.push_back (region);
686                         }
687                         
688                         /* "end" ====== */
689                         
690                         _session.region_name (new_name, current->name(), false);
691                         region = createRegion (*current, pos3 - pos1, pos4 - pos3, new_name, 
692                                                regions.size(), Region::Flag(current->flags()|Region::Automatic|Region::RightOfSplit));
693
694                         add_region_internal (region, end, true);
695                         new_regions.push_back (region);
696
697                         /* "front" ***** */
698                                 
699                         current->freeze ();
700                         thawlist.push_back (current);
701                         current->trim_end (pos2, this);
702
703                 } else if (overlap == OverlapEnd) {
704
705                         /*
706                                                               start           end
707                                     ---------------*************************------------
708                                                    P1           P2         P4   P3
709                                     SPLIT:                                                 
710                                     ---------------**************+++++++++++------------
711                                     CUT:                                                   
712                                     ---------------**************-----------------------
713
714                         */
715
716                         if (!cutting) {
717                                 
718                                 /* end +++++ */
719                                 
720                                 _session.region_name (new_name, current->name(), false);
721                                 region = createRegion (*current, pos2 - pos1, pos4 - pos2, new_name, (layer_t) regions.size(),
722                                                        Region::Flag(current->flags()|Region::Automatic|Region::LeftOfSplit));
723                                 add_region_internal (region, start, true);
724                                 new_regions.push_back (region);
725                         }
726
727                         /* front ****** */
728
729                         current->freeze ();
730                         thawlist.push_back (current);
731                         current->trim_end (pos2, this);
732
733                 } else if (overlap == OverlapStart) {
734
735                         /* split: we need 2 regions: the front and the end.
736                            cut: just trim current to skip the cut area
737                         */
738                                 
739                         /*
740                                                         start           end
741                                     ---------------*************************------------
742                                        P2          P1 P3                   P4          
743
744                                     SPLIT:
745                                     ---------------****+++++++++++++++++++++------------
746                                     CUT:
747                                     -------------------*********************------------
748                                     
749                         */
750
751                         if (!cutting) {
752                                 
753                                 /* front **** */
754                                  _session.region_name (new_name, current->name(), false);
755                                  region = createRegion (*current, 0, pos3 - pos1, new_name,
756                                                         regions.size(), Region::Flag(current->flags()|Region::Automatic|Region::RightOfSplit));
757                                  add_region_internal (region, pos1, true);
758                                  new_regions.push_back (region);
759                         } 
760                         
761                         /* end */
762                         
763                         current->freeze ();
764                         thawlist.push_back (current);
765                         current->trim_front (pos3, this);
766
767                 } else if (overlap == OverlapExternal) {
768
769                         /* split: no split required.
770                            cut: remove the region.
771                         */
772                                 
773                         /*
774                                        start                                      end
775                                     ---------------*************************------------
776                                        P2          P1 P3                   P4          
777
778                                     SPLIT:
779                                     ---------------*************************------------
780                                     CUT:
781                                     ----------------------------------------------------
782                                     
783                         */
784
785                         if (cutting) {
786                                 remove_region_internal (current);
787                         }
788                         new_regions.push_back (current);
789                 }
790         }
791
792         in_partition = false;
793
794         for (RegionList::iterator i = new_regions.begin(); i != new_regions.end(); ++i) {
795                 check_dependents (**i, false);
796         }
797 }
798
799 Playlist*
800 Playlist::cut_copy (Playlist* (Playlist::*pmf)(jack_nframes_t, jack_nframes_t,bool), list<AudioRange>& ranges, bool result_is_hidden)
801 {
802         Playlist* ret;
803         Playlist* pl;
804         jack_nframes_t start;
805
806         if (ranges.empty()) {
807                 return 0;
808         }
809
810         start = ranges.front().start;
811
812
813         for (list<AudioRange>::iterator i = ranges.begin(); i != ranges.end(); ++i) {
814
815                 pl = (this->*pmf)((*i).start, (*i).length(), result_is_hidden);
816                 
817                 if (i == ranges.begin()) {
818                         ret = pl;
819                 } else {
820                         
821                         /* paste the next section into the nascent playlist,
822                            offset to reflect the start of the first range we
823                            chopped.
824                         */
825
826                         ret->paste (*pl, (*i).start - start, 1.0f);
827                         delete pl;
828                 }
829         }
830
831         if (ret) {
832                 /* manually notify session of new playlist here
833                    because the playlists were constructed without notifying 
834                 */
835                 PlaylistCreated (ret);
836         }
837         
838         return ret;
839 }
840
841 Playlist*
842 Playlist::cut (list<AudioRange>& ranges, bool result_is_hidden)
843 {
844         Playlist* (Playlist::*pmf)(jack_nframes_t,jack_nframes_t,bool) = &Playlist::cut;
845         return cut_copy (pmf, ranges, result_is_hidden);
846 }
847
848 Playlist*
849 Playlist::copy (list<AudioRange>& ranges, bool result_is_hidden)
850 {
851         Playlist* (Playlist::*pmf)(jack_nframes_t,jack_nframes_t,bool) = &Playlist::copy;
852         return cut_copy (pmf, ranges, result_is_hidden);
853 }
854
855 Playlist *
856 Playlist::cut (jack_nframes_t start, jack_nframes_t cnt, bool result_is_hidden)
857 {
858         Playlist *the_copy;
859         RegionList thawlist;
860         char buf[32];
861
862         snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
863         string new_name = _name;
864         new_name += '.';
865         new_name += buf;
866
867         if ((the_copy = copyPlaylist (*this, start, cnt, new_name, result_is_hidden)) == 0) {
868                 return 0;
869         }
870
871         partition_internal (start, start+cnt-1, true, thawlist);
872         possibly_splice ();
873
874         for (RegionList::iterator i = thawlist.begin(); i != thawlist.end(); ++i) {
875                 (*i)->thaw ("playlist cut");
876         }
877
878         maybe_save_state (_("cut"));
879
880         return the_copy;
881 }
882
883 Playlist *
884 Playlist::copy (jack_nframes_t start, jack_nframes_t cnt, bool result_is_hidden)
885 {
886         char buf[32];
887         
888         snprintf (buf, sizeof (buf), "%" PRIu32, ++subcnt);
889         string new_name = _name;
890         new_name += '.';
891         new_name += buf;
892
893         cnt = min (_get_maximum_extent() - start, cnt);
894         return copyPlaylist (*this, start, cnt, new_name, result_is_hidden);
895 }
896
897 int
898 Playlist::paste (Playlist& other, jack_nframes_t position, float times)
899 {
900         times = fabs (times);
901         jack_nframes_t old_length;
902
903         {
904                 RegionLock rl1 (this);
905                 RegionLock rl2 (&other);
906
907                 old_length = _get_maximum_extent();
908         
909                 int itimes = (int) floor (times);
910                 jack_nframes_t pos = position;
911                 jack_nframes_t shift = other._get_maximum_extent();
912                 layer_t top_layer = regions.size();
913
914                 while (itimes--) {
915                         for (RegionList::iterator i = other.regions.begin(); i != other.regions.end(); ++i) {
916                                 Region *copy_of_region = createRegion (**i);
917
918                                 /* put these new regions on top of all existing ones, but preserve
919                                    the ordering they had in the original playlist.
920                                 */
921                                 
922                                 copy_of_region->set_layer (copy_of_region->layer() + top_layer);
923                                 add_region_internal (copy_of_region, copy_of_region->position() + pos);
924                         }
925                         pos += shift;
926                 }
927
928                 possibly_splice_unlocked ();
929
930                 /* XXX shall we handle fractional cases at some point? */
931
932                 if (old_length != _get_maximum_extent()) {
933                         notify_length_changed ();
934                 }
935
936                 
937         }
938
939         maybe_save_state (_("paste"));
940
941         return 0;
942 }
943
944
945 void
946 Playlist::duplicate (Region& region, jack_nframes_t position, float times)
947 {
948         times = fabs (times);
949
950         RegionLock rl (this);
951         int itimes = (int) floor (times);
952         jack_nframes_t pos = position;
953
954         while (itimes--) {
955                 Region *copy = createRegion (region);
956                 add_region_internal (copy, pos, true);
957                 pos += region.length();
958         }
959
960         if (floor (times) != times) {
961                 jack_nframes_t length = (jack_nframes_t) floor (region.length() * (times - floor (times)));
962                 string name;
963                 _session.region_name (name, region.name(), false);
964                 Region *sub = createRegion (region, 0, length, name, region.layer(), region.flags());
965                 add_region_internal (sub, pos, true);
966         }
967
968         maybe_save_state (_("duplicate"));
969 }
970
971 void
972 Playlist::split_region (Region& region, jack_nframes_t playlist_position)
973 {
974         RegionLock rl (this);
975
976         if (!region.covers (playlist_position)) {
977                 return;
978         }
979
980         if (region.position() == playlist_position ||
981             region.last_frame() == playlist_position) {
982                 return;
983         }
984
985         if (remove_region_internal (&region, true)) {
986                 return;
987         }
988
989         Region *left;
990         Region *right;
991         jack_nframes_t before;
992         jack_nframes_t after;
993         string before_name;
994         string after_name;
995
996         before = playlist_position - region.position();
997         after = region.length() - before;
998         
999         _session.region_name (before_name, region.name(), false);
1000         left = createRegion (region, 0, before, before_name, region.layer(), Region::Flag (region.flags()|Region::LeftOfSplit));
1001
1002         _session.region_name (after_name, region.name(), false);
1003         right = createRegion (region, before, after, after_name, region.layer(), Region::Flag (region.flags()|Region::RightOfSplit));
1004         
1005         add_region_internal (left, region.position(), true);
1006         add_region_internal (right, region.position() + before);
1007
1008         maybe_save_state (_("split"));
1009 }
1010
1011 void
1012 Playlist::possibly_splice ()
1013 {
1014         if (_edit_mode == Splice) {
1015                 splice_locked ();
1016         }
1017 }
1018
1019 void
1020 Playlist::possibly_splice_unlocked ()
1021 {
1022         if (_edit_mode == Splice) {
1023                 splice_unlocked ();
1024         }
1025 }
1026
1027 void
1028 Playlist::splice_locked ()
1029 {
1030         {
1031                 RegionLock rl (this);
1032                 core_splice ();
1033         }
1034
1035         notify_length_changed ();
1036 }
1037
1038 void
1039 Playlist::splice_unlocked ()
1040 {
1041         core_splice ();
1042         notify_length_changed ();
1043 }
1044
1045 void
1046 Playlist::core_splice ()
1047 {
1048         _splicing = true;
1049         
1050         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
1051                 
1052                 RegionList::iterator next;
1053                 
1054                 next = i;
1055                 ++next;
1056                 
1057                 if (next == regions.end()) {
1058                         break;
1059                 }
1060                 
1061                 (*next)->set_position ((*i)->last_frame() + 1, this);
1062         }
1063         
1064         _splicing = false;
1065 }
1066
1067 void
1068 Playlist::region_bounds_changed (Change what_changed, Region *region)
1069 {
1070         if (in_set_state || _splicing || _nudging) {
1071                 return;
1072         }
1073
1074         if (what_changed & ARDOUR::PositionChanged) {
1075
1076                 /* remove it from the list then add it back in
1077                    the right place again.
1078                 */
1079                 
1080                 RegionSortByPosition cmp;
1081
1082                 RegionList::iterator i = find (regions.begin(), regions.end(), region);
1083                 
1084                 if (i == regions.end()) {
1085                         warning << string_compose (_("%1: bounds changed received for region (%2)not in playlist"),
1086                                             _name, region->name())
1087                                 << endmsg;
1088                         return;
1089                 }
1090
1091                 regions.erase (i);
1092                 regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp),
1093                                 region);
1094
1095         }
1096
1097         if (what_changed & Change (ARDOUR::PositionChanged|ARDOUR::LengthChanged)) {
1098         
1099                 if (holding_state ()) {
1100                         pending_bounds.push_back (region);
1101                 } else {
1102                         if (_session.get_layer_model() == Session::MoveAddHigher) {
1103                                 /* it moved or changed length, so change the timestamp */
1104                                 timestamp_layer_op (*region);
1105                         }
1106                         
1107                         possibly_splice ();
1108                         check_dependents (*region, false);
1109                         notify_length_changed ();
1110                         relayer ();
1111                 }
1112         }
1113 }
1114
1115 void
1116 Playlist::region_changed_proxy (Change what_changed, Region* region)
1117 {
1118         /* this makes a virtual call to the right kind of playlist ... */
1119
1120         region_changed (what_changed, region);
1121 }
1122
1123 bool
1124 Playlist::region_changed (Change what_changed, Region* region)
1125 {
1126         Change our_interests = Change (Region::MuteChanged|Region::LayerChanged|Region::OpacityChanged);
1127         bool save = false;
1128
1129         if (in_set_state || in_flush) {
1130                 return false;
1131         }
1132
1133         {
1134                 if (what_changed & BoundsChanged) {
1135                         region_bounds_changed (what_changed, region);
1136                         save = !(_splicing || _nudging);
1137                 }
1138                 
1139                 if ((what_changed & Region::MuteChanged) && 
1140                     !(what_changed &  Change (ARDOUR::PositionChanged|ARDOUR::LengthChanged))) {
1141                         check_dependents (*region, false);
1142                 }
1143                 
1144                 if (what_changed & our_interests) {
1145                         save = true;
1146                 }
1147         }
1148
1149         return save;
1150 }
1151
1152 void
1153 Playlist::clear (bool with_delete, bool with_save)
1154 {
1155         RegionList::iterator i;
1156         RegionList tmp;
1157
1158         { 
1159                 RegionLock rl (this);
1160                 tmp = regions;
1161                 regions.clear ();
1162         }
1163         
1164         for (i = tmp.begin(); i != tmp.end(); ++i) {
1165                 notify_region_removed (*i);
1166                 if (with_delete) {
1167                         delete *i;
1168                 }
1169         }
1170
1171         if (with_save) {
1172                 maybe_save_state (_("clear"));
1173         }
1174 }
1175
1176 /***********************************************************************
1177  FINDING THINGS
1178  **********************************************************************/
1179
1180 Playlist::RegionList *
1181 Playlist::regions_at (jack_nframes_t frame)
1182
1183 {
1184         RegionLock rlock (this);
1185         return find_regions_at (frame);
1186 }       
1187
1188 Region *
1189 Playlist::top_region_at (jack_nframes_t frame)
1190
1191 {
1192         RegionLock rlock (this);
1193         RegionList *rlist = find_regions_at (frame);
1194         Region *region = 0;
1195
1196         if (rlist->size()) {
1197                 RegionSortByLayer cmp;
1198                 rlist->sort (cmp);
1199                 region = rlist->back();
1200         } 
1201
1202         delete rlist;
1203         return region;
1204 }       
1205
1206 Playlist::RegionList *
1207 Playlist::find_regions_at (jack_nframes_t frame)
1208 {
1209         RegionList *rlist = new RegionList;
1210
1211         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
1212                 if ((*i)->covers (frame)) {
1213                         rlist->push_back (*i);
1214                 }
1215         }
1216
1217         return rlist;
1218 }
1219
1220 Playlist::RegionList *
1221 Playlist::regions_touched (jack_nframes_t start, jack_nframes_t end)
1222 {
1223         RegionLock rlock (this);
1224         RegionList *rlist = new RegionList;
1225
1226         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
1227                 if ((*i)->coverage (start, end) != OverlapNone) {
1228                         rlist->push_back (*i);
1229                 }
1230         }
1231
1232         return rlist;
1233 }
1234
1235
1236 Region*
1237
1238 Playlist::find_next_region (jack_nframes_t frame, RegionPoint point, int dir)
1239 {
1240         RegionLock rlock (this);
1241         Region* ret = 0;
1242         jack_nframes_t closest = max_frames;
1243
1244         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
1245
1246                 jack_nframes_t distance;
1247                 Region* r = (*i);
1248                 jack_nframes_t pos = 0;
1249
1250                 switch (point) {
1251                 case Start:
1252                         pos = r->first_frame ();
1253                         break;
1254                 case End:
1255                         pos = r->last_frame ();
1256                         break;
1257                 case SyncPoint:
1258                         pos = r->adjust_to_sync (r->first_frame());
1259                         break;
1260                 }
1261
1262                 switch (dir) {
1263                 case 1: /* forwards */
1264
1265                         if (pos > frame) {
1266                                 if ((distance = pos - frame) < closest) {
1267                                         closest = distance;
1268                                         ret = r;
1269                                 }
1270                         }
1271
1272                         break;
1273
1274                 default: /* backwards */
1275
1276                         if (pos < frame) {
1277                                 if ((distance = frame - pos) < closest) {
1278                                         closest = distance;
1279                                         ret = r;
1280                                 }
1281                         }
1282                         break;
1283                 }
1284         }
1285
1286         return ret;
1287 }
1288
1289 /***********************************************************************/
1290
1291
1292
1293 void
1294 Playlist::mark_session_dirty ()
1295 {
1296         if (!in_set_state && !holding_state ()) {
1297                 _session.set_dirty();
1298         }
1299 }
1300
1301 int
1302 Playlist::set_state (const XMLNode& node)
1303 {
1304         in_set_state = true;
1305
1306         XMLNode *child;
1307         XMLNodeList nlist;
1308         XMLNodeConstIterator niter;
1309         XMLPropertyList plist;
1310         XMLPropertyConstIterator piter;
1311         XMLProperty *prop;
1312         Region *region;
1313         string region_name;
1314
1315         clear (false, false);
1316
1317         if (node.name() != "Playlist") {
1318                 in_set_state = false;
1319                 return -1;
1320         }
1321
1322         plist = node.properties();
1323
1324         for (piter = plist.begin(); piter != plist.end(); ++piter) {
1325
1326                 prop = *piter;
1327                 
1328                 if (prop->name() == X_("name")) {
1329                         _name = prop->value();
1330                 } else if (prop->name() == X_("orig_diskstream_id")) {
1331                         sscanf (prop->value().c_str(), "%" PRIu64, &_orig_diskstream_id);
1332                 } else if (prop->name() == X_("frozen")) {
1333                         _frozen = (prop->value() == X_("yes"));
1334                 }
1335         }
1336
1337         nlist = node.children();
1338
1339         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
1340
1341                 child = *niter;
1342                 
1343                 if (child->name() == "Region") {
1344
1345                         if ((region = createRegion (_session, *child, true)) == 0) {
1346                                 error << _("Playlist: cannot create region from state file") << endmsg;
1347                                 continue;
1348                         }
1349
1350                         add_region (*region, region->position(), 1.0, false);
1351
1352                         // So that layer_op ordering doesn't get screwed up
1353                         region->set_last_layer_op( region->layer());
1354
1355                 }                       
1356         }
1357
1358         
1359         /* update dependents, which was not done during add_region_internal 
1360            due to in_set_state being true 
1361         */
1362
1363         for (RegionList::iterator r = regions.begin(); r != regions.end(); ++r) {
1364                 check_dependents (**r, false);
1365         }
1366         
1367         in_set_state = false;
1368
1369         return 0;
1370 }
1371
1372 XMLNode&
1373 Playlist::get_state()
1374 {
1375         return state(true);
1376 }
1377
1378 XMLNode&
1379 Playlist::get_template()
1380 {
1381         return state(false);
1382 }
1383
1384 XMLNode&
1385 Playlist::state (bool full_state)
1386 {
1387         XMLNode *node = new XMLNode (X_("Playlist"));
1388         char buf[64];
1389         
1390         node->add_property (X_("name"), _name);
1391
1392         snprintf (buf, sizeof(buf), "%" PRIu64, _orig_diskstream_id);
1393         node->add_property (X_("orig_diskstream_id"), buf);
1394         node->add_property (X_("frozen"), _frozen ? "yes" : "no");
1395
1396         if (full_state) {
1397                 RegionLock rlock (this, false);
1398
1399                 for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
1400                         node->add_child_nocopy ((*i)->get_state());
1401                 }
1402         }
1403
1404         if (_extra_xml) {
1405                 node->add_child_copy (*_extra_xml);
1406         }
1407
1408         return *node;
1409 }
1410
1411 bool
1412 Playlist::empty() const
1413 {
1414         return regions.empty();
1415 }
1416
1417 jack_nframes_t
1418 Playlist::get_maximum_extent () const
1419 {
1420         RegionLock rlock (const_cast<Playlist *>(this));
1421         return _get_maximum_extent ();
1422 }
1423
1424 jack_nframes_t
1425 Playlist::_get_maximum_extent () const
1426 {
1427         RegionList::const_iterator i;
1428         jack_nframes_t max_extent = 0;
1429         jack_nframes_t end = 0;
1430
1431         for (i = regions.begin(); i != regions.end(); ++i) {
1432                 if ((end = (*i)->position() + (*i)->length()) > max_extent) {
1433                         max_extent = end;
1434                 }
1435         }
1436
1437         return max_extent;
1438 }
1439
1440 string 
1441 Playlist::bump_name (string name, Session &session)
1442 {
1443         string newname = name;
1444
1445         do {
1446                 newname = Playlist::bump_name_once (newname);
1447         } while (session.playlist_by_name(newname)!=NULL);
1448
1449         return newname;
1450 }
1451
1452 string
1453 Playlist::bump_name_once (string name)
1454 {
1455         string::size_type period;
1456         string newname;
1457
1458         if ((period = name.find_last_of ('.')) == string::npos) {
1459                 newname = name;
1460                 newname += ".1";
1461         } else {
1462                 char buf[32];
1463                 int version;
1464                 
1465                 sscanf (name.substr (period+1).c_str(), "%d", &version);
1466                 snprintf (buf, sizeof(buf), "%d", version+1);
1467                 
1468                 newname = name.substr (0, period+1);
1469                 newname += buf;
1470         }
1471
1472         return newname;
1473 }
1474
1475 layer_t
1476 Playlist::top_layer() const
1477 {
1478         RegionLock rlock (const_cast<Playlist *> (this));
1479         layer_t top = 0;
1480
1481         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
1482                 top = max (top, (*i)->layer());
1483         }
1484         return top;
1485 }
1486
1487 void
1488 Playlist::set_edit_mode (EditMode mode)
1489 {
1490         _edit_mode = mode;
1491 }
1492
1493 /********************
1494  * Region Layering
1495  ********************/
1496
1497 void
1498 Playlist::relayer ()
1499 {
1500         RegionList::iterator i;
1501         uint32_t layer = 0;
1502
1503         /* don't send multiple Modified notifications
1504            when multiple regions are relayered.
1505         */
1506
1507         freeze ();
1508
1509         if (_session.get_layer_model() == Session::MoveAddHigher || 
1510             _session.get_layer_model() == Session::AddHigher) {
1511
1512                 RegionSortByLastLayerOp cmp;
1513                 RegionList copy = regions;
1514
1515                 copy.sort (cmp);
1516
1517                 for (i = copy.begin(); i != copy.end(); ++i) {
1518                         (*i)->set_layer (layer++);
1519                 }
1520
1521         } else {
1522                 
1523                 /* Session::LaterHigher model */
1524
1525                 for (i = regions.begin(); i != regions.end(); ++i) {
1526                         (*i)->set_layer (layer++);
1527                 }
1528         }
1529
1530         /* sending Modified means that various kinds of layering
1531            models operate correctly at the GUI
1532            level. slightly inefficient, but only slightly.
1533
1534            We force a Modified signal here in case no layers actually
1535            changed.
1536         */
1537
1538         notify_modified ();
1539
1540         thaw ();
1541 }
1542
1543 /* XXX these layer functions are all deprecated */
1544
1545 void
1546 Playlist::raise_region (Region& region)
1547 {
1548         uint32_t rsz = regions.size();
1549         layer_t target = region.layer() + 1U;
1550
1551         if (target >= rsz) {
1552                 /* its already at the effective top */
1553                 return;
1554         }
1555
1556         move_region_to_layer (target, region, 1);
1557 }
1558
1559 void
1560 Playlist::lower_region (Region& region)
1561 {
1562         if (region.layer() == 0) {
1563                 /* its already at the bottom */
1564                 return;
1565         }
1566
1567         layer_t target = region.layer() - 1U;
1568
1569         move_region_to_layer (target, region, -1);
1570 }
1571
1572 void
1573 Playlist::raise_region_to_top (Region& region)
1574 {
1575         /* does nothing useful if layering mode is later=higher */
1576         if ((_session.get_layer_model() == Session::MoveAddHigher) ||
1577             (_session.get_layer_model() == Session::AddHigher)) {
1578                 timestamp_layer_op (region);
1579                 relayer ();
1580         }
1581 }
1582
1583 void
1584 Playlist::lower_region_to_bottom (Region& region)
1585 {
1586         /* does nothing useful if layering mode is later=higher */
1587         if ((_session.get_layer_model() == Session::MoveAddHigher) ||
1588             (_session.get_layer_model() == Session::AddHigher)) {
1589                 region.set_last_layer_op (0);
1590                 relayer ();
1591         }
1592 }
1593
1594 int
1595 Playlist::move_region_to_layer (layer_t target_layer, Region& region, int dir)
1596 {
1597         RegionList::iterator i;
1598         typedef pair<Region*,layer_t> LayerInfo;
1599         list<LayerInfo> layerinfo;
1600         layer_t dest;
1601
1602         {
1603                 RegionLock rlock (const_cast<Playlist *> (this));
1604                 
1605                 for (i = regions.begin(); i != regions.end(); ++i) {
1606                         
1607                         if (&region == *i) {
1608                                 continue;
1609                         }
1610
1611                         if (dir > 0) {
1612
1613                                 /* region is moving up, move all regions on intermediate layers
1614                                    down 1
1615                                 */
1616                                 
1617                                 if ((*i)->layer() > region.layer() && (*i)->layer() <= target_layer) {
1618                                         dest = (*i)->layer() - 1;
1619                                 } else {
1620                                         /* not affected */
1621                                         continue;
1622                                 }
1623                         } else {
1624
1625                                 /* region is moving down, move all regions on intermediate layers
1626                                    up 1
1627                                 */
1628
1629                                 if ((*i)->layer() < region.layer() && (*i)->layer() >= target_layer) {
1630                                         dest = (*i)->layer() + 1;
1631                                 } else {
1632                                         /* not affected */
1633                                         continue;
1634                                 }
1635                         }
1636
1637                         LayerInfo newpair;
1638                         
1639                         newpair.first = *i;
1640                         newpair.second = dest;
1641                         
1642                         layerinfo.push_back (newpair);
1643                 } 
1644         }
1645
1646         /* now reset the layers without holding the region lock */
1647
1648         for (list<LayerInfo>::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) {
1649                 x->first->set_layer (x->second);
1650         }
1651
1652         region.set_layer (target_layer);
1653
1654         /* now check all dependents */
1655
1656         for (list<LayerInfo>::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) {
1657                 check_dependents (*(x->first), false);
1658         }
1659         
1660         check_dependents (region, false);
1661         
1662         return 0;
1663 }
1664
1665 void
1666 Playlist::nudge_after (jack_nframes_t start, jack_nframes_t distance, bool forwards)
1667 {
1668         RegionList::iterator i;
1669         jack_nframes_t new_pos;
1670         bool moved = false;
1671
1672         _nudging = true;
1673
1674         {
1675                 RegionLock rlock (const_cast<Playlist *> (this));
1676                 
1677                 for (i = regions.begin(); i != regions.end(); ++i) {
1678
1679                         if ((*i)->position() >= start) {
1680
1681                                 if (forwards) {
1682
1683                                         if ((*i)->last_frame() > max_frames - distance) {
1684                                                 new_pos = max_frames - (*i)->length();
1685                                         } else {
1686                                                 new_pos = (*i)->position() + distance;
1687                                         }
1688                                         
1689                                 } else {
1690                                         
1691                                         if ((*i)->position() > distance) {
1692                                                 new_pos = (*i)->position() - distance;
1693                                         } else {
1694                                                 new_pos = 0;
1695                                         }
1696                                 }
1697
1698                                 (*i)->set_position (new_pos, this);
1699                                 moved = true;
1700                         }
1701                 }
1702         }
1703
1704         if (moved) {
1705                 _nudging = false;
1706                 maybe_save_state (_("nudged"));
1707                 notify_length_changed ();
1708         }
1709
1710 }
1711
1712 Region*
1713 Playlist::find_region (id_t id) const
1714 {
1715         RegionLock rlock (const_cast<Playlist*> (this));
1716         RegionList::const_iterator i;
1717         
1718         for (i = regions.begin(); i != regions.end(); ++i) {
1719                 if ((*i)->id() == id) {
1720                         return (*i);
1721                 }
1722         }
1723
1724         return 0;
1725 }
1726         
1727 void
1728 Playlist::save_state (std::string why)
1729 {
1730         if (!in_set_state) {
1731                 StateManager::save_state (why);
1732         }
1733 }
1734
1735 void
1736 Playlist::dump () const
1737 {
1738         Region *r;
1739
1740         cerr << "Playlist \"" << _name << "\" " << endl
1741              << regions.size() << " regions "
1742              << endl;
1743
1744         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
1745                 r = *i;
1746                 cerr << "  " << r->name() << " [" 
1747                      << r->start() << "+" << r->length() 
1748                      << "] at " 
1749                      << r->position()
1750                      << " on layer "
1751                      << r->layer ()
1752                      << endl;
1753         }
1754 }
1755
1756 void
1757 Playlist::set_frozen (bool yn)
1758 {
1759         _frozen = yn;
1760 }
1761
1762 void
1763 Playlist::timestamp_layer_op (Region& region)
1764 {
1765 //      struct timeval tv;
1766 //      gettimeofday (&tv, 0);
1767         region.set_last_layer_op (++layer_op_counter);
1768 }
1769
1770 void
1771 Playlist::maybe_save_state (string why)
1772 {
1773         if (holding_state ()) {
1774                 save_on_thaw = true;
1775                 last_save_reason = why;
1776         } else {
1777                 save_state (why);
1778         }
1779 }