X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fsurfaces%2Fgeneric_midi%2Fgeneric_midi_control_protocol.cc;h=6277adc84e0ede668162645e1880650b81db8938;hb=e0ff70cf86c01c42f98faf8b0eaf1a8ccf867946;hp=702d361965d6f0d385d1857daf1b63cf2e7edf57;hpb=e5e12acc5698090f2c0c614385e457cc0b46fbb0;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 702d361965..6277adc84e 100644 --- a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc +++ b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc @@ -1,6 +1,6 @@ /* Copyright (C) 2006 Paul Davis - + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or @@ -30,9 +30,12 @@ #include "pbd/failed_constructor.h" #include "pbd/file_utils.h" #include "pbd/xml++.h" +#include "pbd/compose.h" #include "midi++/port.h" +#include "ardour/async_midi_port.h" +#include "ardour/audioengine.h" #include "ardour/audioengine.h" #include "ardour/filesystem_paths.h" #include "ardour/session.h" @@ -40,6 +43,7 @@ #include "ardour/midi_ui.h" #include "ardour/rc_configuration.h" #include "ardour/midiport_manager.h" +#include "ardour/debug.h" #include "generic_midi_control_protocol.h" #include "midicontrollable.h" @@ -56,12 +60,13 @@ using namespace std; GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s) : ControlProtocol (s, _("Generic MIDI")) + , connection_state (ConnectionState (0)) , _motorised (false) , _threshold (10) , gui (0) { - _input_port = s.midi_input_port (); - _output_port = s.midi_output_port (); + _input_port = boost::dynamic_pointer_cast (s.midi_input_port ()); + _output_port = boost::dynamic_pointer_cast (s.midi_output_port ()); do_feedback = false; _feedback_interval = 10000; // microseconds @@ -90,7 +95,12 @@ GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s) /* this one is cross-thread */ - Route::RemoteControlIDChange.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::reset_controllables, this), midi_ui_context()); + Stripable::PresentationInfoChange.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::reset_controllables, this), midi_ui_context()); + + /* 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()); reload_maps (); } @@ -158,7 +168,7 @@ GenericMidiControlProtocol::reload_maps () MapInfo mi; - XMLProperty* prop = tree.root()->property ("name"); + XMLProperty const * prop = tree.root()->property ("name"); if (!prop) { continue; @@ -166,14 +176,15 @@ GenericMidiControlProtocol::reload_maps () mi.name = prop->value (); mi.path = fullpath; - + map_info.push_back (mi); } } - + void GenericMidiControlProtocol::drop_all () { + DEBUG_TRACE (DEBUG::GenericMidi, "Drop all bindings\n"); Glib::Threads::Mutex::Lock lm (pending_lock); Glib::Threads::Mutex::Lock lm2 (controllables_lock); @@ -201,6 +212,7 @@ GenericMidiControlProtocol::drop_all () void GenericMidiControlProtocol::drop_bindings () { + DEBUG_TRACE (DEBUG::GenericMidi, "Drop bindings, leave learned\n"); Glib::Threads::Mutex::Lock lm2 (controllables_lock); for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ) { @@ -225,7 +237,9 @@ GenericMidiControlProtocol::drop_bindings () int GenericMidiControlProtocol::set_active (bool /*yn*/) { - /* start/stop delivery/outbound thread */ + /* nothing to do here: the MIDI UI thread in libardour handles all our + I/O needs. + */ return 0; } @@ -235,7 +249,7 @@ GenericMidiControlProtocol::set_feedback_interval (microseconds_t ms) _feedback_interval = ms; } -void +void GenericMidiControlProtocol::send_feedback () { /* This is executed in RT "process" context", so no blocking calls @@ -254,11 +268,11 @@ GenericMidiControlProtocol::send_feedback () } _send_feedback (); - + last_feedback_time = now; } -void +void GenericMidiControlProtocol::_send_feedback () { /* This is executed in RT "process" context", so no blocking calls @@ -278,7 +292,7 @@ GenericMidiControlProtocol::_send_feedback () 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) { @@ -295,6 +309,7 @@ GenericMidiControlProtocol::start_learning (Controllable* c) } Glib::Threads::Mutex::Lock lm2 (controllables_lock); + DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("Learn binding: Controlable number: %1\n", c)); MIDIControllables::iterator tmp; for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ) { @@ -309,7 +324,7 @@ GenericMidiControlProtocol::start_learning (Controllable* c) { Glib::Threads::Mutex::Lock lm (pending_lock); - + MIDIPendingControllables::iterator ptmp; for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) { ptmp = i; @@ -336,7 +351,7 @@ GenericMidiControlProtocol::start_learning (Controllable* c) if (!mc) { mc = new MIDIControllable (this, *_input_port->parser(), *c, false); } - + { Glib::Threads::Mutex::Lock lm (pending_lock); @@ -346,7 +361,6 @@ GenericMidiControlProtocol::start_learning (Controllable* c) pending_controllables.push_back (element); } - mc->learn_about_external_control (); return true; } @@ -356,7 +370,7 @@ GenericMidiControlProtocol::learning_stopped (MIDIControllable* mc) { Glib::Threads::Mutex::Lock lm (pending_lock); Glib::Threads::Mutex::Lock lm2 (controllables_lock); - + MIDIPendingControllables::iterator tmp; for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) { @@ -397,7 +411,7 @@ GenericMidiControlProtocol::stop_learning (Controllable* c) break; } } - + delete dptr; } @@ -406,30 +420,31 @@ GenericMidiControlProtocol::delete_binding (PBD::Controllable* control) { if (control != 0) { Glib::Threads::Mutex::Lock lm2 (controllables_lock); - + for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end();) { MIDIControllable* existingBinding = (*iter); - + if (control == (existingBinding->get_controllable())) { delete existingBinding; iter = controllables.erase (iter); } else { ++iter; } - + } } } +// This next function seems unused void GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, int control_number) { if (control != NULL) { 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->parser(), *control, false); @@ -437,39 +452,99 @@ GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, // 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();) { MIDIControllable* existingBinding = (*iter); - + if ((existingBinding->get_control_channel() & 0xf ) == channel && existingBinding->get_control_additional() == value && (existingBinding->get_control_type() & 0xf0 ) == MIDI::controller) { - + delete existingBinding; iter = controllables.erase (iter); } else { ++iter; } - + } - + // Update the MIDI Controllable based on the the pos param // Here is where a table lookup for user mappings could go; for now we'll just wing it... mc->bind_midi(channel, MIDI::controller, value); - + DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("Create binding: Channel: %1 Controller: %2 Value: %3 \n", channel, MIDI::controller, value)); controllables.push_back (mc); } } +void +GenericMidiControlProtocol::check_used_event (int pos, int control_number) +{ + Glib::Threads::Mutex::Lock lm2 (controllables_lock); + + MIDI::channel_t channel = (pos & 0xf); + MIDI::byte value = control_number; + + DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("checking for used event: Channel: %1 Controller: %2 value: %3\n", (int) channel, (pos & 0xf0), (int) value)); + + // 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();) { + MIDIControllable* existingBinding = (*iter); + if ( (existingBinding->get_control_type() & 0xf0 ) == (pos & 0xf0) && (existingBinding->get_control_channel() & 0xf ) == channel ) { + if ( ((int) existingBinding->get_control_additional() == (int) value) || ((pos & 0xf0) == MIDI::pitchbend)) { + DEBUG_TRACE (DEBUG::GenericMidi, "checking: found match, delete old binding.\n"); + delete existingBinding; + iter = controllables.erase (iter); + } else { + ++iter; + } + } else { + ++iter; + } + } + + for (MIDIFunctions::iterator iter = functions.begin(); iter != functions.end();) { + MIDIFunction* existingBinding = (*iter); + if ( (existingBinding->get_control_type() & 0xf0 ) == (pos & 0xf0) && (existingBinding->get_control_channel() & 0xf ) == channel ) { + if ( ((int) existingBinding->get_control_additional() == (int) value) || ((pos & 0xf0) == MIDI::pitchbend)) { + DEBUG_TRACE (DEBUG::GenericMidi, "checking: found match, delete old binding.\n"); + delete existingBinding; + iter = functions.erase (iter); + } else { + ++iter; + } + } else { + ++iter; + } + } + + for (MIDIActions::iterator iter = actions.begin(); iter != actions.end();) { + MIDIAction* existingBinding = (*iter); + if ( (existingBinding->get_control_type() & 0xf0 ) == (pos & 0xf0) && (existingBinding->get_control_channel() & 0xf ) == channel ) { + if ( ((int) existingBinding->get_control_additional() == (int) value) || ((pos & 0xf0) == MIDI::pitchbend)) { + DEBUG_TRACE (DEBUG::GenericMidi, "checking: found match, delete old binding.\n"); + delete existingBinding; + iter = actions.erase (iter); + } else { + ++iter; + } + } else { + ++iter; + } + } + +} + XMLNode& -GenericMidiControlProtocol::get_state () +GenericMidiControlProtocol::get_state () { XMLNode& node (ControlProtocol::get_state()); char buf[32]; - 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); + node.add_property (X_("motorized"), _motorised ? "yes" : "no"); + if (!_current_binding.empty()) { node.add_property ("binding", _current_binding); } @@ -501,10 +576,8 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version) XMLNodeConstIterator niter; const XMLProperty* prop; - if ((prop = node.property ("feedback")) != 0) { - do_feedback = (bool) atoi (prop->value().c_str()); - } else { - do_feedback = false; + if (ControlProtocol::set_state (node, version)) { + return -1; } if ((prop = node.property ("feedback_interval")) != 0) { @@ -523,8 +596,14 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version) _threshold = 10; } + if ((prop = node.property ("motorized")) != 0) { + _motorised = string_is_affirmative (prop->value ()); + } else { + _motorised = false; + } + boost::shared_ptr c; - + { Glib::Threads::Mutex::Lock lm (pending_lock); for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) { @@ -533,13 +612,22 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version) pending_controllables.clear (); } + // midi map has to be loaded first so learned binding can go on top + if ((prop = node.property ("binding")) != 0) { + for (list::iterator x = map_info.begin(); x != map_info.end(); ++x) { + if (prop->value() == (*x).name) { + load_bindings ((*x).path); + break; + } + } + } + /* Load up specific bindings from the * ... section */ { Glib::Threads::Mutex::Lock lm2 (controllables_lock); - controllables.clear (); nlist = node.children(); // "Controls" if (!nlist.empty()) { @@ -547,23 +635,24 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version) if (!nlist.empty()) { for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - + if ((prop = (*niter)->property ("id")) != 0) { - + ID id = prop->value (); + DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("Relearned binding for session: Control ID: %1\n", id.to_s())); 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; + id.to_s()) << endmsg; } } } @@ -571,15 +660,6 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version) } } - if ((prop = node.property ("binding")) != 0) { - for (list::iterator x = map_info.begin(); x != map_info.end(); ++x) { - if (prop->value() == (*x).name) { - load_bindings ((*x).path); - break; - } - } - } - return 0; } @@ -600,6 +680,7 @@ GenericMidiControlProtocol::get_feedback () const int GenericMidiControlProtocol::load_bindings (const string& xmlpath) { + DEBUG_TRACE (DEBUG::GenericMidi, "Load bindings: Reading midi map\n"); XMLTree state_tree; if (!state_tree.read (xmlpath.c_str())) { @@ -626,7 +707,7 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath) sscanf (prop->value().c_str(), "%d.%d.%d", &major, &minor, µ); Stateful::loading_state_version = (major * 1000) + minor; } - + const XMLNodeList& children (root->children()); XMLNodeConstIterator citer; XMLNodeConstIterator gciter; @@ -635,8 +716,9 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath) drop_all (); + DEBUG_TRACE (DEBUG::GenericMidi, "Loading bindings\n"); for (citer = children.begin(); citer != children.end(); ++citer) { - + if ((*citer)->name() == "DeviceInfo") { const XMLProperty* prop; @@ -645,7 +727,7 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath) _current_bank = 0; } - if ((prop = (*citer)->property ("motorised")) != 0 || ((prop = (*citer)->property ("motorized")) != 0)) { + if ((prop = (*citer)->property ("motorized")) != 0) { _motorised = string_is_affirmative (prop->value ()); } else { _motorised = false; @@ -664,7 +746,7 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath) if (child->property ("uri")) { /* controllable */ - + if ((mc = create_binding (*child)) != 0) { Glib::Threads::Mutex::Lock lm2 (controllables_lock); controllables.push_back (mc); @@ -688,7 +770,7 @@ GenericMidiControlProtocol::load_bindings (const string& xmlpath) } } } - + if ((prop = root->property ("name")) != 0) { _current_binding = prop->value (); } @@ -708,6 +790,11 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node) MIDI::eventType ev; int intval; bool momentary; + MIDIControllable::Encoder encoder = MIDIControllable::No_enc; + bool rpn_value = false; + bool nrpn_value = false; + bool rpn_change = false; + bool nrpn_change = false; if ((prop = node.property (X_("ctl"))) != 0) { ev = MIDI::controller; @@ -717,20 +804,40 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node) ev = MIDI::program; } else if ((prop = node.property (X_("pb"))) != 0) { ev = MIDI::pitchbend; + } else if ((prop = node.property (X_("enc-l"))) != 0) { + encoder = MIDIControllable::Enc_L; + ev = MIDI::controller; + } else if ((prop = node.property (X_("enc-r"))) != 0) { + encoder = MIDIControllable::Enc_R; + ev = MIDI::controller; + } else if ((prop = node.property (X_("enc-2"))) != 0) { + encoder = MIDIControllable::Enc_2; + ev = MIDI::controller; + } else if ((prop = node.property (X_("enc-b"))) != 0) { + encoder = MIDIControllable::Enc_B; + ev = MIDI::controller; + } else if ((prop = node.property (X_("rpn"))) != 0) { + rpn_value = true; + } else if ((prop = node.property (X_("nrpn"))) != 0) { + nrpn_value = true; + } else if ((prop = node.property (X_("rpn-delta"))) != 0) { + rpn_change = true; + } else if ((prop = node.property (X_("nrpn-delta"))) != 0) { + nrpn_change = true; } else { return 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; } @@ -745,7 +852,7 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node) } else { momentary = false; } - + prop = node.property (X_("uri")); uri = prop->value(); @@ -756,7 +863,18 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node) return 0; } - mc->bind_midi (channel, ev, detail); + if (rpn_value) { + mc->bind_rpn_value (channel, detail); + } else if (nrpn_value) { + mc->bind_nrpn_value (channel, detail); + } else if (rpn_change) { + mc->bind_rpn_change (channel, detail); + } else if (nrpn_change) { + mc->bind_nrpn_change (channel, detail); + } else { + mc->set_encoder (encoder); + mc->bind_midi (channel, ev, detail); + } return mc; } @@ -832,7 +950,7 @@ GenericMidiControlProtocol::create_function (const XMLNode& node) cnt = 0; stringstream ss (prop->value()); ss << hex; - + while (ss >> val) { cnt++; } @@ -844,12 +962,12 @@ GenericMidiControlProtocol::create_function (const XMLNode& node) data = new MIDI::byte[cnt]; data_size = cnt; - + { stringstream ss (prop->value()); ss << hex; cnt = 0; - + while (ss >> val) { data[cnt++] = (MIDI::byte) val; } @@ -864,13 +982,13 @@ GenericMidiControlProtocol::create_function (const XMLNode& node) 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; } @@ -886,9 +1004,9 @@ GenericMidiControlProtocol::create_function (const XMLNode& node) } prop = node.property (X_("function")); - + MIDIFunction* mf = new MIDIFunction (*_input_port->parser()); - + if (mf->setup (*this, prop->value(), argument, data, data_size)) { delete mf; return 0; @@ -932,7 +1050,7 @@ GenericMidiControlProtocol::create_action (const XMLNode& node) cnt = 0; stringstream ss (prop->value()); ss << hex; - + while (ss >> val) { cnt++; } @@ -944,12 +1062,12 @@ GenericMidiControlProtocol::create_action (const XMLNode& node) data = new MIDI::byte[cnt]; data_size = cnt; - + { stringstream ss (prop->value()); ss << hex; cnt = 0; - + while (ss >> val) { data[cnt++] = (MIDI::byte) val; } @@ -964,13 +1082,13 @@ GenericMidiControlProtocol::create_action (const XMLNode& node) 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; } @@ -982,9 +1100,9 @@ GenericMidiControlProtocol::create_action (const XMLNode& node) } prop = node.property (X_("action")); - + MIDIAction* ma = new MIDIAction (*_input_port->parser()); - + if (ma->init (*this, prop->value(), data, data_size)) { delete ma; return 0; @@ -1029,3 +1147,67 @@ GenericMidiControlProtocol::set_threshold (int t) { _threshold = t; } + +bool +GenericMidiControlProtocol::connection_handler (boost::weak_ptr, std::string name1, boost::weak_ptr, std::string name2, bool yn) +{ + if (!_input_port || !_output_port) { + return false; + } + + string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr(_input_port)->name()); + string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr(_output_port)->name()); + + if (ni == name1 || ni == name2) { + if (yn) { + connection_state |= InputConnected; + } else { + connection_state &= ~InputConnected; + } + } else if (no == name1 || no == name2) { + if (yn) { + connection_state |= OutputConnected; + } else { + connection_state &= ~OutputConnected; + } + } else { + /* not our ports */ + 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 (); + + } else { + + } + + ConnectionChange (); /* emit signal for our GUI */ + + return true; /* connection status changed */ +} + +void +GenericMidiControlProtocol::connected () +{ + cerr << "Now connected\n"; +} + +boost::shared_ptr +GenericMidiControlProtocol::output_port() const +{ + return _output_port; +} + +boost::shared_ptr +GenericMidiControlProtocol::input_port() const +{ + return _input_port; +}