Add Launch Control XL control surface support
authorTérence Clastres <t.clastres@gmail.com>
Tue, 7 Aug 2018 01:41:12 +0000 (03:41 +0200)
committerTérence Clastres <t.clastres@gmail.com>
Tue, 7 Aug 2018 02:16:09 +0000 (04:16 +0200)
14 files changed:
gtk2_ardour/ardev_common.sh.in
libs/ardour/ardour/debug.h
libs/ardour/debug.cc
libs/surfaces/launch_control_xl/controllers.cc [new file with mode: 0644]
libs/surfaces/launch_control_xl/gui.cc [new file with mode: 0644]
libs/surfaces/launch_control_xl/gui.h [new file with mode: 0644]
libs/surfaces/launch_control_xl/interface.cc [new file with mode: 0644]
libs/surfaces/launch_control_xl/launch_control_xl.cc [new file with mode: 0644]
libs/surfaces/launch_control_xl/launch_control_xl.h [new file with mode: 0644]
libs/surfaces/launch_control_xl/leds.cc [new file with mode: 0644]
libs/surfaces/launch_control_xl/midi_byte_array.cc [new file with mode: 0644]
libs/surfaces/launch_control_xl/midi_byte_array.h [new file with mode: 0644]
libs/surfaces/launch_control_xl/wscript [new file with mode: 0644]
libs/surfaces/wscript

index f62575078b8d570b466f4d1fb2d4ffd6247cb8c8..89dd925420f4a59d88bf5ae9d5969cee673d2d9b 100644 (file)
@@ -13,7 +13,7 @@ export GTK2_RC_FILES=/nonexistent
 # can find all the components.
 #
 
-export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/us2400:$libs/surfaces/wiimote:$libs/surfaces/push2:$libs/surfaces/maschine2:$libs/surfaces/cc121
+export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/us2400:$libs/surfaces/wiimote:$libs/surfaces/push2:$libs/surfaces/maschine2:$libs/surfaces/cc121:$libs/surfaces/launch_control_xl
 export ARDOUR_PANNER_PATH=$libs/panners
 export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:.
 export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:.
index c6887482314fbce61e926f01f580ca066bd4d763..e91e0edf51b2395fb807a6f869b9be6ab628746d 100644 (file)
@@ -87,9 +87,9 @@ namespace PBD {
                LIBARDOUR_API extern DebugBits VCA;
                LIBARDOUR_API extern DebugBits Push2;
                LIBARDOUR_API extern DebugBits US2400;
+               LIBARDOUR_API extern DebugBits LaunchControlXL;
 
        }
 }
 
 #endif /* __ardour_debug_h__ */
-
index 2ac53dab51e56004f336f5363a3b7f1a43cb3ec7..a578bd40ff3997b0bfd87752bf355fdb8b31905b 100644 (file)
@@ -84,3 +84,4 @@ PBD::DebugBits PBD::DEBUG::CC121 = PBD::new_debug_bit ("cc121");
 PBD::DebugBits PBD::DEBUG::VCA = PBD::new_debug_bit ("vca");
 PBD::DebugBits PBD::DEBUG::Push2 = PBD::new_debug_bit ("push2");
 PBD::DebugBits PBD::DEBUG::US2400 = PBD::new_debug_bit ("us2400");
