89b59147533c677a6c064532d0e284cdaea6640c
[ardour.git] / libs / ardour / midi_playlist.cc
1 /*
2     Copyright (C) 2006 Paul Davis
3     Written by Dave Robillard, 2006
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 "evoral/EventList.hpp"
28
29 #include "ardour/debug.h"
30 #include "ardour/types.h"
31 #include "ardour/configuration.h"
32 #include "ardour/midi_playlist.h"
33 #include "ardour/midi_region.h"
34 #include "ardour/session.h"
35 #include "ardour/midi_ring_buffer.h"
36
37 #include "pbd/error.h"
38
39 #include "i18n.h"
40
41 using namespace ARDOUR;
42 using namespace PBD;
43 using namespace std;
44
45 MidiPlaylist::MidiPlaylist (Session& session, const XMLNode& node, bool hidden)
46         : Playlist (session, node, DataType::MIDI, hidden)
47         , _note_mode(Sustained)
48 {
49         const XMLProperty* prop = node.property("type");
50         assert(prop && DataType(prop->value()) == DataType::MIDI);
51
52         in_set_state++;
53         set_state (node, Stateful::loading_state_version);
54         in_set_state--;
55 }
56
57 MidiPlaylist::MidiPlaylist (Session& session, string name, bool hidden)
58         : Playlist (session, name, DataType::MIDI, hidden)
59         , _note_mode(Sustained)
60 {
61 }
62
63 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, string name, bool hidden)
64         : Playlist (other, name, hidden)
65         , _note_mode(other->_note_mode)
66 {
67 }
68
69 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, nframes_t start, nframes_t dur, string name, bool hidden)
70         : Playlist (other, start, dur, name, hidden)
71         , _note_mode(other->_note_mode)
72 {
73         /* this constructor does NOT notify others (session) */
74 }
75
76 MidiPlaylist::~MidiPlaylist ()
77 {
78 }
79
80 template<typename Time>
81 struct EventsSortByTime {
82     bool operator() (Evoral::Event<Time>* a, Evoral::Event<Time>* b) {
83             return a->time() < b->time();
84     }
85 };
86
87 /** Returns the number of frames in time duration read (eg could be large when 0 events are read) */
88 nframes_t
89 MidiPlaylist::read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_t dur, unsigned chan_n)
90 {
91         /* this function is never called from a realtime thread, so
92            its OK to block (for short intervals).
93         */
94
95         Glib::RecMutex::Lock rm (region_lock);
96         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("++++++ %1 .. %2  +++++++++++++++++++++++++++++++++++++++++++++++\n", start, start + dur));
97
98         nframes_t end = start + dur - 1;
99
100         _read_data_count = 0;
101
102         // relevent regions overlapping start <--> end
103         vector< boost::shared_ptr<Region> > regs;
104         typedef pair<MidiStateTracker*,nframes64_t> TrackerInfo;
105         vector<TrackerInfo> tracker_info;
106         uint32_t note_cnt = 0;
107
108         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
109                 if ((*i)->coverage (start, end) != OverlapNone) {
110                         regs.push_back(*i);
111                 } else {
112                         NoteTrackers::iterator t = _note_trackers.find ((*i).get());
113                         if (t != _note_trackers.end()) {
114
115                                 /* add it the set of trackers we will do note resolution
116                                    on, and remove it from the list we are keeping
117                                    around, because we don't need it anymore.
118
119                                    if the end of the region (where we want to theoretically resolve notes)
120                                    is outside the current read range, then just do it at the start
121                                    of this read range.
122                                 */
123
124                                 nframes64_t resolve_at = (*i)->last_frame();
125                                 if (resolve_at >= end) {
126                                         resolve_at = start;
127                                 }
128
129                                 tracker_info.push_back (TrackerInfo (t->second, resolve_at));
130                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("time to resolve & remove tracker for %1 @ %2\n", (*i)->name(), resolve_at));
131                                 note_cnt += (t->second->on());
132                                 _note_trackers.erase (t);
133                         }
134                 }
135         }
136
137         if (note_cnt == 0 && !tracker_info.empty()) {
138                 /* trackers to dispose of, but they have no notes in them */
139                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Clearing %1 empty trackers\n", tracker_info.size()));
140                 for (vector<TrackerInfo>::iterator t = tracker_info.begin(); t != tracker_info.end(); ++t) {
141                         delete (*t).first;
142                 }
143                 tracker_info.clear ();
144         }
145
146         if (regs.size() == 1 && tracker_info.empty()) {
147
148                 /* just a single region - read directly into dst */
149
150                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Single region (%1) read, no out-of-bound region tracking info\n", regs.front()->name()));
151
152                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(regs.front());
153
154                 if (mr) {
155
156                         NoteTrackers::iterator t = _note_trackers.find (mr.get());
157                         MidiStateTracker* tracker;
158                         bool new_tracker = false;
159
160                         if (t == _note_trackers.end()) {
161                                 tracker = new MidiStateTracker;
162                                 new_tracker = true;
163                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tBEFORE: new tracker\n");
164                         } else {
165                                 tracker = t->second;
166                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tBEFORE: tracker says there are %1 on notes\n", tracker->on()));
167                         }
168
169                         mr->read_at (dst, start, dur, chan_n, _note_mode, tracker);
170                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tAFTER: tracker says there are %1 on notes\n", tracker->on()));
171
172                         if (new_tracker) {
173                                 pair<Region*,MidiStateTracker*> newpair;
174                                 newpair.first = mr.get();
175                                 newpair.second = tracker;
176                                 _note_trackers.insert (newpair);
177                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
178                         }
179
180                         _read_data_count += mr->read_data_count();
181                 }
182
183         } else {
184
185                 /* multiple regions and/or note resolution: sort by layer, read into a temporary non-monotonically
186                    sorted EventSink, sort and then insert into dst.
187                 */
188
189                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("%1 regions to read, plus %2 trackers\n", regs.size(), tracker_info.size()));
190
191                 Evoral::EventList<nframes_t> evlist;
192
193                 for (vector<TrackerInfo>::iterator t = tracker_info.begin(); t != tracker_info.end(); ++t) {
194                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Resolve %1 notes\n", (*t).first->on()));
195                         (*t).first->resolve_notes (evlist, (*t).second);
196                         delete (*t).first;
197                 }
198
199 #ifndef NDEBUG
200                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("After resolution we now have %1 events\n",  evlist.size()));
201                 for (Evoral::EventList<nframes_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
202                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
203                 }
204 #endif
205
206                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("for %1 .. %2 we have %3 to consider\n", start, start+dur-1, regs.size()));
207
208                 for (vector<boost::shared_ptr<Region> >::iterator i = regs.begin(); i != regs.end(); ++i) {
209                         boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*i);
210                         if (!mr) {
211                                 continue;
212                         }
213
214                         NoteTrackers::iterator t = _note_trackers.find (mr.get());
215                         MidiStateTracker* tracker;
216                         bool new_tracker = false;
217
218
219                         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()));
220
221                         if (t == _note_trackers.end()) {
222                                 tracker = new MidiStateTracker;
223                                 new_tracker = true;
224                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tBEFORE: new tracker\n");
225                         } else {
226                                 tracker = t->second;
227                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tBEFORE: tracker says there are %1 on notes\n", tracker->on()));
228                         }
229
230
231                         mr->read_at (evlist, start, dur, chan_n, _note_mode, tracker);
232                         _read_data_count += mr->read_data_count();
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<nframes_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<nframes_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<nframes_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<nframes_t>::iterator e = evlist.begin(); e != evlist.end(); ++e) {
265                                 Evoral::Event<nframes_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         Playlist::set_state (node, version);
328
329         thaw();
330         in_set_state--;
331
332         return 0;
333 }
334
335 void
336 MidiPlaylist::dump () const
337 {
338         boost::shared_ptr<Region> r;
339
340         cerr << "Playlist \"" << _name << "\" " << endl
341         << regions.size() << " regions "
342         << endl;
343
344         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
345                 r = *i;
346                 cerr << "  " << r->name() << " @ " << r << " ["
347                 << r->start() << "+" << r->length()
348                 << "] at "
349                 << r->position()
350                 << " on layer "
351                 << r->layer ()
352                 << endl;
353         }
354 }
355
356 bool
357 MidiPlaylist::destroy_region (boost::shared_ptr<Region> region)
358 {
359         boost::shared_ptr<MidiRegion> r = boost::dynamic_pointer_cast<MidiRegion> (region);
360         bool changed = false;
361
362         if (r == 0) {
363                 PBD::fatal << _("programming error: non-midi Region passed to remove_overlap in midi playlist")
364                 << endmsg;
365                 /*NOTREACHED*/
366                 return false;
367         }
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         // Feeling rather uninterested today, but thanks for the heads up anyway!
429
430         PBD::PropertyChange our_interests;
431
432         bool parent_wants_notify;
433
434         parent_wants_notify = Playlist::region_changed (what_changed, region);
435
436         if (parent_wants_notify || what_changed.contains (our_interests)) {
437                 notify_contents_changed ();
438         }
439
440         return true;
441 }
442