Initial changes needed for building Mixbus (with MSVC) as version 5
[ardour.git] / libs / ardour / disk_reader.cc
index 10cf428c822c202b821abfc36ca8ae10b1477c42..375dda70759f0f4345e193223d07eb67b0c04fb5 100644 (file)
@@ -17,7 +17,9 @@
 
 */
 
 
 */
 
-#include "pbd/i18n.h"
+#include <boost/smart_ptr/scoped_array.hpp>
+
+#include "pbd/enumwriter.h"
 #include "pbd/memento_command.h"
 
 #include "ardour/audioengine.h"
 #include "pbd/memento_command.h"
 
 #include "ardour/audioengine.h"
 #include "ardour/disk_reader.h"
 #include "ardour/midi_ring_buffer.h"
 #include "ardour/midi_playlist.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)
-       , loop_location (0)
-       , overwrite_frame (0)
-        , overwrite_offset (0)
-        , _pending_overwrite (false)
-        , overwrite_queued (false)
-        , file_frame (0)
-        , playback_sample (0)
-       , _monitoring_choice (MonitorDisk)
-       , _need_butler (false)
-       , _gui_feed_buffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI))
-       , _frames_written_to_ringbuffer (0)
-       , _frames_read_from_ringbuffer (0)
+       , overwrite_sample (0)
+       , overwrite_offset (0)
+       , _pending_overwrite (false)
+       , overwrite_queued (false)
+       , _declick_gain (0)
 {
 {
+       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));
-       Glib::Threads::Mutex::Lock lm (state_lock);
+       DEBUG_TRACE (DEBUG::Destruction, string_compose ("DiskReader %1 @ %2 deleted\n", _name, this));
 
        for (uint32_t n = 0; n < DataType::num_types; ++n) {
                if (_playlists[n]) {
                        _playlists[n]->release ();
                }
        }
 
        for (uint32_t n = 0; n < DataType::num_types; ++n) {
                if (_playlists[n]) {
                        _playlists[n]->release ();
                }
        }
+       delete _midi_buf;
+}
 
 
-       {
-               RCUWriter<ChannelList> writer (channels);
-               boost::shared_ptr<ChannelList> c = writer.get_copy();
-
-               for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
-                       delete *chan;
-               }
+void
+DiskReader::ReaderChannelInfo::resize (samplecnt_t bufsize)
+{
+       delete rbuf;
+       /* touch memory to lock it */
+       rbuf = new RingBufferNPT<Sample> (bufsize);
+       memset (rbuf->buffer(), 0, sizeof (Sample) * rbuf->bufsize());
+}
 
 
-               c->clear();
+int
+DiskReader::add_channel_to (boost::shared_ptr<ChannelList> c, uint32_t how_many)
+{
+       while (how_many--) {
+               c->push_back (new ReaderChannelInfo (_session.butler()->audio_diskstream_playback_buffer_size()));
+               DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: new reader channel, write space = %2 read = %3\n",
+                                                           name(),
+                                                           c->back()->rbuf->write_space(),
+                                                           c->back()->rbuf->read_space()));
        }
 
        }
 
-       channels.flush ();
-
-       delete _midi_buf;
+       return 0;
 }
 
 void
 }
 
 void
@@ -105,8 +118,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;
 }