+PBD::DebugBits PBD::DEBUG::LaunchControlXL = PBD::new_debug_bit("launchcontrolxl");
diff --git a/libs/surfaces/launch_control_xl/controllers.cc b/libs/surfaces/launch_control_xl/controllers.cc
new file mode 100644 (file)
index 0000000..b686791
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+  Copyright (C) 2016 Paul Davis
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <algorithm>
+
+#include "ardour/debug.h"
+#include "ardour/mute_control.h"
+#include "ardour/session.h"
+#include "ardour/solo_control.h"
+
+#include "launch_control_xl.h"
+
+using namespace ArdourSurface;
+using namespace ARDOUR;
+using namespace PBD;
+using std::cerr;
+
+void
+LaunchControlXL::build_maps ()
+{
+       /* Knobs */
+
+       Knob* knob;
+
+       #define MAKE_KNOB(i,cc, index, color) \
+               knob = new Knob ((i), (cc), (index), (color), (*this)); \
+               cc_knob_map.insert (std::make_pair (knob->controller_number(), knob)); \
+               id_knob_map.insert (std::make_pair (knob->id(), knob))
+
+               for (uint8_t n = 0; n < 8; ++n) {
+                       MAKE_KNOB (static_cast<KnobID>(n), (n + 13), n, LEDColor::RedFull);
+                       MAKE_KNOB (static_cast<KnobID>(n + 8), (n + 29), (n + 8), LEDColor::GreenFull);
+                       MAKE_KNOB (static_cast<KnobID>(n + 16), (n + 49), (n + 16), LEDColor::Yellow);
+               }
+
+       /* Faders */
+
+       Fader* fader;
+
+       #define MAKE_FADER(i,cc) \
+               fader = new Fader ((i), (cc)); \
+               cc_fader_map.insert (std::make_pair (fader->controller_number(), fader)); \
+               id_fader_map.insert (std::make_pair (fader->id(), fader))
+
+               for (uint8_t n = 0; n < 8; ++n) {
+                       MAKE_FADER (static_cast<FaderID>(n), (n + 77) );
+               }
+
+       /* Buttons */
+
+       ControllerButton *controller_button;
+       NoteButton *note_button;
+
+
+       #define MAKE_TRACK_BUTTON_PRESS(i,nn,index,color,p) \
+               note_button = new TrackButton ((i), (nn), (index), (color), (p), (*this)); \
+               nn_note_button_map.insert (std::make_pair (note_button->note_number(), note_button)); \
+               id_note_button_map.insert (std::make_pair (note_button->id(), note_button))
+       #define MAKE_SELECT_BUTTON_PRESS(i,cc,index,p) \
+               controller_button = new SelectButton ((i), (cc), (index), (p), (*this)); \
+               cc_controller_button_map.insert (std::make_pair (controller_button->controller_number(), controller_button)); \
+               id_controller_button_map.insert (std::make_pair (controller_button->id(), controller_button))
+       #define MAKE_TRACK_STATE_BUTTON_PRESS(i,nn,index,p) \
+               note_button = new TrackStateButton ((i), (nn), (index), (p), (*this)); \
+               nn_note_button_map.insert (std::make_pair (note_button->note_number(), note_button)); \
+               id_note_button_map.insert (std::make_pair (note_button->id(), note_button))
+               #define MAKE_TRACK_STATE_BUTTON_PRESS_RELEASE_LONG(i,nn,index, p,r,l) \
+                       note_button = new TrackStateButton ((i), (nn), (index), (p), (r), (l), (*this)); \
+                       nn_note_button_map.insert (std::make_pair (note_button->note_number(), note_button)); \
+                       id_note_button_map.insert (std::make_pair (note_button->id(), note_button))
+
+
+       MAKE_TRACK_BUTTON_PRESS(Focus1, 41, 24, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_1);
+       MAKE_TRACK_BUTTON_PRESS(Focus2, 42, 25, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_2);
+       MAKE_TRACK_BUTTON_PRESS(Focus3, 43, 26, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_3);
+       MAKE_TRACK_BUTTON_PRESS(Focus4, 44, 27, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_4);
+       MAKE_TRACK_BUTTON_PRESS(Focus5, 57, 28, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_5);
+       MAKE_TRACK_BUTTON_PRESS(Focus6, 58, 29, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_6);
+       MAKE_TRACK_BUTTON_PRESS(Focus7, 59, 30, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_7);
+       MAKE_TRACK_BUTTON_PRESS(Focus8, 60, 31, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_8);
+       MAKE_TRACK_BUTTON_PRESS(Control1, 73, 32, LEDColor::Yellow, &LaunchControlXL::button_track_control_1);
+       MAKE_TRACK_BUTTON_PRESS(Control2, 74, 33, LEDColor::Yellow, &LaunchControlXL::button_track_control_2);
+       MAKE_TRACK_BUTTON_PRESS(Control3, 75, 34, LEDColor::Yellow, &LaunchControlXL::button_track_control_3);
+       MAKE_TRACK_BUTTON_PRESS(Control4, 76, 35, LEDColor::Yellow, &LaunchControlXL::button_track_control_4);
+       MAKE_TRACK_BUTTON_PRESS(Control5, 89, 36, LEDColor::Yellow, &LaunchControlXL::button_track_control_5);
+       MAKE_TRACK_BUTTON_PRESS(Control6, 90, 37, LEDColor::Yellow, &LaunchControlXL::button_track_control_6);
+       MAKE_TRACK_BUTTON_PRESS(Control7, 91, 38, LEDColor::Yellow, &LaunchControlXL::button_track_control_7);
+       MAKE_TRACK_BUTTON_PRESS(Control8, 92, 39, LEDColor::Yellow, &LaunchControlXL::button_track_control_8);
+
+       MAKE_SELECT_BUTTON_PRESS(SelectUp, 104, 44, &LaunchControlXL::button_select_up);
+       MAKE_SELECT_BUTTON_PRESS(SelectDown, 105, 45, &LaunchControlXL::button_select_down);
+       MAKE_SELECT_BUTTON_PRESS(SelectLeft, 106, 46, &LaunchControlXL::button_select_left);
+       MAKE_SELECT_BUTTON_PRESS(SelectRight, 107, 47, &LaunchControlXL::button_select_right);
+
+       MAKE_TRACK_STATE_BUTTON_PRESS_RELEASE_LONG(Device, 105, 40, &LaunchControlXL::relax, &LaunchControlXL::button_device, &LaunchControlXL::button_device_long_press);;
+       MAKE_TRACK_STATE_BUTTON_PRESS(Mute, 106, 41, &LaunchControlXL::button_mute);
+       MAKE_TRACK_STATE_BUTTON_PRESS(Solo, 107, 42, &LaunchControlXL::button_solo);
+       MAKE_TRACK_STATE_BUTTON_PRESS(Record, 108, 43, &LaunchControlXL::button_record);
+
+}
+
+std::string
+LaunchControlXL::button_name_by_id (ButtonID id)
+{
+       switch (id) {
+               case Device:
+                       return "Device";
+               case Mute:
+                       return "Mute";
+               case Solo:
+                       return "Solo";
+               case Record:
+                       return "Record";
+               case SelectUp:
+                       return "Select Up";
+               case SelectDown:
+                       return "Select Down";
+               case SelectRight:
+                       return "Select Right";
+               case SelectLeft:
+                       return "Select Left";
+               case Focus1:
+                       return "Focus 1";
+               case Focus2:
+                       return "Focus 2";
+               case Focus3:
+                       return "Focus 3";
+               case Focus4:
+                       return "Focus 4";
+               case Focus5:
+                       return "Focus 5";
+               case Focus6:
+                       return "Focus 6";
+               case Focus7:
+                       return "Focus 7";
+               case Focus8:
+                       return "Focus 8";
+               case Control1:
+                       return "Control 1";
+               case Control2:
+                       return "Control 2";
+               case Control3:
+                       return "Control 3";
+               case Control4:
+                       return "Control 4";
+               case Control5:
+                       return "Control 5";
+               case Control6:
+                       return "Control 6";
+               case Control7:
+                       return "Control 7";
+               case Control8:
+                       return "Control 8";
+       default:
+               break;
+       }
+
+       return "???";
+}
+
+std::string
+LaunchControlXL::knob_name_by_id (KnobID id)
+{
+       switch (id) {
+               case SendA1:
+                       return "SendA 1";
+               case SendA2:
+                       return "SendA 2";
+               case SendA3:
+                       return "SendA 3";
+               case SendA4:
+                       return "SendA 4";
+               case SendA5:
+                       return "SendA 5";
+               case SendA6:
+                       return "SendA 6";
+               case SendA7:
+                       return "SendA 7";
+               case SendA8:
+                       return "SendA 8";
+               case SendB1:
+                       return "SendB 1";
+               case SendB2:
+                       return "SendB 2";
+               case SendB3:
+                       return "SendB 3";
+               case SendB4:
+                       return "SendB 4";
+               case SendB5:
+                       return "SendB 5";
+               case SendB6:
+                       return "SendB 6";
+               case SendB7:
+                       return "SendB 7";
+               case SendB8:
+                       return "SendB 8";
+               case Pan1:
+                       return "Pan 1";
+               case Pan2:
+                       return "Pan 2";
+               case Pan3:
+                       return "Pan 3";
+               case Pan4:
+                       return "Pan 4";
+               case Pan5:
+                       return "Pan 5";
+               case Pan6:
+                       return "Pan 6";
+               case Pan7:
+                       return "Pan 7";
+               case Pan8:
+                       return "Pan 8";
+       default:
+               break;
+       }
+
+       return "???";
+}
+
+std::string
+LaunchControlXL::fader_name_by_id (FaderID id)
+{
+       switch (id) {
+               case Fader1:
+                       return "Fader 1";
+               case Fader2:
+                       return "Fader 2";
+               case Fader3:
+                       return "Fader 3";
+               case Fader4:
+                       return "Fader 4";
+               case Fader5:
+                       return "Fader 5";
+               case Fader6:
+                       return "Fader 6";
+               case Fader7:
+                       return "Fader 7";
+               case Fader8:
+                       return "Fader 8";
+       default:
+               break;
+       }
+
+       return "???";
+}
+
+LaunchControlXL::TrackButton*
+LaunchControlXL::track_button_by_number(uint8_t n, uint8_t first, uint8_t middle)
+{
+       NNNoteButtonMap::iterator b;
+       if ( n < 5)     {
+               b = nn_note_button_map.find (first + n);
+       }
+       else {
+               b = nn_note_button_map.find (middle + n);
+       }
+
+       TrackButton* button;
+
+       if (b != nn_note_button_map.end()) {
+               button = static_cast<TrackButton*>(b->second);
+       }
+
+       return button;
+
+}
+
+void
+LaunchControlXL::button_track_focus(uint8_t n)
+{
+       if (!stripable[n]) {
+               return;
+       }
+
+       TrackButton* b = focus_button_by_number(n);
+
+       if (b == 0) {
+               return;
+       }
+
+       if ( stripable[n]->is_selected() ) {
+               b->set_color(LEDColor::AmberFull);
+       }
+       else {
+               b->set_color(LEDColor::AmberLow);
+       }
+       write (b->state_msg());
+
+}
+
+boost::shared_ptr<AutomationControl>
+LaunchControlXL::get_ac_by_state(uint8_t n) {
+               boost::shared_ptr<AutomationControl> ac;
+
+               switch(track_mode()) {
+                       case TrackMute:
+                               ac = stripable[n]->mute_control();
+                               break;
+
+                       case TrackSolo:
+                               ac = stripable[n]->solo_control();
+                               break;
+
+                       case TrackRecord:
+                               ac = stripable[n]->rec_enable_control();
+                               break;
+
+                       default:
+                       break;
+               }
+               return ac;
+}
+
+
+void
+LaunchControlXL::update_track_control_led(uint8_t n)
+{
+       TrackButton* b = control_button_by_number(n);
+
+       if (!stripable[n] || !b) {
+               return;
+       }
+
+       boost::shared_ptr<AutomationControl> ac = get_ac_by_state(n);
+
+
+               switch(track_mode()) {
+                       case TrackMute:
+                               if (ac->get_value()) {
+                                       b->set_color(LEDColor::AmberFull);
+                               }
+                               else {
+                                       b->set_color(LEDColor::AmberLow);
+                               }
+                               break;
+
+                       case TrackSolo:
+                               if (ac && stripable[n] != master ) {
+                                       if (ac->get_value()) {
+                                               b->set_color(LEDColor::GreenFull);
+                                       }
+                                       else {
+                                               b->set_color(LEDColor::GreenLow);
+                                       }
+                               }
+                               else {
+                                       b->set_color(LEDColor::Off);
+                               }
+                               break;
+
+                       case TrackRecord:
+                               if (ac) {
+                                       if (ac->get_value()) {
+                                               b->set_color(LEDColor::RedFull);
+                                       }
+                                       else {
+                                               b->set_color(LEDColor::RedLow);
+                                       }
+                               }
+                               else {
+
+                               }
+                               break;
+
+                       default:
+                       break;
+               }
+               if (ac) {
+                       write (b->state_msg());
+               }
+}
+
+void
+LaunchControlXL::solo_mute_rec_changed(uint32_t n) {
+       if (!stripable[n]) {
+               return;
+       }
+       update_track_control_led(n);
+}
+
+void
+LaunchControlXL::button_track_control(uint8_t n) {
+       if (!stripable[n]) {
+               return;
+       }
+       boost::shared_ptr<AutomationControl> ac = get_ac_by_state(n);
+
+       if (ac) {
+               session->set_control (ac, !ac->get_value(), PBD::Controllable::UseGroup);
+       }
+}
+
+void
+LaunchControlXL::button_track_mode(TrackMode state)
+{
+               set_track_mode(state);
+               for (uint8_t n = 0; n < 8; ++n) {
+                       update_track_control_led(n);
+               }
+
+               TrackStateButton* mute = static_cast<TrackStateButton*>(id_note_button_map[Mute]);
+               TrackStateButton* solo = static_cast<TrackStateButton*>(id_note_button_map[Solo]);
+               TrackStateButton* record = static_cast<TrackStateButton*>(id_note_button_map[Record]);
+
+               write(mute->state_msg( (state == TrackMute) ));
+               write(solo->state_msg( (state == TrackSolo) ));
+               write(record->state_msg( (state == TrackRecord) ));
+}
+
+void
+LaunchControlXL::button_select_left()
+{
+       switch_bank (max (0, bank_start - 1));
+}
+
+void
+LaunchControlXL::button_select_right()
+{
+       switch_bank (max (0, bank_start + 1));
+}
+
+void
+LaunchControlXL::button_select_up()
+{
+
+}
+
+void
+LaunchControlXL::button_select_down()
+{
+
+}
+
+void
+LaunchControlXL::button_device()
+{
+
+}
+
+void
+LaunchControlXL::button_device_long_press()
+{
+
+}
+
+bool
+LaunchControlXL::button_long_press_timeout (ButtonID id, Button* button)
+{
+       if (buttons_down.find (id) != buttons_down.end()) {
+               DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("long press timeout for %1, invoking method\n", id));
+               (this->*button->long_press_method) ();
+       } else {
+               DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("long press timeout for %1, expired/cancelled\n", id));
+               /* release happened and somehow we were not cancelled */
+       }
+
+       /* whichever button this was, we've used it ... don't invoke the
+          release action.
+       */
+       consumed.insert (id);
+
+       return false; /* don't get called again */
+}
+
+
+void
+LaunchControlXL::start_press_timeout (Button* button, ButtonID id)
+{
+       Glib::RefPtr<Glib::TimeoutSource> timeout = Glib::TimeoutSource::create (500); // milliseconds
+       button->timeout_connection = timeout->connect (sigc::bind (sigc::mem_fun (*this, &LaunchControlXL::button_long_press_timeout), id, button));
+       timeout->attach (main_loop()->get_context());
+}
diff --git a/libs/surfaces/launch_control_xl/gui.cc b/libs/surfaces/launch_control_xl/gui.cc
new file mode 100644 (file)
index 0000000..3aaab01
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+    Copyright (C) 2015 Paul Davis
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm/alignment.h>
+#include <gtkmm/label.h>
+#include <gtkmm/liststore.h>
+
+#include "pbd/unwind.h"
+#include "pbd/strsplit.h"
+#include "pbd/file_utils.h"
+
+#include "gtkmm2ext/bindings.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/utils.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/filesystem_paths.h"
+#include "ardour/parameter_descriptor.h"
+
+#include "launch_control_xl.h"
+#include "gui.h"
+
+#include "pbd/i18n.h"
+
+using namespace PBD;
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace std;
+using namespace Gtk;
+using namespace Gtkmm2ext;
+
+void*
+LaunchControlXL::get_gui () const
+{
+       if (!gui) {
+               const_cast<LaunchControlXL*>(this)->build_gui ();
+       }
+       static_cast<Gtk::VBox*>(gui)->show_all();
+       return gui;
+}
+
+void
+LaunchControlXL::tear_down_gui ()
+{
+       if (gui) {
+               Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
+               if (w) {
+                       w->hide();
+                       delete w;
+               }
+       }
+       delete gui;
+       gui = 0;
+}
+
+void
+LaunchControlXL::build_gui ()
+{
+       gui = new LCXLGUI (*this);
+}
+
+/*--------------------*/
+
+LCXLGUI::LCXLGUI (LaunchControlXL& p)
+       : lcxl (p)
+       , table (2, 5)
+       , action_table (5, 4)
+       , ignore_active_change (false)
+{
+       set_border_width (12);
+
+       table.set_row_spacings (4);
+       table.set_col_spacings (6);
+       table.set_border_width (12);
+       table.set_homogeneous (false);
+
+       std::string data_file_path;
+       string name = "push2-small.png";
+       Searchpath spath(ARDOUR::ardour_data_search_path());
+       spath.add_subdirectory_to_paths ("icons");
+       find_file (spath, name, data_file_path);
+       if (!data_file_path.empty()) {
+               image.set (data_file_path);
+               hpacker.pack_start (image, false, false);
+       }
+
+       Gtk::Label* l;
+       int row = 0;
+
+       input_combo.pack_start (midi_port_columns.short_name);
+       output_combo.pack_start (midi_port_columns.short_name);
+
+       input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &LCXLGUI::active_port_changed), &input_combo, true));
+       output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &LCXLGUI::active_port_changed), &output_combo, false));
+
+       l = manage (new Gtk::Label);
+       l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
+       l->set_alignment (1.0, 0.5);
+       table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+       table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+       row++;
+
+       l = manage (new Gtk::Label);
+       l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
+       l->set_alignment (1.0, 0.5);
+       table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+       table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+       row++;
+
+       hpacker.pack_start (table, true, true);
+
+       set_spacing (12);
+
+       pack_start (hpacker, false, false);
+
+       /* update the port connection combos */
+
+       update_port_combos ();
+
+       /* catch future changes to connection state */
+
+       ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, invalidator (*this), boost::bind (&LCXLGUI::connection_handler, this), gui_context());
+       lcxl.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&LCXLGUI::connection_handler, this), gui_context());
+}
+
+LCXLGUI::~LCXLGUI ()
+{
+}
+
+void
+LCXLGUI::connection_handler ()
+{
+       /* ignore all changes to combobox active strings here, because we're
+          updating them to match a new ("external") reality - we were called
+          because port connections have changed.
+       */
+
+       PBD::Unwinder<bool> ici (ignore_active_change, true);
+
+       update_port_combos ();
+}
+
+void
+LCXLGUI::update_port_combos ()
+{
+       vector<string> midi_inputs;
+       vector<string> midi_outputs;
+
+       ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
+       ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
+
+       Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
+       Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
+       bool input_found = false;
+       bool output_found = false;
+       int n;
+
+       input_combo.set_model (input);
+       output_combo.set_model (output);
+
+       Gtk::TreeModel::Children children = input->children();
+       Gtk::TreeModel::Children::iterator i;
+       i = children.begin();
+       ++i; /* skip "Disconnected" */
+
+
+       for (n = 1;  i != children.end(); ++i, ++n) {
+               string port_name = (*i)[midi_port_columns.full_name];
+               if (lcxl.input_port()->connected_to (port_name)) {
+                       input_combo.set_active (n);
+                       input_found = true;
+                       break;
+               }
+       }
+
+       if (!input_found) {
+               input_combo.set_active (0); /* disconnected */
+       }
+
+       children = output->children();
+       i = children.begin();
+       ++i; /* skip "Disconnected" */
+
+       for (n = 1;  i != children.end(); ++i, ++n) {
+               string port_name = (*i)[midi_port_columns.full_name];
+               if (lcxl.output_port()->connected_to (port_name)) {
+                       output_combo.set_active (n);
+                       output_found = true;
+                       break;
+               }
+       }
+
+       if (!output_found) {
+               output_combo.set_active (0); /* disconnected */
+       }
+}
+
+Glib::RefPtr<Gtk::ListStore>
+LCXLGUI::build_midi_port_list (vector<string> const & ports, bool for_input)
+{
+       Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
+       TreeModel::Row row;
+
+       row = *store->append ();
+       row[midi_port_columns.full_name] = string();
+       row[midi_port_columns.short_name] = _("Disconnected");
+
+       for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
+               row = *store->append ();
+               row[midi_port_columns.full_name] = *p;
+               std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
+               if (pn.empty ()) {
+                       pn = (*p).substr ((*p).find (':') + 1);
+               }
+               row[midi_port_columns.short_name] = pn;
+       }
+
+       return store;
+}
+
+void
+LCXLGUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
+{
+       if (ignore_active_change) {
+               return;
+       }
+
+       TreeModel::iterator active = combo->get_active ();
+       string new_port = (*active)[midi_port_columns.full_name];
+
+       if (new_port.empty()) {
+               if (for_input) {
+                       lcxl.input_port()->disconnect_all ();
+               } else {
+                       lcxl.output_port()->disconnect_all ();
+               }
+
+               return;
+       }
+
+       if (for_input) {
+               if (!lcxl.input_port()->connected_to (new_port)) {
+                       lcxl.input_port()->disconnect_all ();
+                       lcxl.input_port()->connect (new_port);
+               }
+       } else {
+               if (!lcxl.output_port()->connected_to (new_port)) {
+                       lcxl.output_port()->disconnect_all ();
+                       lcxl.output_port()->connect (new_port);
+               }
+       }
+}
diff --git a/libs/surfaces/launch_control_xl/gui.h b/libs/surfaces/launch_control_xl/gui.h
new file mode 100644 (file)
index 0000000..3ab2cb3
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+    Copyright (C) 2015 Paul Davis
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __ardour_launch_control_gui_h__
+#define __ardour_launch_control_gui_h__
+
+#include <vector>
+#include <string>
+
+#include <gtkmm/box.h>
+#include <gtkmm/button.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/image.h>
+#include <gtkmm/table.h>
+#include <gtkmm/treestore.h>
+#include <gtkmm/spinbutton.h>
+#include <gtkmm/notebook.h>
+
+namespace Gtk {
+       class CellRendererCombo;
+       class ListStore;
+}
+
+#include "ardour/mode.h"
+
+#include "launch_control_xl.h"
+
+namespace ArdourSurface {
+
+class LCXLGUI : public Gtk::VBox
+{
+public:
+       LCXLGUI (LaunchControlXL&);
+       ~LCXLGUI ();
+
+private:
+       LaunchControlXL& lcxl;
+       PBD::ScopedConnectionList lcxl_connections;
+       Gtk::HBox hpacker;
+       Gtk::Table table;
+       Gtk::Table action_table;
+       Gtk::ComboBox input_combo;
+       Gtk::ComboBox output_combo;
+       Gtk::Image    image;
+
+       void update_port_combos ();
+       PBD::ScopedConnection connection_change_connection;
+       void connection_handler ();
+       PBD::ScopedConnection port_reg_connection;
+
+       struct MidiPortColumns : public Gtk::TreeModel::ColumnRecord {
+               MidiPortColumns() {
+                       add (short_name);
+                       add (full_name);
+               }
+               Gtk::TreeModelColumn<std::string> short_name;
+               Gtk::TreeModelColumn<std::string> full_name;
+       };
+
+       MidiPortColumns midi_port_columns;
+       bool ignore_active_change;
+
+       Glib::RefPtr<Gtk::ListStore> build_midi_port_list (std::vector<std::string> const & ports, bool for_input);
+       void active_port_changed (Gtk::ComboBox*,bool for_input);
+
+       struct ActionColumns : public Gtk::TreeModel::ColumnRecord {
+               ActionColumns() {
+                       add (name);
+                       add (path);
+               }
+               Gtk::TreeModelColumn<std::string> name;
+               Gtk::TreeModelColumn<std::string> path;
+       };
+
+       ActionColumns action_columns;
+       Glib::RefPtr<Gtk::TreeStore> available_action_model;
+       std::map<std::string,std::string> action_map; // map from action names to paths
+
+
+};
+
+}
+
+#endif /* __ardour_launch_control_gui_h__ */
diff --git a/libs/surfaces/launch_control_xl/interface.cc b/libs/surfaces/launch_control_xl/interface.cc
new file mode 100644 (file)
index 0000000..95f3825
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+       Copyright (C) 2017 Paul Davis
+
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <stdexcept>
+
+#include "pbd/error.h"
+
+#include "ardour/rc_configuration.h"
+
+#include "control_protocol/control_protocol.h"
+#include "launch_control_xl.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace std;
+using namespace ArdourSurface;
+
+static ControlProtocol*
+new_launch_control_xl (ControlProtocolDescriptor*, Session* s)
+{
+       LaunchControlXL * lcxl = 0;
+
+       try {
+               lcxl = new LaunchControlXL (*s);
+               /* do not set active here - wait for set_state() */
+       }
+       catch (exception & e) {
+               error << "Error instantiating LaunchControlXL support: " << e.what() << endmsg;
+               delete lcxl;
+               lcxl = 0;
+       }
+
+       return lcxl;
+}
+
+static void
+delete_launch_control_xl (ControlProtocolDescriptor*, ControlProtocol* cp)
+{
+       try
+       {
+               delete cp;
+       }
+       catch ( exception & e )
+       {
+               cout << "Exception caught trying to finalize LaunchControlXL support: " << e.what() << endl;
+       }
+}
+
+/**
+       This is called on startup to check whether the lib should be loaded.
+
+       So anything that can be changed in the UI should not be used here to
+       prevent loading of the lib.
+*/
+static bool
+probe_launch_control_xl (ControlProtocolDescriptor*)
+{
+       return LaunchControlXL::probe();
+}
+
+static ControlProtocolDescriptor launch_control_xl_descriptor = {
+       /*name :              */   "Novation Launch Control XL",
+       /*id :                */   "uri://ardour.org/surfaces/launch_control_xl:0",
+       /*ptr :               */   0,
+       /*module :            */   0,
+       /*mandatory :         */   0,
+       // actually, the surface does support feedback, but all this
+       // flag does is show a submenu on the UI, which is useless for the mackie
+       // because feedback is always on. In any case, who'd want to use the
+       // mcu without the motorised sliders doing their thing?
+       /*supports_feedback : */   true,
+       /*probe :             */   probe_launch_control_xl,
+       /*initialize :        */   new_launch_control_xl,
+       /*destroy :           */   delete_launch_control_xl,
+};
+
+extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () { return &launch_control_xl_descriptor; }
diff --git a/libs/surfaces/launch_control_xl/launch_control_xl.cc b/libs/surfaces/launch_control_xl/launch_control_xl.cc
new file mode 100644 (file)
index 0000000..6c71559
--- /dev/null
@@ -0,0 +1,898 @@
+/*
+  Copyright (C) 2016 Paul Davis
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <stdlib.h>
+#include <pthread.h>
+
+#include "pbd/compose.h"
+#include "pbd/convert.h"
+#include "pbd/debug.h"
+#include "pbd/failed_constructor.h"
+#include "pbd/file_utils.h"
+#include "pbd/search_path.h"
+#include "pbd/enumwriter.h"
+
+#include "midi++/parser.h"
+
+#include "temporal/time.h"
+#include "temporal/bbt_time.h"
+
+#include "ardour/amp.h"
+#include "ardour/async_midi_port.h"
+#include "ardour/audioengine.h"
+#include "ardour/debug.h"
+#include "ardour/midiport_manager.h"
+#include "ardour/midi_track.h"
+#include "ardour/midi_port.h"
+#include "ardour/session.h"
+#include "ardour/tempo.h"
+#include "ardour/types_convert.h"
+#include "ardour/vca_manager.h"
+
+
+#include "gtkmm2ext/gui_thread.h"
+
+#include "gui.h"
+#include "launch_control_xl.h"
+
+#include "pbd/i18n.h"
+
+#ifdef PLATFORM_WINDOWS
+#define random() rand()
+#endif
+
+using namespace ARDOUR;
+using namespace std;
+using namespace PBD;
+using namespace Glib;
+using namespace ArdourSurface;
+#include "pbd/abstract_ui.cc" // instantiate template
+
+/* init global object */
+LaunchControlXL* lcxl = 0;
+
+LaunchControlXL::LaunchControlXL (ARDOUR::Session& s)
+       : ControlProtocol (s, string (X_("Novation Launch Control XL")))
+       , AbstractUI<LaunchControlRequest> (name())
+       , in_use (false)
+       , _track_mode(TrackMute)
+       , _template_number(8) // default template (factory 1)
+       , bank_start (0)
+       , connection_state (ConnectionState (0))
+       , gui (0)
+       , in_range_select (false)
+{
+       lcxl = this;
+       /* we're going to need this */
+
+       build_maps ();
+
+       /* master cannot be removed, so no need to connect to going-away signal */
+       master = session->master_out ();
+       /* the master bus will always be on the last channel on the lcxl */
+       stripable[7] = master;
+
+
+       run_event_loop ();
+
+       /* Ports exist for the life of this instance */
+
+       ports_acquire ();
+
+       /* catch arrival and departure of LaunchControlXL itself */
+       ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::port_registration_handler, this), this);
+
+       /* Catch port connections and disconnections */
+       ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::connection_handler, this, _1, _2, _3, _4, _5), this);
+
+       /* Launch Control XL ports might already be there */
+       port_registration_handler ();
+
+       session->RouteAdded.connect (session_connections, invalidator(*this), boost::bind (&LaunchControlXL::stripables_added, this), lcxl);
+       session->vca_manager().VCAAdded.connect (session_connections, invalidator (*this), boost::bind (&LaunchControlXL::stripables_added, this), lcxl);
+
+       switch_bank (bank_start);
+}
+
+LaunchControlXL::~LaunchControlXL ()
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "Launch Control XL  control surface object being destroyed\n");
+
+       /* do this before stopping the event loop, so that we don't get any notifications */
+       port_reg_connection.disconnect ();
+       port_connection.disconnect ();
+
+       stop_using_device ();
+       ports_release ();
+
+       stop_event_loop ();
+}
+
+
+void
+LaunchControlXL::run_event_loop ()
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "start event loop\n");
+       BaseUI::run ();
+}
+
+void
+LaunchControlXL::stop_event_loop ()
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "stop event loop\n");
+       BaseUI::quit ();
+}
+
+int
+LaunchControlXL::begin_using_device ()
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "begin using device\n");
+
+       switch_template(template_number()); // first factory template
+
+       connect_session_signals ();
+
+
+       init_buttons (true);
+
+       in_use = true;
+
+       return 0;
+}
+
+int
+LaunchControlXL::stop_using_device ()
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "stop using device\n");
+
+       if (!in_use) {
+               DEBUG_TRACE (DEBUG::LaunchControlXL, "nothing to do, device not in use\n");
+               return 0;
+       }
+
+       init_buttons (false);
+
+       session_connections.drop_connections ();
+
+       in_use = false;
+       return 0;
+}
+
+int
+LaunchControlXL::ports_acquire ()
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "acquiring ports\n");
+
+       /* setup ports */
+
+       _async_in  = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Launch Control XL in"), true);
+       _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Launch Control XL out"), true);
+
+       if (_async_in == 0 || _async_out == 0) {
+               DEBUG_TRACE (DEBUG::LaunchControlXL, "cannot register ports\n");
+               return -1;
+       }
+
+       /* We do not add our ports to the input/output bundles because we don't
+        * want users wiring them by hand. They could use JACK tools if they
+        * really insist on that (and use JACK)
+        */
+
+       _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
+       _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get();
+
+       session->BundleAddedOrRemoved ();
+
+       connect_to_parser ();
+
+       /* Connect input port to event loop */
+
+       AsyncMIDIPort* asp;
+
+       asp = static_cast<AsyncMIDIPort*> (_input_port);
+       asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &LaunchControlXL::midi_input_handler), _input_port));
+       asp->xthread().attach (main_loop()->get_context());
+
+       return 0;
+}
+
+void
+LaunchControlXL::ports_release ()
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "releasing ports\n");
+
+       /* wait for button data to be flushed */
+       AsyncMIDIPort* asp;
+       asp = static_cast<AsyncMIDIPort*> (_output_port);
+       asp->drain (10000, 500000);
+
+       {
+               Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
+               AudioEngine::instance()->unregister_port (_async_in);
+               AudioEngine::instance()->unregister_port (_async_out);
+       }
+
+       _async_in.reset ((ARDOUR::Port*) 0);
+       _async_out.reset ((ARDOUR::Port*) 0);
+       _input_port = 0;
+       _output_port = 0;
+}
+
+list<boost::shared_ptr<ARDOUR::Bundle> >
+LaunchControlXL::bundles ()
+{
+       list<boost::shared_ptr<ARDOUR::Bundle> > b;
+
+       if (_output_bundle) {
+               b.push_back (_output_bundle);
+       }
+
+       return b;
+}
+
+
+void
+LaunchControlXL::init_buttons (bool startup)
+{
+       if (startup) {
+               button_track_mode(track_mode());
+       }
+}
+
+bool
+LaunchControlXL::probe ()
+{
+       return true;
+}
+
+void*
+LaunchControlXL::request_factory (uint32_t num_requests)
+{
+       /* AbstractUI<T>::request_buffer_factory() is a template method only
+          instantiated in this source module. To provide something visible for
+          use in the interface/descriptor, we have this static method that is
+          template-free.
+       */
+       return request_buffer_factory (num_requests);
+}
+
+void
+LaunchControlXL::do_request (LaunchControlRequest * req)
+{
+       if (req->type == CallSlot) {
+
+               call_slot (MISSING_INVALIDATOR, req->the_slot);
+
+       } else if (req->type == Quit) {
+
+               stop_using_device ();
+       }
+}
+
+int
+LaunchControlXL::set_active (bool yn)
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active init with yn: '%1'\n", yn));
+
+       if (yn == active()) {
+               return 0;
+       }
+
+       if (yn) {
+               if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
+                       begin_using_device ();
+               } else {
+                       /* begin_using_device () will get called once we're connected */
+               }
+
+       } else {
+               /* Control Protocol Manager never calls us with false, but
+                * insteads destroys us.
+                */
+       }
+
+       ControlProtocol::set_active (yn);
+
+       DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active done with yn: '%1'\n", yn));
+
+       return 0;
+}
+
+void
+LaunchControlXL::write (const MidiByteArray& data)
+{
+       /* immediate delivery */
+       _output_port->write (&data[0], data.size(), 0);
+}
+
+/* Device to Ardour message handling */
+
+bool
+LaunchControlXL::midi_input_handler (IOCondition ioc, MIDI::Port* port)
+{
+       if (ioc & ~IO_IN) {
+               DEBUG_TRACE (DEBUG::LaunchControlXL, "MIDI port closed\n");
+               return false;
+       }
+
+       if (ioc & IO_IN) {
+
+               DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("something happened on  %1\n", port->name()));
+
+               AsyncMIDIPort* asp = static_cast<AsyncMIDIPort*>(port);
+               if (asp) {
+                       asp->clear ();
+               }
+
+               DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("data available on %1\n", port->name()));
+               if (in_use) {
+                       samplepos_t now = AudioEngine::instance()->sample_time();
+                       port->parse (now);
+               }
+       }
+
+       return true;
+}
+
+
+void
+LaunchControlXL::connect_to_parser ()
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Connecting to signals on port %1\n", _input_port->name()));
+
+       MIDI::Parser* p = _input_port->parser();
+
+       /* Incoming sysex */
+       p->sysex.connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_sysex, this, _1, _2, _3));
+
+ for (MIDI::channel_t n = 0; n < 16; ++n) {
+       /* Controller */
+               p->channel_controller[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_controller_message, this, _1, _2, n));
+               /* Button messages are NoteOn */
+               p->channel_note_on[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_note_on_message, this, _1, _2, n));
+               /* Button messages are NoteOn but libmidi++ sends note-on w/velocity = 0 as note-off so catch them too */
+               p->channel_note_off[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_note_off_message, this, _1, _2, n));
+       }
+}
+
+void
+LaunchControlXL::handle_midi_sysex (MIDI::Parser&, MIDI::byte* raw_bytes, size_t sz)
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Sysex, %1 bytes\n", sz));
+
+       if (sz < 8) {
+               return;
+       }
+
+       MidiByteArray msg (sz, raw_bytes);
+       MidiByteArray lcxl_sysex_header (6, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11);
+
+       if (!lcxl_sysex_header.compare_n (msg, 6)) {
+               return;
+       }
+
+
+       switch (msg[6]) {
+       case 0x77: /* template change */
+               DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Template change: %1 n", msg[7]));
+               _template_number = msg[7];
+               break;
+       }
+}
+
+
+void
+LaunchControlXL::handle_button_message(Button* button, MIDI::EventTwoBytes* ev)
+{
+  if (ev->value) {
+    /* any press cancels any pending long press timeouts */
+    for (set<ButtonID>::iterator x = buttons_down.begin(); x != buttons_down.end(); ++x) {
+      ControllerButton* cb = id_controller_button_map[*x];
+                       NoteButton*     nb = id_note_button_map[*x];
+                       if (cb != 0) {
+                               cb->timeout_connection.disconnect();
+                       }
+                       else if (nb != 0) {
+                                       nb->timeout_connection.disconnect();
+                       }
+    }
+
+    buttons_down.insert(button->id());
+    DEBUG_TRACE(DEBUG::LaunchControlXL, string_compose("button pressed: %1\n", LaunchControlXL::button_name_by_id(button->id())));
+    start_press_timeout(button, button->id());
+  }
+  else {
+    DEBUG_TRACE(DEBUG::LaunchControlXL, string_compose("button depressed: %1\n", LaunchControlXL::button_name_by_id(button->id())));
+    buttons_down.erase(button->id());
+    button->timeout_connection.disconnect();
+  }
+
+       set<ButtonID>::iterator c = consumed.find(button->id());
+
+  if (c == consumed.end()) {
+    if (ev->value == 0) {
+      (this->*button->release_method)();
+    }
+    else {
+      (this->*button->press_method)();
+    }
+  }
+  else {
+    DEBUG_TRACE(DEBUG::LaunchControlXL, "button was consumed, ignored\n");
+    consumed.erase(c);
+  }
+}
+
+void
+LaunchControlXL::handle_knob_message (Knob* knob)
+{
+       uint8_t chan = knob->id() % 8; // get the strip channel number
+       if (!stripable[chan]) {
+               return;
+       }
+
+       boost::shared_ptr<AutomationControl> ac;
+
+       if (knob->id() < 8) { // sendA
+               ac = stripable[chan]->trim_control();
+       }
+       else if (knob->id() >= 8 && knob->id() < 16) { // sendB
+               ac = stripable[chan]->pan_width_control();
+       }
+       else if (knob->id() >= 16 && knob->id() < 24) { // pan
+               ac = stripable[chan]->pan_azimuth_control();
+       }
+
+       if (ac) {
+               ac->set_value ( ac->interface_to_internal( knob->value() / 127.0), PBD::Controllable::UseGroup );
+       }
+}
+
+void
+LaunchControlXL::handle_fader_message (Fader* fader)
+{
+
+       if (!stripable[fader->id()]) {
+               return;
+       }
+
+       boost::shared_ptr<AutomationControl> ac = stripable[fader->id()]->gain_control();
+       if (ac) {
+               ac->set_value ( ac->interface_to_internal( fader->value() / 127.0), PBD::Controllable::UseGroup );
+       }
+}
+
+void
+LaunchControlXL::handle_midi_controller_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev, MIDI::channel_t chan)
+{
+       _template_number = (int)chan;
+
+       if (template_number() < 8) {
+               return; // only treat factory templates
+       }
+       // DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
+
+       CCControllerButtonMap::iterator b = cc_controller_button_map.find (ev->controller_number);
+       CCFaderMap::iterator f = cc_fader_map.find (ev->controller_number);
+       CCKnobMap::iterator k = cc_knob_map.find (ev->controller_number);
+
+       if (b != cc_controller_button_map.end()) {
+               Button* button = b->second;
+               handle_button_message(button, ev);
+       }
+       else if (f != cc_fader_map.end()) {
+               Fader* fader = f->second;
+               fader->set_value(ev->value);
+               handle_fader_message(fader);
+
+       }
+       else if (k != cc_knob_map.end()) {
+               Knob* knob = k->second;
+               knob->set_value(ev->value);
+               handle_knob_message(knob);
+       }
+}
+
+void
+LaunchControlXL::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev, MIDI::channel_t chan)
+{
+       _template_number = (int)chan;
+
+       if (template_number() < 8) {
+               return; // only treat factory templates
+       }
+
+        //DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Note On %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
+
+        NNNoteButtonMap::iterator b = nn_note_button_map.find (ev->controller_number);
+
+        if (b != nn_note_button_map.end()) {
+               Button* button = b->second;
+               handle_button_message(button, ev);
+       }
+}
+
+void LaunchControlXL::handle_midi_note_off_message(MIDI::Parser & parser, MIDI::EventTwoBytes *ev, MIDI::channel_t chan)
+{
+  //DEBUG_TRACE(DEBUG::LaunchControlXL, string_compose("Note Off %1 (velocity %2)\n",(int)ev->note_number, (int)ev->velocity));
+       handle_midi_note_on_message(parser, ev, chan); /* we handle both case in handle_midi_note_on_message */
+}
+
+/* Ardour session signals connection */
+
+void
+LaunchControlXL::thread_init ()
+{
+       pthread_set_name (event_loop_name().c_str());
+
+       PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
+       ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
+
+       set_thread_priority ();
+}
+
+void
+LaunchControlXL::connect_session_signals()
+{
+       // receive transport state changed
+       session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_transport_state_changed, this), this);
+       session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_loop_state_changed, this), this);
+       // receive punch-in and punch-out
+       Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_parameter_changed, this, _1), this);
+       session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_parameter_changed, this, _1), this);
+
+       // receive rude solo changed
+       //session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_solo_active_changed, this, _1), this);
+       // receive record state toggled
+       //session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_record_state_changed, this), this);
+
+}
+
+
+void
+LaunchControlXL::notify_transport_state_changed ()
+{ /*
+       Button* b = id_button_map[Play];
+
+       if (session->transport_rolling()) {
+               b->set_state (LED::OneShot24th);
+               b->set_color (LED::GreenFull);
+       } else {
+
+                disable any blink on FixedLength from pending edit range op
+               Button* fl = id_button_map[FixedLength];
+
+               fl->set_color (LED::Black);
+               fl->set_state (LED::NoTransition);
+               write (fl->state_msg());
+
+               b->set_color (LED::White);
+               b->set_state (LED::NoTransition);
+       }
+
+       write (b->state_msg()); */
+}
+
+void
+LaunchControlXL::notify_loop_state_changed ()
+{
+}
+
+void
+LaunchControlXL::notify_parameter_changed (std::string param)
+{ /*
+       IDButtonMap::iterator b;
+
+       if (param == "clicking") {
+               if ((b = id_button_map.find (Metronome)) == id_button_map.end()) {
+                       return;
+               }
+               if (Config->get_clicking()) {
+                       b->second->set_state (LED::Blinking4th);
+                       b->second->set_color (LED::White);
+               } else {
+                       b->second->set_color (LED::White);
+                       b->second->set_state (LED::NoTransition);
+               }
+               write (b->second->state_msg ()) ;
+       } */
+}
+
+/* connection handling */
+
+XMLNode&
+LaunchControlXL::get_state()
+{
+       XMLNode& node (ControlProtocol::get_state());
+       XMLNode* child;
+
+       child = new XMLNode (X_("Input"));
+       child->add_child_nocopy (_async_in->get_state());
+       node.add_child_nocopy (*child);
+       child = new XMLNode (X_("Output"));
+       child->add_child_nocopy (_async_out->get_state());
+       node.add_child_nocopy (*child);
+
+       return node;
+}
+
+int
+LaunchControlXL::set_state (const XMLNode & node, int version)
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("LaunchControlXL::set_state: active %1\n", active()));
+
+       int retval = 0;
+
+       if (ControlProtocol::set_state (node, version)) {
+               return -1;
+       }
+
+       XMLNode* child;
+
+       if ((child = node.child (X_("Input"))) != 0) {
+               XMLNode* portnode = child->child (Port::state_node_name.c_str());
+               if (portnode) {
+                       _async_in->set_state (*portnode, version);
+               }
+       }
+
+       if ((child = node.child (X_("Output"))) != 0) {
+               XMLNode* portnode = child->child (Port::state_node_name.c_str());
+               if (portnode) {
+                       _async_out->set_state (*portnode, version);
+               }
+       }
+
+       return retval;
+}
+
+void
+LaunchControlXL::port_registration_handler ()
+{
+       if (!_async_in && !_async_out) {
+               /* ports not registered yet */
+               return;
+       }
+
+       if (_async_in->connected() && _async_out->connected()) {
+               /* don't waste cycles here */
+               return;
+       }
+
+#ifdef __APPLE__
+       /* the origin of the numeric magic identifiers is known only to Ableton
+          and may change in time. This is part of how CoreMIDI works.
+       */
+       string input_port_name = X_("system:midi_capture_1319078870");
+       string output_port_name = X_("system:midi_playback_3409210341");
+#else
+       string input_port_name = X_("Novation Launch Control XL MIDI 1 in");
+       string output_port_name = X_("Novation Launch Control XL MIDI 1 out");
+#endif
+       vector<string> in;
+       vector<string> out;
+
+       AudioEngine::instance()->get_ports (string_compose (".*%1", input_port_name), DataType::MIDI, PortFlags (IsPhysical|IsOutput), in);
+       AudioEngine::instance()->get_ports (string_compose (".*%1", output_port_name), DataType::MIDI, PortFlags (IsPhysical|IsInput), out);
+
+       if (!in.empty() && !out.empty()) {
+               cerr << "LaunchControlXL: both ports found\n";
+               cerr << "\tconnecting to " << in.front() <<  " + " << out.front() << endl;
+               if (!_async_in->connected()) {
+                       AudioEngine::instance()->connect (_async_in->name(), in.front());
+               }
+               if (!_async_out->connected()) {
+                       AudioEngine::instance()->connect (_async_out->name(), out.front());
+               }
+       }
+}
+
+bool
+LaunchControlXL::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::connection_handler start\n");
+       if (!_input_port || !_output_port) {
+               return false;
+       }
+
+       string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_in)->name());
+       string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_out)->name());
+
+       if (ni == name1 || ni == name2) {
+               if (yn) {
+                       connection_state |= InputConnected;
+               } else {
+                       connection_state &= ~InputConnected;
+               }
+       } else if (no == name1 || no == name2) {
+               if (yn) {
+                       connection_state |= OutputConnected;
+               } else {
+                       connection_state &= ~OutputConnected;
+               }
+       } else {
+               DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
+               // not our ports
+               return false;
+       }
+
+       DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("our ports changed connection state: %1 -> %2 connected ? %3\n",
+                                                  name1, name2, yn));
+
+       if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
+
+               /* XXX this is a horrible hack. Without a short sleep here,
+                  something prevents the device wakeup messages from being
+                  sent and/or the responses from being received.
+               */
+
+               g_usleep (100000);
+                DEBUG_TRACE (DEBUG::LaunchControlXL, "device now connected for both input and output\n");
+
+                begin_using_device ();
+
+       } else {
+               DEBUG_TRACE (DEBUG::LaunchControlXL, "Device disconnected (input or output or both) or not yet fully connected\n");
+               stop_using_device ();
+       }
+
+       ConnectionChange (); /* emit signal for our GUI */
+
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::connection_handler  end\n");
+
+       return true; /* connection status changed */
+}
+
+
+boost::shared_ptr<Port>
+LaunchControlXL::output_port()
+{
+       return _async_out;
+}
+
+boost::shared_ptr<Port>
+LaunchControlXL::input_port()
+{
+       return _async_in;
+}
+
+/* Stripables handling */
+
+void
+LaunchControlXL::stripable_selection_changed () // we don't need it but it's needs to be declared...
+{
+}
+
+
+void
+LaunchControlXL::stripable_property_change (PropertyChange const& what_changed, uint32_t which)
+{
+
+       if (what_changed.contains (Properties::hidden)) {
+               switch_bank (bank_start);
+       }
+
+       if (what_changed.contains (Properties::selected)) {
+
+               if (!stripable[which]) {
+                       return;
+               }
+               if (which < 8) {
+                       button_track_focus( (uint8_t)which );
+               }
+       }
+
+}
+
+void
+LaunchControlXL::switch_template (uint8_t t)
+{
+       MidiByteArray msg (9, 0xf0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x77, t, 0xf7);
+       write (msg);
+}
+
+void
+LaunchControlXL::switch_bank (uint32_t base)
+{
+       SelectButton* sl = static_cast<SelectButton*>(id_controller_button_map[SelectLeft]);
+       SelectButton* sr = static_cast<SelectButton*>(id_controller_button_map[SelectRight]);
+
+       if (sl && sr) {
+               write(sl->state_msg( (base) ));
+               write(sr->state_msg( !(base) ));
+       }
+
+
+
+       stripable_connections.drop_connections ();
+
+       /* work backwards so we can tell if we should actually switch banks */
+
+       boost::shared_ptr<Stripable> s[8];
+       uint32_t different = 0;
+
+       for (int n = 0; n < 7; ++n) {
+               s[n] = session->get_remote_nth_stripable (base+n, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
+               if (s[n] != stripable[n]) {
+                       different++;
+               }
+       }
+
+       if (!s[0]) {
+               /* not even the first stripable exists, do nothing */
+               for (int n = 0; n < 7; ++n) {
+                       stripable[n].reset ();
+               }
+               return;
+       }
+
+       for (int n = 0; n < 7; ++n) {
+               stripable[n] = s[n];
+       }
+
+       /* at least one stripable in this bank */
+
+       bank_start = base;
+
+       for (int n = 0; n < 8; ++n) {
+
+               if (stripable[n]) {
+                       /* stripable goes away? refill the bank, starting at the same point */
+
+                       stripable[n]->DropReferences.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::switch_bank, this, bank_start), lcxl);
+                       stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::stripable_property_change, this, _1, n), lcxl);
+                       stripable[n]->solo_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::solo_changed, this, n), lcxl);
+                       stripable[n]->mute_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::mute_changed, this, n), lcxl);
+                       if (stripable[n]->rec_enable_control()) {
+                               stripable[n]->rec_enable_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::rec_changed, this, n), lcxl);
+                       }
+
+
+                       button_track_focus(n);
+                       update_track_control_led(n);
+               }
+       }
+}
+
+void
+LaunchControlXL::stripables_added ()
+{
+       DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::new stripable added!\n");
+       /* reload current bank */
+       switch_bank (bank_start);
+}
+
+
+void LaunchControlXL::set_track_mode (TrackMode mode) {
+       _track_mode = mode;
+
+       // now do led stuffs to signify the change
+       switch(mode) {
+               case TrackMute:
+
+                       break;
+               case TrackSolo:
+
+                       break;
+               case TrackRecord:
+
+                       break;
+       default:
+               break;
+       }
+}
diff --git a/libs/surfaces/launch_control_xl/launch_control_xl.h b/libs/surfaces/launch_control_xl/launch_control_xl.h
new file mode 100644 (file)
index 0000000..0ac0434
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+    Copyright (C) 2016 Paul Davis
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef __ardour_launch_control_h__
+#define __ardour_launch_control_h__
+
+#include <vector>
+#include <map>
+#include <stack>
+#include <list>
+#include <set>
+
+#define ABSTRACT_UI_EXPORTS
+#include "pbd/abstract_ui.h"
+
+#include "midi++/types.h"
+
+#include "ardour/mode.h"
+#include "ardour/types.h"
+
+#include "control_protocol/control_protocol.h"
+#include "control_protocol/types.h"
+
+#include "midi_byte_array.h"
+
+namespace MIDI {
+class Parser;
+class Port;
+} // namespace MIDI
+
+namespace ARDOUR {
+class AsyncMIDIPort;
+class Port;
+class MidiBuffer;
+class MidiTrack;
+} // namespace ARDOUR
+
+namespace ArdourSurface {
+
+
+struct LaunchControlRequest : public BaseUI::BaseRequestObject {
+public:
+  LaunchControlRequest() {}
+  ~LaunchControlRequest() {}
+};
+
+class LCXLGUI;
+class LaunchControlMenu;
+
+class LaunchControlXL : public ARDOUR::ControlProtocol,
+                        public AbstractUI<LaunchControlRequest> {
+public:
+  enum TrackMode {
+    TrackMute,
+    TrackSolo,
+    TrackRecord
+  };
+
+  enum ButtonID {
+    Focus1 = 0,
+    Focus2,
+    Focus3,
+    Focus4,
+    Focus5,
+    Focus6,
+    Focus7,
+    Focus8,
+    Control1,
+    Control2,
+    Control3,
+    Control4,
+    Control5,
+    Control6,
+    Control7,
+    Control8,
+    Device,
+    Mute,
+    Solo,
+    Record,
+    SelectUp,
+    SelectDown,
+    SelectLeft,
+    SelectRight
+  };
+
+  enum FaderID {
+    Fader1 = 0,
+    Fader2,
+    Fader3,
+    Fader4,
+    Fader5,
+    Fader6,
+    Fader7,
+    Fader8
+  };
+
+  enum KnobID {
+    SendA1 = 0,
+    SendA2,
+    SendA3,
+    SendA4,
+    SendA5,
+    SendA6,
+    SendA7,
+    SendA8,
+    SendB1,
+    SendB2,
+    SendB3,
+    SendB4,
+    SendB5,
+    SendB6,
+    SendB7,
+    SendB8,
+    Pan1,
+    Pan2,
+    Pan3,
+    Pan4,
+    Pan5,
+    Pan6,
+    Pan7,
+    Pan8
+  };
+
+  enum LEDFlag { Normal = 0xC, Blink = 0x8, DoubleBuffering = 0x0 };
+
+  /*
+  enum LEDState {
+          Toggle = 0x7F,
+          Momentary = 0x0,
+  }; */
+
+  enum LEDColor { Off=0, RedLow = 1, RedFull = 3, GreenLow = 16, GreenFull = 48, Yellow = 50, AmberLow = 17, AmberFull = 51};
+
+
+  struct Controller {
+    Controller(uint8_t cn,  uint8_t val = 0) : _controller_number(cn), _value(val)  {}
+
+    uint8_t  controller_number() const { return _controller_number; }
+    uint8_t value() const { return _value; }
+    void set_value(uint8_t val) { _value = val; }
+
+  protected:
+    uint8_t _controller_number;
+    uint8_t _value;
+  };
+
+
+  struct LED {
+    LED(uint8_t i, LEDColor c, LaunchControlXL& l) : _index(i), _color(c), _flag(LEDFlag::Normal), lcxl(&l)  {}
+    LED(uint8_t i, LEDColor c, LEDFlag f, LaunchControlXL& lcxl) : _index(i), _color(c), _flag(f) {}
+
+    LEDColor color() const { return _color; }
+    LEDFlag flag() const { return _flag; }
+    uint8_t index() const { return _index; }
+    void set_flag(LEDFlag f) { _flag = f; }
+
+    virtual MidiByteArray state_msg(bool light) const = 0;
+
+  protected:
+    uint8_t _index;
+    LEDColor _color;
+    LEDFlag _flag;
+    MidiByteArray _state_msg;
+    LaunchControlXL* lcxl;
+  };
+
+  struct MultiColorLED : public LED {
+    MultiColorLED(uint8_t i, LEDColor c, LaunchControlXL& l) : LED(i, c, l) {}
+    MultiColorLED(uint8_t i, LEDColor c, LEDFlag f, LaunchControlXL& l )
+        : LED(i, c, f, l) {}
+
+    void set_color(LEDColor c) { _color = c; }
+  };
+
+  struct Button {
+    Button(ButtonID id)
+        : press_method(&LaunchControlXL::relax),
+          release_method(&LaunchControlXL::relax),
+          long_press_method(&LaunchControlXL::relax), _id(id) {}
+
+    Button(ButtonID id, void (LaunchControlXL::*press)())
+        : press_method(press),
+          release_method(&LaunchControlXL::relax),
+          long_press_method(&LaunchControlXL::relax), _id(id) {}
+
+    Button(ButtonID id, void (LaunchControlXL::*press)(),
+            void (LaunchControlXL::*release)())
+        : press_method(press), release_method(release),
+          long_press_method(&LaunchControlXL::relax), _id(id) {}
+
+    Button(ButtonID id, void (LaunchControlXL::*press)(),
+           void (LaunchControlXL::*release)(),
+           void (LaunchControlXL::*long_press)())
+        : press_method(press), release_method(release),
+          long_press_method(long_press), _id(id) {}
+
+    virtual ~Button() {}
+
+    ButtonID id() const { return _id; }
+
+    void (LaunchControlXL::*press_method)();
+    void (LaunchControlXL::*release_method)();
+    void (LaunchControlXL::*long_press_method)();
+
+    sigc::connection timeout_connection;
+
+  protected:
+    ButtonID _id;
+  };
+
+  struct ControllerButton : public Button {
+
+    ControllerButton(ButtonID id, uint8_t cn,
+                      void (LaunchControlXL::*press)())
+      : Button(id, press), _controller_number(cn) {}
+
+    ControllerButton(ButtonID id, uint8_t cn,
+                      void (LaunchControlXL::*press)(),
+                      void (LaunchControlXL::*release)())
+        : Button(id, press, release), _controller_number(cn) {}
+
+
+    uint8_t controller_number() const { return _controller_number; }
+
+    private:
+      uint8_t _controller_number;
+  };
+
+  struct NoteButton : public Button {
+
+    NoteButton(ButtonID id, uint8_t cn,
+                void (LaunchControlXL::*press)())
+      : Button(id, press), _note_number(cn) {}
+
+    NoteButton(ButtonID id, uint8_t cn,
+                void (LaunchControlXL::*press)(),
+                void (LaunchControlXL::*release)())
+        : Button(id, press, release), _note_number(cn) {}
+    NoteButton(ButtonID id, uint8_t cn,
+                void (LaunchControlXL::*press)(),
+                void (LaunchControlXL::*release)(),
+                void (LaunchControlXL::*release_long)())
+        : Button(id, press, release, release_long), _note_number(cn) {}
+
+    uint8_t note_number() const { return _note_number; }
+
+    private:
+      uint8_t _note_number;
+  };
+
+  struct TrackButton : public NoteButton, public MultiColorLED {
+    TrackButton(ButtonID id, uint8_t nn, uint8_t index, LEDColor color,
+                void (LaunchControlXL::*press)(), LaunchControlXL& l)
+        : NoteButton(id, nn, press), MultiColorLED(index, color, l) {}
+
+    TrackButton(ButtonID id, uint8_t nn, uint8_t index, LEDColor color,
+                void (LaunchControlXL::*press)(),
+                void (LaunchControlXL::*release)(),
+                LaunchControlXL& l)
+        : NoteButton(id, nn, press, release), MultiColorLED(index, color, l) {}
+
+      MidiByteArray state_msg(bool light = true) const;
+
+  };
+
+  struct SelectButton : public ControllerButton, public LED {
+    SelectButton(ButtonID id, uint8_t cn, uint8_t index, void (LaunchControlXL::*press)(), LaunchControlXL& l)
+        : ControllerButton(id, cn, press), LED(index, LEDColor::RedFull, l) {}
+
+    MidiByteArray state_msg(bool light) const;
+
+  };
+
+  struct TrackStateButton : public NoteButton, public LED {
+    TrackStateButton(ButtonID id, uint8_t nn, uint8_t index, void (LaunchControlXL::*press)(), LaunchControlXL& l)
+        : NoteButton(id, nn, press), LED(index, LEDColor::Yellow, l) {}
+
+    TrackStateButton(ButtonID id, uint8_t nn, uint8_t index, void (LaunchControlXL::*press)(),
+                     void (LaunchControlXL::*release)(),
+                     LaunchControlXL& l)
+        : NoteButton(id, nn, press, release), LED(index, LEDColor::Yellow, l) {}
+        TrackStateButton(ButtonID id, uint8_t nn, uint8_t index, void (LaunchControlXL::*press)(),
+                         void (LaunchControlXL::*release)(),
+                         void (LaunchControlXL::*release_long)(),
+                         LaunchControlXL& l)
+            : NoteButton(id, nn, press, release, release_long), LED(index, LEDColor::Yellow, l) {}
+
+    MidiByteArray state_msg(bool light) const;
+
+  };
+
+  struct Fader : public Controller {
+
+    Fader(FaderID id, uint8_t cn)
+          : Controller(cn, 0), _id(id) {} // minimal value
+
+    FaderID id() const { return _id; }
+
+    void controller_changed(Controller* controller);
+
+  private:
+    FaderID _id;
+  };
+
+  struct Knob : public Controller, public MultiColorLED {
+
+    Knob(KnobID id, uint8_t cn, uint8_t index, LEDColor color, LaunchControlXL& l)
+        : Controller(cn, 64), MultiColorLED(index, color, l), _id(id) {} // knob 50/50 value
+
+    KnobID id() const { return _id; }
+
+    MidiByteArray state_msg(bool light = true) const;
+
+  private:
+    KnobID _id;
+  };
+
+public:
+  LaunchControlXL(ARDOUR::Session &);
+  ~LaunchControlXL();
+
+
+  static bool probe();
+  static void *request_factory(uint32_t);
+
+  std::list<boost::shared_ptr<ARDOUR::Bundle>> bundles();
+
+  bool has_editor() const { return true; }
+  void *get_gui() const;
+  void tear_down_gui();
+
+  int set_active(bool yn);
+  XMLNode &get_state();
+  int set_state(const XMLNode &node, int version);
+
+  PBD::Signal0<void> ConnectionChange;
+
+  boost::shared_ptr<ARDOUR::Port> input_port();
+  boost::shared_ptr<ARDOUR::Port> output_port();
+
+  Button *button_by_id(ButtonID);
+
+  static std::string button_name_by_id(ButtonID);
+  static std::string knob_name_by_id(KnobID);
+  static std::string fader_name_by_id(FaderID);
+
+  void write(const MidiByteArray &);
+
+  TrackMode track_mode() const { return _track_mode; }
+  void set_track_mode(TrackMode mode);
+
+  uint8_t template_number() const { return _template_number; }
+
+
+private:
+  bool in_use;
+  TrackMode _track_mode;
+  uint8_t _template_number;
+
+  void do_request(LaunchControlRequest *);
+
+  int begin_using_device();
+  int stop_using_device();
+  int ports_acquire();
+  void ports_release();
+  void run_event_loop();
+  void stop_event_loop();
+
+  void relax() {}
+
+  /* map of NoteButtons by NoteNumber */
+  typedef std::map<int, NoteButton *> NNNoteButtonMap;
+  NNNoteButtonMap nn_note_button_map;
+  /* map of NoteButtons by ButtonID */
+  typedef std::map<ButtonID, NoteButton *> IDNoteButtonMap;
+  IDNoteButtonMap id_note_button_map;
+  /* map of ControllerNoteButtons by CC */
+  typedef std::map<int, ControllerButton *> CCControllerButtonMap;
+  CCControllerButtonMap cc_controller_button_map;
+  /* map of ControllerButtons by ButtonID */
+  typedef std::map<ButtonID, ControllerButton *> IDControllerButtonMap;
+  IDControllerButtonMap id_controller_button_map;
+
+
+  /* map of Fader by CC */
+  typedef std::map<int, Fader *> CCFaderMap;
+  CCFaderMap cc_fader_map;
+  /* map of Fader by FaderID */
+  typedef std::map<FaderID, Fader *> IDFaderMap;
+  IDFaderMap id_fader_map;
+
+  /* map of Knob by CC */
+  typedef std::map<int, Knob *> CCKnobMap;
+  CCKnobMap cc_knob_map;
+  /* map of Knob by KnobID */
+  typedef std::map<KnobID, Knob *> IDKnobMap;
+  IDKnobMap id_knob_map;
+
+  std::set<ButtonID> buttons_down;
+  std::set<ButtonID> consumed;
+
+  bool button_long_press_timeout(ButtonID id, Button *button);
+  void start_press_timeout(Button *, ButtonID);
+
+  void init_buttons(bool startup);
+
+  void switch_template(uint8_t t);
+
+  void build_maps();
+
+  // Bundle to represent our input ports
+  boost::shared_ptr<ARDOUR::Bundle> _input_bundle;
+  // Bundle to represent our output ports
+  boost::shared_ptr<ARDOUR::Bundle> _output_bundle;
+
+  MIDI::Port *_input_port;
+  MIDI::Port *_output_port;
+  boost::shared_ptr<ARDOUR::Port> _async_in;
+  boost::shared_ptr<ARDOUR::Port> _async_out;
+
+  void connect_to_parser();
+  void handle_button_message(Button* button, MIDI::EventTwoBytes *);
+  void handle_fader_message(Fader* fader);
+  void handle_knob_message(Knob* knob);
+
+  void handle_midi_controller_message(MIDI::Parser &, MIDI::EventTwoBytes *, MIDI::channel_t chan);
+  void handle_midi_note_on_message(MIDI::Parser &, MIDI::EventTwoBytes *, MIDI::channel_t chan);
+  void handle_midi_note_off_message(MIDI::Parser &, MIDI::EventTwoBytes *, MIDI::channel_t chan);
+  void handle_midi_sysex(MIDI::Parser &, MIDI::byte *, size_t count);
+
+  bool midi_input_handler(Glib::IOCondition ioc, MIDI::Port *port);
+
+  void thread_init();
+
+  PBD::ScopedConnectionList session_connections;
+  void connect_session_signals();
+  void notify_transport_state_changed();
+  void notify_loop_state_changed();
+  void notify_parameter_changed(std::string);
+
+
+  /* Button methods */
+
+  TrackButton* track_button_by_number(uint8_t n, uint8_t first, uint8_t middle);
+  TrackButton* focus_button_by_number(uint8_t n) { return track_button_by_number(n, 41, 57) ; }
+  TrackButton* control_button_by_number(uint8_t n) { return track_button_by_number(n, 73, 89) ; }
+
+
+  void button_device();
+  void button_device_long_press();
+  void button_track_mode(TrackMode state);
+  void button_mute() { button_track_mode(TrackMode::TrackMute); }
+  void button_solo() { button_track_mode(TrackMode::TrackSolo); }
+  void button_record() { button_track_mode(TrackMode::TrackRecord); }
+  void button_select_up();
+  void button_select_down();
+  void button_select_left();
+  void button_select_right();
+
+  void button_track_focus(uint8_t n);
+  void button_track_control(uint8_t n);
+
+  boost::shared_ptr<ARDOUR::AutomationControl> get_ac_by_state(uint8_t n);
+  void update_track_control_led(uint8_t n);
+
+  void button_track_focus_1() { ControlProtocol::ToggleStripableSelection (stripable[0]); }
+  void button_track_focus_2() { ControlProtocol::ToggleStripableSelection (stripable[1]); }
+  void button_track_focus_3() { ControlProtocol::ToggleStripableSelection (stripable[2]); }
+  void button_track_focus_4() { ControlProtocol::ToggleStripableSelection (stripable[3]); }
+  void button_track_focus_5() { ControlProtocol::ToggleStripableSelection (stripable[4]); }
+  void button_track_focus_6() { ControlProtocol::ToggleStripableSelection (stripable[5]); }
+  void button_track_focus_7() { ControlProtocol::ToggleStripableSelection (stripable[6]); }
+  void button_track_focus_8() { ControlProtocol::ToggleStripableSelection (stripable[7]); }
+
+  void button_track_control_1() { button_track_control(0); }
+  void button_track_control_2() { button_track_control(1); }
+  void button_track_control_3() { button_track_control(2); }
+  void button_track_control_4() { button_track_control(3); }
+  void button_track_control_5() { button_track_control(4); }
+  void button_track_control_6() { button_track_control(5); }
+  void button_track_control_7() { button_track_control(6); }
+  void button_track_control_8() { button_track_control(7); }
+
+  /* stripables */
+
+       int32_t bank_start;
+       PBD::ScopedConnectionList stripable_connections;
+       boost::shared_ptr<ARDOUR::Stripable> stripable[8];
+
+       void stripables_added ();
+
+       void stripable_property_change (PBD::PropertyChange const& what_changed, uint32_t which);
+
+       void switch_bank (uint32_t base);
+
+  void solo_changed (uint32_t n) { solo_mute_rec_changed(n); }
+  void mute_changed (uint32_t n) { solo_mute_rec_changed(n); }
+  void rec_changed (uint32_t n) { solo_mute_rec_changed(n); }
+  void solo_mute_rec_changed (uint32_t n);
+
+  /* special Stripable */
+
+  boost::shared_ptr<ARDOUR::Stripable> master;
+
+  PBD::ScopedConnection port_reg_connection;
+  void port_registration_handler();
+
+  enum ConnectionState { InputConnected = 0x1, OutputConnected = 0x2 };
+
+  int connection_state;
+  bool connection_handler(boost::weak_ptr<ARDOUR::Port>, std::string name1,
+                          boost::weak_ptr<ARDOUR::Port>, std::string name2,
+                          bool yn);
+  PBD::ScopedConnection port_connection;
+  void connected();
+
+  /* GUI */
+
+  mutable LCXLGUI *gui;
+  void build_gui();
+
+  void stripable_selection_changed();
+
+  bool in_range_select;
+};
+
+
+} // namespace ArdourSurface
+
+#endif /* __ardour_launch_control_h__ */
diff --git a/libs/surfaces/launch_control_xl/leds.cc b/libs/surfaces/launch_control_xl/leds.cc
new file mode 100644 (file)
index 0000000..49ac244
--- /dev/null
@@ -0,0 +1,62 @@
+#include <algorithm>
+
+#include "launch_control_xl.h"
+#include "pbd/compose.h"
+#include "pbd/convert.h"
+#include "pbd/debug.h"
+#include "pbd/failed_constructor.h"
+#include "pbd/file_utils.h"
+#include "pbd/search_path.h"
+#include "pbd/enumwriter.h"
+
+#include "midi++/parser.h"
+
+#include "temporal/time.h"
+#include "temporal/bbt_time.h"
+
+#include "ardour/amp.h"
+#include "ardour/async_midi_port.h"
+#include "ardour/audioengine.h"
+#include "ardour/debug.h"
+#include "ardour/midiport_manager.h"
+#include "ardour/midi_track.h"
+#include "ardour/midi_port.h"
+#include "ardour/session.h"
+#include "ardour/tempo.h"
+#include "ardour/types_convert.h"
+#include "ardour/vca_manager.h"
+
+#include "gui.h"
+
+#include "pbd/i18n.h"
+
+using namespace ArdourSurface;
+using namespace ARDOUR;
+using namespace std;
+using namespace PBD;
+
+MidiByteArray
+LaunchControlXL::SelectButton::state_msg(bool light) const {
+  uint8_t velocity = ( color() + flag() ) * light;
+  return MidiByteArray (11, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x78, lcxl->template_number(), index(), velocity, 0xF7);
+}
+
+MidiByteArray
+LaunchControlXL::TrackButton::state_msg(bool light) const {
+  uint8_t velocity = ( color() + flag() ) * light;
+  return MidiByteArray (11, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x78, lcxl->template_number(), index(), velocity, 0xF7);
+
+}
+
+MidiByteArray
+LaunchControlXL::TrackStateButton::state_msg(bool light) const {
+  uint8_t velocity = ( color() + flag() ) * light;
+  return MidiByteArray (11, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x78, lcxl->template_number(), index(), velocity, 0xF7);
+
+}
+
+MidiByteArray
+LaunchControlXL::Knob::state_msg(bool light) const {
+  uint8_t velocity = ( color() + flag() ) * light;
+  return MidiByteArray (11, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x78, lcxl->template_number(), index(), velocity, 0xF7);
+}
diff --git a/libs/surfaces/launch_control_xl/midi_byte_array.cc b/libs/surfaces/launch_control_xl/midi_byte_array.cc
new file mode 100644 (file)
index 0000000..e66cd7d
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+       Copyright (C) 2006,2007 John Anderson
+
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+#include "midi_byte_array.h"
+
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <algorithm>
+#include <cstdarg>
+#include <iomanip>
+#include <stdexcept>
+
+using namespace std;
+
+MidiByteArray::MidiByteArray (size_t size, MIDI::byte array[])
+  : std::vector<MIDI::byte>()
+{
+       for  (size_t i = 0; i < size; ++i)
+       {
+               push_back (array[i]);
+       }
+}
+
+MidiByteArray::MidiByteArray (size_t count, MIDI::byte first, ...)
+  : vector<MIDI::byte>()
+{
+       push_back (first);
+       va_list var_args;
+       va_start (var_args, first);
+       for  (size_t i = 1; i < count; ++i)
+       {
+               MIDI::byte b = va_arg (var_args, int);
+               push_back (b);
+       }
+       va_end (var_args);
+}
+
+
+void MidiByteArray::copy (size_t count, MIDI::byte * arr)
+{
+       for (size_t i = 0; i < count; ++i) {
+               push_back (arr[i]);
+       }
+}
+
+MidiByteArray & operator <<  (MidiByteArray & mba, const MIDI::byte & b)
+{
+       mba.push_back (b);
+       return mba;
+}
+
+MidiByteArray & operator <<  (MidiByteArray & mba, const MidiByteArray & barr)
+{
+       back_insert_iterator<MidiByteArray> bit (mba);
+       copy (barr.begin(), barr.end(), bit);
+       return mba;
+}
+
+ostream & operator <<  (ostream & os, const MidiByteArray & mba)
+{
+       os << "[";
+       char fill = os.fill('0');
+       for (MidiByteArray::const_iterator it = mba.begin(); it != mba.end(); ++it) {
+               if  (it != mba.begin()) os << " ";
+               os << hex << setw(2) << (int)*it;
+       }
+       os.fill (fill);
+       os << dec;
+       os << "]";
+       return os;
+}
+
+MidiByteArray & operator <<  (MidiByteArray & mba, const std::string & st)
+{
+       /* note that this assumes that "st" is ASCII encoded
+        */
+
+       mba.insert (mba.end(), st.begin(), st.end());
+       return mba;
+}
+
+bool
+MidiByteArray::compare_n (const MidiByteArray& other, MidiByteArray::size_type n) const
+{
+       MidiByteArray::const_iterator us = begin();
+       MidiByteArray::const_iterator them = other.begin();
+
+       while (n && us != end() && them != other.end()) {
+               if ((*us) != (*them)) {
+                       return false;
+               }
+               --n;
+               ++us;
+               ++them;
+       }
+
+       return true;
+}
+       
diff --git a/libs/surfaces/launch_control_xl/midi_byte_array.h b/libs/surfaces/launch_control_xl/midi_byte_array.h
new file mode 100644 (file)
index 0000000..f9ab60e
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+       Copyright (C) 2006,2007 John Anderson
+
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+#ifndef midi_byte_array_h
+#define midi_byte_array_h
+
+#include <iostream>
+#include <vector>
+
+#include <boost/shared_array.hpp>
+
+//#include <midi++/types.h>
+namespace MIDI {
+       typedef unsigned char byte;
+}
+
+/**
+       To make building arrays of bytes easier. Thusly:
+
+       MidiByteArray mba;
+       mba << 0xf0 << 0x00 << 0xf7;
+
+       MidiByteArray buf;
+       buf << mba;
+
+       MidiByteArray direct( 3, 0xf0, 0x00, 0xf7 );
+
+       cout << mba << endl;
+       cout << buf << endl;
+       cout << direct << endl;
+
+       will all result in "f0 00 f7" being output to stdout
+*/
+class MidiByteArray : public std::vector<MIDI::byte>
+{
+public:
+       MidiByteArray() : std::vector<MIDI::byte>() {}
+
+       MidiByteArray( size_t count, MIDI::byte array[] );
+
+       bool compare_n (const MidiByteArray& other, MidiByteArray::size_type len) const;
+
+       /**
+               Accepts a preceding count, and then a list of bytes
+       */
+       MidiByteArray( size_t count, MIDI::byte first, ... );
+
+       /// copy the given number of bytes from the given array
+       void copy( size_t count, MIDI::byte arr[] );
+};
+
+/// append the given byte to the end of the array
+MidiByteArray & operator << ( MidiByteArray & mba, const MIDI::byte & b );
+
+/// append the given string to the end of the array
+MidiByteArray & operator << ( MidiByteArray & mba, const std::string & );
+
+/// append the given array to the end of this array
+MidiByteArray & operator << ( MidiByteArray & mba, const MidiByteArray & barr );
+
+/// output the bytes as hex to the given stream
+std::ostream & operator << ( std::ostream & os, const MidiByteArray & mba );
+
+#endif
diff --git a/libs/surfaces/launch_control_xl/wscript b/libs/surfaces/launch_control_xl/wscript
new file mode 100644 (file)
index 0000000..d8d1fa8
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+import os
+
+# Mandatory variables
+top = '.'
+out = 'build'
+
+
+def options(opt):
+    autowaf.set_options(opt)
+
+
+def configure(conf):
+    conf.load('compiler_cxx')
+    autowaf.configure(conf)
+
+
+def build(bld):
+    obj = bld(features='cxx cxxshlib')
+    obj.source = '''
+        launch_control_xl.cc
+        controllers.cc
+        interface.cc
+        midi_byte_array.cc
+        leds.cc
+        gui.cc
+    '''
+    obj.export_includes = ['.']
+    obj.defines = ['PACKAGE="ardour_launch_control_xl"']
+    obj.defines += ['ARDOURSURFACE_DLL_EXPORTS']
+    obj.defines += ['VERSIONSTRING="' + bld.env['VERSION'] + '"']
+    obj.includes = ['.', './launch_control_xl']
+    obj.name = 'libardour_launch_control_xl'
+    obj.target = 'ardour_launch_control_xl'
+    obj.uselib = 'GTKMM SIGCPP'
+    obj.use = 'libardour libardour_cp libpbd libevoral libcanvas libtemporal'
+    obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
+
+
+def shutdown():
+    autowaf.shutdown()
index 56bf2983fca266fdd8445e10eb1262c9f6f107c4..e3a32d05bfacce14fd65a8438e66d266f0e2f637 100644 (file)
@@ -27,6 +27,7 @@ children = [
         'generic_midi',
         'mackie',
         'us2400',
+        'launch_control_xl',
 ]
 
 def options(opt):
@@ -50,7 +51,7 @@ def configure(conf):
         children += [ 'push2' ]
     else:
         print ('You are missing the libusb-1.0 development package needed to compile Push2 support')
-    
+
     if conf.is_defined('HAVE_HIDAPI') and Options.options.maschine:
         children += [ 'maschine2' ]
         conf.define('BUILD_MASCHINE', 1)
@@ -85,6 +86,7 @@ def build(bld):
     bld.recurse('cc121')
     bld.recurse('mackie')
     bld.recurse('us2400')
+    bld.recurse('launch_control_xl')
 
     if bld.is_defined ('HAVE_LO'):
         bld.recurse('osc')