diff --git a/libs/ardour/ardour/fixed_delay.h b/libs/ardour/ardour/fixed_delay.h index c98b40c315..21eaa6567e 100644 --- a/libs/ardour/ardour/fixed_delay.h +++ b/libs/ardour/ardour/fixed_delay.h @@ -26,22 +26,67 @@ namespace ARDOUR { class ChanCount; +/** Multichannel Audio/Midi Delay Line + * + * This is an efficient delay line operating directly on Ardour buffers. + * The drawback is that there is no thread safety: + * All calls need to be executed in the same thread. + * + * After configuration, the delay can be changed safely up to the maximum + * configured delay but doing so flushes the buffer. There is no de-clicking + * (see ARDOUR::Delayline for those cases). + * + * Increasing the delay above the max configured or requesting more + * buffers will allocate the required space (not realtime safe). + * + * All buffers part of the set are treated separately. + */ class LIBARDOUR_API FixedDelay { public: FixedDelay (); ~FixedDelay (); - void configure (const ChanCount& count, framecnt_t); - void set (const ChanCount& count, framecnt_t); + /** initial configuration, usually done after instantiation + * + * @param count Channel Count (audio+midi) + * @param max_delay the maximum number of samples to delay + * @param shrink when false already allocated buffers are kept if both channel-count and max-delay requirements are satisified + */ + void configure (const ChanCount& count, framecnt_t max_delay, bool shrink = true); - void delay (ARDOUR::DataType, uint32_t, Buffer&, const Buffer&, pframes_t, framecnt_t dst_offset = 0, framecnt_t src_offset = 0); - void flush() { _pending_flush = true; } + /** set delay time and update active process buffers + * + * This calls configure with shrink = false and sets the current delay time + * if the delay time mismatches, the buffers are silenced (zeroed). + * + * @param count channels to be processed + * @param delay number of audio samples to delay + */ + void set (const ChanCount& count, framecnt_t delay); + + /** process a channel + * + * Read N samples from the input buffer, delay them by the configured delay-time and write + * the delayed samples to the output buffer at the given offset. + * + * @param dt datatype + * @param id buffer number (starting at 0) + * @param out output buffer to write data to + * @param in input buffer to read data from + * @param n_samples number of samples to process (must be <= 8192) + * @param dst_offset offset in output buffer to start writing to + * @param src_offset offset in input buffer to start reading from + */ + void delay (ARDOUR::DataType dt, uint32_t id, Buffer& out, const Buffer& in, pframes_t n_samples, framecnt_t dst_offset = 0, framecnt_t src_offset = 0); + + /** zero all buffers */ + void flush(); private: framecnt_t _max_delay; + framecnt_t _buf_size; framecnt_t _delay; - bool _pending_flush; ChanCount _count; struct DelayBuffer { diff --git a/libs/ardour/fixed_delay.cc b/libs/ardour/fixed_delay.cc index d59619cdf9..f4c8b981ab 100644 --- a/libs/ardour/fixed_delay.cc +++ b/libs/ardour/fixed_delay.cc @@ -25,8 +25,8 @@ using namespace ARDOUR; FixedDelay::FixedDelay () : _max_delay (0) + , _buf_size (0) , _delay (0) - , _pending_flush (false) { for (size_t i = 0; i < DataType::num_types; ++i) { _buffers.push_back (BufferVec ()); @@ -39,25 +39,6 @@ FixedDelay::~FixedDelay () clear (); } -void -FixedDelay::configure (const ChanCount& count, framecnt_t max_delay) -{ - if (max_delay <= _max_delay || count <= _count) { - return; - } - _max_delay = std::max (_max_delay, max_delay); - for (DataType::iterator i = DataType::begin (); i != DataType::end (); ++i) { - ensure_buffers (*i, count.get (*i), _max_delay + 1); - } -} - -void -FixedDelay::set (const ChanCount& count, framecnt_t delay) -{ - configure (count, delay); - _delay = delay; -} - void FixedDelay::ensure_buffers (DataType type, size_t num_buffers, size_t buffer_capacity) { @@ -92,6 +73,47 @@ FixedDelay::clear () _count.reset (); } +void +FixedDelay::flush() +{ + for (std::vector::iterator i = _buffers.begin (); i != _buffers.end (); ++i) { + for (BufferVec::iterator j = (*i).begin (); j != (*i).end (); ++j) { + (*j)->buf->silence (_buf_size); + } + } +} + +void +FixedDelay::configure (const ChanCount& count, framecnt_t max_delay, bool shrink) +{ + if (shrink) { + if (max_delay == _max_delay && count == _count) { + return; + } + _max_delay = max_delay; + } else if (max_delay <= _max_delay || count <= _count) { + return; + _max_delay = std::max (_max_delay, max_delay); + } + + // max possible (with all engines and during export) + static const framecnt_t max_block_length = 8192; + _buf_size = _max_delay + max_block_length; + for (DataType::iterator i = DataType::begin (); i != DataType::end (); ++i) { + ensure_buffers (*i, count.get (*i), _buf_size); + } +} + +void +FixedDelay::set (const ChanCount& count, framecnt_t delay) +{ + configure (count, delay, false); + if (_delay != delay) { + flush (); + } + _delay = delay; +} + void FixedDelay::delay ( ARDOUR::DataType dt, uint32_t id, @@ -108,25 +130,25 @@ FixedDelay::delay ( assert (id < _buffers[dt].size ()); DelayBuffer *db = _buffers[dt][id]; - if (db->pos + n_frames > _max_delay) { - uint32_t w0 = _max_delay - db->pos; - uint32_t w1 = db->pos + n_frames - _max_delay; + if (db->pos + n_frames > _buf_size) { + uint32_t w0 = _buf_size - db->pos; + uint32_t w1 = db->pos + n_frames - _buf_size; db->buf->read_from (in, w0, db->pos, src_offset); db->buf->read_from (in, w1, 0, src_offset + w0); } else { db->buf->read_from (in, n_frames, db->pos, src_offset); } - uint32_t rp = (db->pos + _max_delay - _delay) % _max_delay; + uint32_t rp = (db->pos + _buf_size - _delay) % _buf_size; - if (rp + n_frames > _max_delay) { - uint32_t r0 = _max_delay - rp; - uint32_t r1 = rp + n_frames - _max_delay; + if (rp + n_frames > _buf_size) { + uint32_t r0 = _buf_size - rp; + uint32_t r1 = rp + n_frames - _buf_size; out.read_from (*db->buf, r0, dst_offset, rp); out.read_from (*db->buf, r1, dst_offset + r0, 0); } else { out.read_from (*db->buf, n_frames, dst_offset, rp); } - db->pos = (db->pos + n_frames) % _max_delay; + db->pos = (db->pos + n_frames) % _buf_size; }