2 Copyright (C) 2006 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include "pbd/controllable_descriptor.h"
26 #include "pbd/error.h"
27 #include "pbd/failed_constructor.h"
28 #include "pbd/pathscanner.h"
29 #include "pbd/xml++.h"
31 #include "midi++/port.h"
32 #include "midi++/manager.h"
34 #include "ardour/filesystem_paths.h"
35 #include "ardour/session.h"
36 #include "ardour/route.h"
37 #include "ardour/midi_ui.h"
38 #include "ardour/rc_configuration.h"
40 #include "generic_midi_control_protocol.h"
41 #include "midicontrollable.h"
42 #include "midifunction.h"
43 #include "midiaction.h"
45 using namespace ARDOUR;
51 #define midi_ui_context() MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
52 #define ui_bind(x) boost::protect (boost::bind ((x)))
54 GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
55 : ControlProtocol (s, _("Generic MIDI"), midi_ui_context())
59 _input_port = MIDI::Manager::instance()->midi_input_port ();
60 _output_port = MIDI::Manager::instance()->midi_output_port ();
63 _feedback_interval = 10000; // microseconds
64 last_feedback_time = 0;
69 /* XXX is it right to do all these in the same thread as whatever emits the signal? */
71 Controllable::StartLearning.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::start_learning, this, _1));
72 Controllable::StopLearning.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::stop_learning, this, _1));
73 Controllable::CreateBinding.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::create_binding, this, _1, _2, _3));
74 Controllable::DeleteBinding.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::delete_binding, this, _1));
76 Session::SendFeedback.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::send_feedback, this), midi_ui_context());;
77 Route::RemoteControlIDChange.connect (*this, MISSING_INVALIDATOR, boost::bind (&GenericMidiControlProtocol::reset_controllables, this), midi_ui_context());
82 GenericMidiControlProtocol::~GenericMidiControlProtocol ()
88 static const char* const midi_map_dir_name = "midi_maps";
89 static const char* const midi_map_suffix = ".map";
92 system_midi_map_search_path ()
94 SearchPath spath(system_data_search_path());
95 spath.add_subdirectory_to_paths(midi_map_dir_name);
97 // just return the first directory in the search path that exists
98 SearchPath::const_iterator i = std::find_if(spath.begin(), spath.end(), sys::exists);
100 if (i == spath.end()) return sys::path();
106 user_midi_map_directory ()
108 sys::path p(user_config_directory());
109 p /= midi_map_dir_name;
115 midi_map_filter (const string &str, void */*arg*/)
117 return (str.length() > strlen(midi_map_suffix) &&
118 str.find (midi_map_suffix) == (str.length() - strlen (midi_map_suffix)));
122 GenericMidiControlProtocol::reload_maps ()
124 vector<string *> *midi_maps;
126 SearchPath spath (system_midi_map_search_path());
127 spath += user_midi_map_directory ();
129 midi_maps = scanner (spath.to_string(), midi_map_filter, 0, false, true);
132 cerr << "No MIDI maps found using " << spath.to_string() << endl;
136 cerr << "Found " << midi_maps->size() << " MIDI maps along " << spath.to_string() << endl;
138 for (vector<string*>::iterator i = midi_maps->begin(); i != midi_maps->end(); ++i) {
139 string fullpath = *(*i);
143 if (!tree.read (fullpath.c_str())) {
149 XMLProperty* prop = tree.root()->property ("name");
155 mi.name = prop->value ();
158 map_info.push_back (mi);
165 GenericMidiControlProtocol::drop_all ()
167 Glib::Mutex::Lock lm (pending_lock);
168 Glib::Mutex::Lock lm2 (controllables_lock);
170 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
173 controllables.clear ();
175 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
178 pending_controllables.clear ();
180 for (MIDIFunctions::iterator i = functions.begin(); i != functions.end(); ++i) {
185 for (MIDIActions::iterator i = actions.begin(); i != actions.end(); ++i) {
192 GenericMidiControlProtocol::drop_bindings ()
194 Glib::Mutex::Lock lm2 (controllables_lock);
196 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ) {
197 if (!(*i)->learned()) {
199 i = controllables.erase (i);
205 for (MIDIFunctions::iterator i = functions.begin(); i != functions.end(); ++i) {
210 _current_binding = "";
216 GenericMidiControlProtocol::set_active (bool /*yn*/)
218 /* start/stop delivery/outbound thread */
223 GenericMidiControlProtocol::set_feedback_interval (microseconds_t ms)
225 _feedback_interval = ms;
229 GenericMidiControlProtocol::send_feedback ()
235 microseconds_t now = get_microseconds ();
237 if (last_feedback_time != 0) {
238 if ((now - last_feedback_time) < _feedback_interval) {
245 last_feedback_time = now;
249 GenericMidiControlProtocol::_send_feedback ()
251 const int32_t bufsize = 16 * 1024; /* XXX too big */
252 MIDI::byte buf[bufsize];
253 int32_t bsize = bufsize;
254 MIDI::byte* end = buf;
256 for (MIDIControllables::iterator r = controllables.begin(); r != controllables.end(); ++r) {
257 end = (*r)->write_feedback (end, bsize);
264 _output_port->write (buf, (int32_t) (end - buf), 0);
268 GenericMidiControlProtocol::start_learning (Controllable* c)
274 Glib::Mutex::Lock lm2 (controllables_lock);
276 MIDIControllables::iterator tmp;
277 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ) {
280 if ((*i)->get_controllable() == c) {
282 controllables.erase (i);
288 Glib::Mutex::Lock lm (pending_lock);
290 MIDIPendingControllables::iterator ptmp;
291 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) {
294 if (((*i)->first)->get_controllable() == c) {
295 (*i)->second.disconnect();
298 pending_controllables.erase (i);
304 MIDIControllable* mc = 0;
306 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
307 if ((*i)->get_controllable() && ((*i)->get_controllable()->id() == c->id())) {
314 mc = new MIDIControllable (*_input_port, *c, false);
318 Glib::Mutex::Lock lm (pending_lock);
320 MIDIPendingControllable* element = new MIDIPendingControllable;
322 c->LearningFinished.connect_same_thread (element->second, boost::bind (&GenericMidiControlProtocol::learning_stopped, this, mc));
324 pending_controllables.push_back (element);
327 mc->learn_about_external_control ();
332 GenericMidiControlProtocol::learning_stopped (MIDIControllable* mc)
334 Glib::Mutex::Lock lm (pending_lock);
335 Glib::Mutex::Lock lm2 (controllables_lock);
337 MIDIPendingControllables::iterator tmp;
339 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) {
343 if ( (*i)->first == mc) {
344 (*i)->second.disconnect();
346 pending_controllables.erase(i);
352 controllables.push_back (mc);
356 GenericMidiControlProtocol::stop_learning (Controllable* c)
358 Glib::Mutex::Lock lm (pending_lock);
359 Glib::Mutex::Lock lm2 (controllables_lock);
360 MIDIControllable* dptr = 0;
362 /* learning timed out, and we've been told to consider this attempt to learn to be cancelled. find the
363 relevant MIDIControllable and remove it from the pending list.
366 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
367 if (((*i)->first)->get_controllable() == c) {
368 (*i)->first->stop_learning ();
370 (*i)->second.disconnect();
373 pending_controllables.erase (i);
382 GenericMidiControlProtocol::delete_binding (PBD::Controllable* control)
385 Glib::Mutex::Lock lm2 (controllables_lock);
387 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end();) {
388 MIDIControllable* existingBinding = (*iter);
390 if (control == (existingBinding->get_controllable())) {
391 delete existingBinding;
392 iter = controllables.erase (iter);
402 GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, int control_number)
404 if (control != NULL) {
405 Glib::Mutex::Lock lm2 (controllables_lock);
407 MIDI::channel_t channel = (pos & 0xf);
408 MIDI::byte value = control_number;
410 // Create a MIDIControllable
411 MIDIControllable* mc = new MIDIControllable (*_input_port, *control, false);
413 // Remove any old binding for this midi channel/type/value pair
414 // Note: can't use delete_binding() here because we don't know the specific controllable we want to remove, only the midi information
415 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end();) {
416 MIDIControllable* existingBinding = (*iter);
418 if ((existingBinding->get_control_channel() & 0xf ) == channel &&
419 existingBinding->get_control_additional() == value &&
420 (existingBinding->get_control_type() & 0xf0 ) == MIDI::controller) {
422 delete existingBinding;
423 iter = controllables.erase (iter);
430 // Update the MIDI Controllable based on the the pos param
431 // Here is where a table lookup for user mappings could go; for now we'll just wing it...
432 mc->bind_midi(channel, MIDI::controller, value);
434 controllables.push_back (mc);
439 GenericMidiControlProtocol::get_state ()
441 XMLNode* node = new XMLNode ("Protocol");
444 node->add_property (X_("name"), _name);
445 node->add_property (X_("feedback"), do_feedback ? "1" : "0");
446 snprintf (buf, sizeof (buf), "%" PRIu64, _feedback_interval);
447 node->add_property (X_("feedback_interval"), buf);
449 if (!_current_binding.empty()) {
450 node->add_property ("binding", _current_binding);
453 XMLNode* children = new XMLNode (X_("Controls"));
455 node->add_child_nocopy (*children);
457 Glib::Mutex::Lock lm2 (controllables_lock);
458 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
460 /* we don't care about bindings that come from a bindings map, because
461 they will all be reset/recreated when we load the relevant bindings
465 if ((*i)->learned()) {
466 children->add_child_nocopy ((*i)->get_state());
474 GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
477 XMLNodeConstIterator niter;
478 const XMLProperty* prop;
480 if ((prop = node.property ("feedback")) != 0) {
481 do_feedback = (bool) atoi (prop->value().c_str());
486 if ((prop = node.property ("feedback_interval")) != 0) {
487 if (sscanf (prop->value().c_str(), "%" PRIu64, &_feedback_interval) != 1) {
488 _feedback_interval = 10000;
491 _feedback_interval = 10000;
494 boost::shared_ptr<Controllable> c;
497 Glib::Mutex::Lock lm (pending_lock);
498 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
501 pending_controllables.clear ();
505 Glib::Mutex::Lock lm2 (controllables_lock);
506 controllables.clear ();
507 nlist = node.children(); // "Controls"
513 nlist = nlist.front()->children ();
515 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
517 if ((prop = (*niter)->property ("id")) != 0) {
519 cerr << "Looking for MIDI Controllable with ID " << prop->value() << endl;
521 ID id = prop->value ();
522 Controllable* c = Controllable::by_id (id);
524 cerr << "\tresult = " << c << endl;
527 MIDIControllable* mc = new MIDIControllable (*_input_port, *c, false);
529 if (mc->set_state (**niter, version) == 0) {
530 controllables.push_back (mc);
534 warning << string_compose (
535 _("Generic MIDI control: controllable %1 not found in session (ignored)"),
543 if ((prop = node.property ("binding")) != 0) {
544 for (list<MapInfo>::iterator x = map_info.begin(); x != map_info.end(); ++x) {
545 if (prop->value() == (*x).name) {
546 load_bindings ((*x).path);
556 GenericMidiControlProtocol::set_feedback (bool yn)
559 last_feedback_time = 0;
564 GenericMidiControlProtocol::get_feedback () const
573 GenericMidiControlProtocol::load_bindings (const string& xmlpath)
577 if (!state_tree.read (xmlpath.c_str())) {
578 error << string_compose(_("Could not understand MIDI bindings file %1"), xmlpath) << endmsg;
582 XMLNode* root = state_tree.root();
584 if (root->name() != X_("ArdourMIDIBindings")) {
585 error << string_compose (_("MIDI Bindings file %1 is not really a MIDI bindings file"), xmlpath) << endmsg;
589 const XMLProperty* prop;
591 if ((prop = root->property ("version")) == 0) {
598 sscanf (prop->value().c_str(), "%d.%d.%d", &major, &minor, µ);
599 Stateful::loading_state_version = (major * 1000) + minor;
602 const XMLNodeList& children (root->children());
603 XMLNodeConstIterator citer;
604 XMLNodeConstIterator gciter;
606 MIDIControllable* mc;
610 for (citer = children.begin(); citer != children.end(); ++citer) {
612 if ((*citer)->name() == "DeviceInfo") {
613 const XMLProperty* prop;
615 if ((prop = (*citer)->property ("bank-size")) != 0) {
616 _bank_size = atoi (prop->value());
621 if ((*citer)->name() == "Binding") {
622 const XMLNode* child = *citer;
624 if (child->property ("uri")) {
627 if ((mc = create_binding (*child)) != 0) {
628 Glib::Mutex::Lock lm2 (controllables_lock);
629 controllables.push_back (mc);
632 } else if (child->property ("function")) {
637 if ((mf = create_function (*child)) != 0) {
638 functions.push_back (mf);
641 } else if (child->property ("action")) {
644 if ((ma = create_action (*child)) != 0) {
645 actions.push_back (ma);
651 if ((prop = root->property ("name")) != 0) {
652 _current_binding = prop->value ();
655 reset_controllables ();
661 GenericMidiControlProtocol::create_binding (const XMLNode& node)
663 const XMLProperty* prop;
665 MIDI::channel_t channel;
671 if ((prop = node.property (X_("ctl"))) != 0) {
672 ev = MIDI::controller;
673 } else if ((prop = node.property (X_("note"))) != 0) {
675 } else if ((prop = node.property (X_("pgm"))) != 0) {
681 if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
685 detail = (MIDI::byte) intval;
687 if ((prop = node.property (X_("channel"))) == 0) {
691 if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
694 channel = (MIDI::channel_t) intval;
695 /* adjust channel to zero-based counting */
700 if ((prop = node.property (X_("momentary"))) != 0) {
701 momentary = string_is_affirmative (prop->value());
706 prop = node.property (X_("uri"));
709 MIDIControllable* mc = new MIDIControllable (*_input_port, momentary);
711 if (mc->init (uri)) {
716 mc->bind_midi (channel, ev, detail);
722 GenericMidiControlProtocol::reset_controllables ()
724 Glib::Mutex::Lock lm2 (controllables_lock);
726 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
727 MIDIControllable* existingBinding = (*iter);
729 if (!existingBinding->learned()) {
730 ControllableDescriptor& desc (existingBinding->descriptor());
733 desc.set_bank_offset (_current_bank * _bank_size);
736 boost::shared_ptr<Controllable> c = session->controllable_by_descriptor (desc);
737 existingBinding->set_controllable (c.get());
743 GenericMidiControlProtocol::create_function (const XMLNode& node)
745 const XMLProperty* prop;
747 MIDI::byte detail = 0;
748 MIDI::channel_t channel = 0;
751 MIDI::byte* data = 0;
752 uint32_t data_size = 0;
754 if ((prop = node.property (X_("ctl"))) != 0) {
755 ev = MIDI::controller;
756 } else if ((prop = node.property (X_("note"))) != 0) {
758 } else if ((prop = node.property (X_("pgm"))) != 0) {
760 } else if ((prop = node.property (X_("sysex"))) != 0 || (prop = node.property (X_("msg"))) != 0) {
762 if (prop->name() == X_("sysex")) {
773 stringstream ss (prop->value());
785 data = new MIDI::byte[cnt];
789 stringstream ss (prop->value());
794 data[cnt++] = (MIDI::byte) val;
799 warning << "Binding ignored - unknown type" << endmsg;
803 if (data_size == 0) {
804 if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
808 detail = (MIDI::byte) intval;
810 if ((prop = node.property (X_("channel"))) == 0) {
814 if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
817 channel = (MIDI::channel_t) intval;
818 /* adjust channel to zero-based counting */
824 prop = node.property (X_("function"));
826 MIDIFunction* mf = new MIDIFunction (*_input_port);
828 if (mf->init (*this, prop->value(), data, data_size)) {
833 mf->bind_midi (channel, ev, detail);
839 GenericMidiControlProtocol::create_action (const XMLNode& node)
841 const XMLProperty* prop;
843 MIDI::byte detail = 0;
844 MIDI::channel_t channel = 0;
847 MIDI::byte* data = 0;
848 uint32_t data_size = 0;
850 if ((prop = node.property (X_("ctl"))) != 0) {
851 ev = MIDI::controller;
852 } else if ((prop = node.property (X_("note"))) != 0) {
854 } else if ((prop = node.property (X_("pgm"))) != 0) {
856 } else if ((prop = node.property (X_("sysex"))) != 0 || (prop = node.property (X_("msg"))) != 0) {
858 if (prop->name() == X_("sysex")) {
869 stringstream ss (prop->value());
881 data = new MIDI::byte[cnt];
885 stringstream ss (prop->value());
890 data[cnt++] = (MIDI::byte) val;
895 warning << "Binding ignored - unknown type" << endmsg;
899 if (data_size == 0) {
900 if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
904 detail = (MIDI::byte) intval;
906 if ((prop = node.property (X_("channel"))) == 0) {
910 if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
913 channel = (MIDI::channel_t) intval;
914 /* adjust channel to zero-based counting */
920 prop = node.property (X_("action"));
922 MIDIAction* ma = new MIDIAction (*_input_port);
924 if (ma->init (*this, prop->value(), data, data_size)) {
929 ma->bind_midi (channel, ev, detail);
935 GenericMidiControlProtocol::set_current_bank (uint32_t b)
938 reset_controllables ();
942 GenericMidiControlProtocol::next_bank ()
945 reset_controllables ();
949 GenericMidiControlProtocol::prev_bank()
953 reset_controllables ();