Prevent multiple tempo / meter changes being inserted at the same point
[ardour.git] / libs / surfaces / generic_midi / generic_midi_control_protocol.cc
index 42c53588165454ffcee0b6a82d4613e1ebc17d5a..62cc93263befc29d956433458471c5e6c08917f5 100644 (file)
@@ -23,6 +23,7 @@
 #include <sstream>
 #include <algorithm>
 
+#include "pbd/controllable_descriptor.h"
 #include "pbd/error.h"
 #include "pbd/failed_constructor.h"
 #include "pbd/pathscanner.h"
 #include "ardour/session.h"
 #include "ardour/route.h"
 #include "ardour/midi_ui.h"
+#include "ardour/rc_configuration.h"
 
 #include "generic_midi_control_protocol.h"
 #include "midicontrollable.h"
 #include "midifunction.h"
+#include "midiaction.h"
 
 using namespace ARDOUR;
 using namespace PBD;
@@ -60,10 +63,12 @@ GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
           the name is defined in ardour.rc which is likely not internationalized.
        */
        
-       _port = mm->port (X_("control"));
+       _port = mm->port (Config->get_midi_port_name());
 
        if (_port == 0) {
-               error << _("no MIDI port named \"control\" exists - generic MIDI control disabled") << endmsg;
+               error << string_compose (_("no MIDI port named \"%1\" exists - generic MIDI control disabled"), 
+                                         Config->get_midi_port_name()) 
+                      << endmsg;
                throw failed_constructor();
        }
 
@@ -81,8 +86,8 @@ 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, boost::bind (&GenericMidiControlProtocol::send_feedback, this), midi_ui_context());;
-       Route::RemoteControlIDChange.connect (*this, boost::bind (&GenericMidiControlProtocol::reset_controllables, this), midi_ui_context());
+       Session::SendFeedback.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::send_feedback, this), midi_ui_context());;
+       Route::RemoteControlIDChange.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::reset_controllables, this), midi_ui_context());
 
        reload_maps ();
 }
@@ -189,6 +194,11 @@ GenericMidiControlProtocol::drop_all ()
                delete *i;
        }
        functions.clear ();
+
+       for (MIDIActions::iterator i = actions.begin(); i != actions.end(); ++i) {
+               delete *i;
+       }
+       actions.clear ();
 }
 
 void
@@ -307,14 +317,14 @@ GenericMidiControlProtocol::start_learning (Controllable* c)
        MIDIControllable* mc = 0;
 
        for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
-               if ((*i)->get_controllable()->id() == c->id()) {
+               if ((*i)->get_controllable() && ((*i)->get_controllable()->id() == c->id())) {
                        mc = *i;
                        break;
                }
        }
 
        if (!mc) {
-               mc = new MIDIControllable (*_port, *c);
+               mc = new MIDIControllable (*_port, *c, false);
        }
        
        {
@@ -387,12 +397,14 @@ GenericMidiControlProtocol::delete_binding (PBD::Controllable* control)
        if (control != 0) {
                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);
                        
                        if (control == (existingBinding->get_controllable())) {
                                delete existingBinding;
-                               controllables.erase (iter);
+                               iter = controllables.erase (iter);
+                       } else {
+                               ++iter;
                        }
                        
                }
@@ -409,11 +421,11 @@ GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos,
                MIDI::byte value = control_number;
                
                // Create a MIDIControllable
-               MIDIControllable* mc = new MIDIControllable (*_port, *control);
+               MIDIControllable* mc = new MIDIControllable (*_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
-               for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
+               for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end();) {
                        MIDIControllable* existingBinding = (*iter);
                        
                        if ((existingBinding->get_control_channel() & 0xf ) == channel &&
@@ -421,7 +433,9 @@ GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos,
                            (existingBinding->get_control_type() & 0xf0 ) == MIDI::controller) {
                                
                                delete existingBinding;
-                               controllables.erase (iter);
+                               iter = controllables.erase (iter);
+                       } else {
+                               ++iter;
                        }
                        
                }
