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