remove unused API
[ardour.git] / libs / ardour / disk_reader.cc
index 4250cd5991640718e59988c8547a6a815d28e5b6..0df940a13b97dcbd099ff44f12f0392988ad49c5 100644 (file)
@@ -1,26 +1,29 @@
 /*
 /*
-    Copyright (C) 2009-2016 Paul Davis
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * Copyright (C) 2017-2018 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
 
 
-*/
+#include <boost/smart_ptr/scoped_array.hpp>
 
 #include "pbd/enumwriter.h"
 
 #include "pbd/enumwriter.h"
-#include "pbd/i18n.h"
 #include "pbd/memento_command.h"
 #include "pbd/memento_command.h"
+#include "pbd/playback_buffer.h"
 
 
+#include "ardour/amp.h"
 #include "ardour/audioengine.h"
 #include "ardour/audioplaylist.h"
 #include "ardour/audio_buffer.h"
 #include "ardour/audioengine.h"
 #include "ardour/audioplaylist.h"
 #include "ardour/audio_buffer.h"
 #include "ardour/session.h"
 #include "ardour/session_playlists.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;
 PBD::Signal0<void> DiskReader::Underrun;
+Sample* DiskReader::_sum_buffer = 0;
 Sample* DiskReader::_mixdown_buffer = 0;
 gain_t* DiskReader::_gain_buffer = 0;
 Sample* DiskReader::_mixdown_buffer = 0;
 gain_t* DiskReader::_gain_buffer = 0;
-framecnt_t DiskReader::midi_readahead = 4096;
-bool DiskReader::no_disk_output = false;
+samplecnt_t DiskReader::midi_readahead = 4096;
+gint DiskReader::_no_disk_output (0);
 
 DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f)
        : DiskIOProcessor (s, str, f)
 
 DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f)
        : DiskIOProcessor (s, str, f)
-       , _roll_delay (0)
-       , overwrite_frame (0)
-        , overwrite_offset (0)
-        , _pending_overwrite (false)
-        , overwrite_queued (false)
-       , _gui_feed_buffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI))
+       , overwrite_sample (0)
+       , overwrite_queued (false)
+       , run_must_resolve (false)
+       , _declick_amp (s.nominal_sample_rate ())
+       , _declick_offs (0)
 {
 {
-       file_frame[DataType::AUDIO] = 0;
-       file_frame[DataType::MIDI] = 0;
+       file_sample[DataType::AUDIO] = 0;
+       file_sample[DataType::MIDI] = 0;
+       g_atomic_int_set (&_pending_overwrite, 0);
 }
 
 DiskReader::~DiskReader ()
 {
        DEBUG_TRACE (DEBUG::Destruction, string_compose ("DiskReader %1 @ %2 deleted\n", _name, this));
 }
 
 DiskReader::~DiskReader ()
 {
        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 ();
-               }
-       }
-
-       {
-               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;
+       rbuf = new PlaybackBuffer<Sample> (bufsize);
+       /* touch memory to lock it */
+       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
@@ -94,6 +102,7 @@ DiskReader::allocate_working_buffers()
           need to reflect the maximum size we could use, which is 4MB reads, or 2M samples
           using 16 bit samples.
        */
           need to reflect the maximum size we could use, which is 4MB reads, or 2M samples
           using 16 bit samples.
        */
+       _sum_buffer           = new Sample[2*1048576];
        _mixdown_buffer       = new Sample[2*1048576];
        _gain_buffer          = new gain_t[2*1048576];
 }
        _mixdown_buffer       = new Sample[2*1048576];
        _gain_buffer          = new gain_t[2*1048576];
 }
@@ -101,14 +110,16 @@ DiskReader::allocate_working_buffers()
 void
 DiskReader::free_working_buffers()
 {
 void
 DiskReader::free_working_buffers()
 {
+       delete [] _sum_buffer;
        delete [] _mixdown_buffer;
        delete [] _gain_buffer;
        delete [] _mixdown_buffer;
        delete [] _gain_buffer;
-       _mixdown_buffer       = 0;
-       _gain_buffer          = 0;
+       _sum_buffer     = 0;
+       _mixdown_buffer = 0;
+       _gain_buffer    = 0;
 }
 
 }
 
-framecnt_t
-DiskReader::default_chunk_frames()
+samplecnt_t
+DiskReader::default_chunk_samples()
 {
        return 65536;
 }
 {
        return 65536;
 }
@@ -126,16 +137,10 @@ DiskReader::set_name (string const & str)
        return true;
 }
 
        return true;
 }
 
-void
-DiskReader::set_roll_delay (ARDOUR::framecnt_t nframes)
-{
-       _roll_delay = nframes;
-}
-
 XMLNode&
 XMLNode&
-DiskReader::state (bool full)
+DiskReader::state ()
 {
 {
-       XMLNode& node (DiskIOProcessor::state (full));
+       XMLNode& node (DiskIOProcessor::state ());
        node.set_property(X_("type"), X_("diskreader"));
        return node;
 }
        node.set_property(X_("type"), X_("diskreader"));
        return node;
 }
@@ -153,7 +158,11 @@ DiskReader::set_state (const XMLNode& node, int version)
 void
 DiskReader::realtime_handle_transport_stopped ()
 {
 void
 DiskReader::realtime_handle_transport_stopped ()
 {
-       realtime_speed_change ();
+       /* can't do the resolve here because we don't have a place to put the
+        * note resolving data. Defer to
+        * MidiTrack::realtime_handle_transport_stopped() which will call
+        * ::resolve_tracker() and put the output in its _immediate_events store.
+        */
 }
 
 void
 }
 
 void
@@ -181,7 +190,7 @@ DiskReader::buffer_load () const
                return 1.0;
        }
 
                return 1.0;
        }
 
-       PBD::RingBufferNPT<Sample> * b = c->front()->buf;
+       PBD::PlaybackBuffer<Sample>* b = c->front()->rbuf;
        return (float) ((double) b->read_space() / (double) b->bufsize());
 }
 
        return (float) ((double) b->read_space() / (double) b->bufsize());
 }
 
@@ -195,17 +204,11 @@ DiskReader::adjust_buffering ()
        }
 }
 
        }
 }
 
-void
-DiskReader::playlist_changed (const PropertyChange&)
-{
-       playlist_modified ();
-}
-
 void
 DiskReader::playlist_modified ()
 {
 void
 DiskReader::playlist_modified ()
 {
-       if (!i_am_the_modifier && !overwrite_queued) {
-               _session.request_overwrite_buffer (_route);
+       if (!overwrite_queued) {
+               _session.request_overwrite_buffer (_track);
                overwrite_queued = true;
        }
 }
                overwrite_queued = true;
        }
 }
@@ -228,8 +231,10 @@ DiskReader::use_playlist (DataType dt, boost::shared_ptr<Playlist> playlist)
           take care of the buffer refill.
        */
 
           take care of the buffer refill.
        */
 
