convert GenericMIDI into a real control protocol module, with its own event loop and ports

This commit is contained in:
Paul Davis 2019-12-31 12:06:00 -07:00
parent 1ced4067aa
commit d100c92731
2 changed files with 202 additions and 41 deletions

View 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"
@ -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;
}
int
GenericMidiControlProtocol::set_active (bool /*yn*/)
void
GenericMidiControlProtocol::do_request (GenericMIDIRequest* req)
{
/* nothing to do here: the MIDI UI thread in libardour handles all our
I/O needs.
*/
if (req->type == CallSlot) {
call_slot (MISSING_INVALIDATOR, req->the_slot);
} else if (req->type == Quit) {
stop ();
}
}
int
GenericMidiControlProtocol::stop ()
{
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;
}