@@ -114,97 +127,34 @@ DiskReader::default_chunk_frames()
 bool
 DiskReader::set_name (string const & str)
 {
 bool
 DiskReader::set_name (string const & str)
 {
-       if (_name != str) {
-               for (uint32_t n = 0; n < DataType::num_types; ++n) {
-                       if (_playlists[n]) {
-                               _playlists[n]->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 ("audio-playlist")) == 0) {
-               return -1;
-       }
-
-       if (find_and_use_playlist (DataType::AUDIO, prop->value())) {
-               return -1;
-       }
-
-       if ((prop = node.property ("midi-playlist")) == 0) {
-               return -1;
-       }
-
-       if (find_and_use_playlist (DataType::MIDI, prop->value())) {
-               return -1;
-       }
-
        return 0;
 }
 
        return 0;
 }
 
-/* Processor interface */
-
-bool
-DiskReader::configure_io (ChanCount in, ChanCount out)
-{
-       Glib::Threads::Mutex::Lock lm (state_lock);
-
-       RCUWriter<ChannelList> writer (channels);
-       boost::shared_ptr<ChannelList> c = writer.get_copy();
-
-       uint32_t n_audio = in.n_audio();
-
-       if (n_audio > c->size()) {
-               add_channel_to (c, n_audio - c->size());
-       } else if (n_audio < c->size()) {
-               remove_channel_from (c, c->size() - n_audio);
-       }
-
-       if (in.n_midi() > 0 && !_midi_buf) {
-               const size_t size = _session.butler()->midi_diskstream_buffer_size();
-               _midi_buf = new MidiRingBuffer<framepos_t>(size);
-               midi_interpolation.add_channel_to (0,0);
-       }
-
-       Processor::configure_io (in, out);
-
-       return true;
-}
-
-bool
-DiskReader::can_support_io_configuration (const ChanCount& in, ChanCount& out)
-{
-       if (in.n_midi() != 0 && in.n_midi() != 1) {
-               /* we only support zero or 1 MIDI stream */
-               return false;
-       }
-
-       if (in != out) {
-               /* currently no way to deliver different channels that we receive */
-               return false;
-       }
-
-       return true;
-}
-
 void
 DiskReader::realtime_handle_transport_stopped ()
 {
 void
 DiskReader::realtime_handle_transport_stopped ()
 {
@@ -215,22 +165,6 @@ DiskReader::realtime_locate ()
 {
 }
 
 {
 }
 
-int
-DiskReader::set_loop (Location *location)
-{
-       if (location) {
-               if (location->start() >= location->end()) {
-                       error << string_compose(_("Location \"%1\" not valid for track loop (start >= end)"), location->name()) << endl;
-                       return -1;
-               }
-       }
-
-       loop_location = location;
-
-       LoopSet (location); /* EMIT SIGNAL */
-       return 0;
-}
-
 float
 DiskReader::buffer_load () const
 {
 float
 DiskReader::buffer_load () const
 {
@@ -251,7 +185,7 @@ DiskReader::buffer_load () const
                return 1.0;
        }
 
                return 1.0;
        }
 
-       PBD::RingBufferNPT<Sample> * b = c->front()->buf;
+       PBD::RingBufferNPT<Sample>* b = c->front()->rbuf;
        return (float) ((double) b->read_space() / (double) b->bufsize());
 }
 
        return (float) ((double) b->read_space() / (double) b->bufsize());
 }
 
@@ -265,87 +199,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&)
 {
@@ -356,75 +209,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) {
-               return;
-       }
-
-       for (uint32_t n = 0; n < DataType::num_types; ++n) {
-               if (pl == _playlists[n]) {
-
-                       /* 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.
-                       */
-                       _playlists[n].reset ();
-                       break;
-               }
-       }
-}
-
-boost::shared_ptr<AudioPlaylist>
-DiskReader::audio_playlist () const
-{
-       return boost::dynamic_pointer_cast<AudioPlaylist> (_playlists[DataType::AUDIO]);
-}
-
-boost::shared_ptr<MidiPlaylist>
-DiskReader::midi_playlist () const
-{
-       return boost::dynamic_pointer_cast<MidiPlaylist> (_playlists[DataType::MIDI]);
-}
-
 int
 DiskReader::use_playlist (DataType dt, boost::shared_ptr<Playlist> playlist)
 {
 int
 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 == _playlists[dt]) {
-                       return 0;
-               }
-
-               playlist_connections.drop_connections ();
-
-               if (_playlists[dt]) {
-                       _playlists[dt]->release();
-                        prior_playlist = true;
-               }
-
-               _playlists[dt] = 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
@@ -432,366 +232,275 @@ DiskReader::use_playlist (DataType dt, 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 (dt); /* EMIT SIGNAL */
-       _session.set_dirty ();
-
        return 0;
 }
 
        return 0;
 }
 