+        cerr << "DR " << _track->name() << " using playlist, loading ? " << _session.loading() << endl;
+
         if (!overwrite_queued && (prior_playlist || _session.loading())) {
         if (!overwrite_queued && (prior_playlist || _session.loading())) {
-               _session.request_overwrite_buffer (_route);
+               _session.request_overwrite_buffer (_track);
                overwrite_queued = true;
        }
 
                overwrite_queued = true;
        }
 
@@ -237,14 +242,22 @@ DiskReader::use_playlist (DataType dt, boost::shared_ptr<Playlist> playlist)
 }
 
 void
 }
 
 void
-DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame,
+DiskReader::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample,
                  double speed, pframes_t nframes, bool result_required)
 {
        uint32_t n;
        boost::shared_ptr<ChannelList> c = channels.reader();
        ChannelList::iterator chan;
                  double speed, pframes_t nframes, bool result_required)
 {
        uint32_t n;
        boost::shared_ptr<ChannelList> c = channels.reader();
        ChannelList::iterator chan;
-       frameoffset_t playback_distance;
-       MonitorState ms = _route->monitoring_state ();
+       sampleoffset_t disk_samples_to_consume;
+       MonitorState ms = _track->monitoring_state ();
+
+       if (run_must_resolve) {
+               boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_track);
+               if (mt) {
+                       resolve_tracker (mt->immediate_events(), start_sample);
+               }
+               run_must_resolve = false;
+       }
 
        if (_active) {
                if (!_pending_active) {
 
        if (_active) {
                if (!_pending_active) {
@@ -259,42 +272,63 @@ DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame,
                }
        }
 
                }
        }
 
-       if (speed == 0.0 && (ms == MonitoringDisk)) {
-               /* stopped. Don't accidentally pass any data from disk
-                * into our outputs (e.g. via interpolation)
+       const bool declick_out = _session.declick_in_progress();
+       const gain_t target_gain = (declick_out || (speed == 0.0) || ((ms & MonitoringDisk) == 0)) ? 0.0 : 1.0;
+
+       if (!_session.cfg ()->get_use_transport_fades ()) {
+               _declick_amp.set_gain (target_gain);
+       }
+
+       if (declick_out && (ms == MonitoringDisk) && _declick_amp.gain () == target_gain) {
+               /* no channels, or stopped. Don't accidentally pass any data
+                * from disk into our outputs (e.g. via interpolation)
                 */
                 */
-               bufs.silence (nframes, 0);
                return;
        }
 
                return;
        }
 
-       if (speed != 1.0f && speed != -1.0f) {
-               interpolation.set_speed (speed);
-               midi_interpolation.set_speed (speed);
-               playback_distance = midi_interpolation.distance (nframes);
-               if (speed < 0.0) {
-                       playback_distance = -playback_distance;
-               }
+       BufferSet& scratch_bufs (_session.get_scratch_buffers (bufs.count()));
+       const bool still_locating = _session.global_locate_pending() || pending_overwrite ();
+
+       assert (speed == -1 || speed == 0 || speed == 1);
+
+       if (speed == 0) {
+               disk_samples_to_consume = 0;
        } else {
        } else {
-               playback_distance = nframes;
+               disk_samples_to_consume = nframes;
        }
 
        }
 
-       BufferSet& scratch_bufs (_session.get_scratch_buffers (bufs.count()));
-       const bool still_locating = _session.global_locate_pending();
+       if (c->empty()) {
+               /* do nothing with audio */
+               goto midi;
+       }
 
 
-       if (!result_required || ((ms & MonitoringDisk) == 0) || still_locating) {
+       if (_declick_amp.gain () != target_gain && target_gain == 0) {
+               /* fade-out */
+#if 0
+               printf ("DR fade-out speed=%.1f gain=%.3f off=%ld start=%ld playpos=%ld (%s)\n",
+                               speed, _declick_amp.gain (), _declick_offs, start_sample, playback_sample, owner()->name().c_str());
+#endif
+               ms = MonitorState (ms | MonitoringDisk);
+               assert (result_required);
+               result_required = true;
+       } else {
+               _declick_offs = 0;
+       }
+
+       if (!result_required || ((ms & MonitoringDisk) == 0) || still_locating || _no_disk_output) {
 
                /* no need for actual disk data, just advance read pointer and return */
 
 
                /* no need for actual disk data, just advance read pointer and return */
 
-               if (!still_locating) {
+               if (!still_locating || _no_disk_output) {
                        for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
                        for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
-                               (*chan)->buf->increment_read_ptr (playback_distance);
+                               (*chan)->rbuf->increment_read_ptr (disk_samples_to_consume);
                        }
                }
 
                /* if monitoring disk but locating put silence in the buffers */
 
                        }
                }
 
                /* if monitoring disk but locating put silence in the buffers */
 
