summaryrefslogtreecommitdiff
path: root/libs/surfaces/generic_midi
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2019-12-31 12:06:00 -0700
committerPaul Davis <paul@linuxaudiosystems.com>2019-12-31 12:06:14 -0700
commitd100c92731f7f65f0b3a625bead7b11d57fa4503 (patch)
tree01a59903d024b9188ca987c7aa4f91dbd64d8419 /libs/surfaces/generic_midi
parent1ced4067aa152d40256bb318535374c08e4109b3 (diff)
convert GenericMIDI into a real control protocol module, with its own event loop and ports
Diffstat (limited to 'libs/surfaces/generic_midi')
-rw-r--r--libs/surfaces/generic_midi/generic_midi_control_protocol.cc219
-rw-r--r--libs/surfaces/generic_midi/generic_midi_control_protocol.h22
2 files changed, 201 insertions, 40 deletions
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<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;
+}
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 <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 */