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