-               if (still_locating && (ms == MonitoringDisk)) {
-                       bufs.silence (playback_distance, 0);
+               if ((_no_disk_output || still_locating) && (ms == MonitoringDisk)) {
+                       bufs.silence (nframes, 0);
                }
 
        } else {
                }
 
        } else {
@@ -306,7 +340,7 @@ DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame,
                gain_t scaling;
 
                if (n_chans > n_buffers) {
                gain_t scaling;
 
                if (n_chans > n_buffers) {
-                       scaling = ((float) n_buffers)/n_chans;
+                       scaling = ((float) n_buffers) / n_chans;
                } else {
                        scaling = 1.0;
                }
                } else {
                        scaling = 1.0;
                }
@@ -314,86 +348,59 @@ DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame,
                for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
 
                        ChannelInfo* chaninfo (*chan);
                for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
 
                        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 (ms & MonitoringInput) {
-                               /* put disk stream in scratch buffer, blend at end */
-                               disk_signal = scratch_bufs.get_audio(n).data ();
-                       } else {
-                               /* no input stream needed, just overwrite buffers */
-                               disk_signal = output.data ();
-                       }
-
-                       chaninfo->buf->get_read_vector (&(*chan)->rw_vector);
+                       AudioBuffer& output (bufs.get_audio (n % n_buffers));
 
 
-                       if (playback_distance <= (framecnt_t) chaninfo->rw_vector.len[0]) {
-
-                               if (fabsf (speed) != 1.0f) {
-                                       (void) interpolation.interpolate (
-                                               n, nframes,
-                                               chaninfo->rw_vector.buf[0],
-                                               disk_signal);
-                               } else if (speed != 0.0) {
-                                       memcpy (disk_signal, chaninfo->rw_vector.buf[0], sizeof (Sample) * playback_distance);
-                               }
-
-                       } else {
-
-                               const framecnt_t total = chaninfo->rw_vector.len[0] + chaninfo->rw_vector.len[1];
-
-                               if (playback_distance <= total) {
-
-                                       /* We have enough samples, but not in one lump.
-                                        */
-
-                                       if (fabsf (speed) != 1.0f) {
-                                               interpolation.interpolate (n, chaninfo->rw_vector.len[0],
-                                                                          chaninfo->rw_vector.buf[0],
-                                                                          disk_signal);
-                                               disk_signal += chaninfo->rw_vector.len[0];
-                                               interpolation.interpolate (n, playback_distance - chaninfo->rw_vector.len[0],
-                                                                          chaninfo->rw_vector.buf[1],
-                                                                          disk_signal);
-                                       } else 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],
-                                                       (playback_distance - chaninfo->rw_vector.len[0]) * sizeof (Sample));
-                                       }
+                       AudioBuffer& disk_buf ((ms & MonitoringInput) ? scratch_bufs.get_audio(n) : output);
 
 
+                       if (start_sample != playback_sample && target_gain != 0) {
+                               if (can_internal_playback_seek (start_sample - playback_sample)) {
+                                       internal_playback_seek (start_sample - playback_sample);
                                } else {
                                } else {
+                                       disk_samples_to_consume = 0; /* will force an underrun below */
+                               }
+                       }
 
 
-                                       cerr << _name << " Need " << playback_distance << " total = " << total << endl;
+                       if (!declick_out) {
+                               const samplecnt_t total = chaninfo->rbuf->read (disk_buf.data(), disk_samples_to_consume);
+                               if (disk_samples_to_consume > total) {
+                                       cerr << _name << " Need " << total << " have only " << disk_samples_to_consume << 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;
                                        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 if (_declick_amp.gain () != target_gain) {
+                               assert (target_gain == 0);
+                               const samplecnt_t total = chaninfo->rbuf->read (disk_buf.data(), nframes, false, _declick_offs);
+                               _declick_offs += total;
                        }
 
                        }
 
-                       if (scaling != 1.0f && speed != 0.0) {
-                               apply_gain_to_buffer (disk_signal, nframes, scaling);
-                       }
+                       _declick_amp.apply_gain (disk_buf, nframes, target_gain);
 
 
-                       chaninfo->buf->increment_read_ptr (playback_distance);
+                       Amp::apply_simple_gain (disk_buf, nframes, scaling);
 
 
-                       if (!no_disk_output && (speed != 0.0) && (ms & MonitoringInput)) {
+                       if (ms & MonitoringInput) {
                                /* mix the disk signal into the input signal (already in bufs) */
                                /* mix the disk signal into the input signal (already in bufs) */
-                               mix_buffers_no_gain (output.data(), disk_signal, speed == 0.0 ? nframes : playback_distance);
+                               mix_buffers_no_gain (output.data(), disk_buf.data(), nframes);
                        }
                }
        }
 
        /* MIDI data handling */
 
                        }
                }
        }
 
        /* MIDI data handling */
 
-       if (!_session.declick_out_pending()) {
-               if (ms & MonitoringDisk && !still_locating) {
-                       get_midi_playback (bufs.get_midi (0), playback_distance, ms, scratch_bufs, speed, playback_distance);
+  midi:
+       if (!declick_in_progress() && bufs.count().n_midi()) {
+               MidiBuffer* dst;
+
+               if (_no_disk_output) {
+                       dst = &scratch_bufs.get_midi(0);
+               } else {
+                       dst = &bufs.get_midi (0);
+               }
+
+               if ((ms & MonitoringDisk) && !still_locating && speed) {
+                       get_midi_playback (*dst, start_sample, end_sample, ms, scratch_bufs, speed, disk_samples_to_consume);
                }
        }
 
                }
        }
 
@@ -402,78 +409,37 @@ DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame,
                bool butler_required = false;
 
                if (speed < 0.0) {
                bool butler_required = false;
 
                if (speed < 0.0) {
-                       playback_sample -= playback_distance;
+                       playback_sample -= disk_samples_to_consume;
                } else {
                } else {
-                       playback_sample += playback_distance;
+                       playback_sample += disk_samples_to_consume;
+               }
+
+               Location* loc = _loop_location;
+               if (loc) {
+                       Evoral::Range<samplepos_t> loop_range (loc->start(), loc->end() - 1);
+                       playback_sample = loop_range.squish (playback_sample);
                }
 
                if (_playlists[DataType::AUDIO]) {
                        if (!c->empty()) {
                                if (_slaved) {
                }
 
                if (_playlists[DataType::AUDIO]) {
                        if (!c->empty()) {
                                if (_slaved) {
-                                       if (c->front()->buf->write_space() >= c->front()->buf->bufsize() / 2) {
-                                               DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: slaved, write space = %2 of %3\n", name(), c->front()->buf->write_space(),
-                                                                                           c->front()->buf->bufsize()));
+                                       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 {
                                                butler_required = true;
                                        }
                                } else {
-                                       if ((framecnt_t) c->front()->buf->write_space() >= _chunk_frames) {
-                                               DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: write space = %2 of %3\n", name(), c->front()->buf->write_space(),
-                                                                                           _chunk_frames));
+                                       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;
                                        }
                                }
                        }
                }
 
                                                butler_required = true;
                                        }
                                }
                        }
                }
 
-               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));
-
-                       /*
-                         cerr << name() << " MDS written: " << frames_written << " - read: " << frames_read <<
-                         " = " << frames_written - frames_read
-                         << " + " << playback_distance << " < " << 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.
-                        */
-
-                       /* 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) {
-                                       butler_required = true;
-                               }
-                       } else {
-                               butler_required = true;
-                       }
-
-               }
+               /* All of MIDI is in RAM, no need to call the butler unless we
+                * have to overwrite buffers because of a playlist change.
+                */
 
                _need_butler = butler_required;
        }
 
                _need_butler = butler_required;
        }
@@ -481,148 +447,133 @@ DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame,
        // DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 reader run, needs butler = %2\n", name(), _need_butler));
 }
 
        // DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 reader run, needs butler = %2\n", name(), _need_butler));
 }
 
+bool
+DiskReader::declick_in_progress () const
+{
+       return _declick_amp.gain() != 0; // declick-out
+}
+
+bool
+DiskReader::pending_overwrite () const {
+       return g_atomic_int_get (&_pending_overwrite) != 0;
+}
+
 void
 void
-DiskReader::set_pending_overwrite (bool yn)
+DiskReader::set_pending_overwrite ()
 {
        /* called from audio thread, so we can use the read ptr and playback sample as we wish */
 
 {
        /* called from audio thread, so we can use the read ptr and playback sample as we wish */
 
-       _pending_overwrite = yn;
-
-       overwrite_frame = playback_sample;
+       assert (!pending_overwrite ());
+       overwrite_sample = playback_sample;
 
        boost::shared_ptr<ChannelList> c = channels.reader ();
 
        boost::shared_ptr<ChannelList> c = channels.reader ();
-       if (!c->empty ()) {
-               overwrite_offset = c->front()->buf->get_read_ptr();
+       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
+               (*chan)->rbuf->read_flush ();
        }
        }
+
+       g_atomic_int_set (&_pending_overwrite, 1);
+       run_must_resolve = true;
 }
 
 }
 
