Add method to find subgroup_bus
[ardour.git] / libs / ardour / disk_reader.cc
index 0a12f693feb97c6e33db0ab5c2638cbeaf24e45e..4896a95af2af8943a2d271e8507035f5583d0fb7 100644 (file)
 
 */
 
 
 */
 
-#include "pbd/i18n.h"
+#include <boost/smart_ptr/scoped_ptr.hpp>
+
+#include "pbd/enumwriter.h"
 #include "pbd/memento_command.h"
 
 #include "pbd/memento_command.h"
 
+#include "ardour/audioengine.h"
 #include "ardour/audioplaylist.h"
 #include "ardour/audio_buffer.h"
 #include "ardour/butler.h"
 #include "ardour/debug.h"
 #include "ardour/disk_reader.h"
 #include "ardour/audioplaylist.h"
 #include "ardour/audio_buffer.h"
 #include "ardour/butler.h"
 #include "ardour/debug.h"
 #include "ardour/disk_reader.h"
+#include "ardour/midi_ring_buffer.h"
+#include "ardour/midi_playlist.h"
+#include "ardour/midi_track.h"
+#include "ardour/pannable.h"
 #include "ardour/playlist.h"
 #include "ardour/playlist_factory.h"
 #include "ardour/session.h"
 #include "ardour/session_playlists.h"
 
 #include "ardour/playlist.h"
 #include "ardour/playlist_factory.h"
 #include "ardour/session.h"
 #include "ardour/session_playlists.h"
 
+#include "pbd/i18n.h"
+
 using namespace ARDOUR;
 using namespace PBD;
 using namespace std;
 
 using namespace ARDOUR;
 using namespace PBD;
 using namespace std;
 
-ARDOUR::framecnt_t DiskReader::_chunk_frames = default_chunk_frames ();
+ARDOUR::samplecnt_t DiskReader::_chunk_samples = default_chunk_samples ();
+PBD::Signal0<void> DiskReader::Underrun;
+Sample* DiskReader::_mixdown_buffer = 0;
+gain_t* DiskReader::_gain_buffer = 0;
+samplecnt_t DiskReader::midi_readahead = 4096;
+bool DiskReader::_no_disk_output = false;
 
 DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f)
        : DiskIOProcessor (s, str, f)
 
 DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f)
        : DiskIOProcessor (s, str, f)
-       , _roll_delay (0)
-       , overwrite_frame (0)
-        , overwrite_offset (0)
-        , _pending_overwrite (false)
-        , overwrite_queued (false)
-        , file_frame (0)
-        , playback_sample (0)
-       , _monitoring_choice (MonitorDisk)
-       , channels (new ChannelList)
+       , overwrite_sample (0)
+       , overwrite_offset (0)
+       , _pending_overwrite (false)
+       , overwrite_queued (false)
 {
 {
+       file_sample[DataType::AUDIO] = 0;
+       file_sample[DataType::MIDI] = 0;
 }
 
 DiskReader::~DiskReader ()
 {
 }
 
 DiskReader::~DiskReader ()
 {
-       DEBUG_TRACE (DEBUG::Destruction, string_compose ("DiskReader %1 deleted\n", _name));
+       DEBUG_TRACE (DEBUG::Destruction, string_compose ("DiskReader %1 @ %2 deleted\n", _name, this));
 
 
-       if (_playlist) {
-               _playlist->release ();
+       for (uint32_t n = 0; n < DataType::num_types; ++n) {
+               if (_playlists[n]) {
+                       _playlists[n]->release ();
+               }
        }
 
        {
        }
 
        {
@@ -70,6 +83,8 @@ DiskReader::~DiskReader ()
        }
 
        channels.flush ();
        }
 
        channels.flush ();
+
+       delete _midi_buf;
 }
 
 void
 }
 
 void
@@ -93,8 +108,8 @@ DiskReader::free_working_buffers()
        _gain_buffer          = 0;
 }
 
        _gain_buffer          = 0;
 }
 
-framecnt_t
-DiskReader::default_chunk_frames()
+samplecnt_t
+DiskReader::default_chunk_samples()
 {
        return 65536;
 }
 {
        return 65536;
 }
@@ -102,55 +117,34 @@ DiskReader::default_chunk_frames()
 bool
 DiskReader::set_name (string const & str)
 {
 bool
 DiskReader::set_name (string const & str)
 {
-       if (_name != str) {
-               assert (_playlist);
-               _playlist->set_name (str);
-               SessionObject::set_name(str);
+       string my_name = X_("player:");
+       my_name += str;
+
+       if (_name != my_name) {
+               SessionObject::set_name (my_name);
        }
 
        return true;
 }
 
        }
 
        return true;
 }
 
-void
-DiskReader::set_roll_delay (ARDOUR::framecnt_t nframes)
+XMLNode&
+DiskReader::state ()
 {
 {
-       _roll_delay = nframes;
+       XMLNode& node (DiskIOProcessor::state ());
+       node.set_property(X_("type"), X_("diskreader"));
+       return node;
 }
 
 int
 DiskReader::set_state (const XMLNode& node, int version)
 {
 }
 
 int
 DiskReader::set_state (const XMLNode& node, int version)
 {
-       XMLProperty const * prop;
-
        if (DiskIOProcessor::set_state (node, version)) {
                return -1;
        }
 
        if (DiskIOProcessor::set_state (node, version)) {
                return -1;
        }
 
-       if ((prop = node.property ("playlist")) == 0) {
-               return -1;
-       }
-
-       if (find_and_use_playlist (prop->value())) {
-               return -1;
-       }
-
        return 0;
 }
 
        return 0;
 }
 
-/* Processor interface */
-
-bool
-DiskReader::configure_io (ChanCount in, ChanCount out)
-{
-       return true;
-}
-
-bool
-DiskReader::can_support_io_configuration (const ChanCount& in, ChanCount& out)
-{
-       return true;
-}
-
 void
 DiskReader::realtime_handle_transport_stopped ()
 {
 void
 DiskReader::realtime_handle_transport_stopped ()
 {
@@ -161,66 +155,28 @@ DiskReader::realtime_locate ()
 {
 }
 
 {
 }
 
-int
-DiskReader::add_channel_to (boost::shared_ptr<ChannelList> c, uint32_t how_many)
-{
-       while (how_many--) {
-               c->push_back (new ChannelInfo(
-                                     _session.butler()->audio_diskstream_playback_buffer_size(),
-                                     speed_buffer_size, wrap_buffer_size));
-               interpolation.add_channel_to (
-                       _session.butler()->audio_diskstream_playback_buffer_size(),
-                       speed_buffer_size);
-       }
-
-       _n_channels.set (DataType::AUDIO, c->size());
-
-       return 0;
-}
-
-int
-DiskReader::add_channel (uint32_t how_many)
-{
-       RCUWriter<ChannelList> writer (channels);
-       boost::shared_ptr<ChannelList> c = writer.get_copy();
-
-       return add_channel_to (c, how_many);
-}
-
-int
-DiskReader::remove_channel_from (boost::shared_ptr<ChannelList> c, uint32_t how_many)
+float
+DiskReader::buffer_load () const
 {
 {
-       while (how_many-- && !c->empty()) {
-               delete c->back();
-               c->pop_back();
-               interpolation.remove_channel_from ();
-       }
+       /* Note: for MIDI it's not trivial to differentiate the following two cases:
 
 
-       _n_channels.set(DataType::AUDIO, c->size());
+          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.
 
 
-       return 0;
-}
-
-int
-DiskReader::remove_channel (uint32_t how_many)
-{
-       RCUWriter<ChannelList> writer (channels);
-       boost::shared_ptr<ChannelList> c = writer.get_copy();
-
-       return remove_channel_from (c, how_many);
-}
+          If we use a simple buffer load computation, we will report that the MIDI diskstream
+          cannot keep up when #2 happens, when in fact it can.  Since MIDI data rates
+          are so low compared to audio, just use the audio value here.
+       */
 
 
-float
-DiskReader::buffer_load () const
-{
        boost::shared_ptr<ChannelList> c = channels.reader();
 
        if (c->empty ()) {
        boost::shared_ptr<ChannelList> c = channels.reader();
 
        if (c->empty ()) {
+               /* no channels, so no buffers, so completely full and ready to playback, sir! */
                return 1.0;
        }
 
                return 1.0;
        }
 
-       return (float) ((double) c->front()->buf->read_space()/
-                          (double) c->front()->buf->bufsize());
+       PBD::RingBufferNPT<Sample> * b = c->front()->buf;
+       return (float) ((double) b->read_space() / (double) b->bufsize());
 }
 
 void
 }
 
 void
@@ -233,87 +189,6 @@ DiskReader::adjust_buffering ()
        }
 }
 
        }
 }
 
