X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fsurfaces%2Fgeneric_midi%2Fgeneric_midi_control_protocol.cc;h=2820b069dc7af246917dd417ce851e3478e262c1;hb=1ab61b8564f9934c533d1c1a229888bc7e2fd557;hp=52ec2dcd71a8c0321466bd1b6ce2ea78c60afaab;hpb=fb6895ba8634d86fc57f9638f0e0c61d32eb131f;p=ardour.git diff --git a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc index 52ec2dcd71..2820b069dc 100644 --- a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc +++ b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc @@ -22,6 +22,7 @@ #include #include +#include #include #include "pbd/controllable_descriptor.h" @@ -31,13 +32,14 @@ #include "pbd/xml++.h" #include "midi++/port.h" -#include "midi++/manager.h" +#include "ardour/audioengine.h" #include "ardour/filesystem_paths.h" #include "ardour/session.h" #include "ardour/route.h" #include "ardour/midi_ui.h" #include "ardour/rc_configuration.h" +#include "ardour/midiport_manager.h" #include "generic_midi_control_protocol.h" #include "midicontrollable.h" @@ -51,15 +53,15 @@ using namespace std; #include "i18n.h" #define midi_ui_context() MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */ -#define ui_bind(x) boost::protect (boost::bind ((x))) GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s) - : ControlProtocol (s, _("Generic MIDI"), midi_ui_context()) + : ControlProtocol (s, _("Generic MIDI")) , _motorised (false) + , _threshold (10) , gui (0) { - _input_port = MIDI::Manager::instance()->midi_input_port (); - _output_port = MIDI::Manager::instance()->midi_output_port (); + _input_port = AudioEngine::instance()->midi_input_port (); + _output_port = AudioEngine::instance()->midi_output_port (); do_feedback = false; _feedback_interval = 10000; // microseconds @@ -68,7 +70,10 @@ GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s) _current_bank = 0; _bank_size = 0; - /* XXX is it right to do all these in the same thread as whatever emits the signal? */ + /* these signals are emitted by the MidiControlUI's 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)); @@ -76,6 +81,17 @@ GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s) Controllable::DeleteBinding.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::delete_binding, this, _1)); Session::SendFeedback.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::send_feedback, this), midi_ui_context());; +#if 0 + /* XXXX SOMETHING GOES WRONG HERE (april 2012) - STILL DEBUGGING */ + /* this signal is emitted by the process() callback, and if + * send_feedback() is going to do anything, it should do it in the + * context of the process() callback itself. + */ + + Session::SendFeedback.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::send_feedback, this)); +#endif + /* this one is cross-thread */ + Route::RemoteControlIDChange.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::reset_controllables, this), midi_ui_context()); reload_maps (); @@ -91,34 +107,25 @@ static const char * const midimap_env_variable_name = "ARDOUR_MIDIMAPS_PATH"; static const char* const midi_map_dir_name = "midi_maps"; static const char* const midi_map_suffix = ".map"; -static sys::path +SearchPath system_midi_map_search_path () { bool midimap_path_defined = false; - sys::path spath_env (Glib::getenv (midimap_env_variable_name, midimap_path_defined)); + std::string spath_env (Glib::getenv (midimap_env_variable_name, midimap_path_defined)); if (midimap_path_defined) { return spath_env; } - SearchPath spath (system_data_search_path()); + SearchPath spath (ardour_data_search_path()); spath.add_subdirectory_to_paths(midi_map_dir_name); - - // just return the first directory in the search path that exists - SearchPath::const_iterator i = std::find_if(spath.begin(), spath.end(), sys::exists); - - if (i == spath.end()) return sys::path(); - - return *i; + return spath; } -static sys::path +static std::string user_midi_map_directory () { - sys::path p(user_config_directory()); - p /= midi_map_dir_name; - - return p; + return Glib::build_filename (user_config_directory(), midi_map_dir_name); } static bool @@ -172,8 +179,8 @@ GenericMidiControlProtocol::reload_maps () void GenericMidiControlProtocol::drop_all () { - Glib::Mutex::Lock lm (pending_lock); - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm (pending_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) { delete *i; @@ -199,7 +206,7 @@ GenericMidiControlProtocol::drop_all () void GenericMidiControlProtocol::drop_bindings () { - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ) { if (!(*i)->learned()) { @@ -236,6 +243,9 @@ GenericMidiControlProtocol::set_feedback_interval (microseconds_t ms) void GenericMidiControlProtocol::send_feedback () { + /* This is executed in RT "process" context", so no blocking calls + */ + if (!do_feedback) { return; } @@ -256,6 +266,9 @@ GenericMidiControlProtocol::send_feedback () void GenericMidiControlProtocol::_send_feedback () { + /* This is executed in RT "process" context", so no blocking calls + */ + const int32_t bufsize = 16 * 1024; /* XXX too big */ MIDI::byte buf[bufsize]; int32_t bsize = bufsize; @@ -265,6 +278,12 @@ GenericMidiControlProtocol::_send_feedback () in a single jack_midi_event_write then some bridges will only pass the first on to ALSA. */ + + Glib::Threads::Mutex::Lock lm (controllables_lock, Glib::Threads::TRY_LOCK); + if (!lm.locked ()) { + return; + } + for (MIDIControllables::iterator r = controllables.begin(); r != controllables.end(); ++r) { MIDI::byte* end = (*r)->write_feedback (buf, bsize); if (end != buf) { @@ -280,7 +299,7 @@ GenericMidiControlProtocol::start_learning (Controllable* c) return false; } - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); MIDIControllables::iterator tmp; for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ) { @@ -294,7 +313,7 @@ GenericMidiControlProtocol::start_learning (Controllable* c) } { - Glib::Mutex::Lock lm (pending_lock); + Glib::Threads::Mutex::Lock lm (pending_lock); MIDIPendingControllables::iterator ptmp; for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) { @@ -320,11 +339,11 @@ GenericMidiControlProtocol::start_learning (Controllable* c) } if (!mc) { - mc = new MIDIControllable (this, *_input_port, *c, false); + mc = new MIDIControllable (this, *_input_port->parser(), *c, false); } { - Glib::Mutex::Lock lm (pending_lock); + Glib::Threads::Mutex::Lock lm (pending_lock); MIDIPendingControllable* element = new MIDIPendingControllable; element->first = mc; @@ -340,8 +359,8 @@ GenericMidiControlProtocol::start_learning (Controllable* c) void GenericMidiControlProtocol::learning_stopped (MIDIControllable* mc) { - Glib::Mutex::Lock lm (pending_lock); - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm (pending_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); MIDIPendingControllables::iterator tmp; @@ -364,8 +383,8 @@ GenericMidiControlProtocol::learning_stopped (MIDIControllable* mc) void GenericMidiControlProtocol::stop_learning (Controllable* c) { - Glib::Mutex::Lock lm (pending_lock); - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm (pending_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); MIDIControllable* dptr = 0; /* learning timed out, and we've been told to consider this attempt to learn to be cancelled. find the @@ -391,7 +410,7 @@ void GenericMidiControlProtocol::delete_binding (PBD::Controllable* control) { if (control != 0) { - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end();) { MIDIControllable* existingBinding = (*iter); @@ -411,13 +430,13 @@ void GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, int control_number) { if (control != NULL) { - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); MIDI::channel_t channel = (pos & 0xf); MIDI::byte value = control_number; // Create a MIDIControllable - MIDIControllable* mc = new MIDIControllable (this, *_input_port, *control, false); + MIDIControllable* mc = new MIDIControllable (this, *_input_port->parser(), *control, false); // Remove any old binding for this midi channel/type/value pair // Note: can't use delete_binding() here because we don't know the specific controllable we want to remove, only the midi information @@ -454,6 +473,8 @@ GenericMidiControlProtocol::get_state () node->add_property (X_("feedback"), do_feedback ? "1" : "0"); snprintf (buf, sizeof (buf), "%" PRIu64, _feedback_interval); node->add_property (X_("feedback_interval"), buf); + snprintf (buf, sizeof (buf), "%d", _threshold); + node->add_property (X_("threshold"), buf); if (!_current_binding.empty()) { node->add_property ("binding", _current_binding); @@ -463,7 +484,7 @@ GenericMidiControlProtocol::get_state () node->add_child_nocopy (*children); - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) { /* we don't care about bindings that come from a bindings map, because @@ -471,7 +492,7 @@ GenericMidiControlProtocol::get_state () file. */ - if ((*i)->learned()) { + if ((*i)->get_controllable() && (*i)->learned()) { children->add_child_nocopy ((*i)->get_state()); } } @@ -500,53 +521,60 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version) _feedback_interval = 10000; } + if ((prop = node.property ("threshold")) != 0) { + if (sscanf (prop->value().c_str(), "%d", &_threshold) != 1) { + _threshold = 10; + } + } else { + _threshold = 10; + } + boost::shared_ptr c; { - Glib::Mutex::Lock lm (pending_lock); + Glib::Threads::Mutex::Lock lm (pending_lock); for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) { delete *i; } pending_controllables.clear (); } + /* Load up specific bindings from the + * ... section + */ + { - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); controllables.clear (); nlist = node.children(); // "Controls" - - if (nlist.empty()) { - return 0; - } - - nlist = nlist.front()->children (); - - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - - if ((prop = (*niter)->property ("id")) != 0) { - cerr << "Looking for MIDI Controllable with ID " << prop->value() << endl; - - ID id = prop->value (); - Controllable* c = Controllable::by_id (id); + if (!nlist.empty()) { + nlist = nlist.front()->children(); // "MIDIControllable" ... - cerr << "\tresult = " << c << endl; - - if (c) { - MIDIControllable* mc = new MIDIControllable (this, *_input_port, *c, false); - - if (mc->set_state (**niter, version) == 0) { - controllables.push_back (mc); - } + if (!nlist.empty()) { + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - } else { - warning << string_compose ( - _("Generic MIDI control: controllable %1 not found in session (ignored)"), - id) << endmsg; + if ((prop = (*niter)->property ("id")) != 0) { + + ID id = prop->value (); + Controllable* c = Controllable::by_id (id); + + if (c) { + MIDIControllable* mc = new MIDIControllable (this, *_input_port->parser(), *c, false); + + if (mc->set_state (**niter, version) == 0) { + controllables.push_back (mc); + } + + } else { + warning << string_compose ( + _("Generic MIDI control: controllable %1 not found in session (ignored)"), + id) << endmsg; + } + } } } } - } if ((prop = node.property ("binding")) != 0) { @@ -623,11 +651,18 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath) _current_bank = 0; } - if ((prop = (*citer)->property ("motorised")) != 0) { + if ((prop = (*citer)->property ("motorised")) != 0 || ((prop = (*citer)->property ("motorized")) != 0)) { _motorised = string_is_affirmative (prop->value ()); } else { _motorised = false; } + + if ((prop = (*citer)->property ("threshold")) != 0) { + _threshold = atoi (prop->value ()); + } else { + _threshold = 10; + } + } if ((*citer)->name() == "Binding") { @@ -637,7 +672,7 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath) /* controllable */ if ((mc = create_binding (*child)) != 0) { - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); controllables.push_back (mc); } @@ -720,7 +755,7 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node) prop = node.property (X_("uri")); uri = prop->value(); - MIDIControllable* mc = new MIDIControllable (this, *_input_port, momentary); + MIDIControllable* mc = new MIDIControllable (this, *_input_port->parser(), momentary); if (mc->init (uri)) { delete mc; @@ -735,7 +770,7 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node) void GenericMidiControlProtocol::reset_controllables () { - Glib::Mutex::Lock lm2 (controllables_lock); + Glib::Threads::Mutex::Lock lm2 (controllables_lock); for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ) { MIDIControllable* existingBinding = (*iter); @@ -751,22 +786,24 @@ GenericMidiControlProtocol::reset_controllables () /* its entirely possible that the session doesn't have * the specified controllable (e.g. it has too few - * tracks). if we find this to be the case, drop any - * bindings that would be left without controllables. + * tracks). if we find this to be the case, we just leave + * the binding around, unbound, and it will do "late + * binding" (or "lazy binding") if/when any data arrives. */ - boost::shared_ptr c = session->controllable_by_descriptor (desc); - if (c) { - existingBinding->set_controllable (c.get()); - } else { - controllables.erase (iter); - } + existingBinding->lookup_controllable (); } iter = next; } } +boost::shared_ptr +GenericMidiControlProtocol::lookup_controllable (const ControllableDescriptor& desc) const +{ + return session->controllable_by_descriptor (desc); +} + MIDIFunction* GenericMidiControlProtocol::create_function (const XMLNode& node) { @@ -856,7 +893,7 @@ GenericMidiControlProtocol::create_function (const XMLNode& node) prop = node.property (X_("function")); - MIDIFunction* mf = new MIDIFunction (*_input_port); + MIDIFunction* mf = new MIDIFunction (*_input_port->parser()); if (mf->setup (*this, prop->value(), argument, data, data_size)) { delete mf; @@ -952,7 +989,7 @@ GenericMidiControlProtocol::create_action (const XMLNode& node) prop = node.property (X_("action")); - MIDIAction* ma = new MIDIAction (*_input_port); + MIDIAction* ma = new MIDIAction (*_input_port->parser()); if (ma->init (*this, prop->value(), data, data_size)) { delete ma; @@ -986,3 +1023,15 @@ GenericMidiControlProtocol::prev_bank() reset_controllables (); } } + +void +GenericMidiControlProtocol::set_motorised (bool m) +{ + _motorised = m; +} + +void +GenericMidiControlProtocol::set_threshold (int t) +{ + _threshold = t; +}