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
~SessionPlaylists()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
add(boost::shared_ptr<Playlist> playlist)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
remove_weak(boost::weak_ptr<Playlist> playlist)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
remove(boost::shared_ptr<Playlist> playlist)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
update_tracking()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
track(bool inuse,boost::weak_ptr<Playlist> wpl)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
n_playlists() const183 SessionPlaylists::n_playlists () const
184 {
185 Glib::Threads::Mutex::Lock lm (lock);
186 return playlists.size();
187 }
188
189 boost::shared_ptr<Playlist>
for_pgroup(string pgroup_id,const PBD::ID & id)190 SessionPlaylists::for_pgroup (string pgroup_id, const PBD::ID& id)
191 {
192 Glib::Threads::Mutex::Lock lm (lock);
193
194 for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
195 if ((*i)->pgroup_id() == pgroup_id) {
196 if ((*i)->get_orig_track_id() == id) {
197 return* i;
198 }
199 }
200 }
201
202 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
203 if ((*i)->pgroup_id() == pgroup_id) {
204 if ((*i)->get_orig_track_id() == id) {
205 return* i;
206 }
207 }
208 }
209
210 return boost::shared_ptr<Playlist>();
211 }
212
213 std::vector<boost::shared_ptr<Playlist> >
playlists_for_pgroup(std::string pgroup)214 SessionPlaylists::playlists_for_pgroup (std::string pgroup)
215 {
216 Glib::Threads::Mutex::Lock lm (lock);
217
218 vector<boost::shared_ptr<Playlist> > pl_tr;
219
220 for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
221 if ((*i)->pgroup_id().compare(pgroup)==0) {
222 pl_tr.push_back (*i);
223 }
224 }
225
226 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
227 if ((*i)->pgroup_id().compare(pgroup)==0) {
228 pl_tr.push_back (*i);
229 }
230 }
231
232 return pl_tr;
233 }
234
235 boost::shared_ptr<Playlist>
by_name(string name)236 SessionPlaylists::by_name (string name)
237 {
238 Glib::Threads::Mutex::Lock lm (lock);
239
240 for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
241 if ((*i)->name() == name) {
242 return* i;
243 }
244 }
245
246 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
247 if ((*i)->name() == name) {
248 return* i;
249 }
250 }
251
252 return boost::shared_ptr<Playlist>();
253 }
254
255 boost::shared_ptr<Playlist>
by_id(const PBD::ID & id)256 SessionPlaylists::by_id (const PBD::ID& id)
257 {
258 Glib::Threads::Mutex::Lock lm (lock);
259
260 for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
261 if ((*i)->id() == id) {
262 return* i;
263 }
264 }
265
266 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
267 if ((*i)->id() == id) {
268 return* i;
269 }
270 }
271
272 return boost::shared_ptr<Playlist>();
273 }
274
275 void
unassigned(std::list<boost::shared_ptr<Playlist>> & list)276 SessionPlaylists::unassigned (std::list<boost::shared_ptr<Playlist> > & list)
277 {
278 Glib::Threads::Mutex::Lock lm (lock);
279
280 for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
281 if (!(*i)->get_orig_track_id().to_s().compare ("0")) {
282 list.push_back (*i);
283 }
284 }
285
286 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
287 if (!(*i)->get_orig_track_id().to_s().compare ("0")) {
288 list.push_back (*i);
289 }
290 }
291 }
292
293 void
update_orig_2X(PBD::ID old_orig,PBD::ID new_orig)294 SessionPlaylists::update_orig_2X (PBD::ID old_orig, PBD::ID new_orig)
295 {
296 Glib::Threads::Mutex::Lock lm (lock);
297
298 for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
299 if ((*i)->get_orig_track_id() == old_orig) {
300 (*i)->set_orig_track_id (new_orig);
301 }
302 }
303
304 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
305 if ((*i)->get_orig_track_id() == old_orig) {
306 (*i)->set_orig_track_id (new_orig);
307 }
308 }
309 }
310
311 void
get(vector<boost::shared_ptr<Playlist>> & s) const312 SessionPlaylists::get (vector<boost::shared_ptr<Playlist> >& s) const
313 {
314 Glib::Threads::Mutex::Lock lm (lock);
315
316 for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
317 s.push_back (*i);
318 }
319
320 for (List::const_iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
321 s.push_back (*i);
322 }
323 }
324
325 void
destroy_region(boost::shared_ptr<Region> r)326 SessionPlaylists::destroy_region (boost::shared_ptr<Region> r)
327 {
328 Glib::Threads::Mutex::Lock lm (lock);
329
330 for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
331 (*i)->destroy_region (r);
332 }
333
334 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
335 (*i)->destroy_region (r);
336 }
337 }
338
339 /** Return the number of playlists (not regions) that contain @a src
340 * Important: this counts usage in both used and not-used playlists.
341 */
342 uint32_t
source_use_count(boost::shared_ptr<const Source> src) const343 SessionPlaylists::source_use_count (boost::shared_ptr<const Source> src) const
344 {
345 uint32_t count = 0;
346
347 /* XXXX this can go wildly wrong in the presence of circular references
348 * between compound regions.
349 */
350
351 for (List::const_iterator p = playlists.begin(); p != playlists.end(); ++p) {
352 if ((*p)->uses_source (src)) {
353 ++count;
354 break;
355 }
356 }
357
358 for (List::const_iterator p = unused_playlists.begin(); p != unused_playlists.end(); ++p) {
359 if ((*p)->uses_source (src)) {
360 ++count;
361 break;
362 }
363 }
364
365 return count;
366 }
367
368 void
sync_all_regions_with_regions()369 SessionPlaylists::sync_all_regions_with_regions ()
370 {
371 Glib::Threads::Mutex::Lock lm (lock);
372
373 for (List::const_iterator p = playlists.begin(); p != playlists.end(); ++p) {
374 (*p)->sync_all_regions_with_regions ();
375 }
376 }
377
378 void
update_after_tempo_map_change()379 SessionPlaylists::update_after_tempo_map_change ()
380 {
381 for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
382 (*i)->update_after_tempo_map_change ();
383 }
384
385 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
386 (*i)->update_after_tempo_map_change ();
387 }
388 }
389
390 namespace {
391 struct id_compare
392 {
operator ()__anon91b1ae710111::id_compare393 bool operator()(const boost::shared_ptr<Playlist>& p1, const boost::shared_ptr<Playlist>& p2) const
394 {
395 return p1->id () < p2->id ();
396 }
397 };
398
399 typedef std::set<boost::shared_ptr<Playlist> > List;
400 typedef std::set<boost::shared_ptr<Playlist>, id_compare> IDSortedList;
401
402 static void
get_id_sorted_playlists(const List & playlists,IDSortedList & id_sorted_playlists)403 get_id_sorted_playlists (const List& playlists, IDSortedList& id_sorted_playlists)
404 {
405 for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
406 id_sorted_playlists.insert(*i);
407 }
408 }
409
410 } // anonymous namespace
411
412 void
add_state(XMLNode * node,bool save_template,bool include_unused)413 SessionPlaylists::add_state (XMLNode* node, bool save_template, bool include_unused)
414 {
415 XMLNode* child = node->add_child ("Playlists");
416
417 IDSortedList id_sorted_playlists;
418 get_id_sorted_playlists (playlists, id_sorted_playlists);
419
420 for (IDSortedList::iterator i = id_sorted_playlists.begin (); i != id_sorted_playlists.end (); ++i) {
421 if (!(*i)->hidden ()) {
422 if (save_template) {
423 child->add_child_nocopy ((*i)->get_template ());
424 } else {
425 child->add_child_nocopy ((*i)->get_state ());
426 }
427 }
428 }
429
430 if (!include_unused) {
431 return;
432 }
433
434 child = node->add_child ("UnusedPlaylists");
435
436 IDSortedList id_sorted_unused_playlists;
437 get_id_sorted_playlists (unused_playlists, id_sorted_unused_playlists);
438
439 for (IDSortedList::iterator i = id_sorted_unused_playlists.begin ();
440 i != id_sorted_unused_playlists.end (); ++i) {
441 if (!(*i)->hidden()) {
442 if (!(*i)->empty()) {
443 if (save_template) {
444 child->add_child_nocopy ((*i)->get_template());
445 } else {
446 child->add_child_nocopy ((*i)->get_state());
447 }
448 }
449 }
450 }
451 }
452
453 /** @return true for `stop cleanup', otherwise false */
454 bool
maybe_delete_unused(boost::function<int (boost::shared_ptr<Playlist>)> ask)455 SessionPlaylists::maybe_delete_unused (boost::function<int(boost::shared_ptr<Playlist>)> ask)
456 {
457 vector<boost::shared_ptr<Playlist> > playlists_tbd;
458
459 bool delete_remaining = false;
460 bool keep_remaining = false;
461
462 for (List::iterator x = unused_playlists.begin(); x != unused_playlists.end(); ++x) {
463
464 if (keep_remaining) {
465 break;
466 }
467
468 if (delete_remaining) {
469 playlists_tbd.push_back (*x);
470 continue;
471 }
472
473 int status = ask (*x);
474
475 switch (status) {
476 case -1:
477 // abort
478 return true;
479
480 case -2:
481 // keep this and all later
482 keep_remaining = true;
483 break;
484
485 case 2:
486 // delete this and all later
487 delete_remaining = true;
488
489 /* fallthrough */
490 case 1:
491 // delete this
492 playlists_tbd.push_back (*x);
493 break;
494
495 default:
496 /* leave it alone */
497 break;
498 }
499 }
500
501 /* now delete any that were marked for deletion */
502
503 for (vector<boost::shared_ptr<Playlist> >::iterator x = playlists_tbd.begin(); x != playlists_tbd.end(); ++x) {
504 boost::shared_ptr<Playlist> keeper (*x);
505 (*x)->drop_references ();
506 }
507
508 playlists_tbd.clear ();
509
510 return false;
511 }
512
513 int
load(Session & session,const XMLNode & node)514 SessionPlaylists::load (Session& session, const XMLNode& node)
515 {
516 XMLNodeList nlist;
517 XMLNodeConstIterator niter;
518 boost::shared_ptr<Playlist> playlist;
519
520 nlist = node.children();
521
522 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
523
524 if ((playlist = XMLPlaylistFactory (session, **niter)) == 0) {
525 error << _("Session: cannot create Playlist from XML description.") << endmsg;
526 return -1;
527 }
528 }
529
530 return 0;
531 }
532
533 int
load_unused(Session & session,const XMLNode & node)534 SessionPlaylists::load_unused (Session& session, const XMLNode& node)
535 {
536 XMLNodeList nlist;
537 XMLNodeConstIterator niter;
538 boost::shared_ptr<Playlist> playlist;
539
540 nlist = node.children();
541
542 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
543
544 if ((playlist = XMLPlaylistFactory (session, **niter)) == 0) {
545 error << _("Session: cannot create Playlist from XML description.") << endmsg;
546 continue;
547 }
548
549 // now manually untrack it
550
551 track (false, boost::weak_ptr<Playlist> (playlist));
552 }
553
554 return 0;
555 }
556
557 boost::shared_ptr<Playlist>
XMLPlaylistFactory(Session & session,const XMLNode & node)558 SessionPlaylists::XMLPlaylistFactory (Session& session, const XMLNode& node)
559 {
560 try {
561 return PlaylistFactory::create (session, node);
562 }
563
564 catch (failed_constructor& err) {
565 return boost::shared_ptr<Playlist>();
566 }
567 }
568
569 boost::shared_ptr<Crossfade>
find_crossfade(const PBD::ID & id)570 SessionPlaylists::find_crossfade (const PBD::ID& id)
571 {
572 Glib::Threads::Mutex::Lock lm (lock);
573
574 boost::shared_ptr<Crossfade> c;
575
576 for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
577 c = (*i)->find_crossfade (id);
578 if (c) {
579 return c;
580 }
581 }
582
583 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
584 c = (*i)->find_crossfade (id);
585 if (c) {
586 return c;
587 }
588 }
589
590 return boost::shared_ptr<Crossfade> ();
591 }
592
593 uint32_t
region_use_count(boost::shared_ptr<Region> region) const594 SessionPlaylists::region_use_count (boost::shared_ptr<Region> region) const
595 {
596 Glib::Threads::Mutex::Lock lm (lock);
597 uint32_t cnt = 0;
598
599 for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
600 cnt += (*i)->region_use_count (region);
601 }
602
603 for (List::const_iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
604 cnt += (*i)->region_use_count (region);
605 }
606
607 return cnt;
608 }
609
610 vector<boost::shared_ptr<Playlist> >
get_used() const611 SessionPlaylists::get_used () const
612 {
613 vector<boost::shared_ptr<Playlist> > pl;
614
615 Glib::Threads::Mutex::Lock lm (lock);
616
617 for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
618 pl.push_back (*i);
619 }
620
621 return pl;
622 }
623
624 vector<boost::shared_ptr<Playlist> >
get_unused() const625 SessionPlaylists::get_unused () const
626 {
627 vector<boost::shared_ptr<Playlist> > pl;
628
629 Glib::Threads::Mutex::Lock lm (lock);
630
631 for (List::const_iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
632 pl.push_back (*i);
633 }
634
635 return pl;
636 }
637
638 /** @return list of Playlists that are associated with a track */
639 vector<boost::shared_ptr<Playlist> >
playlists_for_track(boost::shared_ptr<Track> tr) const640 SessionPlaylists::playlists_for_track (boost::shared_ptr<Track> tr) const
641 {
642 vector<boost::shared_ptr<Playlist> > pl;
643 get (pl);
644
645 vector<boost::shared_ptr<Playlist> > pl_tr;
646
647 for (vector<boost::shared_ptr<Playlist> >::iterator i = pl.begin(); i != pl.end(); ++i) {
648 if ( ((*i)->get_orig_track_id() == tr->id()) ||
649 (tr->playlist()->id() == (*i)->id()) ||
650 ((*i)->shared_with (tr->id())) )
651 {
652 pl_tr.push_back (*i);
653 }
654 }
655
656 return pl_tr;
657 }
658
659 void
foreach(boost::function<void (boost::shared_ptr<const Playlist>)> functor,bool incl_unused)660 SessionPlaylists::foreach (boost::function<void(boost::shared_ptr<const Playlist>)> functor, bool incl_unused)
661 {
662 Glib::Threads::Mutex::Lock lm (lock);
663 for (List::iterator i = playlists.begin(); i != playlists.end(); i++) {
664 if (!(*i)->hidden()) {
665 functor (*i);
666 }
667 }
668 if (!incl_unused) {
669 return;
670 }
671 for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); i++) {
672 if (!(*i)->hidden()) {
673 functor (*i);
674 }
675 }
676 }
677