main fix: when transport stops, clear per-region per-playlist note trackers even...
[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         DEBUG_TRACE (DEBUG::MidiTrackers, string_compose ("%1 clears all note trackers\n", name()));
285         _note_trackers.clear ();
286 }
287
288 void
289 MidiPlaylist::remove_dependents (boost::shared_ptr<Region> region)
290 {
291         /* MIDI regions have no dependents (crossfades) but we might be tracking notes */
292         NoteTrackers::iterator t = _note_trackers.find (region.get());
293
294         /* GACK! THREAD SAFETY! */
295
296         if (t != _note_trackers.end()) {
297                 delete t->second;
298                 _note_trackers.erase (t);
299         }
300 }
301
302
303 void
304 MidiPlaylist::refresh_dependents (boost::shared_ptr<Region> /*r*/)
305 {
306         /* MIDI regions have no dependents (crossfades) */
307 }
308
309 void
310 MidiPlaylist::finalize_split_region (boost::shared_ptr<Region> /*original*/, boost::shared_ptr<Region> /*left*/, boost::shared_ptr<Region> /*right*/)
311 {
312         /* No MIDI crossfading (yet?), so nothing to do here */
313 }
314
315 void
316 MidiPlaylist::check_dependents (boost::shared_ptr<Region> /*r*/, bool /*norefresh*/)
317 {
318         /* MIDI regions have no dependents (crossfades) */
319 }
320
321
322 int
323 MidiPlaylist::set_state (const XMLNode& node, int version)
324 {
325         in_set_state++;
326         freeze ();
327
328         if (Playlist::set_state (node, version)) {
329                 return -1;
330         }
331
332         thaw();
333         in_set_state--;
334
335         return 0;
336 }
337
338 void
339 MidiPlaylist::dump () const
340 {
341         boost::shared_ptr<Region> r;
342
343         cerr << "Playlist \"" << _name << "\" " << endl
344         << regions.size() << " regions "
345         << endl;
346
347         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
348                 r = *i;
349                 cerr << "  " << r->name() << " @ " << r << " ["
350                 << r->start() << "+" << r->length()
351                 << "] at "
352                 << r->position()
353                 << " on layer "
354                 << r->layer ()
355                 << endl;
356         }
357 }
358
359 bool
360 MidiPlaylist::destroy_region (boost::shared_ptr<Region> region)
361 {
362         boost::shared_ptr<MidiRegion> r = boost::dynamic_pointer_cast<MidiRegion> (region);
363
364         if (!r) {
365                 return false;
366         }
367
368         bool changed = false;
369
370         {
371                 RegionLock rlock (this);
372                 RegionList::iterator i;
373                 RegionList::iterator tmp;
374
375                 for (i = regions.begin(); i != regions.end(); ) {
376
377                         tmp = i;
378                         ++tmp;
379
380                         if ((*i) == region) {
381                                 regions.erase (i);
382                                 changed = true;
383                         }
384
385                         i = tmp;
386                 }
387         }
388
389
390         if (changed) {
391                 /* overload this, it normally means "removed", not destroyed */
392                 notify_region_removed (region);
393         }
394
395         return changed;
396 }
397
398 set<Evoral::Parameter>
399 MidiPlaylist::contained_automation()
400 {
401         /* this function is never called from a realtime thread, so
402            its OK to block (for short intervals).
403         */
404
405         Glib::RecMutex::Lock rm (region_lock);
406
407         set<Evoral::Parameter> ret;
408
409         for (RegionList::const_iterator r = regions.begin(); r != regions.end(); ++r) {
410                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*r);
411
412                 for (Automatable::Controls::iterator c = mr->model()->controls().begin();
413                                 c != mr->model()->controls().end(); ++c) {
414                         ret.insert(c->first);
415                 }
416         }
417
418         return ret;
419 }
420
421
422 bool
423 MidiPlaylist::region_changed (const PBD::PropertyChange& what_changed, boost::shared_ptr<Region> region)
424 {
425         if (in_flush || in_set_state) {
426                 return false;
427         }
428
429         PBD::PropertyChange our_interests;
430         our_interests.add (Properties::midi_data);
431
432         bool parent_wants_notify = Playlist::region_changed (what_changed, region);
433
434         if (parent_wants_notify || what_changed.contains (our_interests)) {
435                 notify_contents_changed ();
436         }
437
438         return true;
439 }
440