In marks_either_side, don't return a marker that is exactly at the position that...
[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
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/session.h"
35 #include "ardour/audiofilesource.h"
36
37 #include "i18n.h"
38
39 #define SUFFIX_MAX 32
40
41 using namespace std;
42 using namespace ARDOUR;
43 using namespace PBD;
44
45 Location::Location (const Location& other)
46         : StatefulDestructible(),
47           _name (other._name),
48           _start (other._start),
49           _end (other._end),
50           _flags (other._flags)
51 {
52         /* copy is not locked even if original was */
53
54         _locked = false;
55 }
56
57 Location::Location (const XMLNode& node)
58 {
59         if (set_state (node, Stateful::loading_state_version)) {
60                 throw failed_constructor ();
61         }
62 }
63
64 Location*
65 Location::operator= (const Location& other)
66 {
67         if (this == &other) {
68                 return this;
69         }
70
71         _name = other._name;
72         _start = other._start;
73         _end = other._end;
74         _flags = other._flags;
75
76         /* copy is not locked even if original was */
77
78         _locked = false;
79
80         /* "changed" not emitted on purpose */
81
82         return this;
83 }
84
85 /** Set start position.
86  *  @param s New start.
87  *  @param force true to force setting, even if the given new start is after the current end.
88  */
89 int
90 Location::set_start (nframes64_t s, bool force)
91 {
92         if (_locked) {
93                 return -1;
94         }
95
96         if (!force) {
97                 if (((is_auto_punch() || is_auto_loop()) && s >= _end) || (!is_mark() && s > _end)) {
98                         return -1;
99                 }
100         }
101
102         if (is_mark()) {
103                 if (_start != s) {
104                         _start = s;
105                         _end = s;
106                         start_changed (this); /* EMIT SIGNAL */
107                         end_changed (this); /* EMIT SIGNAL */
108                 }
109                 return 0;
110         }
111         
112         if (s != _start) {
113                 _start = s;
114                 start_changed (this); /* EMIT SIGNAL */
115                 if (is_session_range ()) {
116                         Session::StartTimeChanged (); /* EMIT SIGNAL */
117                         AudioFileSource::set_header_position_offset (s);
118                 }
119         }
120
121         return 0;
122 }
123
124 /** Set end position.
125  *  @param s New end.
126  *  @param force true to force setting, even if the given new start is after the current end.
127  */
128 int
129 Location::set_end (nframes64_t e, bool force)
130 {
131         if (_locked) {
132                 return -1;
133         }
134
135         if (!force) {
136                 if (((is_auto_punch() || is_auto_loop()) && e <= _start) || e < _start) {
137                         return -1;
138                 }
139         }
140         
141         if (is_mark()) {
142                 if (_start != e) {
143                         _start = e;
144                         _end = e;
145                         start_changed (this); /* EMIT SIGNAL */
146                         end_changed (this); /* EMIT SIGNAL */
147                 }
148                 return 0;
149         }
150
151         if (e != _end) {
152                 _end = e;
153                 end_changed(this); /* EMIT SIGNAL */
154
155                 if (is_session_range()) {
156                         Session::EndTimeChanged (); /* EMIT SIGNAL */
157                 }
158         }
159
160         return 0;
161 }
162
163 int
164 Location::set (nframes64_t start, nframes64_t end)
165 {
166         /* check validity */
167         if (((is_auto_punch() || is_auto_loop()) && start >= end) || (!is_mark() && start > end)) {
168                 return -1;
169         }
170
171         /* now we know these values are ok, so force-set them */
172         int const s = set_start (start, true);
173         int const e = set_end (end, true);
174
175         return (s == 0 && e == 0) ? 0 : -1;
176 }
177
178 int
179 Location::move_to (nframes64_t pos)
180 {
181         if (_locked) {
182                 return -1;
183         }
184
185         if (_start != pos) {
186                 _start = pos;
187                 _end = _start + length();
188
189                 changed (this); /* EMIT SIGNAL */
190         }
191
192         return 0;
193 }
194
195 void
196 Location::set_hidden (bool yn, void *src)
197 {
198         if (set_flag_internal (yn, IsHidden)) {
199                  FlagsChanged (this, src); /* EMIT SIGNAL */
200         }
201 }
202
203 void
204 Location::set_cd (bool yn, void *src)
205 {
206         // XXX this really needs to be session start
207         // but its not available here - leave to GUI
208
209         if (_start == 0) {
210                 error << _("You cannot put a CD marker at this position") << endmsg;
211                 return;
212         }
213
214         if (set_flag_internal (yn, IsCDMarker)) {
215                  FlagsChanged (this, src); /* EMIT SIGNAL */
216         }
217 }
218
219 void
220 Location::set_is_range_marker (bool yn, void *src)
221 {
222        if (set_flag_internal (yn, IsRangeMarker)) {
223                 FlagsChanged (this, src); /* EMIT SIGNAL */
224        }
225 }
226
227 void
228 Location::set_auto_punch (bool yn, void *src)
229 {
230         if (is_mark() || _start == _end) {
231                 return;
232         }
233
234         if (set_flag_internal (yn, IsAutoPunch)) {
235                  FlagsChanged (this, src); /* EMIT SIGNAL */
236         }
237 }
238
239 void
240 Location::set_auto_loop (bool yn, void *src)
241 {
242         if (is_mark() || _start == _end) {
243                 return;
244         }
245
246         if (set_flag_internal (yn, IsAutoLoop)) {
247                  FlagsChanged (this, src); /* EMIT SIGNAL */
248         }
249 }
250
251 bool
252 Location::set_flag_internal (bool yn, Flags flag)
253 {
254         if (yn) {
255                 if (!(_flags & flag)) {
256                         _flags = Flags (_flags | flag);
257                         return true;
258                 }
259         } else {
260                 if (_flags & flag) {
261                         _flags = Flags (_flags & ~flag);
262                         return true;
263                 }
264         }
265         return false;
266 }
267
268 void
269 Location::set_mark (bool yn)
270 {
271         /* This function is private, and so does not emit signals */
272
273         if (_start != _end) {
274                 return;
275         }
276
277         set_flag_internal (yn, IsMark);
278 }
279
280
281 XMLNode&
282 Location::cd_info_node(const string & name, const string & value)
283 {
284         XMLNode* root = new XMLNode("CD-Info");
285
286         root->add_property("name", name);
287         root->add_property("value", value);
288
289         return *root;
290 }
291
292
293 XMLNode&
294 Location::get_state (void)
295 {
296         XMLNode *node = new XMLNode ("Location");
297         char buf[64];
298
299         typedef map<string, string>::const_iterator CI;
300
301         for(CI m = cd_info.begin(); m != cd_info.end(); ++m){
302                 node->add_child_nocopy(cd_info_node(m->first, m->second));
303         }
304
305         id().print (buf, sizeof (buf));
306         node->add_property("id", buf);
307         node->add_property ("name", name());
308         snprintf (buf, sizeof (buf), "%" PRId64, start());
309         node->add_property ("start", buf);
310         snprintf (buf, sizeof (buf), "%" PRId64, end());
311         node->add_property ("end", buf);
312         node->add_property ("flags", enum_2_string (_flags));
313         node->add_property ("locked", (_locked ? "yes" : "no"));
314
315         return *node;
316 }
317
318 int
319 Location::set_state (const XMLNode& node, int /*version*/)
320 {
321         const XMLProperty *prop;
322
323         XMLNodeList cd_list = node.children();
324         XMLNodeConstIterator cd_iter;
325         XMLNode *cd_node;
326
327         string cd_name;
328         string cd_value;
329
330         if (node.name() != "Location") {
331                 error << _("incorrect XML node passed to Location::set_state") << endmsg;
332                 return -1;
333         }
334
335         if ((prop = node.property ("id")) == 0) {
336                 warning << _("XML node for Location has no ID information") << endmsg;
337         } else {
338                 _id = prop->value ();
339         }
340
341         if ((prop = node.property ("name")) == 0) {
342                 error << _("XML node for Location has no name information") << endmsg;
343                 return -1;
344         }
345
346         set_name (prop->value());
347
348         if ((prop = node.property ("start")) == 0) {
349                 error << _("XML node for Location has no start information") << endmsg;
350                 return -1;
351         }
352
353                 /* can't use set_start() here, because _end
354                    may make the value of _start illegal.
355                 */
356
357         sscanf (prop->value().c_str(), "%" PRId64, &_start);
358
359         if ((prop = node.property ("end")) == 0) {
360                   error << _("XML node for Location has no end information") << endmsg;
361                   return -1;
362         }
363
364         sscanf (prop->value().c_str(), "%" PRId64, &_end);
365
366         if ((prop = node.property ("flags")) == 0) {
367                   error << _("XML node for Location has no flags information") << endmsg;
368                   return -1;
369         }
370
371         _flags = Flags (string_2_enum (prop->value(), _flags));
372
373         if ((prop = node.property ("locked")) != 0) {
374                 _locked = string_is_affirmative (prop->value());
375         } else {
376                 _locked = false;
377         }
378
379         for (cd_iter = cd_list.begin(); cd_iter != cd_list.end(); ++cd_iter) {
380
381                   cd_node = *cd_iter;
382
383                   if (cd_node->name() != "CD-Info") {
384                     continue;
385                   }
386
387                   if ((prop = cd_node->property ("name")) != 0) {
388                     cd_name = prop->value();
389                   } else {
390                     throw failed_constructor ();
391                   }
392
393                   if ((prop = cd_node->property ("value")) != 0) {
394                     cd_value = prop->value();
395                   } else {
396                     throw failed_constructor ();
397                   }
398
399
400                   cd_info[cd_name] = cd_value;
401
402         }
403
404         changed(this); /* EMIT SIGNAL */
405
406         return 0;
407 }
408
409 /*---------------------------------------------------------------------- */
410
411 Locations::Locations ()
412
413 {
414         current_location = 0;
415 }
416
417 Locations::~Locations ()
418 {
419         for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
420                 LocationList::iterator tmp = i;
421                 ++tmp;
422                 delete *i;
423                 i = tmp;
424         }
425 }
426
427 int
428 Locations::set_current (Location *loc, bool want_lock)
429
430 {
431         int ret;
432
433         if (want_lock) {
434                 Glib::Mutex::Lock lm (lock);
435                 ret = set_current_unlocked (loc);
436         } else {
437                 ret = set_current_unlocked (loc);
438         }
439
440         if (ret == 0) {
441                  current_changed (current_location); /* EMIT SIGNAL */
442         }
443         return ret;
444 }
445
446 int
447 Locations::next_available_name(string& result,string base)
448 {
449         LocationList::iterator i;
450         Location* location;
451         string temp;
452         string::size_type l;
453         int suffix;
454         char buf[32];
455         bool available[SUFFIX_MAX+1];
456
457         result = base;
458         for (int k=1; k<SUFFIX_MAX; k++) {
459                 available[k] = true;
460         }
461         l = base.length();
462         for (i = locations.begin(); i != locations.end(); ++i) {
463                 location =* i;
464                 temp = location->name();
465                 if (l && !temp.find(base,0)) {
466                         suffix = atoi(temp.substr(l,3).c_str());
467                         if (suffix) available[suffix] = false;
468                 }
469         }
470         for (int k=1; k<=SUFFIX_MAX; k++) {
471                 if (available[k]) {
472                         snprintf (buf, 31, "%d", k);
473                         result += buf;
474                         return 1;
475                 }
476         }
477         return 0;
478 }
479
480 int
481 Locations::set_current_unlocked (Location *loc)
482 {
483         if (find (locations.begin(), locations.end(), loc) == locations.end()) {
484                 error << _("Locations: attempt to use unknown location as selected location") << endmsg;
485                 return -1;
486         }
487
488         current_location = loc;
489         return 0;
490 }
491
492 void
493 Locations::clear ()
494 {
495         {
496                 Glib::Mutex::Lock lm (lock);
497
498                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
499
500                         LocationList::iterator tmp = i;
501                         ++tmp;
502
503                         if (!(*i)->is_session_range()) {
504                                 locations.erase (i);
505                         }
506
507                         i = tmp;
508                 }
509
510                 current_location = 0;
511         }
512
513         changed (OTHER); /* EMIT SIGNAL */
514         current_changed (0); /* EMIT SIGNAL */
515 }
516
517 void
518 Locations::clear_markers ()
519 {
520         {
521                 Glib::Mutex::Lock lm (lock);
522                 LocationList::iterator tmp;
523
524                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
525                         tmp = i;
526                         ++tmp;
527
528                         if ((*i)->is_mark() && !(*i)->is_session_range()) {
529                                 locations.erase (i);
530                         }
531
532                         i = tmp;
533                 }
534         }
535
536         changed (OTHER); /* EMIT SIGNAL */
537 }
538
539 void
540 Locations::clear_ranges ()
541 {
542         {
543                 Glib::Mutex::Lock lm (lock);
544                 LocationList::iterator tmp;
545
546                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
547
548                         tmp = i;
549                         ++tmp;
550
551                         if (!(*i)->is_mark()) {
552                                 locations.erase (i);
553
554                         }
555
556                         i = tmp;
557                 }
558
559                 current_location = 0;
560         }
561
562         changed (OTHER); /* EMIT SIGNAL */
563         current_changed (0); /* EMIT SIGNAL */
564 }
565
566 void
567 Locations::add (Location *loc, bool make_current)
568 {
569         assert (loc);
570         
571         {
572                 Glib::Mutex::Lock lm (lock);
573                 locations.push_back (loc);
574
575                 if (make_current) {
576                         current_location = loc;
577                 }
578         }
579
580         added (loc); /* EMIT SIGNAL */
581
582         if (make_current) {
583                  current_changed (current_location); /* EMIT SIGNAL */
584         }
585 }
586
587 void
588 Locations::remove (Location *loc)
589 {
590         bool was_removed = false;
591         bool was_current = false;
592         LocationList::iterator i;
593
594         if (loc->is_session_range()) {
595                 return;
596         }
597
598         {
599                 Glib::Mutex::Lock lm (lock);
600
601                 for (i = locations.begin(); i != locations.end(); ++i) {
602                         if ((*i) == loc) {
603                                 locations.erase (i);
604                                 was_removed = true;
605                                 if (current_location == loc) {
606                                         current_location = 0;
607                                         was_current = true;
608                                 }
609                                 break;
610                         }
611                 }
612         }
613
614         if (was_removed) {
615
616                 removed (loc); /* EMIT SIGNAL */
617
618                 if (was_current) {
619                          current_changed (0); /* EMIT SIGNAL */
620                 }
621
622                 changed (REMOVAL); /* EMIT_SIGNAL */
623         }
624 }
625
626 void
627 Locations::location_changed (Location* /*loc*/)
628 {
629         changed (OTHER); /* EMIT SIGNAL */
630 }
631
632 XMLNode&
633 Locations::get_state ()
634 {
635         XMLNode *node = new XMLNode ("Locations");
636         LocationList::iterator iter;
637         Glib::Mutex::Lock lm (lock);
638
639         for (iter  = locations.begin(); iter != locations.end(); ++iter) {
640                 node->add_child_nocopy ((*iter)->get_state ());
641         }
642
643         return *node;
644 }
645
646 int
647 Locations::set_state (const XMLNode& node, int version)
648 {
649         if (node.name() != "Locations") {
650                 error << _("incorrect XML mode passed to Locations::set_state") << endmsg;
651                 return -1;
652         }
653
654         XMLNodeList nlist = node.children();
655
656         locations.clear ();
657         current_location = 0;
658
659         Location* session_range_location = 0;
660         if (version < 3000) {
661                 session_range_location = new Location (0, 0, _("session"), Location::IsSessionRange);
662                 locations.push_back (session_range_location);
663         }
664
665         {
666                 Glib::Mutex::Lock lm (lock);
667
668                 XMLNodeConstIterator niter;
669                 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
670
671                         try {
672
673                                 Location *loc = new Location (**niter);
674
675                                 bool add = true;
676
677                                 if (version < 3000) {
678                                         /* look for old-style IsStart / IsEnd properties in this location;
679                                            if they are present, update the session_range_location accordingly
680                                         */
681                                         XMLProperty const * prop = (*niter)->property ("flags");
682                                         if (prop) {
683                                                 string v = prop->value ();
684                                                 while (1) {
685                                                         string::size_type const c = v.find_first_of (',');
686                                                         string const s = v.substr (0, c);
687                                                         if (s == X_("IsStart")) {
688                                                                 session_range_location->set_start (loc->start());
689                                                                 add = false;
690                                                         } else if (s == X_("IsEnd")) {
691                                                                 session_range_location->set_end (loc->start());
692                                                                 add = false;
693                                                         }
694
695                                                         if (c == string::npos) {
696                                                                 break;
697                                                         }
698
699                                                         v = v.substr (c + 1);
700                                                 }
701                                         }
702                                 }
703
704                                 if (add) {
705                                         locations.push_back (loc);
706                                 }
707                         }
708
709                         catch (failed_constructor& err) {
710                                 error << _("could not load location from session file - ignored") << endmsg;
711                         }
712                 }
713
714                 if (locations.size()) {
715                         current_location = locations.front();
716                 } else {
717                         current_location = 0;
718                 }
719         }
720
721         changed (OTHER); /* EMIT SIGNAL */
722
723         return 0;
724 }
725
726 struct LocationStartEarlierComparison
727 {
728     bool operator() (Location *a, Location *b) {
729         return a->start() < b->start();
730     }
731 };
732
733 struct LocationStartLaterComparison
734 {
735     bool operator() (Location *a, Location *b) {
736         return a->start() > b->start();
737     }
738 };
739
740 Location *
741 Locations::first_location_before (nframes64_t frame, bool include_special_ranges)
742 {
743         LocationList locs;
744
745         {
746                 Glib::Mutex::Lock lm (lock);
747                 locs = locations;
748         }
749
750         LocationStartLaterComparison cmp;
751         locs.sort (cmp);
752
753         /* locs is now sorted latest..earliest */
754
755         for (LocationList::iterator i = locs.begin(); i != locs.end(); ++i) {
756                 if (!include_special_ranges && ((*i)->is_auto_loop() || (*i)->is_auto_punch())) {
757                         continue;
758                 }
759                 if (!(*i)->is_hidden() && (*i)->start() < frame) {
760                         return (*i);
761                 }
762         }
763
764         return 0;
765 }
766
767 Location *
768 Locations::first_location_after (nframes64_t frame, bool include_special_ranges)
769 {
770         LocationList locs;
771
772         {
773                 Glib::Mutex::Lock lm (lock);
774                 locs = locations;
775         }
776
777         LocationStartEarlierComparison cmp;
778         locs.sort (cmp);
779
780         /* locs is now sorted earliest..latest */
781
782         for (LocationList::iterator i = locs.begin(); i != locs.end(); ++i) {
783                 if (!include_special_ranges && ((*i)->is_auto_loop() || (*i)->is_auto_punch())) {
784                         continue;
785                 }
786                 if (!(*i)->is_hidden() && (*i)->start() > frame) {
787                         return (*i);
788                 }
789         }
790
791         return 0;
792 }
793
794 /** Look for the `marks' (either locations which are marks, or start/end points of range markers) either
795  *  side of a frame.  Note that if frame is exactly on a `mark', that mark will not be considered for returning
796  *  as before/after.
797  *  @param frame Frame to look for.
798  *  @param before Filled in with the position of the last `mark' before `frame' (or max_frames if none exists)
799  *  @param after Filled in with the position of the next `mark' after `frame' (or max_frames if none exists)
800  */
801 void
802 Locations::marks_either_side (nframes64_t const frame, nframes64_t& before, nframes64_t& after) const
803 {
804         before = after = max_frames;
805         
806         LocationList locs;
807
808         {
809                 Glib::Mutex::Lock lm (lock);
810                 locs = locations;
811         }
812
813         /* Get a list of positions; don't store any that are exactly on our requested position */
814         
815         std::list<nframes64_t> positions;
816
817         for (LocationList::const_iterator i = locs.begin(); i != locs.end(); ++i) {
818                 if (((*i)->is_auto_loop() || (*i)->is_auto_punch())) {
819                         continue;
820                 }
821
822                 if (!(*i)->is_hidden()) {
823                         if ((*i)->is_mark ()) {
824                                 if ((*i)->start() != frame) {
825                                         positions.push_back ((*i)->start ());
826                                 }
827                         } else {
828                                 if ((*i)->start() != frame) {
829                                         positions.push_back ((*i)->start ());
830                                 }
831                                 if ((*i)->end() != frame) {
832                                         positions.push_back ((*i)->end ());
833                                 }
834                         }
835                 }
836         }
837
838         if (positions.empty ()) {
839                 return;
840         }
841
842         positions.sort ();
843
844         std::list<nframes64_t>::iterator i = positions.begin ();
845         while (i != positions.end () && *i < frame) {
846                 ++i;
847         }
848
849         if (i == positions.end ()) {
850                 /* run out of marks */
851                 before = positions.back ();
852                 return;
853         }
854
855         after = *i;
856
857         if (i == positions.begin ()) {
858                 /* none before */
859                 return;
860         }
861         
862         --i;
863         before = *i;
864 }
865
866 Location*
867 Locations::session_range_location () const
868 {
869         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
870                 if ((*i)->is_session_range()) {
871                         return const_cast<Location*> (*i);
872                 }
873         }
874         return 0;
875 }
876
877 Location*
878 Locations::auto_loop_location () const
879 {
880         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
881                 if ((*i)->is_auto_loop()) {
882                         return const_cast<Location*> (*i);
883                 }
884         }
885         return 0;
886 }
887
888 Location*
889 Locations::auto_punch_location () const
890 {
891         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
892                 if ((*i)->is_auto_punch()) {
893                         return const_cast<Location*> (*i);
894                 }
895         }
896        return 0;
897 }
898
899 uint32_t
900 Locations::num_range_markers () const
901 {
902         uint32_t cnt = 0;
903         Glib::Mutex::Lock lm (lock);
904         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
905                 if ((*i)->is_range_marker()) {
906                         ++cnt;
907                 }
908         }
909         return cnt;
910 }
911
912 Location *
913 Locations::get_location_by_id(PBD::ID id)
914 {
915     LocationList::iterator it;
916     for (it  = locations.begin(); it != locations.end(); ++it)
917         if (id == (*it)->id())
918             return *it;
919
920     return 0;
921 }
922
923 void
924 Locations::find_all_between (nframes64_t start, nframes64_t end, LocationList& ll, Location::Flags flags)
925 {
926         Glib::Mutex::Lock lm (lock);
927
928         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
929                 if ((flags == 0 || (*i)->matches (flags)) &&
930                     ((*i)->start() >= start && (*i)->end() < end)) {
931                         ll.push_back (*i);
932                 }
933         }
934 }