debug flag for session destruction and waf option for boost SP debug
[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 #include <sigc++/bind.h>
27
28 #include "evoral/EventList.hpp"
29
30 #include "ardour/debug.h"
31 #include "ardour/types.h"
32 #include "ardour/configuration.h"
33 #include "ardour/midi_playlist.h"
34 #include "ardour/midi_region.h"
35 #include "ardour/session.h"
36 #include "ardour/midi_ring_buffer.h"
37
38 #include "pbd/error.h"
39
40 #include "i18n.h"
41
42 using namespace ARDOUR;
43 using namespace sigc;
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         const XMLProperty* prop = node.property("type");
51         assert(prop && DataType(prop->value()) == DataType::MIDI);
52
53         in_set_state++;
54         set_state (node, Stateful::loading_state_version);
55         in_set_state--;
56 }
57
58 MidiPlaylist::MidiPlaylist (Session& session, string name, bool hidden)
59         : Playlist (session, name, DataType::MIDI, hidden)
60 {
61 }
62
63 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, string name, bool hidden)
64         : Playlist (other, name, hidden)
65 {
66 }
67
68 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, nframes_t start, nframes_t dur, string name, bool hidden)
69                 : Playlist (other, start, dur, name, hidden)
70 {
71         /* this constructor does NOT notify others (session) */
72 }
73
74 MidiPlaylist::~MidiPlaylist ()
75 {
76         GoingAway (); /* EMIT SIGNAL */
77
78         /* drop connections to signals */
79
80         notify_callbacks ();
81 }
82
83 template<typename Time>
84 struct EventsSortByTime {
85     bool operator() (Evoral::Event<Time>* a, Evoral::Event<Time>* b) {
86             return a->time() < b->time();
87     }
88 };
89
90 /** Returns the number of frames in time duration read (eg could be large when 0 events are read) */
91 nframes_t
92 MidiPlaylist::read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_t dur, unsigned chan_n)
93 {
94         /* this function is never called from a realtime thread, so
95            its OK to block (for short intervals).
96         */
97
98         Glib::RecMutex::Lock rm (region_lock);
99         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("++++++ %1 .. %2  +++++++++++++++++++++++++++++++++++++++++++++++\n", start, start + dur));
100
101         nframes_t end = start + dur - 1;
102
103         _read_data_count = 0;
104
105         // relevent regions overlapping start <--> end
106         vector< boost::shared_ptr<Region> > regs;
107         typedef pair<MidiStateTracker*,nframes64_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                                 nframes64_t resolve_at = (*i)->last_frame();
128                                 if (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", 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", 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                         _read_data_count += mr->read_data_count();
184                 }
185
186         } else {
187
188                 /* multiple regions and/or note resolution: sort by layer, read into a temporary non-monotonically
189                    sorted EventSink, sort and then insert into dst.
190                 */
191
192                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("%1 regions to read, plus %2 trackers\n", regs.size(), tracker_info.size()));
193
194                 Evoral::EventList<nframes_t> evlist;
195
196                 for (vector<TrackerInfo>::iterator t = tracker_info.begin(); t != tracker_info.end(); ++t) {
197                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Resolve %1 notes\n", (*t).first->on()));
198                         (*t).first->resolve_notes (evlist, (*t).second);
199                         delete (*t).first;
200                 }
201
202 #ifndef NDEBUG
203                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("After resolution we now have %1 events\n",  evlist.size()));
204                 for (Evoral::EventList<nframes_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
205                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
206                 }
207 #endif
208
209                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("for %1 .. %2 we have %3 to consider\n", start, start+dur-1, regs.size()));
210
211                 for (vector<boost::shared_ptr<Region> >::iterator i = regs.begin(); i != regs.end(); ++i) {
212                         boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*i);
213                         if (!mr) {
214                                 continue;
215                         }
216
217                         NoteTrackers::iterator t = _note_trackers.find (mr.get());
218                         MidiStateTracker* tracker;
219                         bool new_tracker = false;
220
221
222                         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()));
223
224                         if (t == _note_trackers.end()) {
225                                 tracker = new MidiStateTracker;
226                                 new_tracker = true;
227                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tBEFORE: new tracker\n");
228                         } else {
229                                 tracker = t->second;
230                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tBEFORE: tracker says there are %1 on notes\n", tracker->on()));
231                         }
232
233
234                         mr->read_at (evlist, start, dur, chan_n, _note_mode, tracker);
235                         _read_data_count += mr->read_data_count();
236
237 #ifndef NDEBUG
238                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("After %1 (%2 .. %3) we now have %4\n", mr->name(), mr->position(), mr->last_frame(), evlist.size()));
239                         for (Evoral::EventList<nframes_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
240                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
241                         }
242                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tAFTER: tracker says there are %1 on notes\n", tracker->on()));
243 #endif
244
245                         if (new_tracker) {
246                                 pair<Region*,MidiStateTracker*> newpair;
247                                 newpair.first = mr.get();
248                                 newpair.second = tracker;
249                                 _note_trackers.insert (newpair);
250                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
251                         }
252                 }
253
254                 if (!evlist.empty()) {
255
256                         /* sort the event list */
257                         EventsSortByTime<nframes_t> time_cmp;
258                         evlist.sort (time_cmp);
259
260 #ifndef NDEBUG
261                         DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Final we now have %1 events\n",  evlist.size()));
262                         for (Evoral::EventList<nframes_t>::iterator x = evlist.begin(); x != evlist.end(); ++x) {
263                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1\n", **x));
264                         }
265 #endif
266                         /* write into dst */
267                         for (Evoral::EventList<nframes_t>::iterator e = evlist.begin(); e != evlist.end(); ++e) {
268                                 Evoral::Event<nframes_t>* ev (*e);
269                                 dst.write (ev->time(), ev->event_type(), ev->size(), ev->buffer());
270                                 delete ev;
271                         }
272
273                 }
274         }
275
276         DEBUG_TRACE (DEBUG::MidiPlaylistIO, "-------------------------------------------------------------\n");
277         return dur;
278 }
279
280 void
281 MidiPlaylist::clear_note_trackers ()
282 {
283         Glib::RecMutex::Lock rm (region_lock);
284         for (NoteTrackers::iterator n = _note_trackers.begin(); n != _note_trackers.end(); ++n) {
285                 delete n->second;
286         }
287         _note_trackers.clear ();
288 }
289
290 void
291 MidiPlaylist::remove_dependents (boost::shared_ptr<Region> region)
292 {
293         /* MIDI regions have no dependents (crossfades) but we might be tracking notes */
294         NoteTrackers::iterator t = _note_trackers.find (region.get());
295
296         /* GACK! THREAD SAFETY! */
297
298         if (t != _note_trackers.end()) {
299                 delete t->second;
300                 _note_trackers.erase (t);
301         }
302 }
303
304
305 void
306 MidiPlaylist::refresh_dependents (boost::shared_ptr<Region> /*r*/)
307 {
308         /* MIDI regions have no dependents (crossfades) */
309 }
310
311 void
312 MidiPlaylist::finalize_split_region (boost::shared_ptr<Region> /*original*/, boost::shared_ptr<Region> /*left*/, boost::shared_ptr<Region> /*right*/)
313 {
314         /* No MIDI crossfading (yet?), so nothing to do here */
315 }
316
317 void
318 MidiPlaylist::check_dependents (boost::shared_ptr<Region> /*r*/, bool /*norefresh*/)
319 {
320         /* MIDI regions have no dependents (crossfades) */
321 }
322
323
324 int
325 MidiPlaylist::set_state (const XMLNode& node, int version)
326 {
327         in_set_state++;
328         freeze ();
329
330         Playlist::set_state (node, version);
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         bool changed = false;
364
365         if (r == 0) {
366                 PBD::fatal << _("programming error: non-midi Region passed to remove_overlap in midi playlist")
367                 << endmsg;
368                 /*NOTREACHED*/
369                 return false;
370         }
371
372         {
373                 RegionLock rlock (this);
374                 RegionList::iterator i;
375                 RegionList::iterator tmp;
376
377                 for (i = regions.begin(); i != regions.end(); ) {
378
379                         tmp = i;
380                         ++tmp;
381
382                         if ((*i) == region) {
383                                 regions.erase (i);
384                                 changed = true;
385                         }
386
387                         i = tmp;
388                 }
389         }
390
391
392         if (changed) {
393                 /* overload this, it normally means "removed", not destroyed */
394                 notify_region_removed (region);
395         }
396
397         return changed;
398 }
399
400 set<Evoral::Parameter>
401 MidiPlaylist::contained_automation()
402 {
403         /* this function is never called from a realtime thread, so
404            its OK to block (for short intervals).
405         */
406
407         Glib::RecMutex::Lock rm (region_lock);
408
409         set<Evoral::Parameter> ret;
410
411         for (RegionList::const_iterator r = regions.begin(); r != regions.end(); ++r) {
412                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*r);
413
414                 for (Automatable::Controls::iterator c = mr->model()->controls().begin();
415                                 c != mr->model()->controls().end(); ++c) {
416                         ret.insert(c->first);
417                 }
418         }
419
420         return ret;
421 }
422
423
424 bool
425 MidiPlaylist::region_changed (Change what_changed, boost::shared_ptr<Region> region)
426 {
427         if (in_flush || in_set_state) {
428                 return false;
429         }
430
431         // Feeling rather uninterested today, but thanks for the heads up anyway!
432
433         Change our_interests = Change (/*MidiRegion::FadeInChanged|
434                                        MidiRegion::FadeOutChanged|
435                                        MidiRegion::FadeInActiveChanged|
436                                        MidiRegion::FadeOutActiveChanged|
437                                        MidiRegion::EnvelopeActiveChanged|
438                                        MidiRegion::ScaleAmplitudeChanged|
439                                        MidiRegion::EnvelopeChanged*/);
440         bool parent_wants_notify;
441
442         parent_wants_notify = Playlist::region_changed (what_changed, region);
443
444         if ((parent_wants_notify || (what_changed & our_interests))) {
445                 notify_modified ();
446         }
447
448         return true;
449 }
450