-int
-DiskReader::find_and_use_playlist (DataType dt, const string& name)
-{
-       boost::shared_ptr<Playlist> playlist;
-
-       if ((playlist = _session.playlists->by_name (name)) == 0) {
-               playlist = PlaylistFactory::create (dt, _session, name);
-       }
-
-       if (!playlist) {
-               error << string_compose(_("DiskReader: \"%1\" isn't an playlist"), name) << endmsg;
-               return -1;
-       }
-
-       return use_playlist (dt, playlist);
-}
-
-int
-DiskReader::use_new_playlist (DataType dt)
+void
+DiskReader::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample,
+                 double speed, pframes_t nframes, bool result_required)
 {
 {
-       string newname;
-       boost::shared_ptr<Playlist> playlist = _playlists[dt];
+       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) {
-               newname = Playlist::bump_name (playlist->name(), _session);
+       if (_active) {
+               if (!_pending_active) {
+                       _active = false;
+                       return;
+               }
        } else {
        } else {
-               newname = Playlist::bump_name (_name, _session);
+               if (_pending_active) {
+                       _active = true;
+               } else {
+                       return;
+               }
        }
 
        }
 
-       playlist = boost::dynamic_pointer_cast<AudioPlaylist> (PlaylistFactory::create (dt, _session, newname, hidden()));
-
-       if (!playlist) {
-               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 (dt, playlist);
-}
+       BufferSet& scratch_bufs (_session.get_scratch_buffers (bufs.count()));
+       const bool still_locating = _session.global_locate_pending();
 
 
-int
-DiskReader::use_copy_playlist (DataType dt)
-{
-       assert (_playlists[dt]);
-
-       if (_playlists[dt] == 0) {
-               error << string_compose(_("DiskReader %1: there is no existing playlist to make a copy of!"), _name) << endmsg;
-               return -1;
+       if (c->empty()) {
+               /* do nothing with audio */
+               goto midi;
        }
 
        }
 
-       string newname;
-       boost::shared_ptr<Playlist> playlist;
-
-       newname = Playlist::bump_name (_playlists[dt]->name(), _session);
+       assert (speed == -1 || speed == 0 || speed == 1);
 
 
-       if ((playlist = PlaylistFactory::create (_playlists[dt], newname)) == 0) {
-               return -1;
+       if (speed < 0) {
+               disk_samples_to_consume = -nframes;
+       } else if (speed > 0) {
+               disk_samples_to_consume = nframes;
+       } else {
+               disk_samples_to_consume = 0;
        }
 
        }
 
-       playlist->reset_shares();
-
-       return use_playlist (dt, playlist);
-}
+       if (!result_required || ((ms & MonitoringDisk) == 0) || still_locating || _no_disk_output) {
 
 
+               /* no need for actual disk data, just advance read pointer and return */
 
 
-/** 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)
-{
-       uint32_t n;
-       boost::shared_ptr<ChannelList> c = channels.reader();
-       ChannelList::iterator chan;
-       framecnt_t playback_distance = 0;
-
-       Glib::Threads::Mutex::Lock sm (state_lock, Glib::Threads::TRY_LOCK);
-
-       if (!sm.locked()) {
-               return;
-       }
+               if (!still_locating || _no_disk_output) {
+                       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
+                               (*chan)->rbuf->increment_read_ptr (disk_samples_to_consume);
+                       }
+               }
 
 
-       for (chan = c->begin(); chan != c->end(); ++chan) {
-               (*chan)->current_buffer = 0;
-       }
+               /* if monitoring disk but locating put silence in the buffers */
 
 
-       const bool need_disk_signal = result_required || _monitoring_choice == MonitorDisk || _monitoring_choice == MonitorCue;
+               if ((_no_disk_output || still_locating) && (ms == MonitoringDisk)) {
+                       bufs.silence (nframes, 0);
+               }
 
 
-       if (need_disk_signal) {
+       } else {
 
 
-               /* we're doing playback */
+               /* we need audio data from disk */
 
 
-               framecnt_t necessary_samples;
+               size_t n_buffers = bufs.count().n_audio();
+               size_t n_chans = c->size();
+               gain_t scaling;
 
 
-               if (_actual_speed != 1.0) {
-                       necessary_samples = (framecnt_t) ceil ((nframes * fabs (_actual_speed))) + 2;
+               if (n_chans > n_buffers) {
+                       scaling = ((float) n_buffers)/n_chans;
                } else {
                } else {
-                       necessary_samples = nframes;
+                       scaling = 1.0;
                }
 
                }
 
-               for (chan = c->begin(); chan != c->end(); ++chan) {
-                       (*chan)->buf->get_read_vector (&(*chan)->read_vector);
-               }
-
-               n = 0;
-
-               /* 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.
-               */
-
-               for (chan = c->begin(); chan != c->end(); ++chan, ++n) {
+               for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
 
                        ChannelInfo* chaninfo (*chan);
 
                        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 */
 
 
-                       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];
-
+                       if (ms & MonitoringInput) {
+                               /* put disk stream in scratch buffer, blend at end */
+                               disk_signal = scratch_bufs.get_audio(n).data ();
                        } else {
                        } else {
-                               framecnt_t total = chaninfo->read_vector.len[0] + chaninfo->read_vector.len[1];
-
-                               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;
-
-                               } else {
-
-                                       /* 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.
-                                       */
-
-                                       assert(wrap_buffer_size >= necessary_samples);
-
-                                       /* Copy buf[0] from buf */
-                                       memcpy ((char *) chaninfo->wrap_buffer,
-                                                       chaninfo->read_vector.buf[0],
-                                                       chaninfo->read_vector.len[0] * sizeof (Sample));
-
-                                       /* 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));
+                               /* no input stream needed, just overwrite buffers */
+                               disk_signal = output.data ();
+                       }
 
 
-                                       chaninfo->current_buffer = chaninfo->wrap_buffer;
+                       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.0f && _actual_speed != -1.0f) {
-
-                       interpolation.set_speed (_target_speed);
 
 
-                       int channel = 0;
-                       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) {
-                               ChannelInfo* chaninfo (*chan);
+                       if ((speed > 0) && (start_sample != playback_sample)) {
+                               cerr << owner()->name() << " playback not aligned, jump ahead " << (start_sample - playback_sample) << endl;
 
 
-                               playback_distance = interpolation.interpolate (
-                                       channel, nframes, chaninfo->current_buffer, chaninfo->speed_buffer);
-
-                               chaninfo->current_buffer = chaninfo->speed_buffer;
+                               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;
+                               }
                        }
 
                        }
 
-               } else {
-                       playback_distance = nframes;
-               }
-
-               _speed = _target_speed;
-       }
+                       chaninfo->rbuf->get_read_vector (&(*chan)->rw_vector);
 
 
-       if (need_disk_signal) {
+                       if (disk_samples_to_consume <= (samplecnt_t) chaninfo->rw_vector.len[0]) {
 
 
-               /* copy data over to buffer set */
+                               if (speed != 0.0) {
+                                       memcpy (disk_signal, chaninfo->rw_vector.buf[0], sizeof (Sample) * disk_samples_to_consume);
+                               }
 
 
-               size_t n_buffers = bufs.count().n_audio();
-               size_t n_chans = c->size();
-               gain_t scaling = 1.0f;
+                       } else {
 
 
-               if (n_chans > n_buffers) {
-                       scaling = ((float) n_buffers)/n_chans;
-               }
+                               const samplecnt_t total = chaninfo->rw_vector.len[0] + chaninfo->rw_vector.len[1];
 
 
-               for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
+                               if (disk_samples_to_consume <= total) {
 
 
-                       AudioBuffer& buf (bufs.get_audio (n%n_buffers));
-                       ChannelInfo* chaninfo (*chan);
+                                               if (speed != 0.0) {
+                                                       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));
+                                       }
 
 
-                       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);
                                } else {
                                } else {
-                                       buf.accumulate_from (chaninfo->current_buffer, nframes);
-                               }
-                       }
-               }
 
 
-               /* extra buffers will already be silent, so leave them alone */
-       }
+                                       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;
 
 
-       _need_butler = false;
+                               }
+                       }
 
 
-       if (_actual_speed < 0.0) {
-               playback_sample -= playback_distance;
-       } else {
-               playback_sample += playback_distance;
-       }
+                       if (scaling != 1.0f && speed != 0.0) {
+                               apply_gain_to_buffer (disk_signal, disk_samples_to_consume, scaling);
+                       }
 
 
-       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
-               (*chan)->buf->increment_read_ptr (playback_distance);
-       }
+                       chaninfo->rbuf->increment_read_ptr (disk_samples_to_consume);
 
 
-       if (!c->empty()) {
-               if (_slaved) {
-                       if (c->front()->buf->write_space() >= c->front()->buf->bufsize() / 2) {
-                               _need_butler = true;
-                       }
-               } else {
-                       if ((framecnt_t) c->front()->buf->write_space() >= _chunk_frames) {
-                               _need_butler = true;
+                       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);
                        }
                }
        }
 
        /* MIDI data handling */
 
                        }
                }
        }
 
        /* MIDI data handling */
 