@@ -519,7 +533,7 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
                                c = session->controllable_by_id (id);
                                
                                if (c) {
-                                       MIDIControllable* mc = new MIDIControllable (*_port, *c);
+                                       MIDIControllable* mc = new MIDIControllable (*_port, *c, false);
 
                                        if (mc->set_state (**niter, version) == 0) {
                                                controllables.push_back (mc);
@@ -632,7 +646,14 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath)
                                if ((mf = create_function (*child)) != 0) {
                                        functions.push_back (mf);
                                }
-                       }
+
+                       } else if (child->property ("action")) {
+                                MIDIAction* ma;
+
+                               if ((ma = create_action (*child)) != 0) {
+                                       actions.push_back (ma);
+                               }
+                        }
                }
        }
        
@@ -654,6 +675,7 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node)
        string uri;
        MIDI::eventType ev;
        int intval;
+       bool momentary;
 
        if ((prop = node.property (X_("ctl"))) != 0) {
                ev = MIDI::controller;
@@ -683,11 +705,17 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node)
        if (channel > 0) {
                channel -= 1;
        }
+
+       if ((prop = node.property (X_("momentary"))) != 0) {
+               momentary = string_is_affirmative (prop->value());
+       } else {
+               momentary = false;
+       }
        
        prop = node.property (X_("uri"));
        uri = prop->value();
 
-       MIDIControllable* mc = new MIDIControllable (*_port, false);
+       MIDIControllable* mc = new MIDIControllable (*_port, momentary);
 
        if (mc->init (uri)) {
                delete mc;
@@ -696,26 +724,25 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node)
 
        mc->bind_midi (channel, ev, detail);
 
-       cerr << "New MC with URI " << uri << " on channel " << (int) channel << " detail = " << (int) detail << endl;
-
        return mc;
 }
 
 void
 GenericMidiControlProtocol::reset_controllables ()
 {
-       cerr << "GM::RC\n";
        Glib::Mutex::Lock lm2 (controllables_lock);
        
        for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
                MIDIControllable* existingBinding = (*iter);
 
                if (!existingBinding->learned()) {
-                       uint32_t rid = existingBinding->rid();
-                       if (existingBinding->bank_relative()) {
-                               rid += _current_bank * _bank_size;
+                       ControllableDescriptor& desc (existingBinding->descriptor());
+
+                       if (desc.banked()) {
+                               desc.set_bank_offset (_current_bank * _bank_size);
                        }
-                       boost::shared_ptr<Controllable> c = session->controllable_by_rid_and_name (rid, existingBinding->what().c_str());
+
+                       boost::shared_ptr<Controllable> c = session->controllable_by_descriptor (desc);
                        existingBinding->set_controllable (c.get());
                }
        }
@@ -730,8 +757,8 @@ GenericMidiControlProtocol::create_function (const XMLNode& node)
        MIDI::channel_t channel = 0;
        string uri;
        MIDI::eventType ev;
-       MIDI::byte* sysex = 0;
-       uint32_t sysex_size = 0;
+       MIDI::byte* data = 0;
+       uint32_t data_size = 0;
 
        if ((prop = node.property (X_("ctl"))) != 0) {
                ev = MIDI::controller;
@@ -739,9 +766,14 @@ GenericMidiControlProtocol::create_function (const XMLNode& node)
                ev = MIDI::on;
        } else if ((prop = node.property (X_("pgm"))) != 0) {
                ev = MIDI::program;
-       } else if ((prop = node.property (X_("sysex"))) != 0) {
+       } else if ((prop = node.property (X_("sysex"))) != 0 || (prop = node.property (X_("msg"))) != 0) {
+
+                if (prop->name() == X_("sysex")) {
+                        ev = MIDI::sysex;
+                } else {
+                        ev = MIDI::any;
+                }
 
-               ev = MIDI::sysex;
                int val;
                uint32_t cnt;
 
@@ -759,8 +791,8 @@ GenericMidiControlProtocol::create_function (const XMLNode& node)
                        return 0;
                }
 
-               sysex = new MIDI::byte[cnt];
-               sysex_size = cnt;
+               data = new MIDI::byte[cnt];
+               data_size = cnt;
                
                {
                        stringstream ss (prop->value());
@@ -768,16 +800,16 @@ GenericMidiControlProtocol::create_function (const XMLNode& node)
                        cnt = 0;
                        
                        while (ss >> val) {
-                               sysex[cnt++] = (MIDI::byte) val;
+                               data[cnt++] = (MIDI::byte) val;
                        }
                }
-               
+
        } else {
                warning << "Binding ignored - unknown type" << endmsg;
                return 0;
        }
 
