Merge branch 'master' into windows
[ardour.git] / libs / surfaces / generic_midi / generic_midi_control_protocol.cc
index 62d7b8158b494faa549e905d07a57af5154cf390..e0b06a936984ead8f1c10da783f41bb77839ff32 100644 (file)
@@ -22,6 +22,7 @@
 #include <sstream>
 #include <algorithm>
 
+#include <glibmm/fileutils.h>
 #include <glibmm/miscutils.h>
 
 #include "pbd/controllable_descriptor.h"
 #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(f, ...) boost::protect (boost::bind (f, __VA_ARGS__))
 
 GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
        : 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 = s.midi_input_port ();
+       _output_port = s.midi_output_port ();
 
        do_feedback = false;
        _feedback_interval = 10000; // microseconds
@@ -78,7 +80,7 @@ GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
        Controllable::CreateBinding.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::create_binding, this, _1, _2, _3));
        Controllable::DeleteBinding.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::delete_binding, this, _1));
 
-       Session::SendFeedback.connect (*this, MISSING_INVALIDATOR, ui_bind (&GenericMidiControlProtocol::send_feedback, this), midi_ui_context());;
+       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
@@ -90,7 +92,7 @@ GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
 #endif
        /* this one is cross-thread */
 
-       Route::RemoteControlIDChange.connect (*this, MISSING_INVALIDATOR, ui_bind (&GenericMidiControlProtocol::reset_controllables, this), midi_ui_context());
+       Route::RemoteControlIDChange.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::reset_controllables, this), midi_ui_context());
 
        reload_maps ();
 }
@@ -105,38 +107,29 @@ 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
-midi_map_filter (const string &str, void */*arg*/)
+midi_map_filter (const string &str, void/*arg*/)
 {
        return (str.length() > strlen(midi_map_suffix) &&
                str.find (midi_map_suffix) == (str.length() - strlen (midi_map_suffix)));
@@ -147,7 +140,7 @@ GenericMidiControlProtocol::reload_maps ()
 {
        vector<string *> *midi_maps;
        PathScanner scanner;
-       SearchPath spath (system_midi_map_search_path());
+       Searchpath spath (system_midi_map_search_path());
        spath += user_midi_map_directory ();
 
        midi_maps = scanner (spath.to_string(), midi_map_filter, 0, false, true);
@@ -186,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;
@@ -213,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()) {
@@ -285,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) {
@@ -300,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(); ) {
@@ -314,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(); ) {
@@ -340,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;
@@ -360,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;
 
@@ -384,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
@@ -411,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);
@@ -431,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
@@ -474,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);
@@ -483,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
@@ -491,7 +492,7 @@ GenericMidiControlProtocol::get_state ()
                   file.
                */
 
-               if ((*i)->learned()) {
+               if ((*i)->get_controllable() && (*i)->learned()) {
                        children->add_child_nocopy ((*i)->get_state());
                }
        }
@@ -520,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<Controllable> 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
+        * <Controls><MidiControllable>...</MidiControllable><Controls> 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) {
@@ -643,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") {
@@ -657,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);
                                }
 
@@ -740,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;
@@ -755,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);
@@ -771,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<Controllable> 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<Controllable>
+GenericMidiControlProtocol::lookup_controllable (const ControllableDescriptor& desc) const
+{
+       return session->controllable_by_descriptor (desc);
+}
+
 MIDIFunction*
 GenericMidiControlProtocol::create_function (const XMLNode& node)
 {
@@ -876,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;
@@ -972,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;
@@ -1012,3 +1029,9 @@ GenericMidiControlProtocol::set_motorised (bool m)
 {
        _motorised = m;
 }
+
+void
+GenericMidiControlProtocol::set_threshold (int t)
+{
+       _threshold = t;
+}