Latency compensation for plugin thru routing.
authorRobin Gareus <robin@gareus.org>
Fri, 8 Apr 2016 16:21:46 +0000 (18:21 +0200)
committerRobin Gareus <robin@gareus.org>
Fri, 8 Apr 2016 16:21:46 +0000 (18:21 +0200)
libs/ardour/ardour/plugin_insert.h
libs/ardour/plugin_insert.cc

index 2064696800c77d825a635dc2adaa76955cfc2535..4a9d87c88c5833f7d932967a3acc3c072bc9855b 100644 (file)
@@ -28,6 +28,7 @@
 #include "ardour/ardour.h"
 #include "ardour/libardour_visibility.h"
 #include "ardour/chan_mapping.h"
+#include "ardour/fixed_delay.h"
 #include "ardour/io.h"
 #include "ardour/types.h"
 #include "ardour/parameter_descriptor.h"
@@ -206,6 +207,8 @@ class LIBARDOUR_API PluginInsert : public Processor
                }
        }
 
+       framecnt_t plugin_latency () const;
+
        bool has_sidechain () const {
                return _sidechain ? true : false;
        }
@@ -295,6 +298,8 @@ class LIBARDOUR_API PluginInsert : public Processor
        BufferSet _signal_analysis_inputs;
        BufferSet _signal_analysis_outputs;
 
+       FixedDelay _delaybuffers;
+
        ChanCount _configured_in;
        ChanCount _configured_internal; // with side-chain
        ChanCount _configured_out;
@@ -332,6 +337,9 @@ class LIBARDOUR_API PluginInsert : public Processor
 
        void start_touch (uint32_t param_id);
        void end_touch (uint32_t param_id);
+
+       void latency_changed (framecnt_t, framecnt_t);
+       bool _latency_changed;
 };
 
 } // namespace ARDOUR
index 55b592561cf3bac2925a9e3aa6be7a071a58479f..b522cb3379df4339777fafbd4355e71c7c43eaad 100644 (file)
@@ -331,6 +331,11 @@ PluginInsert::has_no_audio_inputs() const
        return _plugins[0]->get_info()->n_inputs.n_audio() == 0;
 }
 
+framecnt_t
+PluginInsert::plugin_latency () const {
+       return _plugins.front()->signal_latency ();
+}
+
 bool
 PluginInsert::is_midi_instrument() const
 {
@@ -472,11 +477,28 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
        PinMappings in_map (_in_map);
        PinMappings out_map (_out_map);
        ChanMapping thru_map (_thru_map);
-       if (_mapping_changed) {
+       if (_mapping_changed) { // ToDo use a counters, increment until match.
                _no_inplace = check_inplace ();
                _mapping_changed = false;
        }
 
+       if (_latency_changed) {
+               /* delaylines are configured with the max possible latency (as reported by the plugin)
+                * so this won't allocate memory (unless the plugin lied about its max latency)
+                * It may still 'click' though, since the fixed delaylines are not de-clicked.
+                * Then again plugin-latency changes are not click-free to begin with.
+                *
+                * This is also worst case, there is currently no concept of per-stream latency.
+                *
+                * e.g.  Two identical latent plugins:
+                *   1st plugin: process left (latent), bypass right.
+                *   2nd plugin: bypass left, process right (latent).
+                * -> currently this yields 2 times latency of the plugin,
+                */
+               _latency_changed = false;
+               _delaybuffers.set (ChanCount::max(bufs.count(), _configured_out), plugin_latency ());
+       }
+
        if (_match.method == Split && !_no_inplace) {
                // TODO: also use this optimization if one source-buffer
                // feeds _all_ *connected* inputs.
@@ -600,7 +622,7 @@ PluginInsert::connect_and_run (BufferSet& bufs, pframes_t nframes, framecnt_t of
                                uint32_t in_idx = thru_map.get (*t, out, &valid);
                                if (valid) {
                                        uint32_t m = out + natural_input_streams ().get (*t);
-                                       inplace_bufs.get (*t, m).read_from (bufs.get (*t, in_idx), nframes, offset, offset);
+                                       _delaybuffers.delay (*t, out, inplace_bufs.get (*t, m), bufs.get (*t, in_idx), nframes, offset, offset);
                                        used_outputs.set (*t, out, 1); // mark as used
                                }
                        }
@@ -1419,6 +1441,9 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
                PluginIoReConfigure (); /* EMIT SIGNAL */
        }
 
+       _delaybuffers.configure (_configured_out, _plugins.front()->max_latency ());
+       _latency_changed = true;
+
        // we don't know the analysis window size, so we must work with the
        // current buffer size here. each request for data fills in these
        // buffers and the analyser makes sure it gets enough data for the
@@ -2395,6 +2420,7 @@ PluginInsert::add_plugin (boost::shared_ptr<Plugin> plugin)
                plugin->ParameterChangedExternally.connect_same_thread (*this, boost::bind (&PluginInsert::parameter_changed_externally, this, _1, _2));
                plugin->StartTouch.connect_same_thread (*this, boost::bind (&PluginInsert::start_touch, this, _1));
                plugin->EndTouch.connect_same_thread (*this, boost::bind (&PluginInsert::end_touch, this, _1));
+               plugin->LatencyChanged.connect_same_thread (*this, boost::bind (&PluginInsert::latency_changed, this, _1, _2));
                // cache sidechain ports
                _cached_sidechain_pins.reset ();
                const ChanCount& nis (plugin->get_info()->n_inputs);
@@ -2434,6 +2460,13 @@ PluginInsert::monitoring_changed ()
        }
 }
 
+void
+PluginInsert::latency_changed (framecnt_t, framecnt_t)
+{
+       // this is called in RT context, LatencyChanged is emitted after run()
+       _latency_changed = true;
+}
+
 void
 PluginInsert::start_touch (uint32_t param_id)
 {