-       if (_actual_speed != 1.0f && _target_speed > 0) {
-
-               interpolation.set_speed (_target_speed);
+  midi:
+       if (/*!_session.declick_out_pending() && */ bufs.count().n_midi()) {
+               MidiBuffer* dst;
 
 
-               playback_distance = midi_interpolation.distance  (nframes);
+               if (_no_disk_output) {
+                       dst = &scratch_bufs.get_midi(0);
+               } else {
+                       dst = &bufs.get_midi (0);
+               }
 
 
-       } else {
-               playback_distance = nframes;
+               if ((ms & MonitoringDisk) && !still_locating) {
+                       get_midi_playback (*dst, start_sample, end_sample, ms, scratch_bufs, speed, disk_samples_to_consume);
+               }
        }
 
        }
 
-       if (need_disk_signal && !_session.declick_out_pending()) {
-
-               /* copy the diskstream data to all output buffers */
+       if (!still_locating) {
 
 
-               MidiBuffer& mbuf (bufs.get_midi (0));
-               get_playback (mbuf, playback_distance);
+               bool butler_required = false;
 
 
-               /* leave the audio count alone */
-               ChanCount cnt (DataType::MIDI, 1);
-               cnt.set (DataType::AUDIO, bufs.count().n_audio());
-               bufs.set_count (cnt);
+               if (speed < 0.0) {
+                       playback_sample -= disk_samples_to_consume;
+               } else {
+                       playback_sample += disk_samples_to_consume;
+               }
 
 
-               /* 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;
+               if (_playlists[DataType::AUDIO]) {
+                       if (!c->empty()) {
+                               if (_slaved) {
+                                       if (c->front()->rbuf->write_space() >= c->front()->rbuf->bufsize() / 2) {
+                                               DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: slaved, write space = %2 of %3\n", name(), c->front()->rbuf->write_space(), c->front()->rbuf->bufsize()));
+                                               butler_required = true;
+                                       }
+                               } else {
+                                       if ((samplecnt_t) c->front()->rbuf->write_space() >= _chunk_samples) {
+                                               DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: write space = %2 of %3\n", name(), c->front()->rbuf->write_space(),
+                                                                                           _chunk_samples));
+                                               butler_required = true;
+                                       }
+                               }
                        }
                }
                        }
                }
-       }
 
 
-       /* MIDI butler needed part */
+               if (_playlists[DataType::MIDI]) {
+                       /* MIDI butler needed part */
 
 
-       uint32_t frames_read = g_atomic_int_get(const_cast<gint*>(&_frames_read_from_ringbuffer));
-       uint32_t frames_written = g_atomic_int_get(const_cast<gint*>(&_frames_written_to_ringbuffer));
+                       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));
 
 
-       /*
-         cerr << name() << " MDS written: " << frames_written << " - read: " << frames_read <<
-         " = " << frames_written - frames_read
-         << " + " << playback_distance << " < " << midi_readahead << " = " << need_butler << ")" << endl;
-       */
+                       /*
+                         cerr << name() << " MDS written: " << samples_written << " - read: " << samples_read <<
+                         " = " << samples_written - samples_read
+                         << " + " << disk_samples_to_consume << " < " << midi_readahead << " = " << need_butler << ")" << endl;
+                       */
 
 
-       /* frames_read will generally be less than frames_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.
-        */
+                       /* 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;
+                       }
 
 
-       /* 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;
-       }
-
-       /* make sure bufs shows whatever data we had available */
 
 
-       ChanCount cnt;
-       cnt.set (DataType::MIDI, 1);
-       cnt.set (DataType::AUDIO, bufs.count().n_audio());
-       bufs.set_count (cnt);
-}
-
-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
@@ -801,11 +510,11 @@ 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 ()) {
-               overwrite_offset = c->front()->buf->get_read_ptr();
+               overwrite_offset = c->front()->rbuf->get_read_ptr();
        }
 }
 
        }
 }
 
@@ -818,17 +527,19 @@ DiskReader::overwrite_existing_buffers ()
 
        overwrite_queued = false;
 
 
        overwrite_queued = false;
 