-DiskReader::ChannelInfo::ChannelInfo (framecnt_t bufsize, framecnt_t speed_size, framecnt_t wrap_size)
-{
-       current_buffer = 0;
-
-       speed_buffer = new Sample[speed_size];
-       wrap_buffer = new Sample[wrap_size];
-
-       buf = new RingBufferNPT<Sample> (bufsize);
-
-       /* touch the ringbuffer buffer, which will cause
-          them to be mapped into locked physical RAM if
-          we're running with mlockall(). this doesn't do
-          much if we're not.
-       */
-
-       memset (buf->buffer(), 0, sizeof (Sample) * buf->bufsize());
-}
-
-void
-DiskReader::ChannelInfo::resize (framecnt_t bufsize)
-{
-       delete buf;
-       buf = new RingBufferNPT<Sample> (bufsize);
-       memset (buf->buffer(), 0, sizeof (Sample) * buf->bufsize());
-}
-
-DiskReader::ChannelInfo::~ChannelInfo ()
-{
-       delete [] speed_buffer;
-       speed_buffer = 0;
-
-       delete [] wrap_buffer;
-       wrap_buffer = 0;
-
-       delete buf;
-       buf = 0;
-}
-
-int
-DiskReader::set_block_size (pframes_t /*nframes*/)
-{
-       if (_session.get_block_size() > speed_buffer_size) {
-               speed_buffer_size = _session.get_block_size();
-               boost::shared_ptr<ChannelList> c = channels.reader();
-
-               for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
-                       delete [] (*chan)->speed_buffer;
-                       (*chan)->speed_buffer = new Sample[speed_buffer_size];
-               }
-       }
-       allocate_temporary_buffers ();
-       return 0;
-}
-
-void
-DiskReader::allocate_temporary_buffers ()
-{
-       /* make sure the wrap buffer is at least large enough to deal
-          with the speeds up to 1.2, to allow for micro-variation
-          when slaving to MTC, Timecode etc.
-       */
-
-       double const sp = max (fabs (_actual_speed), 1.2);
-       framecnt_t required_wrap_size = (framecnt_t) ceil (_session.get_block_size() * sp) + 2;
-
-       if (required_wrap_size > wrap_buffer_size) {
-
-               boost::shared_ptr<ChannelList> c = channels.reader();
-
-               for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
-                       if ((*chan)->wrap_buffer) {
-                               delete [] (*chan)->wrap_buffer;
-                       }
-                       (*chan)->wrap_buffer = new Sample[required_wrap_size];
-               }
-
-               wrap_buffer_size = required_wrap_size;
-       }
-}
-
-
 void
 DiskReader::playlist_changed (const PropertyChange&)
 {
 void
 DiskReader::playlist_changed (const PropertyChange&)
 {
@@ -324,59 +199,22 @@ void
 DiskReader::playlist_modified ()
 {
        if (!i_am_the_modifier && !overwrite_queued) {
 DiskReader::playlist_modified ()
 {
        if (!i_am_the_modifier && !overwrite_queued) {
-               // !!!! _session.request_overwrite_buffer (this);
+               _session.request_overwrite_buffer (_route);
                overwrite_queued = true;
        }
 }
 
                overwrite_queued = true;
        }
 }
 
-void
-DiskReader::playlist_deleted (boost::weak_ptr<Playlist> wpl)
-{
-       boost::shared_ptr<Playlist> pl (wpl.lock());
-
-       if (pl == _playlist) {
-
-               /* this catches an ordering issue with session destruction. playlists
-                  are destroyed before disk readers. we have to invalidate any handles
-                  we have to the playlist.
-               */
-
-               if (_playlist) {
-                       _playlist.reset ();
-               }
-       }
-}
-
 int
 int
-DiskReader::use_playlist (boost::shared_ptr<Playlist> playlist)
+DiskReader::use_playlist (DataType dt, boost::shared_ptr<Playlist> playlist)
 {
 {
-        if (!playlist) {
-                return 0;
-        }
-
         bool prior_playlist = false;
 
         bool prior_playlist = false;
 
-       {
-               Glib::Threads::Mutex::Lock lm (state_lock);
-
-               if (playlist == _playlist) {
-                       return 0;
-               }
-
-               playlist_connections.drop_connections ();
-
-               if (_playlist) {
-                       _playlist->release();
-                        prior_playlist = true;
-               }
-
-               _playlist = playlist;
-               _playlist->use();
+        if (_playlists[dt]) {
+               prior_playlist = true;
+        }
 
 
-               _playlist->ContentsChanged.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_modified, this));
-               _playlist->LayeringChanged.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_modified, this));
-               _playlist->DropReferences.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_deleted, this, boost::weak_ptr<Playlist>(_playlist)));
-               _playlist->RangesMoved.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_ranges_moved, this, _1, _2));
+        if (DiskIOProcessor::use_playlist (dt, playlist)) {
+               return -1;
        }
 
        /* don't do this if we've already asked for it *or* if we are setting up
        }
 
        /* don't do this if we've already asked for it *or* if we are setting up
@@ -384,283 +222,292 @@ DiskReader::use_playlist (boost::shared_ptr<Playlist> playlist)
           take care of the buffer refill.
        */
 
           take care of the buffer refill.
        */
 
-       if (!overwrite_queued && prior_playlist) {
-               // !!! _session.request_overwrite_buffer (this);
+        if (!overwrite_queued && (prior_playlist || _session.loading())) {
+               _session.request_overwrite_buffer (_route);
                overwrite_queued = true;
        }
 
                overwrite_queued = true;
        }
 
-       PlaylistChanged (); /* EMIT SIGNAL */
-       _session.set_dirty ();
-
        return 0;
 }
 
        return 0;
 }
 