-int
+bool
 DiskReader::overwrite_existing_buffers ()
 {
 DiskReader::overwrite_existing_buffers ()
 {
-       int ret = -1;
-
-       boost::shared_ptr<ChannelList> c = channels.reader();
-
+       /* called from butler thread */
+       assert (pending_overwrite ());
        overwrite_queued = false;
 
        overwrite_queued = false;
 
-       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1 overwriting existing buffers at %2\n", overwrite_frame));
+       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1 overwriting existing buffers at %2\n", overwrite_sample));
 
 
+       boost::shared_ptr<ChannelList> c = channels.reader();
        if (!c->empty ()) {
        if (!c->empty ()) {
-
                /* AUDIO */
 
                const bool reversed = _session.transport_speed() < 0.0f;
 
                /* assume all are the same size */
                /* AUDIO */
 
                const bool reversed = _session.transport_speed() < 0.0f;
 
                /* assume all are the same size */
-               framecnt_t size = c->front()->buf->bufsize();
+               samplecnt_t size = c->front()->rbuf->write_space ();
+               assert (size > 0);
 
 
-               std::auto_ptr<Sample> mixdown_buffer (new Sample[size]);
-               std::auto_ptr<float> gain_buffer (new float[size]);
+               boost::scoped_array<Sample> sum_buffer (new Sample[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
 
                /* reduce size so that we can fill the buffer correctly (ringbuffers
-                  can only handle size-1, otherwise they appear to be empty)
-               */
+                * can only handle size-1, otherwise they appear to be empty)
+                */
                size--;
 
                uint32_t n=0;
                size--;
 
                uint32_t n=0;
-               framepos_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;
-
-                       /* to fill the buffer without resetting the playback sample, we need to
-                          do it one or two chunks (normally two).
-
-                          |----------------------------------------------------------------------|
-
-                          ^
-                          overwrite_offset
-                          |<- second chunk->||<----------------- first chunk ------------------>|
+                       samplepos_t start = overwrite_sample;
+                       samplecnt_t to_read = size;
 
 
-                       */
-
-                       framecnt_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"),
-                                                       id(), size, playback_sample) << endmsg;
+                       if (audio_read ((*chan)->rbuf, sum_buffer.get(), 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, overwrite_sample) << endmsg;
                                goto midi;
                        }
                                goto midi;
                        }
-
-                       if (cnt > to_read) {
-
-                               cnt -= to_read;
-
-                               if (audio_read ((*chan)->buf->buffer(), mixdown_buffer.get(), gain_buffer.get(), start, cnt, n, reversed)) {
-                                       error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at frame %3"),
-                                                               id(), size, playback_sample) << endmsg;
-                                       goto midi;
-                               }
-                       }
                }
                }
-
-               ret = 0;
-
        }
 
   midi:
 
        }
 
   midi:
 
-       if (_midi_buf && _playlists[DataType::MIDI]) {
+       RTMidiBuffer* mbuf = rt_midibuffer ();
 
 
-               /* Clear the playback buffer contents.  This is safe as long as the butler
-                  thread is suspended, which it should be.
-               */
-               _midi_buf->reset ();
-               _midi_buf->reset_tracker ();
-
-               g_atomic_int_set (&_frames_read_from_ringbuffer, 0);
-               g_atomic_int_set (&_frames_written_to_ringbuffer, 0);
-
-               /* Resolve all currently active notes in the playlist.  This is more
-                  aggressive than it needs to be: ideally we would only resolve what is
-                  absolutely necessary, but this seems difficult and/or impossible without
-                  having the old data or knowing what change caused the overwrite.
-               */
-               midi_playlist()->resolve_note_trackers (*_midi_buf, overwrite_frame);
-
-               midi_read (overwrite_frame, _chunk_frames, false);
-               file_frame[DataType::MIDI] = overwrite_frame; // overwrite_frame was adjusted by ::midi_read() to the new position
+       if (mbuf) {
+               PBD::Timing minsert;
+               minsert.start();
+               midi_playlist()->render (0);
+               minsert.update();
+               assert (midi_playlist()->rendered());
+               // cerr << "Reading " << name()  << " took " << minsert.elapsed() << " microseconds, final size = " << midi_playlist()->rendered()->size() << endl;
        }
 
        }
 
-       _pending_overwrite = false;
+       g_atomic_int_set (&_pending_overwrite, 0);
 
 
-       return ret;
+       return true;
 }
 
 int
 }
 
 int
-DiskReader::seek (framepos_t frame, bool complete_refill)
+DiskReader::seek (samplepos_t sample, bool complete_refill)
 {
 {
+       /* called via non_realtime_locate() from butler thread */
+
        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();
 
-       for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
-               (*chan)->buf->reset ();
+#ifndef NDEBUG
+       if (_declick_amp.gain() != 0) {
+               /* this should not happen. new transport should postponse seeking
+                * until de-click is complete */
+               printf ("LOCATE WITHOUT DECLICK (gain=%f) at %ld seek-to %ld\n", _declick_amp.gain (), playback_sample, sample);
+               //return -1;
        }
        }
+       if (sample == playback_sample && !complete_refill) {
+               return 0; // XXX double-check this
+       }
+#endif
 
 
-       if (g_atomic_int_get (&_frames_read_from_ringbuffer) == 0) {
-               /* we haven't read anything since the last seek,
-                  so flush all note trackers to prevent
-                  wierdness
-               */
-               reset_tracker ();
+       g_atomic_int_set (&_pending_overwrite, 0);
+
+       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("DiskReader::seek %s %ld -> %ld refill=%d\n", owner()->name().c_str(), playback_sample, sample, complete_refill));
+
+       const samplecnt_t distance = sample - playback_sample;
+       if (!complete_refill && can_internal_playback_seek (distance)) {
+               internal_playback_seek (distance);
+               return 0;
        }
 
        }
 
-       _midi_buf->reset();
-       g_atomic_int_set(&_frames_read_from_ringbuffer, 0);
-       g_atomic_int_set(&_frames_written_to_ringbuffer, 0);
+       for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
+               (*chan)->rbuf->reset ();
+       }
 
 
-       playback_sample = frame;
-       file_frame[DataType::AUDIO] = frame;
-       file_frame[DataType::MIDI] = 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
@@ -636,12 +587,11 @@ DiskReader::seek (framepos_t frame, bool complete_refill)
                ret = do_refill_with_alloc (true);
        }
 
                ret = do_refill_with_alloc (true);
        }
 
-
        return ret;
 }
 
        return ret;
 }
 
-int
-DiskReader::can_internal_playback_seek (framecnt_t distance)
+bool
+DiskReader::can_internal_playback_seek (sampleoffset_t distance)
 {
        /* 1. Audio */
 
 {
        /* 1. Audio */
 
@@ -649,32 +599,36 @@ 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->can_seek (distance)) {
                        return false;
                }
        }
 
                        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);
+       /* 2. MIDI can always seek any distance */
 
 
-       return ((frames_written - frames_read) < distance);
+       return true;
 }
 
 }
 