+       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1 overwriting existing buffers at %2\n", overwrite_sample));
+
        if (!c->empty ()) {
 
                /* AUDIO */
 
        if (!c->empty ()) {
 
                /* AUDIO */
 
-               bool reversed = (_visible_speed * _session.transport_speed()) < 0.0f;
+               const bool reversed = _session.transport_speed() < 0.0f;
 
                /* assume all are the same size */
 
                /* assume all are the same size */
-               framecnt_t size = c->front()->buf->bufsize();
+               samplecnt_t size = c->front()->rbuf->bufsize();
 
 
-               std::auto_ptr<Sample> mixdown_buffer (new Sample[size]);
-               std::auto_ptr<float> gain_buffer (new float[size]);
+               boost::scoped_array<Sample> mixdown_buffer (new Sample[size]);
+               boost::scoped_array<float> gain_buffer (new float[size]);
 
                /* reduce size so that we can fill the buffer correctly (ringbuffers
                   can only handle size-1, otherwise they appear to be empty)
 
                /* reduce size so that we can fill the buffer correctly (ringbuffers
                   can only handle size-1, otherwise they appear to be empty)
@@ -836,12 +547,12 @@ DiskReader::overwrite_existing_buffers ()
                size--;
 
                uint32_t n=0;
                size--;
 
                uint32_t n=0;
-               framepos_t start;
+               samplepos_t start;
 
                for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++n) {
 
 
                for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++n) {
 
-                       start = overwrite_frame;
-                       framecnt_t cnt = size;
+                       start = overwrite_sample;
+                       samplecnt_t cnt = size;
 
                        /* to fill the buffer without resetting the playback sample, we need to
                           do it one or two chunks (normally two).
 
                        /* to fill the buffer without resetting the playback sample, we need to
                           do it one or two chunks (normally two).
@@ -854,10 +565,10 @@ DiskReader::overwrite_existing_buffers ()
 
                        */
 
 
                        */
 
-                       framecnt_t to_read = size - overwrite_offset;
+                       samplecnt_t to_read = size - overwrite_offset;
 
 
-                       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 frame %3"),
+                       if (audio_read ((*chan)->rbuf->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;
                        }
                                                        id(), size, playback_sample) << endmsg;
                                goto midi;
                        }
@@ -866,8 +577,8 @@ DiskReader::overwrite_existing_buffers ()
 
                                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 frame %3"),
+                               if (audio_read ((*chan)->rbuf->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;
                                }
                                                                id(), size, playback_sample) << endmsg;
                                        goto midi;
                                }
@@ -888,19 +599,18 @@ DiskReader::overwrite_existing_buffers ()
                _midi_buf->reset ();
                _midi_buf->reset_tracker ();
 
                _midi_buf->reset ();
                _midi_buf->reset_tracker ();
 
-               g_atomic_int_set (&_frames_read_from_ringbuffer, 0);
-               g_atomic_int_set (&_frames_written_to_ringbuffer, 0);
+               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.
                */
 
                /* 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_frame);
-
-               midi_read (overwrite_frame, _chunk_frames, false);
+               midi_playlist()->resolve_note_trackers (*_midi_buf, overwrite_sample);
 
 
-               file_frame = overwrite_frame; // it was adjusted by ::midi_read()
+               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;
        }
 
        _pending_overwrite = false;
@@ -908,33 +618,21 @@ DiskReader::overwrite_existing_buffers ()
        return ret;
 }
 
        return ret;
 }
 
-void
-DiskReader::non_realtime_locate (framepos_t location)
-{
-       /* now refill channel buffers */
-
-       if (speed() != 1.0f || speed() != -1.0f) {
-               seek ((framepos_t) (location * (double) speed()), true);
-       } else {
-               seek (location, true);
-       }
-}
-
 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) {
 
        for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
-               (*chan)->buf->reset ();
+               (*chan)->rbuf->reset ();
        }
 
        }
 