-int
-DiskReader::find_and_use_playlist (const string& name)
+void
+DiskReader::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample,
+                 double speed, pframes_t nframes, bool result_required)
 {
 {
-       boost::shared_ptr<AudioPlaylist> playlist;
+       uint32_t n;
+       boost::shared_ptr<ChannelList> c = channels.reader();
+       ChannelList::iterator chan;
+       sampleoffset_t disk_samples_to_consume;
+       MonitorState ms = _route->monitoring_state ();
 
 
-       if ((playlist = boost::dynamic_pointer_cast<AudioPlaylist> (_session.playlists->by_name (name))) == 0) {
-               playlist = boost::dynamic_pointer_cast<AudioPlaylist> (PlaylistFactory::create (DataType::AUDIO, _session, name));
+       if (_active) {
+               if (!_pending_active) {
+                       _active = false;
+                       return;
+               }
+       } else {
+               if (_pending_active) {
+                       _active = true;
+               } else {
+                       return;
+               }
        }
 
        }
 
-       if (!playlist) {
-               error << string_compose(_("DiskReader: Playlist \"%1\" isn't an audio playlist"), name) << endmsg;
-               return -1;
+       if ((speed == 0.0) && (ms == MonitoringDisk)) {
+               /* no channels, or stopped. Don't accidentally pass any data
+                * from disk into our outputs (e.g. via interpolation)
+                */
+               return;
        }
 
        }
 
-       return use_playlist (playlist);
-}
+       BufferSet& scratch_bufs (_session.get_scratch_buffers (bufs.count()));
+       const bool still_locating = _session.global_locate_pending();
 
 
-int
-DiskReader::use_new_playlist ()
-{
-       string newname;
-       boost::shared_ptr<AudioPlaylist> playlist;
+       if (c->empty()) {
+               /* do nothing with audio */
+               goto midi;
+       }
 
 
-       if (_playlist) {
-               newname = Playlist::bump_name (_playlist->name(), _session);
+       if (speed != 1.0f && speed != -1.0f) {
+               interpolation.set_speed (speed);
+               disk_samples_to_consume = interpolation.distance (nframes);
+               if (speed < 0.0) {
+                       disk_samples_to_consume = -disk_samples_to_consume;
+               }
        } else {
        } else {
-               newname = Playlist::bump_name (_name, _session);
+               disk_samples_to_consume = nframes;
        }
 
        }
 
-       if ((playlist = boost::dynamic_pointer_cast<AudioPlaylist> (PlaylistFactory::create (DataType::AUDIO, _session, newname, hidden()))) != 0) {
 
 
-               return use_playlist (playlist);
+       if (!result_required || ((ms & MonitoringDisk) == 0) || still_locating || _no_disk_output) {
 
 
-       } else {
-               return -1;
-       }
-}
-
-int
-DiskReader::use_copy_playlist ()
-{
-       assert(audio_playlist());
+               /* no need for actual disk data, just advance read pointer and return */
 
 
-       if (_playlist == 0) {
-               error << string_compose(_("DiskReader %1: there is no existing playlist to make a copy of!"), _name) << endmsg;
-               return -1;
-       }
+               if (!still_locating || _no_disk_output) {
+                       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
+                               (*chan)->buf->increment_read_ptr (disk_samples_to_consume);
+                       }
+               }
 
 
-       string newname;
-       boost::shared_ptr<AudioPlaylist> playlist;
+               /* if monitoring disk but locating put silence in the buffers */
 
 
-       newname = Playlist::bump_name (_playlist->name(), _session);
+               if ((_no_disk_output || still_locating) && (ms == MonitoringDisk)) {
+                       bufs.silence (nframes, 0);
+               }
 
 
-       if ((playlist = boost::dynamic_pointer_cast<AudioPlaylist>(PlaylistFactory::create (audio_playlist(), newname))) != 0) {
-               playlist->reset_shares();
-               return use_playlist (playlist);
        } else {
        } else {
-               return -1;
-       }
-}
-
 
 
-/** Do some record stuff [not described in this comment!]
- *
- *  Also:
- *    - Setup playback_distance with the nframes, or nframes adjusted
- *      for current varispeed, if appropriate.
- *    - Setup current_buffer in each ChannelInfo to point to data
- *      that someone can read playback_distance worth of data from.
- */
-void
-DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame,
-                 double speed, pframes_t nframes, bool result_required)
-/*
-       int
-       DiskReader::process (BufferSet& bufs, framepos_t transport_frame, pframes_t nframes,
-       framecnt_t& playback_distance, bool need_disk_signal)
-*/
-{
-       uint32_t n;
-       boost::shared_ptr<ChannelList> c = channels.reader();
-       ChannelList::iterator chan;
-       framecnt_t playback_distance = 0;
+               /* we need audio data from disk */
 
 
-       Glib::Threads::Mutex::Lock sm (state_lock, Glib::Threads::TRY_LOCK);
+               size_t n_buffers = bufs.count().n_audio();
+               size_t n_chans = c->size();
+               gain_t scaling;
 
 
-       if (!sm.locked()) {
-               return;
-       }
+               if (n_chans > n_buffers) {
+                       scaling = ((float) n_buffers)/n_chans;
+               } else {
+                       scaling = 1.0;
+               }
 
 
-       for (chan = c->begin(); chan != c->end(); ++chan) {
-               (*chan)->current_buffer = 0;
-       }
+               for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
 
 
-       if (result_required || _monitoring_choice == MonitorDisk || _monitoring_choice == MonitorCue) {
+                       ChannelInfo* chaninfo (*chan);
+                       AudioBuffer& output (bufs.get_audio (n%n_buffers));
+                       Sample* disk_signal = 0; /* assignment not really needed but it keeps the compiler quiet and helps track bugs */
 
 
-               /* we're doing playback */
+                       if (ms & MonitoringInput) {
+                               /* put disk stream in scratch buffer, blend at end */
+                               disk_signal = scratch_bufs.get_audio(n).data ();
+                       } else {
+                               /* no input stream needed, just overwrite buffers */
+                               disk_signal = output.data ();
+                       }
 
 
-               framecnt_t necessary_samples;
+                       if (speed > 0) {
+                               if (start_sample < playback_sample) {
+                                       cerr << owner()->name() << " SS = " << start_sample << " PS = " << playback_sample << endl;
+                                       abort ();
+                               }
+                       } else if (speed < 0) {
+                               if (playback_sample < start_sample) {
+                                       cerr << owner()->name() << " SS = " << start_sample << " PS = " << playback_sample << " REVERSE" << endl;
+                                       abort ();
+                               }
+                       }
 
 
-               if (_actual_speed != 1.0) {
-                       necessary_samples = (framecnt_t) ceil ((nframes * fabs (_actual_speed))) + 2;
-               } else {
-                       necessary_samples = nframes;
-               }
+                       if ((speed > 0) && (start_sample != playback_sample)) {
+                               cerr << owner()->name() << " playback not aligned, jump ahead " << (start_sample - playback_sample) << endl;
 
 
-               for (chan = c->begin(); chan != c->end(); ++chan) {
-                       (*chan)->buf->get_read_vector (&(*chan)->read_vector);
-               }
+                               if (can_internal_playback_seek (start_sample - playback_sample)) {
+                                       internal_playback_seek (start_sample - playback_sample);
+                               } else {
+                                       cerr << owner()->name() << " playback not possible: ss = " << start_sample << " ps = " << playback_sample << endl;
+                                       goto midi;
+                               }
+                       }
 
 
-               n = 0;
+                       chaninfo->buf->get_read_vector (&(*chan)->rw_vector);
 
 
-               /* Setup current_buffer in each ChannelInfo to point to data that someone
-                  can read necessary_samples (== nframes at a transport speed of 1) worth of data
-                  from right now.
-               */
+                       if (disk_samples_to_consume <= (samplecnt_t) chaninfo->rw_vector.len[0]) {
 
 
-               for (chan = c->begin(); chan != c->end(); ++chan, ++n) {
+                               if (fabsf (speed) != 1.0f) {
+                                       samplecnt_t ocnt = nframes;
+                                       samplecnt_t icnt = chaninfo->rw_vector.len[0];
+                                       (void) interpolation.interpolate (n, icnt, chaninfo->rw_vector.buf[0], ocnt, disk_signal);
+                               } else if (speed != 0.0) {
+                                       memcpy (disk_signal, chaninfo->rw_vector.buf[0], sizeof (Sample) * disk_samples_to_consume);
+                               }
 
 
-                       ChannelInfo* chaninfo (*chan);
+                       } else {
 
 
-                       if (necessary_samples <= (framecnt_t) chaninfo->read_vector.len[0]) {
-                               /* There are enough samples in the first part of the ringbuffer */
-                               chaninfo->current_buffer = chaninfo->read_vector.buf[0];
+                               const samplecnt_t total = chaninfo->rw_vector.len[0] + chaninfo->rw_vector.len[1];
 
 
-                       } else {
-                               framecnt_t total = chaninfo->read_vector.len[0] + chaninfo->read_vector.len[1];
+                               if (disk_samples_to_consume <= total) {
 
 
-                               if (necessary_samples > total) {
-                                       cerr << _name << " Need " << necessary_samples << " total = " << total << endl;
-                                       cerr << "underrun for " << _name << endl;
-                                        DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 underrun in %2, total space = %3\n",
-                                                                                    DEBUG_THREAD_SELF, name(), total));
-                                       Underrun ();
-                                       return;
+                                       if (fabsf (speed) != 1.0f) {
+                                               samplecnt_t ocnt = nframes;
+                                               interpolation.interpolate (n, chaninfo->rw_vector.len[0], chaninfo->rw_vector.buf[0], ocnt, disk_signal);
 
 
-                               } else {
+                                               if (ocnt < nframes) {
+                                                       disk_signal += ocnt;
+                                                       ocnt = nframes - ocnt;
+                                                       interpolation.interpolate (n, chaninfo->rw_vector.len[1], chaninfo->rw_vector.buf[1], ocnt, disk_signal);
+                                               }
 
 
-                                       /* We have enough samples, but not in one lump.  Coalesce the two parts
-                                          into one in wrap_buffer in our ChannelInfo, and specify that
-                                          as our current_buffer.
-                                       */
+                                       } else if (speed != 0.0) {
 
 
-                                       assert(wrap_buffer_size >= necessary_samples);
+                                               memcpy (disk_signal,
+                                                       chaninfo->rw_vector.buf[0],
+                                                       chaninfo->rw_vector.len[0] * sizeof (Sample));
+                                               memcpy (disk_signal + chaninfo->rw_vector.len[0],
+                                                       chaninfo->rw_vector.buf[1],
+                                                       (disk_samples_to_consume - chaninfo->rw_vector.len[0]) * sizeof (Sample));
+                                       }
 
 
-                                       /* Copy buf[0] from buf */
-                                       memcpy ((char *) chaninfo->wrap_buffer,
-                                                       chaninfo->read_vector.buf[0],
-                                                       chaninfo->read_vector.len[0] * sizeof (Sample));
+                               } else {
 
 
-                                       /* Copy buf[1] from buf */
-                                       memcpy (chaninfo->wrap_buffer + chaninfo->read_vector.len[0],
-                                                       chaninfo->read_vector.buf[1],
-                                                       (necessary_samples - chaninfo->read_vector.len[0])
-                                                                       * sizeof (Sample));
+                                       cerr << _name << " Need " << disk_samples_to_consume << " total = " << total << endl;
+                                       cerr << "underrun for " << _name << endl;
+                                       DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 underrun in %2, total space = %3\n",
+                                                                                   DEBUG_THREAD_SELF, name(), total));
+                                       Underrun ();
+                                       return;
 
 
-                                       chaninfo->current_buffer = chaninfo->wrap_buffer;
                                }
                        }
                                }
                        }
