b3cd5746a6c0339a90550e8ce63aad44b92a35f7
[ardour.git] / libs / surfaces / generic_midi / generic_midi_control_protocol.cc
1 /*
2     Copyright (C) 2006 Paul Davis
3  
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.
8
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.
13
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.
17
18 */
19
20 #define __STDC_FORMAT_MACROS 1
21 #include <stdint.h>
22
23 #include <algorithm>
24
25 #include <pbd/error.h>
26 #include <pbd/failed_constructor.h>
27
28 #include <midi++/port.h>
29 #include <midi++/manager.h>
30
31 #include <ardour/route.h>
32 #include <ardour/session.h>
33
34 #include "generic_midi_control_protocol.h"
35 #include "midicontrollable.h"
36
37 using namespace ARDOUR;
38 using namespace PBD;
39
40 #include "i18n.h"
41
42 GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s)
43         : ControlProtocol (s, _("Generic MIDI"))
44 {
45         MIDI::Manager* mm = MIDI::Manager::instance();
46
47         /* XXX it might be nice to run "control" through i18n, but thats a bit tricky because
48            the name is defined in ardour.rc which is likely not internationalized.
49         */
50         
51         _port = mm->port (X_("control"));
52
53         if (_port == 0) {
54                 error << _("no MIDI port named \"control\" exists - generic MIDI control disabled") << endmsg;
55                 throw failed_constructor();
56         }
57
58         do_feedback = false;
59         _feedback_interval = 10000; // microseconds
60         last_feedback_time = 0;
61
62         auto_binding = FALSE;
63
64         Controllable::StartLearning.connect (mem_fun (*this, &GenericMidiControlProtocol::start_learning));
65         Controllable::StopLearning.connect (mem_fun (*this, &GenericMidiControlProtocol::stop_learning));
66         Session::SendFeedback.connect (mem_fun (*this, &GenericMidiControlProtocol::send_feedback));
67         
68         Controllable::CreateBinding.connect (mem_fun (*this, &GenericMidiControlProtocol::create_binding));
69         Controllable::DeleteBinding.connect (mem_fun (*this, &GenericMidiControlProtocol::delete_binding));
70
71         Session::AutoBindingOn.connect (mem_fun (*this, &GenericMidiControlProtocol::auto_binding_on));
72         Session::AutoBindingOff.connect (mem_fun (*this, &GenericMidiControlProtocol::auto_binding_off));
73 }
74
75 GenericMidiControlProtocol::~GenericMidiControlProtocol ()
76 {
77 }
78
79 int
80 GenericMidiControlProtocol::set_active (bool /*yn*/)
81 {
82         /* start/stop delivery/outbound thread */
83         return 0;
84 }
85
86 void
87 GenericMidiControlProtocol::set_feedback_interval (microseconds_t ms)
88 {
89         _feedback_interval = ms;
90 }
91
92 void 
93 GenericMidiControlProtocol::send_feedback ()
94 {
95         if (!do_feedback) {
96                 return;
97         }
98
99         microseconds_t now = get_microseconds ();
100
101         if (last_feedback_time != 0) {
102                 if ((now - last_feedback_time) < _feedback_interval) {
103                         return;
104                 }
105         }
106
107         _send_feedback ();
108         
109         last_feedback_time = now;
110 }
111
112 void 
113 GenericMidiControlProtocol::_send_feedback ()
114 {
115         const int32_t bufsize = 16 * 1024; /* XXX too big */
116         MIDI::byte buf[bufsize];
117         int32_t bsize = bufsize;
118         MIDI::byte* end = buf;
119         
120         for (MIDIControllables::iterator r = controllables.begin(); r != controllables.end(); ++r) {
121                 end = (*r)->write_feedback (end, bsize);
122         }
123         
124         if (end == buf) {
125                 return;
126         } 
127
128         _port->write (buf, (int32_t) (end - buf), 0);
129 }
130
131 bool
132 GenericMidiControlProtocol::start_learning (Controllable* c)
133 {
134         if (c == 0) {
135                 return false;
136         }
137
138         MIDIControllables::iterator tmp;
139         for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ) {
140                 tmp = i;
141                 ++tmp;
142                 if (&(*i)->get_controllable() == c) {
143                         delete (*i);
144                         controllables.erase (i);
145                 }
146                 i = tmp;
147         }
148
149         MIDIPendingControllables::iterator ptmp;
150         for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) {
151                 ptmp = i;
152                 ++ptmp;
153                 if (&((*i).first)->get_controllable() == c) {
154                         (*i).second.disconnect();
155                         delete (*i).first;
156                         pending_controllables.erase (i);
157                 }
158                 i = ptmp;
159         }
160
161
162         MIDIControllable* mc = 0;
163
164         for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
165                 if ((*i)->get_controllable().id() == c->id()) {
166                         mc = *i;
167                         break;
168                 }
169         }
170
171         if (!mc) {
172                 mc = new MIDIControllable (*_port, *c);
173         }
174         
175         {
176                 Glib::Mutex::Lock lm (pending_lock);
177
178                 std::pair<MIDIControllable *, sigc::connection> element;
179                 element.first = mc;
180                 element.second = c->LearningFinished.connect (bind (mem_fun (*this, &GenericMidiControlProtocol::learning_stopped), mc));
181
182                 pending_controllables.push_back (element);
183         }
184
185         mc->learn_about_external_control ();
186         return true;
187 }
188
189 void
190 GenericMidiControlProtocol::learning_stopped (MIDIControllable* mc)
191 {
192         Glib::Mutex::Lock lm (pending_lock);
193         Glib::Mutex::Lock lm2 (controllables_lock);
194         
195         MIDIPendingControllables::iterator tmp;
196
197         for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ) {
198                 tmp = i;
199                 ++tmp;
200
201                 if ( (*i).first == mc) {
202                         (*i).second.disconnect();
203                         pending_controllables.erase(i);
204                 }
205
206                 i = tmp;
207         }
208
209         controllables.insert (mc);
210 }
211
212 void
213 GenericMidiControlProtocol::stop_learning (Controllable* c)
214 {
215         Glib::Mutex::Lock lm (pending_lock);
216         Glib::Mutex::Lock lm2 (controllables_lock);
217         MIDIControllable* dptr = 0;
218
219         /* learning timed out, and we've been told to consider this attempt to learn to be cancelled. find the
220            relevant MIDIControllable and remove it from the pending list.
221         */
222
223         for (MIDIPendingControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) {
224                 if (&((*i).first)->get_controllable() == c) {
225                         (*i).first->stop_learning ();
226                         dptr = (*i).first;
227                         (*i).second.disconnect();
228
229                         pending_controllables.erase (i);
230                         break;
231                 }
232         }
233         
234         delete dptr;
235 }
236
237 void
238 GenericMidiControlProtocol::delete_binding (PBD::Controllable* control)
239 {
240         if (control != 0) {
241                 Glib::Mutex::Lock lm2 (controllables_lock);
242                 
243                 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
244                         MIDIControllable* existingBinding = (*iter);
245                         
246                         if (control == &(existingBinding->get_controllable())) {
247                                 delete existingBinding;
248                                 controllables.erase (iter);
249                         }
250                         
251                 }
252         }
253 }
254
255 void
256 GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, int control_number)
257 {
258         if (control != NULL) {
259                 Glib::Mutex::Lock lm2 (controllables_lock);
260                 
261                 MIDI::channel_t channel = (pos & 0xf);
262                 MIDI::byte value = control_number;
263                 
264                 // Create a MIDIControllable
265                 MIDIControllable* mc = new MIDIControllable (*_port, *control);
266                 
267                 // Remove any old binding for this midi channel/type/value pair
268                 // Note:  can't use delete_binding() here because we don't know the specific controllable we want to remove, only the midi information
269                 for (MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
270                         MIDIControllable* existingBinding = (*iter);
271                         
272                         if ((existingBinding->get_control_channel() & 0xf ) == channel &&
273                             existingBinding->get_control_additional() == value &&
274                             (existingBinding->get_control_type() & 0xf0 ) == MIDI::controller) {
275                                 
276                                 delete existingBinding;
277                                 controllables.erase (iter);
278                         }
279                         
280                 }
281                 
282                 // Update the MIDI Controllable based on the the pos param
283                 // Here is where a table lookup for user mappings could go; for now we'll just wing it...
284                 mc->bind_midi(channel, MIDI::controller, value);
285                 
286                 controllables.insert (mc);
287         }
288 }
289
290 void
291 GenericMidiControlProtocol::auto_binding_on()
292 {
293         auto_binding = TRUE;
294 }
295
296 void
297 GenericMidiControlProtocol::auto_binding_off()
298 {
299         auto_binding = FALSE;
300 }
301
302 XMLNode&
303 GenericMidiControlProtocol::get_state () 
304 {
305         XMLNode* node = new XMLNode ("Protocol"); 
306         char buf[32];
307
308         node->add_property (X_("name"), _name);
309         node->add_property (X_("feedback"), do_feedback ? "1" : "0");
310         snprintf (buf, sizeof (buf), "%" PRIu64, _feedback_interval);
311         node->add_property (X_("feedback_interval"), buf);
312
313         XMLNode* children = new XMLNode (X_("controls"));
314
315         node->add_child_nocopy (*children);
316
317         Glib::Mutex::Lock lm2 (controllables_lock);
318         for (MIDIControllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
319                 children->add_child_nocopy ((*i)->get_state());
320         }
321
322         return *node;
323 }
324
325 int
326 GenericMidiControlProtocol::set_state (const XMLNode& node, int version)
327 {
328         XMLNodeList nlist;
329         XMLNodeConstIterator niter;
330         const XMLProperty* prop;
331
332         if ((prop = node.property ("feedback")) != 0) {
333                 do_feedback = (bool) atoi (prop->value().c_str());
334         } else {
335                 do_feedback = false;
336         }
337
338         if ((prop = node.property ("feedback_interval")) != 0) {
339                 if (sscanf (prop->value().c_str(), "%" PRIu64, &_feedback_interval) != 1) {
340                         _feedback_interval = 10000;
341                 }
342         } else {
343                 _feedback_interval = 10000;
344         }
345
346         if ( !auto_binding ) {
347                 
348                 boost::shared_ptr<Controllable> c;
349                 
350                 {
351                         Glib::Mutex::Lock lm (pending_lock);
352                         pending_controllables.clear ();
353                 }
354                 
355                 Glib::Mutex::Lock lm2 (controllables_lock);
356                 controllables.clear ();
357                 nlist = node.children(); // "controls"
358                 
359                 if (nlist.empty()) {
360                         return 0;
361                 }
362                 
363                 nlist = nlist.front()->children ();
364                 
365                 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
366                         
367                         if ((prop = (*niter)->property ("id")) != 0) {
368
369                                 ID id = prop->value ();
370                                 c = session->controllable_by_id (id);
371                                 
372                                 if (c) {
373                                         MIDIControllable* mc = new MIDIControllable (*_port, *c);
374                                         if (mc->set_state (**niter) == 0) {
375                                                 controllables.insert (mc);
376                                         }
377                                         
378                                 } else {
379                                         warning << string_compose (
380                                                         _("Generic MIDI control: controllable %1 not found in session (ignored)"),
381                                                         id) << endmsg;
382                                 }
383                         }
384                 }
385         }
386         return 0;
387 }
388
389 int
390 GenericMidiControlProtocol::set_feedback (bool yn)
391 {
392         do_feedback = yn;
393         last_feedback_time = 0;
394         return 0;
395 }
396
397 bool
398 GenericMidiControlProtocol::get_feedback () const
399 {
400         return do_feedback;
401 }
402