convert GenericMIDI into a real control protocol module, with its own event loop...
authorPaul Davis <paul@linuxaudiosystems.com>
Tue, 31 Dec 2019 19:06:00 +0000 (12:06 -0700)
committerPaul Davis <paul@linuxaudiosystems.com>
Tue, 31 Dec 2019 19:06:14 +0000 (12:06 -0700)
libs/surfaces/generic_midi/generic_midi_control_protocol.cc
libs/surfaces/generic_midi/generic_midi_control_protocol.h

index ec69b286213fe155409955070e90e82e10128929..c5ba0b7949e30111a406f94d4308fa9deb4aa1de 100644 (file)
@@ -41,6 +41,7 @@
 #include "pbd/error.h"
 #include "pbd/failed_constructor.h"
 #include "pbd/file_utils.h"
+#include "pbd/i18n.h"
 #include "pbd/strsplit.h"
 #include "pbd/types_convert.h"
 #include "pbd/xml++.h"
 #include "midifunction.h"
 #include "midiaction.h"
 
+#include "pbd/abstract_ui.cc" // instantiate template
+
 using namespace ARDOUR;
 using namespace PBD;
+using namespace Glib;
 using namespace std;
 
-#include "pbd/i18n.h"
-
-#define midi_ui_context() MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
-
 GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
        : ControlProtocol (s, _("Generic MIDI"))
+       , AbstractUI<GenericMIDIRequest> (name())
        , connection_state (ConnectionState (0))
        , _motorised (false)
        , _threshold (10)
        , gui (0)
 {
-       // _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort> (s.midi_input_port ());
-       // _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort> (s.midi_output_port ());
+       boost::shared_ptr<ARDOUR::Port> inp;
+       boost::shared_ptr<ARDOUR::Port> outp;
+
+       inp  = AudioEngine::instance()->register_input_port (DataType::MIDI, _("MIDI Control In"), true);
+       outp = AudioEngine::instance()->register_output_port (DataType::MIDI, _("MIDI Control Out"), true);
+
+       if (inp == 0 || outp == 0) {
+               throw failed_constructor();
+       }
+
+       _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(inp);
+       _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(outp);
 
        _input_bundle.reset (new ARDOUR::Bundle (_("Generic MIDI Control In"), true));
        _output_bundle.reset (new ARDOUR::Bundle (_("Generic MIDI Control Out"), false));
 
        _input_bundle->add_channel (
-               boost::static_pointer_cast<MidiPort>(_input_port)->name(),
+               inp->name(),
                ARDOUR::DataType::MIDI,
-               session->engine().make_port_name_non_relative (boost::static_pointer_cast<MidiPort>(_input_port)->name())
+               session->engine().make_port_name_non_relative (inp->name())
                );
 
        _output_bundle->add_channel (
-               boost::static_pointer_cast<MidiPort>(_output_port)->name(),
+               outp->name(),
                ARDOUR::DataType::MIDI,
-               session->engine().make_port_name_non_relative (boost::static_pointer_cast<MidiPort>(_output_port)->name())
+               session->engine().make_port_name_non_relative (outp->name())
                );
 
        session->BundleAddedOrRemoved ();
@@ -105,13 +116,13 @@ GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
        _current_bank = 0;
        _bank_size = 0;
 
-       /* these signals are emitted by the MidiControlUI's event loop thread
+       /* these signals are emitted by our event loop thread
         * and we may as well handle them right there in the same the same
         * thread
         */
 
        Controllable::StartLearning.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::start_learning, this, _1));
-                                        Controllable::StopLearning.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::stop_learning, this, _1));
+       Controllable::StopLearning.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::stop_learning, this, _1));
 
        /* this signal is emitted by the process() callback, and if
         * send_feedback() is going to do anything, it should do it in the
@@ -119,22 +130,36 @@ GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
         */
 
        Session::SendFeedback.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::send_feedback, this));
-       //Session::SendFeedback.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::send_feedback, this), midi_ui_context());;
 
        /* this one is cross-thread */
 
-       PresentationInfo::Change.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::reset_controllables, this), midi_ui_context());
+       PresentationInfo::Change.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::reset_controllables, this), this);
 
        /* Catch port connections and disconnections (cross-thread) */
        ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR,
                                                                              boost::bind (&GenericMidiControlProtocol::connection_handler, this, _1, _2, _3, _4, _5),
