summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTérence Clastres <t.clastres@gmail.com>2018-08-07 03:41:12 +0200
committerTérence Clastres <t.clastres@gmail.com>2018-08-07 04:16:09 +0200
commit8c7a1e004b05625a41aed5051697b88acfc70ac2 (patch)
tree7b6e705dfd1e758c2e1928cced98d67c62a7dfcd
parentf4c11666519a6118aaa07c58762eb379b39345df (diff)
Add Launch Control XL control surface support
-rw-r--r--gtk2_ardour/ardev_common.sh.in2
-rw-r--r--libs/ardour/ardour/debug.h2
-rw-r--r--libs/ardour/debug.cc1
-rw-r--r--libs/surfaces/launch_control_xl/controllers.cc487
-rw-r--r--libs/surfaces/launch_control_xl/gui.cc268
-rw-r--r--libs/surfaces/launch_control_xl/gui.h100
-rw-r--r--libs/surfaces/launch_control_xl/interface.cc92
-rw-r--r--libs/surfaces/launch_control_xl/launch_control_xl.cc898
-rw-r--r--libs/surfaces/launch_control_xl/launch_control_xl.h545
-rw-r--r--libs/surfaces/launch_control_xl/leds.cc62
-rw-r--r--libs/surfaces/launch_control_xl/midi_byte_array.cc115
-rw-r--r--libs/surfaces/launch_control_xl/midi_byte_array.h78
-rw-r--r--libs/surfaces/launch_control_xl/wscript42
-rw-r--r--libs/surfaces/wscript4
14 files changed, 2693 insertions, 3 deletions
diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in
index f62575078b..89dd925420 100644
--- a/gtk2_ardour/ardev_common.sh.in
+++ b/gtk2_ardour/ardev_common.sh.in
@@ -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:.
diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h
index c688748231..e91e0edf51 100644
--- a/libs/ardour/ardour/debug.h
+++ b/libs/ardour/ardour/debug.h
@@ -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__ */
-
diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc
index 2ac53dab51..a578bd40ff 100644
--- a/libs/ardour/debug.cc
+++ b/libs/ardour/debug.cc
@@ -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
index 0000000000..b686791405
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/controllers.cc
@@ -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
index 0000000000..3aaab01cd5
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/gui.cc
@@ -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
index 0000000000..3ab2cb30b2
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/gui.h
@@ -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
index 0000000000..95f3825bb2
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/interface.cc
@@ -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
index 0000000000..6c71559ac4
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/launch_control_xl.cc
@@ -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
index 0000000000..0ac043429d
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/launch_control_xl.h
@@ -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
index 0000000000..49ac244465
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/leds.cc
@@ -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
index 0000000000..e66cd7d287
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/midi_byte_array.cc
@@ -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
index 0000000000..f9ab60e31b
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/midi_byte_array.h
@@ -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
index 0000000000..d8d1fa8010
--- /dev/null
+++ b/libs/surfaces/launch_control_xl/wscript
@@ -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()
diff --git a/libs/surfaces/wscript b/libs/surfaces/wscript
index 56bf2983fc..e3a32d05bf 100644
--- a/libs/surfaces/wscript
+++ b/libs/surfaces/wscript
@@ -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')