-       if (g_atomic_int_get (&_frames_read_from_ringbuffer) == 0) {
+       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
                /* we haven't read anything since the last seek,
                   so flush all note trackers to prevent
                   wierdness
@@ -943,11 +641,12 @@ DiskReader::seek (framepos_t frame, bool complete_refill)
        }
 
        _midi_buf->reset();
        }
 
        _midi_buf->reset();
-       g_atomic_int_set(&_frames_read_from_ringbuffer, 0);
-       g_atomic_int_set(&_frames_written_to_ringbuffer, 0);
+       g_atomic_int_set(&_samples_read_from_ringbuffer, 0);
+       g_atomic_int_set(&_samples_written_to_ringbuffer, 0);
 
 
-       playback_sample = frame;
-       file_frame = frame;
+       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
@@ -966,7 +665,7 @@ DiskReader::seek (framepos_t frame, bool complete_refill)
 }
 
 int
 }
 
 int
-DiskReader::can_internal_playback_seek (framecnt_t distance)
+DiskReader::can_internal_playback_seek (samplecnt_t distance)
 {
        /* 1. Audio */
 
 {
        /* 1. Audio */
 
@@ -974,27 +673,27 @@ DiskReader::can_internal_playback_seek (framecnt_t distance)
        boost::shared_ptr<ChannelList> c = channels.reader();
 
        for (chan = c->begin(); chan != c->end(); ++chan) {
        boost::shared_ptr<ChannelList> c = channels.reader();
 
        for (chan = c->begin(); chan != c->end(); ++chan) {
-               if ((*chan)->buf->read_space() < (size_t) distance) {
+               if ((*chan)->rbuf->read_space() < (size_t) distance) {
                        return false;
                }
        }
 
        /* 2. MIDI */
 
                        return false;
                }
        }
 
        /* 2. MIDI */
 
-       uint32_t frames_read    = g_atomic_int_get(&_frames_read_from_ringbuffer);
-       uint32_t frames_written = g_atomic_int_get(&_frames_written_to_ringbuffer);
+       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 ((frames_written - frames_read) < distance);
+       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();
 
        for (chan = c->begin(); chan != c->end(); ++chan) {
 {
        ChannelList::iterator chan;
        boost::shared_ptr<ChannelList> c = channels.reader();
 
        for (chan = c->begin(); chan != c->end(); ++chan) {
-               (*chan)->buf->increment_read_ptr (::llabs(distance));
+               (*chan)->rbuf->increment_read_ptr (::llabs(distance));
        }
 
        playback_sample += distance;
        }
 
        playback_sample += distance;
@@ -1014,28 +713,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
 DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
  *         after the read.
  *  @param cnt Count of samples to read.
  *  @param reversed true if we are running backwards, otherwise false.
  */
 int
 DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
-                        framepos_t& start, framecnt_t cnt,
+                        samplepos_t& start, samplecnt_t cnt,
                         int channel, bool reversed)
 {
                         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.
 
@@ -1045,13 +749,13 @@ DiskReader::audio_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.
                */
 
@@ -1088,7 +792,7 @@ DiskReader::audio_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;
                }
@@ -1125,10 +829,10 @@ DiskReader::_do_refill_with_alloc (bool partial_fill)
        */
 
        {
        */
 
        {
-               std::auto_ptr<Sample> mix_buf (new Sample[2*1048576]);
-               std::auto_ptr<float>  gain_buf (new float[2*1048576]);
+               boost::scoped_array<Sample> mix_buf (new Sample[2*1048576]);
+               boost::scoped_array<float>  gain_buf (new float[2*1048576]);
 
 
-               int ret = refill_audio (mix_buf.get(), gain_buf.get(), (partial_fill ? _chunk_frames : 0));
+               int ret = refill_audio (mix_buf.get(), gain_buf.get(), (partial_fill ? _chunk_samples : 0));
 
                if (ret) {
                        return ret;
 
                if (ret) {
                        return ret;
@@ -1139,7 +843,7 @@ DiskReader::_do_refill_with_alloc (bool partial_fill)
 }
 
 int
 }
 
 int
-DiskReader::refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t fill_level)
+DiskReader::refill (Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level)
 {
        int ret = refill_audio (mixdown_buffer, gain_buffer, fill_level);
 
 {
        int ret = refill_audio (mixdown_buffer, gain_buffer, fill_level);
 
@@ -1161,26 +865,26 @@ DiskReader::refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t fill_
  */
 
 int
  */
 
 int
-DiskReader::refill_audio (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;
@@ -1194,9 +898,10 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
        vector.buf[1] = 0;
        vector.len[1] = 0;
 
        vector.buf[1] = 0;
        vector.len[1] = 0;
 
-       c->front()->buf->get_write_vector (&vector);
+       c->front()->rbuf->get_write_vector (&vector);
 
        if ((total_space = vector.len[0] + vector.len[1]) == 0) {
 
        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;
        }
@@ -1211,17 +916,18 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
        }
 
        /* 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;
        }
 
@@ -1230,37 +936,40 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
           work with.
        */
 
           work with.
        */
 
-       if (_slaved && total_space < (framecnt_t) (c->front()->buf->bufsize() / 2)) {
+       if (_slaved && total_space < (samplecnt_t) (c->front()->rbuf->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 */
 
                        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
 
                                ChannelInfo* chan (*i);
 
                        /* at start: nothing to do but fill with silence */
 
                        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
 
                                ChannelInfo* chan (*i);
-                               chan->buf->get_write_vector (&vector);
+                               chan->rbuf->get_write_vector (&vector);
                                memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]);
                                if (vector.len[1]) {
                                        memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]);
                                }
                                memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]);
                                if (vector.len[1]) {
                                        memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]);
                                }
-                               chan->buf->increment_write_ptr (vector.len[0] + vector.len[1]);
+                               chan->rbuf->increment_write_ptr (vector.len[0] + vector.len[1]);
                        }
                        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 {
 
@@ -1269,36 +978,36 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
 
        } else {
 
 
        } else {
 
-               if (file_frame == max_framepos) {
+               if (ffa == max_samplepos) {
 
                        /* at end: nothing to do but fill with silence */
 
                        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
 
                                ChannelInfo* chan (*i);
 
                        /* at end: nothing to do but fill with silence */
 
                        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
 
                                ChannelInfo* chan (*i);
-                               chan->buf->get_write_vector (&vector);
+                               chan->rbuf->get_write_vector (&vector);
                                memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]);
                                if (vector.len[1]) {
                                        memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]);
                                }
                                memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]);
                                if (vector.len[1]) {
                                        memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]);
                                }
-                               chan->buf->increment_write_ptr (vector.len[0] + vector.len[1]);
+                               chan->rbuf->increment_write_ptr (vector.len[0] + vector.len[1]);
                        }
                        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 */
 
@@ -1315,11 +1024,9 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
 
        /* 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;
@@ -1329,18 +1036,18 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
                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->rbuf->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
@@ -1357,7 +1064,7 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
                }
 
                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];
@@ -1365,18 +1072,17 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
                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 (audio_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);
+                       chan->rbuf->increment_write_ptr (to_read);
                        ts -= to_read;
                }
 
                        ts -= to_read;
                }
 
@@ -1389,12 +1095,12 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
                           all of vector.len[1] as well.
                        */
 
                           all of vector.len[1] as well.
                        */
 
-                       if (audio_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;
                        }
 
