add self-removing Sends (remove on disconnect)
authorRobin Gareus <robin@gareus.org>
Sun, 17 Apr 2016 12:01:09 +0000 (14:01 +0200)
committerRobin Gareus <robin@gareus.org>
Sun, 17 Apr 2016 12:01:09 +0000 (14:01 +0200)
The idea is to dynamically add/remove sends for feeding a sidechain
and re-use all existing "External Send" infrastructure in particular
latency compensation.

libs/ardour/ardour/route.h
libs/ardour/ardour/send.h
libs/ardour/route.cc
libs/ardour/send.cc

index c8f60058725c9cca521ba430c4d662bdced31b1c..b8060f059d3dbd03ecf85dc4655da198d330988c 100644 (file)
@@ -836,6 +836,10 @@ class LIBARDOUR_API Route : public SessionObject, public Automatable, public Rou
        void output_change_handler (IOChange, void *src);
        void sidechain_change_handler (IOChange, void *src);
 
+       void processor_selfdestruct (boost::weak_ptr<Processor>);
+       std::vector<boost::weak_ptr<Processor> > selfdestruct_sequence;
+       Glib::Threads::Mutex  selfdestruct_lock;
+
        bool input_port_count_changing (ChanCount);
        bool output_port_count_changing (ChanCount);
 
index 3b0e8a4309ae8ac51c83c9a5bcb0fa1388c20da7..967957d854079771a142ec4a14d86908195333fc 100644 (file)
@@ -55,6 +55,10 @@ class LIBARDOUR_API Send : public Delivery
        XMLNode& get_state ();
        int set_state(const XMLNode&, int version);
 
+       PBD::Signal0<void> SelfDestruct;
+       void set_remove_on_disconnect (bool b) { _remove_on_disconnect = b; }
+       bool remove_on_disconnect () const { return _remove_on_disconnect; }
+
        uint32_t pans_required() const { return _configured_input.n_audio(); }
 
        void run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, pframes_t nframes, bool);
@@ -89,6 +93,7 @@ class LIBARDOUR_API Send : public Delivery
        /* disallow copy construction */
        Send (const Send&);
        void panshell_changed ();
+       void snd_output_changed (IOChange, void*);
 
        int set_state_2X (XMLNode const &, int);
 
@@ -96,6 +101,7 @@ class LIBARDOUR_API Send : public Delivery
 
        framecnt_t _delay_in;
        framecnt_t _delay_out;
+       bool       _remove_on_disconnect;
 };
 
 } // namespace ARDOUR
index 42807bcf03290c929f8d3fb6da8dae1afda1486c..edb9542d1b1e49e74073289e430c091228bb9595 100644 (file)
@@ -1339,9 +1339,26 @@ Route::add_processor (boost::shared_ptr<Processor> processor, boost::shared_ptr<
        processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
        set_processor_positions ();
 
+       boost::shared_ptr<Send> send;
+       if ((send = boost::dynamic_pointer_cast<Send> (processor))) {
+               send->SelfDestruct.connect_same_thread (*this,
+                               boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr<Processor> (processor)));
+       }
+
        return 0;
 }
 
+void
+Route::processor_selfdestruct (boost::weak_ptr<Processor> wp)
+{
+       /* We cannot destruct the processor here (usually RT-thread
+        * with various locks held - in case of sends also io_locks).
+        * Queue for deletion in low-priority thread.
+        */
+       Glib::Threads::Mutex::Lock lx (selfdestruct_lock);
+       selfdestruct_sequence.push_back (wp);
+}
+
 bool
 Route::add_processor_from_xml_2X (const XMLNode& node, int version)
 {
@@ -3362,6 +3379,9 @@ Route::set_processor_state (const XMLNode& node)
                                } else if (prop->value() == "send") {
 
                                        processor.reset (new Send (_session, _pannable, _mute_master, Delivery::Send, true));
+                                       boost::shared_ptr<Send> send = boost::dynamic_pointer_cast<Send> (processor);
+                                       send->SelfDestruct.connect_same_thread (*this,
+                                                       boost::bind (&Route::processor_selfdestruct, this, boost::weak_ptr<Processor> (processor)));
 
                                } else {
                                        error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg;
@@ -4108,13 +4128,12 @@ Route::apply_processor_changes_rt ()
                g_atomic_int_set (&_pending_signals, emissions);
                return true;
        }
