Fix importing to a fixed-point format with resampling
[ardour.git] / libs / ardour / delayline.cc
index 828d2fb96e9b214ae448fb6e67769d57eed5cbc6..548aad12ecce95177907a489d2709e6c35821978 100644 (file)
 
 #include "pbd/compose.h"
 
-#include "ardour/debug.h"
 #include "ardour/audio_buffer.h"
-#include "ardour/midi_buffer.h"
 #include "ardour/buffer_set.h"
+#include "ardour/debug.h"
 #include "ardour/delayline.h"
+#include "ardour/midi_buffer.h"
+#include "ardour/runtime_functions.h"
 
 using namespace std;
 using namespace PBD;
 using namespace ARDOUR;
 
 DelayLine::DelayLine (Session& s, const std::string& name)
-    : Processor (s, string_compose ("latency-compensation-%1", name))
-               , _delay(0)
-               , _pending_delay(0)
-               , _bsiz(0)
-               , _pending_bsiz(0)
-               , _roff(0)
-               , _woff(0)
-               , _pending_flush(false)
+    : Processor (s, string_compose ("latcomp-%1-%2", name, this))
+               , _bsiz (0)
+               , _delay (0)
+               , _pending_delay (0)
+               , _roff (0)
+               , _woff (0)
+               , _pending_flush (false)
 {
 }
 
@@ -48,176 +48,186 @@ DelayLine::~DelayLine ()
 {
 }
 
-#define FADE_LEN (16)
+bool
+DelayLine::set_name (const string& name)
+{
+       return Processor::set_name (string_compose ("latcomp-%1-%2", name, this));
+}
+
+#define FADE_LEN (128)
+
 void
-DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end_frame */, double /* speed */, pframes_t nsamples, bool)
+DelayLine::run (BufferSet& bufs, samplepos_t /* start_sample */, samplepos_t /* end_sample */, double /* speed */, pframes_t n_samples, bool)
 {
-       const uint32_t chn = _configured_output.n_audio();
-       pframes_t p0 = 0;
-       uint32_t c;
+#ifndef NDEBUG
+       Glib::Threads::Mutex::Lock lm (_set_delay_mutex, Glib::Threads::TRY_LOCK);
+       assert (lm.locked ());
+#endif
 
-       const frameoffset_t pending_delay = _pending_delay;
-       const frameoffset_t delay_diff = _delay - pending_delay;
+       const sampleoffset_t pending_delay = _pending_delay;
+       sampleoffset_t delay_diff = _delay - pending_delay;
        const bool pending_flush = _pending_flush;
        _pending_flush = false;
 
-       /* run() and set_delay() may be called in parallel by
-        * different threads.
-        * if a larger buffer is needed, it is allocated in
-        * set_delay(), here it is just swap'ed in place
-        */
-       if (_pending_bsiz) {
-               assert(_pending_bsiz >= _bsiz);
-
-               const size_t boff = _pending_bsiz - _bsiz;
-               if (_bsiz > 0) {
-                       /* write offset is retained. copy existing data to new buffer */
-                       frameoffset_t wl = _bsiz - _woff;
-                       memcpy(_pending_buf.get(), _buf.get(), sizeof(Sample) * _woff * chn);
-                       memcpy(_pending_buf.get() + (_pending_bsiz - wl) * chn, _buf.get() + _woff * chn, sizeof(Sample) * wl * chn);
-
-                       /* new buffer is all zero by default, fade into the existing data copied above */
-                       frameoffset_t wo = _pending_bsiz - wl;
-                       for (pframes_t pos = 0; pos < FADE_LEN; ++pos) {
-                               const gain_t gain = (gain_t)pos / (gain_t)FADE_LEN;
-                               for (c = 0; c < _configured_input.n_audio(); ++c) {
-                                       _pending_buf.get()[ wo * chn + c ] *= gain;
-                                       wo = (wo + 1) % (_pending_bsiz + 1);
+       // TODO handle pending_flush.
+
+       /* Audio buffers */
+       if (_buf.size () == bufs.count ().n_audio () && _buf.size () > 0) {
+
+               /* handle delay-changes first */
+               if (delay_diff < 0) {
+                       /* delay increases: fade out, insert silence, fade-in */
+                       const samplecnt_t fade_in_len = std::min (n_samples, (pframes_t)FADE_LEN);
+                       samplecnt_t fade_out_len;
+
+                       if (_delay < FADE_LEN) {
+                               /* if old delay was 0 or smaller than new-delay, add some data to fade.
+                                * Add at most (FADE_LEN - _delay) samples, but no more than -delay_diff
+                                */
+                               samplecnt_t add = std::min ((samplecnt_t)FADE_LEN - _delay, (samplecnt_t) -delay_diff);
+                               fade_out_len = std::min (_delay + add, (samplecnt_t)FADE_LEN);
+
+                               if (add > 0) {
+                                       AudioDlyBuf::iterator bi = _buf.begin ();
+                                       for (BufferSet::audio_iterator i = bufs.audio_begin (); i != bufs.audio_end (); ++i, ++bi) {
+                                               Sample* rb = (*bi).get ();
+                                               write_to_rb (rb, i->data (), add);
+                                       }
+                                       _woff = (_woff + add) & _bsiz_mask;
+                                       delay_diff += add;
                                }
+                       } else {
+                               fade_out_len = FADE_LEN;
                        }
 
-                       /* read-pointer will be moved and may up anywhere..
-                        * copy current data for smooth fade-out below
-                        */
-                       frameoffset_t roold = _roff;
-                       frameoffset_t ro = _roff;
-                       if (ro > _woff) {
-                               ro += boff;
-                       }
-                       ro += delay_diff;
-                       if (ro < 0) {
-                               ro -= (_pending_bsiz +1) * floor(ro / (float)(_pending_bsiz +1));
-                       }
-                       ro = ro % (_pending_bsiz + 1);
-                       for (pframes_t pos = 0; pos < FADE_LEN; ++pos) {
-                               for (c = 0; c < _configured_input.n_audio(); ++c) {
-                                       _pending_buf.get()[ ro * chn + c ] = _buf.get()[ roold * chn + c ];
-                                       ro = (ro + 1) % (_pending_bsiz + 1);
-                                       roold = (roold + 1) % (_bsiz + 1);
+                       /* fade-out, end of previously written data */
+                       for (AudioDlyBuf::iterator i = _buf.begin(); i != _buf.end (); ++i) {
+                               Sample* rb = (*i).get ();
+                               for (uint32_t s = 0; s < fade_out_len; ++s) {
+                                       sampleoffset_t off = (_woff + _bsiz - s) & _bsiz_mask;
+                                       rb[off] *= s / (float) fade_out_len;
                                }
-                       }
-               }
-
-               if (_roff > _woff) {
-                       _roff += boff;
-               }
-
-               // use shared_array::swap() ??
-               _buf = _pending_buf;
-               _bsiz = _pending_bsiz;
-               _pending_bsiz = 0;
-               _pending_buf.reset();
-       }
-
-       /* there may be no buffer when delay == 0.
-        * we also need to check audio-channels in case all audio-channels
-        * were removed in which case no new buffer was allocated. */
-       Sample *buf = _buf.get();
-       if (buf && _configured_output.n_audio() > 0) {
-
-               assert (_bsiz >= pending_delay);
-               const framecnt_t rbs = _bsiz + 1;
-
-               if (pending_delay != _delay || pending_flush) {
-                       const pframes_t fade_len = (nsamples >= FADE_LEN) ? FADE_LEN : nsamples / 2;
-
-                       DEBUG_TRACE (DEBUG::LatencyCompensation,
-                                       string_compose ("Old %1 delay: %2 bufsiz: %3 offset-diff: %4 write-offset: %5 read-offset: %6\n",
-                                               name(), _delay, _bsiz, ((_woff - _roff + rbs) % rbs), _woff, _roff));
-
-                       // fade out at old position
-                       c = 0;
-                       for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++c) {
-                               Sample * const data = i->data();
-                               for (pframes_t pos = 0; pos < fade_len; ++pos) {
-                                       const gain_t gain = (gain_t)(fade_len - pos) / (gain_t)fade_len;
-                                       buf[ _woff * chn + c ] = data[ pos ];
-                                       data[ pos ] = buf[ _roff * chn + c ] * gain;
-                                       _roff = (_roff + 1) % rbs;
-                                       _woff = (_woff + 1) % rbs;
+                               /* clear data in rb */
+                               // TODO optimize this using memset
+                               for (uint32_t s = 0; s < -delay_diff; ++s) {
+                                       sampleoffset_t off = (_woff + _bsiz + s) & _bsiz_mask;
+                                       rb[off] = 0.f;
                                }
                        }
 
-                       if (pending_flush) {
-                               DEBUG_TRACE (DEBUG::LatencyCompensation,
-                                               string_compose ("Flush buffer: %1\n", name()));
-                               memset(buf, 0, _configured_output.n_audio() * rbs * sizeof (Sample));
-                       }
-
-                       // adjust read pointer
-                       _roff += _delay - pending_delay;
+                       _woff = (_woff - delay_diff) & _bsiz_mask;
 
-                       if (_roff < 0) {
-                               _roff -= rbs * floor(_roff / (float)rbs);
+                       /* fade-in, directly apply to input buffer */
+                       for (BufferSet::audio_iterator i = bufs.audio_begin (); i != bufs.audio_end (); ++i) {
+                               Sample* src = i->data ();
+                               for (uint32_t s = 0; s < fade_in_len; ++s) {
+                                       src[s] *= s / (float) fade_in_len;
+                               }
                        }
-                       _roff = _roff % rbs;
-
-                       // fade in at new position
-                       c = 0;
-                       for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++c) {
-                               Sample * const data = i->data();
-                               for (pframes_t pos = fade_len; pos < 2 * fade_len; ++pos) {
-                                       const gain_t gain = (gain_t)(pos - fade_len) / (gain_t)fade_len;
-                                       buf[ _woff * chn + c ] = data[ pos ];
-                                       data[ pos ] = buf[ _roff * chn + c ] * gain;
-                                       _roff = (_roff + 1) % rbs;
-                                       _woff = (_woff + 1) % rbs;
+               } else if (delay_diff > 0) {
+                       /* delay decreases: cross-fade, if possible */
+                       const samplecnt_t fade_out_len = std::min (_delay, (samplecnt_t)FADE_LEN);
+                       const samplecnt_t fade_in_len = std::min (n_samples, (pframes_t)FADE_LEN);
+                       const samplecnt_t xfade_len = std::min (fade_out_len, fade_in_len);
+
+                       AudioDlyBuf::iterator bi = _buf.begin ();
+                       for (BufferSet::audio_iterator i = bufs.audio_begin (); i != bufs.audio_end (); ++i, ++bi) {
+                               Sample* rb = (*bi).get ();
+                               Sample* src = i->data ();
+
+                               // TODO consider handling fade_out & fade_in separately
+                               // if fade_out_len < fade_in_len.
+                               for (uint32_t s = 0; s < xfade_len; ++s) {
+                                       sampleoffset_t off = (_roff + s) & _bsiz_mask;
+                                       const gain_t g = s / (float) xfade_len;
+                                       src[s] *= g;
+                                       src[s] += (1.f - g) * rb[off];
                                }
                        }
-                       p0  = 2 * fade_len;
-
-                       _delay = pending_delay;
 
-                       DEBUG_TRACE (DEBUG::LatencyCompensation,
-                                       string_compose ("New %1 delay: %2 bufsiz: %3 offset-diff: %4 write-offset: %5 read-offset: %6\n",
-                                               name(), _delay, _bsiz, ((_woff - _roff + rbs) % rbs), _woff, _roff));
+#ifndef NDEBUG
+                       sampleoffset_t check = (_roff + delay_diff) & _bsiz_mask;
+#endif
+                       _roff = (_woff + _bsiz - pending_delay) & _bsiz_mask;
+#ifndef NDEBUG
+                       assert (_roff == check);
+#endif
                }
 
-               assert(_delay == ((_woff - _roff + rbs) % rbs));
+               /* set new delay */
+               _delay = pending_delay;
+
+               if (pending_flush) {
+                       /* fade out data after read-pointer, clear buffer until write-pointer */
+                       const samplecnt_t fade_out_len = std::min (_delay, (samplecnt_t)FADE_LEN);
 
-               c = 0;
-               for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++c) {
-                       Sample * const data = i->data();
-                       for (pframes_t pos = p0; pos < nsamples; ++pos) {
-                               buf[ _woff * chn + c ] = data[ pos ];
-                               data[ pos ] = buf[ _roff * chn + c ];
-                               _roff = (_roff + 1) % rbs;
-                               _woff = (_woff + 1) % rbs;
+                       for (AudioDlyBuf::iterator i = _buf.begin(); i != _buf.end (); ++i) {
+                               Sample* rb = (*i).get ();
+                               uint32_t s = 0;
+                               for (; s < fade_out_len; ++s) {
+                                       sampleoffset_t off = (_roff + s) & _bsiz_mask;
+                                       rb[off] *= 1. - (s / (float) fade_out_len);
+                               }
+                               for (; s < _delay; ++s) {
+                                       sampleoffset_t off = (_roff + s) & _bsiz_mask;
+                                       rb[off] = 0;
+                               }
+                               assert (_woff == ((_roff + s) & _bsiz_mask));
                        }
+                       // TODO consider adding a fade-in to bufs
                }
-       }
 
-       if (_midi_buf.get()) {
+               /* delay audio buffers */
+               assert (_delay == ((_woff - _roff + _bsiz) & _bsiz_mask));
+               AudioDlyBuf::iterator bi = _buf.begin ();
+               if (_delay == 0) {
+                       /* do nothing */
+               } else if (n_samples <= _delay) {
+                       /* write all samples to rb, read all from rb */
+                       for (BufferSet::audio_iterator i = bufs.audio_begin (); i != bufs.audio_end (); ++i, ++bi) {
+                               Sample* rb = (*bi).get ();
+                               write_to_rb (rb, i->data (), n_samples);
+                               read_from_rb (rb, i->data (), n_samples);
+                       }
+                       _roff = (_roff + n_samples) & _bsiz_mask;
+                       _woff = (_woff + n_samples) & _bsiz_mask;
+               } else {
+                       /* only write _delay samples to ringbuffer, memmove buffer */
+                       samplecnt_t tail = n_samples - _delay;
+                       for (BufferSet::audio_iterator i = bufs.audio_begin (); i != bufs.audio_end (); ++i, ++bi) {
+                               Sample* rb = (*bi).get ();
+                               Sample* src = i->data ();
+                               write_to_rb (rb, &src[tail], _delay);
+                               memmove (&src[_delay], src, tail * sizeof(Sample));
+                               read_from_rb (rb, src, _delay);
+                       }
+                       _roff = (_roff + _delay) & _bsiz_mask;
+                       _woff = (_woff + _delay) & _bsiz_mask;
+               }
+       } else {
+               /* set new delay for MIDI only */
                _delay = pending_delay;
+       }
 
-               for (BufferSet::midi_iterator i = bufs.midi_begin(); i != bufs.midi_end(); ++i) {
-                       if (i != bufs.midi_begin()) { break; } // XXX only one buffer for now
+       if (_midi_buf.get ()) {
+               for (BufferSet::midi_iterator i = bufs.midi_begin (); i != bufs.midi_end (); ++i) {
+                       if (i != bufs.midi_begin ()) { break; } // XXX only one buffer for now
 
-                       MidiBuffer* dly = _midi_buf.get();
+                       MidiBuffer* dly = _midi_buf.get ();
                        MidiBuffer& mb (*i);
                        if (pending_flush) {
-                               dly->silence(nsamples);
+                               dly->silence (n_samples);
                        }
 
                        // If the delay time changes, iterate over all events in the dly-buffer
                        // and adjust the time in-place. <= 0 becomes 0.
                        //
                        // iterate over all events in dly-buffer and subtract one cycle
-                       // (nsamples) from the timestamp, bringing them closer to de-queue.
-                       for (MidiBuffer::iterator m = dly->begin(); m != dly->end(); ++m) {
-                               MidiBuffer::TimeType *t = m.timeptr();
-                               if (*t > nsamples + delay_diff) {
-                                       *t -= nsamples + delay_diff;
+                       // (n_samples) from the timestamp, bringing them closer to de-queue.
+                       for (MidiBuffer::iterator m = dly->begin (); m != dly->end (); ++m) {
+                               MidiBuffer::TimeType *t = m.timeptr ();
+                               if (*t > n_samples + delay_diff) {
+                                       *t -= n_samples + delay_diff;
                                } else {
                                        *t = 0;
                                }
@@ -225,21 +235,21 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
 
                        if (_delay != 0) {
                                // delay events in current-buffer, in place.
-                               for (MidiBuffer::iterator m = mb.begin(); m != mb.end(); ++m) {
-                                       MidiBuffer::TimeType *t = m.timeptr();
+                               for (MidiBuffer::iterator m = mb.begin (); m != mb.end (); ++m) {
+                                       MidiBuffer::TimeType *t = m.timeptr ();
                                        *t += _delay;
                                }
                        }
 
-                       // move events from dly-buffer into current-buffer until nsamples
+                       // move events from dly-buffer into current-buffer until n_samples
                        // and remove them from the dly-buffer
-                       for (MidiBuffer::iterator m = dly->begin(); m != dly->end();) {
+                       for (MidiBuffer::iterator m = dly->begin (); m != dly->end ();) {
                                const Evoral::Event<MidiBuffer::TimeType> ev (*m, false);
-                               if (ev.time() >= nsamples) {
+                               if (ev.time () >= n_samples) {
                                        break;
                                }
-                               mb.insert_event(ev);
-                               m = dly->erase(m);
+                               mb.insert_event (ev);
+                               m = dly->erase (m);
                        }
 
                        /* For now, this is only relevant if there is there's a positive delay.
@@ -247,66 +257,52 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
                         * (ie '_global_port_buffer_offset + _port_buffer_offset' - midi_port.cc)
                         */
                        if (_delay != 0) {
-                               // move events after nsamples from current-buffer into dly-buffer
-                               // and trim current-buffer after nsamples
-                               for (MidiBuffer::iterator m = mb.begin(); m != mb.end();) {
+                               // move events after n_samples from current-buffer into dly-buffer
+                               // and trim current-buffer after n_samples
+                               for (MidiBuffer::iterator m = mb.begin (); m != mb.end ();) {
                                        const Evoral::Event<MidiBuffer::TimeType> ev (*m, false);
-                                       if (ev.time() < nsamples) {
+                                       if (ev.time () < n_samples) {
                                                ++m;
                                                continue;
                                        }
-                                       dly->insert_event(ev);
-                                       m = mb.erase(m);
+                                       dly->insert_event (ev);
+                                       m = mb.erase (m);
                                }
                        }
                }
        }
-
-       _delay = pending_delay;
 }
 
-void
-DelayLine::set_delay(framecnt_t signal_delay)
+bool
+DelayLine::set_delay (samplecnt_t signal_delay)
 {
+#ifndef NDEBUG
+       Glib::Threads::Mutex::Lock lm (_set_delay_mutex, Glib::Threads::TRY_LOCK);
+       assert (lm.locked ());
+#endif
+
        if (signal_delay < 0) {
                signal_delay = 0;
                cerr << "WARNING: latency compensation is not possible.\n";
        }
 
-       const framecnt_t rbs = signal_delay + 1;
+       if (signal_delay == _pending_delay) {
+               DEBUG_TRACE (DEBUG::LatencyCompensation,
+                               string_compose ("%1 set_delay - no change: %2 samples for %3 channels\n",
+                                       name (), signal_delay, _configured_output.n_audio ()));
+               return false;
+       }
 
        DEBUG_TRACE (DEBUG::LatencyCompensation,
                        string_compose ("%1 set_delay to %2 samples for %3 channels\n",
-                               name(), signal_delay, _configured_output.n_audio()));
+                               name (), signal_delay, _configured_output.n_audio ()));
 
-       if (signal_delay <= _bsiz) {
-               _pending_delay = signal_delay;
-               return;
-       }
-
-       if (_pending_bsiz) {
-               if (_pending_bsiz < signal_delay) {
-                       cerr << "LatComp: buffer resize in progress. "<< name() << "pending: "<< _pending_bsiz <<" want: " << signal_delay <<"\n"; // XXX
-               } else {
-                       _pending_delay = signal_delay;
-               }
-               return;
-       }
-
-       if (_configured_output.n_audio() > 0 ) {
-               _pending_buf.reset(new Sample[_configured_output.n_audio() * rbs]);
-               memset(_pending_buf.get(), 0, _configured_output.n_audio() * rbs * sizeof (Sample));
-               _pending_bsiz = signal_delay;
-       } else {
-               _pending_buf.reset();
-               _pending_bsiz = 0;
+       if (signal_delay + 8192 + 1 > _bsiz) {
+               allocate_pending_buffers (signal_delay, _configured_output);
        }
 
        _pending_delay = signal_delay;
-
-       DEBUG_TRACE (DEBUG::LatencyCompensation,
-                       string_compose ("allocated buffer for %1 of size %2\n",
-                               name(), signal_delay));
+       return true;
 }
 
 bool
@@ -316,37 +312,129 @@ DelayLine::can_support_io_configuration (const ChanCount& in, ChanCount& out)
        return true;
 }
 
+void
+DelayLine::allocate_pending_buffers (samplecnt_t signal_delay, ChanCount const& cc)
+{
+       assert (signal_delay >= 0);
+       samplecnt_t rbs = signal_delay + 8192 + 1;
+       rbs = std::max (_bsiz, rbs);
+
+       uint64_t power_of_two;
+       for (power_of_two = 1; 1 << power_of_two < rbs; ++power_of_two) {}
+       rbs = 1 << power_of_two;
+
+       if (cc.n_audio () == _buf.size () && _bsiz == rbs) {
+               return;
+       }
+
+       _buf.clear ();
+       if (cc.n_audio () == 0) {
+               return;
+       }
+
+       AudioDlyBuf pending_buf;
+       for (uint32_t i = 0; i < cc.n_audio (); ++i) {
+               boost::shared_array<Sample> b (new Sample[rbs]);
+               pending_buf.push_back (b);
+               memset (b.get (), 0, rbs * sizeof (Sample));
+       }
+
+       AudioDlyBuf::iterator bo = _buf.begin ();
+       AudioDlyBuf::iterator bn = pending_buf.begin ();
+
+       for (; bo != _buf.end () && bn != pending_buf.end(); ++bo, ++bn) {
+               Sample* rbo = (*bo).get ();
+               Sample* rbn = (*bn).get ();
+               if (_roff < _woff) {
+                       /* copy data between _roff .. _woff to new buffer */
+                       copy_vector (&rbn[_roff], &rbo[_roff], _woff - _roff);
+               } else {
+                       /* copy data between _roff .. old_size to end of new buffer, increment _roff
+                        * copy data from 0.._woff to beginning of new buffer
+                        */
+                       sampleoffset_t offset = rbs - _bsiz;
+                       copy_vector (&rbn[_roff + offset], &rbo[_roff], _bsiz - _roff);
+                       copy_vector (rbn, rbo, _woff);
+                       _roff += offset;
+                       assert (_roff < rbs);
+               }
+       }
+       _bsiz = rbs;
+       _bsiz_mask = _bsiz - 1;
+       _buf.swap (pending_buf);
+}
+
 bool
 DelayLine::configure_io (ChanCount in, ChanCount out)
 {
+#ifndef NDEBUG
+       Glib::Threads::Mutex::Lock lm (_set_delay_mutex, Glib::Threads::TRY_LOCK);
+       assert (lm.locked ());
+#endif
+
        if (out != in) { // always 1:1
                return false;
        }
 
-       // TODO realloc buffers if channel count changes..
-       // TODO support multiple midi buffers
+       if (_configured_output != out) {
+               allocate_pending_buffers (_pending_delay, out);
+       }
 
        DEBUG_TRACE (DEBUG::LatencyCompensation,
                        string_compose ("configure IO: %1 Ain: %2 Aout: %3 Min: %4 Mout: %5\n",
-                               name(), in.n_audio(), out.n_audio(), in.n_midi(), out.n_midi()));
+                               name (), in.n_audio (), out.n_audio (), in.n_midi (), out.n_midi ()));
 
-       if (in.n_midi() > 0 && !_midi_buf) {
-               _midi_buf.reset(new MidiBuffer(16384));
+       // TODO support multiple midi buffers
+       if (in.n_midi () > 0 && !_midi_buf) {
+               _midi_buf.reset (new MidiBuffer (16384));
        }
+#ifndef NDEBUG
+       lm.release ();
+#endif
 
        return Processor::configure_io (in, out);
 }
 
 void
-DelayLine::flush()
+DelayLine::flush ()
 {
        _pending_flush = true;
 }
 
 XMLNode&
-DelayLine::state (bool full_state)
+DelayLine::state ()
 {
-       XMLNode& node (Processor::state (full_state));
-       node.set_property("type", "delay");
+       XMLNode& node (Processor::state ());
+       node.set_property ("type", "delay");
        return node;
 }
+
+void
+DelayLine::write_to_rb (Sample* rb, Sample* src, samplecnt_t n_samples)
+{
+       assert (n_samples < _bsiz);
+       if (_woff + n_samples < _bsiz) {
+               copy_vector (&rb[_woff], src, n_samples);
+       } else {
+               const samplecnt_t s0 = _bsiz - _woff;
+               const samplecnt_t s1 = n_samples - s0;
+
+               copy_vector (&rb[_woff], src, s0);
+               copy_vector (rb, &src[s0], s1);
+       }
+}
+
+void
+DelayLine::read_from_rb (Sample* rb, Sample* dst, samplecnt_t n_samples)
+{
+       assert (n_samples < _bsiz);
+       if (_roff + n_samples < _bsiz) {
+               copy_vector (dst, &rb[_roff], n_samples);
+       } else {
+               const samplecnt_t s0 = _bsiz - _roff;
+               const samplecnt_t s1 = n_samples - s0;
+
+               copy_vector (dst, &rb[_roff], s0);
+               copy_vector (&dst[s0], rb, s1);
+       }
+}