-int
-DiskReader::internal_playback_seek (framecnt_t distance)
+void
+DiskReader::internal_playback_seek (sampleoffset_t distance)
 {
 {
+       if (distance == 0) {
+               return;
+       }
+
+       sampleoffset_t off = distance;
+
        ChannelList::iterator chan;
        boost::shared_ptr<ChannelList> c = channels.reader();
        ChannelList::iterator chan;
        boost::shared_ptr<ChannelList> c = channels.reader();
-
        for (chan = c->begin(); chan != c->end(); ++chan) {
        for (chan = c->begin(); chan != c->end(); ++chan) {
-               (*chan)->buf->increment_read_ptr (::llabs(distance));
+               if (distance < 0) {
+                       off = 0 - (sampleoffset_t) (*chan)->rbuf->decrement_read_ptr (::llabs (distance));
+               } else {
+                       off = (*chan)->rbuf->increment_read_ptr (distance);
+               }
        }
 
        }
 
-       playback_sample += distance;
-
-       return 0;
+       playback_sample += off;
 }
 
 static
 }
 
 static
@@ -689,25 +643,27 @@ void swap_by_ptr (Sample *first, Sample *last)
 
 /** Read some data for 1 channel from our playlist into a buffer.
  *  @param buf Buffer to write to.
 
 /** Read some data for 1 channel from our playlist into a buffer.
  *  @param buf Buffer to write to.
- *  @param start Session frame to start reading from; updated to where we end up
+ *  @param start Session sample to start reading from; updated to where we end up
  *         after the read.
  *  @param cnt Count of samples to read.
  *  @param reversed true if we are running backwards, otherwise false.
  */
 int
  *         after the read.
  *  @param cnt Count of samples to read.
  *  @param reversed true if we are running backwards, otherwise false.
  */
 int
-DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
-                        framepos_t& start, framecnt_t cnt,
+DiskReader::audio_read (PBD::PlaybackBuffer<Sample>*rb,
+                        Sample* sum_buffer,
+                        Sample* mixdown_buffer,
+                        float* gain_buffer,
+                        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;
        Location *loc = 0;
 
        if (!_playlists[DataType::AUDIO]) {
        Location *loc = 0;
 
        if (!_playlists[DataType::AUDIO]) {
-               memset (buf, 0, sizeof (Sample) * cnt);
+               rb->write_zero (cnt);
                return 0;
        }
 
                return 0;
        }
 
@@ -715,7 +671,7 @@ DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
 
        if (!reversed) {
 
 
        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.
 
@@ -725,13 +681,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.
                */
 
@@ -765,17 +721,16 @@ DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
                        break;
                }
 
                        break;
                }
 
-               this_read = min(cnt,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,
-                                        start) << endmsg;
+               if (audio_playlist()->read (sum_buffer, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) {
+                       error << string_compose(_("DiskReader %1: cannot read %2 from playlist at sample %3"), id(), this_read, start) << endmsg;
                        return -1;
                }
 
                if (reversed) {
 
                        return -1;
                }
 
                if (reversed) {
 
-                       swap_by_ptr (buf, buf + this_read - 1);
+                       swap_by_ptr (sum_buffer, sum_buffer + this_read - 1);
 
                } else {
 
 
                } else {
 
@@ -788,8 +743,11 @@ DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
                        }
                }
 
                        }
                }
 
+               if (rb->write (sum_buffer, this_read) != this_read) {
+                       cerr << owner()->name() << " Ringbuffer Write overrun" << endl;
+               }
+
                cnt -= this_read;
                cnt -= this_read;
-               offset += this_read;
        }
 
        return 0;
        }
 
        return 0;
@@ -805,10 +763,11 @@ 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> sum_buf (new Sample[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 (sum_buf.get(), mix_buf.get(), gain_buf.get(), (partial_fill ? _chunk_samples : 0));
 
                if (ret) {
                        return ret;
 
                if (ret) {
                        return ret;
@@ -819,9 +778,9 @@ 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* sum_buffer, Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level)
 {
 {
-       int ret = refill_audio (mixdown_buffer, gain_buffer, fill_level);
+       int ret = refill_audio (sum_buffer, mixdown_buffer, gain_buffer, fill_level);
 
        if (ret) {
                return ret;
 
        if (ret) {
                return ret;
@@ -841,7 +800,7 @@ 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* sum_buffer, Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level)
 {
        /* do not read from disk while session is marked as Loading, to avoid
           useless redundant I/O.
 {
        /* do not read from disk while session is marked as Loading, to avoid
           useless redundant I/O.
@@ -852,15 +811,11 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
        }
 
        int32_t ret = 0;
        }
 
        int32_t ret = 0;
-       framecnt_t to_read;
-       RingBufferNPT<Sample>::rw_vector vector;
        bool const reversed = _session.transport_speed() < 0.0f;
        bool const reversed = _session.transport_speed() < 0.0f;
-       framecnt_t total_space;
-       framecnt_t zero_fill;
+       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;
 
        if (c->empty()) {
                return 0;
 
        if (c->empty()) {
                return 0;
@@ -869,14 +824,9 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
        assert(mixdown_buffer);
        assert(gain_buffer);
 
        assert(mixdown_buffer);
        assert(gain_buffer);
 
-       vector.buf[0] = 0;
-       vector.len[0] = 0;
-       vector.buf[1] = 0;
-       vector.len[1] = 0;
-
-       c->front()->buf->get_write_vector (&vector);
+       samplecnt_t total_space = c->front()->rbuf->write_space();
 
 
-       if ((total_space = vector.len[0] + vector.len[1]) == 0) {
+       if (total_space == 0) {
                DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: no space to refill\n", name()));
                /* nowhere to write to */
                return 0;
                DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: no space to refill\n", name()));
                /* nowhere to write to */
                return 0;
@@ -892,18 +842,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.
        */
 
-       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: space to refill %2 vs. chunk %3 (speed = %4)\n", name(), total_space, _chunk_frames, _session.transport_speed()));
-       if ((total_space < _chunk_frames) && fabs (_session.transport_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;
        }
 
@@ -912,86 +862,58 @@ 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;
        }
 
                DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: not enough to refill while slaved\n", this));
                return 0;
        }
 
-       framepos_t ffa = file_frame[DataType::AUDIO];
+       samplepos_t ffa = file_sample[DataType::AUDIO];
 
        if (reversed) {
 
                if (ffa == 0) {
 
        if (reversed) {
 
                if (ffa == 0) {
-
                        /* at start: nothing to do but fill with silence */
                        /* at start: nothing to do but fill with silence */
-
                        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
                        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
-
                                ChannelInfo* chan (*i);
                                ChannelInfo* chan (*i);
-                               chan->buf->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]);
-                               }
-                               chan->buf->increment_write_ptr (vector.len[0] + vector.len[1]);
+                               chan->rbuf->write_zero (chan->rbuf->write_space ());
                        }
                        return 0;
                }
 
                if (ffa < total_space) {
                        }
                        return 0;
                }
 
                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 - ffa;
                        total_space = ffa;
                        zero_fill = total_space - ffa;
                        total_space = ffa;
-
                } else {
                } else {
-
                        zero_fill = 0;
                }
 
        } else {
 
                        zero_fill = 0;
                }
 
        } else {
 
-               if (ffa == max_framepos) {
-
+               if (ffa == max_samplepos) {
                        /* at end: nothing to do but fill with silence */
                        /* at end: nothing to do but fill with silence */
-
                        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
                        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
-
                                ChannelInfo* chan (*i);
                                ChannelInfo* chan (*i);
-                               chan->buf->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]);
-                               }
-                               chan->buf->increment_write_ptr (vector.len[0] + vector.len[1]);
+                               chan->rbuf->write_zero (chan->rbuf->write_space ());
                        }
                        return 0;
                }
 
                        }
                        return 0;
                }
 
