NO-OP: whitespace and comments
[ardour.git] / libs / ardour / session_playlists.cc
1 /*
2  * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
3  * Copyright (C) 2009-2016 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2011-2012 David Robillard <d@drobilla.net>
5  * Copyright (C) 2013-2016 John Emmas <john@creativepost.co.uk>
6  * Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
7  * Copyright (C) 2016 Tim Mayberry <mojofunk@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 #include <vector>
24
25 #include "ardour/debug.h"
26 #include "ardour/playlist.h"
27 #include "ardour/playlist_factory.h"
28 #include "ardour/session_playlists.h"
29 #include "ardour/track.h"
30 #include "pbd/i18n.h"
31 #include "pbd/compose.h"
32 #include "pbd/xml++.h"
33
34 using namespace std;
35 using namespace PBD;
36 using namespace ARDOUR;
37
38 SessionPlaylists::~SessionPlaylists ()
39 {
40         DEBUG_TRACE (DEBUG::Destruction, "delete playlists\n");
41
42         for (List::iterator i = playlists.begin(); i != playlists.end(); ) {
43                 SessionPlaylists::List::iterator tmp;
44
45                 tmp = i;
46                 ++tmp;
47
48                 DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for used playlist %1 ; pre-ref = %2\n", (*i)->name(), (*i).use_count()));
49                 boost::shared_ptr<Playlist> keeper (*i);
50                 (*i)->drop_references ();
51
52                 i = tmp;
53         }
54
55         DEBUG_TRACE (DEBUG::Destruction, "delete unused playlists\n");
56         for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ) {
57                 List::iterator tmp;
58
59                 tmp = i;
60                 ++tmp;
61
62                 DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for unused playlist %1 ; pre-ref = %2\n", (*i)->name(), (*i).use_count()));
63                 boost::shared_ptr<Playlist> keeper (*i);
64                 (*i)->drop_references ();
65
66                 i = tmp;
67         }
68
69         playlists.clear ();
70         unused_playlists.clear ();
71 }
72
73 bool
74 SessionPlaylists::add (boost::shared_ptr<Playlist> playlist)
75 {
76         Glib::Threads::Mutex::Lock lm (lock);
77
78         bool const existing = find (playlists.begin(), playlists.end(), playlist) != playlists.end();
79
80         if (!existing) {
81                 playlists.insert (playlists.begin(), playlist);
82                 playlist->InUse.connect_same_thread (*this, boost::bind (&SessionPlaylists::track, this, _1, boost::weak_ptr<Playlist>(playlist)));
83                 playlist->DropReferences.connect_same_thread (
84                         *this, boost::bind (&SessionPlaylists::remove_weak, this, boost::weak_ptr<Playlist> (playlist))
85                         );
86         }
87
88         return existing;
89 }
90
91 void
92 SessionPlaylists::remove_weak (boost::weak_ptr<Playlist> playlist)
93 {
94         boost::shared_ptr<Playlist> p = playlist.lock ();
95         if (p) {
96                 remove (p);
97         }
98 }
99
100 void
101 SessionPlaylists::remove (boost::shared_ptr<Playlist> playlist)
102 {
103         Glib::Threads::Mutex::Lock lm (lock);
104
105         List::iterator i;
106
107         i = find (playlists.begin(), playlists.end(), playlist);
108         if (i != playlists.end()) {
109                 playlists.erase (i);
110         }
111
112         i = find (unused_playlists.begin(), unused_playlists.end(), playlist);
113         if (i != unused_playlists.end()) {
114                 unused_playlists.erase (i);
115         }
116 }
117
118 void
119 SessionPlaylists::update_tracking ()
120 {
121         /* This is intended to be called during session-load, after loading
122          * playlists and re-assigning them to tracks (refcnt is up to date).
123          * Check playlist refcnt, move unused playlist to unused_playlists
124          * array (which may be the case when loading old sessions)
125          */
126         for (List::iterator i = playlists.begin(); i != playlists.end(); ) {
127                 if ((*i)->hidden () || (*i)->used ()) {
128                         ++i;
129                         continue;
130                 }
131
132                 warning << _("Session State: Unused playlist was listed as used.") << endmsg;
133
134                 assert (unused_playlists.find (*i) == unused_playlists.end());
135                 unused_playlists.insert (*i);
136
137                 List::iterator rm = i;
138                 ++i;
139                  playlists.erase (rm);
140         }
141 }
142
143 void
144 SessionPlaylists::track (bool inuse, boost::weak_ptr<Playlist> wpl)
145 {
146         boost::shared_ptr<Playlist> pl(wpl.lock());
147
148         if (!pl) {
149                 return;
150         }
151
152         List::iterator x;
153
154         if (pl->hidden()) {
155                 /* its not supposed to be visible */
156                 return;
157         }
158
159         {
160                 Glib::Threads::Mutex::Lock lm (lock);
161
162                 if (!inuse) {
163
164                         unused_playlists.insert (pl);
165
166                         if ((x = playlists.find (pl)) != playlists.end()) {
167                                 playlists.erase (x);
168                         }
169
170
171                 } else {
172
173                         playlists.insert (pl);
174
175                         if ((x = unused_playlists.find (pl)) != unused_playlists.end()) {
176                                 unused_playlists.erase (x);
177                         }
178                 }
179         }
180 }
181
182 uint32_t
183 SessionPlaylists::n_playlists () const
184 {
185         Glib::Threads::Mutex::Lock lm (lock);
186         return playlists.size();
187 }
188
189 boost::shared_ptr<Playlist>
190 SessionPlaylists::by_name (string name)
191 {
192         Glib::Threads::Mutex::Lock lm (lock);
193
194         for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
195                 if ((*i)->name() == name) {
196                         return* i;
197                 }
198         }
199
200         for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
201                 if ((*i)->name() == name) {
202                         return* i;
203                 }
204         }
205
206         return boost::shared_ptr<Playlist>();
207 }
208
209 boost::shared_ptr<Playlist>
210 SessionPlaylists::by_id (const PBD::ID& id)
211 {
212         Glib::Threads::Mutex::Lock lm (lock);
213
214         for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
215                 if ((*i)->id() == id) {
216                         return* i;
217                 }
218         }
219
220         for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
221                 if ((*i)->id() == id) {
222                         return* i;
223                 }
224         }
225
226         return boost::shared_ptr<Playlist>();
227 }
228
229 void
230 SessionPlaylists::unassigned (std::list<boost::shared_ptr<Playlist> > & list)
231 {
232         Glib::Threads::Mutex::Lock lm (lock);
233
234         for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
235                 if (!(*i)->get_orig_track_id().to_s().compare ("0")) {
236                         list.push_back (*i);
237                 }
238         }
239
240         for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
241                 if (!(*i)->get_orig_track_id().to_s().compare ("0")) {
242                         list.push_back (*i);
243                 }
244         }
245 }
246
247 void
248 SessionPlaylists::get (vector<boost::shared_ptr<Playlist> >& s) const
249 {
250         Glib::Threads::Mutex::Lock lm (lock);
251
252         for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
253                 s.push_back (*i);
254         }
255
256         for (List::const_iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
257                 s.push_back (*i);
258         }
259 }
260
261 void
262 SessionPlaylists::destroy_region (boost::shared_ptr<Region> r)
263 {
264         Glib::Threads::Mutex::Lock lm (lock);
265
266         for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
267                 (*i)->destroy_region (r);
268         }
269
270         for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
271                 (*i)->destroy_region (r);
272         }
273 }
274
275 void
276 SessionPlaylists::find_equivalent_playlist_regions (boost::shared_ptr<Region> region, vector<boost::shared_ptr<Region> >& result)
277 {
278         for (List::iterator i = playlists.begin(); i != playlists.end(); ++i)
279                 (*i)->get_region_list_equivalent_regions (region, result);
280 }
281
282 /** Return the number of playlists (not regions) that contain @a src
283  *  Important: this counts usage in both used and not-used playlists.
284  */
285 uint32_t
286 SessionPlaylists::source_use_count (boost::shared_ptr<const Source> src) const
287 {
288         uint32_t count = 0;
289
290         /* XXXX this can go wildly wrong in the presence of circular references
291          * between compound regions.
292          */
293
294         for (List::const_iterator p = playlists.begin(); p != playlists.end(); ++p) {
295                 if ((*p)->uses_source (src)) {
296                         ++count;
297                         break;
298                 }
299         }
300
301         for (List::const_iterator p = unused_playlists.begin(); p != unused_playlists.end(); ++p) {
302                 if ((*p)->uses_source (src)) {
303                         ++count;
304                         break;
305                 }
306         }
307
308         return count;
309 }
310
311 void
312 SessionPlaylists::sync_all_regions_with_regions ()
313 {
314         Glib::Threads::Mutex::Lock lm (lock);
315
316         for (List::const_iterator p = playlists.begin(); p != playlists.end(); ++p) {
317                 (*p)->sync_all_regions_with_regions ();
318         }
319 }
320
321 void
322 SessionPlaylists::update_after_tempo_map_change ()
323 {
324         for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
325                 (*i)->update_after_tempo_map_change ();
326         }
327
328         for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
329                 (*i)->update_after_tempo_map_change ();
330         }
331 }
332
333 namespace {
334 struct id_compare
335 {
336         bool operator()(const boost::shared_ptr<Playlist>& p1, const boost::shared_ptr<Playlist>& p2)
337         {
338                 return p1->id () < p2->id ();
339         }
340 };
341
342 typedef std::set<boost::shared_ptr<Playlist> > List;
343 typedef std::set<boost::shared_ptr<Playlist>, id_compare> IDSortedList;
344
345 static void
346 get_id_sorted_playlists (const List& playlists, IDSortedList& id_sorted_playlists)
347 {
348         for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
349                 id_sorted_playlists.insert(*i);
350         }
351 }
352
353 } // anonymous namespace
354
355 void
356 SessionPlaylists::add_state (XMLNode* node, bool save_template, bool include_unused)
357 {
358         XMLNode* child = node->add_child ("Playlists");
359
360         IDSortedList id_sorted_playlists;
361         get_id_sorted_playlists (playlists, id_sorted_playlists);
362
363         for (IDSortedList::iterator i = id_sorted_playlists.begin (); i != id_sorted_playlists.end (); ++i) {
364                 if (!(*i)->hidden ()) {
365                         if (save_template) {
366                                 child->add_child_nocopy ((*i)->get_template ());
367                         } else {
368                                 child->add_child_nocopy ((*i)->get_state ());
369                         }
370                 }
371         }
372
373         if (!include_unused) {
374                 return;
375         }
376
377         child = node->add_child ("UnusedPlaylists");
378
379         IDSortedList id_sorted_unused_playlists;
380         get_id_sorted_playlists (unused_playlists, id_sorted_unused_playlists);
381
382         for (IDSortedList::iterator i = id_sorted_unused_playlists.begin ();
383              i != id_sorted_unused_playlists.end (); ++i) {
384                 if (!(*i)->hidden()) {
385                         if (!(*i)->empty()) {
386                                 if (save_template) {
387                                         child->add_child_nocopy ((*i)->get_template());
388                                 } else {
389                                         child->add_child_nocopy ((*i)->get_state());
390                                 }
391                         }
392                 }
393         }
394 }
395
396 /** @return true for `stop cleanup', otherwise false */
397 bool
398 SessionPlaylists::maybe_delete_unused (boost::function<int(boost::shared_ptr<Playlist>)> ask)
399 {
400         vector<boost::shared_ptr<Playlist> > playlists_tbd;
401
402         bool delete_remaining = false;
403         bool keep_remaining = false;
404
405         for (List::iterator x = unused_playlists.begin(); x != unused_playlists.end(); ++x) {
406
407                 if (keep_remaining) {
408                         break;
409                 }
410
411                 if (delete_remaining) {
412                         playlists_tbd.push_back (*x);
413                         continue;
414                 }
415
416                 int status = ask (*x);
417
418                 switch (status) {
419                 case -1:
420                         // abort
421                         return true;
422
423                 case -2:
424                         // keep this and all later
425                         keep_remaining = true;
426                         break;
427
428                 case 2:
429                         // delete this and all later
430                         delete_remaining = true;
431
432                         /* fallthrough */
433                 case 1:
434                         // delete this
435                         playlists_tbd.push_back (*x);
436                         break;
437
438                 default:
439                         /* leave it alone */
440                         break;
441                 }
442         }
443
444         /* now delete any that were marked for deletion */
445
446         for (vector<boost::shared_ptr<Playlist> >::iterator x = playlists_tbd.begin(); x != playlists_tbd.end(); ++x) {
447                 boost::shared_ptr<Playlist> keeper (*x);
448                 (*x)->drop_references ();
449         }
450
451         playlists_tbd.clear ();
452
453         return false;
454 }
455
456 int
457 SessionPlaylists::load (Session& session, const XMLNode& node)
458 {
459         XMLNodeList nlist;
460         XMLNodeConstIterator niter;
461         boost::shared_ptr<Playlist> playlist;
462
463         nlist = node.children();
464
465         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
466
467                 if ((playlist = XMLPlaylistFactory (session, **niter)) == 0) {
468                         error << _("Session: cannot create Playlist from XML description.") << endmsg;
469                 }
470         }
471
472         return 0;
473 }
474
475 int
476 SessionPlaylists::load_unused (Session& session, const XMLNode& node)
477 {
478         XMLNodeList nlist;
479         XMLNodeConstIterator niter;
480         boost::shared_ptr<Playlist> playlist;
481
482         nlist = node.children();
483
484         for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
485
486                 if ((playlist = XMLPlaylistFactory (session, **niter)) == 0) {
487                         error << _("Session: cannot create Playlist from XML description.") << endmsg;
488                         continue;
489                 }
490
491                 // now manually untrack it
492
493                 track (false, boost::weak_ptr<Playlist> (playlist));
494         }
495
496         return 0;
497 }
498
499 boost::shared_ptr<Playlist>
500 SessionPlaylists::XMLPlaylistFactory (Session& session, const XMLNode& node)
501 {
502         try {
503                 return PlaylistFactory::create (session, node);
504         }
505
506         catch (failed_constructor& err) {
507                 return boost::shared_ptr<Playlist>();
508         }
509 }
510
511 boost::shared_ptr<Crossfade>
512 SessionPlaylists::find_crossfade (const PBD::ID& id)
513 {
514         Glib::Threads::Mutex::Lock lm (lock);
515
516         boost::shared_ptr<Crossfade> c;
517
518         for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
519                 c = (*i)->find_crossfade (id);
520                 if (c) {
521                         return c;
522                 }
523         }
524
525         for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
526                 c = (*i)->find_crossfade (id);
527                 if (c) {
528                         return c;
529                 }
530         }
531
532         return boost::shared_ptr<Crossfade> ();
533 }
534
535 uint32_t
536 SessionPlaylists::region_use_count (boost::shared_ptr<Region> region) const
537 {
538         Glib::Threads::Mutex::Lock lm (lock);
539         uint32_t cnt = 0;
540
541         for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
542                 cnt += (*i)->region_use_count (region);
543         }
544
545         for (List::const_iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
546                 cnt += (*i)->region_use_count (region);
547         }
548
549         return cnt;
550 }
551
552 vector<boost::shared_ptr<Playlist> >
553 SessionPlaylists::get_used () const
554 {
555         vector<boost::shared_ptr<Playlist> > pl;
556
557         Glib::Threads::Mutex::Lock lm (lock);
558
559         for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
560                 pl.push_back (*i);
561         }
562
563         return pl;
564 }
565
566 vector<boost::shared_ptr<Playlist> >
567 SessionPlaylists::get_unused () const
568 {
569         vector<boost::shared_ptr<Playlist> > pl;
570
571         Glib::Threads::Mutex::Lock lm (lock);
572
573         for (List::const_iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
574                 pl.push_back (*i);
575         }
576
577         return pl;
578 }
579
580 /** @return list of Playlists that are associated with a track */
581 vector<boost::shared_ptr<Playlist> >
582 SessionPlaylists::playlists_for_track (boost::shared_ptr<Track> tr) const
583 {
584         vector<boost::shared_ptr<Playlist> > pl;
585         get (pl);
586
587         vector<boost::shared_ptr<Playlist> > pl_tr;
588
589         for (vector<boost::shared_ptr<Playlist> >::iterator i = pl.begin(); i != pl.end(); ++i) {
590                 if ( ((*i)->get_orig_track_id() == tr->id()) ||
591                         (tr->playlist()->id() == (*i)->id())    ||
592                         ((*i)->shared_with (tr->id())) )
593                 {
594                         pl_tr.push_back (*i);
595                 }
596         }
597
598         return pl_tr;
599 }
600
601 void
602 SessionPlaylists::foreach (boost::function<void(boost::shared_ptr<const Playlist>)> functor, bool incl_unused)
603 {
604         Glib::Threads::Mutex::Lock lm (lock);
605         for (List::iterator i = playlists.begin(); i != playlists.end(); i++) {
606                 if (!(*i)->hidden()) {
607                         functor (*i);
608                 }
609         }
610         if (!incl_unused) {
611                 return;
612         }
613         for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); i++) {
614                 if (!(*i)->hidden()) {
615                         functor (*i);
616                 }
617         }
618 }