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