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