LCXL: Make knobs' leds' color constant when controlling master bus
[ardour.git] / libs / surfaces / launch_control_xl / launch_control_xl.cc
1 /*
2   Copyright (C) 2016 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 #include <stdlib.h>
20 #include <pthread.h>
21
22 #include "pbd/compose.h"
23 #include "pbd/convert.h"
24 #include "pbd/debug.h"
25 #include "pbd/failed_constructor.h"
26 #include "pbd/file_utils.h"
27 #include "pbd/search_path.h"
28 #include "pbd/enumwriter.h"
29
30 #include "midi++/parser.h"
31
32 #include "ardour/amp.h"
33 #include "ardour/async_midi_port.h"
34 #include "ardour/audioengine.h"
35 #include "ardour/debug.h"
36 #include "ardour/midiport_manager.h"
37 #include "ardour/midi_track.h"
38 #include "ardour/midi_port.h"
39 #include "ardour/session.h"
40 #include "ardour/tempo.h"
41 #include "ardour/types_convert.h"
42 #include "ardour/vca_manager.h"
43
44
45 #include "gtkmm2ext/gui_thread.h"
46
47 #include "gui.h"
48 #include "launch_control_xl.h"
49
50 #include "pbd/i18n.h"
51
52 #ifdef PLATFORM_WINDOWS
53 #define random() rand()
54 #endif
55
56 using namespace ARDOUR;
57 using namespace std;
58 using namespace PBD;
59 using namespace Glib;
60 using namespace ArdourSurface;
61 #include "pbd/abstract_ui.cc" // instantiate template
62
63 /* init global object */
64 LaunchControlXL* lcxl = 0;
65
66 LaunchControlXL::LaunchControlXL (ARDOUR::Session& s)
67         : ControlProtocol (s, string (X_("Novation Launch Control XL")))
68         , AbstractUI<LaunchControlRequest> (name())
69         , in_use (false)
70         , _track_mode(TrackMute)
71         , _template_number(8) // default template (factory 1)
72         , bank_start (0)
73         , connection_state (ConnectionState (0))
74         , gui (0)
75         , in_range_select (false)
76 {
77         lcxl = this;
78         /* we're going to need this */
79
80         build_maps ();
81
82         /* master cannot be removed, so no need to connect to going-away signal */
83         master = session->master_out ();
84
85         run_event_loop ();
86
87         /* Ports exist for the life of this instance */
88
89         ports_acquire ();
90
91         /* catch arrival and departure of LaunchControlXL itself */
92         ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::port_registration_handler, this), this);
93
94         /* Catch port connections and disconnections */
95         ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::connection_handler, this, _1, _2, _3, _4, _5), this);
96
97         /* Launch Control XL ports might already be there */
98         port_registration_handler ();
99
100         session->RouteAdded.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::stripables_added, this), lcxl);
101         session->vca_manager().VCAAdded.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::stripables_added, this), lcxl);
102
103         switch_bank (bank_start);
104 }
105
106 LaunchControlXL::~LaunchControlXL ()
107 {
108         DEBUG_TRACE (DEBUG::LaunchControlXL, "Launch Control XL  control surface object being destroyed\n");
109
110         /* do this before stopping the event loop, so that we don't get any notifications */
111         port_reg_connection.disconnect ();
112         port_connection.disconnect ();
113         session_connections.drop_connections ();
114         stripable_connections.drop_connections ();
115
116         stop_using_device ();
117         ports_release ();
118
119         stop_event_loop ();
120         tear_down_gui ();
121 }
122
123
124 void
125 LaunchControlXL::run_event_loop ()
126 {
127         DEBUG_TRACE (DEBUG::LaunchControlXL, "start event loop\n");
128         BaseUI::run ();
129 }
130
131 void
132 LaunchControlXL::stop_event_loop ()
133 {
134         DEBUG_TRACE (DEBUG::LaunchControlXL, "stop event loop\n");
135         BaseUI::quit ();
136 }
137
138 int
139 LaunchControlXL::begin_using_device ()
140 {
141         DEBUG_TRACE (DEBUG::LaunchControlXL, "begin using device\n");
142
143         switch_template(template_number()); // first factory template
144
145         connect_session_signals ();
146
147         init_buttons (true);
148
149         in_use = true;
150
151         DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("use_fader8master inital value  '%1'\n", use_fader8master));
152         set_fader8master(use_fader8master);
153
154         return 0;
155 }
156
157 int
158 LaunchControlXL::stop_using_device ()
159 {
160         DEBUG_TRACE (DEBUG::LaunchControlXL, "stop using device\n");
161
162         if (!in_use) {
163                 DEBUG_TRACE (DEBUG::LaunchControlXL, "nothing to do, device not in use\n");
164                 return 0;
165         }
166
167         init_buttons (false);
168
169         session_connections.drop_connections ();
170
171         in_use = false;
172         return 0;
173 }
174
175 int
176 LaunchControlXL::ports_acquire ()
177 {
178         DEBUG_TRACE (DEBUG::LaunchControlXL, "acquiring ports\n");
179
180         /* setup ports */
181
182         _async_in  = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Launch Control XL in"), true);
183         _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Launch Control XL out"), true);
184
185         if (_async_in == 0 || _async_out == 0) {
186                 DEBUG_TRACE (DEBUG::LaunchControlXL, "cannot register ports\n");
187                 return -1;
188         }
189
190         /* We do not add our ports to the input/output bundles because we don't
191          * want users wiring them by hand. They could use JACK tools if they
192          * really insist on that (and use JACK)
193          */
194
195         _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
196         _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get();
197
198         session->BundleAddedOrRemoved ();
199
200         connect_to_parser ();
201
202         /* Connect input port to event loop */
203
204         AsyncMIDIPort* asp;
205
206         asp = static_cast<AsyncMIDIPort*> (_input_port);
207         asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &LaunchControlXL::midi_input_handler), _input_port));
208         asp->xthread().attach (main_loop()->get_context());
209
210         return 0;
211 }
212
213 void
214 LaunchControlXL::ports_release ()
215 {
216         DEBUG_TRACE (DEBUG::LaunchControlXL, "releasing ports\n");
217
218         /* wait for button data to be flushed */
219         AsyncMIDIPort* asp;
220         asp = static_cast<AsyncMIDIPort*> (_output_port);
221         asp->drain (10000, 500000);
222
223         {
224                 Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
225                 AudioEngine::instance()->unregister_port (_async_in);
226                 AudioEngine::instance()->unregister_port (_async_out);
227         }
228
229         _async_in.reset ((ARDOUR::Port*) 0);
230         _async_out.reset ((ARDOUR::Port*) 0);
231         _input_port = 0;
232         _output_port = 0;
233 }
234
235 list<boost::shared_ptr<ARDOUR::Bundle> >
236 LaunchControlXL::bundles ()
237 {
238         list<boost::shared_ptr<ARDOUR::Bundle> > b;
239
240         if (_output_bundle) {
241                 b.push_back (_output_bundle);
242         }
243
244         return b;
245 }
246
247
248 void
249 LaunchControlXL::init_buttons (bool startup)
250 {
251         reset(template_number());
252
253         if (startup) {
254                 switch_bank(bank_start);
255         }
256 }
257
258 bool
259 LaunchControlXL::probe ()
260 {
261         return true;
262 }
263
264 void*
265 LaunchControlXL::request_factory (uint32_t num_requests)
266 {
267         /* AbstractUI<T>::request_buffer_factory() is a template method only
268            instantiated in this source module. To provide something visible for
269            use in the interface/descriptor, we have this static method that is
270            template-free.
271         */
272         return request_buffer_factory (num_requests);
273 }
274
275 void
276 LaunchControlXL::do_request (LaunchControlRequest * req)
277 {
278         if (req->type == CallSlot) {
279
280                 call_slot (MISSING_INVALIDATOR, req->the_slot);
281
282         } else if (req->type == Quit) {
283
284                 stop_using_device ();
285         }
286 }
287
288 void
289 LaunchControlXL::reset(uint8_t chan)
290 {
291         MidiByteArray msg (3, 176 + chan, 0, 0); // turn off all leds, reset buffer settings and duty cycle
292
293         write(msg);
294 }
295 int
296 LaunchControlXL::set_active (bool yn)
297 {
298         DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active init with yn: '%1'\n", yn));
299
300         if (yn == active()) {
301                 return 0;
302         }
303
304         if (yn) {
305                 if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
306                         begin_using_device ();
307                 } else {
308                         /* begin_using_device () will get called once we're connected */
309                 }
310
311         } else {
312                 /* Control Protocol Manager never calls us with false, but
313                  * insteads destroys us.
314                  */
315         }
316
317         ControlProtocol::set_active (yn);
318
319         DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active done with yn: '%1'\n", yn));
320
321         return 0;
322 }
323
324 void
325 LaunchControlXL::write (const MidiByteArray& data)
326 {
327         /* immediate delivery */
328         _output_port->write (&data[0], data.size(), 0);
329 }
330
331 /* Device to Ardour message handling */
332
333 bool
334 LaunchControlXL::midi_input_handler (IOCondition ioc, MIDI::Port* port)
335 {
336         if (ioc & ~IO_IN) {
337                 DEBUG_TRACE (DEBUG::LaunchControlXL, "MIDI port closed\n");
338                 return false;
339         }
340
341         if (ioc & IO_IN) {
342
343                 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("something happened on  %1\n", port->name()));
344
345                 AsyncMIDIPort* asp = static_cast<AsyncMIDIPort*>(port);
346                 if (asp) {
347                         asp->clear ();
348                 }
349
350                 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("data available on %1\n", port->name()));
351                 if (in_use) {
352                         samplepos_t now = AudioEngine::instance()->sample_time();
353                         port->parse (now);
354                 }
355         }
356
357         return true;
358 }
359
360
361 void
362 LaunchControlXL::connect_to_parser ()
363 {
364         DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Connecting to signals on port %1\n", _input_port->name()));
365
366         MIDI::Parser* p = _input_port->parser();
367
368         /* Incoming sysex */
369         p->sysex.connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_sysex, this, _1, _2, _3));
370
371  for (MIDI::channel_t n = 0; n < 16; ++n) {
372         /* Controller */
373                 p->channel_controller[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_controller_message, this, _1, _2, n));
374                 /* Button messages are NoteOn */
375                 p->channel_note_on[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_note_on_message, this, _1, _2, n));
376                 /* Button messages are NoteOn but libmidi++ sends note-on w/velocity = 0 as note-off so catch them too */
377                 p->channel_note_off[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_note_off_message, this, _1, _2, n));
378         }
379 }
380
381 void
382 LaunchControlXL::handle_midi_sysex (MIDI::Parser&, MIDI::byte* raw_bytes, size_t sz)
383 {
384         DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Sysex, %1 bytes\n", sz));
385
386         if (sz < 8) {
387                 return;
388         }
389
390         MidiByteArray msg (sz, raw_bytes);
391         MidiByteArray lcxl_sysex_header (6, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11);
392
393         if (!lcxl_sysex_header.compare_n (msg, 6)) {
394                 return;
395         }
396
397
398         switch (msg[6]) {
399         case 0x77: /* template change */
400                 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Template change: %1 n", msg[7]));
401                 _template_number = msg[7];
402                 break;
403         }
404 }
405
406
407 void
408 LaunchControlXL::handle_button_message(Button* button, MIDI::EventTwoBytes* ev)
409 {
410   if (ev->value) {
411     /* any press cancels any pending long press timeouts */
412     for (set<ButtonID>::iterator x = buttons_down.begin(); x != buttons_down.end(); ++x) {
413       ControllerButton* cb = id_controller_button_map[*x];
414                         NoteButton*     nb = id_note_button_map[*x];
415                         if (cb != 0) {
416                                 cb->timeout_connection.disconnect();
417                         } else if (nb != 0) {
418                                         nb->timeout_connection.disconnect();
419                         }
420     }
421
422     buttons_down.insert(button->id());
423     DEBUG_TRACE(DEBUG::LaunchControlXL, string_compose("button pressed: %1\n", LaunchControlXL::button_name_by_id(button->id())));
424     start_press_timeout(button, button->id());
425   } else {
426     DEBUG_TRACE(DEBUG::LaunchControlXL, string_compose("button depressed: %1\n", LaunchControlXL::button_name_by_id(button->id())));
427     buttons_down.erase(button->id());
428     button->timeout_connection.disconnect();
429   }
430
431         set<ButtonID>::iterator c = consumed.find(button->id());
432
433   if (c == consumed.end()) {
434     if (ev->value == 0) {
435       (this->*button->release_method)();
436     } else {
437       (this->*button->press_method)();
438     }
439   } else {
440     DEBUG_TRACE(DEBUG::LaunchControlXL, "button was consumed, ignored\n");
441     consumed.erase(c);
442   }
443 }
444
445 bool
446 LaunchControlXL::check_pick_up(Controller* controller, boost::shared_ptr<AutomationControl> ac)
447 {
448         /* returns false until the controller value matches with the current setting of the stripable's ac */
449         return ( abs( controller->value() / 127.0 -  ac->internal_to_interface(ac->get_value()) ) < 0.007875 );
450 }
451
452 void
453 LaunchControlXL::handle_knob_message (Knob* knob)
454 {
455         uint8_t chan = knob->id() % 8; // get the strip channel number
456         if (!stripable[chan]) {
457                 return;
458         }
459
460         boost::shared_ptr<AutomationControl> ac;
461
462         if (knob->id() < 8) { // sendA knob
463                 if (buttons_down.find(Device) != buttons_down.end()) { // Device button hold
464                         ac = stripable[chan]->trim_control();
465                 } else {
466                         ac = stripable[chan]->send_level_controllable (0);
467                 }
468         } else if (knob->id() >= 8 && knob->id() < 16) { // sendB knob
469                 if (buttons_down.find(Device) != buttons_down.end()) { // Device button hold
470 #ifdef MIXBUS
471                         ac = stripable[chan]->filter_freq_controllable (true);
472 #else
473                         /* something */
474 #endif
475                 } else {
476                         ac = stripable[chan]->send_level_controllable (1);
477                 }
478         } else if (knob->id() >= 16 && knob->id() < 24) { // pan knob
479                 if (buttons_down.find(Device) != buttons_down.end()) { // Device button hold
480 #ifdef MIXBUS
481                         ac = stripable[chan]->comp_threshold_controllable();
482 #else
483                         ac = stripable[chan]->pan_width_control();
484 #endif
485                 } else {
486                         ac = stripable[chan]->pan_azimuth_control();
487                 }
488         }
489
490         if (ac && check_pick_up(knob, ac)) {
491                 ac->set_value ( ac->interface_to_internal( knob->value() / 127.0), PBD::Controllable::UseGroup );
492         }
493 }
494
495 void
496 LaunchControlXL::handle_fader_message (Fader* fader)
497 {
498
499         if (!stripable[fader->id()]) {
500                 return;
501         }
502
503         boost::shared_ptr<AutomationControl> ac = stripable[fader->id()]->gain_control();
504         if (ac && check_pick_up(fader, ac)) {
505                 ac->set_value ( ac->interface_to_internal( fader->value() / 127.0), PBD::Controllable::UseGroup );
506         }
507 }
508
509 void
510 LaunchControlXL::handle_midi_controller_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev, MIDI::channel_t chan)
511 {
512         _template_number = (int)chan;
513
514         if (template_number() < 8) {
515                 return; // only treat factory templates
516         }
517         // DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
518
519         CCControllerButtonMap::iterator b = cc_controller_button_map.find (ev->controller_number);
520         CCFaderMap::iterator f = cc_fader_map.find (ev->controller_number);
521         CCKnobMap::iterator k = cc_knob_map.find (ev->controller_number);
522
523         if (b != cc_controller_button_map.end()) {
524                 Button* button = b->second;
525                 handle_button_message(button, ev);
526         } else if (f != cc_fader_map.end()) {
527                 Fader* fader = f->second;
528                 fader->set_value(ev->value);
529                 handle_fader_message(fader);
530
531         } else if (k != cc_knob_map.end()) {
532                 Knob* knob = k->second;
533                 knob->set_value(ev->value);
534                 handle_knob_message(knob);
535         }
536 }
537
538 void
539 LaunchControlXL::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev, MIDI::channel_t chan)
540 {
541         _template_number = (int)chan;
542
543         if (template_number() < 8) {
544                 return; // only treat factory templates
545         }
546
547          //DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Note On %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
548
549          NNNoteButtonMap::iterator b = nn_note_button_map.find (ev->controller_number);
550
551          if (b != nn_note_button_map.end()) {
552                 Button* button = b->second;
553                 handle_button_message(button, ev);
554         }
555 }
556
557 void LaunchControlXL::handle_midi_note_off_message(MIDI::Parser & parser, MIDI::EventTwoBytes *ev, MIDI::channel_t chan)
558 {
559   //DEBUG_TRACE(DEBUG::LaunchControlXL, string_compose("Note Off %1 (velocity %2)\n",(int)ev->note_number, (int)ev->velocity));
560         handle_midi_note_on_message(parser, ev, chan); /* we handle both case in handle_midi_note_on_message */
561 }
562
563 /* Ardour session signals connection */
564
565 void
566 LaunchControlXL::thread_init ()
567 {
568         pthread_set_name (event_loop_name().c_str());
569
570         PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
571         ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
572
573         set_thread_priority ();
574 }
575
576 void
577 LaunchControlXL::connect_session_signals()
578 {
579         // receive transport state changed
580         session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_transport_state_changed, this), this);
581         session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_loop_state_changed, this), this);
582         // receive punch-in and punch-out
583         Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_parameter_changed, this, _1), this);
584         session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_parameter_changed, this, _1), this);
585
586         // receive rude solo changed
587         //session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_solo_active_changed, this, _1), this);
588         // receive record state toggled
589         //session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_record_state_changed, this), this);
590
591 }
592
593
594 void
595 LaunchControlXL::notify_transport_state_changed ()
596 { /*
597         Button* b = id_button_map[Play];
598
599         if (session->transport_rolling()) {
600                 b->set_state (LED::OneShot24th);
601                 b->set_color (LED::GreenFull);
602         } else {
603
604                  disable any blink on FixedLength from pending edit range op
605                 Button* fl = id_button_map[FixedLength];
606
607                 fl->set_color (LED::Black);
608                 fl->set_state (LED::NoTransition);
609                 write (fl->state_msg());
610
611                 b->set_color (LED::White);
612                 b->set_state (LED::NoTransition);
613         }
614
615         write (b->state_msg()); */
616 }
617
618 void
619 LaunchControlXL::notify_loop_state_changed ()
620 {
621 }
622
623 void
624 LaunchControlXL::notify_parameter_changed (std::string param)
625 { /*
626         IDButtonMap::iterator b;
627
628         if (param == "clicking") {
629                 if ((b = id_button_map.find (Metronome)) == id_button_map.end()) {
630                         return;
631                 }
632                 if (Config->get_clicking()) {
633                         b->second->set_state (LED::Blinking4th);
634                         b->second->set_color (LED::White);
635                 } else {
636                         b->second->set_color (LED::White);
637                         b->second->set_state (LED::NoTransition);
638                 }
639                 write (b->second->state_msg ()) ;
640         } */
641 }
642
643 /* connection handling */
644
645 XMLNode&
646 LaunchControlXL::get_state()
647 {
648         XMLNode& node (ControlProtocol::get_state());
649         XMLNode* child;
650
651         child = new XMLNode (X_("Input"));
652         child->add_child_nocopy (_async_in->get_state());
653         node.add_child_nocopy (*child);
654         child = new XMLNode (X_("Output"));
655         child->add_child_nocopy (_async_out->get_state());
656         node.add_child_nocopy (*child);
657
658         child = new XMLNode (X_("Configuration"));
659         child->set_property ("fader8master", LaunchControlXL::use_fader8master);
660         node.add_child_nocopy (*child);
661
662         return node;
663 }
664
665 int
666 LaunchControlXL::set_state (const XMLNode & node, int version)
667 {
668         DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("LaunchControlXL::set_state: active %1\n", active()));
669
670         int retval = 0;
671
672         if (ControlProtocol::set_state (node, version)) {
673                 return -1;
674         }
675
676         XMLNode* child;
677
678         if ((child = node.child (X_("Input"))) != 0) {
679                 XMLNode* portnode = child->child (Port::state_node_name.c_str());
680                 if (portnode) {
681                         _async_in->set_state (*portnode, version);
682                 }
683         }
684
685         if ((child = node.child (X_("Output"))) != 0) {
686                 XMLNode* portnode = child->child (Port::state_node_name.c_str());
687                 if (portnode) {
688                         _async_out->set_state (*portnode, version);
689                 }
690         }
691
692         if ((child = node.child (X_("Configuration"))) !=0) {
693                 /* this should propably become a for-loop at some point */
694                 child->get_property ("fader8master", use_fader8master);
695         }
696
697         return retval;
698 }
699
700 void
701 LaunchControlXL::port_registration_handler ()
702 {
703         if (!_async_in && !_async_out) {
704                 /* ports not registered yet */
705                 return;
706         }
707
708         if (_async_in->connected() && _async_out->connected()) {
709                 /* don't waste cycles here */
710                 return;
711         }
712
713 #ifdef __APPLE__
714         /* the origin of the numeric magic identifiers is known only to Ableton
715            and may change in time. This is part of how CoreMIDI works.
716         */
717         string input_port_name = X_("system:midi_capture_1319078870");
718         string output_port_name = X_("system:midi_playback_3409210341");
719 #else
720         string input_port_name = X_("Novation Launch Control XL MIDI 1 in");
721         string output_port_name = X_("Novation Launch Control XL MIDI 1 out");
722 #endif
723         vector<string> in;
724         vector<string> out;
725
726         AudioEngine::instance()->get_ports (string_compose (".*%1", input_port_name), DataType::MIDI, PortFlags (IsPhysical|IsOutput), in);
727         AudioEngine::instance()->get_ports (string_compose (".*%1", output_port_name), DataType::MIDI, PortFlags (IsPhysical|IsInput), out);
728
729         if (!in.empty() && !out.empty()) {
730                 cerr << "LaunchControlXL: both ports found\n";
731                 cerr << "\tconnecting to " << in.front() <<  " + " << out.front() << endl;
732                 if (!_async_in->connected()) {
733                         AudioEngine::instance()->connect (_async_in->name(), in.front());
734                 }
735                 if (!_async_out->connected()) {
736                         AudioEngine::instance()->connect (_async_out->name(), out.front());
737                 }
738         }
739 }
740
741 bool
742 LaunchControlXL::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
743 {
744         DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::connection_handler start\n");
745         if (!_input_port || !_output_port) {
746                 return false;
747         }
748
749         string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_in)->name());
750         string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_out)->name());
751
752         if (ni == name1 || ni == name2) {
753                 if (yn) {
754                         connection_state |= InputConnected;
755                 } else {
756                         connection_state &= ~InputConnected;
757                 }
758         } else if (no == name1 || no == name2) {
759                 if (yn) {
760                         connection_state |= OutputConnected;
761                 } else {
762                         connection_state &= ~OutputConnected;
763                 }
764         } else {
765                 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
766                 // not our ports
767                 return false;
768         }
769
770         DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("our ports changed connection state: %1 -> %2 connected ? %3\n",
771                                                    name1, name2, yn));
772
773         if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
774
775                 /* XXX this is a horrible hack. Without a short sleep here,
776                    something prevents the device wakeup messages from being
777                    sent and/or the responses from being received.
778                 */
779
780                 g_usleep (100000);
781                 DEBUG_TRACE (DEBUG::LaunchControlXL, "device now connected for both input and output\n");
782
783                 begin_using_device ();
784
785         } else {
786                 DEBUG_TRACE (DEBUG::LaunchControlXL, "Device disconnected (input or output or both) or not yet fully connected\n");
787                 stop_using_device ();
788         }
789
790         ConnectionChange (); /* emit signal for our GUI */
791
792         DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::connection_handler  end\n");
793
794         return true; /* connection status changed */
795 }
796
797
798 boost::shared_ptr<Port>
799 LaunchControlXL::output_port()
800 {
801         return _async_out;
802 }
803
804 boost::shared_ptr<Port>
805 LaunchControlXL::input_port()
806 {
807         return _async_in;
808 }
809
810 /* Stripables handling */
811
812 void
813 LaunchControlXL::stripable_selection_changed () // we don't need it but it's needs to be declared...
814 {
815 }
816
817
818 void
819 LaunchControlXL::stripable_property_change (PropertyChange const& what_changed, uint32_t which)
820 {
821
822         if (what_changed.contains (Properties::hidden)) {
823                 switch_bank (bank_start);
824         }
825
826         if (what_changed.contains (Properties::selected)) {
827
828                 if (!stripable[which]) {
829                         return;
830                 }
831                 if (which < 8) {
832                         update_track_focus_led ((uint8_t) which);
833                         update_knob_led((uint8_t) which);
834                 }
835         }
836 }
837
838 void
839 LaunchControlXL::switch_template (uint8_t t)
840 {
841         MidiByteArray msg (9, 0xf0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x77, t, 0xf7);
842         write (msg);
843 }
844
845 void
846 LaunchControlXL::switch_bank (uint32_t base)
847 {
848         SelectButton* sl = static_cast<SelectButton*>(id_controller_button_map[SelectLeft]);
849         SelectButton* sr = static_cast<SelectButton*>(id_controller_button_map[SelectRight]);
850
851         /* work backwards so we can tell if we should actually switch banks */
852
853         boost::shared_ptr<Stripable> s[8];
854         uint32_t different = 0;
855         uint8_t stripable_counter;
856
857         if (LaunchControlXL::use_fader8master) {
858                 stripable_counter = 7;
859         } else {
860                 stripable_counter = 8;
861         }
862
863         for (int n = 0; n < stripable_counter; ++n) {
864                 s[n] = session->get_remote_nth_stripable (base+n, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
865                 if (s[n] != stripable[n]) {
866                         different++;
867                 }
868         }
869
870         if (!s[0]) {
871                 /* not even the first stripable exists, do nothing */
872                 return;
873         }
874
875         if (sl && sr) {
876                 boost::shared_ptr<Stripable> next_base = session->get_remote_nth_stripable (base+8, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
877                 write(sl->state_msg((base)));
878                 write(sr->state_msg((next_base != 0)));
879         }
880
881         stripable_connections.drop_connections ();
882
883         for (int n = 0; n < stripable_counter; ++n) {
884                 stripable[n] = s[n];
885         }
886
887         /* at least one stripable in this bank */
888
889         bank_start = base;
890
891         for (int n = 0; n < 8; ++n) {
892
893                 if (stripable[n]) {
894                         /* stripable goes away? refill the bank, starting at the same point */
895
896                         stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::switch_bank, this, bank_start), lcxl);
897                         stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::stripable_property_change, this, _1, n), lcxl);
898                         stripable[n]->solo_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::solo_changed, this, n), lcxl);
899                         stripable[n]->mute_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::mute_changed, this, n), lcxl);
900                         if (stripable[n]->rec_enable_control()) {
901                                 stripable[n]->rec_enable_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::rec_changed, this, n), lcxl);
902                         }
903                 }
904                 update_track_focus_led(n);
905                 button_track_mode(track_mode());
906                 update_knob_led(n);
907         }
908 }
909
910 void
911 LaunchControlXL::stripables_added ()
912 {
913         DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::new stripable added!\n");
914         /* reload current bank */
915         switch_bank (bank_start);
916 }
917
918
919 void LaunchControlXL::set_track_mode (TrackMode mode) {
920         _track_mode = mode;
921
922         // now do led stuffs to signify the change
923         switch(mode) {
924                 case TrackMute:
925
926                         break;
927                 case TrackSolo:
928
929                         break;
930                 case TrackRecord:
931
932                         break;
933         default:
934                 break;
935         }
936 }
937
938 void
939 LaunchControlXL::set_fader8master (bool yn)
940 {
941         if (yn) {
942                 stripable[7] = master;
943         }
944         switch_bank(bank_start);
945 }