Clarify stop-at-session-end behaviour; should fix #4033.
[ardour.git] / libs / ardour / midi_playlist.cc
1 /*
2     Copyright (C) 2006 Paul Davis
3     Author: David Robillard
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <cassert>
21
22 #include <algorithm>
23 #include <iostream>
24
25 #include <stdlib.h>
26
27 #include "pbd/error.h"
28
29 #include "evoral/EventList.hpp"
30
31 #include "ardour/configuration.h"
32 #include "ardour/debug.h"
33 #include "ardour/midi_model.h"
34 #include "ardour/midi_playlist.h"
35 #include "ardour/midi_region.h"
36 #include "ardour/midi_ring_buffer.h"
37 #include "ardour/session.h"
38 #include "ardour/types.h"
39
40 #include "i18n.h"
41
42 using namespace ARDOUR;
43 using namespace PBD;
44 using namespace std;
45
46 MidiPlaylist::MidiPlaylist (Session& session, const XMLNode& node, bool hidden)
47         : Playlist (session, node, DataType::MIDI, hidden)
48         , _note_mode(Sustained)
49 {
50 #ifndef NDEBUG
51         const XMLProperty* prop = node.property("type");
52         assert(prop && DataType(prop->value()) == DataType::MIDI);
53 #endif
54
55         in_set_state++;
56         if (set_state (node, Stateful::loading_state_version)) {
57                 throw failed_constructor ();
58         }
59         in_set_state--;
60 }
61
62 MidiPlaylist::MidiPlaylist (Session& session, string name, bool hidden)
63         : Playlist (session, name, DataType::MIDI, hidden)
64         , _note_mode(Sustained)
65 {
66 }
67
68 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, string name, bool hidden)
69         : Playlist (other, name, hidden)
70         , _note_mode(other->_note_mode)
71 {
72 }
73
74 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, framepos_t start, framecnt_t dur, string name, bool hidden)
75         : Playlist (other, start, dur, name, hidden)
76         , _note_mode(other->_note_mode)
77 {
78         /* this constructor does NOT notify others (session) */
79 }
80
81 MidiPlaylist::~MidiPlaylist ()
82 {
83 }
84
85 template<typename Time>
86 struct EventsSortByTime {
87     bool operator() (Evoral::Event<Time>* a, Evoral::Event<Time>* b) {
88             return a->time() < b->time();
89     }
90 };
91
92 /** Returns the number of frames in time duration read (eg could be large when 0 events are read) */
93 framecnt_t
94 MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst, framepos_t start, framecnt_t dur, unsigned chan_n)
95 {
96         /* this function is never called from a realtime thread, so
97            its OK to block (for short intervals).
98         */
99
100         Glib::RecMutex::Lock rm (region_lock);
101         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("++++++ %1 .. %2  +++++++++++++++++++++++++++++++++++++++++++++++\n", start, start + dur));
102
103         framepos_t end = start + dur - 1;
104
105         // relevent regions overlapping start <--> end
106         vector< boost::shared_ptr<Region> > regs;
107         typedef pair<MidiStateTracker*,framepos_t> TrackerInfo;
108         vector<TrackerInfo> tracker_info;
109         uint32_t note_cnt = 0;
110
111         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
112                 if ((*i)->coverage (start, end) != OverlapNone) {
113                         regs.push_back(*i);
114                 } else {
115                         NoteTrackers::iterator t = _note_trackers.find ((*i).get());
116                         if (t != _note_trackers.end()) {
117
118                                 /* add it the set of trackers we will do note resolution
119                                    on, and remove it from the list we are keeping
120                                    around, because we don't need it anymore.
121
122                                    if the end of the region (where we want to theoretically resolve notes)
123                                    is outside the current read range, then just do it at the start
124                                    of this read range.
125                                 */
126
127                                 framepos_t resolve_at = (*i)->last_frame();
128                                 if (resolve_at < start || resolve_at >= end) {
129                                         resolve_at = start;
130                                 }
131
132                                 tracker_info.push_back (TrackerInfo (t->second, resolve_at));
133                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("time to resolve & remove tracker for %1 @ %2\n", (*i)->name(), resolve_at));
134                                 note_cnt += (t->second->on());
135                                 _note_trackers.erase (t);
136                         }
137                 }
138         }
139
140         if (note_cnt == 0 && !tracker_info.empty()) {
141                 /* trackers to dispose of, but they have no notes in them */
142                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Clearing %1 empty trackers\n", tracker_info.size()));
143                 for (vector<TrackerInfo>::iterator t = tracker_info.begin(); t != tracker_info.end(); ++t) {
144                         delete (*t).first;
145                 }
146                 tracker_info.clear ();
147         }
148
149         if (regs.size() == 1 && tracker_info.empty()) {
150
151                 /* just a single region - read directly into dst */
152
153                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Single region (%1) read, no out-of-bound region tracking info\n", regs.front()->name()));
154
155                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(regs.front());
156
157                 if (mr) {
158
159                         NoteTrackers::iterator t = _note_trackers.find (mr.get());
160                         MidiStateTracker* tracker;
161                         bool new_tracker = false;
162
163                         if (t == _note_trackers.end()) {
164                                 tracker = new MidiStateTracker;
165                                 new_tracker = true;
166                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tBEFORE: new tracker\n");
167                         } else {
168                                 tracker = t->second;
169                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tBEFORE: tracker says there are %1 on notes\n", tracker->on()));
170                         }
171
172                         mr->read_at (dst, start, dur, chan_n, _note_mode, tracker);
173                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tAFTER: tracker says there are %1 on notes\n", tracker->on()));
174
175                         if (new_tracker) {
176                                 pair<Region*,MidiStateTracker*> newpair;
177                                 newpair.first = mr.get();
178                                 newpair.second = tracker;
179                                 _note_trackers.insert (newpair);
180                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
181                         }
182                 }
183
184         } else {
185
186                 /* multiple regions and/or note resolution: sort by layer, read into a temporary non-monotonically
187                    sorted EventSink, sort and then insert into dst.
188                 */
189
190                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("%1 regions to read, plus %2 trackers\n", regs.size(), tracker_info.size()));
191
192                 Evoral::EventList<framepos_t> evlist;
193
194                 for (vector<TrackerInfo>::iterator t = tracker_info.begin(); t != tracker_info.end(); ++t) {
195                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Resolve %1 notes\n", (*t).first->on()));
196                         (*t).first->resolve_notes (evlist, (*t).second);
197                         delete (*t).first;
198                 }
199
200 #ifndef NDEBUG
201                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("After resolution we now have %1 events\n",  evlist.size()));
202                 for (Evoral::EventList<framepos_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
203                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
204                 }
205 #endif
206
207                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("for %1 .. %2 we have %3 to consider\n", start, start+dur-1, regs.size()));
208
209                 for (vector<boost::shared_ptr<Region> >::iterator i = regs.begin(); i != regs.end(); ++i) {
210                         boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*i);
211                         if (!mr) {
212                                 continue;
213                         }
214
215                         NoteTrackers::iterator t = _note_trackers.find (mr.get());
216                         MidiStateTracker* tracker;
217                         bool new_tracker = false;
218
219
220                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Before %1 (%2 .. %3) we now have %4 events\n", mr->name(), mr->position(), mr->last_frame(), evlist.size()));
221
222                         if (t == _note_trackers.end()) {
223                                 tracker = new MidiStateTracker;
224                                 new_tracker = true;
225                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tBEFORE: new tracker\n");
226                         } else {
227                                 tracker = t->second;
228                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tBEFORE: tracker says there are %1 on notes\n", tracker->on()));
229                         }
230
231
232                         mr->read_at (evlist, start, dur, chan_n, _note_mode, tracker);
233
234 #ifndef NDEBUG
235                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("After %1 (%2 .. %3) we now have %4\n", mr->name(), mr->position(), mr->last_frame(), evlist.size()));
236                         for (Evoral::EventList<framepos_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
237                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
238                         }
239                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tAFTER: tracker says there are %1 on notes\n", tracker->on()));
240 #endif
241
242                         if (new_tracker) {
243                                 pair<Region*,MidiStateTracker*> newpair;
244                                 newpair.first = mr.get();
245                                 newpair.second = tracker;
246                                 _note_trackers.insert (newpair);
247                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
248                         }
249                 }
250
251                 if (!evlist.empty()) {
252
253                         /* sort the event list */
254                         EventsSortByTime<framepos_t> time_cmp;
255                         evlist.sort (time_cmp);
256
257 #ifndef NDEBUG
258                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Final we now have %1 events\n",  evlist.size()));
259                         for (Evoral::EventList<framepos_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
260                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
261                         }
262 #endif
263                         /* write into dst */
264                         for (Evoral::EventList<framepos_t>::iterator e = evlist.begin(); e != evlist.end(); ++e) {
265                                 Evoral::Event<framepos_t>* ev (*e);
266                                 dst.write (ev->time(), ev->event_type(), ev->size(), ev->buffer());
267                                 delete ev;
268                         }
269
270                 }
271         }
272
273         DEBUG_TRACE (DEBUG::MidiPlaylistIO, "-------------------------------------------------------------\n");
274         return dur;
275 }
276
277 void
278 MidiPlaylist::clear_note_trackers ()
279 {
280         Glib::RecMutex::Lock rm (region_lock);
281         for (NoteTrackers::iterator n = _note_trackers.begin(); n != _note_trackers.end(); ++n) {
282                 delete n->second;
283         }
284         _note_trackers.clear ();
285 }
286
287 void
288 MidiPlaylist::remove_dependents (boost::shared_ptr<Region> region)
289 {
290         /* MIDI regions have no dependents (crossfades) but we might be tracking notes */
291         NoteTrackers::iterator t = _note_trackers.find (region.get());
292
293         /* GACK! THREAD SAFETY! */
294
295         if (t != _note_trackers.end()) {
296                 delete t->second;
297                 _note_trackers.erase (t);
298         }
299 }
300
301
302 void
303 MidiPlaylist::refresh_dependents (boost::shared_ptr<Region> /*r*/)
304 {
305         /* MIDI regions have no dependents (crossfades) */
306 }
307
308 void
309 MidiPlaylist::finalize_split_region (boost::shared_ptr<Region> /*original*/, boost::shared_ptr<Region> /*left*/, boost::shared_ptr<Region> /*right*/)
310 {
311         /* No MIDI crossfading (yet?), so nothing to do here */
312 }
313
314 void
315 MidiPlaylist::check_dependents (boost::shared_ptr<Region> /*r*/, bool /*norefresh*/)
316 {
317         /* MIDI regions have no dependents (crossfades) */
318 }
319
320
321 int
322 MidiPlaylist::set_state (const XMLNode& node, int version)
323 {
324         in_set_state++;
325         freeze ();
326
327         if (Playlist::set_state (node, version)) {
328                 return -1;
329         }
330
331         thaw();
332         in_set_state--;
333
334         return 0;
335 }
336
337 void
338 MidiPlaylist::dump () const
339 {
340         boost::shared_ptr<Region> r;
341
342         cerr << "Playlist \"" << _name << "\" " << endl
343         << regions.size() << " regions "
344         << endl;
345
346         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
347                 r = *i;
348                 cerr << "  " << r->name() << " @ " << r << " ["
349                 << r->start() << "+" << r->length()
350                 << "] at "
351                 << r->position()
352                 << " on layer "
353                 << r->layer ()
354                 << endl;
355         }
356 }
357
358 bool
359 MidiPlaylist::destroy_region (boost::shared_ptr<Region> region)
360 {
361         boost::shared_ptr<MidiRegion> r = boost::dynamic_pointer_cast<MidiRegion> (region);
362
363         if (!r) {
364                 return false;
365         }
366
367         bool changed = false;
368
369         {
370                 RegionLock rlock (this);
371                 RegionList::iterator i;
372                 RegionList::iterator tmp;
373
374                 for (i = regions.begin(); i != regions.end(); ) {
375
376                         tmp = i;
377                         ++tmp;
378
379                         if ((*i) == region) {
380                                 regions.erase (i);
381                                 changed = true;
382                         }
383
384                         i = tmp;
385                 }
386         }
387
388
389         if (changed) {
390                 /* overload this, it normally means "removed", not destroyed */
391                 notify_region_removed (region);
392         }
393
394         return changed;
395 }
396
397 set<Evoral::Parameter>
398 MidiPlaylist::contained_automation()
399 {
400         /* this function is never called from a realtime thread, so
401            its OK to block (for short intervals).
402         */
403
404         Glib::RecMutex::Lock rm (region_lock);
405
406         set<Evoral::Parameter> ret;
407
408         for (RegionList::const_iterator r = regions.begin(); r != regions.end(); ++r) {
409                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*r);
410
411                 for (Automatable::Controls::iterator c = mr->model()->controls().begin();
412                                 c != mr->model()->controls().end(); ++c) {
413                         ret.insert(c->first);
414                 }
415         }
416
417         return ret;
418 }
419
420
421 bool
422 MidiPlaylist::region_changed (const PBD::PropertyChange& what_changed, boost::shared_ptr<Region> region)
423 {
424         if (in_flush || in_set_state) {
425                 return false;
426         }
427
428         PBD::PropertyChange our_interests;
429         our_interests.add (Properties::midi_data);
430
431         bool parent_wants_notify = Playlist::region_changed (what_changed, region);
432
433         if (parent_wants_notify || what_changed.contains (our_interests)) {
434                 notify_contents_changed ();
435         }
436
437         return true;
438 }
439