Ongoing work on latency compensation
[ardour.git] / libs / ardour / delayline.cc
index 8e24fd55ec01b898af36fdc935b445684fae2d55..689fd58c8175116ea6eb70162ebf9438d738efd2 100644 (file)
@@ -33,7 +33,7 @@ using namespace PBD;
 using namespace ARDOUR;
 
 DelayLine::DelayLine (Session& s, const std::string& name)
-    : Processor (s, string_compose ("latency-compensation-%1", name))
+    : Processor (s, string_compose ("latency-compensation-%1-%2", name, this))
                , _delay(0)
                , _pending_delay(0)
                , _bsiz(0)
@@ -50,14 +50,14 @@ DelayLine::~DelayLine ()
 
 #define FADE_LEN (16)
 void
-DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end_frame */, pframes_t nsamples, bool)
+DelayLine::run (BufferSet& bufs, samplepos_t /* start_sample */, samplepos_t /* end_sample */, double /* speed */, 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 sampleoffset_t pending_delay = _pending_delay;
+       const sampleoffset_t delay_diff = _delay - pending_delay;
        const bool pending_flush = _pending_flush;
        _pending_flush = false;
 
@@ -72,15 +72,15 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
                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;
+                       sampleoffset_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;
+                       sampleoffset_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) {
+                               for (c = 0; c < _configured_output.n_audio(); ++c) {
                                        _pending_buf.get()[ wo * chn + c ] *= gain;
                                        wo = (wo + 1) % (_pending_bsiz + 1);
                                }
@@ -89,22 +89,22 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
                        /* 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;
+                       sampleoffset_t roold = _roff;
+                       sampleoffset_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 -= (_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) {
+                               for (c = 0; c < _configured_output.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);
                                }
+                               ro = (ro + 1) % (_pending_bsiz + 1);
+                               roold = (roold + 1) % (_bsiz + 1);
                        }
                }
 
@@ -112,9 +112,11 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
                        _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.
@@ -124,7 +126,7 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
        if (buf && _configured_output.n_audio() > 0) {
 
                assert (_bsiz >= pending_delay);
-               const framecnt_t rbs = _bsiz + 1;
+               const samplecnt_t rbs = _bsiz + 1;
 
                if (pending_delay != _delay || pending_flush) {
                        const pframes_t fade_len = (nsamples >= FADE_LEN) ? FADE_LEN : nsamples / 2;
@@ -135,16 +137,20 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
 
                        // fade out at old position
                        c = 0;
-                       for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++c) {
+                       for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end() && c <= chn; ++i, ++c) {
                                Sample * const data = i->data();
+                               sampleoffset_t roff = _roff;
+                               sampleoffset_t woff = _woff;
                                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;
+                                       buf[ woff * chn + c ] = data[ pos ];
+                                       data[ pos ] = buf[ roff * chn + c ] * gain;
+                                       roff = (roff + 1) % rbs;
+                                       woff = (woff + 1) % rbs;
                                }
                        }
+                       _roff = (_roff + fade_len) % rbs;
+                       _woff = (_woff + fade_len) % rbs;
 
                        if (pending_flush) {
                                DEBUG_TRACE (DEBUG::LatencyCompensation,
@@ -162,16 +168,20 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
 
                        // fade in at new position
                        c = 0;
-                       for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++c) {
+                       for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end() && c <= chn; ++i, ++c) {
                                Sample * const data = i->data();
+                               sampleoffset_t roff = _roff;
+                               sampleoffset_t woff = _woff;
                                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;
+                                       buf[ woff * chn + c ] = data[ pos ];
+                                       data[ pos ] = buf[ roff * chn + c ] * gain;
+                                       roff = (roff + 1) % rbs;
+                                       woff = (woff + 1) % rbs;
                                }
                        }
+                       _roff = (_roff + fade_len) % rbs;
+                       _woff = (_woff + fade_len) % rbs;
                        p0  = 2 * fade_len;
 
                        _delay = pending_delay;
@@ -184,15 +194,19 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
                assert(_delay == ((_woff - _roff + rbs) % rbs));
 
                c = 0;
-               for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end(); ++i, ++c) {
+               for (BufferSet::audio_iterator i = bufs.audio_begin(); i != bufs.audio_end() && c <= chn; ++i, ++c) {
                        Sample * const data = i->data();
+                       sampleoffset_t roff = _roff;
+                       sampleoffset_t woff = _woff;
                        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;
+                               buf[ woff * chn + c ] = data[ pos ];
+                               data[ pos ] = buf[ roff * chn + c ];
+                               roff = (roff + 1) % rbs;
+                               woff = (woff + 1) % rbs;
                        }
                }
+               _roff = (_roff + nsamples) % rbs;
+               _woff = (_woff + nsamples) % rbs;
        }
 
        if (_midi_buf.get()) {
@@ -221,16 +235,18 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
                                }
                        }
 
-                       // delay events in current-buffer, in place.
-                       for (MidiBuffer::iterator m = mb.begin(); m != mb.end(); ++m) {
-                               MidiBuffer::TimeType *t = m.timeptr();
-                               *t += _delay;
+                       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();
+                                       *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);
+                               const Evoral::Event<MidiBuffer::TimeType> ev (*m, false);
                                if (ev.time() >= nsamples) {
                                        break;
                                }
@@ -238,16 +254,22 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
                                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;
+                       /* For now, this is only relevant if there is there's a positive delay.
+                        * In the future this could also be used to delay 'too early' events
+                        * (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();) {
+                                       const Evoral::Event<MidiBuffer::TimeType> ev (*m, false);
+                                       if (ev.time() < nsamples) {
+                                               ++m;
+                                               continue;
+                                       }
+                                       dly->insert_event(ev);
+                                       m = mb.erase(m);
                                }
-                               dly->insert_event(ev);
-                               m = mb.erase(m);
                        }
                }
        }
@@ -256,15 +278,13 @@ DelayLine::run (BufferSet& bufs, framepos_t /* start_frame */, framepos_t /* end
 }
 
 void
-DelayLine::set_delay(framecnt_t signal_delay)
+DelayLine::set_delay(samplecnt_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()));
@@ -283,14 +303,7 @@ DelayLine::set_delay(framecnt_t 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;
-       }
+       allocate_pending_buffers (signal_delay);
 
        _pending_delay = signal_delay;
 
@@ -306,6 +319,22 @@ DelayLine::can_support_io_configuration (const ChanCount& in, ChanCount& out)
        return true;
 }
 
+void
+DelayLine::allocate_pending_buffers (samplecnt_t signal_delay)
+{
+       assert (signal_delay >= 0);
+       const samplecnt_t rbs = signal_delay + 1;
+
+       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;
+       }
+}
+
 bool
 DelayLine::configure_io (ChanCount in, ChanCount out)
 {
@@ -313,13 +342,20 @@ DelayLine::configure_io (ChanCount in, ChanCount out)
                return false;
        }
 
-       // TODO realloc buffers if channel count changes..
-       // TODO support multiple midi buffers
+       if (_configured_output != out) {
+               // run() won't be called concurrently, so it's
+               // save for replace existing _pending_buf.
+               //
+               // configure_io is either called with process-lock held
+               // from route's configure_io() or by use_target() from the c'tor.
+               allocate_pending_buffers (_pending_delay);
+       }
 
        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()));
 
+       // TODO support multiple midi buffers
        if (in.n_midi() > 0 && !_midi_buf) {
                _midi_buf.reset(new MidiBuffer(16384));
        }
@@ -337,6 +373,6 @@ XMLNode&
 DelayLine::state (bool full_state)
 {
        XMLNode& node (Processor::state (full_state));
-       node.add_property("type", "delay");
+       node.set_property("type", "delay");
        return node;
 }