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