-                       chan->buf->increment_write_ptr (to_read);
+                       chan->rbuf->increment_write_ptr (to_read);
                }
 
                if (zero_fill) {
                }
 
                if (zero_fill) {
@@ -1404,21 +1110,21 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
        }
 
        // 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()->rbuf->get_write_vector (&vector);
 
   out:
        return ret;
 }
 
 void
 
   out:
        return ret;
 }
 
 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
@@ -1429,22 +1135,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) {
@@ -1464,12 +1169,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) {
@@ -1477,7 +1181,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));
        }
 
@@ -1500,16 +1204,6 @@ DiskReader::move_processor_automation (boost::weak_ptr<Processor> p, list< Evora
        }
 }
 
        }
 }
 
-boost::shared_ptr<MidiBuffer>
-DiskReader::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;
-}
-
 void
 DiskReader::reset_tracker ()
 {
 void
 DiskReader::reset_tracker ()
 {
@@ -1523,7 +1217,7 @@ DiskReader::reset_tracker ()
 }
 
 void
 }
 
 void
-DiskReader::resolve_tracker (Evoral::EventSink<framepos_t>& buffer, framepos_t time)
+DiskReader::resolve_tracker (Evoral::EventSink<samplepos_t>& buffer, samplepos_t time)
 {
        _midi_buf->resolve_tracker(buffer, time);
 
 {
        _midi_buf->resolve_tracker(buffer, time);
 
@@ -1534,143 +1228,159 @@ DiskReader::resolve_tracker (Evoral::EventSink<framepos_t>& buffer, framepos_t t
        }
 }
 
        }
 }
 
-void
-DiskReader::flush_playback (framepos_t start, framepos_t end)
-{
-       _midi_buf->flush (start, end);
-       g_atomic_int_add (&_frames_read_from_ringbuffer, end - start);
-}
-
 /** Writes playback events from playback_sample for nframes to dst, translating time stamps
  *  so that an event at playback_sample has time = 0
  */
 void
 /** 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_playback (MidiBuffer& dst, framecnt_t nframes)
+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)
 {
 {
-       dst.clear();
+       MidiBuffer* target;
+       samplepos_t nframes = end_sample - start_sample;
 
 
-       Location* loc = loop_location;
+       if ((ms & MonitoringInput) == 0) {
+               /* Route::process_output_buffers() clears the buffer as-needed */
+               target = &dst;
+       } else {
+               target = &scratch_bufs.get_midi (0);
+       }
 
 
-       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(), playback_sample, playback_sample + nframes,
-                            (loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes, Port::port_offset()));
+       if (ms & MonitoringDisk) {
+               /* disk data needed */
 
 
-       //cerr << "======== PRE ========\n";
-       //_midi_buf->dump (cerr);
-       //cerr << "----------------\n";
+               Location* loc = _loop_location;
 
 
-       size_t events_read = 0;
+               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()));
 
 
-       if (loc) {
-               framepos_t effective_start;
+               //cerr << "======== PRE ========\n";
+               //_midi_buf->dump (cerr);
+               //cerr << "----------------\n";
 
 
-               Evoral::Range<framepos_t> loop_range (loc->start(), loc->end() - 1);
-               effective_start = loop_range.squish (playback_sample);
+               size_t events_read = 0;
 
 
-               DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("looped, effective start adjusted to %1\n", effective_start));
+               if (loc) {
+                       samplepos_t effective_start;
 
 
-               if (effective_start == loc->start()) {
-                       /* We need to turn off notes that may extend
-                          beyond the loop end.
-                       */
+                       Evoral::Range<samplepos_t> loop_range (loc->start(), loc->end() - 1);
+                       effective_start = loop_range.squish (start_sample);
 
 
-                       _midi_buf->resolve_tracker (dst, 0);
-               }
+                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("looped, effective start adjusted to %1\n", effective_start));
 
 
-               /* for split-cycles we need to offset the events */
+                       if (effective_start == loc->start()) {
+                               /* We need to turn off notes that may extend
+                                  beyond the loop end.
+                               */
 
 
-               if (loc->end() >= effective_start && loc->end() < effective_start + nframes) {
+                               _midi_buf->resolve_tracker (*target, 0);
+                       }
 
 
-                       /* 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
-                       */
+                       /* for split-cycles we need to offset the events */
 
 
-                       framecnt_t first, second;
+                       if (loc->end() >= effective_start && loc->end() < effective_start + nframes) {
 
 
-                       first = loc->end() - effective_start;
-                       second = nframes - first;
+                               /* 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
+                               */
 
 
-                       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));
+                               samplecnt_t 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 (dst, effective_start, first);
-                       }
+                               first = loc->end() - effective_start;
+                               second = nframes - 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 (dst, loc->start(), 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 = _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 {
                } 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 (dst, effective_start, effective_start + nframes);
-               }
-       } else {
-               const size_t n_skipped = _midi_buf->skip_to (playback_sample);
-               if (n_skipped > 0) {
-                       warning << string_compose(_("MidiDiskstream %1: skipped %2 events, possible underflow"), id(), n_skipped) << endmsg;
+                       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 ("playback buffer read, from %1 to %2 (%3)", playback_sample, playback_sample + nframes, nframes));
-               events_read = _midi_buf->read (dst, playback_sample, playback_sample + nframes);
+
+               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()));
        }
 
        }
 
