From d100c92731f7f65f0b3a625bead7b11d57fa4503 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 31 Dec 2019 12:06:00 -0700 Subject: convert GenericMIDI into a real control protocol module, with its own event loop and ports --- .../generic_midi/generic_midi_control_protocol.cc | 219 +++++++++++++++++---- .../generic_midi/generic_midi_control_protocol.h | 22 ++- 2 files changed, 201 insertions(+), 40 deletions(-) (limited to 'libs/surfaces/generic_midi') diff --git a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc index ec69b28621..c5ba0b7949 100644 --- a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc +++ b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc @@ -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" @@ -63,37 +64,47 @@ #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 (name()) , connection_state (ConnectionState (0)) , _motorised (false) , _threshold (10) , gui (0) { - // _input_port = boost::dynamic_pointer_cast (s.midi_input_port ()); - // _output_port = boost::dynamic_pointer_cast (s.midi_output_port ()); + boost::shared_ptr inp; + boost::shared_ptr 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(inp); + _output_port = boost::dynamic_pointer_cast(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(_input_port)->name(), + inp->name(), ARDOUR::DataType::MIDI, - session->engine().make_port_name_non_relative (boost::static_pointer_cast(_input_port)->name()) + session->engine().make_port_name_non_relative (inp->name()) ); _output_bundle->add_channel ( - boost::static_pointer_cast(_output_port)->name(), + outp->name(), ARDOUR::DataType::MIDI, - session->engine().make_port_name_non_relative (boost::static_pointer_cast(_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(_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(_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(_input_port)->get_state()); + node.add_child_nocopy (*child); + + child = new XMLNode (X_("Output")); + child->add_child_nocopy (boost::shared_ptr(_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(_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(_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, std::string name1, boost::weak_ptr, 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(_input_port)->name()); string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr(_output_port)->name()); @@ -1474,18 +1577,14 @@ GenericMidiControlProtocol::connection_handler (boost::weak_ptr, 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, s return true; /* connection status changed */ } -void -GenericMidiControlProtocol::connected () -{ -} - boost::shared_ptr GenericMidiControlProtocol::output_port() const { @@ -1518,3 +1612,52 @@ GenericMidiControlProtocol::maybe_start_touch (boost::shared_ptr 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 (_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 wport) +{ + boost::shared_ptr port (wport.lock()); + + if (!port) { + return false; + } + + DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("something happend on %1\n", boost::shared_ptr(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(port)->name())); + samplepos_t now = session->engine().sample_time(); + port->parse (now); + } + + return true; +} diff --git a/libs/surfaces/generic_midi/generic_midi_control_protocol.h b/libs/surfaces/generic_midi/generic_midi_control_protocol.h index d2e1bd83db..9fa5d99197 100644 --- a/libs/surfaces/generic_midi/generic_midi_control_protocol.h +++ b/libs/surfaces/generic_midi/generic_midi_control_protocol.h @@ -25,6 +25,9 @@ #include #include +#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 { 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, std::string name1, boost::weak_ptr, 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 port); + void start_midi_handling (); + void stop_midi_handling (); }; #endif /* ardour_generic_midi_control_protocol_h */ -- cgit v1.2.3