-               }
 
 
-               if (_actual_speed != 1.0f && _actual_speed != -1.0f) {
+                       if (scaling != 1.0f && speed != 0.0) {
+                               apply_gain_to_buffer (disk_signal, disk_samples_to_consume, scaling);
+                       }
 
 
-                       interpolation.set_speed (_target_speed);
+                       chaninfo->buf->increment_read_ptr (disk_samples_to_consume);
 
 
-                       int channel = 0;
-                       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) {
-                               ChannelInfo* chaninfo (*chan);
+                       if (ms & MonitoringInput) {
+                               /* mix the disk signal into the input signal (already in bufs) */
+                               mix_buffers_no_gain (output.data(), disk_signal, disk_samples_to_consume);
+                       }
+               }
+       }
 
 
-                               playback_distance = interpolation.interpolate (
-                                       channel, nframes, chaninfo->current_buffer, chaninfo->speed_buffer);
+       /* MIDI data handling */
 
 
-                               chaninfo->current_buffer = chaninfo->speed_buffer;
-                       }
+  midi:
+       if (!_session.declick_out_pending() && bufs.count().n_midi()) {
+               MidiBuffer* dst;
 
 
+               if (_no_disk_output) {
+                       dst = &scratch_bufs.get_midi(0);
                } else {
                } else {
-                       playback_distance = nframes;
+                       dst = &bufs.get_midi (0);
                }
 
                }
 
-               _speed = _target_speed;
+               if ((ms & MonitoringDisk) && !still_locating) {
+                       get_midi_playback (*dst, start_sample, end_sample, ms, scratch_bufs, speed, disk_samples_to_consume);
+               }
        }
 
        }
 
-       if (result_required || _monitoring_choice == MonitorDisk || _monitoring_choice == MonitorCue) {
-
-               /* copy data over to buffer set */
+       if (!still_locating) {
 
 
-               size_t n_buffers = bufs.count().n_audio();
-               size_t n_chans = c->size();
-               gain_t scaling = 1.0f;
+               bool butler_required = false;
 
 
-               if (n_chans > n_buffers) {
-                       scaling = ((float) n_buffers)/n_chans;
+               if (speed < 0.0) {
+                       playback_sample -= disk_samples_to_consume;
+               } else {
+                       playback_sample += disk_samples_to_consume;
                }
 
                }
 
-               for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
-
-                       AudioBuffer& buf (bufs.get_audio (n%n_buffers));
-                       ChannelInfo* chaninfo (*chan);
-
-                       if (n < n_chans) {
-                               if (scaling != 1.0f) {
-                                       buf.read_from_with_gain (chaninfo->current_buffer, nframes, scaling);
-                               } else {
-                                       buf.read_from (chaninfo->current_buffer, nframes);
-                               }
-                       } else {
-                               if (scaling != 1.0f) {
-                                       buf.accumulate_with_gain_from (chaninfo->current_buffer, nframes, scaling);
+               if (_playlists[DataType::AUDIO]) {
+                       if (!c->empty()) {
+                               if (_slaved) {
+                                       if (c->front()->buf->write_space() >= c->front()->buf->bufsize() / 2) {
+                                               DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: slaved, write space = %2 of %3\n", name(), c->front()->buf->write_space(),
+                                                                                           c->front()->buf->bufsize()));
+                                               butler_required = true;
+                                       }
                                } else {
                                } else {
-                                       buf.accumulate_from (chaninfo->current_buffer, nframes);
+                                       if ((samplecnt_t) c->front()->buf->write_space() >= _chunk_samples) {
+                                               DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: write space = %2 of %3\n", name(), c->front()->buf->write_space(),
+                                                                                           _chunk_samples));
+                                               butler_required = true;
+                                       }
                                }
                        }
                }
 
                                }
                        }
                }
 
-               /* leave the MIDI count alone */
-               ChanCount cnt (DataType::AUDIO, n_chans);
-               cnt.set (DataType::MIDI, bufs.count().n_midi());
-               bufs.set_count (cnt);
+               if (_playlists[DataType::MIDI]) {
+                       /* MIDI butler needed part */
 
 
-               /* extra buffers will already be silent, so leave them alone */
-       }
-
-       bool need_butler = false;
+                       uint32_t samples_read = g_atomic_int_get(const_cast<gint*>(&_samples_read_from_ringbuffer));
+                       uint32_t samples_written = g_atomic_int_get(const_cast<gint*>(&_samples_written_to_ringbuffer));
 
 
-       if (_actual_speed < 0.0) {
-               playback_sample -= playback_distance;
-       } else {
-               playback_sample += playback_distance;
-       }
+                       /*
+                         cerr << name() << " MDS written: " << samples_written << " - read: " << samples_read <<
+                         " = " << samples_written - samples_read
+                         << " + " << disk_samples_to_consume << " < " << midi_readahead << " = " << need_butler << ")" << endl;
+                       */
 
 
-       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
-               (*chan)->buf->increment_read_ptr (playback_distance);
-       }
+                       /* samples_read will generally be less than samples_written, but
+                        * immediately after an overwrite, we can end up having read some data
+                        * before we've written any. we don't need to trip an assert() on this,
+                        * 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
+                        * â€˜samples_read’ (from buffer to output) will become larger
+                        * than â€˜samples_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 (samples_read - samples_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 (samples_read <= samples_written) {
+                               if ((samples_written - samples_read) + disk_samples_to_consume < midi_readahead) {
+                                       butler_required = true;
+                               }
+                       } else {
+                               butler_required = true;
+                       }
 
 
-       if (!c->empty()) {
-               if (_slaved) {
-                       need_butler = c->front()->buf->write_space() >= c->front()->buf->bufsize() / 2;
-               } else {
-                       need_butler = (framecnt_t) c->front()->buf->write_space() >= _chunk_frames;
                }
                }
-       }
-
-       //return need_butler;
-}
 
 
-frameoffset_t
-DiskReader::calculate_playback_distance (pframes_t nframes)
-{
-       frameoffset_t playback_distance = nframes;
-
-       if (_actual_speed != 1.0f && _actual_speed != -1.0f) {
-               interpolation.set_speed (_target_speed);
-               boost::shared_ptr<ChannelList> c = channels.reader();
-               int channel = 0;
-               for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) {
-                       playback_distance = interpolation.interpolate (channel, nframes, NULL, NULL);
-               }
-       } else {
-               playback_distance = nframes;
+               _need_butler = butler_required;
        }
 
        }
 
-       if (_actual_speed < 0.0) {
-               return -playback_distance;
-       } else {
-               return playback_distance;
-       }
+       // DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 reader run, needs butler = %2\n", name(), _need_butler));
 }
 
 void
 }
 
 void
@@ -670,7 +517,7 @@ DiskReader::set_pending_overwrite (bool yn)
 
        _pending_overwrite = yn;
 
 
        _pending_overwrite = yn;
 
-       overwrite_frame = playback_sample;
+       overwrite_sample = playback_sample;
 
        boost::shared_ptr<ChannelList> c = channels.reader ();
        if (!c->empty ()) {
 
        boost::shared_ptr<ChannelList> c = channels.reader ();
        if (!c->empty ()) {
@@ -681,106 +528,132 @@ DiskReader::set_pending_overwrite (bool yn)
 int
 DiskReader::overwrite_existing_buffers ()
 {
 int
 DiskReader::overwrite_existing_buffers ()
 {
-       boost::shared_ptr<ChannelList> c = channels.reader();
-       if (c->empty ()) {
-               _pending_overwrite = false;
-               return 0;
-       }
-
-       Sample* mixdown_buffer;
-       float* gain_buffer;
        int ret = -1;
        int ret = -1;
-       bool reversed = (_visible_speed * _session.transport_speed()) < 0.0f;
+
+       boost::shared_ptr<ChannelList> c = channels.reader();
 
        overwrite_queued = false;
 
 
        overwrite_queued = false;
 
-       /* assume all are the same size */
-       framecnt_t size = c->front()->buf->bufsize();
+       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1 overwriting existing buffers at %2\n", overwrite_sample));
 
 
-       mixdown_buffer = new Sample[size];
-       gain_buffer = new float[size];
+       if (!c->empty ()) {
 
 
-       /* reduce size so that we can fill the buffer correctly (ringbuffers
-          can only handle size-1, otherwise they appear to be empty)
-       */
-       size--;
+               /* AUDIO */
 
 
-       uint32_t n=0;
-       framepos_t start;
+               const bool reversed = _session.transport_speed() < 0.0f;
 
 
-       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++n) {
+               /* assume all are the same size */
+               samplecnt_t size = c->front()->buf->bufsize();
 
 
-               start = overwrite_frame;
-               framecnt_t cnt = size;
+               boost::scoped_ptr<Sample> mixdown_buffer (new Sample[size]);
+               boost::scoped_ptr<float> gain_buffer (new float[size]);
 
 
-               /* to fill the buffer without resetting the playback sample, we need to
-                  do it one or two chunks (normally two).
+               /* reduce size so that we can fill the buffer correctly (ringbuffers
+                  can only handle size-1, otherwise they appear to be empty)
+               */
+               size--;
 
 
-                  |----------------------------------------------------------------------|
+               uint32_t n=0;
+               samplepos_t start;
 
 
-                                      ^
-                                      overwrite_offset
-                   |<- second chunk->||<----------------- first chunk ------------------>|
+               for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++n) {
 
 
-               */
+                       start = overwrite_sample;
+                       samplecnt_t cnt = size;
 
 
-               framecnt_t to_read = size - overwrite_offset;
+                       /* to fill the buffer without resetting the playback sample, we need to
+                          do it one or two chunks (normally two).
 
 
-               if (read ((*chan)->buf->buffer() + overwrite_offset, mixdown_buffer, gain_buffer, start, to_read, n, reversed)) {
-                       error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at frame %3"),
-                                               id(), size, playback_sample) << endmsg;
-                       goto out;
-               }
+                          |----------------------------------------------------------------------|
+
+                          ^
+                          overwrite_offset
+                          |<- second chunk->||<----------------- first chunk ------------------>|
 
 
-               if (cnt > to_read) {
+                       */
 
 
-                       cnt -= to_read;
+                       samplecnt_t to_read = size - overwrite_offset;
 
 
-                       if (read ((*chan)->buf->buffer(), mixdown_buffer, gain_buffer, start, cnt, n, reversed)) {
-                               error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at frame %3"),
-                                                       id(), size, playback_sample) << endmsg;
-                               goto out;
+                       if (audio_read ((*chan)->buf->buffer() + overwrite_offset, mixdown_buffer.get(), gain_buffer.get(), start, to_read, n, reversed)) {
+                               error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"),
+                                                       id(), size, playback_sample) << endmsg;
+                               goto midi;
+                       }
+
+                       if (cnt > to_read) {
+
+                               cnt -= to_read;
+
+                               if (audio_read ((*chan)->buf->buffer(), mixdown_buffer.get(), gain_buffer.get(), start, cnt, n, reversed)) {
+                                       error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"),
+                                                               id(), size, playback_sample) << endmsg;
+                                       goto midi;
+                               }
                        }
                }
                        }
                }
