Use Glib instead of pbd/filesystem.h in GenericMidiControlProtocol
[ardour.git] / libs / surfaces / generic_midi / generic_midi_control_protocol.cc
index 62cc93263befc29d956433458471c5e6c08917f5..fab6431c0894c3f4964eb35737043748a05eafcb 100644 (file)
 
 */
 
-#define __STDC_FORMAT_MACROS 1
 #include <stdint.h>
 
 #include <sstream>
 #include <algorithm>
 
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+
 #include "pbd/controllable_descriptor.h"
 #include "pbd/error.h"
 #include "pbd/failed_constructor.h"
@@ -50,27 +52,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)
 {
-
-       MIDI::Manager* mm = MIDI::Manager::instance();
-       
-       /* XXX it might be nice to run "control" through i18n, but thats a bit tricky because
-          the name is defined in ardour.rc which is likely not internationalized.
-       */
-       
-       _port = mm->port (Config->get_midi_port_name());
-
-       if (_port == 0) {
-               error << string_compose (_("no MIDI port named \"%1\" exists - generic MIDI control disabled"), 
-                                         Config->get_midi_port_name()) 
-                      << endmsg;
-               throw failed_constructor();
-       }
+       _input_port = MIDI::Manager::instance()->midi_input_port ();
+       _output_port = MIDI::Manager::instance()->midi_output_port ();
 
        do_feedback = false;
        _feedback_interval = 10000; // microseconds
@@ -79,7 +69,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));
@@ -87,6 +80,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 ();
@@ -98,30 +102,36 @@ GenericMidiControlProtocol::~GenericMidiControlProtocol ()
        tear_down_gui ();
 }
 
+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
+static std::string
 system_midi_map_search_path ()
 {
-       SearchPath spath(system_data_search_path());
-       spath.add_subdirectory_to_paths(midi_map_dir_name);
+       bool midimap_path_defined = false;
+       std::string spath_env (Glib::getenv (midimap_env_variable_name, midimap_path_defined));
 
-       // 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 (midimap_path_defined) {
+               return spath_env;
+       }
 
-       if (i == spath.end()) return sys::path();
+       SearchPath spath (ardour_data_search_path());
+       spath.add_subdirectory_to_paths(midi_map_dir_name);
 
-       return *i;
+       // just return the first directory in the search path that exists
+       for (SearchPath::const_iterator i = spath.begin(); i != spath.end(); ++i) {
+               if (Glib::file_test (*i, Glib::FILE_TEST_EXISTS)) {
+                       return *i;
+               }
+       }
+       return std::string();
 }
 
-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
@@ -146,8 +156,6 @@ GenericMidiControlProtocol::reload_maps ()
                return;
        }
 
-       cerr << "Found " << midi_maps->size() << " MIDI maps along " << spath.to_string() << endl;
-
        for (vector<string*>::iterator i = midi_maps->begin(); i != midi_maps->end(); ++i) {
                string fullpath = *(*i);
 
@@ -241,6 +249,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;
        }
@@ -261,20 +272,24 @@ 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;
-       MIDI::byte* end = buf;
-       
+
+       /* XXX: due to bugs in some ALSA / JACK MIDI bridges, we have to do separate
+          writes for each controllable here; if we send more than one MIDI message
+          in a single jack_midi_event_write then some bridges will only pass the
+          first on to ALSA.
+       */
        for (MIDIControllables::iterator r = controllables.begin(); r != controllables.end(); ++r) {
-               end = (*r)->write_feedback (end, bsize);
+               MIDI::byte* end = (*r)->write_feedback (buf, bsize);
+               if (end != buf) {
+                       _output_port->write (buf, (int32_t) (end - buf), 0);
+               }
        }
-       
-       if (end == buf) {
-               return;
-       } 
-
-       _port->write (buf, (int32_t) (end - buf), 0);
 }
 
 bool
