fadeport: restructure button info actions to allow modifiers (shift, rewind, stop...
[ardour.git] / libs / surfaces / faderport / faderport.cc
1 /*
2     Copyright (C) 2015 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 #include <cstdlib>
21 #include <sstream>
22 #include <algorithm>
23
24 #include <stdint.h>
25
26 #include <glibmm/fileutils.h>
27 #include <glibmm/miscutils.h>
28
29 #include "pbd/controllable_descriptor.h"
30 #include "pbd/error.h"
31 #include "pbd/failed_constructor.h"
32 #include "pbd/file_utils.h"
33 #include "pbd/pthread_utils.h"
34 #include "pbd/compose.h"
35 #include "pbd/xml++.h"
36
37 #include "midi++/port.h"
38
39 #include "ardour/audioengine.h"
40 #include "ardour/filesystem_paths.h"
41 #include "ardour/session.h"
42 #include "ardour/route.h"
43 #include "ardour/midi_port.h"
44 #include "ardour/rc_configuration.h"
45 #include "ardour/midiport_manager.h"
46 #include "ardour/debug.h"
47 #include "ardour/async_midi_port.h"
48
49 #include "faderport.h"
50
51 using namespace ARDOUR;
52 using namespace ArdourSurface;
53 using namespace PBD;
54 using namespace Glib;
55 using namespace std;
56
57 #include "i18n.h"
58
59 #include "pbd/abstract_ui.cc" // instantiate template
60
61 FaderPort::FaderPort (Session& s)
62         : ControlProtocol (s, _("Faderport"))
63         , AbstractUI<FaderPortRequest> ("faderport")
64         , _motorised (true)
65         , _threshold (10)
66         , gui (0)
67         , connection_state (ConnectionState (0))
68         , _device_active (false)
69         , fader_msb (0)
70         , fader_lsb (0)
71         , button_state (ButtonState (0))
72 {
73         boost::shared_ptr<ARDOUR::Port> inp;
74         boost::shared_ptr<ARDOUR::Port> outp;
75
76         inp  = AudioEngine::instance()->register_input_port (DataType::MIDI, "Faderport Recv", true);
77         outp = AudioEngine::instance()->register_output_port (DataType::MIDI, "Faderport Send", true);
78
79         _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(inp);
80         _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(outp);
81
82         if (_input_port == 0 || _output_port == 0) {
83                 throw failed_constructor();
84         }
85
86         do_feedback = false;
87         _feedback_interval = 10 * 1000; // microseconds
88         last_feedback_time = 0;
89         native_counter = 0;
90
91         _current_bank = 0;
92         _bank_size = 0;
93
94         Session::SendFeedback.connect_same_thread (*this, boost::bind (&FaderPort::send_feedback, this));
95         //Session::SendFeedback.connect (*this, MISSING_INVALIDATOR, boost::bind (&FaderPort::send_feedback, this), this);;
96
97         /* Catch port connections and disconnections */
98         ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&FaderPort::connection_handler, this, _1, _2, _3, _4, _5), this);
99
100         buttons.insert (std::make_pair (Mute, ButtonInfo (*this, _("Mute"), Mute, 21)));
101         buttons.insert (std::make_pair (Solo, ButtonInfo (*this, _("Solo"), Solo, 22)));
102         buttons.insert (std::make_pair (Rec, ButtonInfo (*this, _("Rec"), Rec, 23)));
103         buttons.insert (std::make_pair (Left, ButtonInfo (*this, _("Left"), Left, 20)));
104         buttons.insert (std::make_pair (Bank, ButtonInfo (*this, _("Bank"), Bank, 19)));
105         buttons.insert (std::make_pair (Right, ButtonInfo (*this, _("Right"), Right, 18)));
106         buttons.insert (std::make_pair (Output, ButtonInfo (*this, _("Output"), Output, 17)));
107         buttons.insert (std::make_pair (Read, ButtonInfo (*this, _("Read"), Read, 13)));
108         buttons.insert (std::make_pair (Write, ButtonInfo (*this, _("Write"), Write, 14)));
109         buttons.insert (std::make_pair (Touch, ButtonInfo (*this, _("Touch"), Touch, 15)));
110         buttons.insert (std::make_pair (Off, ButtonInfo (*this, _("Off"), Off, 16)));
111         buttons.insert (std::make_pair (Mix, ButtonInfo (*this, _("Mix"), Mix, 12)));
112         buttons.insert (std::make_pair (Proj, ButtonInfo (*this, _("Proj"), Proj, 11)));
113         buttons.insert (std::make_pair (Trns, ButtonInfo (*this, _("Trns"), Trns, 10)));
114         buttons.insert (std::make_pair (Undo, ButtonInfo (*this, _("Undo"), Undo, 9)));
115         buttons.insert (std::make_pair (Shift, ButtonInfo (*this, _("Shift"), Shift, 5)));
116         buttons.insert (std::make_pair (Punch, ButtonInfo (*this, _("Punch"), Punch, 6)));
117         buttons.insert (std::make_pair (User, ButtonInfo (*this, _("User"), User, 7)));
118         buttons.insert (std::make_pair (Loop, ButtonInfo (*this, _("Loop"), Loop, 8)));
119         buttons.insert (std::make_pair (Rewind, ButtonInfo (*this, _("Rewind"), Rewind, 4)));
120         buttons.insert (std::make_pair (Ffwd, ButtonInfo (*this, _("Ffwd"), Ffwd, 3)));
121         buttons.insert (std::make_pair (Stop, ButtonInfo (*this, _("Stop"), Stop, 2)));
122         buttons.insert (std::make_pair (Play, ButtonInfo (*this, _("Play"), Play, 1)));
123         buttons.insert (std::make_pair (RecEnable, ButtonInfo (*this, _("RecEnable"), RecEnable, 0)));
124         buttons.insert (std::make_pair (FaderTouch, ButtonInfo (*this, _("Fader (touch)"), FaderTouch, -1)));
125
126         button_info (Undo).set_action (boost::bind (&FaderPort::undo, this), true);
127         button_info (Undo).set_action (boost::bind (&FaderPort::redo, this), true, ButtonState (ShiftDown));
128         button_info (Undo).set_flash (true);
129
130         button_info (Play).set_action (boost::bind (&BasicUI::transport_play, this, false), true);
131         button_info (RecEnable).set_action (boost::bind (&BasicUI::rec_enable_toggle, this), true);
132         button_info (Stop).set_action (boost::bind (&BasicUI::transport_stop, this), true);
133         button_info (Ffwd).set_action (boost::bind (&BasicUI::ffwd, this), true);
134         button_info (Rewind).set_action (boost::bind (&BasicUI::rewind, this), true);
135 }
136
137 FaderPort::~FaderPort ()
138 {
139         if (_input_port) {
140                 DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("unregistering input port %1\n", boost::shared_ptr<ARDOUR::Port>(_input_port)->name()));
141                 AudioEngine::instance()->unregister_port (_input_port);
142                 _input_port.reset ();
143         }
144
145         if (_output_port) {
146 //              _output_port->drain (10000);  //ToDo:  is this necessary?  It hangs the shutdown, for me
147                 DEBUG_TRACE (DEBUG::GenericMidi, string_compose ("unregistering output port %1\n", boost::shared_ptr<ARDOUR::Port>(_output_port)->name()));
148                 AudioEngine::instance()->unregister_port (_output_port);
149                 _output_port.reset ();
150         }
151
152         tear_down_gui ();
153 }
154
155 void
156 FaderPort::start_midi_handling ()
157 {
158         /* handle device inquiry response */
159         _input_port->parser()->sysex.connect_same_thread (midi_connections, boost::bind (&FaderPort::sysex_handler, this, _1, _2, _3));
160         /* handle switches */
161         _input_port->parser()->poly_pressure.connect_same_thread (midi_connections, boost::bind (&FaderPort::switch_handler, this, _1, _2));
162         /* handle encoder */
163         _input_port->parser()->pitchbend.connect_same_thread (midi_connections, boost::bind (&FaderPort::encoder_handler, this, _1, _2));
164         /* handle fader */
165         _input_port->parser()->controller.connect_same_thread (midi_connections, boost::bind (&FaderPort::fader_handler, this, _1, _2));
166
167         /* This connection means that whenever data is ready from the input
168          * port, the relevant thread will invoke our ::midi_input_handler()
169          * method, which will read the data, and invoke the parser.
170          */
171
172         _input_port->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &FaderPort::midi_input_handler), _input_port));
173         _input_port->xthread().attach (main_loop()->get_context());
174 }
175
176 void
177 FaderPort::stop_midi_handling ()
178 {
179         midi_connections.drop_connections ();
180
181         /* Note: the input handler is still active at this point, but we're no
182          * longer connected to any of the parser signals
183          */
184 }
185
186 void
187 FaderPort::do_request (FaderPortRequest* req)
188 {
189         if (req->type == CallSlot) {
190
191                 call_slot (MISSING_INVALIDATOR, req->the_slot);
192
193         } else if (req->type == Quit) {
194
195                 stop ();
196         }
197 }
198
199 int
200 FaderPort::stop ()
201 {
202         BaseUI::quit ();
203
204         return 0;
205 }
206
207 void
208 FaderPort::thread_init ()
209 {
210         struct sched_param rtparam;
211
212         pthread_set_name (X_("FaderPort"));
213
214         PBD::notify_gui_about_thread_creation (X_("gui"), pthread_self(), X_("FaderPort"), 2048);
215         ARDOUR::SessionEvent::create_per_thread_pool (X_("FaderPort"), 128);
216
217         memset (&rtparam, 0, sizeof (rtparam));
218         rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
219
220         if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) {
221                 // do we care? not particularly.
222         }
223 }
224
225 void
226 FaderPort::all_lights_out ()
227 {
228         for (ButtonMap::iterator b = buttons.begin(); b != buttons.end(); ++b) {
229                 b->second.set_led_state (_output_port, false);
230                 g_usleep (1000);
231         }
232 }
233
234 void
235 FaderPort::party ()
236 {
237         for (int n = 0; n < 5; ++n) {
238                 for (ButtonMap::iterator b = buttons.begin(); b != buttons.end(); ++b) {
239                         b->second.set_led_state (_output_port, random() % 3);
240                         g_usleep (1000);
241                 }
242                 g_usleep (250000);
243         }
244
245         all_lights_out ();
246 }
247
248
249 FaderPort::ButtonInfo&
250 FaderPort::button_info (ButtonID id) const
251 {
252         ButtonMap::const_iterator b = buttons.find (id);
253         assert (b != buttons.end());
254         return const_cast<ButtonInfo&>(b->second);
255 }
256
257 void
258 FaderPort::switch_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
259 {
260         ButtonID id (ButtonID (tb->controller_number));
261
262         switch (id) {
263         case Shift:
264                 button_state = (tb->value ? ButtonState (button_state|ShiftDown) : ButtonState (button_state&~ShiftDown));
265                 break;
266         case Stop:
267                 button_state = (tb->value ? ButtonState (button_state|StopDown) : ButtonState (button_state&~StopDown));
268                 break;
269         case Rewind:
270                 button_state = (tb->value ? ButtonState (button_state|RewindDown) : ButtonState (button_state&~RewindDown));
271                 break;
272         default:
273                 break;
274         }
275
276         ButtonInfo& bi (button_info (id));
277
278         if (bi.uses_flash()) {
279                 bi.set_led_state (_output_port, (int)tb->value);
280         }
281
282         bi.invoke (button_state, tb->value ? true : false);
283 }
284
285 void
286 FaderPort::encoder_handler (MIDI::Parser &, MIDI::pitchbend_t pb)
287 {
288         if (pb < 8192) {
289                 cerr << "Encoder right\n";
290         } else {
291                 cerr << "Encoder left\n";
292         }
293 }
294
295 void
296 FaderPort::fader_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
297 {
298         bool was_fader = false;
299
300         if (tb->controller_number == 0x0) {
301                 fader_msb = tb->value;
302                 was_fader = true;
303         } else if (tb->controller_number == 0x20) {
304                 fader_lsb = tb->value;
305                 was_fader = true;
306         }
307
308         if (was_fader) {
309                 cerr << "Fader now at " << ((fader_msb<<7)|fader_lsb) << endl;
310         }
311 }
312
313 void
314 FaderPort::sysex_handler (MIDI::Parser &p, MIDI::byte *buf, size_t sz)
315 {
316         if (sz < 17) {
317                 return;
318         }
319
320         if (buf[2] == 0x7f &&
321             buf[3] == 0x06 &&
322             buf[4] == 0x02 &&
323             buf[5] == 0x0 &&
324             buf[6] == 0x1 &&
325             buf[7] == 0x06 &&
326             buf[8] == 0x02 &&
327             buf[9] == 0x0 &&
328             buf[10] == 0x01 &&
329             buf[11] == 0x0) {
330                 _device_active = true;
331
332                 cerr << "FaderPort identified\n";
333
334                 /* put it into native mode */
335
336                 MIDI::byte native[3];
337                 native[0] = 0x91;
338                 native[1] = 0x00;
339                 native[2] = 0x64;
340
341                 _output_port->write (native, 3, 0);
342
343                 g_usleep (10000);
344                 party ();
345         }
346 }
347
348 int
349 FaderPort::set_active (bool yn)
350 {
351         // DEBUG_TRACE (DEBUG::MackieControl, string_compose("MackieControlProtocol::set_active init with yn: '%1'\n", yn));
352
353         if (yn == active()) {
354                 return 0;
355         }
356
357         if (yn) {
358
359                 /* start event loop */
360
361                 BaseUI::run ();
362
363                 connect_session_signals ();
364
365         } else {
366
367                 BaseUI::quit ();
368                 close ();
369
370         }
371
372         ControlProtocol::set_active (yn);
373
374         // DEBUG_TRACE (DEBUG::MackieControl, string_compose("MackieControlProtocol::set_active done with yn: '%1'\n", yn));
375
376         return 0;
377 }
378
379 void
380 FaderPort::close ()
381 {
382         stop_midi_handling ();
383 #if 0
384         port_connection.disconnect ();
385         session_connections.drop_connections ();
386         route_connections.drop_connections ();
387         periodic_connection.disconnect ();
388
389         clear_surfaces();
390 #endif
391 }
392
393 void
394 FaderPort::connect_session_signals()
395 {
396 #if 0
397         // receive routes added
398         session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::notify_route_added, this, _1), this);
399         // receive record state toggled
400         session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::notify_record_state_changed, this), this);
401         // receive transport state changed
402         session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::notify_transport_state_changed, this), this);
403         session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::notify_loop_state_changed, this), this);
404         // receive punch-in and punch-out
405         Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::notify_parameter_changed, this, _1), this);
406         session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::notify_parameter_changed, this, _1), this);
407         // receive rude solo changed
408         session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::notify_solo_active_changed, this, _1), this);
409 #endif
410 }
411
412 void
413 FaderPort::set_feedback_interval (microseconds_t ms)
414 {
415         _feedback_interval = ms;
416 }
417
418 void
419 FaderPort::send_feedback ()
420 {
421         /* This is executed in RT "process" context", so no blocking calls
422          */
423
424         if (!do_feedback) {
425                 return;
426         }
427
428         microseconds_t now = get_microseconds ();
429
430         if (last_feedback_time != 0) {
431                 if ((now - last_feedback_time) < _feedback_interval) {
432                         return;
433                 }
434         }
435
436         last_feedback_time = now;
437 }
438
439 bool
440 FaderPort::midi_input_handler (Glib::IOCondition ioc, boost::shared_ptr<ARDOUR::AsyncMIDIPort> port)
441 {
442         DEBUG_TRACE (DEBUG::MidiIO, string_compose ("something happend on  %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
443
444         if (ioc & ~IO_IN) {
445                 return false;
446         }
447
448         if (ioc & IO_IN) {
449
450                 if (port) {
451                         port->clear ();
452                 }
453
454                 DEBUG_TRACE (DEBUG::MidiIO, string_compose ("data available on %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
455                 framepos_t now = session->engine().sample_time();
456                 port->parse (now);
457         }
458
459         return true;
460 }
461
462
463 XMLNode&
464 FaderPort::get_state ()
465 {
466         XMLNode& node (ControlProtocol::get_state());
467
468         XMLNode* child;
469
470         child = new XMLNode (X_("Input"));
471         child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_input_port)->get_state());
472         node.add_child_nocopy (*child);
473
474
475         child = new XMLNode (X_("Output"));
476         child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_output_port)->get_state());
477         node.add_child_nocopy (*child);
478
479         return node;
480 }
481
482 int
483 FaderPort::set_state (const XMLNode& node, int version)
484 {
485         XMLNodeList nlist;
486         XMLNodeConstIterator niter;
487         XMLNode const* child;
488
489         if (ControlProtocol::set_state (node, version)) {
490                 return -1;
491         }
492
493         if ((child = node.child (X_("Input"))) != 0) {
494                 XMLNode* portnode = child->child (Port::state_node_name.c_str());
495                 if (portnode) {
496                         boost::shared_ptr<ARDOUR::Port>(_input_port)->set_state (*portnode, version);
497                 }
498         }
499
500         if ((child = node.child (X_("Output"))) != 0) {
501                 XMLNode* portnode = child->child (Port::state_node_name.c_str());
502                 if (portnode) {
503                         boost::shared_ptr<ARDOUR::Port>(_output_port)->set_state (*portnode, version);
504                 }
505         }
506
507         return 0;
508 }
509
510 int
511 FaderPort::set_feedback (bool yn)
512 {
513         do_feedback = yn;
514         last_feedback_time = 0;
515         return 0;
516 }
517
518 bool
519 FaderPort::get_feedback () const
520 {
521         return do_feedback;
522 }
523
524 void
525 FaderPort::set_current_bank (uint32_t b)
526 {
527         _current_bank = b;
528 //      reset_controllables ();
529 }
530
531 void
532 FaderPort::next_bank ()
533 {
534         _current_bank++;
535 //      reset_controllables ();
536 }
537
538 void
539 FaderPort::prev_bank()
540 {
541         if (_current_bank) {
542                 _current_bank--;
543 //              reset_controllables ();
544         }
545 }
546
547 void
548 FaderPort::set_motorised (bool m)
549 {
550         _motorised = m;
551 }
552
553 void
554 FaderPort::set_threshold (int t)
555 {
556         _threshold = t;
557 }
558
559 void
560 FaderPort::reset_controllables ()
561 {
562 }
563
564 bool
565 FaderPort::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
566 {
567         if (!_input_port || !_output_port) {
568                 return false;
569         }
570
571         string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_input_port)->name());
572         string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_output_port)->name());
573
574         if (ni == name1 || ni == name2) {
575                 if (yn) {
576                         connection_state |= InputConnected;
577                 } else {
578                         connection_state &= ~InputConnected;
579                 }
580         } else if (no == name1 || no == name2) {
581                 if (yn) {
582                         connection_state |= OutputConnected;
583                 } else {
584                         connection_state &= ~OutputConnected;
585                 }
586         } else {
587                 /* not our ports */
588                 return false;
589         }
590
591         if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
592
593                 /* XXX this is a horrible hack. Without a short sleep here,
594                    something prevents the device wakeup messages from being
595                    sent and/or the responses from being received.
596                 */
597
598                 g_usleep (100000);
599                 connected ();
600
601         } else {
602                 // DEBUG_TRACE (DEBUG::FaderPort, string_compose ("Surface %1 disconnected (input or output or both)\n", _name));
603                 _device_active = false;
604         }
605
606         return true; /* connection status changed */
607 }
608
609 void
610 FaderPort::connected ()
611 {
612         std::cerr << "faderport connected\n";
613
614         start_midi_handling ();
615
616         /* send device inquiry */
617
618         MIDI::byte buf[6];
619
620         buf[0] = 0xf0;
621         buf[1] = 0x7e;
622         buf[2] = 0x7f;
623         buf[3] = 0x06;
624         buf[4] = 0x01;
625         buf[5] = 0xf7;
626
627         _output_port->write (buf, 6, 0);
628 }
629
630 void
631 FaderPort::ButtonInfo::invoke (FaderPort::ButtonState bs, bool press)
632 {
633         switch (type) {
634         case NamedAction:
635                 if (press) {
636                         ToDoMap::iterator x = on_press.find (bs);
637                         if (x != on_press.end()) {
638                                 if (!x->second.action_name.empty()) {
639                                         fp.access_action (x->second.action_name);
640                                 }
641                         }
642                 } else {
643                         ToDoMap::iterator x = on_release.find (bs);
644                         if (x != on_release.end()) {
645                                 if (!x->second.action_name.empty()) {
646                                         fp.access_action (x->second.action_name);
647                                 }
648                         }
649                 }
650                 break;
651         case InternalFunction:
652                 if (press) {
653                         ToDoMap::iterator x = on_press.find (bs);
654                         if (x != on_press.end()) {
655                                 if (x->second.function) {
656                                         x->second.function ();
657                                 }
658                         }
659                 } else {
660                         ToDoMap::iterator x = on_release.find (bs);
661                         if (x != on_release.end()) {
662                                 if (x->second.function) {
663                                         x->second.function ();
664                                 }
665                         }
666                 }
667                 break;
668         }
669 }
670
671 void
672 FaderPort::ButtonInfo::set_action (string const& name, bool when_pressed, FaderPort::ButtonState bs)
673 {
674         ToDo todo;
675
676         type = NamedAction;
677
678         if (when_pressed) {
679                 todo.action_name = name;
680                 on_press[bs] = todo;
681         } else {
682                 todo.action_name = name;
683                 on_release[bs] = todo;
684         }
685
686 }
687
688 void
689 FaderPort::ButtonInfo::set_action (boost::function<void()> f, bool when_pressed, FaderPort::ButtonState bs)
690 {
691         ToDo todo;
692         type = InternalFunction;
693
694         if (when_pressed) {
695                 todo.function = f;
696                 on_press[bs] = todo;
697         } else {
698                 todo.function = f;
699                 on_release[bs] = todo;
700         }
701 }
702
703 void
704 FaderPort::ButtonInfo::set_led_state (boost::shared_ptr<MIDI::Port> port, int onoff)
705 {
706         if (led_on == (bool) onoff) {
707                 /* nothing to do */
708                 return;
709         }
710
711         if (out < 0) {
712                 /* fader button ID - no LED */
713                 return;
714         }
715
716         MIDI::byte buf[3];
717         buf[0] = 0xa0;
718         buf[1] = out;
719         buf[2] = onoff ? 1 : 0;
720         port->write (buf, 3, 0);
721         led_on = (onoff ? true : false);
722 }