Mac VST-2.x support
[ardour.git] / libs / ardour / midi_diskstream.cc
index 7b196e1fd0db6ab8cd0111870cab8c1f737b6bf8..359dd573cf513f32dd466799b3e65e0e75bdd5ad 100644 (file)
@@ -16,7 +16,6 @@
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
-#include <fstream>
 #include <cstdio>
 #include <unistd.h>
 #include <cmath>
@@ -59,7 +58,7 @@
 
 #include "midi++/types.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 #include <locale.h>
 
 using namespace std;
@@ -135,6 +134,7 @@ MidiDiskstream::init ()
        _capture_buf = new MidiRingBuffer<framepos_t>(size);
 
        _n_channels = ChanCount(DataType::MIDI, 1);
+       interpolation.add_channel_to (0,0);
 }
 
 MidiDiskstream::~MidiDiskstream ()
@@ -360,7 +360,8 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
        framepos_t            loop_start  = 0;
        framepos_t            loop_end    = 0;
        framepos_t            loop_length = 0;
-       get_location_times(loop_loc, &loop_start, &loop_end, &loop_length);
+
+       get_location_times (loop_loc, &loop_start, &loop_end, &loop_length);
 
        adjust_capture_position = 0;
 
@@ -402,17 +403,10 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
        }
 
        if (nominally_recording || rec_nframes) {
-
-               // Pump entire port buffer into the ring buffer (FIXME: split cycles?)
-               MidiBuffer& buf = sp->get_midi_buffer(nframes);
-               ChannelMode mode = AllChannels;
-               uint32_t mask = 0xffff;
-
-               MidiTrack * mt = dynamic_cast<MidiTrack*> (_track);
-               if (mt) {
-                       mode = mt->get_capture_channel_mode ();
-                       mask = mt->get_capture_channel_mask ();
-               }
+               // Pump entire port buffer into the ring buffer (TODO: split cycles?)
+               MidiBuffer&        buf    = sp->get_midi_buffer(nframes);
+               MidiTrack*         mt     = dynamic_cast<MidiTrack*>(_track);
+               MidiChannelFilter* filter = mt ? &mt->capture_filter() : NULL;
 
                for (MidiBuffer::iterator i = buf.begin(); i != buf.end(); ++i) {
                        Evoral::MIDIEvent<MidiBuffer::TimeType> ev(*i, false);
@@ -420,7 +414,7 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
                                break;
                        }
 #ifndef NDEBUG
-                       if (DEBUG::MidiIO & PBD::debug_bits) {
+                       if (DEBUG_ENABLED(DEBUG::MidiIO)) {
                                const uint8_t* __data = ev.buffer();
                                DEBUG_STR_DECL(a);
                                DEBUG_STR_APPEND(a, string_compose ("mididiskstream %1 capture event @ %2 + %3 sz %4 ", this, ev.time(), transport_frame, ev.size()));
@@ -446,31 +440,12 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
                        const framecnt_t loop_offset = _num_captured_loops * loop_length;
                        const framepos_t event_time = transport_frame + loop_offset - _accumulated_capture_offset + ev.time();
                        if (event_time < 0 || event_time < first_recordable_frame) {
+                               /* Event out of range, skip */
                                continue;
                        }
-                       switch (mode) {
-                       case AllChannels:
-                               _capture_buf->write(event_time,
-                                                   ev.type(), ev.size(), ev.buffer());
-                               break;
-                       case FilterChannels:
-                               if (ev.is_channel_event()) {
-                                       if ((1<<ev.channel()) & mask) {
-                                               _capture_buf->write(event_time,
-                                                                   ev.type(), ev.size(), ev.buffer());
-                                       }
-                               } else {
-                                       _capture_buf->write(event_time,
-                                                           ev.type(), ev.size(), ev.buffer());
-                               }
-                               break;
-                       case ForceChannel:
-                               if (ev.is_channel_event()) {
-                                       ev.set_channel (PBD::ffs(mask) - 1);
-                               }
-                               _capture_buf->write(event_time,
-                                                   ev.type(), ev.size(), ev.buffer());
-                               break;
+
+                       if (!filter || !filter->filter(ev.buffer(), ev.size())) {
+                               _capture_buf->write(event_time, ev.type(), ev.size(), ev.buffer());
                        }
                }
                g_atomic_int_add(const_cast<gint*>(&_frames_pending_write), nframes);
@@ -483,7 +458,7 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
                                   that it can read it if it likes.
                                */
                                _gui_feed_buffer.clear ();
-                               
+
                                for (MidiBuffer::iterator i = buf.begin(); i != buf.end(); ++i) {
                                        /* This may fail if buf is larger than _gui_feed_buffer, but it's not really
                                           the end of the world if it does.
@@ -522,24 +497,35 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
 
                playback_distance = nframes;
 
-       } else {
+       } else if (_actual_speed != 1.0f && _target_speed > 0) {
 
-               /* XXX: should be doing varispeed stuff here, similar to the code in AudioDiskstream::process */
+               interpolation.set_speed (_target_speed);
 
-               playback_distance = nframes;
+               playback_distance = interpolation.distance  (nframes);
 
+       } else {
+               playback_distance = nframes;
        }
 
-       if (need_disk_signal) {
+       if (need_disk_signal && !_session.declick_out_pending()) {
                /* copy the diskstream data to all output buffers */
-               
+
                MidiBuffer& mbuf (bufs.get_midi (0));
-               get_playback (mbuf, nframes);
-               
+               get_playback (mbuf, playback_distance);
+
                /* leave the audio count alone */
                ChanCount cnt (DataType::MIDI, 1);
                cnt.set (DataType::AUDIO, bufs.count().n_audio());
                bufs.set_count (cnt);
+
+               /* vari-speed */
+               if (_target_speed > 0 && _actual_speed != 1.0f) {
+                       MidiBuffer& mbuf (bufs.get_midi (0));
+                       for (MidiBuffer::iterator i = mbuf.begin(); i != mbuf.end(); ++i) {
+                               MidiBuffer::TimeType *tme = i.timeptr();
+                               *tme = (*tme) * nframes / playback_distance;
+                       }
+               }
        }
 
        return 0;
@@ -550,7 +536,10 @@ MidiDiskstream::calculate_playback_distance (pframes_t nframes)
 {
        frameoffset_t playback_distance = nframes;
 
-       /* XXX: should be doing varispeed stuff once it's implemented in ::process() above */
+       if (!record_enabled() && _actual_speed != 1.0f && _actual_speed > 0.f) {
+               interpolation.set_speed (_target_speed);
+               playback_distance = interpolation.distance (nframes, false);
+       }
 
        if (_actual_speed < 0.0) {
                return -playback_distance;
@@ -564,6 +553,10 @@ MidiDiskstream::commit (framecnt_t playback_distance)
 {
        bool need_butler = false;
 
+       if (!_io || !_io->active()) {
+               return false;
+       }
+
        if (_actual_speed < 0.0) {
                playback_sample -= playback_distance;
        } else {
@@ -590,11 +583,34 @@ MidiDiskstream::commit (framecnt_t playback_distance)
         * but we do need to check so that the decision on whether or not we
         * need the butler is done correctly.
         */
-       
+
+       /* furthermore..
+        *
+        * Doing heavy GUI operations[1] can stall also the butler.
+        * The RT-thread meanwhile will happily continue and
+        * ‘frames_read’ (from buffer to output) will become larger
+        * than ‘frames_written’ (from disk to buffer).
+        *
+        * The disk-stream is now behind..
+        *
+        * In those cases the butler needs to be summed to refill the buffer (done now)
+        * AND we need to skip (frames_read - frames_written). ie remove old events
+        * before playback_sample from the rinbuffer.
+        *
+        * [1] one way to do so is described at #6170.
+        * For me just popping up the context-menu on a MIDI-track header
+        * of a track with a large (think beethoven :) midi-region also did the
+        * trick. The playhead stalls for 2 or 3 sec, until the context-menu shows.
+        *
+        * In both cases the root cause is that redrawing MIDI regions on the GUI is still very slow
+        * and can stall
+        */
        if (frames_read <= frames_written) {
                if ((frames_written - frames_read) + playback_distance < midi_readahead) {
                        need_butler = true;
                }
+       } else {
+               need_butler = true;
        }
 
 
@@ -688,39 +704,48 @@ int
 MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
 {
        framecnt_t this_read   = 0;
-       bool       reloop      = false;
        framepos_t loop_end    = 0;
        framepos_t loop_start  = 0;
        framecnt_t loop_length = 0;
-       Location*  loc         = 0;
-
-       if (!reversed) {
+       Location*  loc         = loop_location;
+       framepos_t effective_start = start;
+       Evoral::Range<framepos_t>*  loop_range (0);
 
-               loc = loop_location;
-               get_location_times(loc, &loop_start, &loop_end, &loop_length);
+       MidiTrack*         mt     = dynamic_cast<MidiTrack*>(_track);
+       MidiChannelFilter* filter = mt ? &mt->playback_filter() : NULL;
 
-               /* if we are looping, ensure that the first frame we read is at the correct
-                  position within the loop.
-               */
+       frameoffset_t loop_offset = 0;
 
-               if (loc && (start >= loop_end)) {
-                       //cerr << "start adjusted from " << start;
-                       start = loop_start + ((start - loop_start) % loop_length);
-                       //cerr << "to " << start << endl;
-               }
-               // cerr << "start is " << start << " end " << start+dur << "  loopstart: " << loop_start << "  loopend: " << loop_end << endl;
+       if (!reversed && loc) {
+               get_location_times (loc, &loop_start, &loop_end, &loop_length);
        }
 
        while (dur) {
 
                /* take any loop into account. we can't read past the end of the loop. */
 
-               if (loc && (loop_end - start <= dur)) {
-                       this_read = loop_end - start;
-                       // cerr << "reloop true: thisread: " << this_read << "  dur: " << dur << endl;
-                       reloop = true;
+               if (loc && !reversed) {
+
+                       if (!loop_range) {
+                               loop_range = new Evoral::Range<framepos_t> (loop_start, loop_end-1); // inclusive semantics require -1
+                       }
+
+                       /* if we are (seamlessly) looping, ensure that the first frame we read is at the correct
+                          position within the loop.
+                       */
+
+                       effective_start = loop_range->squish (effective_start);
+
+                       if ((loop_end - effective_start) <= dur) {
+                               /* too close to end of loop to read "dur", so
+                                  shorten it.
+                               */
+                               this_read = loop_end - effective_start;
+                       } else {
+                               this_read = dur;
+                       }
+
                } else {
-                       reloop = false;
                        this_read = dur;
                }
 
@@ -728,15 +753,17 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
                        break;
                }
 
-               this_read = min(dur,this_read);
+               this_read = min (dur,this_read);
+
+               DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS ::read at %1 for %2 loffset %3\n", effective_start, this_read, loop_offset));
 
-               if (midi_playlist()->read (*_playback_buf, start, this_read) != this_read) {
+               if (midi_playlist()->read (*_playback_buf, effective_start, this_read, loop_range, 0, filter) != this_read) {
                        error << string_compose(
                                        _("MidiDiskstream %1: cannot read %2 from playlist at frame %3"),
                                        id(), this_read, start) << endmsg;
                        return -1;
                }
-               
+
                g_atomic_int_add (&_frames_written_to_ringbuffer, this_read);
 
                if (reversed) {
@@ -747,14 +774,16 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
 
                } else {
 
-                       /* if we read to the end of the loop, go back to the beginning */
-                       if (reloop) {
-                               // Synthesize LoopEvent here, because the next events
-                               // written will have non-monotonic timestamps.
-                               start = loop_start;
-                       } else {
-                               start += this_read;
-                       }
+                       /* adjust passed-by-reference argument (note: this is
+                          monotonic and does not reflect looping.
+                       */
+                       start += this_read;
+
+                       /* similarly adjust effective_start, but this may be
+                          readjusted for seamless looping as we continue around
+                          the loop.
+                       */
+                       effective_start += this_read;
                }
 
                dur -= this_read;
@@ -765,7 +794,7 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
 }
 
 int
-MidiDiskstream::do_refill_with_alloc ()
+MidiDiskstream::_do_refill_with_alloc (bool /* partial_fill */)
 {
        return do_refill();
 }
@@ -777,6 +806,10 @@ MidiDiskstream::do_refill ()
        size_t  write_space = _playback_buf->write_space();
        bool    reversed    = (_visible_speed * _session.transport_speed()) < 0.0f;
 
+       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS refill, write space = %1 file frame = %2\n",
+                                                             write_space, file_frame));
+
+       /* no space to write */
        if (write_space == 0) {
                return 0;
        }
@@ -790,23 +823,17 @@ MidiDiskstream::do_refill ()
                return 0;
        }
 
-       /* no space to write */
-       if (_playback_buf->write_space() == 0) {
-               return 0;
-       }
-
        uint32_t frames_read = g_atomic_int_get(&_frames_read_from_ringbuffer);
        uint32_t frames_written = g_atomic_int_get(&_frames_written_to_ringbuffer);
-       if ((frames_written - frames_read) >= midi_readahead) {
+
+       if ((frames_read < frames_written) && (frames_written - frames_read) >= midi_readahead) {
                return 0;
        }
 
-       framecnt_t to_read = midi_readahead - (frames_written - frames_read);
-
-       //cout << "MDS read for midi_readahead " << to_read << "  rb_contains: "
-       //      << frames_written - frames_read << endl;
+       framecnt_t to_read = midi_readahead - ((framecnt_t)frames_written - (framecnt_t)frames_read);
 
-       to_read = (framecnt_t) min ((framecnt_t) to_read, (framecnt_t) (max_framepos - file_frame));
+       to_read = min (to_read, (framecnt_t) (max_framepos - file_frame));
+       to_read = min (to_read, (framecnt_t) write_space);
 
        if (read (file_frame, to_read, reversed)) {
                ret = -1;
@@ -837,8 +864,8 @@ MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush)
 
        const framecnt_t total = g_atomic_int_get(const_cast<gint*> (&_frames_pending_write));
 
-       if (total == 0 || 
-           _capture_buf->read_space() == 0 || 
+       if (total == 0 ||
+           _capture_buf->read_space() == 0 ||
            (!force_flush && (total < disk_write_chunk_frames) && was_recording)) {
                goto out;
        }
@@ -870,7 +897,7 @@ MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush)
                if (_write_source->midi_write (lm, *_capture_buf, get_capture_start_frame (0), to_write) != to_write) {
                        error << string_compose(_("MidiDiskstream %1: cannot write to disk"), id()) << endmsg;
                        return -1;
-               } 
+               }
                g_atomic_int_add(const_cast<gint*> (&_frames_pending_write), -to_write);
        }
 
@@ -1024,7 +1051,7 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen
                                        /* start of this region is the offset between the start of its capture and the start of the whole pass */
                                        plist.add (Properties::start, (*ci)->start - initial_capture);
                                        plist.add (Properties::length, (*ci)->frames);
-                                       plist.add (Properties::length_beats, converter.from((*ci)->frames));
+                                       plist.add (Properties::length_beats, converter.from((*ci)->frames).to_double());
                                        plist.add (Properties::name, region_name);
 
                                        boost::shared_ptr<Region> rx (RegionFactory::create (srcs, plist));
@@ -1058,7 +1085,7 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen
                                _write_source->drop_references ();
                                _write_source.reset();
                        }
-               }
+               }
 
        }
 
@@ -1123,7 +1150,7 @@ MidiDiskstream::finish_capture ()
 void
 MidiDiskstream::set_record_enabled (bool yn)
 {
-       if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_midi() == 0) {
+       if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_midi() == 0 || record_safe ()) {
                return;
        }
 
@@ -1137,22 +1164,44 @@ MidiDiskstream::set_record_enabled (bool yn)
                } else {
                        disengage_record_enable ();
                }
-               
+
                RecordEnableChanged (); /* EMIT SIGNAL */
        }
 }
 