-       if (sysex_size == 0) {
+       if (data_size == 0) {
                if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
                        return 0;
                }
@@ -802,18 +834,112 @@ GenericMidiControlProtocol::create_function (const XMLNode& node)
        
        MIDIFunction* mf = new MIDIFunction (*_port);
        
-       if (mf->init (*this, prop->value(), sysex, sysex_size)) {
+       if (mf->init (*this, prop->value(), data, data_size)) {
                delete mf;
                return 0;
        }
 
-       cerr << "New MF with function = " << prop->value() << " on channel " << (int) channel << " detail = " << (int) detail << endl;
        mf->bind_midi (channel, ev, detail);
 
-
        return mf;
 }
 
+MIDIAction*
+GenericMidiControlProtocol::create_action (const XMLNode& node)
+{
+       const XMLProperty* prop;
+       int intval;
+       MIDI::byte detail = 0;
+       MIDI::channel_t channel = 0;
+       string uri;
+       MIDI::eventType ev;
+       MIDI::byte* data = 0;
+       uint32_t data_size = 0;
+
+       if ((prop = node.property (X_("ctl"))) != 0) {
+               ev = MIDI::controller;
+       } else if ((prop = node.property (X_("note"))) != 0) {
+               ev = MIDI::on;
+       } else if ((prop = node.property (X_("pgm"))) != 0) {
+               ev = MIDI::program;
+       } else if ((prop = node.property (X_("sysex"))) != 0 || (prop = node.property (X_("msg"))) != 0) {
+
+                if (prop->name() == X_("sysex")) {
+                        ev = MIDI::sysex;
+                } else {
+                        ev = MIDI::any;
+                }
+
+               int val;
+               uint32_t cnt;
+
+               {
+                       cnt = 0;
+                       stringstream ss (prop->value());
+                       ss << hex;
+                       
+                       while (ss >> val) {
+                               cnt++;
+                       }
+               }
+
+               if (cnt == 0) {
+                       return 0;
+               }
+
+               data = new MIDI::byte[cnt];
+               data_size = cnt;
+               
+               {
+                       stringstream ss (prop->value());
+                       ss << hex;
+                       cnt = 0;
+                       
+                       while (ss >> val) {
+                               data[cnt++] = (MIDI::byte) val;
+                       }
+               }
+
+       } else {
+               warning << "Binding ignored - unknown type" << endmsg;
+               return 0;
+       }
+
+       if (data_size == 0) {
+               if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
+                       return 0;
+               }
+               
+               detail = (MIDI::byte) intval;
+
+               if ((prop = node.property (X_("channel"))) == 0) {
+                       return 0;
+               }
+       
+               if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
+                       return 0;
+               }
+               channel = (MIDI::channel_t) intval;
+               /* adjust channel to zero-based counting */
+               if (channel > 0) {
+                       channel -= 1;
+               }
+       }
+
+       prop = node.property (X_("action"));
+       
+       MIDIAction* ma = new MIDIAction (*_port);
+        
+       if (ma->init (*this, prop->value(), data, data_size)) {
+               delete ma;
+               return 0;
+       }
+
+       ma->bind_midi (channel, ev, detail);
+
+       return ma;
+}
+
 void
 GenericMidiControlProtocol::set_current_bank (uint32_t b)
 {