add delayline implementaion (in prep for latency compensation)
[ardour.git] / libs / ardour / delayline.cc
diff --git a/libs/ardour/delayline.cc b/libs/ardour/delayline.cc
new file mode 100644 (file)
index 0000000..8e24fd5
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+    Copyright (C) 2006, 2013 Paul Davis
+    Copyright (C) 2013, 2014 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.,
+    675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <assert.h>
+#include <cmath>
+
+#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/delayline.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)
+{
+}
+
+DelayLine::~DelayLine ()
+{
+}
+
+#define FADE_LEN (16)
+void
+DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end_frame */, pframes_t nsamples, bool)
+{
+       const uint32_t chn = _configured_output.n_audio();
+       pframes_t p0 = 0;
+       uint32_t c;
+
+       const frameoffset_t pending_delay = _pending_delay;
+       const frameoffset_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);
+                               }
+                       }
+
+                       /* 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);
+                               }
+                       }
+               }
+
+               if (_roff > _woff) {
+                       _roff += boff;
+               }
+
+               _buf = _pending_buf;
+               _bsiz = _pending_bsiz;
+               _pending_bsiz = 0;
+       }
+
+       /* 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;
+                               }
+                       }
+
+                       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;
+
+                       if (_roff < 0) {
+                               _roff -= rbs * floor(_roff / (float)rbs);
+                       }
+                       _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;
+                               }
+                       }
+                       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));
+               }
+
+               assert(_delay == ((_woff - _roff + rbs) % rbs));
+
+               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;
+                       }
+               }
+       }
+
+       if (_midi_buf.get()) {
+               _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
+
+                       MidiBuffer* dly = _midi_buf.get();
+                       MidiBuffer& mb (*i);
+                       if (pending_flush) {
+                               dly->silence(nsamples);
+                       }
+
+                       // 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;
+                               } else {
+                                       *t = 0;
+                               }
+                       }
+
+                       // delay events in current-buffer, in place.
+                       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
+                       // and remove them from the dly-buffer
+                       for (MidiBuffer::iterator m = dly->begin(); m != dly->end();) {
+                               const Evoral::MIDIEvent<MidiBuffer::TimeType> ev (*m, false);
+                               if (ev.time() >= nsamples) {
+                                       break;
+                               }
+                               mb.insert_event(ev);
+                               m = dly->erase(m);
+                       }
+
+                       // 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();) {
+                               const Evoral::MIDIEvent<MidiBuffer::TimeType> ev (*m, false);
+                               if (ev.time() < nsamples) {
+                                       ++m;
+                                       continue;
+                               }
+                               dly->insert_event(ev);
+                               m = mb.erase(m);
+                       }
+               }
+       }
+
+       _delay = pending_delay;
+}
+
+void
+DelayLine::set_delay(framecnt_t signal_delay)
+{
+       if (signal_delay < 0) {
+               signal_delay = 0;
+               cerr << "WARNING: latency compensation is not possible.\n";
+       }
+
+       const framecnt_t rbs = signal_delay + 1;
+
+       DEBUG_TRACE (DEBUG::LatencyCompensation,
+                       string_compose ("%1 set_delay to %2 samples for %3 channels\n",
+                               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;
+       }
+
+       _pending_delay = signal_delay;
+
+       DEBUG_TRACE (DEBUG::LatencyCompensation,
+                       string_compose ("allocated buffer for %1 of size %2\n",
+                               name(), signal_delay));
+}
+
+bool
+DelayLine::can_support_io_configuration (const ChanCount& in, ChanCount& out)
+{
+       out = in;
+       return true;
+}
+
+bool
+DelayLine::configure_io (ChanCount in, ChanCount out)
+{
+       if (out != in) { // always 1:1
+               return false;
+       }
+
+       // TODO realloc buffers if channel count changes..
+       // TODO support multiple midi buffers
+
+       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()));
+
+       if (in.n_midi() > 0 && !_midi_buf) {
+               _midi_buf.reset(new MidiBuffer(16384));
+       }
+
+       return Processor::configure_io (in, out);
+}
+
+void
+DelayLine::flush()
+{
+       _pending_flush = true;
+}
+
+XMLNode&
+DelayLine::state (bool full_state)
+{
+       XMLNode& node (Processor::state (full_state));
+       node.add_property("type", "delay");
+       return node;
+}