@@ -324,7 +339,7 @@ GenericMidiControlProtocol::start_learning (Controllable* c)
        }
 
        if (!mc) {
-               mc = new MIDIControllable (*_port, *c, false);
+               mc = new MIDIControllable (this, *_input_port, *c, false);
        }
        
        {
@@ -421,7 +436,7 @@ GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos,
                MIDI::byte value = control_number;
                
                // Create a MIDIControllable
-               MIDIControllable* mc = new MIDIControllable (*_port, *control, false);
+               MIDIControllable* mc = new MIDIControllable (this, *_input_port, *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
@@ -463,7 +478,7 @@ GenericMidiControlProtocol::get_state ()
                node->add_property ("binding", _current_binding);
        }
 
-       XMLNode* children = new XMLNode (X_("controls"));
+       XMLNode* children = new XMLNode (X_("Controls"));
 
        node->add_child_nocopy (*children);
 
@@ -517,7 +532,7 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
        {
                Glib::Mutex::Lock lm2 (controllables_lock);
                controllables.clear ();
-               nlist = node.children(); // "controls"
+               nlist = node.children(); // "Controls"
                
                if (nlist.empty()) {
                        return 0;
@@ -528,13 +543,13 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
                for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
                        
                        if ((prop = (*niter)->property ("id")) != 0) {
-                               
+
                                ID id = prop->value ();
-                               c = session->controllable_by_id (id);
-                               
-                               if (c) {
-                                       MIDIControllable* mc = new MIDIControllable (*_port, *c, false);
+                               Controllable* c = Controllable::by_id (id);
 
+                               if (c) {
+                                       MIDIControllable* mc = new MIDIControllable (this, *_input_port, *c, false);
+                                        
                                        if (mc->set_state (**niter, version) == 0) {
                                                controllables.push_back (mc);
                                        }
@@ -575,9 +590,6 @@ GenericMidiControlProtocol::get_feedback () const
        return do_feedback;
 }
 
-
-
-
 int
 GenericMidiControlProtocol::load_bindings (const string& xmlpath)
 {
@@ -625,6 +637,19 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath)
                                _bank_size = atoi (prop->value());
                                _current_bank = 0;
                        }
+
+                       if ((prop = (*citer)->property ("motorised")) != 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") {
@@ -683,6 +708,8 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node)
                ev = MIDI::on;
        } else if ((prop = node.property (X_("pgm"))) != 0) {
                ev = MIDI::program;
+       } else if ((prop = node.property (X_("pb"))) != 0) {
+               ev = MIDI::pitchbend;
        } else {
                return 0;
        }
@@ -715,7 +742,7 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node)
        prop = node.property (X_("uri"));
        uri = prop->value();
 
-       MIDIControllable* mc = new MIDIControllable (*_port, momentary);
+       MIDIControllable* mc = new MIDIControllable (this, *_input_port, momentary);
 
        if (mc->init (uri)) {
                delete mc;
@@ -731,9 +758,11 @@ void
 GenericMidiControlProtocol::reset_controllables ()
 {
        Glib::Mutex::Lock lm2 (controllables_lock);
-       
-       for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
+
+       for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ) {
                MIDIControllable* existingBinding = (*iter);
+               MIDIControllables::iterator next = iter;
+               ++next;
 
                if (!existingBinding->learned()) {
                        ControllableDescriptor& desc (existingBinding->descriptor());
@@ -742,9 +771,21 @@ GenericMidiControlProtocol::reset_controllables ()
                                desc.set_bank_offset (_current_bank * _bank_size);
                        }
 
+                       /* 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.
+                        */
+
                        boost::shared_ptr<Controllable> c = session->controllable_by_descriptor (desc);
-                       existingBinding->set_controllable (c.get());
+                       if (c) {
+                               existingBinding->set_controllable (c.get());
+                       } else {
+                               controllables.erase (iter);
+                       }
                }
+
+               iter = next;
        }
 }
 
@@ -759,6 +800,7 @@ GenericMidiControlProtocol::create_function (const XMLNode& node)
        MIDI::eventType ev;
        MIDI::byte* data = 0;
        uint32_t data_size = 0;
+       string argument;
 
        if ((prop = node.property (X_("ctl"))) != 0) {
                ev = MIDI::controller;
@@ -830,11 +872,15 @@ GenericMidiControlProtocol::create_function (const XMLNode& node)
                }
        }
 
+       if ((prop = node.property (X_("arg"))) != 0 || (prop = node.property (X_("argument"))) != 0 || (prop = node.property (X_("arguments"))) != 0) {
+               argument = prop->value ();
+       }
+
        prop = node.property (X_("function"));
        
-       MIDIFunction* mf = new MIDIFunction (*_port);
+       MIDIFunction* mf = new MIDIFunction (*_input_port);
        
-       if (mf->init (*this, prop->value(), data, data_size)) {
+       if (mf->setup (*this, prop->value(), argument, data, data_size)) {
                delete mf;
                return 0;
        }
@@ -928,7 +974,7 @@ GenericMidiControlProtocol::create_action (const XMLNode& node)
 
        prop = node.property (X_("action"));
        
-       MIDIAction* ma = new MIDIAction (*_port);
+       MIDIAction* ma = new MIDIAction (*_input_port);
         
        if (ma->init (*this, prop->value(), data, data_size)) {
                delete ma;
@@ -962,3 +1008,15 @@ GenericMidiControlProtocol::prev_bank()
                reset_controllables ();
        }
 }
+
+void
+GenericMidiControlProtocol::set_motorised (bool m)
+{
+       _motorised = m;
+}
+
+void
+GenericMidiControlProtocol::set_threshold (int t)
+{
+       _threshold = t;
+}