+void
+MidiDiskstream::set_record_safe (bool yn)
+{
+       if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_midi() == 0) { // REQUIRES REVIEW
+               return;
+       }
+
+       /* yes, i know that this not proof against race conditions, but its
+        good enough. i think.
+        */
+
+       if (record_safe () != yn) {
+               if (yn) {
+                       engage_record_safe ();
+               } else {
+                       disengage_record_safe ();
+               }
+
+               RecordSafeChanged (); /* EMIT SIGNAL */
+       }
+}
+
 bool
 MidiDiskstream::prep_record_enable ()
 {
-       if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_midi() == 0) {
+       if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_midi() == 0 || record_safe ()) { // REQUIRES REVIEW "|| record_safe ()"
                return false;
        }
 
        bool const rolling = _session.transport_speed() != 0.0f;
 
        boost::shared_ptr<MidiPort> sp = _source_port.lock ();
-       
+
        if (sp && Config->get_monitoring_model() == HardwareMonitoring) {
                sp->request_input_monitoring (!(_session.config.get_auto_input() && rolling));
        }
@@ -1172,7 +1221,7 @@ MidiDiskstream::get_state ()
 {
        XMLNode& node (Diskstream::get_state());
        char buf[64];
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
 
        if (_write_source && _session.get_record_enabled()) {
 
@@ -1206,7 +1255,7 @@ MidiDiskstream::set_state (const XMLNode& node, int version)
        XMLNodeList nlist = node.children();
        XMLNodeIterator niter;
        XMLNode* capture_pending_node = 0;
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
 
        /* prevent write sources from being created */
 
@@ -1279,14 +1328,14 @@ MidiDiskstream::steal_write_source_name ()
 
        try {
                string new_path = _session.new_midi_source_path (name());
-               
+
                if (_write_source->rename (new_path)) {
                        return string();
                }
        } catch (...) {
                return string ();
        }
-       
+
        return our_old_name;
 }
 
@@ -1318,7 +1367,7 @@ void
 MidiDiskstream::ensure_input_monitoring (bool yn)
 {
        boost::shared_ptr<MidiPort> sp = _source_port.lock ();
-       
+
        if (sp) {
                sp->ensure_input_monitoring (yn);
        }
@@ -1343,7 +1392,7 @@ float
 MidiDiskstream::playback_buffer_load () const
 {
        /* For MIDI it's not trivial to differentiate the following two cases:
-          
+
           1.  The playback buffer is empty because the system has run out of time to fill it.
           2.  The playback buffer is empty because there is no more data on the playlist.
 
@@ -1351,7 +1400,7 @@ MidiDiskstream::playback_buffer_load () const
           cannot keep up when #2 happens, when in fact it can.  Since MIDI data rates
           are so low compared to audio, just give a pretend answer here.
        */
-       
+
        return 1;
 }
 
@@ -1359,7 +1408,7 @@ float
 MidiDiskstream::capture_buffer_load () const
 {
        /* We don't report playback buffer load, so don't report capture load either */
-       
+
        return 1;
 }
 
@@ -1387,25 +1436,22 @@ MidiDiskstream::get_playback (MidiBuffer& dst, framecnt_t nframes)
        Location* loc = loop_location;
 
        DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose (
-                            "%1 MDS pre-read read %8 @ %4..%5 from %2 write to %3, LOOPED ? %6-%7\n", _name,
-                            _playback_buf->get_read_ptr(), _playback_buf->get_write_ptr(), playback_sample, playback_sample + nframes, 
-                            (loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes));
+                            "%1 MDS pre-read read %8 offset = %9 @ %4..%5 from %2 write to %3, LOOPED ? %6 .. %7\n", _name,
+                            _playback_buf->get_read_ptr(), _playback_buf->get_write_ptr(), playback_sample, playback_sample + nframes,
+                            (loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes, Port::port_offset()));
 
-        // cerr << "================\n";
-        // _playback_buf->dump (cerr);
-        // cerr << "----------------\n";
+       //cerr << "======== PRE ========\n";
+       //_playback_buf->dump (cerr);
+       //cerr << "----------------\n";
 
-       size_t events_read = 0; 
+       size_t events_read = 0;
 
        if (loc) {
                framepos_t effective_start;
 
-               if (playback_sample >= loc->end()) {
-                       effective_start = loc->start() + ((playback_sample - loc->end()) % loc->length());
-               } else {
-                       effective_start = playback_sample;
-               }
-               
+               Evoral::Range<framepos_t> loop_range (loc->start(), loc->end() - 1);
+               effective_start = loop_range.squish (playback_sample);
+
                DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("looped, effective start adjusted to %1\n", effective_start));
 
                if (effective_start == loc->start()) {
@@ -1416,37 +1462,46 @@ MidiDiskstream::get_playback (MidiBuffer& dst, framecnt_t nframes)
                        _playback_buf->resolve_tracker (dst, 0);
                }
 
+               /* for split-cycles we need to offset the events */
+
                if (loc->end() >= effective_start && loc->end() < effective_start + nframes) {
+
                        /* end of loop is within the range we are reading, so
                           split the read in two, and lie about the location
                           for the 2nd read
                        */
+
                        framecnt_t first, second;
 
                        first = loc->end() - effective_start;
                        second = nframes - first;
 
-                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read for eff %1 end %2: %3 and %4\n",
-                                                                             effective_start, loc->end(), first, second));
+                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read for eff %1 end %2: %3 and %4, cycle offset %5\n",
+                                                                             effective_start, loc->end(), first, second));
 
                        if (first) {
                                DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #1, from %1 for %2\n",
                                                                                      effective_start, first));
                                events_read = _playback_buf->read (dst, effective_start, first);