+
+               ret = 0;
+
        }
 
        }
 
-       ret = 0;
+  midi:
 
 
-  out:
-       _pending_overwrite = false;
-       delete [] gain_buffer;
-       delete [] mixdown_buffer;
-       return ret;
-}
+       if (_midi_buf && _playlists[DataType::MIDI]) {
 
 
-void
-DiskReader::non_realtime_locate (framepos_t location)
-{
-       /* now refill channel buffers */
+               /* Clear the playback buffer contents.  This is safe as long as the butler
+                  thread is suspended, which it should be.
+               */
+               _midi_buf->reset ();
+               _midi_buf->reset_tracker ();
 
 
-       if (speed() != 1.0f || speed() != -1.0f) {
-               seek ((framepos_t) (location * (double) speed()), true);
-       } else {
-               seek (location, true);
+               g_atomic_int_set (&_samples_read_from_ringbuffer, 0);
+               g_atomic_int_set (&_samples_written_to_ringbuffer, 0);
+
+               /* Resolve all currently active notes in the playlist.  This is more
+                  aggressive than it needs to be: ideally we would only resolve what is
+                  absolutely necessary, but this seems difficult and/or impossible without
+                  having the old data or knowing what change caused the overwrite.
+               */
+               midi_playlist()->resolve_note_trackers (*_midi_buf, overwrite_sample);
+
+               midi_read (overwrite_sample, _chunk_samples, false);
+               file_sample[DataType::MIDI] = overwrite_sample; // overwrite_sample was adjusted by ::midi_read() to the new position
        }
        }
+
+       _pending_overwrite = false;
+
+       return ret;
 }
 
 int
 }
 
 int
-DiskReader::seek (framepos_t frame, bool complete_refill)
+DiskReader::seek (samplepos_t sample, bool complete_refill)
 {
        uint32_t n;
        int ret = -1;
        ChannelList::iterator chan;
        boost::shared_ptr<ChannelList> c = channels.reader();
 
 {
        uint32_t n;
        int ret = -1;
        ChannelList::iterator chan;
        boost::shared_ptr<ChannelList> c = channels.reader();
 
-       Glib::Threads::Mutex::Lock lm (state_lock);
+       //sample = std::max ((samplecnt_t)0, sample -_session.worst_output_latency ());
 
        for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
                (*chan)->buf->reset ();
        }
 
 
        for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
                (*chan)->buf->reset ();
        }
 
-       playback_sample = frame;
-       file_frame = frame;
+       if (g_atomic_int_get (&_samples_read_from_ringbuffer) == 0) {
+               /* we haven't read anything since the last seek,
+                  so flush all note trackers to prevent
+                  wierdness
+               */
+               reset_tracker ();
+       }
+
+       _midi_buf->reset();
+       g_atomic_int_set(&_samples_read_from_ringbuffer, 0);
+       g_atomic_int_set(&_samples_written_to_ringbuffer, 0);
+
+       playback_sample = sample;
+       file_sample[DataType::AUDIO] = sample;
+       file_sample[DataType::MIDI] = sample;
 
        if (complete_refill) {
                /* call _do_refill() to refill the entire buffer, using
 
        if (complete_refill) {
                /* call _do_refill() to refill the entire buffer, using
@@ -794,12 +667,15 @@ DiskReader::seek (framepos_t frame, bool complete_refill)
                ret = do_refill_with_alloc (true);
        }
 
                ret = do_refill_with_alloc (true);
        }
 
+
        return ret;
 }
 
 int
        return ret;
 }
 
 int
-DiskReader::can_internal_playback_seek (framecnt_t distance)
+DiskReader::can_internal_playback_seek (samplecnt_t distance)
 {
 {
+       /* 1. Audio */
+
        ChannelList::iterator chan;
        boost::shared_ptr<ChannelList> c = channels.reader();
 
        ChannelList::iterator chan;
        boost::shared_ptr<ChannelList> c = channels.reader();
 
@@ -808,11 +684,17 @@ DiskReader::can_internal_playback_seek (framecnt_t distance)
                        return false;
                }
        }
                        return false;
                }
        }
-       return true;
+
+       /* 2. MIDI */
+
+       uint32_t samples_read    = g_atomic_int_get(&_samples_read_from_ringbuffer);
+       uint32_t samples_written = g_atomic_int_get(&_samples_written_to_ringbuffer);
+
+       return ((samples_written - samples_read) < distance);
 }
 
 int
 }
 
 int
