Remove all use of nframes_t.
[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 #ifndef NDEBUG
50         const XMLProperty* prop = node.property("type");
51         assert(prop && DataType(prop->value()) == DataType::MIDI);
52 #endif
53
54         in_set_state++;
55         set_state (node, Stateful::loading_state_version);
56         in_set_state--;
57 }
58
59 MidiPlaylist::MidiPlaylist (Session& session, string name, bool hidden)
60         : Playlist (session, name, DataType::MIDI, hidden)
61         , _note_mode(Sustained)
62 {
63 }
64
65 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, string name, bool hidden)
66         : Playlist (other, name, hidden)
67         , _note_mode(other->_note_mode)
68 {
69 }
70
71 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, framepos_t start, framecnt_t dur, string name, bool hidden)
72         : Playlist (other, start, dur, name, hidden)
73         , _note_mode(other->_note_mode)
74 {
75         /* this constructor does NOT notify others (session) */
76 }
77
78 MidiPlaylist::~MidiPlaylist ()
79 {
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 framecnt_t
91 MidiPlaylist::read (MidiRingBuffer<framepos_t>& dst, framepos_t start, framecnt_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         framepos_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*,framepos_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                                 framepos_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\n", 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\n", 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<framepos_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<framepos_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<framepos_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<framepos_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<framepos_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<framepos_t>::iterator e = evlist.begin(); e != evlist.end(); ++e) {
267                                 Evoral::Event<framepos_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
363         if (!r) {
364                 return false;
365         }
366
367         bool changed = false;
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         PBD::PropertyChange our_interests;
429         our_interests.add (Properties::midi_data);
430
431         bool parent_wants_notify = Playlist::region_changed (what_changed, region);
432
433         if (parent_wants_notify || what_changed.contains (our_interests)) {
434                 notify_contents_changed ();
435         }
436
437         return true;
438 }
439