Trim include tree.
[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         _read_data_count = 0;
106
107         // relevent regions overlapping start <--> end
108         vector< boost::shared_ptr<Region> > regs;
109         typedef pair<MidiStateTracker*,framepos_t> TrackerInfo;
110         vector<TrackerInfo> tracker_info;
111         uint32_t note_cnt = 0;
112
113         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
114                 if ((*i)->coverage (start, end) != OverlapNone) {
115                         regs.push_back(*i);
116                 } else {
117                         NoteTrackers::iterator t = _note_trackers.find ((*i).get());
118                         if (t != _note_trackers.end()) {
119
120                                 /* add it the set of trackers we will do note resolution
121                                    on, and remove it from the list we are keeping
122                                    around, because we don't need it anymore.
123
124                                    if the end of the region (where we want to theoretically resolve notes)
125                                    is outside the current read range, then just do it at the start
126                                    of this read range.
127                                 */
128
129                                 framepos_t resolve_at = (*i)->last_frame();
130                                 if (resolve_at < start || resolve_at >= end) {
131                                         resolve_at = start;
132                                 }
133
134                                 tracker_info.push_back (TrackerInfo (t->second, resolve_at));
135                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("time to resolve & remove tracker for %1 @ %2\n", (*i)->name(), resolve_at));
136                                 note_cnt += (t->second->on());
137                                 _note_trackers.erase (t);
138                         }
139                 }
140         }
141
142         if (note_cnt == 0 && !tracker_info.empty()) {
143                 /* trackers to dispose of, but they have no notes in them */
144                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Clearing %1 empty trackers\n", tracker_info.size()));
145                 for (vector<TrackerInfo>::iterator t = tracker_info.begin(); t != tracker_info.end(); ++t) {
146                         delete (*t).first;
147                 }
148                 tracker_info.clear ();
149         }
150
151         if (regs.size() == 1 && tracker_info.empty()) {
152
153                 /* just a single region - read directly into dst */
154
155                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Single region (%1) read, no out-of-bound region tracking info\n", regs.front()->name()));
156
157                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(regs.front());
158
159                 if (mr) {
160
161                         NoteTrackers::iterator t = _note_trackers.find (mr.get());
162                         MidiStateTracker* tracker;
163                         bool new_tracker = false;
164
165                         if (t == _note_trackers.end()) {
166                                 tracker = new MidiStateTracker;
167                                 new_tracker = true;
168                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tBEFORE: new tracker\n");
169                         } else {
170                                 tracker = t->second;
171                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tBEFORE: tracker says there are %1 on notes\n", tracker->on()));
172                         }
173
174                         mr->read_at (dst, start, dur, chan_n, _note_mode, tracker);
175                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tAFTER: tracker says there are %1 on notes\n", tracker->on()));
176
177                         if (new_tracker) {
178                                 pair<Region*,MidiStateTracker*> newpair;
179                                 newpair.first = mr.get();
180                                 newpair.second = tracker;
181                                 _note_trackers.insert (newpair);
182                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
183                         }
184
185                         _read_data_count += mr->read_data_count();
186                 }
187
188         } else {
189
190                 /* multiple regions and/or note resolution: sort by layer, read into a temporary non-monotonically
191                    sorted EventSink, sort and then insert into dst.
192                 */
193
194                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("%1 regions to read, plus %2 trackers\n", regs.size(), tracker_info.size()));
195
196                 Evoral::EventList<framepos_t> evlist;
197
198                 for (vector<TrackerInfo>::iterator t = tracker_info.begin(); t != tracker_info.end(); ++t) {
199                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Resolve %1 notes\n", (*t).first->on()));
200                         (*t).first->resolve_notes (evlist, (*t).second);
201                         delete (*t).first;
202                 }
203
204 #ifndef NDEBUG
205                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("After resolution we now have %1 events\n",  evlist.size()));
206                 for (Evoral::EventList<framepos_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
207                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
208                 }
209 #endif
210
211                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("for %1 .. %2 we have %3 to consider\n", start, start+dur-1, regs.size()));
212
213                 for (vector<boost::shared_ptr<Region> >::iterator i = regs.begin(); i != regs.end(); ++i) {
214                         boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*i);
215                         if (!mr) {
216                                 continue;
217                         }
218
219                         NoteTrackers::iterator t = _note_trackers.find (mr.get());
220                         MidiStateTracker* tracker;
221                         bool new_tracker = false;
222
223
224                         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()));
225
226                         if (t == _note_trackers.end()) {
227                                 tracker = new MidiStateTracker;
228                                 new_tracker = true;
229                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tBEFORE: new tracker\n");
230                         } else {
231                                 tracker = t->second;
232                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tBEFORE: tracker says there are %1 on notes\n", tracker->on()));
233                         }
234
235
236                         mr->read_at (evlist, start, dur, chan_n, _note_mode, tracker);
237                         _read_data_count += mr->read_data_count();
238
239 #ifndef NDEBUG
240                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("After %1 (%2 .. %3) we now have %4\n", mr->name(), mr->position(), mr->last_frame(), evlist.size()));
241                         for (Evoral::EventList<framepos_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
242                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
243                         }
244                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tAFTER: tracker says there are %1 on notes\n", tracker->on()));
245 #endif
246
247                         if (new_tracker) {
248                                 pair<Region*,MidiStateTracker*> newpair;
249                                 newpair.first = mr.get();
250                                 newpair.second = tracker;
251                                 _note_trackers.insert (newpair);
252                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
253                         }
254                 }
255
256                 if (!evlist.empty()) {
257
258                         /* sort the event list */
259                         EventsSortByTime<framepos_t> time_cmp;
260                         evlist.sort (time_cmp);
261
262 #ifndef NDEBUG
263                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Final we now have %1 events\n",  evlist.size()));
264                         for (Evoral::EventList<framepos_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
265                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
266                         }
267 #endif
268                         /* write into dst */
269                         for (Evoral::EventList<framepos_t>::iterator e = evlist.begin(); e != evlist.end(); ++e) {
270                                 Evoral::Event<framepos_t>* ev (*e);
271                                 dst.write (ev->time(), ev->event_type(), ev->size(), ev->buffer());
272                                 delete ev;
273                         }
274
275                 }
276         }
277
278         DEBUG_TRACE (DEBUG::MidiPlaylistIO, "-------------------------------------------------------------\n");
279         return dur;
280 }
281
282 void
283 MidiPlaylist::clear_note_trackers ()
284 {
285         Glib::RecMutex::Lock rm (region_lock);
286         for (NoteTrackers::iterator n = _note_trackers.begin(); n != _note_trackers.end(); ++n) {
287                 delete n->second;
288         }
289         _note_trackers.clear ();
290 }
291
292 void
293 MidiPlaylist::remove_dependents (boost::shared_ptr<Region> region)
294 {
295         /* MIDI regions have no dependents (crossfades) but we might be tracking notes */
296         NoteTrackers::iterator t = _note_trackers.find (region.get());
297
298         /* GACK! THREAD SAFETY! */
299
300         if (t != _note_trackers.end()) {
301                 delete t->second;
302                 _note_trackers.erase (t);
303         }
304 }
305
306
307 void
308 MidiPlaylist::refresh_dependents (boost::shared_ptr<Region> /*r*/)
309 {
310         /* MIDI regions have no dependents (crossfades) */
311 }
312
313 void
314 MidiPlaylist::finalize_split_region (boost::shared_ptr<Region> /*original*/, boost::shared_ptr<Region> /*left*/, boost::shared_ptr<Region> /*right*/)
315 {
316         /* No MIDI crossfading (yet?), so nothing to do here */
317 }
318
319 void
320 MidiPlaylist::check_dependents (boost::shared_ptr<Region> /*r*/, bool /*norefresh*/)
321 {
322         /* MIDI regions have no dependents (crossfades) */
323 }
324
325
326 int
327 MidiPlaylist::set_state (const XMLNode& node, int version)
328 {
329         in_set_state++;
330         freeze ();
331
332         if (Playlist::set_state (node, version)) {
333                 return -1;
334         }
335
336         thaw();
337         in_set_state--;
338
339         return 0;
340 }
341
342 void
343 MidiPlaylist::dump () const
344 {
345         boost::shared_ptr<Region> r;
346
347         cerr << "Playlist \"" << _name << "\" " << endl
348         << regions.size() << " regions "
349         << endl;
350
351         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
352                 r = *i;
353                 cerr << "  " << r->name() << " @ " << r << " ["
354                 << r->start() << "+" << r->length()
355                 << "] at "
356                 << r->position()
357                 << " on layer "
358                 << r->layer ()
359                 << endl;
360         }
361 }
362
363 bool
364 MidiPlaylist::destroy_region (boost::shared_ptr<Region> region)
365 {
366         boost::shared_ptr<MidiRegion> r = boost::dynamic_pointer_cast<MidiRegion> (region);
367
368         if (!r) {
369                 return false;
370         }
371
372         bool changed = false;
373
374         {
375                 RegionLock rlock (this);
376                 RegionList::iterator i;
377                 RegionList::iterator tmp;
378
379                 for (i = regions.begin(); i != regions.end(); ) {
380
381                         tmp = i;
382                         ++tmp;
383
384                         if ((*i) == region) {
385                                 regions.erase (i);
386                                 changed = true;
387                         }
388
389                         i = tmp;
390                 }
391         }
392
393
394         if (changed) {
395                 /* overload this, it normally means "removed", not destroyed */
396                 notify_region_removed (region);
397         }
398
399         return changed;
400 }
401
402 set<Evoral::Parameter>
403 MidiPlaylist::contained_automation()
404 {
405         /* this function is never called from a realtime thread, so
406            its OK to block (for short intervals).
407         */
408
409         Glib::RecMutex::Lock rm (region_lock);
410
411         set<Evoral::Parameter> ret;
412
413         for (RegionList::const_iterator r = regions.begin(); r != regions.end(); ++r) {
414                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*r);
415
416                 for (Automatable::Controls::iterator c = mr->model()->controls().begin();
417                                 c != mr->model()->controls().end(); ++c) {
418                         ret.insert(c->first);
419                 }
420         }
421
422         return ret;
423 }
424
425
426 bool
427 MidiPlaylist::region_changed (const PBD::PropertyChange& what_changed, boost::shared_ptr<Region> region)
428 {
429         if (in_flush || in_set_state) {
430                 return false;
431         }
432
433         PBD::PropertyChange our_interests;
434         our_interests.add (Properties::midi_data);
435
436         bool parent_wants_notify = Playlist::region_changed (what_changed, region);
437
438         if (parent_wants_notify || what_changed.contains (our_interests)) {
439                 notify_contents_changed ();
440         }
441
442         return true;
443 }
444