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
26 #include "pbd/error.h"
27 #include "pbd/failed_constructor.h"
29 #include "midi++/port.h"
30 #include "midi++/manager.h"
32 #include "ardour/session.h"
33 #include "ardour/route.h"
34 #include "ardour/midi_ui.h"
36 #include "generic_midi_control_protocol.h"
37 #include "midicontrollable.h"
38 #include "midifunction.h"
40 using namespace ARDOUR;
46 #define midi_ui_context() MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
47 #define ui_bind(x) boost::protect (boost::bind ((x)))
49 GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
50 : ControlProtocol (s, _("Generic MIDI"), midi_ui_context())
52 MIDI::Manager* mm = MIDI::Manager::instance();
54 /* XXX it might be nice to run "control" through i18n, but thats a bit tricky because
55 the name is defined in ardour.rc which is likely not internationalized.
58 _port = mm->port (X_("control"));
61 error << _("no MIDI port named \"control\" exists - generic MIDI control disabled") << endmsg;
62 throw failed_constructor();
66 _feedback_interval = 10000; // microseconds
67 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, boost::bind (&GenericMidiControlProtocol::send_feedback, this), midi_ui_context());;
78 std::string xmlpath = "/tmp/midi.map";
80 load_bindings (xmlpath);
81 reset_controllables ();
84 GenericMidiControlProtocol::~GenericMidiControlProtocol ()
86 Glib::Mutex::Lock lm (pending_lock);
87 Glib::Mutex::Lock lm2 (controllables_lock);
89 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
92 controllables.clear ();
94 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
97 pending_controllables.clear ();
99 for (MIDIFunctions::iterator i = functions.begin(); i != functions.end(); ++i) {
106 GenericMidiControlProtocol::set_active (bool /*yn*/)
108 /* start/stop delivery/outbound thread */
113 GenericMidiControlProtocol::set_feedback_interval (microseconds_t ms)
115 _feedback_interval = ms;
119 GenericMidiControlProtocol::send_feedback ()
125 microseconds_t now = get_microseconds ();
127 if (last_feedback_time != 0) {
128 if ((now - last_feedback_time) < _feedback_interval) {
135 last_feedback_time = now;
139 GenericMidiControlProtocol::_send_feedback ()
141 const int32_t bufsize = 16 * 1024; /* XXX too big */
142 MIDI::byte buf[bufsize];
143 int32_t bsize = bufsize;
144 MIDI::byte* end = buf;
146 for (MIDIControllables::iterator r = controllables.begin(); r != controllables.end(); ++r) {
147 end = (*r)->write_feedback (end, bsize);
154 _port->write (buf, (int32_t) (end - buf), 0);
158 GenericMidiControlProtocol::start_learning (Controllable* c)
164 Glib::Mutex::Lock lm (pending_lock);
165 Glib::Mutex::Lock lm2 (controllables_lock);
167 MIDIControllables::iterator tmp;
168 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ) {
171 if ((*i)->get_controllable() == c) {
173 controllables.erase (i);
178 MIDIPendingControllables::iterator ptmp;
179 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) {
182 if (((*i)->first)->get_controllable() == c) {
183 (*i)->second.disconnect();
186 pending_controllables.erase (i);
192 MIDIControllable* mc = 0;
194 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
195 if ((*i)->get_controllable()->id() == c->id()) {
202 mc = new MIDIControllable (*_port, *c);
206 Glib::Mutex::Lock lm (pending_lock);
208 MIDIPendingControllable* element = new MIDIPendingControllable;
210 c->LearningFinished.connect_same_thread (element->second, boost::bind (&GenericMidiControlProtocol::learning_stopped, this, mc));
212 pending_controllables.push_back (element);
215 mc->learn_about_external_control ();
220 GenericMidiControlProtocol::learning_stopped (MIDIControllable* mc)
222 Glib::Mutex::Lock lm (pending_lock);
223 Glib::Mutex::Lock lm2 (controllables_lock);
225 MIDIPendingControllables::iterator tmp;
227 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) {
231 if ( (*i)->first == mc) {
232 (*i)->second.disconnect();
234 pending_controllables.erase(i);
240 controllables.insert (mc);
244 GenericMidiControlProtocol::stop_learning (Controllable* c)
246 Glib::Mutex::Lock lm (pending_lock);
247 Glib::Mutex::Lock lm2 (controllables_lock);
248 MIDIControllable* dptr = 0;
250 /* learning timed out, and we've been told to consider this attempt to learn to be cancelled. find the
251 relevant MIDIControllable and remove it from the pending list.
254 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
255 if (((*i)->first)->get_controllable() == c) {
256 (*i)->first->stop_learning ();
258 (*i)->second.disconnect();
261 pending_controllables.erase (i);
270 GenericMidiControlProtocol::delete_binding (PBD::Controllable* control)
273 Glib::Mutex::Lock lm2 (controllables_lock);
275 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
276 MIDIControllable* existingBinding = (*iter);
278 if (control == (existingBinding->get_controllable())) {
279 delete existingBinding;
280 controllables.erase (iter);
288 GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, int control_number)
290 if (control != NULL) {
291 Glib::Mutex::Lock lm2 (controllables_lock);
293 MIDI::channel_t channel = (pos & 0xf);
294 MIDI::byte value = control_number;
296 // Create a MIDIControllable
297 MIDIControllable* mc = new MIDIControllable (*_port, *control);
299 // Remove any old binding for this midi channel/type/value pair
300 // Note: can't use delete_binding() here because we don't know the specific controllable we want to remove, only the midi information
301 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
302 MIDIControllable* existingBinding = (*iter);
304 if ((existingBinding->get_control_channel() & 0xf ) == channel &&
305 existingBinding->get_control_additional() == value &&
306 (existingBinding->get_control_type() & 0xf0 ) == MIDI::controller) {
308 delete existingBinding;
309 controllables.erase (iter);
314 // Update the MIDI Controllable based on the the pos param
315 // Here is where a table lookup for user mappings could go; for now we'll just wing it...
316 mc->bind_midi(channel, MIDI::controller, value);
318 controllables.insert (mc);
323 GenericMidiControlProtocol::get_state ()
325 XMLNode* node = new XMLNode ("Protocol");
328 node->add_property (X_("name"), _name);
329 node->add_property (X_("feedback"), do_feedback ? "1" : "0");
330 snprintf (buf, sizeof (buf), "%" PRIu64, _feedback_interval);
331 node->add_property (X_("feedback_interval"), buf);
333 XMLNode* children = new XMLNode (X_("controls"));
335 node->add_child_nocopy (*children);
337 Glib::Mutex::Lock lm2 (controllables_lock);
338 for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
339 children->add_child_nocopy ((*i)->get_state());
346 GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
349 XMLNodeConstIterator niter;
350 const XMLProperty* prop;
352 if ((prop = node.property ("feedback")) != 0) {
353 do_feedback = (bool) atoi (prop->value().c_str());
358 if ((prop = node.property ("feedback_interval")) != 0) {
359 if (sscanf (prop->value().c_str(), "%" PRIu64, &_feedback_interval) != 1) {
360 _feedback_interval = 10000;
363 _feedback_interval = 10000;
366 boost::shared_ptr<Controllable> c;
369 Glib::Mutex::Lock lm (pending_lock);
370 for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
373 pending_controllables.clear ();
376 Glib::Mutex::Lock lm2 (controllables_lock);
377 controllables.clear ();
378 nlist = node.children(); // "controls"
384 nlist = nlist.front()->children ();
386 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
388 if ((prop = (*niter)->property ("id")) != 0) {
390 ID id = prop->value ();
391 c = session->controllable_by_id (id);
394 MIDIControllable* mc = new MIDIControllable (*_port, *c);
395 if (mc->set_state (**niter, version) == 0) {
396 controllables.insert (mc);
400 warning << string_compose (
401 _("Generic MIDI control: controllable %1 not found in session (ignored)"),
410 GenericMidiControlProtocol::set_feedback (bool yn)
413 last_feedback_time = 0;
418 GenericMidiControlProtocol::get_feedback () const
424 GenericMidiControlProtocol::load_bindings (const string& xmlpath)
428 if (!state_tree.read (xmlpath.c_str())) {
429 error << string_compose(_("Could not understand MIDI bindings file %1"), xmlpath) << endmsg;
433 XMLNode* root = state_tree.root();
435 if (root->name() != X_("ArdourMIDIBindings")) {
436 error << string_compose (_("MIDI Bindings file %1 is not really a MIDI bindings file"), xmlpath) << endmsg;
440 const XMLProperty* prop;
442 if ((prop = root->property ("version")) == 0) {
449 sscanf (prop->value().c_str(), "%d.%d.%d", &major, &minor, µ);
450 Stateful::loading_state_version = (major * 1000) + minor;
453 const XMLNodeList& children (root->children());
454 XMLNodeConstIterator citer;
455 XMLNodeConstIterator gciter;
457 MIDIControllable* mc;
459 for (citer = children.begin(); citer != children.end(); ++citer) {
460 if ((*citer)->name() == "Binding") {
461 const XMLNode* child = *citer;
463 if (child->property ("uri")) {
466 if ((mc = create_binding (*child)) != 0) {
467 Glib::Mutex::Lock lm2 (controllables_lock);
468 controllables.insert (mc);
471 } else if (child->property ("function")) {
473 cerr << "try to create a function from " << child->property ("function")->value() << endl;
478 if ((mf = create_function (*child)) != 0) {
479 functions.push_back (mf);
489 GenericMidiControlProtocol::create_binding (const XMLNode& node)
491 const XMLProperty* prop;
497 if ((prop = node.property (X_("channel"))) == 0) {
501 if (sscanf (prop->value().c_str(), "%d", &channel) != 1) {
505 if ((prop = node.property (X_("ctl"))) != 0) {
506 ev = MIDI::controller;
507 } else if ((prop = node.property (X_("note"))) != 0) {
509 } else if ((prop = node.property (X_("pgm"))) != 0) {
515 if (sscanf (prop->value().c_str(), "%d", &detail) != 1) {
519 prop = node.property (X_("uri"));
522 MIDIControllable* mc = new MIDIControllable (*_port, uri, false);
523 mc->bind_midi (channel, ev, detail);
525 cerr << "New MC with URI = " << uri << endl;
531 GenericMidiControlProtocol::reset_controllables ()
533 Glib::Mutex::Lock lm2 (controllables_lock);
535 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
536 MIDIControllable* existingBinding = (*iter);
538 boost::shared_ptr<Controllable> c = session->controllable_by_uri (existingBinding->current_uri());
539 existingBinding->set_controllable (c.get());
544 GenericMidiControlProtocol::create_function (const XMLNode& node)
546 const XMLProperty* prop;
548 MIDI::byte detail = 0;
549 MIDI::channel_t channel = 0;
552 MIDI::byte* sysex = 0;
553 uint32_t sysex_size = 0;
555 if ((prop = node.property (X_("ctl"))) != 0) {
556 ev = MIDI::controller;
557 } else if ((prop = node.property (X_("note"))) != 0) {
559 } else if ((prop = node.property (X_("pgm"))) != 0) {
561 } else if ((prop = node.property (X_("sysex"))) != 0) {
569 stringstream ss (prop->value());
581 sysex = new MIDI::byte[cnt];
585 stringstream ss (prop->value());
590 sysex[cnt++] = (MIDI::byte) val;
591 cerr << hex << (int) sysex[cnt-1] << dec << ' ' << endl;
596 warning << "Binding ignored - unknown type" << endmsg;
600 if (sysex_size == 0) {
601 if ((prop = node.property (X_("channel"))) == 0) {
605 if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
608 channel = (MIDI::channel_t) intval;
609 /* adjust channel to zero-based counting */
614 if (sscanf (prop->value().c_str(), "%d", &intval) != 1) {
618 detail = (MIDI::byte) intval;
621 prop = node.property (X_("function"));
623 MIDIFunction* mf = new MIDIFunction (*_port);
625 if (mf->init (*this, prop->value(), sysex, sysex_size)) {
630 mf->bind_midi (channel, ev, detail);
632 cerr << "New MF with function = " << prop->value() << endl;