2 Copyright (C) 2016 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
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"
30 #include "midi++/parser.h"
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"
46 #include "gtkmm2ext/gui_thread.h"
49 #include "launch_control_xl.h"
53 #ifdef PLATFORM_WINDOWS
54 #define random() rand()
57 using namespace ARDOUR;
61 using namespace ArdourSurface;
62 #include "pbd/abstract_ui.cc" // instantiate template
64 /* init global object */
65 LaunchControlXL* lcxl = 0;
67 LaunchControlXL::LaunchControlXL (ARDOUR::Session& s)
68 : ControlProtocol (s, string (X_("Novation Launch Control XL")))
69 , AbstractUI<LaunchControlRequest> (name())
71 , _track_mode(TrackMute)
72 , _template_number(8) // default template (factory 1)
73 , _fader8master (false)
74 , _refresh_leds_flag (false)
76 , connection_state (ConnectionState (0))
78 , in_range_select (false)
81 /* we're going to need this */
85 /* master cannot be removed, so no need to connect to going-away signal */
86 master = session->master_out ();
90 /* Ports exist for the life of this instance */
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);
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);
100 /* Launch Control XL ports might already be there */
101 port_registration_handler ();
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);
106 switch_bank (bank_start);
109 LaunchControlXL::~LaunchControlXL ()
111 DEBUG_TRACE (DEBUG::LaunchControlXL, "Launch Control XL control surface object being destroyed\n");
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 ();
119 stop_using_device ();
128 LaunchControlXL::run_event_loop ()
130 DEBUG_TRACE (DEBUG::LaunchControlXL, "start event loop\n");
135 LaunchControlXL::stop_event_loop ()
137 DEBUG_TRACE (DEBUG::LaunchControlXL, "stop event loop\n");
142 LaunchControlXL::begin_using_device ()
144 DEBUG_TRACE (DEBUG::LaunchControlXL, "begin using device\n");
146 switch_template(template_number()); // first factory template
148 connect_session_signals ();
154 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("fader8master inital value '%1'\n", fader8master()));
155 set_fader8master (fader8master());
161 LaunchControlXL::stop_using_device ()
163 DEBUG_TRACE (DEBUG::LaunchControlXL, "stop using device\n");
166 DEBUG_TRACE (DEBUG::LaunchControlXL, "nothing to do, device not in use\n");
170 init_buttons (false);
172 session_connections.drop_connections ();
179 LaunchControlXL::ports_acquire ()
181 DEBUG_TRACE (DEBUG::LaunchControlXL, "acquiring ports\n");
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);
188 if (_async_in == 0 || _async_out == 0) {
189 DEBUG_TRACE (DEBUG::LaunchControlXL, "cannot register ports\n");
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)
198 _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
199 _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get();
201 session->BundleAddedOrRemoved ();
203 connect_to_parser ();
205 /* Connect input port to event loop */
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());
217 LaunchControlXL::ports_release ()
219 DEBUG_TRACE (DEBUG::LaunchControlXL, "releasing ports\n");
221 /* wait for button data to be flushed */
223 asp = static_cast<AsyncMIDIPort*> (_output_port);
224 asp->drain (10000, 500000);
227 Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
228 AudioEngine::instance()->unregister_port (_async_in);
229 AudioEngine::instance()->unregister_port (_async_out);
232 _async_in.reset ((ARDOUR::Port*) 0);
233 _async_out.reset ((ARDOUR::Port*) 0);
238 list<boost::shared_ptr<ARDOUR::Bundle> >
239 LaunchControlXL::bundles ()
241 list<boost::shared_ptr<ARDOUR::Bundle> > b;
243 if (_output_bundle) {
244 b.push_back (_output_bundle);
252 LaunchControlXL::init_buttons (bool startup)
254 reset(template_number());
257 switch_bank(bank_start);
262 LaunchControlXL::probe ()
268 LaunchControlXL::request_factory (uint32_t num_requests)
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
275 return request_buffer_factory (num_requests);
279 LaunchControlXL::do_request (LaunchControlRequest * req)
281 if (req->type == CallSlot) {
283 call_slot (MISSING_INVALIDATOR, req->the_slot);
285 } else if (req->type == Quit) {
287 stop_using_device ();
292 LaunchControlXL::reset(uint8_t chan)
294 MidiByteArray msg (3, 176 + chan, 0, 0); // turn off all leds, reset buffer settings and duty cycle
299 LaunchControlXL::set_active (bool yn)
301 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active init with yn: '%1'\n", yn));
303 if (yn == active()) {
308 if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
309 begin_using_device ();
311 /* begin_using_device () will get called once we're connected */
315 /* Control Protocol Manager never calls us with false, but
316 * insteads destroys us.
320 ControlProtocol::set_active (yn);
322 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active done with yn: '%1'\n", yn));
328 LaunchControlXL::write (const MidiByteArray& data)
330 /* immediate delivery */
331 _output_port->write (&data[0], data.size(), 0);
334 /* Device to Ardour message handling */
337 LaunchControlXL::midi_input_handler (IOCondition ioc, MIDI::Port* port)
340 DEBUG_TRACE (DEBUG::LaunchControlXL, "MIDI port closed\n");
346 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("something happened on %1\n", port->name()));
348 AsyncMIDIPort* asp = static_cast<AsyncMIDIPort*>(port);
353 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("data available on %1\n", port->name()));
355 samplepos_t now = AudioEngine::instance()->sample_time();
365 LaunchControlXL::connect_to_parser ()
367 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Connecting to signals on port %1\n", _input_port->name()));
369 MIDI::Parser* p = _input_port->parser();
372 p->sysex.connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_sysex, this, _1, _2, _3));
374 for (MIDI::channel_t n = 0; n < 16; ++n) {
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));
385 LaunchControlXL::handle_midi_sysex (MIDI::Parser&, MIDI::byte* raw_bytes, size_t sz)
387 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Sysex, %1 bytes\n", sz));
393 MidiByteArray msg (sz, raw_bytes);
394 MidiByteArray lcxl_sysex_header (6, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11);
396 if (!lcxl_sysex_header.compare_n (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];
411 LaunchControlXL::handle_button_message(boost::shared_ptr<Button> button, MIDI::EventTwoBytes* ev)
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];
419 cb->timeout_connection.disconnect();
420 } else if (nb != 0) {
421 nb->timeout_connection.disconnect();
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());
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);
437 set<ButtonID>::iterator c = consumed.find(button->id());
439 if (c == consumed.end()) {
440 if (ev->value == 0) {
441 (this->*button->release_method)();
443 (this->*button->press_method)();
446 DEBUG_TRACE(DEBUG::LaunchControlXL, "button was consumed, ignored\n");
452 LaunchControlXL::check_pick_up(boost::shared_ptr<Controller> controller, boost::shared_ptr<AutomationControl> ac)
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 );
459 LaunchControlXL::handle_knob_message (boost::shared_ptr<Knob> knob)
461 uint8_t chan = knob->id() % 8; // get the strip channel number
462 if (!stripable[chan]) {
466 boost::shared_ptr<AutomationControl> ac;
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();
472 ac = stripable[chan]->send_level_controllable (0);
474 } else if (knob->id() >= 8 && knob->id() < 16) { // sendB knob
475 if (buttons_down.find(Device) != buttons_down.end()) { // Device button hold
477 ac = stripable[chan]->filter_freq_controllable (true);
482 ac = stripable[chan]->send_level_controllable (1);
484 } else if (knob->id() >= 16 && knob->id() < 24) { // pan knob
485 if (buttons_down.find(Device) != buttons_down.end()) { // Device button hold
487 ac = stripable[chan]->comp_threshold_controllable();
489 ac = stripable[chan]->pan_width_control();
492 ac = stripable[chan]->pan_azimuth_control();
496 if (ac && check_pick_up(knob, ac)) {
497 ac->set_value ( ac->interface_to_internal( knob->value() / 127.0), PBD::Controllable::UseGroup );
502 LaunchControlXL::handle_fader_message (boost::shared_ptr<Fader> fader)
505 if (!stripable[fader->id()]) {
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 );
516 LaunchControlXL::handle_midi_controller_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev, MIDI::channel_t chan)
518 _template_number = (int)chan;
520 if (template_number() < 8) {
521 return; // only treat factory templates
523 // DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
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);
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);
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);
545 LaunchControlXL::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev, MIDI::channel_t chan)
547 _template_number = (int)chan;
549 if (template_number() < 8) {
550 return; // only treat factory templates
553 //DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Note On %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
555 NNNoteButtonMap::iterator b = nn_note_button_map.find (ev->controller_number);
557 if (b != nn_note_button_map.end()) {
558 boost::shared_ptr<Button> button = b->second;
559 handle_button_message(button, ev);
563 void LaunchControlXL::handle_midi_note_off_message(MIDI::Parser & parser, MIDI::EventTwoBytes *ev, MIDI::channel_t chan)
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 */
569 /* Ardour session signals connection */
572 LaunchControlXL::thread_init ()
574 pthread_set_name (event_loop_name().c_str());
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);
579 set_thread_priority ();
583 LaunchControlXL::connect_session_signals()
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);
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);
601 LaunchControlXL::notify_transport_state_changed ()
603 Button* b = id_button_map[Play];
605 if (session->transport_rolling()) {
606 b->set_state (LED::OneShot24th);
607 b->set_color (LED::GreenFull);
610 disable any blink on FixedLength from pending edit range op
611 Button* fl = id_button_map[FixedLength];
613 fl->set_color (LED::Black);
614 fl->set_state (LED::NoTransition);
615 write (fl->state_msg());
617 b->set_color (LED::White);
618 b->set_state (LED::NoTransition);
621 write (b->state_msg()); */
625 LaunchControlXL::notify_loop_state_changed ()
630 LaunchControlXL::notify_parameter_changed (std::string param)
632 IDButtonMap::iterator b;
634 if (param == "clicking") {
635 if ((b = id_button_map.find (Metronome)) == id_button_map.end()) {
638 if (Config->get_clicking()) {
639 b->second->set_state (LED::Blinking4th);
640 b->second->set_color (LED::White);
642 b->second->set_color (LED::White);
643 b->second->set_state (LED::NoTransition);
645 write (b->second->state_msg ()) ;
649 /* connection handling */
652 LaunchControlXL::get_state()
654 XMLNode& node (ControlProtocol::get_state());
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);
664 child = new XMLNode (X_("Configuration"));
665 child->set_property ("fader8master", fader8master());
666 node.add_child_nocopy (*child);
672 LaunchControlXL::set_state (const XMLNode & node, int version)
674 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("LaunchControlXL::set_state: active %1\n", active()));
678 if (ControlProtocol::set_state (node, version)) {
684 if ((child = node.child (X_("Input"))) != 0) {
685 XMLNode* portnode = child->child (Port::state_node_name.c_str());
687 _async_in->set_state (*portnode, version);
691 if ((child = node.child (X_("Output"))) != 0) {
692 XMLNode* portnode = child->child (Port::state_node_name.c_str());
694 _async_out->set_state (*portnode, version);
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);
707 LaunchControlXL::port_registration_handler ()
709 if (!_async_in || !_async_out) {
710 /* ports not registered yet */
714 if (_async_in->connected() && _async_out->connected()) {
715 /* don't waste cycles here */
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.
723 string input_port_name = X_("system:midi_capture_1319078870");
724 string output_port_name = X_("system:midi_playback_3409210341");
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");
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);
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());
741 if (!_async_out->connected()) {
742 AudioEngine::instance()->connect (_async_out->name(), out.front());
748 LaunchControlXL::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
750 DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::connection_handler start\n");
751 if (!_input_port || !_output_port) {
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());
758 if (ni == name1 || ni == name2) {
760 connection_state |= InputConnected;
762 connection_state &= ~InputConnected;
764 } else if (no == name1 || no == name2) {
766 connection_state |= OutputConnected;
768 connection_state &= ~OutputConnected;
771 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
776 DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("our ports changed connection state: %1 -> %2 connected ? %3\n",
779 if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
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.
787 DEBUG_TRACE (DEBUG::LaunchControlXL, "device now connected for both input and output\n");
789 begin_using_device ();
792 DEBUG_TRACE (DEBUG::LaunchControlXL, "Device disconnected (input or output or both) or not yet fully connected\n");
793 stop_using_device ();
796 ConnectionChange (); /* emit signal for our GUI */
798 DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::connection_handler end\n");
800 return true; /* connection status changed */
804 boost::shared_ptr<Port>
805 LaunchControlXL::output_port()
810 boost::shared_ptr<Port>
811 LaunchControlXL::input_port()
816 /* Stripables handling */
819 LaunchControlXL::stripable_selection_changed () // we don't need it but it's needs to be declared...
825 LaunchControlXL::stripable_property_change (PropertyChange const& what_changed, uint32_t which)
828 if (what_changed.contains (Properties::hidden)) {
829 switch_bank (bank_start);
832 if (what_changed.contains (Properties::selected)) {
834 if (!stripable[which]) {
838 update_track_focus_led ((uint8_t) which);
839 update_knob_led((uint8_t) which);
845 LaunchControlXL::switch_template (uint8_t t)
847 MidiByteArray msg (9, 0xf0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x77, t, 0xf7);
852 LaunchControlXL::switch_bank (uint32_t base)
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]);
857 /* work backwards so we can tell if we should actually switch banks */
859 boost::shared_ptr<Stripable> s[8];
860 uint32_t different = 0;
863 int stripable_counter = get_amount_of_tracks();
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]) {
873 /* not even the first stripable exists, do nothing */
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)));
883 stripable_connections.drop_connections ();
885 for (int n = 0; n < stripable_counter; ++n) {
889 /* at least one stripable in this bank */
893 for (int n = 0; n < 8; ++n) {
896 /* stripable goes away? refill the bank, starting at the same point */
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);
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);
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);
914 update_track_focus_led(n);
915 button_track_mode(track_mode());
921 LaunchControlXL::stripables_added ()
923 DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::new stripable added!\n");
924 /* reload current bank */
925 switch_bank (bank_start);
929 void LaunchControlXL::set_track_mode (TrackMode mode) {
932 // now do led stuffs to signify the change
949 LaunchControlXL::set_fader8master (bool yn)
953 stripable[7] = master;
959 switch_bank (bank_start);
963 LaunchControlXL::get_amount_of_tracks ()
966 if (fader8master ()) {
976 LaunchControlXL::set_refresh_leds_flag (bool yn)
978 _refresh_leds_flag = yn;