-                       } 
+                       }
 
                        if (second) {
                                DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #2, from %1 for %2\n",
                                                                                      loc->start(), second));
                                events_read += _playback_buf->read (dst, loc->start(), second);
                        }
-                                                                   
+
                } else {
                        DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #3, adjusted start as %1 for %2\n",
                                                                              effective_start, nframes));
                        events_read = _playback_buf->read (dst, effective_start, effective_start + nframes);
                }
        } else {
+               const size_t n_skipped = _playback_buf->skip_to (playback_sample);
+               if (n_skipped > 0) {
+                       warning << string_compose(_("MidiDiskstream %1: skipped %2 events, possible underflow"), id(), n_skipped) << endmsg;
+               }
+               DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("playback buffer read, from %1 to %2 (%3)", playback_sample, playback_sample + nframes, nframes));
                events_read = _playback_buf->read (dst, playback_sample, playback_sample + nframes);
        }
 
@@ -1457,6 +1512,10 @@ MidiDiskstream::get_playback (MidiBuffer& dst, framecnt_t nframes)
                             _playback_buf->get_read_ptr(), _playback_buf->get_write_ptr()));
 
        g_atomic_int_add (&_frames_read_from_ringbuffer, nframes);
+
+       //cerr << "======== POST ========\n";
+       //_playback_buf->dump (cerr);
+       //cerr << "----------------\n";
 }
 
 bool
@@ -1490,7 +1549,7 @@ boost::shared_ptr<MidiBuffer>
 MidiDiskstream::get_gui_feed_buffer () const
 {
        boost::shared_ptr<MidiBuffer> b (new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI)));
-       
+
        Glib::Threads::Mutex::Lock lm (_gui_feed_buffer_mutex);
        b->copy (_gui_feed_buffer);
        return b;
@@ -1508,6 +1567,19 @@ MidiDiskstream::reset_tracker ()
        }
 }
 
+void
+MidiDiskstream::resolve_tracker (Evoral::EventSink<framepos_t>& buffer, framepos_t time)
+{
+       _playback_buf->resolve_tracker(buffer, time);
+
+       boost::shared_ptr<MidiPlaylist> mp (midi_playlist());
+
+       if (mp) {
+               mp->reset_note_trackers ();
+       }
+}
+
+
 boost::shared_ptr<MidiPlaylist>
 MidiDiskstream::midi_playlist ()
 {