Clean up MidiPlaylist::read, kill copy-paste code.
[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 #include <utility>
25
26 #include <stdlib.h>
27
28 #include "evoral/EventList.hpp"
29
30 #include "ardour/debug.h"
31 #include "ardour/midi_model.h"
32 #include "ardour/midi_playlist.h"
33 #include "ardour/midi_region.h"
34 #include "ardour/midi_state_tracker.h"
35 #include "ardour/types.h"
36
37 #include "i18n.h"
38
39 using namespace ARDOUR;
40 using namespace PBD;
41 using namespace std;
42
43 MidiPlaylist::MidiPlaylist (Session& session, const XMLNode& node, bool hidden)
44         : Playlist (session, node, DataType::MIDI, hidden)
45         , _note_mode(Sustained)
46 {
47 #ifndef NDEBUG
48         const XMLProperty* prop = node.property("type");
49         assert(prop && DataType(prop->value()) == DataType::MIDI);
50 #endif
51
52         in_set_state++;
53         if (set_state (node, Stateful::loading_state_version)) {
54                 throw failed_constructor ();
55         }
56         in_set_state--;
57
58         relayer ();
59 }
60
61 MidiPlaylist::MidiPlaylist (Session& session, string name, bool hidden)
62         : Playlist (session, name, DataType::MIDI, hidden)
63         , _note_mode(Sustained)
64 {
65 }
66
67 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, string name, bool hidden)
68         : Playlist (other, name, hidden)
69         , _note_mode(other->_note_mode)
70 {
71 }
72
73 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other,
74                             framepos_t                            start,
75                             framecnt_t                            dur,
76                             string                                name,
77                             bool                                  hidden)
78         : Playlist (other, start, dur, name, hidden)
79         , _note_mode(other->_note_mode)
80 {
81 }
82
83 MidiPlaylist::~MidiPlaylist ()
84 {
85 }
86
87 template<typename Time>
88 struct EventsSortByTimeAndType {
89     bool operator() (Evoral::Event<Time>* a, Evoral::Event<Time>* b) {
90             if (a->time() == b->time()) {
91                     if (parameter_is_midi ((AutomationType)a->event_type()) &&
92                         parameter_is_midi ((AutomationType)b->event_type())) {
93                             /* negate return value since we must return whether
94                              * or not a should sort before b, not b before a
95                              */
96                             return !MidiBuffer::second_simultaneous_midi_byte_is_first (a->buffer()[0], b->buffer()[0]);
97                     }
98             }
99             return a->time() < b->time();
100     }
101 };
102
103 framecnt_t
104 MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst, framepos_t start, framecnt_t dur, unsigned chan_n)
105 {
106         Playlist::RegionReadLock rl (this);
107
108         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
109                      string_compose ("---- MidiPlaylist::read %1 .. %2 (%3 trackers) ----\n",
110                                      start, start + dur, _note_trackers.size()));
111
112         typedef pair<MidiStateTracker*,framepos_t> TrackerInfo;
113
114         /* Find relevant regions that overlap [start..end] */
115         const framepos_t                         end = start + dur - 1;
116         std::vector< boost::shared_ptr<Region> > regs;
117         std::vector< boost::shared_ptr<Region> > ended;
118         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
119                 switch ((*i)->coverage (start, end)) {
120                 case Evoral::OverlapStart:
121                 case Evoral::OverlapInternal:
122                         regs.push_back (*i);
123                         break;
124
125                 case Evoral::OverlapExternal:
126                         /* this region is entirely contained in the read range */
127                         regs.push_back (*i);
128                         ended.push_back (*i);
129                         break;
130
131                 case Evoral::OverlapEnd:
132                         /* this region ends within the read range */
133                         regs.push_back (*i);
134                         ended.push_back (*i);
135                         break;
136
137                 default:
138                         /* we don't care */
139                         break;
140                 }
141         }
142
143         /* If we are reading from a single region, we can read directly into dst.  Otherwise,
144            we read into a temporarily list, sort it, then write that to dst. */
145         const bool direct_read = regs.size() == 1 &&
146                 (ended.empty() || (ended.size() == 1 && ended.front() == regs.front()));
147
148         Evoral::EventList<framepos_t>  evlist;
149         Evoral::EventSink<framepos_t>& tgt = direct_read ? dst : evlist;
150
151         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
152                      string_compose ("\t%1 regions to read, direct: %2\n", regs.size(), direct_read));
153
154         for (vector<boost::shared_ptr<Region> >::iterator i = regs.begin(); i != regs.end(); ++i) {
155                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*i);
156                 if (!mr) {
157                         continue;
158                 }
159
160                 /* Get the existing note tracker for this region, or create a new one. */
161                 NoteTrackers::iterator t           = _note_trackers.find (mr.get());
162                 MidiStateTracker*      tracker     = NULL;
163                 bool                   new_tracker = false;
164                 if (t == _note_trackers.end()) {
165                         tracker = new MidiStateTracker;
166                         new_tracker = true;
167                         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
168                                      string_compose ("\tPre-read %1 (%2 .. %3): new tracker\n",
169                                                      mr->name(), mr->position(), mr->last_frame()));
170                 } else {
171                         tracker = t->second;
172                         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
173                                      string_compose ("\tPre-read %1 (%2 .. %3): %4 active notes\n",
174                                                      mr->name(), mr->position(), mr->last_frame(), tracker->on()));
175                 }
176
177                 /** Read from region into target. */
178                 mr->read_at (tgt, start, dur, chan_n, _note_mode, tracker);
179                 DEBUG_TRACE (DEBUG::MidiPlaylistIO,
180                              string_compose ("\tPost-read: %1 active notes\n", tracker->on()));
181
182                 if (find (ended.begin(), ended.end(), *i) != ended.end()) {
183                         /* Region ended within the read range, so resolve any active notes
184                            (either stuck notes in the data, or notes that end after the end
185                            of the region). */
186                         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
187                                      string_compose ("\t%1 ended, resolve notes and delete (%2) tracker\n",
188                                                      mr->name(), ((new_tracker) ? "new" : "old")));
189
190                         tracker->resolve_notes (tgt, (*i)->last_frame());
191                         delete tracker;
192                         if (!new_tracker) {
193                                 _note_trackers.erase (t);
194                         }
195
196                 } else {
197
198                         if (new_tracker) {
199                                 _note_trackers.insert (make_pair (mr.get(), tracker));
200                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
201                         }
202                 }
203         }
204
205         if (!direct_read && !evlist.empty()) {
206                 /* We've read from multiple regions, sort the event list by time. */
207                 EventsSortByTimeAndType<framepos_t> cmp;
208                 evlist.sort (cmp);
209
210                 /* Copy ordered events from event list to dst. */
211                 for (Evoral::EventList<framepos_t>::iterator e = evlist.begin(); e != evlist.end(); ++e) {
212                         Evoral::Event<framepos_t>* ev (*e);
213                         dst.write (ev->time(), ev->event_type(), ev->size(), ev->buffer());
214                         delete ev;
215                 }
216         }
217
218         DEBUG_TRACE (DEBUG::MidiPlaylistIO, "---- End MidiPlaylist::read ----\n");
219         return dur;
220 }
221
222 void
223 MidiPlaylist::reset_note_trackers ()
224 {
225         Playlist::RegionWriteLock rl (this, false);
226
227         for (NoteTrackers::iterator n = _note_trackers.begin(); n != _note_trackers.end(); ++n) {
228                 delete n->second;
229         }
230         DEBUG_TRACE (DEBUG::MidiTrackers, string_compose ("%1 reset all note trackers\n", name()));
231         _note_trackers.clear ();
232 }
233
234 void
235 MidiPlaylist::resolve_note_trackers (Evoral::EventSink<framepos_t>& dst, framepos_t time)
236 {
237         Playlist::RegionWriteLock rl (this, false);
238
239         for (NoteTrackers::iterator n = _note_trackers.begin(); n != _note_trackers.end(); ++n) {
240                 n->second->resolve_notes(dst, time);
241                 delete n->second;
242         }
243         DEBUG_TRACE (DEBUG::MidiTrackers, string_compose ("%1 resolve all note trackers\n", name()));
244         _note_trackers.clear ();
245 }
246
247 void
248 MidiPlaylist::remove_dependents (boost::shared_ptr<Region> region)
249 {
250         /* MIDI regions have no dependents (crossfades) but we might be tracking notes */
251         NoteTrackers::iterator t = _note_trackers.find (region.get());
252
253         /* GACK! THREAD SAFETY! */
254
255         if (t != _note_trackers.end()) {
256                 delete t->second;
257                 _note_trackers.erase (t);
258         }
259 }
260
261 int
262 MidiPlaylist::set_state (const XMLNode& node, int version)
263 {
264         in_set_state++;
265         freeze ();
266
267         if (Playlist::set_state (node, version)) {
268                 return -1;
269         }
270
271         thaw();
272         in_set_state--;
273
274         return 0;
275 }
276
277 void
278 MidiPlaylist::dump () const
279 {
280         boost::shared_ptr<Region> r;
281
282         cerr << "Playlist \"" << _name << "\" " << endl
283         << regions.size() << " regions "
284         << endl;
285
286         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
287                 r = *i;
288                 cerr << "  " << r->name() << " @ " << r << " ["
289                 << r->start() << "+" << r->length()
290                 << "] at "
291                 << r->position()
292                 << " on layer "
293                 << r->layer ()
294                 << endl;
295         }
296 }
297
298 bool
299 MidiPlaylist::destroy_region (boost::shared_ptr<Region> region)
300 {
301         boost::shared_ptr<MidiRegion> r = boost::dynamic_pointer_cast<MidiRegion> (region);
302
303         if (!r) {
304                 return false;
305         }
306
307         bool changed = false;
308
309         {
310                 RegionWriteLock rlock (this);
311                 RegionList::iterator i;
312                 RegionList::iterator tmp;
313
314                 for (i = regions.begin(); i != regions.end(); ) {
315
316                         tmp = i;
317                         ++tmp;
318
319                         if ((*i) == region) {
320                                 regions.erase (i);
321                                 changed = true;
322                         }
323
324                         i = tmp;
325                 }
326         }
327
328
329         if (changed) {
330                 /* overload this, it normally means "removed", not destroyed */
331                 notify_region_removed (region);
332         }
333
334         return changed;
335 }
336
337 set<Evoral::Parameter>
338 MidiPlaylist::contained_automation()
339 {
340         /* this function is never called from a realtime thread, so
341            its OK to block (for short intervals).
342         */
343
344         Playlist::RegionReadLock rl (this);
345         set<Evoral::Parameter> ret;
346
347         for (RegionList::const_iterator r = regions.begin(); r != regions.end(); ++r) {
348                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*r);
349
350                 for (Automatable::Controls::iterator c = mr->model()->controls().begin();
351                                 c != mr->model()->controls().end(); ++c) {
352                         ret.insert(c->first);
353                 }
354         }
355
356         return ret;
357 }
358
359
360 bool
361 MidiPlaylist::region_changed (const PBD::PropertyChange& what_changed, boost::shared_ptr<Region> region)
362 {
363         if (in_flush || in_set_state) {
364                 return false;
365         }
366
367         PBD::PropertyChange our_interests;
368         our_interests.add (Properties::midi_data);
369
370         bool parent_wants_notify = Playlist::region_changed (what_changed, region);
371
372         if (parent_wants_notify || what_changed.contains (our_interests)) {
373                 notify_contents_changed ();
374         }
375
376         return true;
377 }
378