Fix some compiler warnings (exception catches)
[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 <algorithm>
21 #include <cassert>
22 #include <cstdlib>
23 #include <iostream>
24 #include <utility>
25
26 #include "evoral/EventList.hpp"
27 #include "evoral/Control.hpp"
28
29 #include "ardour/beats_samples_converter.h"
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_source.h"
35 #include "ardour/midi_state_tracker.h"
36 #include "ardour/region_factory.h"
37 #include "ardour/session.h"
38 #include "ardour/tempo.h"
39 #include "ardour/types.h"
40
41 #include "pbd/i18n.h"
42
43 using namespace ARDOUR;
44 using namespace PBD;
45 using namespace std;
46
47 MidiPlaylist::MidiPlaylist (Session& session, const XMLNode& node, bool hidden)
48         : Playlist (session, node, DataType::MIDI, hidden)
49         , _note_mode(Sustained)
50         , _read_end(0)
51 {
52 #ifndef NDEBUG
53         XMLProperty const * prop = node.property("type");
54         assert(prop && DataType(prop->value()) == DataType::MIDI);
55 #endif
56
57         in_set_state++;
58         if (set_state (node, Stateful::loading_state_version)) {
59                 throw failed_constructor ();
60         }
61         in_set_state--;
62
63         relayer ();
64 }
65
66 MidiPlaylist::MidiPlaylist (Session& session, string name, bool hidden)
67         : Playlist (session, name, DataType::MIDI, hidden)
68         , _note_mode(Sustained)
69         , _read_end(0)
70 {
71 }
72
73 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, string name, bool hidden)
74         : Playlist (other, name, hidden)
75         , _note_mode(other->_note_mode)
76         , _read_end(0)
77 {
78 }
79
80 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other,
81                             samplepos_t                            start,
82                             samplecnt_t                            dur,
83                             string                                name,
84                             bool                                  hidden)
85         : Playlist (other, start, dur, name, hidden)
86         , _note_mode(other->_note_mode)
87         , _read_end(0)
88 {
89 }
90
91 MidiPlaylist::~MidiPlaylist ()
92 {
93 }
94
95 template<typename Time>
96 struct EventsSortByTimeAndType {
97     bool operator() (const Evoral::Event<Time>* a, const Evoral::Event<Time>* b) {
98             if (a->time() == b->time()) {
99                     if (parameter_is_midi ((AutomationType)a->event_type()) &&
100                         parameter_is_midi ((AutomationType)b->event_type())) {
101                             /* negate return value since we must return whether
102                              * or not a should sort before b, not b before a
103                              */
104                             return !MidiBuffer::second_simultaneous_midi_byte_is_first (a->buffer()[0], b->buffer()[0]);
105                     }
106             }
107             return a->time() < b->time();
108     }
109 };
110
111 samplecnt_t
112 MidiPlaylist::read (Evoral::EventSink<samplepos_t>& dst,
113                     samplepos_t                     start,
114                     samplecnt_t                     dur,
115                     Evoral::Range<samplepos_t>*     loop_range,
116                     unsigned                       chan_n,
117                     MidiChannelFilter*             filter)
118 {
119         typedef pair<MidiStateTracker*,samplepos_t> TrackerInfo;
120
121         Playlist::RegionReadLock rl (this);
122
123         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
124                      string_compose ("---- MidiPlaylist::read %1 .. %2 (%3 trackers) ----\n",
125                                      start, start + dur, _note_trackers.size()));
126
127         /* First, emit any queued edit fixup events at start. */
128         for (NoteTrackers::iterator t = _note_trackers.begin(); t != _note_trackers.end(); ++t) {
129                 t->second->fixer.emit(dst, _read_end, t->second->tracker);
130         }
131
132         /* Find relevant regions that overlap [start..end] */
133         const samplepos_t                         end = start + dur - 1;
134         std::vector< boost::shared_ptr<Region> > regs;
135         std::vector< boost::shared_ptr<Region> > ended;
136         for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
137
138                 /* check for the case of solo_selection */
139                 bool force_transparent = ( _session.solo_selection_active() && SoloSelectedActive() && !SoloSelectedListIncludes( (const Region*) &(**i) ) );
140                 if ( force_transparent )
141                         continue;
142
143                 switch ((*i)->coverage (start, end)) {
144                 case Evoral::OverlapStart:
145                 case Evoral::OverlapInternal:
146                         regs.push_back (*i);
147                         break;
148
149                 case Evoral::OverlapExternal:
150                         /* this region is entirely contained in the read range */
151                         regs.push_back (*i);
152                         ended.push_back (*i);
153                         break;
154
155                 case Evoral::OverlapEnd:
156                         /* this region ends within the read range */
157                         regs.push_back (*i);
158                         ended.push_back (*i);
159                         break;
160
161                 default:
162                         /* we don't care */
163                         break;
164                 }
165         }
166
167         /* If we are reading from a single region, we can read directly into dst.  Otherwise,
168            we read into a temporarily list, sort it, then write that to dst. */
169         const bool direct_read = regs.size() == 1 &&
170                 (ended.empty() || (ended.size() == 1 && ended.front() == regs.front()));
171
172         Evoral::EventList<samplepos_t>  evlist;
173         Evoral::EventSink<samplepos_t>& tgt = direct_read ? dst : evlist;
174
175         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
176                      string_compose ("\t%1 regions to read, direct: %2\n", regs.size(), direct_read));
177
178         for (vector<boost::shared_ptr<Region> >::iterator i = regs.begin(); i != regs.end(); ++i) {
179                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*i);
180                 if (!mr) {
181                         continue;
182                 }
183
184                 /* Get the existing note tracker for this region, or create a new one. */
185                 NoteTrackers::iterator           t           = _note_trackers.find (mr.get());
186                 bool                             new_tracker = false;
187                 boost::shared_ptr<RegionTracker> tracker;
188                 if (t == _note_trackers.end()) {
189                         tracker     = boost::shared_ptr<RegionTracker>(new RegionTracker);
190                         new_tracker = true;
191                         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
192                                      string_compose ("\tPre-read %1 (%2 .. %3): new tracker\n",
193                                                      mr->name(), mr->position(), mr->last_sample()));
194                 } else {
195                         tracker = t->second;
196                         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
197                                      string_compose ("\tPre-read %1 (%2 .. %3): %4 active notes\n",
198                                                      mr->name(), mr->position(), mr->last_sample(), tracker->tracker.on()));
199                 }
200
201                 /* Read from region into target. */
202                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("read from %1 at %2 for %3 LR %4 .. %5\n",
203                                                                     mr->name(), start, dur, 
204                                                                     (loop_range ? loop_range->from : -1),
205                                                                     (loop_range ? loop_range->to : -1)));
206                 mr->read_at (tgt, start, dur, loop_range, tracker->cursor, chan_n, _note_mode, &tracker->tracker, filter);
207                 DEBUG_TRACE (DEBUG::MidiPlaylistIO,
208                              string_compose ("\tPost-read: %1 active notes\n", tracker->tracker.on()));
209
210                 if (find (ended.begin(), ended.end(), *i) != ended.end()) {
211                         /* Region ended within the read range, so resolve any active notes
212                            (either stuck notes in the data, or notes that end after the end
213                            of the region). */
214                         DEBUG_TRACE (DEBUG::MidiPlaylistIO,
215                                      string_compose ("\t%1 ended, resolve notes and delete (%2) tracker\n",
216                                                      mr->name(), ((new_tracker) ? "new" : "old")));
217
218                         tracker->tracker.resolve_notes (tgt, loop_range ? loop_range->squish ((*i)->last_sample()) : (*i)->last_sample());
219                         tracker->cursor.invalidate (false);
220                         if (!new_tracker) {
221                                 _note_trackers.erase (t);
222                         }
223
224                 } else {
225
226                         if (new_tracker) {
227                                 _note_trackers.insert (make_pair (mr.get(), tracker));
228                                 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "\tadded tracker to trackers\n");
229                         }
230                 }
231         }
232
233         if (!direct_read && !evlist.empty()) {
234                 /* We've read from multiple regions, sort the event list by time. */
235                 EventsSortByTimeAndType<samplepos_t> cmp;
236                 evlist.sort (cmp);
237
238                 /* Copy ordered events from event list to dst. */
239                 for (Evoral::EventList<samplepos_t>::iterator e = evlist.begin(); e != evlist.end(); ++e) {
240                         Evoral::Event<samplepos_t>* ev (*e);
241                         dst.write (ev->time(), ev->event_type(), ev->size(), ev->buffer());
242                         delete ev;
243                 }
244         }
245
246         DEBUG_TRACE (DEBUG::MidiPlaylistIO, "---- End MidiPlaylist::read ----\n");
247         _read_end = start + dur;
248         return dur;
249 }
250
251 void
252 MidiPlaylist::region_edited(boost::shared_ptr<Region>         region,
253                             const MidiModel::NoteDiffCommand* cmd)
254 {
255         typedef MidiModel::NoteDiffCommand Command;
256
257         boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(region);
258         if (!mr || !_session.transport_rolling()) {
259                 return;
260         }
261
262         /* Take write lock to prevent concurrency with read(). */
263         Playlist::RegionWriteLock lock(this);
264
265         NoteTrackers::iterator t = _note_trackers.find(mr.get());
266         if (t == _note_trackers.end()) {
267                 return; /* Region is not currently active, nothing to do. */
268         }
269
270         /* Queue any necessary edit compensation events. */
271         t->second->fixer.prepare(
272                 _session.tempo_map(), cmd, mr->position() - mr->start(),
273                 _read_end, t->second->cursor.active_notes);
274 }
275
276 void
277 MidiPlaylist::reset_note_trackers ()
278 {
279         Playlist::RegionWriteLock rl (this, false);
280
281         DEBUG_TRACE (DEBUG::MidiTrackers, string_compose ("%1 reset all note trackers\n", name()));
282         _note_trackers.clear ();
283 }
284
285 void
286 MidiPlaylist::resolve_note_trackers (Evoral::EventSink<samplepos_t>& dst, samplepos_t time)
287 {
288         Playlist::RegionWriteLock rl (this, false);
289
290         for (NoteTrackers::iterator n = _note_trackers.begin(); n != _note_trackers.end(); ++n) {
291                 n->second->tracker.resolve_notes(dst, time);
292         }
293         DEBUG_TRACE (DEBUG::MidiTrackers, string_compose ("%1 resolve all note trackers\n", name()));
294         _note_trackers.clear ();
295 }
296
297 void
298 MidiPlaylist::remove_dependents (boost::shared_ptr<Region> region)
299 {
300         /* MIDI regions have no dependents (crossfades) but we might be tracking notes */
301         _note_trackers.erase(region.get());
302 }
303
304 void
305 MidiPlaylist::region_going_away (boost::weak_ptr<Region> region)
306 {
307         boost::shared_ptr<Region> r = region.lock();
308         if (r) {
309                 remove_dependents(r);
310         }
311 }
312
313 int
314 MidiPlaylist::set_state (const XMLNode& node, int version)
315 {
316         in_set_state++;
317         freeze ();
318
319         if (Playlist::set_state (node, version)) {
320                 return -1;
321         }
322
323         thaw();
324         in_set_state--;
325
326         return 0;
327 }
328
329 void
330 MidiPlaylist::dump () const
331 {
332         boost::shared_ptr<Region> r;
333
334         cerr << "Playlist \"" << _name << "\" " << endl
335         << regions.size() << " regions "
336         << endl;
337
338         for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
339                 r = *i;
340                 cerr << "  " << r->name() << " @ " << r << " ["
341                 << r->start() << "+" << r->length()
342                 << "] at "
343                 << r->position()
344                 << " on layer "
345                 << r->layer ()
346                 << endl;
347         }
348 }
349
350 bool
351 MidiPlaylist::destroy_region (boost::shared_ptr<Region> region)
352 {
353         boost::shared_ptr<MidiRegion> r = boost::dynamic_pointer_cast<MidiRegion> (region);
354
355         if (!r) {
356                 return false;
357         }
358
359         bool changed = false;
360
361         {
362                 RegionWriteLock rlock (this);
363                 RegionList::iterator i;
364                 RegionList::iterator tmp;
365
366                 for (i = regions.begin(); i != regions.end(); ) {
367
368                         tmp = i;
369                         ++tmp;
370
371                         if ((*i) == region) {
372                                 regions.erase (i);
373                                 changed = true;
374                         }
375
376                         i = tmp;
377                 }
378
379                 NoteTrackers::iterator t = _note_trackers.find(region.get());
380                 if (t != _note_trackers.end()) {
381                         _note_trackers.erase(t);
382                 }
383         }
384
385         if (changed) {
386                 /* overload this, it normally means "removed", not destroyed */
387                 notify_region_removed (region);
388         }
389
390         return changed;
391 }
392 void
393 MidiPlaylist::_split_region (boost::shared_ptr<Region> region, const MusicSample& playlist_position)
394 {
395         if (!region->covers (playlist_position.sample)) {
396                 return;
397         }
398
399         if (region->position() == playlist_position.sample ||
400             region->last_sample() == playlist_position.sample) {
401                 return;
402         }
403
404         boost::shared_ptr<const MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(region);
405
406         if (mr == 0) {
407                 return;
408         }
409
410         boost::shared_ptr<Region> left;
411         boost::shared_ptr<Region> right;
412
413         string before_name;
414         string after_name;
415         const double before_qn = _session.tempo_map().exact_qn_at_sample (playlist_position.sample, playlist_position.division) - region->quarter_note();
416         const double after_qn = mr->length_beats() - before_qn;
417         MusicSample before (playlist_position.sample - region->position(), playlist_position.division);
418         MusicSample after (region->length() - before.sample, playlist_position.division);
419
420         /* split doesn't change anything about length, so don't try to splice */
421         bool old_sp = _splicing;
422         _splicing = true;
423
424         RegionFactory::region_name (before_name, region->name(), false);
425
426         {
427                 PropertyList plist;
428
429                 plist.add (Properties::length, before.sample);
430                 plist.add (Properties::length_beats, before_qn);
431                 plist.add (Properties::name, before_name);
432                 plist.add (Properties::left_of_split, true);
433                 plist.add (Properties::layering_index, region->layering_index ());
434                 plist.add (Properties::layer, region->layer ());
435
436                 /* note: we must use the version of ::create with an offset here,
437                    since it supplies that offset to the Region constructor, which
438                    is necessary to get audio region gain envelopes right.
439                 */
440                 left = RegionFactory::create (region, MusicSample (0, 0), plist, true);
441         }
442
443         RegionFactory::region_name (after_name, region->name(), false);
444
445         {
446                 PropertyList plist;
447
448                 plist.add (Properties::length, after.sample);
449                 plist.add (Properties::length_beats, after_qn);
450                 plist.add (Properties::name, after_name);
451                 plist.add (Properties::right_of_split, true);
452                 plist.add (Properties::layering_index, region->layering_index ());
453                 plist.add (Properties::layer, region->layer ());
454
455                 /* same note as above */
456                 right = RegionFactory::create (region, before, plist, true);
457         }
458
459         add_region_internal (left, region->position(), 0, region->quarter_note(), true);
460         add_region_internal (right, region->position() + before.sample, before.division, region->quarter_note() + before_qn, true);
461
462         remove_region_internal (region);
463
464         _splicing = old_sp;
465 }
466
467 set<Evoral::Parameter>
468 MidiPlaylist::contained_automation()
469 {
470         /* this function is never called from a realtime thread, so
471            its OK to block (for short intervals).
472         */
473
474         Playlist::RegionReadLock rl (this);
475         set<Evoral::Parameter> ret;
476
477         for (RegionList::const_iterator r = regions.begin(); r != regions.end(); ++r) {
478                 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*r);
479
480                 for (Automatable::Controls::iterator c = mr->model()->controls().begin();
481                                 c != mr->model()->controls().end(); ++c) {
482                         if (c->second->list()->size() > 0) {
483                                 ret.insert(c->first);
484                         }
485                 }
486         }
487
488         return ret;
489 }