-               if (ffa > 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 - ffa);
-                       total_space = max_framepos - ffa;
+                       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;
-
        /* 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 */
-
        const size_t bits_per_sample = format_data_width (_session.config.get_native_file_data_format());
        size_t total_bytes = total_space * bits_per_sample / 8;
 
        const size_t bits_per_sample = format_data_width (_session.config.get_native_file_data_format());
        size_t total_bytes = total_space * bits_per_sample / 8;
 
-       /* chunk size range is 256kB to 4MB. Bigger is faster in terms of MB/sec, but bigger chunk size always takes longer
-        */
+       /* chunk size range is 256kB to 4MB. Bigger is faster in terms of MB/sec, but bigger chunk size always takes longer */
        size_t byte_size_for_read = max ((size_t) (256 * 1024), min ((size_t) (4 * 1048576), total_bytes));
 
        /* find nearest (lower) multiple of 16384 */
        size_t byte_size_for_read = max ((size_t) (256 * 1024), min ((size_t) (4 * 1048576), total_bytes));
 
        /* find nearest (lower) multiple of 16384 */
@@ -999,133 +921,82 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, framecnt_t
        byte_size_for_read = (byte_size_for_read / 16384) * 16384;
 
        /* now back to samples */
        byte_size_for_read = (byte_size_for_read / 16384) * 16384;
 
        /* 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);
 
        DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: will refill %2 channels with %3 samples\n", name(), c->size(), total_space));
 
 
        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;
+       samplepos_t file_sample_tmp = ffa;
 
        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
 
        for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
-
                ChannelInfo* chan (*i);
                ChannelInfo* chan (*i);
-               Sample* buf1;
-               Sample* buf2;
-               framecnt_t len1, len2;
-
-               chan->buf->get_write_vector (&vector);
-
-               if ((framecnt_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
-                          ^^^^ => 1 x disk_read_chunk_frames that would be filled
-
-                          |......|+++++++++++++|...............................|
-                          buf1                buf0
-                                               ^^^^^^^^^^^^^^^
-
-
-                          So, just pretend that the buf1 part isn't there.
-
-                       */
-
-                       vector.buf[1] = 0;
-                       vector.len[1] = 0;
-
-               }
-
-               ts = total_space;
-               file_frame_tmp = ffa;
-
-               buf1 = vector.buf[0];
-               len1 = vector.len[0];
-               buf2 = vector.buf[1];
-               len2 = vector.len[1];
-
-               to_read = min (ts, len1);
-               to_read = min (to_read, (framecnt_t) samples_to_read);
+               file_sample_tmp = ffa;
+               samplecnt_t ts = total_space;
 
 
+               samplecnt_t to_read = min (ts, (samplecnt_t) chan->rbuf->write_space ());
+               to_read = min (to_read, samples_to_read);
                assert (to_read >= 0);
 
                assert (to_read >= 0);
 
-               if (to_read) {
-
-                       if (audio_read (buf1, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan_n, reversed)) {
-                               ret = -1;
-                               goto out;
-                       }
-                       chan->buf->increment_write_ptr (to_read);
-                       ts -= to_read;
-               }
-
-               to_read = min (ts, len2);
+               // cerr << owner()->name() << " to-read: " << to_read << endl;
 
                if (to_read) {
 
                if (to_read) {
-
-                       /* we read all of vector.len[0], but it wasn't the
-                          entire samples_to_read of data, so read some or
-                          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 (chan->rbuf, sum_buffer, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, chan_n, reversed)) {
+                               error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), id(), to_read, ffa) << endmsg;
                                ret = -1;
                                goto out;
                        }
                                ret = -1;
                                goto out;
                        }
-
-                       chan->buf->increment_write_ptr (to_read);
                }
 
                if (zero_fill) {
                }
 
                if (zero_fill) {
-                       /* XXX: do something */
+                       /* not sure if action is needed,
+                        * we'll later hit the "to close to the end" case
+                        */
+                       //chan->rbuf->write_zero (zero_fill);
                }
                }
-
        }
 
        // elapsed = g_get_monotonic_time () - before;
        // cerr << '\t' << name() << ": bandwidth = " << (byte_size_for_read / 1048576.0) / (elapsed/1000000.0) << "MB/sec\n";
 
        }
 
        // elapsed = g_get_monotonic_time () - before;
        // cerr << '\t' << name() << ": bandwidth = " << (byte_size_for_read / 1048576.0) / (elapsed/1000000.0) << "MB/sec\n";
 
-       file_frame[DataType::AUDIO] = file_frame_tmp;
-       assert (file_frame[DataType::AUDIO] >= 0);
-
-       ret = ((total_space - samples_to_read) > _chunk_frames);
+       file_sample[DataType::AUDIO] = file_sample_tmp;
+       assert (file_sample[DataType::AUDIO] >= 0);
 
 
-       c->front()->buf->get_write_vector (&vector);
+       ret = ((total_space - samples_to_read) > _chunk_samples);
 
   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_or_shift)
 {
        /* If we're coming from an undo, it will have handled
 {
        /* If we're coming from an undo, it will have handled
-          automation undo (it must, since automation-follows-regions
-          can lose automation data).  Hence we can do nothing here.
-       */
+        * automation undo (it must, since automation-follows-regions
+        * can lose automation data).  Hence we can do nothing here.
+        *
+        * Likewise when shifting regions (insert/remove time)
+        * automation is taken care of separately (busses with
+        * automation have no disk-reader).
+        */
 
 
-       if (from_undo) {
+       if (from_undo_or_shift) {
                return;
        }
 
                return;
        }
 
-       if (!_route || Config->get_automation_follows_regions () == false) {
+       if (!_track || 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 = _route->pannable();
+       boost::shared_ptr<Pannable> pannable = _track->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) {
@@ -1145,11 +1016,11 @@ DiskReader::playlist_ranges_moved (list< Evoral::RangeMove<framepos_t> > const &
                 }
         }
        /* move processor automation */
                 }
         }
        /* move processor automation */
-        _route->foreach_processor (boost::bind (&DiskReader::move_processor_automation, this, _1, movements_frames));
+        _track->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) {
@@ -1157,7 +1028,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));
        }
 
