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.
20 #define __STDC_FORMAT_MACROS 1
25 #include "pbd/error.h"
26 #include "pbd/failed_constructor.h"
28 #include "midi++/port.h"
29 #include "midi++/manager.h"
31 #include "ardour/session.h"
32 #include "ardour/route.h"
33 #include "ardour/midi_ui.h"
35 #include "generic_midi_control_protocol.h"
36 #include "midicontrollable.h"
37 #include "midifunction.h"
39 using namespace ARDOUR;
45 #define midi_ui_context() MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
46 #define ui_bind(x) boost::protect (boost::bind ((x)))
48 GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
49 : ControlProtocol (s, _("Generic MIDI"), midi_ui_context())
51 MIDI::Manager* mm = MIDI::Manager::instance();
53 /* XXX it might be nice to run "control" through i18n, but thats a bit tricky because
54 the name is defined in ardour.rc which is likely not internationalized.
57 _port = mm->port (X_("control"));
60 error << _("no MIDI port named \"control\" exists - generic MIDI control disabled") << endmsg;
61 throw failed_constructor();
65 _feedback_interval = 10000; // microseconds
66 last_feedback_time = 0;
68 /* XXX is it right to do all these in the same thread as whatever emits the signal? */
70 Controllable::StartLearning.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::start_learning, this, _1));
71 Controllable::StopLearning.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::stop_learning, this, _1));
72 Controllable::CreateBinding.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::create_binding, this, _1, _2, _3));
73 Controllable::DeleteBinding.connect_same_thread (*this, boost::bind (&GenericMidiControlProtocol::delete_binding, this, _1));
75 Session::SendFeedback.connect (*this, boost::bind (&GenericMidiControlProtocol::send_feedback, this), midi_ui_context());;
77 std::string xmlpath = "/tmp/midi.map";
79 load_bindings (xmlpath);
80 reset_controllables ();
83 GenericMidiControlProtocol::~GenericMidiControlProtocol ()
85 Glib::Mutex::Lock lm (pending_lock);
86 Glib::Mutex::Lock lm2 (controllables_lock);
88 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
91 controllables.clear ();
93 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
96 pending_controllables.clear ();
98 for (MIDIFunctions::iterator i = functions.begin(); i != functions.end(); ++i) {
105 GenericMidiControlProtocol::set_active (bool /*yn*/)
107 /* start/stop delivery/outbound thread */
112 GenericMidiControlProtocol::set_feedback_interval (microseconds_t ms)
114 _feedback_interval = ms;
118 GenericMidiControlProtocol::send_feedback ()
124 microseconds_t now = get_microseconds ();
126 if (last_feedback_time != 0) {
127 if ((now - last_feedback_time) < _feedback_interval) {
134 last_feedback_time = now;
138 GenericMidiControlProtocol::_send_feedback ()
140 const int32_t bufsize = 16 * 1024; /* XXX too big */
141 MIDI::byte buf[bufsize];
142 int32_t bsize = bufsize;
143 MIDI::byte* end = buf;
145 for (MIDIControllables::iterator r = controllables.begin(); r != controllables.end(); ++r) {
146 end = (*r)->write_feedback (end, bsize);
153 _port->write (buf, (int32_t) (end - buf), 0);
157 GenericMidiControlProtocol::start_learning (Controllable* c)
163 Glib::Mutex::Lock lm (pending_lock);
164 Glib::Mutex::Lock lm2 (controllables_lock);
166 MIDIControllables::iterator tmp;
167 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ) {
170 if ((*i)->get_controllable() == c) {
172 controllables.erase (i);
177 MIDIPendingControllables::iterator ptmp;
178 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) {
181 if (((*i)->first)->get_controllable() == c) {
182 (*i)->second.disconnect();
185 pending_controllables.erase (i);
191 MIDIControllable* mc = 0;
193 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
194 if ((*i)->get_controllable()->id() == c->id()) {
201 mc = new MIDIControllable (*_port, *c);
205 Glib::Mutex::Lock lm (pending_lock);
207 MIDIPendingControllable* element = new MIDIPendingControllable;
209 c->LearningFinished.connect_same_thread (element->second, boost::bind (&GenericMidiControlProtocol::learning_stopped, this, mc));
211 pending_controllables.push_back (element);
214 mc->learn_about_external_control ();
219 GenericMidiControlProtocol::learning_stopped (MIDIControllable* mc)
221 Glib::Mutex::Lock lm (pending_lock);
222 Glib::Mutex::Lock lm2 (controllables_lock);
224 MIDIPendingControllables::iterator tmp;
226 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) {
230 if ( (*i)->first == mc) {
231 (*i)->second.disconnect();
233 pending_controllables.erase(i);
239 controllables.insert (mc);
243 GenericMidiControlProtocol::stop_learning (Controllable* c)
245 Glib::Mutex::Lock lm (pending_lock);
246 Glib::Mutex::Lock lm2 (controllables_lock);
247 MIDIControllable* dptr = 0;
249 /* learning timed out, and we've been told to consider this attempt to learn to be cancelled. find the
250 relevant MIDIControllable and remove it from the pending list.
253 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
254 if (((*i)->first)->get_controllable() == c) {
255 (*i)->first->stop_learning ();
257 (*i)->second.disconnect();
260 pending_controllables.erase (i);
269 GenericMidiControlProtocol::delete_binding (PBD::Controllable* control)
272 Glib::Mutex::Lock lm2 (controllables_lock);
274 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
275 MIDIControllable* existingBinding = (*iter);
277 if (control == (existingBinding->get_controllable())) {
278 delete existingBinding;
279 controllables.erase (iter);
287 GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, int control_number)
289 if (control != NULL) {
290 Glib::Mutex::Lock lm2 (controllables_lock);
292 MIDI::channel_t channel = (pos & 0xf);
293 MIDI::byte value = control_number;
295 // Create a MIDIControllable
296 MIDIControllable* mc = new MIDIControllable (*_port, *control);
298 // Remove any old binding for this midi channel/type/value pair
299 // Note: can't use delete_binding() here because we don't know the specific controllable we want to remove, only the midi information
300 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
301 MIDIControllable* existingBinding = (*iter);
303 if ((existingBinding->get_control_channel() & 0xf ) == channel &&
304 existingBinding->get_control_additional() == value &&
305 (existingBinding->get_control_type() & 0xf0 ) == MIDI::controller) {
307 delete existingBinding;
308 controllables.erase (iter);
313 // Update the MIDI Controllable based on the the pos param
314 // Here is where a table lookup for user mappings could go; for now we'll just wing it...
315 mc->bind_midi(channel, MIDI::controller, value);
317 controllables.insert (mc);
322 GenericMidiControlProtocol::get_state ()
324 XMLNode* node = new XMLNode ("Protocol");
327 node->add_property (X_("name"), _name);
328 node->add_property (X_("feedback"), do_feedback ? "1" : "0");
329 snprintf (buf, sizeof (buf), "%" PRIu64, _feedback_interval);
330 node->add_property (X_("feedback_interval"), buf);
332 XMLNode* children = new XMLNode (X_("controls"));
334 node->add_child_nocopy (*children);
336 Glib::Mutex::Lock lm2 (controllables_lock);
337 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
338 children->add_child_nocopy ((*i)->get_state());
345 GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
348 XMLNodeConstIterator niter;
349 const XMLProperty* prop;
351 if ((prop = node.property ("feedback")) != 0) {
352 do_feedback = (bool) atoi (prop->value().c_str());
357 if ((prop = node.property ("feedback_interval")) != 0) {
358 if (sscanf (prop->value().c_str(), "%" PRIu64, &_feedback_interval) != 1) {
359 _feedback_interval = 10000;
362 _feedback_interval = 10000;
365 boost::shared_ptr<Controllable> c;
368 Glib::Mutex::Lock lm (pending_lock);
369 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
372 pending_controllables.clear ();
375 Glib::Mutex::Lock lm2 (controllables_lock);
376 controllables.clear ();
377 nlist = node.children(); // "controls"
383 nlist = nlist.front()->children ();
385 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
387 if ((prop = (*niter)->property ("id")) != 0) {
389 ID id = prop->value ();
390 c = session->controllable_by_id (id);
393 MIDIControllable* mc = new MIDIControllable (*_port, *c);
394 if (mc->set_state (**niter, version) == 0) {
395 controllables.insert (mc);
399 warning << string_compose (
400 _("Generic MIDI control: controllable %1 not found in session (ignored)"),
409 GenericMidiControlProtocol::set_feedback (bool yn)
412 last_feedback_time = 0;
417 GenericMidiControlProtocol::get_feedback () const
423 GenericMidiControlProtocol::load_bindings (const string& xmlpath)
427 if (!state_tree.read (xmlpath.c_str())) {
428 error << string_compose(_("Could not understand MIDI bindings file %1"), xmlpath) << endmsg;
432 XMLNode* root = state_tree.root();
434 if (root->name() != X_("ArdourMIDIBindings")) {
435 error << string_compose (_("MIDI Bindings file %1 is not really a MIDI bindings file"), xmlpath) << endmsg;
439 const XMLProperty* prop;
441 if ((prop = root->property ("version")) == 0) {
448 sscanf (prop->value().c_str(), "%d.%d.%d", &major, &minor, µ);
449 Stateful::loading_state_version = (major * 1000) + minor;
452 const XMLNodeList& children (root->children());
453 XMLNodeConstIterator citer;
454 XMLNodeConstIterator gciter;
456 MIDIControllable* mc;
458 for (citer = children.begin(); citer != children.end(); ++citer) {
459 if ((*citer)->name() == "Binding") {
460 const XMLNode* child = *citer;
462 if (child->property ("uri")) {
465 if ((mc = create_binding (*child)) != 0) {
466 Glib::Mutex::Lock lm2 (controllables_lock);
467 controllables.insert (mc);
470 } else if (child->property ("function")) {
475 if ((mf = create_function (*child)) != 0) {
476 functions.push_back (mf);
486 GenericMidiControlProtocol::create_binding (const XMLNode& node)
488 const XMLProperty* prop;
494 if ((prop = node.property (X_("channel"))) == 0) {
498 if (sscanf (prop->value().c_str(), "%d", &channel) != 1) {
502 if ((prop = node.property (X_("ctl"))) != 0) {
503 ev = MIDI::controller;
504 } else if ((prop = node.property (X_("note"))) != 0) {
506 } else if ((prop = node.property (X_("pgm"))) != 0) {
512 if (sscanf (prop->value().c_str(), "%d", &detail) != 1) {
516 prop = node.property (X_("uri"));
519 MIDIControllable* mc = new MIDIControllable (*_port, uri, false);
520 mc->bind_midi (channel, ev, detail);
522 cerr << "New MC with URI = " << uri << endl;
528 GenericMidiControlProtocol::reset_controllables ()
530 Glib::Mutex::Lock lm2 (controllables_lock);
532 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
533 MIDIControllable* existingBinding = (*iter);
535 boost::shared_ptr<Controllable> c = session->controllable_by_uri (existingBinding->current_uri());
536 existingBinding->set_controllable (c.get());
541 GenericMidiControlProtocol::create_function (const XMLNode& node)
543 const XMLProperty* prop;
549 if ((prop = node.property (X_("channel"))) == 0) {
553 if (sscanf (prop->value().c_str(), "%d", &channel) != 1) {
557 if ((prop = node.property (X_("ctl"))) != 0) {
558 ev = MIDI::controller;
559 } else if ((prop = node.property (X_("note"))) != 0) {
561 } else if ((prop = node.property (X_("pgm"))) != 0) {
567 if (sscanf (prop->value().c_str(), "%d", &detail) != 1) {
571 prop = node.property (X_("function"));
573 MIDIFunction* mf = new MIDIFunction (*_port);
575 if (mf->init (*this, prop->value())) {
580 mf->bind_midi (channel, ev, detail);
582 cerr << "New MF with function = " << prop->value() << endl;