From 8c7a1e004b05625a41aed5051697b88acfc70ac2 Mon Sep 17 00:00:00 2001 From: Térence Clastres Date: Tue, 7 Aug 2018 03:41:12 +0200 Subject: Add Launch Control XL control surface support --- gtk2_ardour/ardev_common.sh.in | 2 +- libs/ardour/ardour/debug.h | 2 +- libs/ardour/debug.cc | 1 + libs/surfaces/launch_control_xl/controllers.cc | 487 +++++++++++ libs/surfaces/launch_control_xl/gui.cc | 268 ++++++ libs/surfaces/launch_control_xl/gui.h | 100 +++ libs/surfaces/launch_control_xl/interface.cc | 92 +++ .../launch_control_xl/launch_control_xl.cc | 898 +++++++++++++++++++++ .../surfaces/launch_control_xl/launch_control_xl.h | 545 +++++++++++++ libs/surfaces/launch_control_xl/leds.cc | 62 ++ libs/surfaces/launch_control_xl/midi_byte_array.cc | 115 +++ libs/surfaces/launch_control_xl/midi_byte_array.h | 78 ++ libs/surfaces/launch_control_xl/wscript | 42 + libs/surfaces/wscript | 4 +- 14 files changed, 2693 insertions(+), 3 deletions(-) create mode 100644 libs/surfaces/launch_control_xl/controllers.cc create mode 100644 libs/surfaces/launch_control_xl/gui.cc create mode 100644 libs/surfaces/launch_control_xl/gui.h create mode 100644 libs/surfaces/launch_control_xl/interface.cc create mode 100644 libs/surfaces/launch_control_xl/launch_control_xl.cc create mode 100644 libs/surfaces/launch_control_xl/launch_control_xl.h create mode 100644 libs/surfaces/launch_control_xl/leds.cc create mode 100644 libs/surfaces/launch_control_xl/midi_byte_array.cc create mode 100644 libs/surfaces/launch_control_xl/midi_byte_array.h create mode 100644 libs/surfaces/launch_control_xl/wscript 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 + +#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(n), (n + 13), n, LEDColor::RedFull); + MAKE_KNOB (static_cast(n + 8), (n + 29), (n + 8), LEDColor::GreenFull); + MAKE_KNOB (static_cast(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(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(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 +LaunchControlXL::get_ac_by_state(uint8_t n) { + boost::shared_ptr 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 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 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(id_note_button_map[Mute]); + TrackStateButton* solo = static_cast(id_note_button_map[Solo]); + TrackStateButton* record = static_cast(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 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 +#include +#include + +#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(this)->build_gui (); + } + static_cast(gui)->show_all(); + return gui; +} + +void +LaunchControlXL::tear_down_gui () +{ + if (gui) { + Gtk::Widget *w = static_cast(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 ("%1", _("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 ("%1", _("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 ici (ignore_active_change, true); + + update_port_combos (); +} + +void +LCXLGUI::update_port_combos () +{ + vector midi_inputs; + vector 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 input = build_midi_port_list (midi_inputs, true); + Glib::RefPtr 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 +LCXLGUI::build_midi_port_list (vector const & ports, bool for_input) +{ + Glib::RefPtr 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::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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 short_name; + Gtk::TreeModelColumn full_name; + }; + + MidiPortColumns midi_port_columns; + bool ignore_active_change; + + Glib::RefPtr build_midi_port_list (std::vector 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 name; + Gtk::TreeModelColumn path; + }; + + ActionColumns action_columns; + Glib::RefPtr available_action_model; + std::map 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 + +#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 +#include + +#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 (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(_async_in).get(); + _output_port = boost::dynamic_pointer_cast(_async_out).get(); + + session->BundleAddedOrRemoved (); + + connect_to_parser (); + + /* Connect input port to event loop */ + + AsyncMIDIPort* asp; + + asp = static_cast (_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 (_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 > +LaunchControlXL::bundles () +{ + list > 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::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(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::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::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 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 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 in; + vector 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, std::string name1, boost::weak_ptr, 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(_async_in)->name()); + string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr(_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 +LaunchControlXL::output_port() +{ + return _async_out; +} + +boost::shared_ptr +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(id_controller_button_map[SelectLeft]); + SelectButton* sr = static_cast(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 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 +#include +#include +#include +#include + +#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 { +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> 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 ConnectionChange; + + boost::shared_ptr input_port(); + boost::shared_ptr 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 NNNoteButtonMap; + NNNoteButtonMap nn_note_button_map; + /* map of NoteButtons by ButtonID */ + typedef std::map IDNoteButtonMap; + IDNoteButtonMap id_note_button_map; + /* map of ControllerNoteButtons by CC */ + typedef std::map CCControllerButtonMap; + CCControllerButtonMap cc_controller_button_map; + /* map of ControllerButtons by ButtonID */ + typedef std::map IDControllerButtonMap; + IDControllerButtonMap id_controller_button_map; + + + /* map of Fader by CC */ + typedef std::map CCFaderMap; + CCFaderMap cc_fader_map; + /* map of Fader by FaderID */ + typedef std::map IDFaderMap; + IDFaderMap id_fader_map; + + /* map of Knob by CC */ + typedef std::map CCKnobMap; + CCKnobMap cc_knob_map; + /* map of Knob by KnobID */ + typedef std::map IDKnobMap; + IDKnobMap id_knob_map; + + std::set buttons_down; + std::set 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 _input_bundle; + // Bundle to represent our output ports + boost::shared_ptr _output_bundle; + + MIDI::Port *_input_port; + MIDI::Port *_output_port; + boost::shared_ptr _async_in; + boost::shared_ptr _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 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 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 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, std::string name1, + boost::weak_ptr, 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 + +#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 +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +MidiByteArray::MidiByteArray (size_t size, MIDI::byte array[]) + : std::vector() +{ + for (size_t i = 0; i < size; ++i) + { + push_back (array[i]); + } +} + +MidiByteArray::MidiByteArray (size_t count, MIDI::byte first, ...) + : vector() +{ + 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 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 +#include + +#include + +//#include +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 +{ +public: + MidiByteArray() : std::vector() {} + + 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') -- cgit v1.2.3