Prevent note trackers for overlapping MIDI regions inserting events that occur before...
[ardour2.git] / libs / ardour / midi_playlist.cc
blob0d2243da0fdc008edd0043973ff3959a4acae76d
1 /*
2 Copyright (C) 2006 Paul Davis
3 Author: David Robillard
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.
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.
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.
20 #include <cassert>
22 #include <algorithm>
23 #include <iostream>
25 #include <stdlib.h>
27 #include "pbd/error.h"
29 #include "evoral/EventList.hpp"
31 #include "ardour/debug.h"
32 #include "ardour/types.h"
33 #include "ardour/configuration.h"
34 #include "ardour/midi_playlist.h"
35 #include "ardour/midi_region.h"
36 #include "ardour/session.h"
37 #include "ardour/midi_ring_buffer.h"
39 #include "i18n.h"
41 using namespace ARDOUR;
42 using namespace PBD;
43 using namespace std;
45 MidiPlaylist::MidiPlaylist (Session& session, const XMLNode& node, bool hidden)
46 : Playlist (session, node, DataType::MIDI, hidden)
47 , _note_mode(Sustained)
49 #ifndef NDEBUG
50 const XMLProperty* prop = node.property("type");
51 assert(prop && DataType(prop->value()) == DataType::MIDI);
52 #endif
54 in_set_state++;
55 set_state (node, Stateful::loading_state_version);
56 in_set_state--;
59 MidiPlaylist::MidiPlaylist (Session& session, string name, bool hidden)
60 : Playlist (session, name, DataType::MIDI, hidden)
61 , _note_mode(Sustained)
65 MidiPlaylist::MidiPlaylist (boost::shared_ptr<const MidiPlaylist> other, string name, bool hidden)
66 : Playlist (other, name, hidden)
67 , _note_mode(other->_note_mode)
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)
75 /* this constructor does NOT notify others (session) */
78 MidiPlaylist::~MidiPlaylist ()
82 template<typename Time>
83 struct EventsSortByTime {
84 bool operator() (Evoral::Event<Time>* a, Evoral::Event<Time>* b) {
85 return a->time() < b->time();
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)
93 /* this function is never called from a realtime thread, so
94 its OK to block (for short intervals).
97 Glib::RecMutex::Lock rm (region_lock);
98 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("++++++ %1 .. %2 +++++++++++++++++++++++++++++++++++++++++++++++\n", start, start + dur));
100 framepos_t end = start + dur - 1;
102 _read_data_count = 0;
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;
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()) {
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.
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.
126 framepos_t resolve_at = (*i)->last_frame();
127 if (resolve_at < start || resolve_at >= end) {
128 resolve_at = start;
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);
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;
145 tracker_info.clear ();
148 if (regs.size() == 1 && tracker_info.empty()) {
150 /* just a single region - read directly into dst */
152 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("Single region (%1) read, no out-of-bound region tracking info\n", regs.front()->name()));
154 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(regs.front());
156 if (mr) {
158 NoteTrackers::iterator t = _note_trackers.find (mr.get());
159 MidiStateTracker* tracker;
160 bool new_tracker = false;
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()));
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()));
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");
182 _read_data_count += mr->read_data_count();
185 } else {
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.
191 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("%1 regions to read, plus %2 trackers\n", regs.size(), tracker_info.size()));
193 Evoral::EventList<framepos_t> evlist;
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;
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));
206 #endif
208 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("for %1 .. %2 we have %3 to consider\n", start, start+dur-1, regs.size()));
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;
216 NoteTrackers::iterator t = _note_trackers.find (mr.get());
217 MidiStateTracker* tracker;
218 bool new_tracker = false;
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()));
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()));
233 mr->read_at (evlist, start, dur, chan_n, _note_mode, tracker);
234 _read_data_count += mr->read_data_count();
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));
241 DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\tAFTER: tracker says there are %1 on notes\n", tracker->on()));
242 #endif
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");
253 if (!evlist.empty()) {
255 /* sort the event list */
256 EventsSortByTime<framepos_t> time_cmp;
257 evlist.sort (time_cmp);
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));
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;
275 DEBUG_TRACE (DEBUG::MidiPlaylistIO, "-------------------------------------------------------------\n");
276 return dur;
279 void
280 MidiPlaylist::clear_note_trackers ()
282 Glib::RecMutex::Lock rm (region_lock);
283 for (NoteTrackers::iterator n = _note_trackers.begin(); n != _note_trackers.end(); ++n) {
284 delete n->second;
286 _note_trackers.clear ();
289 void
290 MidiPlaylist::remove_dependents (boost::shared_ptr<Region> region)
292 /* MIDI regions have no dependents (crossfades) but we might be tracking notes */
293 NoteTrackers::iterator t = _note_trackers.find (region.get());
295 /* GACK! THREAD SAFETY! */
297 if (t != _note_trackers.end()) {
298 delete t->second;
299 _note_trackers.erase (t);
304 void
305 MidiPlaylist::refresh_dependents (boost::shared_ptr<Region> /*r*/)
307 /* MIDI regions have no dependents (crossfades) */
310 void
311 MidiPlaylist::finalize_split_region (boost::shared_ptr<Region> /*original*/, boost::shared_ptr<Region> /*left*/, boost::shared_ptr<Region> /*right*/)
313 /* No MIDI crossfading (yet?), so nothing to do here */
316 void
317 MidiPlaylist::check_dependents (boost::shared_ptr<Region> /*r*/, bool /*norefresh*/)
319 /* MIDI regions have no dependents (crossfades) */
324 MidiPlaylist::set_state (const XMLNode& node, int version)
326 in_set_state++;
327 freeze ();
329 Playlist::set_state (node, version);
331 thaw();
332 in_set_state--;
334 return 0;
337 void
338 MidiPlaylist::dump () const
340 boost::shared_ptr<Region> r;
342 cerr << "Playlist \"" << _name << "\" " << endl
343 << regions.size() << " regions "
344 << endl;
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;
358 bool
359 MidiPlaylist::destroy_region (boost::shared_ptr<Region> region)
361 boost::shared_ptr<MidiRegion> r = boost::dynamic_pointer_cast<MidiRegion> (region);
363 if (!r) {
364 return false;
367 bool changed = false;
370 RegionLock rlock (this);
371 RegionList::iterator i;
372 RegionList::iterator tmp;
374 for (i = regions.begin(); i != regions.end(); ) {
376 tmp = i;
377 ++tmp;
379 if ((*i) == region) {
380 regions.erase (i);
381 changed = true;
384 i = tmp;
389 if (changed) {
390 /* overload this, it normally means "removed", not destroyed */
391 notify_region_removed (region);
394 return changed;
397 set<Evoral::Parameter>
398 MidiPlaylist::contained_automation()
400 /* this function is never called from a realtime thread, so
401 its OK to block (for short intervals).
404 Glib::RecMutex::Lock rm (region_lock);
406 set<Evoral::Parameter> ret;
408 for (RegionList::const_iterator r = regions.begin(); r != regions.end(); ++r) {
409 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(*r);
411 for (Automatable::Controls::iterator c = mr->model()->controls().begin();
412 c != mr->model()->controls().end(); ++c) {
413 ret.insert(c->first);
417 return ret;
421 bool
422 MidiPlaylist::region_changed (const PBD::PropertyChange& what_changed, boost::shared_ptr<Region> region)
424 if (in_flush || in_set_state) {
425 return false;
428 PBD::PropertyChange our_interests;
429 our_interests.add (Properties::midi_data);
431 bool parent_wants_notify = Playlist::region_changed (what_changed, region);
433 if (parent_wants_notify || what_changed.contains (our_interests)) {
434 notify_contents_changed ();
437 return true;