-                                                                             midi_ui_context());
+                                                                             this);
 
        reload_maps ();
 }
 
 GenericMidiControlProtocol::~GenericMidiControlProtocol ()
 {
+       if (_input_port) {
+               DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("unregistering input port %1\n", boost::shared_ptr<ARDOUR::Port>(_input_port)->name()));
+               Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
+               AudioEngine::instance()->unregister_port (_input_port);
+               _input_port.reset ();
+       }
+
+       if (_output_port) {
+               _output_port->drain (10000,  250000); /* check every 10 msecs, wait up to 1/4 second for the port to drain */
+               DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("unregistering output port %1\n", boost::shared_ptr<ARDOUR::Port>(_output_port)->name()));
+               Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
+               AudioEngine::instance()->unregister_port (_output_port);
+               _output_port.reset ();
+       }
+
        drop_all ();
        tear_down_gui ();
 }
@@ -279,12 +304,57 @@ GenericMidiControlProtocol::drop_bindings ()
        _current_bank = 0;
 }
 
+void
+GenericMidiControlProtocol::do_request (GenericMIDIRequest* req)
+{
+       if (req->type == CallSlot) {
+
+               call_slot (MISSING_INVALIDATOR, req->the_slot);
+
+       } else if (req->type == Quit) {
+
+               stop ();
+       }
+}
+
 int
-GenericMidiControlProtocol::set_active (bool /*yn*/)
+GenericMidiControlProtocol::stop ()
 {
-       /* nothing to do here: the MIDI UI thread in libardour handles all our
-          I/O needs.
-       */
+       BaseUI::quit ();
+
+       return 0;
+}
+
+void
+GenericMidiControlProtocol::thread_init ()
+{
+       pthread_set_name (event_loop_name().c_str());
+
+       PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
+       ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
+
+       set_thread_priority ();
+}
+
+int
+GenericMidiControlProtocol::set_active (bool yn)
+{
+       DEBUG_TRACE (DEBUG::GenericMidi, string_compose("GenericMIDI::set_active init with yn: '%1'\n", yn));
+
+       if (yn == active()) {
+               return 0;
+       }
+
+       if (yn) {
+               BaseUI::run ();
+       } else {
+               BaseUI::quit ();
+       }
+
+       ControlProtocol::set_active (yn);
+
+       DEBUG_TRACE (DEBUG::GenericMidi, string_compose("GenericMIDI::set_active done with yn: '%1'\n", yn));
+
        return 0;
 }
 
@@ -539,6 +609,17 @@ GenericMidiControlProtocol::get_state ()
 {
        XMLNode& node (ControlProtocol::get_state());
 
+
+       XMLNode* child;
+
+       child = new XMLNode (X_("Input"));
+       child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_input_port)->get_state());
+       node.add_child_nocopy (*child);
+
+       child = new XMLNode (X_("Output"));
+       child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_output_port)->get_state());
+       node.add_child_nocopy (*child);
+
        node.set_property (X_("feedback-interval"), _feedback_interval);
        node.set_property (X_("threshold"), _threshold);
        node.set_property (X_("motorized"), _motorised);
