Add automatable click-free bypass/enable feature to a-eq
[ardour.git] / libs / ardour / location.cc
1 /*
2     Copyright (C) 2000 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <algorithm>
21 #include <set>
22 #include <cstdio> /* for sprintf */
23 #include <unistd.h>
24 #include <cerrno>
25 #include <ctime>
26 #include <list>
27
28 #include "pbd/convert.h"
29 #include "pbd/stl_delete.h"
30 #include "pbd/xml++.h"
31 #include "pbd/enumwriter.h"
32
33 #include "ardour/location.h"
34 #include "ardour/midi_scene_change.h"
35 #include "ardour/session.h"
36 #include "ardour/audiofilesource.h"
37 #include "ardour/tempo.h"
38
39 #include "pbd/i18n.h"
40
41 using namespace std;
42 using namespace ARDOUR;
43 using namespace PBD;
44
45 PBD::Signal0<void> Location::scene_changed;
46 PBD::Signal1<void,Location*> Location::name_changed;
47 PBD::Signal1<void,Location*> Location::end_changed;
48 PBD::Signal1<void,Location*> Location::start_changed;
49 PBD::Signal1<void,Location*> Location::flags_changed;
50 PBD::Signal1<void,Location*> Location::lock_changed;
51 PBD::Signal1<void,Location*> Location::position_lock_style_changed;
52 PBD::Signal1<void,Location*> Location::changed;
53
54 Location::Location (Session& s)
55         : SessionHandleRef (s)
56         , _start (0)
57         , _end (0)
58         , _flags (Flags (0))
59         , _locked (false)
60         , _position_lock_style (AudioTime)
61 {
62         assert (_start >= 0);
63         assert (_end >= 0);
64 }
65
66 /** Construct a new Location, giving it the position lock style determined by glue-new-markers-to-bars-and-beats */
67 Location::Location (Session& s, framepos_t sample_start, framepos_t sample_end, const std::string &name, Flags bits)
68         : SessionHandleRef (s)
69         , _name (name)
70         , _start (sample_start)
71         , _end (sample_end)
72         , _flags (bits)
73         , _locked (false)
74         , _position_lock_style (s.config.get_glue_new_markers_to_bars_and_beats() ? MusicTime : AudioTime)
75
76 {
77         recompute_bbt_from_frames ();
78
79         assert (_start >= 0);
80         assert (_end >= 0);
81 }
82
83 Location::Location (const Location& other)
84         : SessionHandleRef (other._session)
85         , StatefulDestructible()
86         , _name (other._name)
87         , _start (other._start)
88         , _bbt_start (other._bbt_start)
89         , _end (other._end)
90         , _bbt_end (other._bbt_end)
91         , _flags (other._flags)
92         , _position_lock_style (other._position_lock_style)
93
94 {
95         /* copy is not locked even if original was */
96
97         _locked = false;
98
99         assert (_start >= 0);
100         assert (_end >= 0);
101
102         /* scene change is NOT COPIED */
103 }
104
105 Location::Location (Session& s, const XMLNode& node)
106         : SessionHandleRef (s)
107         , _flags (Flags (0))
108         , _position_lock_style (AudioTime)
109 {
110         /* Note: _position_lock_style is initialised above in case set_state doesn't set it
111            (for 2.X session file compatibility).
112         */
113
114         if (set_state (node, Stateful::loading_state_version)) {
115                 throw failed_constructor ();
116         }
117
118         assert (_start >= 0);
119         assert (_end >= 0);
120 }
121
122 bool
123 Location::operator== (const Location& other)
124 {
125         if (_name != other._name ||
126             _start != other._start ||
127             _end != other._end ||
128             _bbt_start != other._bbt_start ||
129             _bbt_end != other._bbt_end ||
130             _flags != other._flags ||
131             _position_lock_style != other._position_lock_style) {
132                 return false;
133         }
134         return true;
135 }
136
137 Location*
138 Location::operator= (const Location& other)
139 {
140         if (this == &other) {
141                 return this;
142         }
143
144         _name = other._name;
145         _start = other._start;
146         _bbt_start = other._bbt_start;
147         _end = other._end;
148         _bbt_end = other._bbt_end;
149         _flags = other._flags;
150         _position_lock_style = other._position_lock_style;
151
152         /* XXX need to copy scene change */
153
154         /* copy is not locked even if original was */
155
156         _locked = false;
157
158         /* "changed" not emitted on purpose */
159
160         assert (_start >= 0);
161         assert (_end >= 0);
162
163         return this;
164 }
165
166 /** Set location name
167  */
168
169 void
170 Location::set_name (const std::string& str)
171 {
172         _name = str;
173
174         name_changed (this); /* EMIT SIGNAL */
175         NameChanged  (); /* EMIT SIGNAL */
176 }
177
178 /** Set start position.
179  *  @param s New start.
180  *  @param force true to force setting, even if the given new start is after the current end.
181  *  @param allow_bbt_recompute True to recompute BBT start time from the new given start time.
182  */
183 int
184 Location::set_start (framepos_t s, bool force, bool allow_bbt_recompute)
185 {
186         if (s < 0) {
187                 return -1;
188         }
189
190         if (_locked) {
191                 return -1;
192         }
193
194         if (!force) {
195                 if (((is_auto_punch() || is_auto_loop()) && s >= _end) || (!is_mark() && s > _end)) {
196                         return -1;
197                 }
198         }
199
200         if (is_mark()) {
201                 if (_start != s) {
202                         _start = s;
203                         _end = s;
204                         if (allow_bbt_recompute) {
205                                 recompute_bbt_from_frames ();
206                         }
207
208                         start_changed (this); /* EMIT SIGNAL */
209                         StartChanged (); /* EMIT SIGNAL */
210                         //end_changed (this); /* EMIT SIGNAL */
211                         //EndChanged (); /* EMIT SIGNAL */
212                 }
213
214                 /* moving the start (position) of a marker with a scene change
215                    requires an update in the Scene Changer.
216                 */
217
218                 if (_scene_change) {
219                         scene_changed (); /* EMIT SIGNAL */
220                 }
221
222                 assert (_start >= 0);
223                 assert (_end >= 0);
224
225                 return 0;
226         } else if (!force) {
227                 /* range locations must exceed a minimum duration */
228                 if (_end - s < Config->get_range_location_minimum()) {
229                         return -1;
230                 }
231         }
232
233         if (s != _start) {
234
235                 framepos_t const old = _start;
236
237                 _start = s;
238                 if (allow_bbt_recompute) {
239                         recompute_bbt_from_frames ();
240                 }
241                 start_changed (this); /* EMIT SIGNAL */
242                 StartChanged (); /* EMIT SIGNAL */
243
244                 if (is_session_range ()) {
245                         Session::StartTimeChanged (old); /* EMIT SIGNAL */
246                         AudioFileSource::set_header_position_offset (s);
247                 }
248         }
249
250         assert (_start >= 0);
251
252         return 0;
253 }
254
255 /** Set end position.
256  *  @param s New end.
257  *  @param force true to force setting, even if the given new end is before the current start.
258  *  @param allow_bbt_recompute True to recompute BBT end time from the new given end time.
259  */
260 int
261 Location::set_end (framepos_t e, bool force, bool allow_bbt_recompute)
262 {
263         if (e < 0) {
264                 return -1;
265         }
266
267         if (_locked) {
268                 return -1;
269         }
270
271         if (!force) {
272                 if (((is_auto_punch() || is_auto_loop()) && e <= _start) || e < _start) {
273                         return -1;
274                 }
275         }
276
277         if (is_mark()) {
278                 if (_start != e) {
279                         _start = e;
280                         _end = e;
281                         if (allow_bbt_recompute) {
282                                 recompute_bbt_from_frames ();
283                         }
284                         //start_changed (this); /* EMIT SIGNAL */
285                         //StartChanged (); /* EMIT SIGNAL */
286                         end_changed (this); /* EMIT SIGNAL */
287                         EndChanged (); /* EMIT SIGNAL */
288                 }
289
290                 assert (_start >= 0);
291                 assert (_end >= 0);
292
293                 return 0;
294         } else if (!force) {
295                 /* range locations must exceed a minimum duration */
296                 if (e - _start < Config->get_range_location_minimum()) {
297                         return -1;
298                 }
299         }
300
301         if (e != _end) {
302
303                 framepos_t const old = _end;
304
305                 _end = e;
306                 if (allow_bbt_recompute) {
307                         recompute_bbt_from_frames ();
308                 }
309
310                 end_changed(this); /* EMIT SIGNAL */
311                 EndChanged(); /* EMIT SIGNAL */
312
313                 if (is_session_range()) {
314                         Session::EndTimeChanged (old); /* EMIT SIGNAL */
315                 }
316         }
317
318         assert (_end >= 0);
319
320         return 0;
321 }
322
323 int
324 Location::set (framepos_t s, framepos_t e, bool allow_bbt_recompute)
325 {
326         if (s < 0 || e < 0) {
327                 return -1;
328         }
329
330         /* check validity */
331         if (((is_auto_punch() || is_auto_loop()) && s >= e) || (!is_mark() && s > e)) {
332                 return -1;
333         }
334
335         bool start_change = false;
336         bool end_change = false;
337
338         if (is_mark()) {
339
340                 if (_start != s) {
341                         _start = s;
342                         _end = s;
343
344                         if (allow_bbt_recompute) {
345                                 recompute_bbt_from_frames ();
346                         }
347
348                         start_change = true;
349                         end_change = true;
350                 }
351
352                 assert (_start >= 0);
353                 assert (_end >= 0);
354
355         } else {
356
357                 /* range locations must exceed a minimum duration */
358                 if (e - s < Config->get_range_location_minimum()) {
359                         return -1;
360                 }
361
362                 if (s != _start) {
363
364                         framepos_t const old = _start;
365                         _start = s;
366
367                         if (allow_bbt_recompute) {
368                                 recompute_bbt_from_frames ();
369                         }
370
371                         start_change = true;
372
373                         if (is_session_range ()) {
374                                 Session::StartTimeChanged (old); /* EMIT SIGNAL */
375                                 AudioFileSource::set_header_position_offset (s);
376                         }
377                 }
378
379
380                 if (e != _end) {
381
382                         framepos_t const old = _end;
383                         _end = e;
384
385                         if (allow_bbt_recompute) {
386                                 recompute_bbt_from_frames ();
387                         }
388
389                         end_change = true;
390
391                         if (is_session_range()) {
392                                 Session::EndTimeChanged (old); /* EMIT SIGNAL */
393                         }
394                 }
395
396                 assert (_end >= 0);
397         }
398
399         if (start_change && end_change) {
400                 changed (this);
401                 Changed ();
402         } else if (start_change) {
403                 start_changed(this); /* EMIT SIGNAL */
404                 StartChanged(); /* EMIT SIGNAL */
405         } else if (end_change) {
406                 end_changed(this); /* EMIT SIGNAL */
407                 EndChanged(); /* EMIT SIGNAL */
408         }
409
410         return 0;
411 }
412
413 int
414 Location::move_to (framepos_t pos)
415 {
416         if (pos < 0) {
417                 return -1;
418         }
419
420         if (_locked) {
421                 return -1;
422         }
423
424         if (_start != pos) {
425                 _start = pos;
426                 _end = _start + length();
427                 recompute_bbt_from_frames ();
428
429                 changed (this); /* EMIT SIGNAL */
430                 Changed (); /* EMIT SIGNAL */
431         }
432
433         assert (_start >= 0);
434         assert (_end >= 0);
435
436         return 0;
437 }
438
439 void
440 Location::set_hidden (bool yn, void*)
441 {
442         if (set_flag_internal (yn, IsHidden)) {
443                 flags_changed (this); /* EMIT SIGNAL */
444                 FlagsChanged ();
445         }
446 }
447
448 void
449 Location::set_cd (bool yn, void*)
450 {
451         // XXX this really needs to be session start
452         // but its not available here - leave to GUI
453
454         if (yn && _start == 0) {
455                 error << _("You cannot put a CD marker at this position") << endmsg;
456                 return;
457         }
458
459         if (set_flag_internal (yn, IsCDMarker)) {
460                 flags_changed (this); /* EMIT SIGNAL */
461                 FlagsChanged ();
462         }
463 }
464
465 void
466 Location::set_is_range_marker (bool yn, void*)
467 {
468         if (set_flag_internal (yn, IsRangeMarker)) {
469                 flags_changed (this);
470                 FlagsChanged (); /* EMIT SIGNAL */
471         }
472 }
473
474 void
475 Location::set_skip (bool yn)
476 {
477         if (is_range_marker() && length() > 0) {
478                 if (set_flag_internal (yn, IsSkip)) {
479                         flags_changed (this);
480                         FlagsChanged ();
481                 }
482         }
483 }
484
485 void
486 Location::set_skipping (bool yn)
487 {
488         if (is_range_marker() && is_skip() && length() > 0) {
489                 if (set_flag_internal (yn, IsSkipping)) {
490                         flags_changed (this);
491                         FlagsChanged ();
492                 }
493         }
494 }
495
496 void
497 Location::set_auto_punch (bool yn, void*)
498 {
499         if (is_mark() || _start == _end) {
500                 return;
501         }
502
503         if (set_flag_internal (yn, IsAutoPunch)) {
504                 flags_changed (this); /* EMIT SIGNAL */
505                 FlagsChanged (); /* EMIT SIGNAL */
506         }
507 }
508
509 void
510 Location::set_auto_loop (bool yn, void*)
511 {
512         if (is_mark() || _start == _end) {
513                 return;
514         }
515
516         if (set_flag_internal (yn, IsAutoLoop)) {
517                 flags_changed (this); /* EMIT SIGNAL */
518                 FlagsChanged (); /* EMIT SIGNAL */
519         }
520 }
521
522 bool
523 Location::set_flag_internal (bool yn, Flags flag)
524 {
525         if (yn) {
526                 if (!(_flags & flag)) {
527                         _flags = Flags (_flags | flag);
528                         return true;
529                 }
530         } else {
531                 if (_flags & flag) {
532                         _flags = Flags (_flags & ~flag);
533                         return true;
534                 }
535         }
536         return false;
537 }
538
539 void
540 Location::set_mark (bool yn)
541 {
542         /* This function is private, and so does not emit signals */
543
544         if (_start != _end) {
545                 return;
546         }
547
548         set_flag_internal (yn, IsMark);
549 }
550
551
552 XMLNode&
553 Location::cd_info_node(const string & name, const string & value)
554 {
555         XMLNode* root = new XMLNode("CD-Info");
556
557         root->add_property("name", name);
558         root->add_property("value", value);
559
560         return *root;
561 }
562
563
564 XMLNode&
565 Location::get_state ()
566 {
567         XMLNode *node = new XMLNode ("Location");
568         char buf[64];
569
570         typedef map<string, string>::const_iterator CI;
571
572         for(CI m = cd_info.begin(); m != cd_info.end(); ++m){
573                 node->add_child_nocopy(cd_info_node(m->first, m->second));
574         }
575
576         id().print (buf, sizeof (buf));
577         node->add_property("id", buf);
578         node->add_property ("name", name());
579         snprintf (buf, sizeof (buf), "%" PRId64, start());
580         node->add_property ("start", buf);
581         snprintf (buf, sizeof (buf), "%" PRId64, end());
582         node->add_property ("end", buf);
583         node->add_property ("flags", enum_2_string (_flags));
584         node->add_property ("locked", (_locked ? "yes" : "no"));
585         node->add_property ("position-lock-style", enum_2_string (_position_lock_style));
586
587         if (_scene_change) {
588                 node->add_child_nocopy (_scene_change->get_state());
589         }
590
591         return *node;
592 }
593
594 int
595 Location::set_state (const XMLNode& node, int version)
596 {
597         XMLProperty const * prop;
598
599         XMLNodeList cd_list = node.children();
600         XMLNodeConstIterator cd_iter;
601         XMLNode *cd_node;
602
603         string cd_name;
604         string cd_value;
605
606         if (node.name() != "Location") {
607                 error << _("incorrect XML node passed to Location::set_state") << endmsg;
608                 return -1;
609         }
610
611         if (!set_id (node)) {
612                 warning << _("XML node for Location has no ID information") << endmsg;
613         }
614
615         if ((prop = node.property ("name")) == 0) {
616                 error << _("XML node for Location has no name information") << endmsg;
617                 return -1;
618         }
619
620         set_name (prop->value());
621
622         if ((prop = node.property ("start")) == 0) {
623                 error << _("XML node for Location has no start information") << endmsg;
624                 return -1;
625         }
626
627         /* can't use set_start() here, because _end
628            may make the value of _start illegal.
629         */
630
631         sscanf (prop->value().c_str(), "%" PRId64, &_start);
632
633         if ((prop = node.property ("end")) == 0) {
634                 error << _("XML node for Location has no end information") << endmsg;
635                 return -1;
636         }
637
638         sscanf (prop->value().c_str(), "%" PRId64, &_end);
639
640         if ((prop = node.property ("flags")) == 0) {
641                 error << _("XML node for Location has no flags information") << endmsg;
642                 return -1;
643         }
644
645         Flags old_flags (_flags);
646         _flags = Flags (string_2_enum (prop->value(), _flags));
647
648         if (old_flags != _flags) {
649                 FlagsChanged ();
650         }
651
652         if ((prop = node.property ("locked")) != 0) {
653                 _locked = string_is_affirmative (prop->value());
654         } else {
655                 _locked = false;
656         }
657
658         for (cd_iter = cd_list.begin(); cd_iter != cd_list.end(); ++cd_iter) {
659
660                 cd_node = *cd_iter;
661
662                 if (cd_node->name() != "CD-Info") {
663                         continue;
664                 }
665
666                 if ((prop = cd_node->property ("name")) != 0) {
667                         cd_name = prop->value();
668                 } else {
669                         throw failed_constructor ();
670                 }
671
672                 if ((prop = cd_node->property ("value")) != 0) {
673                         cd_value = prop->value();
674                 } else {
675                         throw failed_constructor ();
676                 }
677
678
679                 cd_info[cd_name] = cd_value;
680         }
681
682         if ((prop = node.property ("position-lock-style")) != 0) {
683                 _position_lock_style = PositionLockStyle (string_2_enum (prop->value(), _position_lock_style));
684         }
685
686         XMLNode* scene_child = find_named_node (node, SceneChange::xml_node_name);
687
688         if (scene_child) {
689                 _scene_change = SceneChange::factory (*scene_child, version);
690         }
691
692         recompute_bbt_from_frames ();
693
694         changed (this); /* EMIT SIGNAL */
695         Changed (); /* EMIT SIGNAL */
696
697         assert (_start >= 0);
698         assert (_end >= 0);
699
700         return 0;
701 }
702
703 void
704 Location::set_position_lock_style (PositionLockStyle ps)
705 {
706         if (_position_lock_style == ps) {
707                 return;
708         }
709
710         _position_lock_style = ps;
711
712         recompute_bbt_from_frames ();
713
714         position_lock_style_changed (this); /* EMIT SIGNAL */
715         PositionLockStyleChanged (); /* EMIT SIGNAL */
716 }
717
718 void
719 Location::recompute_bbt_from_frames ()
720 {
721         if (_position_lock_style != MusicTime) {
722                 return;
723         }
724
725         _bbt_start = _session.tempo_map().beat_at_frame (_start);
726         _bbt_end = _session.tempo_map().beat_at_frame (_end);
727 }
728
729 void
730 Location::recompute_frames_from_bbt ()
731 {
732         if (_position_lock_style != MusicTime) {
733                 return;
734         }
735
736         TempoMap& map (_session.tempo_map());
737         set (map.frame_at_beat (_bbt_start), map.frame_at_beat (_bbt_end), false);
738 }
739
740 void
741 Location::lock ()
742 {
743         _locked = true;
744         lock_changed (this);
745         LockChanged ();
746 }
747
748 void
749 Location::unlock ()
750 {
751         _locked = false;
752         lock_changed (this);
753         LockChanged ();
754 }
755
756 void
757 Location::set_scene_change (boost::shared_ptr<SceneChange>  sc)
758 {
759         if (_scene_change != sc) {
760                 _scene_change = sc;
761                 _session.set_dirty ();
762
763                 scene_changed (); /* EMIT SIGNAL */
764                 SceneChangeChanged (); /* EMIT SIGNAL */
765         }
766 }
767
768 /*---------------------------------------------------------------------- */
769
770 Locations::Locations (Session& s)
771         : SessionHandleRef (s)
772 {
773         current_location = 0;
774 }
775
776 Locations::~Locations ()
777 {
778         for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
779                 LocationList::iterator tmp = i;
780                 ++tmp;
781                 delete *i;
782                 i = tmp;
783         }
784 }
785
786 int
787 Locations::set_current (Location *loc, bool want_lock)
788 {
789         int ret;
790
791         if (want_lock) {
792                 Glib::Threads::Mutex::Lock lm (lock);
793                 ret = set_current_unlocked (loc);
794         } else {
795                 ret = set_current_unlocked (loc);
796         }
797
798         if (ret == 0) {
799                 current_changed (current_location); /* EMIT SIGNAL */
800         }
801         return ret;
802 }
803
804 int
805 Locations::next_available_name(string& result,string base)
806 {
807         LocationList::iterator i;
808         string::size_type l;
809         int suffix;
810         char buf[32];
811         std::map<uint32_t,bool> taken;
812         uint32_t n;
813
814         result = base;
815         l = base.length();
816
817         if (!base.empty()) {
818
819                 /* find all existing names that match "base", and store
820                    the numeric part of them (if any) in the map "taken"
821                 */
822
823                 for (i = locations.begin(); i != locations.end(); ++i) {
824
825                         const string& temp ((*i)->name());
826
827                         if (!temp.find (base,0)) {
828                                 /* grab what comes after the "base" as if it was
829                                    a number, and assuming that works OK,
830                                    store it in "taken" so that we know it
831                                    has been used.
832                                 */
833                                 if ((suffix = atoi (temp.substr(l))) != 0) {
834                                         taken.insert (make_pair (suffix,true));
835                                 }
836                         }
837                 }
838         }
839
840         /* Now search for an un-used suffix to add to "base". This
841            will find "holes" in the numbering sequence when a location
842            was deleted.
843
844            This must start at 1, both for human-numbering reasons
845            and also because the call to atoi() above would return
846            zero if there is no recognizable numeric suffix, causing
847            "base 0" not to be inserted into the "taken" map.
848         */
849
850         n = 1;
851
852         while (n < UINT32_MAX) {
853                 if (taken.find (n) == taken.end()) {
854                         snprintf (buf, sizeof(buf), "%d", n);
855                         result += buf;
856                         return 1;
857                 }
858                 ++n;
859         }
860
861         return 0;
862 }
863
864 int
865 Locations::set_current_unlocked (Location *loc)
866 {
867         if (find (locations.begin(), locations.end(), loc) == locations.end()) {
868                 error << _("Locations: attempt to use unknown location as selected location") << endmsg;
869                 return -1;
870         }
871
872         current_location = loc;
873         return 0;
874 }
875
876 void
877 Locations::clear ()
878 {
879         {
880                 Glib::Threads::Mutex::Lock lm (lock);
881
882                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
883
884                         LocationList::iterator tmp = i;
885                         ++tmp;
886
887                         if (!(*i)->is_session_range()) {
888                                 delete *i;
889                                 locations.erase (i);
890                         }
891
892                         i = tmp;
893                 }
894
895                 current_location = 0;
896         }
897
898         changed (); /* EMIT SIGNAL */
899         current_changed (0); /* EMIT SIGNAL */
900 }
901
902 void
903 Locations::clear_markers ()
904 {
905         {
906                 Glib::Threads::Mutex::Lock lm (lock);
907                 LocationList::iterator tmp;
908
909                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
910                         tmp = i;
911                         ++tmp;
912
913                         if ((*i)->is_mark() && !(*i)->is_session_range()) {
914                                 delete *i;
915                                 locations.erase (i);
916                         }
917
918                         i = tmp;
919                 }
920         }
921
922         changed (); /* EMIT SIGNAL */
923 }
924
925 void
926 Locations::clear_ranges ()
927 {
928         {
929                 Glib::Threads::Mutex::Lock lm (lock);
930                 LocationList::iterator tmp;
931
932                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
933
934                         tmp = i;
935                         ++tmp;
936
937                         /* We do not remove these ranges as part of this
938                          * operation
939                          */
940
941                         if ((*i)->is_auto_punch() ||
942                             (*i)->is_auto_loop() ||
943                             (*i)->is_session_range()) {
944                                 i = tmp;
945                                 continue;
946                         }
947
948                         if (!(*i)->is_mark()) {
949                                 delete *i;
950                                 locations.erase (i);
951
952                         }
953
954                         i = tmp;
955                 }
956
957                 current_location = 0;
958         }
959
960         changed ();
961         current_changed (0); /* EMIT SIGNAL */
962 }
963
964 void
965 Locations::add (Location *loc, bool make_current)
966 {
967         assert (loc);
968
969         {
970                 Glib::Threads::Mutex::Lock lm (lock);
971                 locations.push_back (loc);
972
973                 if (make_current) {
974                         current_location = loc;
975                 }
976         }
977
978         added (loc); /* EMIT SIGNAL */
979
980         if (make_current) {
981                 current_changed (current_location); /* EMIT SIGNAL */
982         }
983
984         if (loc->is_session_range()) {
985                 Session::StartTimeChanged (0);
986                 Session::EndTimeChanged (1);
987         }
988 }
989
990 void
991 Locations::remove (Location *loc)
992 {
993         bool was_removed = false;
994         bool was_current = false;
995         LocationList::iterator i;
996
997         if (!loc) {
998                 return;
999         }
1000
1001         if (loc->is_session_range()) {
1002                 return;
1003         }
1004
1005         {
1006                 Glib::Threads::Mutex::Lock lm (lock);
1007
1008                 for (i = locations.begin(); i != locations.end(); ++i) {
1009                         if ((*i) == loc) {
1010                                 delete *i;
1011                                 locations.erase (i);
1012                                 was_removed = true;
1013                                 if (current_location == loc) {
1014                                         current_location = 0;
1015                                         was_current = true;
1016                                 }
1017                                 break;
1018                         }
1019                 }
1020         }
1021
1022         if (was_removed) {
1023
1024                 removed (loc); /* EMIT SIGNAL */
1025
1026                 if (was_current) {
1027                         current_changed (0); /* EMIT SIGNAL */
1028                 }
1029         }
1030 }
1031
1032 XMLNode&
1033 Locations::get_state ()
1034 {
1035         XMLNode *node = new XMLNode ("Locations");
1036         LocationList::iterator iter;
1037         Glib::Threads::Mutex::Lock lm (lock);
1038
1039         for (iter = locations.begin(); iter != locations.end(); ++iter) {
1040                 node->add_child_nocopy ((*iter)->get_state ());
1041         }
1042
1043         return *node;
1044 }
1045
1046 int
1047 Locations::set_state (const XMLNode& node, int version)
1048 {
1049         if (node.name() != "Locations") {
1050                 error << _("incorrect XML mode passed to Locations::set_state") << endmsg;
1051                 return -1;
1052         }
1053
1054         XMLNodeList nlist = node.children();
1055
1056         /* build up a new locations list in here */
1057         LocationList new_locations;
1058
1059         current_location = 0;
1060
1061         Location* session_range_location = 0;
1062         if (version < 3000) {
1063                 session_range_location = new Location (_session, 0, 0, _("session"), Location::IsSessionRange);
1064                 new_locations.push_back (session_range_location);
1065         }
1066
1067         {
1068                 Glib::Threads::Mutex::Lock lm (lock);
1069
1070                 XMLNodeConstIterator niter;
1071                 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
1072
1073                         try {
1074
1075                                 XMLProperty const * prop_id = (*niter)->property ("id");
1076                                 assert (prop_id);
1077                                 PBD::ID id (prop_id->value ());
1078
1079                                 LocationList::const_iterator i = locations.begin();
1080                                 while (i != locations.end () && (*i)->id() != id) {
1081                                         ++i;
1082                                 }
1083
1084                                 Location* loc;
1085                                 if (i != locations.end()) {
1086                                         /* we can re-use an old Location object */
1087                                         loc = *i;
1088
1089                                         // changed locations will be updated by Locations::changed signal
1090                                         loc->set_state (**niter, version);
1091                                 } else {
1092                                         loc = new Location (_session, **niter);
1093                                 }
1094
1095                                 bool add = true;
1096
1097                                 if (version < 3000) {
1098                                         /* look for old-style IsStart / IsEnd properties in this location;
1099                                            if they are present, update the session_range_location accordingly
1100                                         */
1101                                         XMLProperty const * prop = (*niter)->property ("flags");
1102                                         if (prop) {
1103                                                 string v = prop->value ();
1104                                                 while (1) {
1105                                                         string::size_type const c = v.find_first_of (',');
1106                                                         string const s = v.substr (0, c);
1107                                                         if (s == X_("IsStart")) {
1108                                                                 session_range_location->set_start (loc->start(), true);
1109                                                                 add = false;
1110                                                         } else if (s == X_("IsEnd")) {
1111                                                                 session_range_location->set_end (loc->start(), true);
1112                                                                 add = false;
1113                                                         }
1114
1115                                                         if (c == string::npos) {
1116                                                                 break;
1117                                                         }
1118
1119                                                         v = v.substr (c + 1);
1120                                                 }
1121                                         }
1122                                 }
1123
1124                                 if (add) {
1125                                         new_locations.push_back (loc);
1126                                 }
1127                         }
1128
1129                         catch (failed_constructor& err) {
1130                                 error << _("could not load location from session file - ignored") << endmsg;
1131                         }
1132                 }
1133
1134                 /* We may have some unused locations in the old list. */
1135                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
1136                         LocationList::iterator tmp = i;
1137                         ++tmp;
1138
1139                         LocationList::iterator n = new_locations.begin();
1140                         bool found = false;
1141
1142                         while (n != new_locations.end ()) {
1143                                 if ((*i)->id() == (*n)->id()) {
1144                                         found = true;
1145                                         break;
1146                                 }
1147                                 ++n;
1148                         }
1149
1150                         if (!found) {
1151                                 delete *i;
1152                                 locations.erase (i);
1153                         }
1154
1155                         i = tmp;
1156                 }
1157
1158                 locations = new_locations;
1159
1160                 if (locations.size()) {
1161                         current_location = locations.front();
1162                 } else {
1163                         current_location = 0;
1164                 }
1165         }
1166
1167         changed (); /* EMIT SIGNAL */
1168
1169         return 0;
1170 }
1171
1172
1173 typedef std::pair<framepos_t,Location*> LocationPair;
1174
1175 struct LocationStartEarlierComparison
1176 {
1177         bool operator() (LocationPair a, LocationPair b) {
1178                 return a.first < b.first;
1179         }
1180 };
1181
1182 struct LocationStartLaterComparison
1183 {
1184         bool operator() (LocationPair a, LocationPair b) {
1185                 return a.first > b.first;
1186         }
1187 };
1188
1189 framepos_t
1190 Locations::first_mark_before (framepos_t frame, bool include_special_ranges)
1191 {
1192         Glib::Threads::Mutex::Lock lm (lock);
1193         vector<LocationPair> locs;
1194
1195         for (LocationList::iterator i = locations.begin(); i != locations.end(); ++i) {
1196                 locs.push_back (make_pair ((*i)->start(), (*i)));
1197                 if (!(*i)->is_mark()) {
1198                         locs.push_back (make_pair ((*i)->end(), (*i)));
1199                 }
1200         }
1201
1202         LocationStartLaterComparison cmp;
1203         sort (locs.begin(), locs.end(), cmp);
1204
1205         /* locs is sorted in ascending order */
1206
1207         for (vector<LocationPair>::iterator i = locs.begin(); i != locs.end(); ++i) {
1208                 if ((*i).second->is_hidden()) {
1209                         continue;
1210                 }
1211                 if (!include_special_ranges && ((*i).second->is_auto_loop() || (*i).second->is_auto_punch())) {
1212                         continue;
1213                 }
1214                 if ((*i).first < frame) {
1215                         return (*i).first;
1216                 }
1217         }
1218
1219         return -1;
1220 }
1221
1222 Location*
1223 Locations::mark_at (framepos_t pos, framecnt_t slop) const
1224 {
1225         Glib::Threads::Mutex::Lock lm (lock);
1226         Location* closest = 0;
1227         frameoffset_t mindelta = max_framepos;
1228         frameoffset_t delta;
1229
1230         /* locations are not necessarily stored in linear time order so we have
1231          * to iterate across all of them to find the one closest to a give point.
1232          */
1233
1234         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1235
1236                 if ((*i)->is_mark()) {
1237                         if (pos > (*i)->start()) {
1238                                 delta = pos - (*i)->start();
1239                         } else {
1240                                 delta = (*i)->start() - pos;
1241                         }
1242
1243                         if (slop == 0 && delta == 0) {
1244                                 /* special case: no slop, and direct hit for position */
1245                                 return *i;
1246                         }
1247
1248                         if (delta <= slop) {
1249                                 if (delta < mindelta) {
1250                                         closest = *i;
1251                                         mindelta = delta;
1252                                 }
1253                         }
1254                 }
1255         }
1256
1257         return closest;
1258 }
1259
1260 framepos_t
1261 Locations::first_mark_after (framepos_t frame, bool include_special_ranges)
1262 {
1263         Glib::Threads::Mutex::Lock lm (lock);
1264         vector<LocationPair> locs;
1265
1266         for (LocationList::iterator i = locations.begin(); i != locations.end(); ++i) {
1267                 locs.push_back (make_pair ((*i)->start(), (*i)));
1268                 if (!(*i)->is_mark()) {
1269                         locs.push_back (make_pair ((*i)->end(), (*i)));
1270                 }
1271         }
1272
1273         LocationStartEarlierComparison cmp;
1274         sort (locs.begin(), locs.end(), cmp);
1275
1276         /* locs is sorted in reverse order */
1277
1278         for (vector<LocationPair>::iterator i = locs.begin(); i != locs.end(); ++i) {
1279                 if ((*i).second->is_hidden()) {
1280                         continue;
1281                 }
1282                 if (!include_special_ranges && ((*i).second->is_auto_loop() || (*i).second->is_auto_punch())) {
1283                         continue;
1284                 }
1285                 if ((*i).first > frame) {
1286                         return (*i).first;
1287                 }
1288         }
1289
1290         return -1;
1291 }
1292
1293 /** Look for the `marks' (either locations which are marks, or start/end points of range markers) either
1294  *  side of a frame.  Note that if frame is exactly on a `mark', that mark will not be considered for returning
1295  *  as before/after.
1296  *  @param frame Frame to look for.
1297  *  @param before Filled in with the position of the last `mark' before `frame' (or max_framepos if none exists)
1298  *  @param after Filled in with the position of the next `mark' after `frame' (or max_framepos if none exists)
1299  */
1300 void
1301 Locations::marks_either_side (framepos_t const frame, framepos_t& before, framepos_t& after) const
1302 {
1303         before = after = max_framepos;
1304
1305         LocationList locs;
1306
1307         {
1308                 Glib::Threads::Mutex::Lock lm (lock);
1309                 locs = locations;
1310         }
1311
1312         /* Get a list of positions; don't store any that are exactly on our requested position */
1313
1314         std::list<framepos_t> positions;
1315
1316         for (LocationList::const_iterator i = locs.begin(); i != locs.end(); ++i) {
1317                 if (((*i)->is_auto_loop() || (*i)->is_auto_punch())) {
1318                         continue;
1319                 }
1320
1321                 if (!(*i)->is_hidden()) {
1322                         if ((*i)->is_mark ()) {
1323                                 if ((*i)->start() != frame) {
1324                                         positions.push_back ((*i)->start ());
1325                                 }
1326                         } else {
1327                                 if ((*i)->start() != frame) {
1328                                         positions.push_back ((*i)->start ());
1329                                 }
1330                                 if ((*i)->end() != frame) {
1331                                         positions.push_back ((*i)->end ());
1332                                 }
1333                         }
1334                 }
1335         }
1336
1337         if (positions.empty ()) {
1338                 return;
1339         }
1340
1341         positions.sort ();
1342
1343         std::list<framepos_t>::iterator i = positions.begin ();
1344         while (i != positions.end () && *i < frame) {
1345                 ++i;
1346         }
1347
1348         if (i == positions.end ()) {
1349                 /* run out of marks */
1350                 before = positions.back ();
1351                 return;
1352         }
1353
1354         after = *i;
1355
1356         if (i == positions.begin ()) {
1357                 /* none before */
1358                 return;
1359         }
1360
1361         --i;
1362         before = *i;
1363 }
1364
1365 Location*
1366 Locations::session_range_location () const
1367 {
1368         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1369                 if ((*i)->is_session_range()) {
1370                         return const_cast<Location*> (*i);
1371                 }
1372         }
1373         return 0;
1374 }
1375
1376 Location*
1377 Locations::auto_loop_location () const
1378 {
1379         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1380                 if ((*i)->is_auto_loop()) {
1381                         return const_cast<Location*> (*i);
1382                 }
1383         }
1384         return 0;
1385 }
1386
1387 Location*
1388 Locations::auto_punch_location () const
1389 {
1390         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1391                 if ((*i)->is_auto_punch()) {
1392                         return const_cast<Location*> (*i);
1393                 }
1394         }
1395         return 0;
1396 }
1397
1398 uint32_t
1399 Locations::num_range_markers () const
1400 {
1401         uint32_t cnt = 0;
1402         Glib::Threads::Mutex::Lock lm (lock);
1403         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1404                 if ((*i)->is_range_marker()) {
1405                         ++cnt;
1406                 }
1407         }
1408         return cnt;
1409 }
1410
1411 Location *
1412 Locations::get_location_by_id(PBD::ID id)
1413 {
1414         LocationList::iterator it;
1415         for (it  = locations.begin(); it != locations.end(); ++it)
1416                 if (id == (*it)->id())
1417                         return *it;
1418
1419         return 0;
1420 }
1421
1422 void
1423 Locations::find_all_between (framepos_t start, framepos_t end, LocationList& ll, Location::Flags flags)
1424 {
1425         Glib::Threads::Mutex::Lock lm (lock);
1426
1427         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1428                 if ((flags == 0 || (*i)->matches (flags)) &&
1429                     ((*i)->start() >= start && (*i)->end() < end)) {
1430                         ll.push_back (*i);
1431                 }
1432         }
1433 }