@@ -1180,85 +1051,63 @@ 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 ()
 {
-       _midi_buf->reset_tracker ();
-
-       boost::shared_ptr<MidiPlaylist> mp (midi_playlist());
-
-       if (mp) {
-               mp->reset_note_trackers ();
-       }
+       _tracker.reset ();
 }
 
 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);
-
-       boost::shared_ptr<MidiPlaylist> mp (midi_playlist());
-
-       if (mp) {
-               mp->reset_note_trackers ();
-       }
+       _tracker.resolve_notes (buffer, time);
 }
 
 /** 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_midi_playback (MidiBuffer& dst, framecnt_t nframes, MonitorState ms, BufferSet& scratch_bufs, double speed, framecnt_t playback_distance)
+DiskReader::get_midi_playback (MidiBuffer& dst, samplepos_t start_sample, samplepos_t end_sample, MonitorState ms, BufferSet& scratch_bufs, double speed, samplecnt_t disk_samples_to_consume)
 {
        MidiBuffer* target;
 {
        MidiBuffer* target;
+       samplepos_t nframes = ::llabs (end_sample - start_sample);
+
+       RTMidiBuffer* rtmb = rt_midibuffer();
+
+       if (!rtmb || (rtmb->size() == 0)) {
+               /* no data to read, so do nothing */
+               return;
+       }
 
        if ((ms & MonitoringInput) == 0) {
 
        if ((ms & MonitoringInput) == 0) {
-               dst.clear();
+               /* Route::process_output_buffers() clears the buffer as-needed */
                target = &dst;
        } else {
                target = &scratch_bufs.get_midi (0);
        }
 
                target = &dst;
        } else {
                target = &scratch_bufs.get_midi (0);
        }
 
-       if (ms & MonitoringDisk) {
-               /* no disk data needed */
+       size_t events_read = 0;
 
 
-               Location* loc = loop_location;
+       if (!pending_overwrite() && (ms & MonitoringDisk)) {
 
 
-               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()));
+               /* disk data needed */
 
 
-               //cerr << "======== PRE ========\n";
-               //_midi_buf->dump (cerr);
-               //cerr << "----------------\n";
-
-               size_t events_read = 0;
+               Location* loc = _loop_location;
 
                if (loc) {
 
                if (loc) {
-                       framepos_t effective_start;
+                       samplepos_t effective_start;
 
 
-                       Evoral::Range<framepos_t> loop_range (loc->start(), loc->end() - 1);
-                       effective_start = loop_range.squish (playback_sample);
+                       Evoral::Range<samplepos_t> loop_range (loc->start(), loc->end() - 1);
+                       effective_start = loop_range.squish (start_sample);
 
 
-                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("looped, effective start adjusted to %1\n", effective_start));
+                       DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("looped, effective start adjusted to %1\n", effective_start));
 
                        if (effective_start == loc->start()) {
                                /* We need to turn off notes that may extend
                                   beyond the loop end.
                                */
 
 
                        if (effective_start == loc->start()) {
                                /* We need to turn off notes that may extend
                                   beyond the loop end.
                                */
 
-                               _midi_buf->resolve_tracker (*target, 0);
+                               _tracker.resolve_notes (*target, 0);
                        }
 
                        /* for split-cycles we need to offset the events */
                        }
 
                        /* for split-cycles we need to offset the events */
@@ -1270,222 +1119,154 @@ DiskReader::get_midi_playback (MidiBuffer& dst, framecnt_t nframes, MonitorState
                                   for the 2nd read
                                */
 
                                   for the 2nd read
                                */
 
-                               framecnt_t first, second;
+                               samplecnt_t first, second;
 
                                first = loc->end() - effective_start;
                                second = nframes - first;
 
 
                                first = loc->end() - effective_start;
                                second = nframes - first;
 
-                               DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read for eff %1 end %2: %3 and %4, cycle offset %5\n",
+                               DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("loop read for eff %1 end %2: %3 and %4, cycle offset %5\n",
                                                                                      effective_start, loc->end(), first, second));
 
                                if (first) {
                                                                                      effective_start, loc->end(), first, second));
 
                                if (first) {
-                                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #1, from %1 for %2\n",
+                                       DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("loop read #1, from %1 for %2\n",
                                                                                              effective_start, first));
                                                                                              effective_start, first));
-                                       events_read = _midi_buf->read (*target, effective_start, first);
+                                       events_read = rtmb->read (*target, effective_start, effective_start + first, _tracker);
                                }
 
                                if (second) {
                                }
 
                                if (second) {
-                                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #2, from %1 for %2\n",
+                                       DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("loop read #2, from %1 for %2\n",
                                                                                              loc->start(), second));
                                                                                              loc->start(), second));
-                                       events_read += _midi_buf->read (*target, loc->start(), second);
+                                       events_read += rtmb->read (*target, loc->start(), loc->start() + second, _tracker);
                                }
 
                        } 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 (*target, effective_start, effective_start + nframes);
+                               DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("loop read #3, adjusted start as %1 for %2\n",
+                                                                               effective_start, nframes));
+                               events_read = rtmb->read (*target, effective_start, effective_start + nframes, _tracker);
                        }
                } else {
                        }
                } 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;
-                       }
-                       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 (*target, playback_sample, playback_sample + nframes);
+                       DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("playback buffer read, from %1 to %2 (%3)", start_sample, end_sample, nframes));
+                       events_read = rtmb->read (*target, start_sample, end_sample, _tracker, Port::port_offset ());
                }
 
                }
 
-               DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose (
-                                    "%1 MDS events read %2 range %3 .. %4 rspace %5 wspace %6 r@%7 w@%8\n",
-                                    _name, events_read, playback_sample, playback_sample + nframes,
-                                    _midi_buf->read_space(), _midi_buf->write_space(),
-                                    _midi_buf->get_read_ptr(), _midi_buf->get_write_ptr()));
+               DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("%1 MDS events read %2 range %3 .. %4\n", _name, events_read, playback_sample, playback_sample + nframes));
        }
 
        }
 
-       g_atomic_int_add (&_frames_read_from_ringbuffer, nframes);
 
 
-       /* vari-speed */
+       if (!pending_overwrite() && !_no_disk_output && (ms & MonitoringInput)) {
+               dst.merge_from (*target, nframes);
+       }
 
 
-       if (speed != 0.0 && fabsf (speed) != 1.0f) {
+#if 0
+       if (!target->empty ()) {
+               cerr << "======== MIDI OUT ========\n";
                for (MidiBuffer::iterator i = target->begin(); i != target->end(); ++i) {
                for (MidiBuffer::iterator i = target->begin(); i != target->end(); ++i) {
-                       MidiBuffer::TimeType *tme = i.timeptr();
-                       *tme = (*tme) * nframes / playback_distance;
+                       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";
        }
        }
-
-       if (ms & MonitoringInput) {
-               dst.merge_from (*target, nframes);
-       }
-
-       //cerr << "======== POST ========\n";
-       //_midi_buf->dump (cerr);
-       //cerr << "----------------\n";
+#endif
 }
 
 }
 