@@ -572,11 +653,26 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
 {
        XMLNodeList nlist;
        XMLNodeConstIterator niter;
+       XMLNode const* child;
 
        if (ControlProtocol::set_state (node, version)) {
                return -1;
        }
 
+       if ((child = node.child (X_("Input"))) != 0) {
+               XMLNode* portnode = child->child (Port::state_node_name.c_str());
+               if (portnode) {
+                       boost::shared_ptr<ARDOUR::Port>(_input_port)->set_state (*portnode, version);
+               }
+       }
+
+       if ((child = node.child (X_("Output"))) != 0) {
+               XMLNode* portnode = child->child (Port::state_node_name.c_str());
+               if (portnode) {
+                       boost::shared_ptr<ARDOUR::Port>(_output_port)->set_state (*portnode, version);
+               }
+       }
+
        if (!node.get_property ("feedback-interval", _feedback_interval)) {
                _feedback_interval = 10000;
        }
@@ -623,15 +719,18 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
 
        if (load_dynamic_bindings) {
                Glib::Threads::Mutex::Lock lm2 (controllables_lock);
-               nlist = node.children(); // "Controls"
+               XMLNode* controls_node = node.child (X_("Controls"));
 
-               if (!nlist.empty()) {
-                       nlist = nlist.front()->children(); // "MIDIControllable" ...
+               if (controls_node) {
+
+                       nlist = controls_node->children();
 
                        if (!nlist.empty()) {
+
                                for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
 
                                        PBD::ID id;
+
                                        if ((*niter)->get_property ("id", id)) {
 
                                                DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("Relearned binding for session: Control ID: %1\n", id.to_s()));
@@ -1450,10 +1549,14 @@ GenericMidiControlProtocol::set_threshold (int t)
 bool
 GenericMidiControlProtocol::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
 {
+       bool input_was_connected = (connection_state & InputConnected);
+
        if (!_input_port || !_output_port) {
                return false;
        }
 
+       DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("connection change: %1 and %2 connected ? %3\n", name1, name2, yn));
+
        string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_input_port)->name());
        string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_output_port)->name());
 
@@ -1474,18 +1577,14 @@ GenericMidiControlProtocol::connection_handler (boost::weak_ptr<ARDOUR::Port>, s
                return false;
        }
 
-       if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
-
-               /* XXX this is a horrible hack. Without a short sleep here,
-                  something prevents the device wakeup messages from being
-                  sent and/or the responses from being received.
-               */
-
-               g_usleep (100000);
-               connected ();
-
+       if (connection_state & InputConnected) {
+               if (!input_was_connected) {
+                       start_midi_handling ();
+               }
        } else {
-
+               if (input_was_connected) {
+                       stop_midi_handling ();
+               }
        }
 
        ConnectionChange (); /* emit signal for our GUI */
@@ -1493,11 +1592,6 @@ GenericMidiControlProtocol::connection_handler (boost::weak_ptr<ARDOUR::Port>, s
        return true; /* connection status changed */
 }
 
-void
-GenericMidiControlProtocol::connected ()
-{
-}
-
 boost::shared_ptr<Port>
 GenericMidiControlProtocol::output_port() const
 {
@@ -1518,3 +1612,52 @@ GenericMidiControlProtocol::maybe_start_touch (boost::shared_ptr<Controllable> c
                actl->start_touch (session->audible_sample ());
        }
 }
+
+
+void
+GenericMidiControlProtocol::start_midi_handling ()
+{
+       /* This connection means that whenever data is ready from the input
+        * port, the relevant thread will invoke our ::midi_input_handler()
+        * method, which will read the data, and invoke the parser.
+        */
+
+       _input_port->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &GenericMidiControlProtocol::midi_input_handler), boost::weak_ptr<AsyncMIDIPort> (_input_port)));
+       _input_port->xthread().attach (main_loop()->get_context());
+}
+
+void
+GenericMidiControlProtocol::stop_midi_handling ()
+{
+       midi_connections.drop_connections ();
+
+       /* Note: the input handler is still active at this point, but we're no
+        * longer connected to any of the parser signals
+        */
+}
+
+bool
+GenericMidiControlProtocol::midi_input_handler (Glib::IOCondition ioc, boost::weak_ptr<ARDOUR::AsyncMIDIPort> wport)
+{
+       boost::shared_ptr<AsyncMIDIPort> port (wport.lock());
+
+       if (!port) {
+               return false;
+       }
+
+       DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("something happend on  %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
+
+       if (ioc & ~IO_IN) {
+               return false;
+       }
+
+       if (ioc & IO_IN) {
+
+               port->clear ();
+               DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("data available on %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
+               samplepos_t now = session->engine().sample_time();
+               port->parse (now);
+       }
+
+       return true;
+}
index d2e1bd83dbc9bdd53eae8c782161038dd87700b4..9fa5d99197c84f3ee5b5cf79ff8e7f8a93269917 100644 (file)
@@ -25,6 +25,9 @@
 #include <list>
 #include <glibmm/threads.h>
 
+#define ABSTRACT_UI_EXPORTS
+#include "pbd/abstract_ui.h"
+
 #include "ardour/types.h"
 #include "ardour/port.h"
 
@@ -48,11 +51,23 @@ class MIDIControllable;
 class MIDIFunction;
 class MIDIAction;
 
-class GenericMidiControlProtocol : public ARDOUR::ControlProtocol {
+struct GenericMIDIRequest : public BaseUI::BaseRequestObject {
+public:
+       GenericMIDIRequest () {}
+       ~GenericMIDIRequest () {}
+};
+
+
+class GenericMidiControlProtocol : public ARDOUR::ControlProtocol, public AbstractUI<GenericMIDIRequest> {
 public:
        GenericMidiControlProtocol (ARDOUR::Session&);
        virtual ~GenericMidiControlProtocol();
 
+       void do_request (GenericMIDIRequest*);
+       int stop ();
+
+       void thread_init ();
+
        int set_active (bool yn);
        static bool probe() { return true; }
 
@@ -169,7 +184,6 @@ private:
        int connection_state;
        bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn);
        PBD::ScopedConnection port_connection;
-       void connected();
 
        std::string _current_binding;
        uint32_t _bank_size;
@@ -185,7 +199,11 @@ private:
        mutable void *gui;
        void build_gui ();
 
+       PBD::ScopedConnectionList midi_connections;
 
+       bool midi_input_handler (Glib::IOCondition ioc, boost::weak_ptr<ARDOUR::AsyncMIDIPort> port);
+       void start_midi_handling ();
+       void stop_midi_handling ();
 };
 
 #endif /* ardour_generic_midi_control_protocol_h */