diff options
author | Robin Gareus <robin@gareus.org> | 2014-06-11 16:31:29 +0200 |
---|---|---|
committer | Robin Gareus <robin@gareus.org> | 2014-06-11 16:31:29 +0200 |
commit | 626b2daa8294e8ddb5526c1c6a9540f72b2bd063 (patch) | |
tree | b82ac669fc8d9d39f39e9a45f74519823e33ec14 /libs/ardour/delayline.cc | |
parent | 621e92f2681ec0134d1e64ec7265c0a588553d0d (diff) |
add delayline implementaion (in prep for latency compensation)
Diffstat (limited to 'libs/ardour/delayline.cc')
-rw-r--r-- | libs/ardour/delayline.cc | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/libs/ardour/delayline.cc b/libs/ardour/delayline.cc new file mode 100644 index 0000000000..8e24fd55ec --- /dev/null +++ b/libs/ardour/delayline.cc @@ -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; +} |