-/** @a start is set to the new frame position (TIME) read up to */
 int
 int
-DiskReader::midi_read (framepos_t& start, framecnt_t dur, bool reversed)
+DiskReader::refill_midi ()
 {
 {
-       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);
-
-       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS::midi_read @ %1 cnt %2\n", start, dur));
-
-       boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack>(_route);
-       MidiChannelFilter* filter = mt ? &mt->playback_filter() : 0;
-       frameoffset_t loop_offset = 0;
-
-       if (!reversed && loc) {
-               get_location_times (loc, &loop_start, &loop_end, &loop_length);
-       }
-
-       while (dur) {
-
-               /* take any loop into account. we can't read past the end of the loop. */
+       /* nothing to do ... it's all in RAM thanks to overwrite */
+       return 0;
+}
 
 
-               if (loc && !reversed) {
+void
+DiskReader::dec_no_disk_output ()
+{
+       /* this is called unconditionally when things happen that ought to end
+          a period of "no disk output". It's OK for that to happen when there
+          was no corresponding call to ::inc_no_disk_output(), but we must
+          stop the value from becoming negative.
+       */
 
 
-                       if (!loop_range) {
-                               loop_range = new Evoral::Range<framepos_t> (loop_start, loop_end-1); // inclusive semantics require -1
+       do {
+               gint v  = g_atomic_int_get (&_no_disk_output);
+               if (v > 0) {
+                       if (g_atomic_int_compare_and_exchange (&_no_disk_output, v, v - 1)) {
+                               break;
                        }
                        }
-
-                       /* if we are (seamlessly) looping, ensure that the first frame we read is at the correct
-                          position within the loop.
-                       */
-
-                       effective_start = loop_range->squish (effective_start);
-
-                       if ((loop_end - effective_start) <= dur) {
-                               /* too close to end of loop to read "dur", so
-                                  shorten it.
-                               */
-                               this_read = loop_end - effective_start;
-                       } else {
-                               this_read = dur;
-                       }
-
                } else {
                } else {
-                       this_read = dur;
-               }
-
-               if (this_read == 0) {
                        break;
                }
                        break;
                }
+       } while (true);
+}
 
 
-               this_read = min (dur,this_read);
-
-               DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS ::read at %1 for %2 loffset %3\n", effective_start, this_read, loop_offset));
-
-               if (midi_playlist()->read (*_midi_buf, effective_start, this_read, loop_range, 0, filter) != this_read) {
-                       error << string_compose(
-                                       _("MidiDiskstream %1: cannot read %2 from playlist at frame %3"),
-                                       id(), this_read, start) << endmsg;
-                       return -1;
-               }
-
-               g_atomic_int_add (&_frames_written_to_ringbuffer, this_read);
-
-               if (reversed) {
-
-                       // Swap note ons with note offs here.  etc?
-                       // Fully reversing MIDI requires look-ahead (well, behind) to find previous
-                       // CC values etc.  hard.
-
-               } else {
-
-                       /* adjust passed-by-reference argument (note: this is
-                          monotonic and does not reflect looping.
-                       */
-                       start += this_read;
-
-                       /* similarly adjust effective_start, but this may be
-                          readjusted for seamless looping as we continue around
-                          the loop.
-                       */
-                       effective_start += this_read;
-               }
-
-               dur -= this_read;
-       }
-
-       return 0;
+DiskReader::DeclickAmp::DeclickAmp (samplecnt_t sample_rate)
+{
+       _a = 4550.f / (gain_t)sample_rate;
+       _l = -log1p (_a);
+       _g = 0;
 }
 
 }
 
-int
-DiskReader::refill_midi ()
+void
+DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, const float target)
 {
 {
-       if (!_playlists[DataType::MIDI]) {
-               return 0;
+       if (n_samples == 0) {
+               return;
        }
        }
+       float g = _g;
 
 
-       const size_t  write_space = _midi_buf->write_space();
-       const bool reversed    = _session.transport_speed() < 0.0f;
+       if (g == target) {
+               Amp::apply_simple_gain (buf, n_samples, target, 0);
+               return;
+       }
 
 
-       DEBUG_TRACE (DEBUG::DiskIO, string_compose ("MIDI refill, write space = %1 file frame = %2\n", write_space, file_frame[DataType::MIDI]));
+       const float a = _a;
+       Sample* const buffer = buf.data ();
 
 
-       /* no space to write */
-       if (write_space == 0) {
-               return 0;
+       const int max_nproc = 16;
+       uint32_t remain = n_samples;
+       uint32_t offset = 0;
+       while (remain > 0) {
+               uint32_t n_proc = remain > max_nproc ? max_nproc : remain;
+               for (uint32_t i = 0; i < n_proc; ++i) {
+                       buffer[offset + i] *= g;
+               }
+#if 1
+               g += a * (target - g);
+#else /* accurate exponential fade */
+               if (n_proc == max_nproc) {
+                       g += a * (target - g);
+               } else {
+                       g = target - (target - g) * expf (_l * n_proc / max_nproc);
+               }
+#endif
+               remain -= n_proc;
+               offset += n_proc;
        }
 
        }
 
-       if (reversed) {
-               return 0;
+       if (fabsf (g - target) < /* GAIN_COEFF_DELTA */ 1e-5) {
+               _g = target;
+       } else {
+               _g = g;
        }
        }
+}
 
 
-       /* at end: nothing to do */
-
-       framepos_t ffm = file_frame[DataType::MIDI];
+RTMidiBuffer*
+DiskReader::rt_midibuffer ()
+{
+       boost::shared_ptr<Playlist> pl = _playlists[DataType::MIDI];
 
 
-       if (ffm == max_framepos) {
+       if (!pl) {
                return 0;
        }
 
                return 0;
        }
 
-       int ret = 0;
-       const uint32_t frames_read = g_atomic_int_get (&_frames_read_from_ringbuffer);
-       const uint32_t frames_written = g_atomic_int_get (&_frames_written_to_ringbuffer);
+       boost::shared_ptr<MidiPlaylist> mpl = boost::dynamic_pointer_cast<MidiPlaylist> (pl);
 
 
-       if ((frames_read < frames_written) && (frames_written - frames_read) >= midi_readahead) {
+       if (!mpl) {
+               /* error, but whatever ... */
                return 0;
        }
 
                return 0;
        }
 
-       framecnt_t to_read = midi_readahead - ((framecnt_t)frames_written - (framecnt_t)frames_read);
-
-       to_read = min (to_read, (framecnt_t) (max_framepos - ffm));
-       to_read = min (to_read, (framecnt_t) write_space);
-
-       if (midi_read (ffm, to_read, reversed)) {
-               ret = -1;
-       }
-
-       file_frame[DataType::MIDI] = ffm;
-
-       return ret;
+       return mpl->rendered();
 }
 }
-
-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;
-}
-