-       return false;
+       return (!selfdestruct_sequence.empty ());
 }
 
 void
 Route::emit_pending_signals ()
 {
-
        int sig = g_atomic_int_and (&_pending_signals, 0);
        if (sig & EmitMeterChanged) {
                _meter->emit_configuration_changed();
@@ -4128,6 +4147,24 @@ Route::emit_pending_signals ()
        if (sig & EmitRtProcessorChange) {
                processors_changed (RouteProcessorChange (RouteProcessorChange::RealTimeChange)); /* EMIT SIGNAL */
        }
+
+       /* this would be a job for the butler.
+        * Conceptually we should not take processe/processor locks here.
+        * OTOH its more efficient (less overhead for summoning the butler and
+        * telling her what do do) and signal emission is called
+        * directly after the process callback, which decreases the chance
+        * of x-runs when taking the locks.
+        */
+       while (!selfdestruct_sequence.empty ()) {
+               Glib::Threads::Mutex::Lock lx (selfdestruct_lock);
+               if (selfdestruct_sequence.empty ()) { break; } // re-check with lock
+               boost::shared_ptr<Processor> proc = selfdestruct_sequence.back ().lock ();
+               selfdestruct_sequence.pop_back ();
+               lx.release ();
+               if (proc) {
+                       remove_processor (proc);
+               }
+       }
 }
 
 void
index 23ae860f47123fc7f1893d82e83d94cdc096c04d..ebe8c46c8f9f0061e981f02ef361ae1a807a0d39 100644 (file)
@@ -77,6 +77,7 @@ Send::Send (Session& s, boost::shared_ptr<Pannable> p, boost::shared_ptr<MuteMas
        , _metering (false)
        , _delay_in (0)
        , _delay_out (0)
+       , _remove_on_disconnect (false)
 {
        if (_role == Listen) {
                /* we don't need to do this but it keeps things looking clean
@@ -99,6 +100,9 @@ Send::Send (Session& s, boost::shared_ptr<Pannable> p, boost::shared_ptr<MuteMas
        if (panner_shell()) {
                panner_shell()->Changed.connect_same_thread (*this, boost::bind (&Send::panshell_changed, this));
        }
+       if (_output) {
+               _output->changed.connect_same_thread (*this, boost::bind (&Send::snd_output_changed, this, _1, _2));
+       }
 }
 
 Send::~Send ()
@@ -221,6 +225,8 @@ Send::state (bool full)
                node.add_property ("bitslot", buf);
        }
 
+       node.add_property("selfdestruct", _remove_on_disconnect ? "yes" : "no");
+
        node.add_child_nocopy (_amp->state (full));
 
        return node;
@@ -268,6 +274,10 @@ Send::set_state (const XMLNode& node, int version)
                }
        }
 
+       if ((prop = node.property (X_("selfdestruct"))) != 0) {
+               _remove_on_disconnect = string_is_affirmative (prop->value());
+       }
+
        XMLNodeList nlist = node.children();
        for (XMLNodeIterator i = nlist.begin(); i != nlist.end(); ++i) {
                if ((*i)->name() == X_("Processor")) {
@@ -403,3 +413,14 @@ Send::value_as_string (boost::shared_ptr<AutomationControl> ac) const
 {
        return _amp->value_as_string (ac);
 }
+
+void
+Send::snd_output_changed (IOChange change, void* /*src*/)
+{
+       if (change.type & IOChange::ConnectionsChanged) {
+               if (!_output->connected() && _remove_on_disconnect) {
+                       _remove_on_disconnect = false;
+                       SelfDestruct (); /* EMIT SIGNAL */
+               }
+       }
+}