-       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);
 
 
-       g_atomic_int_add (&_frames_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;
+               }
+       }
 
 
-       //cerr << "======== POST ========\n";
-       //_midi_buf->dump (cerr);
-       //cerr << "----------------\n";
-}
+       if (ms & MonitoringInput) {
+               dst.merge_from (*target, nframes);
+       }
 
 
-/** Get the start, end, and length of a location "atomically".
- *
- * Note: Locations don't get deleted, so all we care about when I say "atomic"
- * is that we are always pointing to the same one and using start/length values
- * obtained just once.  Use this function to achieve this since location being
- * a parameter achieves this.
- */
-static void
-get_location_times(const Location* location,
-                   framepos_t*     start,
-                   framepos_t*     end,
-                   framepos_t*     length)
-{
-       if (location) {
-               *start  = location->start();
-               *end    = location->end();
-               *length = *end - *start;
+#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 frame position (TIME) read up to */
+/** @a start is set to the new sample position (TIME) read up to */
 int
 int
-DiskReader::midi_read (framepos_t& start, framecnt_t dur, bool reversed)
+DiskReader::midi_read (samplepos_t& start, samplecnt_t dur, bool reversed)
 {
 {
-       framecnt_t this_read   = 0;
-       framepos_t loop_end    = 0;
-       framepos_t loop_start  = 0;
-       framecnt_t loop_length = 0;
-       Location*  loc         = loop_location;
-       framepos_t effective_start = start;
-       Evoral::Range<framepos_t>*  loop_range (0);
+       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);
 
 
-//     MidiTrack*         mt     = dynamic_cast<MidiTrack*>(_track);
-//     MidiChannelFilter* filter = mt ? &mt->playback_filter() : 0;
-       MidiChannelFilter* filter = 0;
+       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS::midi_read @ %1 cnt %2\n", start, dur));
 
 
-       frameoffset_t loop_offset = 0;
+       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);
 
        if (!reversed && loc) {
                get_location_times (loc, &loop_start, &loop_end, &loop_length);
@@ -1683,10 +1393,10 @@ DiskReader::midi_read (framepos_t& start, framecnt_t dur, bool reversed)
                if (loc && !reversed) {
 
                        if (!loop_range) {
                if (loc && !reversed) {
 
                        if (!loop_range) {
-                               loop_range = new Evoral::Range<framepos_t> (loop_start, loop_end-1); // inclusive semantics require -1
+                               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 frame we read is at the correct
+                       /* if we are (seamlessly) looping, ensure that the first sample we read is at the correct
                           position within the loop.
                        */
 
                           position within the loop.
                        */
 
@@ -1715,12 +1425,12 @@ DiskReader::midi_read (framepos_t& start, framecnt_t dur, bool reversed)
 
                if (midi_playlist()->read (*_midi_buf, effective_start, this_read, loop_range, 0, filter) != this_read) {
                        error << string_compose(
 
                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 frame %3"),
+                                       _("MidiDiskstream %1: cannot read %2 from playlist at sample %3"),
                                        id(), this_read, start) << endmsg;
                        return -1;
                }
 
                                        id(), this_read, start) << endmsg;
                        return -1;
                }
 
-               g_atomic_int_add (&_frames_written_to_ringbuffer, this_read);
+               g_atomic_int_add (&_samples_written_to_ringbuffer, this_read);
 
                if (reversed) {
 
 
                if (reversed) {
 
@@ -1743,7 +1453,6 @@ DiskReader::midi_read (framepos_t& start, framecnt_t dur, bool reversed)
                }
 
                dur -= this_read;
                }
 
                dur -= this_read;
-               //offset += this_read;
        }
 
        return 0;
        }
 
        return 0;
@@ -1752,12 +1461,14 @@ DiskReader::midi_read (framepos_t& start, framecnt_t dur, bool reversed)
 int
 DiskReader::refill_midi ()
 {
 int
 DiskReader::refill_midi ()
 {
-       int     ret         = 0;
-       size_t  write_space = _midi_buf->write_space();
-       bool    reversed    = (_visible_speed * _session.transport_speed()) < 0.0f;
+       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::MidiDiskstreamIO, string_compose ("MDS refill, write space = %1 file frame = %2\n",
-                                                             write_space, file_frame));
+       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) {
 
        /* no space to write */
        if (write_space == 0) {
@@ -1769,25 +1480,44 @@ DiskReader::refill_midi ()
        }
 
        /* at end: nothing to do */
        }
 
        /* at end: nothing to do */
-       if (file_frame == max_framepos) {
+
+       samplepos_t ffm = file_sample[DataType::MIDI];
+
+       if (ffm == max_samplepos) {
                return 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);
+       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 ((frames_read < frames_written) && (frames_written - frames_read) >= midi_readahead) {
+       if ((samples_read < samples_written) && (samples_written - samples_read) >= midi_readahead) {
                return 0;
        }
 
                return 0;
        }
 
-       framecnt_t to_read = midi_readahead - ((framecnt_t)frames_written - (framecnt_t)frames_read);
+       samplecnt_t to_read = midi_readahead - ((samplecnt_t)samples_written - (samplecnt_t)samples_read);
 
 
-       to_read = min (to_read, (framecnt_t) (max_framepos - file_frame));
-       to_read = min (to_read, (framecnt_t) write_space);
+       to_read = min (to_read, (samplecnt_t) (max_samplepos - ffm));
+       to_read = min (to_read, (samplecnt_t) write_space);
 
 
-       if (midi_read (file_frame, to_read, reversed)) {
+       if (midi_read (ffm, to_read, reversed)) {
                ret = -1;
        }
 
                ret = -1;
        }
 
+       file_sample[DataType::MIDI] = ffm;
+
        return ret;
 }
        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;
+}