-DiskReader::internal_playback_seek (framecnt_t distance)
+DiskReader::internal_playback_seek (samplecnt_t distance)
 {
        ChannelList::iterator chan;
        boost::shared_ptr<ChannelList> c = channels.reader();
 {
        ChannelList::iterator chan;
        boost::shared_ptr<ChannelList> c = channels.reader();
@@ -838,28 +720,33 @@ void swap_by_ptr (Sample *first, Sample *last)
 
 /** Read some data for 1 channel from our playlist into a buffer.
  *  @param buf Buffer to write to.
 
 /** Read some data for 1 channel from our playlist into a buffer.
  *  @param buf Buffer to write to.
- *  @param start Session frame to start reading from; updated to where we end up
+ *  @param start Session sample to start reading from; updated to where we end up
  *         after the read.
  *  @param cnt Count of samples to read.
  *  @param reversed true if we are running backwards, otherwise false.
  */
 int
  *         after the read.
  *  @param cnt Count of samples to read.
  *  @param reversed true if we are running backwards, otherwise false.
  */
 int
-DiskReader::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
-                  framepos_t& start, framecnt_t cnt,
-                  int channel, bool reversed)
+DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
+                        samplepos_t& start, samplecnt_t cnt,
+                        int channel, bool reversed)
 {
 {
-       framecnt_t this_read = 0;
+       samplecnt_t this_read = 0;
        bool reloop = false;
        bool reloop = false;
-       framepos_t loop_end = 0;
-       framepos_t loop_start = 0;
-       framecnt_t offset = 0;
+       samplepos_t loop_end = 0;
+       samplepos_t loop_start = 0;
+       samplecnt_t offset = 0;
        Location *loc = 0;
 
        Location *loc = 0;
 
+       if (!_playlists[DataType::AUDIO]) {
+               memset (buf, 0, sizeof (Sample) * cnt);
+               return 0;
+       }
+
        /* XXX we don't currently play loops in reverse. not sure why */
 
        if (!reversed) {
 
        /* XXX we don't currently play loops in reverse. not sure why */
 
        if (!reversed) {
 
-               framecnt_t loop_length = 0;
+               samplecnt_t loop_length = 0;
 
                /* Make the use of a Location atomic for this read operation.
 
 
                /* Make the use of a Location atomic for this read operation.
 
@@ -869,13 +756,13 @@ DiskReader::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
                   just once.
                */
 
                   just once.
                */
 
-               if ((loc = loop_location) != 0) {
+               if ((loc = _loop_location) != 0) {
                        loop_start = loc->start();
                        loop_end = loc->end();
                        loop_length = loop_end - loop_start;
                }
 
                        loop_start = loc->start();
                        loop_end = loc->end();
                        loop_length = loop_end - loop_start;
                }
 
-               /* if we are looping, ensure that the first frame we read is at the correct
+               /* if we are looping, ensure that the first sample we read is at the correct
                   position within the loop.
                */
 
                   position within the loop.
                */
 
@@ -912,7 +799,7 @@ DiskReader::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
                this_read = min(cnt,this_read);
 
                if (audio_playlist()->read (buf+offset, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) {
                this_read = min(cnt,this_read);
 
                if (audio_playlist()->read (buf+offset, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) {
-                       error << string_compose(_("DiskReader %1: cannot read %2 from playlist at frame %3"), id(), this_read,
+                       error << string_compose(_("DiskReader %1: cannot read %2 from playlist at sample %3"), id(), this_read,
                                         start) << endmsg;
                        return -1;
                }
                                         start) << endmsg;
                        return -1;
                }
@@ -948,17 +835,33 @@ DiskReader::_do_refill_with_alloc (bool partial_fill)
           the smallest sample value .. 4MB = 2M samples (16 bit).
        */
 
           the smallest sample value .. 4MB = 2M samples (16 bit).
        */
 
-       Sample* mix_buf  = new Sample[2*1048576];
-       float*  gain_buf = new float[2*1048576];
+       {
+               boost::scoped_ptr<Sample> mix_buf (new Sample[2*1048576]);
+               boost::scoped_ptr<float>  gain_buf (new float[2*1048576]);
 
 
-       int ret = _do_refill (mix_buf, gain_buf, (partial_fill ? _chunk_frames : 0));
+               int ret = refill_audio (mix_buf.get(), gain_buf.get(), (partial_fill ? _chunk_samples : 0));
 
 
-       delete [] mix_buf;
-       delete [] gain_buf;
+               if (ret) {
+                       return ret;
+               }
+       }
 
 
-       return ret;
+       return refill_midi ();
 }
 
 }
 
+int
+DiskReader::refill (Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level)
+{
+       int ret = refill_audio (mixdown_buffer, gain_buffer, fill_level);
+
+       if (ret) {
+               return ret;
+       }
+
+       return refill_midi ();
+}
+
+
 /** Get some more data from disk and put it in our channels' bufs,
  *  if there is suitable space in them.
  *
 /** Get some more data from disk and put it in our channels' bufs,
  *  if there is suitable space in them.
  *
@@ -969,26 +872,26 @@ DiskReader::_do_refill_with_alloc (bool partial_fill)
  */
 
 int
  */
 
 int
-DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t fill_level)
+DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level)
 {
 {
-       if (_session.state_of_the_state() & Session::Loading) {
+       /* do not read from disk while session is marked as Loading, to avoid
+          useless redundant I/O.
+       */
+
+       if (_session.loading()) {
                return 0;
        }
 
        int32_t ret = 0;
                return 0;
        }
 
        int32_t ret = 0;
-       framecnt_t to_read;
+       samplecnt_t to_read;
        RingBufferNPT<Sample>::rw_vector vector;
        RingBufferNPT<Sample>::rw_vector vector;
-       bool const reversed = (_visible_speed * _session.transport_speed()) < 0.0f;
-       framecnt_t total_space;
-       framecnt_t zero_fill;
+       bool const reversed = _session.transport_speed() < 0.0f;
+       samplecnt_t total_space;
+       samplecnt_t zero_fill;
        uint32_t chan_n;
        ChannelList::iterator i;
        boost::shared_ptr<ChannelList> c = channels.reader();
        uint32_t chan_n;
        ChannelList::iterator i;
        boost::shared_ptr<ChannelList> c = channels.reader();
-       framecnt_t ts;
-
-       /* do not read from disk while session is marked as Loading, to avoid
-          useless redundant I/O.
-       */
+       samplecnt_t ts;
 
        if (c->empty()) {
                return 0;
 
        if (c->empty()) {
                return 0;
@@ -1005,6 +908,7 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
        c->front()->buf->get_write_vector (&vector);
 
        if ((total_space = vector.len[0] + vector.len[1]) == 0) {
        c->front()->buf->get_write_vector (&vector);
 
        if ((total_space = vector.len[0] + vector.len[1]) == 0) {
+               DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: no space to refill\n", name()));
                /* nowhere to write to */
                return 0;
        }
                /* nowhere to write to */
                return 0;
        }
@@ -1019,17 +923,18 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
        }
 
        /* if we're running close to normal speed and there isn't enough
        }
 
        /* if we're running close to normal speed and there isn't enough
-          space to do disk_read_chunk_frames of I/O, then don't bother.
+          space to do disk_read_chunk_samples of I/O, then don't bother.
 
           at higher speeds, just do it because the sync between butler
           and audio thread may not be good enough.
 
 
           at higher speeds, just do it because the sync between butler
           and audio thread may not be good enough.
 
-          Note: it is a design assumption that disk_read_chunk_frames is smaller
+          Note: it is a design assumption that disk_read_chunk_samples is smaller
           than the playback buffer size, so this check should never trip when
           the playback buffer is empty.
        */
 
           than the playback buffer size, so this check should never trip when
           the playback buffer is empty.
        */
 
-       if ((total_space < _chunk_frames) && fabs (_actual_speed) < 2.0f) {
+       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: space to refill %2 vs. chunk %3 (speed = %4)\n", name(), total_space, _chunk_samples, _session.transport_speed()));
+       if ((total_space < _chunk_samples) && fabs (_session.transport_speed()) < 2.0f) {
                return 0;
        }
 
                return 0;
        }
 
@@ -1038,13 +943,16 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
           work with.
        */
 
           work with.
        */
 
-       if (_slaved && total_space < (framecnt_t) (c->front()->buf->bufsize() / 2)) {
+       if (_slaved && total_space < (samplecnt_t) (c->front()->buf->bufsize() / 2)) {
+               DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: not enough to refill while slaved\n", this));
                return 0;
        }
 
                return 0;
        }
 
+       samplepos_t ffa = file_sample[DataType::AUDIO];
+
        if (reversed) {
 
        if (reversed) {
 
-               if (file_frame == 0) {
+               if (ffa == 0) {
 
                        /* at start: nothing to do but fill with silence */
 
 
                        /* at start: nothing to do but fill with silence */
 
@@ -1061,14 +969,14 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
                        return 0;
                }
 
                        return 0;
                }
 
-               if (file_frame < total_space) {
+               if (ffa < total_space) {
 
                        /* too close to the start: read what we can,
                           and then zero fill the rest
                        */
 
 
                        /* too close to the start: read what we can,
                           and then zero fill the rest
                        */
 
-                       zero_fill = total_space - file_frame;
-                       total_space = file_frame;
+                       zero_fill = total_space - ffa;
+                       total_space = ffa;
 
                } else {
 
 
                } else {
 
@@ -1077,7 +985,7 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
 
        } else {
 
 
        } else {
 
-               if (file_frame == max_framepos) {
+               if (ffa == max_samplepos) {
 
                        /* at end: nothing to do but fill with silence */
 
 
                        /* at end: nothing to do but fill with silence */
 
@@ -1094,19 +1002,19 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
                        return 0;
                }
 
                        return 0;
                }
 
-               if (file_frame > max_framepos - total_space) {
+               if (ffa > max_samplepos - total_space) {
 
                        /* to close to the end: read what we can, and zero fill the rest */
 
 
                        /* to close to the end: read what we can, and zero fill the rest */
 
-                       zero_fill = total_space - (max_framepos - file_frame);
-                       total_space = max_framepos - file_frame;
+                       zero_fill = total_space - (max_samplepos - ffa);
+                       total_space = max_samplepos - ffa;
 
                } else {
                        zero_fill = 0;
                }
        }
 
 
                } else {
                        zero_fill = 0;
                }
        }
 
-       framepos_t file_frame_tmp = 0;
+       samplepos_t file_sample_tmp = 0;
 
        /* total_space is in samples. We want to optimize read sizes in various sizes using bytes */
 
 
        /* total_space is in samples. We want to optimize read sizes in various sizes using bytes */
 
@@ -1123,11 +1031,9 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
 
        /* now back to samples */
 
 
        /* now back to samples */
 
-       framecnt_t samples_to_read = byte_size_for_read / (bits_per_sample / 8);
+       samplecnt_t samples_to_read = byte_size_for_read / (bits_per_sample / 8);
 
 
-       //cerr << name() << " will read " << byte_size_for_read << " out of total bytes " << total_bytes << " in buffer of "
-       // << c->front()->buf->bufsize() * bits_per_sample / 8 << " bps = " << bits_per_sample << endl;
-       // cerr << name () << " read samples = " << samples_to_read << " out of total space " << total_space << " in buffer of " << c->front()->buf->bufsize() << " samples\n";
+       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: will refill %2 channels with %3 samples\n", name(), c->size(), total_space));
 
        // uint64_t before = g_get_monotonic_time ();
        // uint64_t elapsed;
 
        // uint64_t before = g_get_monotonic_time ();
        // uint64_t elapsed;
@@ -1137,18 +1043,18 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
                ChannelInfo* chan (*i);
                Sample* buf1;
                Sample* buf2;
                ChannelInfo* chan (*i);
                Sample* buf1;
                Sample* buf2;
-               framecnt_t len1, len2;
+               samplecnt_t len1, len2;
 
                chan->buf->get_write_vector (&vector);
 
 
                chan->buf->get_write_vector (&vector);
 
-               if ((framecnt_t) vector.len[0] > samples_to_read) {
+               if ((samplecnt_t) vector.len[0] > samples_to_read) {
 
                        /* we're not going to fill the first chunk, so certainly do not bother with the
                           other part. it won't be connected with the part we do fill, as in:
 
                           .... => writable space
                           ++++ => readable space
 
                        /* we're not going to fill the first chunk, so certainly do not bother with the
                           other part. it won't be connected with the part we do fill, as in:
 
                           .... => writable space
                           ++++ => readable space
-                          ^^^^ => 1 x disk_read_chunk_frames that would be filled
+                          ^^^^ => 1 x disk_read_chunk_samples that would be filled
 
                           |......|+++++++++++++|...............................|
                           buf1                buf0
 
                           |......|+++++++++++++|...............................|
                           buf1                buf0
@@ -1165,7 +1071,7 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
                }
 
                ts = total_space;
                }
 
                ts = total_space;
-               file_frame_tmp = file_frame;
+               file_sample_tmp = ffa;
 
                buf1 = vector.buf[0];
                len1 = vector.len[0];
 
                buf1 = vector.buf[0];
                len1 = vector.len[0];
@@ -1173,17 +1079,16 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
                len2 = vector.len[1];
 
                to_read = min (ts, len1);
                len2 = vector.len[1];
 
                to_read = min (ts, len1);
-               to_read = min (to_read, (framecnt_t) samples_to_read);
+               to_read = min (to_read, (samplecnt_t) samples_to_read);
 
                assert (to_read >= 0);
 
                if (to_read) {
 
 
                assert (to_read >= 0);
 
                if (to_read) {
 
-                       if (read (buf1, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan_n, reversed)) {
+                       if (audio_read (buf1, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, chan_n, reversed)) {
                                ret = -1;
                                goto out;
                        }
                                ret = -1;
                                goto out;
                        }
-
                        chan->buf->increment_write_ptr (to_read);
                        ts -= to_read;
                }
                        chan->buf->increment_write_ptr (to_read);
                        ts -= to_read;
                }
@@ -1197,7 +1102,7 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
                           all of vector.len[1] as well.
                        */
 
                           all of vector.len[1] as well.
                        */
 
-                       if (read (buf2, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan_n, reversed)) {
+                       if (audio_read (buf2, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, chan_n, reversed)) {
                                ret = -1;
                                goto out;
                        }
                                ret = -1;
                                goto out;
                        }
@@ -1212,12 +1117,12 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
        }
 
        // elapsed = g_get_monotonic_time () - before;
        }
 
        // elapsed = g_get_monotonic_time () - before;
-       // cerr << "\tbandwidth = " << (byte_size_for_read / 1048576.0) / (elapsed/1000000.0) << "MB/sec\n";
+       // cerr << '\t' << name() << ": bandwidth = " << (byte_size_for_read / 1048576.0) / (elapsed/1000000.0) << "MB/sec\n";
 
 
-       file_frame = file_frame_tmp;
-       assert (file_frame >= 0);
+       file_sample[DataType::AUDIO] = file_sample_tmp;
+       assert (file_sample[DataType::AUDIO] >= 0);
 
 
-       ret = ((total_space - samples_to_read) > _chunk_frames);
+       ret = ((total_space - samples_to_read) > _chunk_samples);
 
        c->front()->buf->get_write_vector (&vector);
 
 
        c->front()->buf->get_write_vector (&vector);
 
@@ -1226,7 +1131,7 @@ DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t f
 }
 
 void
 }
 
 void
-DiskReader::playlist_ranges_moved (list< Evoral::RangeMove<framepos_t> > const & movements_frames, bool from_undo)
+DiskReader::playlist_ranges_moved (list< Evoral::RangeMove<samplepos_t> > const & movements_samples, bool from_undo)
 {
        /* If we're coming from an undo, it will have handled
           automation undo (it must, since automation-follows-regions
 {
        /* If we're coming from an undo, it will have handled
           automation undo (it must, since automation-follows-regions
@@ -1237,22 +1142,21 @@ DiskReader::playlist_ranges_moved (list< Evoral::RangeMove<framepos_t> > const &
                return;
        }
 
                return;
        }
 
-#if 0
-       if (!_track || Config->get_automation_follows_regions () == false) {
+       if (!_route || Config->get_automation_follows_regions () == false) {
                return;
        }
 
        list< Evoral::RangeMove<double> > movements;
 
                return;
        }
 
        list< Evoral::RangeMove<double> > movements;
 
-       for (list< Evoral::RangeMove<framepos_t> >::const_iterator i = movements_frames.begin();
-            i != movements_frames.end();
+       for (list< Evoral::RangeMove<samplepos_t> >::const_iterator i = movements_samples.begin();
+            i != movements_samples.end();
             ++i) {
 
                movements.push_back(Evoral::RangeMove<double>(i->from, i->length, i->to));
        }
 
        /* move panner automation */
             ++i) {
 
                movements.push_back(Evoral::RangeMove<double>(i->from, i->length, i->to));
        }
 
        /* move panner automation */
-       boost::shared_ptr<Pannable> pannable = _track->pannable();
+       boost::shared_ptr<Pannable> pannable = _route->pannable();
         Evoral::ControlSet::Controls& c (pannable->controls());
 
         for (Evoral::ControlSet::Controls::iterator ci = c.begin(); ci != c.end(); ++ci) {
         Evoral::ControlSet::Controls& c (pannable->controls());
 
         for (Evoral::ControlSet::Controls::iterator ci = c.begin(); ci != c.end(); ++ci) {
@@ -1272,12 +1176,11 @@ DiskReader::playlist_ranges_moved (list< Evoral::RangeMove<framepos_t> > const &
                 }
         }
        /* move processor automation */
                 }
         }
        /* move processor automation */
-       _track->foreach_processor (boost::bind (&Diskstream::move_processor_automation, this, _1, movements_frames));
-#endif
+        _route->foreach_processor (boost::bind (&DiskReader::move_processor_automation, this, _1, movements_samples));
 }
 
 void
 }
 
 void
-DiskReader::move_processor_automation (boost::weak_ptr<Processor> p, list< Evoral::RangeMove<framepos_t> > const & movements_frames)
+DiskReader::move_processor_automation (boost::weak_ptr<Processor> p, list< Evoral::RangeMove<samplepos_t> > const & movements_samples)
 {
        boost::shared_ptr<Processor> processor (p.lock ());
        if (!processor) {
 {
        boost::shared_ptr<Processor> processor (p.lock ());
        if (!processor) {
@@ -1285,7 +1188,7 @@ DiskReader::move_processor_automation (boost::weak_ptr<Processor> p, list< Evora
        }
 
        list< Evoral::RangeMove<double> > movements;
        }
 
        list< Evoral::RangeMove<double> > movements;
-       for (list< Evoral::RangeMove<framepos_t> >::const_iterator i = movements_frames.begin(); i != movements_frames.end(); ++i) {
+       for (list< Evoral::RangeMove<samplepos_t> >::const_iterator i = movements_samples.begin(); i != movements_samples.end(); ++i) {
                movements.push_back(Evoral::RangeMove<double>(i->from, i->length, i->to));
        }
 
                movements.push_back(Evoral::RangeMove<double>(i->from, i->length, i->to));
        }
 
@@ -1308,3 +1211,320 @@ DiskReader::move_processor_automation (boost::weak_ptr<Processor> p, list< Evora
        }
 }
 
        }
 }
 
+void
+DiskReader::reset_tracker ()
+{
+       _midi_buf->reset_tracker ();
+
+       boost::shared_ptr<MidiPlaylist> mp (midi_playlist());
+
+       if (mp) {
+               mp->reset_note_trackers ();
+       }
+}
+
+void
+DiskReader::resolve_tracker (Evoral::EventSink<samplepos_t>& buffer, samplepos_t time)
+{
+       _midi_buf->resolve_tracker(buffer, time);
+
+       boost::shared_ptr<MidiPlaylist> mp (midi_playlist());
+
+       if (mp) {
+               mp->reset_note_trackers ();
+       }
+}
+
+/** Writes playback events from playback_sample for nframes to dst, translating time stamps
+ *  so that an event at playback_sample has time = 0
+ */
+void
+DiskReader::get_midi_playback (MidiBuffer& dst, samplepos_t start_sample, samplepos_t end_sample, MonitorState ms, BufferSet& scratch_bufs, double speed, samplecnt_t disk_samples_to_consume)
+{
+       MidiBuffer* target;
+       samplepos_t nframes = end_sample - start_sample;
+
+       if ((ms & MonitoringInput) == 0) {
+               /* Route::process_output_buffers() clears the buffer as-needed */
+               target = &dst;
+       } else {
+               target = &scratch_bufs.get_midi (0);
+       }
+
+       if (ms & MonitoringDisk) {
+               /* disk data needed */
+
+               Location* loc = _loop_location;
+
+               DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose (
+                                    "%1 MDS pre-read read %8 offset = %9 @ %4..%5 from %2 write to %3, LOOPED ? %6 .. %7\n", _name,
+                                    _midi_buf->get_read_ptr(), _midi_buf->get_write_ptr(), start_sample, end_sample,
+                                    (loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes, Port::port_offset()));
+
+               //cerr << "======== PRE ========\n";
+               //_midi_buf->dump (cerr);
+               //cerr << "----------------\n";
+
+               size_t events_read = 0;
+
+               if (loc) {
+                       samplepos_t effective_start;
+
+                       Evoral::Range<samplepos_t> loop_range (loc->start(), loc->end() - 1);
+                       effective_start = loop_range.squish (start_sample);
+
+                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("looped, effective start adjusted to %1\n", effective_start));
+
+                       if (effective_start == loc->start()) {
+                               /* We need to turn off notes that may extend
+                                  beyond the loop end.
+                               */
+
+                               _midi_buf->resolve_tracker (*target, 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
+                               */
+
+                               samplecnt_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, 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 = _midi_buf->read (*target, effective_start, first);
+                               }
+
+                               if (second) {
+                                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #2, from %1 for %2\n",
+                                                                                             loc->start(), second));
+                                       events_read += _midi_buf->read (*target, 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 = _midi_buf->read (*target, effective_start, effective_start + nframes);
+                       }
+               } else {
+                       const size_t n_skipped = _midi_buf->skip_to (start_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)", start_sample, end_sample, nframes));
+                       events_read = _midi_buf->read (*target, start_sample, end_sample, Port::port_offset ());
+               }
+
+               DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose (
+                                    "%1 MDS events read %2 range %3 .. %4 rspace %5 wspace %6 r@%7 w@%8\n",
+                                    _name, events_read, playback_sample, playback_sample + nframes,
+                                    _midi_buf->read_space(), _midi_buf->write_space(),
+                                    _midi_buf->get_read_ptr(), _midi_buf->get_write_ptr()));
+       }
+
+       g_atomic_int_add (&_samples_read_from_ringbuffer, nframes);
+
+       /* vari-speed */
+       if (speed != 0.0 && fabsf (speed) != 1.0f) {
+               for (MidiBuffer::iterator i = target->begin(); i != target->end(); ++i) {
+                       MidiBuffer::TimeType *tme = i.timeptr();
+                       // XXX need to subtract port offsets before scaling
+                       // also we must only scale events read from disk
+                       // and not existing input data in the buffer.
+                       *tme = (*tme) * nframes / disk_samples_to_consume;
+               }
+       }
+
+       if (ms & MonitoringInput) {
+               dst.merge_from (*target, nframes);
+       }
+
+#if 0
+       if (!target->empty ()) {
+               cerr << "======== MIDI OUT ========\n";
+               for (MidiBuffer::iterator i = target->begin(); i != target->end(); ++i) {
+                       const Evoral::Event<MidiBuffer::TimeType> ev (*i, false);
+                       cerr << "MIDI EVENT (from disk) @ " << ev.time();
+                       for (size_t xx = 0; xx < ev.size(); ++xx) {
+                               cerr << ' ' << hex << (int) ev.buffer()[xx];
+                       }
+                       cerr << dec << endl;
+               }
+               cerr << "----------------\n";
+       }
+#endif
+#if 0
+       cerr << "======== MIDI Disk Buffer ========\n";
+       _midi_buf->dump (cerr);
+       cerr << "----------------\n";
+#endif
+}
+
+/** @a start is set to the new sample position (TIME) read up to */
+int
+DiskReader::midi_read (samplepos_t& start, samplecnt_t dur, bool reversed)
+{
+       samplecnt_t this_read   = 0;
+       samplepos_t loop_end    = 0;
+       samplepos_t loop_start  = 0;
+       samplecnt_t loop_length = 0;
+       Location*  loc         = _loop_location;
+       samplepos_t effective_start = start;
+       Evoral::Range<samplepos_t>*  loop_range (0);
+
+       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS::midi_read @ %1 cnt %2\n", start, dur));
+
+       boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack>(_route);
+       MidiChannelFilter* filter = mt ? &mt->playback_filter() : 0;
+       sampleoffset_t loop_offset = 0;
+
+       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 && !reversed) {
+
+                       if (!loop_range) {
+                               loop_range = new Evoral::Range<samplepos_t> (loop_start, loop_end-1); // inclusive semantics require -1
+                       }
+
+                       /* if we are (seamlessly) looping, ensure that the first sample 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 {
+                       this_read = dur;
+               }
+
+               if (this_read == 0) {
+                       break;
+               }
+
+               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 (*_midi_buf, effective_start, this_read, loop_range, 0, filter) != this_read) {
+                       error << string_compose(
+                                       _("MidiDiskstream %1: cannot read %2 from playlist at sample %3"),
+                                       id(), this_read, start) << endmsg;
+                       return -1;
+               }
+
+               g_atomic_int_add (&_samples_written_to_ringbuffer, this_read);
+
+               if (reversed) {
+
+                       // Swap note ons with note offs here.  etc?
+                       // Fully reversing MIDI requires look-ahead (well, behind) to find previous
+                       // CC values etc.  hard.
+
+               } else {
+
+                       /* 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;
+       }
+
+       return 0;
+}
+
+int
+DiskReader::refill_midi ()
+{
+       if (!_playlists[DataType::MIDI]) {
+               return 0;
+       }
+
+       const size_t  write_space = _midi_buf->write_space();
+       const bool reversed    = _session.transport_speed() < 0.0f;
+
+       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("MIDI refill, write space = %1 file sample = %2\n", write_space, file_sample[DataType::MIDI]));
+
+       /* no space to write */
+       if (write_space == 0) {
+               return 0;
+       }
+
+       if (reversed) {
+               return 0;
+       }
+
+       /* at end: nothing to do */
+
+       samplepos_t ffm = file_sample[DataType::MIDI];
+
+       if (ffm == max_samplepos) {
+               return 0;
+       }
+
+       int ret = 0;
+       const uint32_t samples_read = g_atomic_int_get (&_samples_read_from_ringbuffer);
+       const uint32_t samples_written = g_atomic_int_get (&_samples_written_to_ringbuffer);
+
+       if ((samples_read < samples_written) && (samples_written - samples_read) >= midi_readahead) {
+               return 0;
+       }
+
+       samplecnt_t to_read = midi_readahead - ((samplecnt_t)samples_written - (samplecnt_t)samples_read);
+
+       to_read = min (to_read, (samplecnt_t) (max_samplepos - ffm));
+       to_read = min (to_read, (samplecnt_t) write_space);
+
+       if (midi_read (ffm, to_read, reversed)) {
+               ret = -1;
+       }
+
+       file_sample[DataType::MIDI] = ffm;
+
+       return ret;
+}
+
+void
+DiskReader::set_no_disk_output (bool yn)
+{
+       /* this MUST be called as part of the process call tree, before any
+          disk readers are invoked. We use it when the session needs the
+          transport (and thus effective read position for DiskReaders) to keep
+          advancing as part of syncing up with a transport master, but we
+          don't want any actual disk output yet because we are still not
+          synced.
+       */
+       _no_disk_output = yn;
+}