From cac849fe6d5fa3139fe0b5abcb9f87ccdaa85982 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 29 Aug 2024 18:31:00 -0600 Subject: [PATCH] add new novation LK4 surface support --- gtk2_ardour/ardev_common.sh.in | 2 +- libs/ardour/ardour/debug.h | 1 + libs/ardour/debug.cc | 1 + libs/surfaces/launchkey_4/gui.cc | 283 ++ libs/surfaces/launchkey_4/gui.h | 101 + libs/surfaces/launchkey_4/launchkey_4.cc | 2715 +++++++++++++++++ libs/surfaces/launchkey_4/launchkey_4.h | 408 +++ .../launchkey_4/launchkey_4_interface.cc | 88 + libs/surfaces/launchkey_4/wscript | 52 + libs/surfaces/wscript | 4 +- 10 files changed, 3653 insertions(+), 2 deletions(-) create mode 100644 libs/surfaces/launchkey_4/gui.cc create mode 100644 libs/surfaces/launchkey_4/gui.h create mode 100644 libs/surfaces/launchkey_4/launchkey_4.cc create mode 100644 libs/surfaces/launchkey_4/launchkey_4.h create mode 100644 libs/surfaces/launchkey_4/launchkey_4_interface.cc create mode 100644 libs/surfaces/launchkey_4/wscript diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in index e6b78e5279..c6d20a4b85 100644 --- a/gtk2_ardour/ardev_common.sh.in +++ b/gtk2_ardour/ardev_common.sh.in @@ -19,7 +19,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:$libs/surfaces/launch_control_xl:$libs/surfaces/contourdesign:$libs/surfaces/websockets:$libs/surfaces/console1:$libs/surfaces/launchpad_pro:$libs/surfaces/launchpad_x +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:$libs/surfaces/contourdesign:$libs/surfaces/websockets:$libs/surfaces/console1:$libs/surfaces/launchpad_pro:$libs/surfaces/launchpad_x:$libs/surfaces/launchkey_4 export ARDOUR_PANNER_PATH=$libs/panners export ARDOUR_DATA_PATH=$TOP/share:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour export ARDOUR_MIDIMAPS_PATH=$TOP/share/midi_maps diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h index a8e127a187..3e6f2661fa 100644 --- a/libs/ardour/ardour/debug.h +++ b/libs/ardour/ardour/debug.h @@ -69,6 +69,7 @@ namespace PBD { LIBARDOUR_API extern DebugBits LatencyRoute; LIBARDOUR_API extern DebugBits LaunchControlXL; LIBARDOUR_API extern DebugBits Launchpad; + LIBARDOUR_API extern DebugBits Launchkey; LIBARDOUR_API extern DebugBits Layering; LIBARDOUR_API extern DebugBits MIDISurface; LIBARDOUR_API extern DebugBits MTC; diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc index 2cb4becc65..bc715bcacc 100644 --- a/libs/ardour/debug.cc +++ b/libs/ardour/debug.cc @@ -64,6 +64,7 @@ PBD::DebugBits PBD::DEBUG::LatencyIO = PBD::new_debug_bit ("latencyio"); PBD::DebugBits PBD::DEBUG::LatencyRoute = PBD::new_debug_bit ("latencyroute"); PBD::DebugBits PBD::DEBUG::LaunchControlXL = PBD::new_debug_bit("launchcontrolxl"); PBD::DebugBits PBD::DEBUG::Launchpad = PBD::new_debug_bit ("launchpad"); +PBD::DebugBits PBD::DEBUG::Launchkey = PBD::new_debug_bit ("launchkey"); PBD::DebugBits PBD::DEBUG::Layering = PBD::new_debug_bit ("layering"); PBD::DebugBits PBD::DEBUG::MIDISurface = PBD::new_debug_bit ("midisurface"); PBD::DebugBits PBD::DEBUG::MTC = PBD::new_debug_bit ("mtc"); diff --git a/libs/surfaces/launchkey_4/gui.cc b/libs/surfaces/launchkey_4/gui.cc new file mode 100644 index 0000000000..ac71bb0dcc --- /dev/null +++ b/libs/surfaces/launchkey_4/gui.cc @@ -0,0 +1,283 @@ +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 "launchkey_4.h" +#include "gui.h" + +#include "pbd/i18n.h" + +#ifdef LAUNCHPAD_MINI +#define LAUNCHPAD_NAMESPACE LP_MINI +#else +#define LAUNCHPAD_NAMESPACE LP_X +#endif + +using namespace PBD; +using namespace ARDOUR; +using namespace ArdourSurface; +using namespace ArdourSurface::LAUNCHPAD_NAMESPACE; +using namespace Gtk; +using namespace Gtkmm2ext; + +void* +LaunchKey4::get_gui () const +{ + if (!_gui) { + const_cast(this)->build_gui (); + } + + static_cast(_gui)->show_all(); + return _gui; +} + +void +LaunchKey4::tear_down_gui () +{ + if (_gui) { + Gtk::Widget *w = static_cast(_gui)->get_parent(); + if (w) { + w->hide(); + delete w; + } + } + delete _gui; + _gui = 0; +} + +void +LaunchKey4::build_gui () +{ + _gui = new LK4_GUI (*this); +} + +/*--------------------*/ + +LK4_GUI::LK4_GUI (LaunchKey4& p) + : _lp (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; +#ifdef LAUNCHPAD_MINI + std::string name = "launchpad-mini.png"; +#else + std::string name = "launchpad-x.png"; +#endif + 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, &LK4_GUI::active_port_changed), &_input_combo, true)); + _output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &LK4_GUI::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_connections, invalidator (*this), boost::bind (&LK4_GUI::connection_handler, this), gui_context()); + ARDOUR::AudioEngine::instance()->PortPrettyNameChanged.connect (_port_connections, invalidator (*this), boost::bind (&LK4_GUI::connection_handler, this), gui_context()); + _lp.ConnectionChange.connect (_port_connections, invalidator (*this), boost::bind (&LK4_GUI::connection_handler, this), gui_context()); +} + +LK4_GUI::~LK4_GUI () +{ +} + +void +LK4_GUI::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 +LK4_GUI::update_port_combos () +{ + std::vector midi_inputs; + std::vector midi_outputs; + + if (!_lp.input_port() || !_lp.output_port()) { + return; + } + + 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) { + std::string port_name = (*i)[_midi_port_columns.full_name]; + if (_lp.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) { + std::string port_name = (*i)[_midi_port_columns.full_name]; + if (_lp.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 +LK4_GUI::build_midi_port_list (std::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] = std::string(); + row[_midi_port_columns.short_name] = _("Disconnected"); + + for (std::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 +LK4_GUI::active_port_changed (Gtk::ComboBox* combo, bool for_input) +{ + if (_ignore_active_change) { + return; + } + + TreeModel::iterator active = combo->get_active (); + std::string new_port = (*active)[_midi_port_columns.full_name]; + + if (new_port.empty()) { + if (for_input) { + _lp.input_port()->disconnect_all (); + } else { + _lp.output_port()->disconnect_all (); + } + + return; + } + + if (for_input) { + if (!_lp.input_port()->connected_to (new_port)) { + _lp.input_port()->disconnect_all (); + _lp.input_port()->connect (new_port); + } + } else { + if (!_lp.output_port()->connected_to (new_port)) { + _lp.output_port()->disconnect_all (); + _lp.output_port()->connect (new_port); + } + } +} diff --git a/libs/surfaces/launchkey_4/gui.h b/libs/surfaces/launchkey_4/gui.h new file mode 100644 index 0000000000..e88b2451b3 --- /dev/null +++ b/libs/surfaces/launchkey_4/gui.h @@ -0,0 +1,101 @@ +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __ardour_lpx_gui_h__ +#define __ardour_lpx_gui_h__ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gtk { + class ListStore; +} + +#include "ardour/mode.h" + +#include "launchkey_4.h" + +namespace ArdourSurface { namespace LAUNCHPAD_NAMESPACE { + +class LK4_GUI : public Gtk::VBox +{ +public: + LK4_GUI (LaunchKey4&); + ~LK4_GUI (); + +private: + LaunchKey4& _lp; + 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 (); + void connection_handler (); + + PBD::ScopedConnectionList _port_connections; + + 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); + +#if 0 + struct PressureModeColumns : public Gtk::TreeModel::ColumnRecord { + PressureModeColumns() { + add (mode); + add (name); + } + Gtk::TreeModelColumn mode; + Gtk::TreeModelColumn name; + }; + + PressureModeColumns _pressure_mode_columns; + Glib::RefPtr build_pressure_mode_columns (); + Gtk::ComboBox _pressure_mode_selector; + Gtk::Label _pressure_mode_label; + + void reprogram_pressure_mode (); +#endif +}; + +} } /* namespaces */ + +#endif /* __ardour_lpx_gui_h__ */ diff --git a/libs/surfaces/launchkey_4/launchkey_4.cc b/libs/surfaces/launchkey_4/launchkey_4.cc new file mode 100644 index 0000000000..9f95a534e7 --- /dev/null +++ b/libs/surfaces/launchkey_4/launchkey_4.cc @@ -0,0 +1,2715 @@ +/* + * Copyright (C) 2016-2018 Paul Davis + * Copyright (C) 2017-2018 Robin Gareus + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include + +#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/dB.h" +#include "ardour/debug.h" +#include "ardour/internal_send.h" +#include "ardour/midiport_manager.h" +#include "ardour/midi_track.h" +#include "ardour/midi_port.h" +#include "ardour/plugin.h" +#include "ardour/plugin_insert.h" +#include "ardour/selection.h" +#include "ardour/session.h" +#include "ardour/tempo.h" +#include "ardour/triggerbox.h" +#include "ardour/types_convert.h" +#include "ardour/utils.h" + +#include "gtkmm2ext/gui_thread.h" +#include "gtkmm2ext/rgb_macros.h" + +#include "gtkmm2ext/colors.h" + +#include "gui.h" +#include "launchkey_4.h" + +#include "pbd/i18n.h" + +#ifdef PLATFORM_WINDOWS +#define random() rand() +#endif + +using namespace ARDOUR; +using namespace PBD; +using namespace Glib; +using namespace ArdourSurface; +using namespace ArdourSurface::LAUNCHPAD_NAMESPACE; +using namespace Gtkmm2ext; + +#include "pbd/abstract_ui.cc" // instantiate template + +/* USB IDs */ + +#define NOVATION 0x1235 + +#define LAUNCHKEY4_MINI_25 0x0141 +#define LAUNCHKEY4_MINI_37 0x0142 +#define LAUNCHKEY4_25 0x0143 +#define LAUNCHKEY4_37 0x0144 +#define LAUNCHKEY4_49 0x0145 +#define LAUNCHKEY4_61 0x0146 + +static int first_fader = 0x9; +static const int PAD_COLUMNS = 8; +static const int PAD_ROWS = 2; +static const int NFADERS = 9; +static int last_detected = 0x0; + +bool +LaunchKey4::available () +{ + /* no preconditions other than the device being present */ + return true; +} + +bool +LaunchKey4::match_usb (uint16_t vendor, uint16_t device) +{ + if (vendor != NOVATION) { + return false; + } + + switch (device) { + case LAUNCHKEY4_MINI_25: + case LAUNCHKEY4_MINI_37: + case LAUNCHKEY4_25: + case LAUNCHKEY4_37: + case LAUNCHKEY4_49: + case LAUNCHKEY4_61: + last_detected = device; + return true; + } + + + return false; +} + +bool +LaunchKey4::probe (std::string& i, std::string& o) +{ + vector midi_inputs; + vector midi_outputs; + + AudioEngine::instance()->get_ports ("", DataType::MIDI, PortFlags (IsOutput|IsTerminal), midi_inputs); + AudioEngine::instance()->get_ports("", DataType::MIDI, PortFlags(IsInput | IsTerminal), midi_outputs); + + if (midi_inputs.empty() || midi_outputs.empty()) { + return false; + } + + std::regex rx (X_("Launchkey (Mini MK4|MK4).*MI"), std::regex::extended); + + auto has_lppro = [&rx](string const &s) { + std::string pn = AudioEngine::instance()->get_hardware_port_name_by_name(s); + return std::regex_search (pn, rx); + }; + + auto pi = std::find_if (midi_inputs.begin(), midi_inputs.end(), has_lppro); + auto po = std::find_if (midi_outputs.begin (), midi_outputs.end (), has_lppro); + + if (pi == midi_inputs.end () || po == midi_outputs.end ()) { + return false; + } + + i = *pi; + o = *po; + return true; +} + +LaunchKey4::LaunchKey4 (ARDOUR::Session& s) +#ifdef LAUNCHPAD_MINI + : MIDISurface (s, X_("Novation Launchkey Mini"), X_("Launchkey Mini"), true) +#else + : MIDISurface (s, X_("Novation Launchkey 4"), X_("Launchkey MK4"), true) +#endif + , _daw_out_port (nullptr) + , _gui (nullptr) + , scroll_x_offset (0) + , scroll_y_offset (0) + , device_pid (0x0) + , mode_channel (0xf) + , pad_function (MuteSolo) + , shift_pressed (false) + , layer_pressed (false) + , bank_start (0) + , button_mode (ButtonsRecEnable) // reset via toggle later + , encoder_mode (EncoderMixer) + , num_plugin_controls (0) +{ + run_event_loop (); + port_setup (); + + std::string pn_in, pn_out; + if (probe (pn_in, pn_out)) { + _async_in->connect (pn_in); + _async_out->connect (pn_out); + } + + build_color_map (); + build_pad_map (); + + Trigger::TriggerPropertyChange.connect (trigger_connections, invalidator (*this), boost::bind (&LaunchKey4::trigger_property_change, this, _1, _2), this); + ControlProtocol::PluginSelected.connect (session_connections, invalidator (*this), boost::bind (&LaunchKey4::plugin_selected, this, _1), this); + + session->RecordStateChanged.connect (session_connections, invalidator(*this), boost::bind (&LaunchKey4::record_state_changed, this), this); + session->TransportStateChange.connect (session_connections, invalidator(*this), boost::bind (&LaunchKey4::transport_state_changed, this), this); + session->RouteAdded.connect (session_connections, invalidator(*this), boost::bind (&LaunchKey4::stripables_added, this), this); + session->SoloChanged.connect (session_connections, invalidator(*this), boost::bind (&LaunchKey4::solo_changed, this), this); +} + +LaunchKey4::~LaunchKey4 () +{ + DEBUG_TRACE (DEBUG::Launchkey, "launchkey control surface object being destroyed\n"); + + trigger_connections.drop_connections (); + route_connections.drop_connections (); + session_connections.drop_connections (); + + for (size_t n = 0; n < sizeof (pads) / sizeof (pads[0]); ++n) { + pads[n].timeout_connection.disconnect (); + } + + stop_event_loop (); + tear_down_gui (); + + MIDISurface::drop (); + +} + +void +LaunchKey4::transport_state_changed () +{ + MIDI::byte msg[9]; + + msg[0] = 0xb0 | mode_channel; + msg[1] = 0x73; + + msg[3] = 0xb0 | mode_channel; + msg[4] = Play; + + msg[6] = 0xb0 | mode_channel; + msg[7] = Stop; + + if (session->transport_rolling()) { + msg[2] = 0x7f; + msg[5] = 0x0; + } else { + msg[2] = 0x0; + msg[5] = 0x7f; + } + + if (session->get_play_loop()) { + msg[8] = 0x7f; + } else { + msg[8] = 0x0; + } + + daw_write (msg, 9); + + map_rec_enable (); +} + +void +LaunchKey4::record_state_changed () +{ + map_rec_enable(); +} + +void +LaunchKey4::map_rec_enable () +{ + if (button_mode != ButtonsRecEnable) { + return; + } + + MIDI::byte msg[3]; + int channel = session->actively_recording() ? 0x0 : 0x2; + const int rec_color_index = 0x5; /* bright red */ + const int norec_color_index = 0x0; + + /* The global rec-enable button */ + + msg[0] = 0xb0 | channel; + msg[1] = 0x75; + msg[2] = session->get_record_enabled() ? rec_color_index : norec_color_index; + + daw_write (msg, 3); + + /* Now all the tracks */ + + for (int i = 0; i < NFADERS-1; ++i) { + show_rec_enable (i); + } +} + +void +LaunchKey4::show_rec_enable (int n) +{ + LightingMode mode = session->actively_recording() ? Solid : Pulse; + const int rec_color_index = 0x5; /* bright red */ + const int norec_color_index = 0x0; + + if (stripable[n]) { + std::shared_ptr ac = stripable[n]->rec_enable_control(); + if (ac) { + light_button (Button1 + n, mode, ac->get_value() ? rec_color_index : norec_color_index); + } else { + light_button (Button1 + n, Solid, 0x0); + } + } else { + light_button (Button1 + n, Solid, 0x0); + } +} + +int +LaunchKey4::set_active (bool yn) +{ + DEBUG_TRACE (DEBUG::Launchkey, string_compose("Launchpad X::set_active init with yn: %1\n", yn)); + + if (yn == active()) { + return 0; + } + + if (yn) { + + if (device_acquire ()) { + return -1; + } + + } else { + /* Control Protocol Manager never calls us with false, but + * insteads destroys us. + */ + } + + ControlProtocol::set_active (yn); + + DEBUG_TRACE (DEBUG::Launchkey, string_compose("Launchpad X::set_active done with yn: '%1'\n", yn)); + + return 0; +} + +void +LaunchKey4::run_event_loop () +{ + DEBUG_TRACE (DEBUG::Launchkey, "start event loop\n"); + BaseUI::run (); +} + +void +LaunchKey4::stop_event_loop () +{ + DEBUG_TRACE (DEBUG::Launchkey, "stop event loop\n"); + BaseUI::quit (); +} + +int +LaunchKey4::begin_using_device () +{ + DEBUG_TRACE (DEBUG::Launchkey, "begin using device\n"); + + /* get device model */ + + _data_required = true; + MidiByteArray device_inquiry (6, 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7); + write (device_inquiry); + + return 0; +} + +void +LaunchKey4::finish_begin_using_device () +{ + DEBUG_TRACE (DEBUG::Launchkey, "finish begin using device\n"); + + _data_required = false; + + if (MIDISurface::begin_using_device ()) { + return; + } + + connect_daw_ports (); + + /* enter DAW mode */ + + set_daw_mode (true); + set_pad_function (MuteSolo); + + /* catch current selection, if any so that we can wire up the pads if appropriate */ + stripable_selection_changed (); + switch_bank (0); + toggle_button_mode (); + use_encoders (true); + set_encoder_bank (0); + + /* Set configuration for fader displays, which is never altered */ + + MIDI::byte display_config[10]; + + display_config[0] = 0xf0; + display_config[1] = 0x0; + display_config[2] = 0x20; + display_config[3] = 0x29; + display_config[4] = (device_pid>>8) & 0x7f; + display_config[5] = device_pid & 0x7f; + display_config[6] = 0x4; + + display_config[8] = 0x61; + display_config[9] = 0xf7; + + for (int fader = 0; fader < 9; ++fader) { + /* 2 line display for all faders */ + display_config[7] = 0x5 + fader; + daw_write (display_config, 10); + } + std::cerr << "Configuring displays now\n"; + configure_display (StationaryDisplay, 0x1); + set_display_target (StationaryDisplay, 0, "ardour", true); + set_display_target (StationaryDisplay, 1, string(), true); + + configure_display (DAWPadFunctionDisplay, 0x1); + + /* In this DAW, mixer mode controls pan */ + set_display_target (MixerPotMode, 1, "Level", false); +} + +void +LaunchKey4::set_daw_mode (bool yn) +{ + MidiByteArray msg; + + msg.push_back (0x9f); + msg.push_back (0xc); + msg.push_back (yn ? 0x7f : 0x0); + daw_write (msg); + + if (yn) { + mode_channel = 0x0; + } else { + mode_channel = 0xf; + } + + if (yn) { + all_pads_out (); + } +} + +void +LaunchKey4::all_pads (int color_index) +{ + MIDI::byte msg[3]; + + msg[0] = 0x90; + msg[2] = color_index; + + /* top row */ + for (int i = 0; i < 8; ++i) { + msg[1] = 0x60 + i; + daw_write (msg, 3); + } + for (int i = 0; i < 8; ++i) { + msg[1] = 0x70 + i; + daw_write (msg, 3); + } +} + +void +LaunchKey4::all_pads_out () +{ + all_pads (0x0); +} + +int +LaunchKey4::stop_using_device () +{ + DEBUG_TRACE (DEBUG::Launchkey, "stop using device\n"); + + if (!_in_use) { + DEBUG_TRACE (DEBUG::Launchkey, "nothing to do, device not in use\n"); + return 0; + } + + set_daw_mode (false); + + return MIDISurface::stop_using_device (); +} + +XMLNode& +LaunchKey4::get_state() const +{ + XMLNode& node (MIDISurface::get_state()); + + XMLNode* child = new XMLNode (X_("DAWInput")); + child->add_child_nocopy (_daw_in->get_state()); + node.add_child_nocopy (*child); + child = new XMLNode (X_("DAWOutput")); + child->add_child_nocopy (_daw_out->get_state()); + node.add_child_nocopy (*child); + + return node; +} + +int +LaunchKey4::set_state (const XMLNode & node, int version) +{ + DEBUG_TRACE (DEBUG::Launchkey, string_compose ("LaunchKey4::set_state: active %1\n", active())); + + int retval = 0; + + if (MIDISurface::set_state (node, version)) { + return -1; + } + + return retval; +} + +std::string +LaunchKey4::input_port_name () const +{ + switch (last_detected) { + case LAUNCHKEY4_MINI_25: + case LAUNCHKEY4_MINI_37: + return X_(":Launchpad Mini MK3.*MIDI (In|2)"); + default: + break; + } + return X_(":Launchpad X MK3.*MIDI (In|2)"); +} + +std::string +LaunchKey4::output_port_name () const +{ + switch (last_detected) { + case LAUNCHKEY4_MINI_25: + case LAUNCHKEY4_MINI_37: + return X_(":Launchpad Mini MK3.*MIDI (Out|2)"); + default: + break; + } + + return X_(":Launchpad X MK3.*MIDI (Out|2)"); +} + +void +LaunchKey4::relax (Pad & pad) +{ +} + +void +LaunchKey4::relax (Pad & pad, int) +{ +} + +void +LaunchKey4::build_pad_map () +{ + for (int n = 0; n < 8; ++n) { + int pid = 0x60 + n; + pads[n] = Pad (pid, n, 0); + } + for (int n = 0; n < 8; ++n) { + int pid = 0x70 + n; + pads[8+n] = Pad (pid, n, 1); + } +} + +void +LaunchKey4::use_encoders (bool onoff) +{ + MIDI::byte msg[3]; + msg[0] = 0xb6; + msg[1] = 0x45; + msg[2] = (onoff ? 0x7f : 0x0); + daw_write (msg, 3); + + if (!onoff) { + return; + } + + MIDI::byte display_config[10]; + + display_config[0] = 0xf0; + display_config[1] = 0x0; + display_config[2] = 0x20; + display_config[3] = 0x29; + display_config[4] = (device_pid>>8) & 0x7f; + display_config[5] = device_pid & 0x7f; + display_config[6] = 0x4; + + display_config[8] = 0x62; + display_config[9] = 0xf7; + + for (int encoder = 0; encoder < 8; ++encoder) { + /* 2 line display for all encoders */ + display_config[7] = 0x15 + encoder; + daw_write (display_config, 10); + } +} + +void +LaunchKey4::handle_midi_sysex (MIDI::Parser& parser, MIDI::byte* raw_bytes, size_t sz) +{ +#ifndef NDEBUG + if (DEBUG_ENABLED(DEBUG::Launchkey)) { + std::stringstream str; + str << "Sysex received, size " << sz << std::endl; + str << hex; + for (size_t n = 0; n < sz; ++n) { + str << "0x" << (int) raw_bytes[n] << ' '; + } + str << std::endl; + std::cerr << str.str(); + } +#endif + + if (sz != 17) { + return; + } + + MIDI::byte dp_lsb; + MIDI::byte dp_msb; + + if (raw_bytes[1] == 0x7e && + raw_bytes[2] == 0x0 && + raw_bytes[3] == 0x6 && + raw_bytes[4] == 0x2 && + raw_bytes[5] == 0x0 && + raw_bytes[6] == 0x20 && + raw_bytes[7] == 0x29) { + dp_lsb = raw_bytes[8]; + dp_msb = raw_bytes[9]; + + const int family = (dp_msb<<8)|dp_lsb; + switch (family) { + case LAUNCHKEY4_MINI_25: + case LAUNCHKEY4_MINI_37: + device_pid = 0x0213; + break; + case LAUNCHKEY4_25: + case LAUNCHKEY4_37: + case LAUNCHKEY4_49: + case LAUNCHKEY4_61: + device_pid = 0x0214; + break; + default: + return; + } + + finish_begin_using_device (); + return; + } +} + +void +LaunchKey4::handle_midi_controller_message_chnF (MIDI::Parser& parser, MIDI::EventTwoBytes* ev) +{ + if (ev->controller_number < 0x05 || ev->controller_number > 0xd) { + return; + } + + int fader_number = ev->controller_number - 0x5; + fader_move (fader_number, ev->value); +} + +void +LaunchKey4::handle_midi_controller_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev) +{ + /* Remember: fader controller events are delivered via if (ev->controller_::handle_midi_controller_message_chnF() */ + if (&parser != _daw_in_port->parser()) { + if (ev->controller_number == 0x69 && ev->value == 0x7f) { + DEBUG_TRACE (DEBUG::Launchkey, string_compose ("function button press on non-DAW port, CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value)); + function_press (); + return; + } + /* we don't process CC messages from the regular port */ + DEBUG_TRACE (DEBUG::Launchkey, string_compose ("skip non-DAW CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value)); + return; + } + +#ifndef NDEBUG + std::stringstream ss; + ss << "CC 0x" << std::hex << (int) ev->controller_number << std::dec << " value (" << (int) ev->value << ")\n"; + DEBUG_TRACE (DEBUG::Launchkey, ss.str()); +#endif + + /* Shift being pressed can change everything */ + + if (ev->controller_number == 0x48) { + if (ev->value) { + shift_pressed = true; + } else { + shift_pressed = false; + } + return; + } + + /* Scene launch */ + if (ev->controller_number == 0x68) { + if (ev->value) { + scene_press (); + } + return; + } + + /* Button 9 (below fader 9 */ + + if (ev->controller_number == Button9) { + /* toggle on press only */ + + if (ev->value) { + toggle_button_mode (); + } + return; + } + + /* Encoder Mode button */ + + if (ev->controller_number == 0x41) { + switch (ev->value) { + case 2: + set_encoder_mode (EncoderPlugins); + break; + case 1: + set_encoder_mode (EncoderMixer); + break; + case 4: + set_encoder_mode (EncoderSendA); + break; + case 5: + set_encoder_mode (EncoderTransport); + break; + default: + break; + } + return; + } + + /* Encoder Bank Buttons */ + + if (ev->controller_number == 0x33) { + /* up'; use press only */ + if (ev->value && encoder_bank > 0) { + set_encoder_bank (encoder_bank - 1); + } + return; + } + + if (ev->controller_number == 0x34) { + /* down; use press only */ + if (ev->value && encoder_bank < 2) { + set_encoder_bank (encoder_bank + 1); + } + return; + } + + switch (ev->controller_number) { + case 0x6a: + if (ev->value) button_up (); + return; + case 0x6b: + if (ev->value) button_down (); + return; + case 0x67: + if (ev->value) button_left (); + return; + case 0x66: + if (ev->value) button_right (); + return; + } + + /* Buttons below faders */ + + if (ev->controller_number >= Button1 && ev->controller_number <= Button8) { + + if (ev->value == 0x7f) { + button_press (ev->controller_number - Button1); + } else { + button_release (ev->controller_number - Button1); + } + + return; + + } else if (ev->controller_number >= Knob1 && ev->controller_number <= Knob8) { + + encoder (ev->controller_number - Knob1, ev->value - 64); + return; + + } else if (ev->controller_number >= 0x55 && ev->controller_number <= 0x5c) { + + encoder (ev->controller_number - Knob1, ev->value - 64); + return; + } + + if (ev->value == 0x7f) { + switch (ev->controller_number) { + case Function: + function_press (); + break; + case Undo: + undo_press (); + break; + case Play: + if (device_pid == 0x213) { + /* Mini version only play button, so toggle */ + if (session->transport_rolling()) { + transport_stop(); + } else { + transport_play(); + } + } else { + transport_play (); + } + break; + case Stop: + transport_stop (); + break; + case RecEnable: + set_record_enable (!get_record_enabled()); + break; + case Loop: + loop_toggle (); + break; + default: + break; + } + } +} + +void +LaunchKey4::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev) +{ + if (ev->velocity == 0) { + handle_midi_note_off_message (parser, ev); + return; + } + + if (&parser != _daw_in_port->parser()) { + /* we don't process note messages from the regular port */ + DEBUG_TRACE (DEBUG::Launchkey, string_compose ("skip non-DAW Note On %1/0x%3%4%5 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity, std::hex, (int) ev->note_number, std::dec)); + return; + } + + int pad_number; + + switch (ev->note_number) { + case 0x60: + pad_number = 0; + break; + case 0x61: + pad_number = 1; + break; + case 0x62: + pad_number = 2; + break; + case 0x63: + pad_number = 3; + break; + case 0x64: + pad_number = 4; + break; + case 0x65: + pad_number = 5; + break; + case 0x66: + pad_number = 6; + break; + case 0x67: + pad_number = 7; + break; + + case 0x70: + pad_number = 8; + break; + case 0x71: + pad_number = 9; + break; + case 0x72: + pad_number = 10; + break; + case 0x73: + pad_number = 11; + break; + case 0x74: + pad_number = 12; + break; + case 0x75: + pad_number = 13; + break; + case 0x76: + pad_number = 14; + break; + case 0x77: + pad_number = 15; + break; + default: + return; + } + + DEBUG_TRACE (DEBUG::Launchkey, string_compose ("Note On %1/0x%3%4%5 (velocity %2) => pad %6\n", (int) ev->note_number, (int) ev->velocity, std::hex, (int) ev->note_number, std::dec, pad_number)); + + Pad& pad = pads[pad_number]; + + switch (pad_function) { + case MuteSolo: + pad_mute_solo (pad); + break; + case Triggers: + pad_trigger (pad, ev->velocity); + break; + default: + break; + } +} + +void +LaunchKey4::handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes* ev) +{ + int pad_number; + + switch (ev->note_number) { + case 0x60: + pad_number = 0; + break; + case 0x61: + pad_number = 1; + break; + case 0x62: + pad_number = 2; + break; + case 0x63: + pad_number = 3; + break; + case 0x64: + pad_number = 4; + break; + case 0x65: + pad_number = 5; + break; + case 0x66: + pad_number = 6; + break; + case 0x67: + pad_number = 7; + break; + + case 0x70: + pad_number = 8; + break; + case 0x71: + pad_number = 9; + break; + case 0x72: + pad_number = 10; + break; + case 0x73: + pad_number = 11; + break; + case 0x74: + pad_number = 12; + break; + case 0x75: + pad_number = 13; + break; + case 0x76: + pad_number = 14; + break; + case 0x77: + pad_number = 15; + break; + default: + return; + } + + DEBUG_TRACE (DEBUG::Launchkey, string_compose ("Note Off %1/0x%3%4%5 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity, std::hex, (int) ev->note_number, std::dec)); + pad_release (pads[pad_number]); +} + +void +LaunchKey4::pad_trigger (Pad& pad, int velocity) +{ + if (shift_pressed) { + trigger_stop_col (pad.x, true); /* immediate */ + } else { + TriggerPtr trigger = session->trigger_at (pad.x, pad.y + scroll_y_offset); + switch (trigger->state()) { + case Trigger::Stopped: + trigger->bang (velocity / 127.0f); + break; + default: + break; + } + start_press_timeout (pad); + } +} + +void +LaunchKey4::pad_release (Pad& pad) +{ + pad.timeout_connection.disconnect (); +} + +void +LaunchKey4::start_press_timeout (Pad& pad) +{ + Glib::RefPtr timeout = Glib::TimeoutSource::create (250); // milliseconds + pad.timeout_connection = timeout->connect (sigc::bind (sigc::mem_fun (*this, &LaunchKey4::long_press_timeout), pad.x)); + timeout->attach (main_loop()->get_context()); +} + +bool +LaunchKey4::long_press_timeout (int col) +{ + std::cerr << "timeout!\n"; + trigger_stop_col (col, false); /* non-immediate */ + return false; /* don't get called again */ +} + +void +LaunchKey4::trigger_property_change (PropertyChange pc, Trigger* t) +{ + if (pad_function != Triggers) { + return; + } + + int x = t->box().order(); + int y = t->index(); + + DEBUG_TRACE (DEBUG::Launchpad, string_compose ("prop change %1 for trigger at %2, %3\n", pc, x, y)); + + if (y < scroll_y_offset || y > scroll_y_offset + 1) { + /* not visible at present */ + return; + } + + if (x < scroll_x_offset || x > scroll_x_offset + 7) { + /* not visible at present */ + return; + } + + y -= scroll_y_offset; + x -= scroll_x_offset; + + /* name property change is sent when slots are loaded or unloaded */ + + PropertyChange our_interests; + our_interests.add (Properties::running); + our_interests.add (Properties::name);; + + if (pc.contains (our_interests)) { + + Pad& pad (pads[(y*8) + x]); + std::shared_ptr r = session->get_remote_nth_route (scroll_x_offset + x); + + trigger_pad_light (pad, r, t); + } +} + +void +LaunchKey4::trigger_pad_light (Pad& pad, std::shared_ptr r, Trigger* t) +{ + if (!r || !t || !t->region()) { + unlight_pad (pad.id); + return; + } + + MIDI::byte msg[3]; + + msg[0] = 0x90; + msg[1] = pad.id; + + switch (t->state()) { + case Trigger::Stopped: + msg[2] = find_closest_palette_color (r->presentation_info().color()); + break; + + case Trigger::WaitingToStart: + msg[0] |= 0x2; /* channel 2=> pulsing */ + msg[2] = 0x17; // find_closest_palette_color (r->presentation_info().color())); + break; + + case Trigger::Running: + /* choose contrasting color from the base one */ + msg[2] = find_closest_palette_color (HSV(r->presentation_info().color()).opposite()); + break; + + case Trigger::WaitingForRetrigger: + case Trigger::WaitingToStop: + case Trigger::WaitingToSwitch: + case Trigger::Stopping: + msg[0] |= 0x2; /* pulse */ + msg[2] = find_closest_palette_color (HSV(r->presentation_info().color()).opposite()); + } + + daw_write (msg, 3); +} + +void +LaunchKey4::map_triggers () +{ + for (int x = 0; x < PAD_COLUMNS; ++x) { + map_triggerbox (x); + } +} + +void +LaunchKey4::map_triggerbox (int x) +{ + std::shared_ptr r = session->get_remote_nth_route (x + scroll_x_offset); + + for (int y = 0; y < PAD_ROWS; ++y) { + Pad& pad (pads[(y*8) + x]); + TriggerPtr t = session->trigger_at (x + scroll_x_offset, y + scroll_y_offset); + trigger_pad_light (pad, r, t.get()); + } +} + +void +LaunchKey4::pad_mute_solo (Pad& pad) +{ + if (!stripable[pad.x]) { + return; + } + + if (pad.y == 0) { + session->set_control (stripable[pad.x]->mute_control(), !stripable[pad.x]->mute_control()->get_value(), PBD::Controllable::UseGroup); + } else { + session->set_control (stripable[pad.x]->solo_control(), !stripable[pad.x]->solo_control()->get_value(), PBD::Controllable::UseGroup); + } +} + +void +LaunchKey4::port_registration_handler () +{ + MIDISurface::port_registration_handler (); + connect_daw_ports (); +} + +void +LaunchKey4::connect_daw_ports () +{ + if (!_daw_in || !_daw_out) { + /* ports not registered yet */ + return; + } + + if (_daw_in->connected() && _daw_out->connected()) { + /* don't waste cycles here */ + return; + } + + std::vector midi_inputs; + std::vector midi_outputs; + + /* get all MIDI Ports */ + + AudioEngine::instance()->get_ports ("", DataType::MIDI, PortFlags (IsOutput|IsTerminal), midi_inputs); + AudioEngine::instance()->get_ports("", DataType::MIDI, PortFlags(IsInput | IsTerminal), midi_outputs); + + if (midi_inputs.empty() || midi_outputs.empty()) { + return; + } + + /* Try to find the DAW port, whose pretty name varies on Linux + * depending on the version of ALSA, but is fairly consistent across + * newer ALSA and other platforms. + */ + + std::string regex_str; + + if (device_pid == 0x213) { + regex_str = X_("Launchkey Mini MK4.*(DAW|MIDI 2|DA$)"); + } else { + regex_str = X_("Launchkey MK4.*(DAW|MIDI 2|DA$)"); + } + + std::regex rx (regex_str, std::regex::extended); + + auto is_dawport = [&rx](string const &s) { + std::string pn = AudioEngine::instance()->get_hardware_port_name_by_name(s); + return std::regex_search (pn, rx); + }; + + auto pi = std::find_if (midi_inputs.begin(), midi_inputs.end(), is_dawport); + auto po = std::find_if (midi_outputs.begin (), midi_outputs.end (), is_dawport); + + if (pi == midi_inputs.end() || po == midi_inputs.end()) { + std::cerr << "daw port not found\n"; + return; + } + + if (!_daw_in->connected()) { + AudioEngine::instance()->connect (_daw_in->name(), *pi); + } + + if (!_daw_out->connected()) { + AudioEngine::instance()->connect (_daw_out->name(), *po); + } + + + connect_to_port_parser (*_daw_in_port); + + MIDI::Parser* p = _daw_in_port->parser(); + /* fader messages are controllers but always on channel 0xf */ + p->channel_controller[15].connect_same_thread (*this, boost::bind (&LaunchKey4::handle_midi_controller_message_chnF, this, _1, _2)); + + /* Connect DAW input port to event loop */ + + AsyncMIDIPort* asp; + + asp = dynamic_cast (_daw_in_port); + asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &MIDISurface::midi_input_handler), _daw_in_port)); + asp->xthread().attach (main_loop()->get_context()); +} + +int +LaunchKey4::ports_acquire () +{ + int ret = MIDISurface::ports_acquire (); + + if (!ret) { + _daw_in = AudioEngine::instance()->register_input_port (DataType::MIDI, string_compose (X_("%1 daw in"), port_name_prefix), true); + if (_daw_in) { + _daw_in_port = std::dynamic_pointer_cast(_daw_in).get(); + _daw_out = AudioEngine::instance()->register_output_port (DataType::MIDI, string_compose (X_("%1 daw out"), port_name_prefix), true); + } + if (_daw_out) { + _daw_out_port = std::dynamic_pointer_cast(_daw_out).get(); + return 0; + } + + ret = -1; + } + + return ret; +} + +void +LaunchKey4::ports_release () +{ + /* wait for button data to be flushed */ + MIDI::Port* daw_port = std::dynamic_pointer_cast(_daw_out).get(); + AsyncMIDIPort* asp; + asp = dynamic_cast (daw_port); + asp->drain (10000, 500000); + + { + Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock()); + AudioEngine::instance()->unregister_port (_daw_in); + AudioEngine::instance()->unregister_port (_daw_out); + } + + _daw_in.reset ((ARDOUR::Port*) 0); + _daw_out.reset ((ARDOUR::Port*) 0); + + MIDISurface::ports_release (); +} + +void +LaunchKey4::daw_write (const MidiByteArray& data) +{ + DEBUG_TRACE (DEBUG::Launchkey, string_compose ("daw write %1 %2\n", data.size(), data)); + _daw_out_port->write (&data[0], data.size(), 0); +} + +void +LaunchKey4::daw_write (MIDI::byte const * data, size_t size) +{ + +#ifndef NDEBUG + std::stringstream str; + + if (DEBUG_ENABLED(DEBUG::Launchkey)) { + str << hex; + for (size_t n = 0; n < size; ++n) { + str << (int) data[n] << ' '; + } + } +#endif + + DEBUG_TRACE (DEBUG::Launchkey, string_compose ("daw write %1 [%2]\n", size, str.str())); + _daw_out_port->write (data, size, 0); +} + +void +LaunchKey4::stripable_selection_changed () +{ + map_selection (); + + if (session->selection().first_selected_stripable()) { + set_display_target (GlobalTemporaryDisplay, 0, session->selection().first_selected_stripable()->name(), true); + } +} + +void +LaunchKey4::show_scene_ids () +{ + set_display_target (DAWPadFunctionDisplay, 0, string_compose ("Scenes %1 + %2", scroll_y_offset + 1, scroll_y_offset + 2), true); +} + +void +LaunchKey4::button_up () +{ + if (pad_function != Triggers) { + return; + } + + if (scroll_y_offset >= 1) { + scroll_y_offset -= 1; + show_scene_ids (); + } +} + +void +LaunchKey4::button_down() +{ + if (pad_function != Triggers) { + return; + } + + scroll_y_offset += 1; + show_scene_ids (); +} + +void +LaunchKey4::build_color_map () +{ + /* RGB values taken from using color picker on PDF of LP manual, page + * 10, but without zero (off) + */ + + static uint32_t novation_color_chart_left_side[] = { + 0xb3b3b3ff, + 0xddddddff, + 0xffffffff, + 0xffb3b3ff, + 0xff6161ff, + 0xdd6161ff, + 0xb36161ff, + 0xfff3d5ff, + 0xffb361ff, + 0xdd8c61ff, + 0xb37661ff, + 0xffeea1ff, + 0xffff61ff, + 0xdddd61ff, + 0xb3b361ff, + 0xddffa1ff, + 0xc2ff61ff, + 0xa1dd61ff, + 0x81b361ff, + 0xc2ffb3ff, + 0x61ff61ff, + 0x61dd61ff, + 0x61b361ff, + 0xc2ffc2ff, + 0x61ff8cff, + 0x61dd76ff, + 0x61b36bff, + 0xc2ffccff, + 0x61ffccff, + 0x61dda1ff, + 0x61b381ff, + 0xc2fff3ff, + 0x61ffe9ff, + 0x61ddc2ff, + 0x61b396ff, + 0xc2f3ffff, + 0x61eeffff, + 0x61c7ddff, + 0x61a1b3ff, + 0xc2ddffff, + 0x61c7ffff, + 0x61a1ddff, + 0x6181b3ff, + 0xa18cffff, + 0x6161ffff, + 0x6161ddff, + 0x6161b3ff, + 0xccb3ffff, + 0xa161ffff, + 0x8161ddff, + 0x7661b3ff, + 0xffb3ffff, + 0xff61ffff, + 0xdd61ddff, + 0xb361b3ff, + 0xffb3d5ff, + 0xff61c2ff, + 0xdd61a1ff, + 0xb3618cff, + 0xff7661ff, + 0xe9b361ff, + 0xddc261ff, + 0xa1a161ff, + }; + + static uint32_t novation_color_chart_right_side[] = { + 0x61b361ff, + 0x61b38cff, + 0x618cd5ff, + 0x6161ffff, + 0x61b3b3ff, + 0x8c61f3ff, + 0xccb3c2ff, + 0x8c7681ff, + /**/ + 0xff6161ff, + 0xf3ffa1ff, + 0xeefc61ff, + 0xccff61ff, + 0x76dd61ff, + 0x61ffccff, + 0x61e9ffff, + 0x61a1ffff, + /**/ + 0x8c61ffff, + 0xcc61fcff, + 0xcc61fcff, + 0xa17661ff, + 0xffa161ff, + 0xddf961ff, + 0xd5ff8cff, + 0x61ff61ff, + /**/ + 0xb3ffa1ff, + 0xccfcd5ff, + 0xb3fff6ff, + 0xcce4ffff, + 0xa1c2f6ff, + 0xd5c2f9ff, + 0xf98cffff, + 0xff61ccff, + /**/ + 0xff61ccff, + 0xf3ee61ff, + 0xe4ff61ff, + 0xddcc61ff, + 0xb3a161ff, + 0x61ba76ff, + 0x76c28cff, + 0x8181a1ff, + /**/ + 0x818cccff, + 0xccaa81ff, + 0xdd6161ff, + 0xf9b3a1ff, + 0xf9ba76ff, + 0xfff38cff, + 0xe9f9a1ff, + 0xd5ee76ff, + /**/ + 0x8181a1ff, + 0xf9f9d5ff, + 0xddfce4ff, + 0xe9e9ffff, + 0xe4d5ffff, + 0xb3b3b3ff, + 0xd5d5d5ff, + 0xf9ffffff, + /**/ + 0xe96161ff, + 0xe96161ff, + 0x81f661ff, + 0x61b361ff, + 0xf3ee61ff, + 0xb3a161ff, + 0xeec261ff, + 0xc27661ff + }; + + for (size_t n = 0; n < sizeof (novation_color_chart_left_side) / sizeof (novation_color_chart_left_side[0]); ++n) { + uint32_t color = novation_color_chart_left_side[n]; + /* Add 1 to account for missing zero at zero in the table */ + std::pair p (1 + n, color); + color_map.insert (p); + } + + for (size_t n = 0; n < sizeof (novation_color_chart_right_side) / sizeof (novation_color_chart_right_side[0]); ++n) { + uint32_t color = novation_color_chart_right_side[n]; + /* Add 40 to account for start offset number shown in page 10 of the LP manual */ + std::pair p (40 + n, color); + color_map.insert (p); + } +} + +int +LaunchKey4::find_closest_palette_color (uint32_t color) +{ + auto distance = std::numeric_limits::max(); + int index = -1; + + NearestMap::iterator n = nearest_map.find (color); + if (n != nearest_map.end()) { + return n->second; + } + + HSV hsv_c (color); + + for (auto const & c : color_map) { + + HSV hsv_p (c.second); + + double chr = M_PI * (hsv_c.h / 180.0); + double phr = M_PI * (hsv_p.h /180.0); + double t1 = (sin (chr) * hsv_c.s * hsv_c.v) - (sin (phr) * hsv_p.s* hsv_p.v); + double t2 = (cos (chr) * hsv_c.s * hsv_c.v) - (cos (phr) * hsv_p.s * hsv_p.v); + double t3 = hsv_c.v - hsv_p.v; + double d = (t1 * t1) + (t2 * t2) + (0.5 * (t3 * t3)); + + + if (d < distance) { + index = c.first; + distance = d; + } + } + + nearest_map.insert (std::pair (color, index)); + + return index; +} + +void +LaunchKey4::route_property_change (PropertyChange const & pc, int col) +{ + if (pc.contains (Properties::color)) { + map_triggerbox (col); + } + + + if (pc.contains (Properties::selected)) { + } +} + +void +LaunchKey4::fader_move (int which, int val) +{ + std::shared_ptr ac; + + if (which == 8) { + std::shared_ptr monitor = session->monitor_out(); + + if (monitor) { + ac = monitor->gain_control(); + } else { + std::shared_ptr master = session->master_out(); + if (!master) { + return; + } + ac = master->gain_control(); + } + + } else { + if (!stripable[which]) { + return; + } + + ac = stripable[which]->gain_control(); + } + + if (ac) { + gain_t gain = ARDOUR::slider_position_to_gain_with_max (val/127.0, ARDOUR::Config->get_max_gain()); + session->set_control (ac, gain, PBD::Controllable::NoGroup); + + char buf[16]; + snprintf (buf, sizeof (buf), "%.1f dB", accurate_coefficient_to_dB (gain)); + set_display_target (DisplayTarget (0x5 + which), 1, buf, true); + } +} + +void +LaunchKey4::automation_control_change (int n, std::weak_ptr wac) +{ + std::shared_ptr ac = wac.lock(); + if (!ac) { + return; + } + + MIDI::byte msg[3]; + msg[0] = 0xb4; + msg[1] = first_fader + n; + + switch (current_fader_bank) { + case VolumeFaders: + case SendAFaders: + case SendBFaders: + msg[2] = (MIDI::byte) (ARDOUR::gain_to_slider_position_with_max (ac->get_value(), ARDOUR::Config->get_max_gain()) * 127.0); + break; + case PanFaders: + msg[2] = (MIDI::byte) (ac->get_value() * 127.0); + break; + default: + break; + } + + daw_write (msg, 3); +} + +void +LaunchKey4::encoder (int which, int step) +{ + switch (encoder_mode) { + case EncoderPlugins: + encoder_plugin (which, step); + break; + case EncoderMixer: + encoder_mixer (which, step); + break; + case EncoderSendA: + encoder_senda (which, step); + break; + case EncoderTransport: + encoder_transport (which, step); + break; + } +} + +void +LaunchKey4::plugin_selected (std::weak_ptr wpi) +{ + std::shared_ptr pi (wpi.lock()); + if (!pi) { + return; + } + + current_plugin = pi->plugin(); + uint32_t n = 0; + + while (n < 24) { + + Evoral::ParameterDescriptor pd; + Evoral::Parameter param (PluginAutomation, 0, n); + + std::shared_ptr ac = pi->automation_control (param, false); + if (ac) { + controls[n] = ac; + } else { + break; + } + + ++n; + } + + num_plugin_controls = n; + + while (n < 24) { + controls[n].reset (); + ++n; + } + + if (encoder_mode == EncoderPlugins) { + label_encoders (); + /* light up/down arrows appropriately */ + set_encoder_bank (encoder_bank); + } +} + +void +LaunchKey4::show_encoder_value (int n, std::shared_ptr plugin, int control, std::shared_ptr ac, bool display) +{ + bool ok; + std::string str; + uint32_t p = plugin->nth_parameter (control, ok); + + if (!ok || !plugin->print_parameter (p, str)) { + char buf[32]; + double val = ac->get_value (); + snprintf (buf, sizeof (buf), "%.2f", val); + set_display_target (DisplayTarget (0x15 + n), 2, buf, display); + return; + } + + set_display_target (DisplayTarget (0x15 + n), 2, str, true); +} + +void +LaunchKey4::setup_screen_for_encoder_plugins () +{ + uint32_t n = 0; + + std::shared_ptr plugin = current_plugin.lock(); + + if (plugin) { + while (n < 8) { + uint32_t ctrl = (encoder_bank * 8) + n; + + std::shared_ptr ac = controls[ctrl].lock(); + bool ok; + + if (!ac) { + break; + } + int p = plugin->nth_parameter (n, ok); + if (!ok) { + break; + } + + std::string label = plugin->parameter_label (p); + + set_display_target (DisplayTarget (0x15+n), 0, plugin->name(), (n == 0)); + set_display_target (DisplayTarget (0x15+n), 1, label,(n == 0)); + show_encoder_value (n, plugin, ctrl, ac, (n == 0)); + ++n; + } + } + + while (n < 8) { + set_display_target (DisplayTarget (0x15+n), 0, plugin->name(), (n == 0)); + set_display_target (DisplayTarget (0x15+n), 1, "--", (n == 0)); + set_display_target (DisplayTarget (0x15+n), 2, string(), (n == 0)); + ++n; + } +} + +void +LaunchKey4::encoder_plugin (int which, int step) +{ + std::shared_ptr plugin (current_plugin.lock()); + if (!plugin) { + return; + } + + int control = which + (encoder_bank * 8); + std::shared_ptr ac (controls[control].lock()); + + if (!ac) { + return; + } + + double val = ac->internal_to_interface (ac->get_value()); + val += step/127.0; + ac->set_value (ac->interface_to_internal (val), PBD::Controllable::NoGroup); + + show_encoder_value (which, plugin, control, ac, true); +} + +void +LaunchKey4::encoder_mixer (int which, int step) +{ + switch (encoder_bank) { + case 0: + encoder_level (which, step); + break; + case 1: + encoder_pan (which, step); + break; + default: + break; + } +} + +void +LaunchKey4::encoder_pan (int which, int step) +{ + if (!stripable[which]) { + return; + } + + std::shared_ptr ac (stripable[which]->pan_azimuth_control()); + + if (!ac) { + return; + } + + double val = ac->internal_to_interface (ac->get_value()); + session->set_control (ac, ac->interface_to_internal (val - (step/127.0)), Controllable::NoGroup); + + char buf[64]; + snprintf (buf, sizeof (buf), _("L:%3d R:%3d"), (int) rint (100.0 * (1.0 - val)), (int) rint (100.0 * val)); + set_display_target (DisplayTarget (0x15 + which), 2, buf, true); +} + + +void +LaunchKey4::encoder_level (int which, int step) +{ + if (!stripable[which]) { + return; + } + + std::shared_ptr gc (stripable[which]->gain_control()); + + if (!gc) { + return; + } + + gain_t gain; + + if (shift_pressed) { + gain = gc->get_value(); + } else { + double pos = ARDOUR::gain_to_slider_position_with_max (gc->get_value(), ARDOUR::Config->get_max_gain()); + pos += (step/127.0); + gain = ARDOUR::slider_position_to_gain_with_max (pos, ARDOUR::Config->get_max_gain()); + session->set_control (gc, gain, Controllable::NoGroup); + } + + char buf[16]; + snprintf (buf, sizeof (buf), "%.1f dB", accurate_coefficient_to_dB (gain)); + set_display_target (DisplayTarget (0x15 + which), 2, buf, true); +} + +void +LaunchKey4::encoder_senda (int which, int step) +{ + std::shared_ptr s = session->selection().first_selected_stripable(); + if (!s) { + return; + } + + std::shared_ptr target_bus = std::dynamic_pointer_cast (s); + if (!target_bus) { + return; + } + + if (!stripable[which]) { + return; + } + + std::shared_ptr route = std::dynamic_pointer_cast (stripable[which]); + if (!route) { + return; + } + + std::shared_ptr send = std::dynamic_pointer_cast (route->internal_send_for (target_bus)); + if (!send) { + return; + } + + std::shared_ptr gc = send->gain_control(); + if (!gc) { + return; + } + gain_t gain; + + if (shift_pressed) { + /* Just display current value */ + gain = gc->get_value(); + } else { + double pos = ARDOUR::gain_to_slider_position_with_max (gc->get_value(), ARDOUR::Config->get_max_gain()); + pos += (step/127.0); + gain = ARDOUR::slider_position_to_gain_with_max (pos, ARDOUR::Config->get_max_gain()); + session->set_control (gc, gain, Controllable::NoGroup); + } + + char buf[16]; + snprintf (buf, sizeof (buf), "%.1f dB", accurate_coefficient_to_dB (gain)); + set_display_target (DisplayTarget (0x15 + which), 1, string_compose ("> %1", send->target_route()->name()), true); + set_display_target (DisplayTarget (0x15 + which), 2, buf, true); +} + +void +LaunchKey4::encoder_transport (int which, int step) +{ + switch (which) { + case 0: + transport_shuttle (step); + break; + case 1: + zoom (step); + break; + case 2: + loop_start_move (step); + break; + case 3: + loop_end_move (step); + break; + case 4: + jump_to_marker (step); + break; + case 5: + break; + case 6: + break; + case 7: + break; + } +} + +void +LaunchKey4::transport_shuttle (int step) +{ + using namespace Temporal; + + /* 1 step == 1/10th current page */ + timepos_t pos (session->transport_sample()); + + if (pos == 0 && step < 0) { + return; + } + + Beats b = pos.beats(); + + if (step > 0) { + b = b.round_up_to_beat (); + b += Beats (1, 0) * step; + } else { + b = b.round_down_to_beat (); + b += Beats (1, 0) * step; // step is negative, so add + if (b < Beats()) { + b = Beats(); + } + } + + BBT_Time bbt = TempoMap::use()->bbt_at (b); + std::stringstream str; + str << bbt; + + set_display_target (DisplayTarget (0x15), 2, str.str(), true); + + session->request_locate (timepos_t (b).samples()); +} + +void +LaunchKey4::zoom (int step) +{ + if (step > 0) { + while (step--) { + temporal_zoom_in (); + } + } else { + while (step++ < 0) { + temporal_zoom_out (); + } + } + set_display_target (DisplayTarget (0x15 + 1), 2, string(), true); +} + +void +LaunchKey4::loop_start_move (int step) +{ + using namespace Temporal; + + Location* l = session->locations()->auto_loop_location (); + BBT_Offset dur; + + if (!l) { + /* XXX NEEDS WRAPPING IN REVERSIBLE COMMAND */ + timepos_t ph (session->transport_sample()); + timepos_t beat_later ((ph.beats() + Beats (1,0)).round_to_beat()); + + Location* loc = new Location (*session, timepos_t (ph.beats()), beat_later, _("Loop"), Location::IsAutoLoop); + session->locations()->add (loc, true); + session->set_auto_loop_location (loc); + + dur = BBT_Offset (0, 1, 0); + + } else { + timepos_t start = l->start(); + start = start.beats() + Beats (step, 0); + if (start.is_zero() || start.is_negative()) { + return; + } + l->set_start (start); + + TempoMap::SharedPtr map (TempoMap::use()); + BBT_Time bbt_start = map->bbt_at (start); + BBT_Time bbt_end = map->bbt_at (l->end()); + + dur = bbt_delta (bbt_end, bbt_start); + } + + std::stringstream str; + str << dur; + set_display_target (DisplayTarget (0x15 + 2), 2, str.str(), true); +} + +void +LaunchKey4::loop_end_move (int step) +{ + using namespace Temporal; + + Location* l = session->locations()->auto_loop_location (); + BBT_Offset dur; + + if (!l) { + /* XXX NEEDS WRAPPING IN REVERSIBLE COMMAND */ + timepos_t ph (session->transport_sample()); + timepos_t beat_later ((ph.beats() + Beats (1,0)).round_to_beat()); + + Location* loc = new Location (*session, timepos_t (ph.beats()), beat_later, _("Loop"), Location::IsAutoLoop); + session->locations()->add (loc, true); + session->set_auto_loop_location (loc); + dur = BBT_Offset (0, 1, 0); + } else { + timepos_t end = l->end(); + end = end.beats() + Beats (step, 0); + if (end.is_zero() || end.is_negative()) { + return; + } + l->set_end (end); + + TempoMap::SharedPtr map (TempoMap::use()); + BBT_Time bbt_start = map->bbt_at (l->start()); + BBT_Time bbt_end = map->bbt_at (end); + + dur = bbt_delta (bbt_end, bbt_start); + } + + std::stringstream str; + str << dur; + set_display_target (DisplayTarget (0x15 + 3), 2, str.str(), true); +} + +void +LaunchKey4::jump_to_marker (int step) +{ + timepos_t pos; + Location::Flags noflags = Location::Flags (0); + Location* loc; + + if (step > 0) { + pos = session->locations()->first_mark_after_flagged (timepos_t (session->audible_sample()+1), true, noflags, noflags, noflags, &loc); + + if (pos == timepos_t::max (Temporal::AudioTime)) { + return; + } + + } else { + pos = session->locations()->first_mark_before_flagged (timepos_t (session->audible_sample()), true, noflags, noflags, noflags, &loc); + + //handle the case where we are rolling, and we're less than one-half second past the mark, we want to go to the prior mark... + if (session->transport_rolling()) { + if ((session->audible_sample() - pos.samples()) < session->sample_rate()/2) { + timepos_t prior = session->locations()->first_mark_before (pos); + pos = prior; + } + } + + if (pos == timepos_t::max (Temporal::AudioTime)) { + return; + } + } + + session->request_locate (pos.samples()); + + set_display_target (DisplayTarget (0x15+4), 2, loc->name(), true); +} + +void +LaunchKey4::set_pad_function (PadFunction f) +{ + MIDI::byte msg[3]; + std::string str; + + /* make the LK forget about any currently lit pads, because we overload + mode 0x2 and it gets confusing when it tries to restore lighting. + */ + all_pads (0x5); + all_pads_out (); + + msg[0] = 0xb6; + msg[1] = 0x40; /* set pad layout */ + + switch (f) { + case MuteSolo: + str = "Mute/Solo"; + break; + case Triggers: + str = "Cues & Scenes"; + break; + } + + pad_function = f; + + if (pad_function == Triggers) { + map_triggers (); + } else if (pad_function == MuteSolo) { + map_mute_solo (); + } + + /* Turn up/down arrows on/off depending on pad mode, also scene mode */ + + msg[0] = 0xb0; + msg[2] = (pad_function == Triggers ? 0x3 : 0x0); + + msg[1] = 0x6a; /* upper */ + daw_write (msg, 3); + msg[1] = 0x6b; /* lower */ + daw_write (msg, 3); + msg[1] = 0x68; /* scene */ + daw_write (msg, 3); + + configure_display (DAWPadFunctionDisplay, 0x1); + set_display_target (DAWPadFunctionDisplay, 0, str, true); +} + +void +LaunchKey4::select_display_target (DisplayTarget dt) +{ + MidiByteArray msg; + + msg.push_back (0xf0); + msg.push_back (0x0); + msg.push_back (0x20); + msg.push_back (0x29); + msg.push_back ((device_pid >> 8) & 0x7f); + msg.push_back (device_pid & 0x7f); + msg.push_back (0x4); + msg.push_back (dt); + msg.push_back (0x7f); + msg.push_back (0xf7); + + daw_write (msg); +} + +void +LaunchKey4::set_plugin_encoder_name (int encoder, int field, std::string const & str) +{ + set_display_target (PluginPotMode, field, str, true); +} + +void +LaunchKey4::set_display_target (DisplayTarget dt, int field, std::string const & str, bool display) +{ + MidiByteArray msg; + + msg.push_back (0xf0); + msg.push_back (0x0); + msg.push_back (0x20); + msg.push_back (0x29); + msg.push_back ((device_pid >> 8) & 0x7f); + msg.push_back (device_pid & 0x7f); + msg.push_back (0x6); + msg.push_back (dt); + msg.push_back (display ? ((1<<6) | (field & 0x7f)) : (field & 0x7f)); + + for (auto c : str) { + msg.push_back (c & 0x7f); + } + + msg.push_back (0xf7); + + daw_write (msg); + write (msg); +} + +void +LaunchKey4::configure_display (DisplayTarget target, int config) +{ + MidiByteArray msg (9, 0xf0, 0x00, 0x29, 0xff, 0xff, 0x04, 0xff, 0xff, 0xf7); + + msg[3] = (device_pid >> 8) & 0x7f; + msg[4] = device_pid & 0x7f; + + msg[6] = target; + msg[7] = config & 0x7f; + + daw_write (msg); +} + +void +LaunchKey4::function_press () +{ + switch (pad_function) { + case MuteSolo: + set_pad_function (Triggers); + break; + case Triggers: + set_pad_function (MuteSolo); + break; + } +} + +void +LaunchKey4::undo_press () +{ + if (shift_pressed) { + redo (); + } else { + undo (); + } +} + +void +LaunchKey4::button_press (int n) +{ + std::shared_ptr ac; + + if (!stripable[n]) { + return; + } + + switch (button_mode) { + case ButtonsSelect: + session->selection().select_stripable_and_maybe_group (stripable[n], SelectionSet); + break; + case ButtonsRecEnable: + ac = stripable[n]->rec_enable_control(); + if (ac) { + ac->set_value (!ac->get_value(), Controllable::NoGroup); + } + break; + } +} + +void +LaunchKey4::button_release (int n) +{ +} + +void +LaunchKey4::solo_changed () +{ + map_mute_solo (); +} + +void +LaunchKey4::mute_changed (uint32_t n) +{ + show_mute (n); +} + +void +LaunchKey4::rec_enable_changed (uint32_t n) +{ + show_rec_enable (n); +} + +void +LaunchKey4::switch_bank (uint32_t base) +{ + stripable_connections.drop_connections (); + + /* work backwards so we can tell if we should actually switch banks */ + + std::shared_ptr s[8]; + + for (int n = 0; n < 8; ++n) { + s[n] = session->get_remote_nth_stripable (base+n, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA)); + } + + if (!s[0]) { + /* not even the first stripable exists, do nothing */ + return; + } + + for (int n = 0; n < 8; ++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 (&LaunchKey4::switch_bank, this, bank_start), this); + stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchKey4::stripable_property_change, this, _1, n), this); + stripable[n]->mute_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchKey4::mute_changed, this, n), this); + std::shared_ptr ac = stripable[n]->rec_enable_control(); + if (ac) { + ac->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchKey4::rec_enable_changed, this, n), this); + } + } + + /* Set fader "title" fields to show current bank */ + + for (int n = 0; n < 8; ++n) { + if (stripable[n]) { + set_display_target (DisplayTarget (0x5 + n), 0, stripable[n]->name(), true); + } else { + set_display_target (DisplayTarget (0x5 + n), 0, string(), true); + } + } + + if (session->monitor_out()) { + set_display_target (DisplayTarget (0x5 + 8), 0, session->monitor_out()->name(), true); + } else if (session->master_out()) { + set_display_target (DisplayTarget (0x5 + 8), 0, session->master_out()->name(), true); + } + } + + if (button_mode == ButtonsSelect) { + map_selection (); + } else { + map_rec_enable (); + } + + switch (pad_function) { + case Triggers: + map_triggers (); + break; + case MuteSolo: + map_mute_solo (); + break; + default: + break; + } + + if (encoder_mode != EncoderTransport) { + set_encoder_titles_to_route_names (); + } +} + +void +LaunchKey4::stripable_property_change (PropertyChange const& what_changed, uint32_t which) +{ + if (what_changed.contains (Properties::color)) { + show_selection (which); + } + + if (what_changed.contains (Properties::hidden)) { + switch_bank (bank_start); + } + + if (what_changed.contains (Properties::selected)) { + + if (!stripable[which]) { + return; + } + } + +} + +void +LaunchKey4::stripables_added () +{ + /* reload current bank */ + switch_bank (bank_start); +} + +void +LaunchKey4::button_right () +{ + if (pad_function == Triggers) { + switch_bank (bank_start + 1); + scroll_x_offset = bank_start; + } else { + switch_bank (bank_start + 8); + } + std::cerr << "rright to " << bank_start << std::endl; + + if (stripable[0]) { + set_display_target (GlobalTemporaryDisplay, 0, stripable[0]->name(), true); + } +} + +void +LaunchKey4::button_left () +{ + if (pad_function == Triggers) { + if (bank_start > 0) { + switch_bank (bank_start - 1); + scroll_x_offset = bank_start; + } + } else { + if (bank_start > 7) { + switch_bank (bank_start - 8); + } + } + + std::cerr << "left to " << bank_start << std::endl; + + if (stripable[0]) { + set_display_target (GlobalTemporaryDisplay, 0, stripable[0]->name(), true); + } +} + +void +LaunchKey4::toggle_button_mode () +{ + switch (button_mode) { + case ButtonsSelect: + button_mode = ButtonsRecEnable; + map_rec_enable (); + break; + case ButtonsRecEnable: + button_mode = ButtonsSelect; + map_selection (); + break; + } + + MIDI::byte msg[3]; + msg[0] = 0xb0; + msg[1] = Button9; + + if (button_mode == ButtonsSelect) { + msg[2] = 0x3; /* brght white */ + } else { + msg[2] = 0x5; /* red */ + } + + daw_write (msg, 3); +} + +void +LaunchKey4::map_selection () +{ + for (int n = 0; n < 8; ++n) { + show_selection (n); + } +} + +void +LaunchKey4::show_selection (int n) +{ + const int first_button = 0x25; + const int selection_color = 0xd; /* bright yellow */ + + if (!stripable[n]) { + light_button (first_button + n, Off, 0); + } else if (stripable[n]->is_selected()) { + light_button (first_button + n, Solid, selection_color); + } else { + light_button (first_button + n, Solid, find_closest_palette_color (stripable[n]->presentation_info().color ())); + } +} + +void +LaunchKey4::map_mute_solo () +{ + for (int n = 0; n < 8; ++n) { + show_mute (n); + show_solo (n); + } +} + +void +LaunchKey4::show_mute (int n) +{ + if (!stripable[n]) { + return; + } + + std::shared_ptr mc (stripable[n]->mute_control()); + if (!mc) { + return; + } + MIDI::byte msg[3]; + msg[0] = 0x90; + msg[1] = 0x60 + n; + if (mc->muted_by_self()) { + // std::cerr << stripable[n]->name() << " muted by self\n"; + msg[2] = 0xd; /* bright yellow */ + } else if (mc->muted_by_others_soloing() || mc->muted_by_masters()) { + // std::cerr << stripable[n]->name() << " muted by others\n"; + msg[2] = 0x49; /* soft yellow */ + } else { + // std::cerr << stripable[n]->name() << " not muted\n"; + msg[2] = 0x0;; + } + + daw_write (msg, 3); +} + +void +LaunchKey4::show_solo (int n) +{ + if (!stripable[n]) { + return; + } + + std::shared_ptr sc (stripable[n]->solo_control()); + if (!sc) { + return; + } + MIDI::byte msg[3]; + msg[0] = 0x90; + msg[1] = 0x70 + n; + if (sc->soloed_by_self_or_masters()) { + msg[2] = 0x15; /* bright green */ + } else if (sc->soloed_by_others()) { + msg[2] = 0x4b; /* soft green */ + } else { + msg[2] = 0x0; + } + + daw_write (msg, 3); +} + +void +LaunchKey4::light_button (int which, LightingMode mode, int color_index) +{ + MIDI::byte msg[3]; + + msg[1] = which; + + switch (mode) { + case Off: + msg[0] = 0xb0; + msg[2] = 0x0; + break; + + case Solid: + msg[0] = 0xb0; + msg[2] = color_index & 0x7f; + break; + + case Flash: + msg[0] = 0xb1; + msg[2] = color_index & 0x7f; + break; + + case Pulse: + msg[0] = 0xb2; + msg[2] = color_index & 0x7f; + break; + } + + daw_write (msg, 3); +} + + +void +LaunchKey4::light_pad (int pid, LightingMode mode, int color_index) +{ + MIDI::byte msg[3]; + + msg[1] = pid; + + switch (mode) { + case Off: + msg[0] = 0x90; + msg[2] = 0x0; + break; + + case Solid: + msg[0] = 0x90; + msg[2] = color_index & 0x7f; + break; + + case Flash: + msg[0] = 0x91; + msg[2] = color_index & 0x7f; + break; + + case Pulse: + msg[0] = 0x92; + msg[2] = color_index & 0x7f; + break; + } + + daw_write (msg, 3); +} + +void +LaunchKey4::unlight_pad (int pad_id) +{ + light_pad (pad_id, Solid, 0x0); +} + +void +LaunchKey4::set_encoder_bank (int n) +{ + bool light_up_arrow = false; + bool light_down_arrow = false; + + encoder_bank = n; + + /* Ordering: + + 9 + 1 + 2 + */ + + if (encoder_mode == EncoderPlugins) { + + switch (encoder_bank) { + case 0: + if (num_plugin_controls > 8) { + light_down_arrow = true; + } + break; + case 1: + if (num_plugin_controls > 8) { + light_up_arrow = true; + } + if (num_plugin_controls > 16) { + light_down_arrow = true; + } + break; + case 2: + if (num_plugin_controls > 16) { + light_up_arrow = true; + } + break; + } + + } else if (encoder_mode == EncoderMixer) { + + switch (encoder_bank) { + case 0: + light_down_arrow = true; + break; + case 1: + light_down_arrow = true; + light_up_arrow = true; + break; + case 2: + light_up_arrow = true; + break; + default: + return; + } + } + + MIDI::byte msg[6]; + /* Color doesn't really matter, these LEDs are single-color. Just turn + it on or off. + */ + const int color_index = 0x3; + + msg[0] = 0xb0; + msg[1] = 0x33; /* top */ + msg[3] = 0xb0; + msg[4] = 0x34; /* bottom */ + + if (light_up_arrow) { + msg[2] = color_index; + } else { + msg[2] = 0x0; + } + + if (light_down_arrow) { + msg[5] = color_index; + } else { + msg[5] = 0x0; + } + + /* Stupid device doesn't seem to like both messages "at once" */ + daw_write (msg, 3); + daw_write (&msg[3], 3); + + label_encoders (); +} + +void +LaunchKey4::label_encoders () +{ + std::shared_ptr plugin (current_plugin.lock()); + + switch (encoder_mode) { + case EncoderMixer: + case EncoderSendA: + set_encoder_titles_to_route_names (); + switch (encoder_bank) { + case 0: + for (int n = 0; n < 8; ++n) { + set_display_target (DisplayTarget (0x15 + n), 1, "Level", false); + } + set_display_target (GlobalTemporaryDisplay, 0, "Levels", true); + break; + case 1: + for (int n = 0; n < 8; ++n) { + set_display_target (DisplayTarget (0x15 + n), 1, "Pan", false); + } + set_display_target (GlobalTemporaryDisplay, 0, "Panning", true); + break; + default: + break; + } + break; + case EncoderPlugins: + setup_screen_for_encoder_plugins (); + break; + case EncoderTransport: + set_display_target (DisplayTarget (0x15), 1, "Shuttle", true); + set_display_target (DisplayTarget (0x16), 1, "Zoom", true); + set_display_target (DisplayTarget (0x17), 1, "Loop Start", true); + set_display_target (DisplayTarget (0x18), 1, "Loop End", true); + set_display_target (DisplayTarget (0x19), 1, "Jump to Marker", true); + set_display_target (DisplayTarget (0x1a), 1, string(), true); + set_display_target (DisplayTarget (0x1b), 1, string(), true); + set_display_target (DisplayTarget (0x1c), 1, string(), true); + for (int n = 0; n < 8; ++n) { + set_display_target (DisplayTarget (0x15 + n), 0, "Transport", true); + } + set_display_target (GlobalTemporaryDisplay, 0, "Transport", true); + break; + } +} + +void +LaunchKey4::set_encoder_mode (EncoderMode m) +{ + encoder_mode = m; + set_encoder_bank (0); + + /* device firmware reset to continuous controller mode, so switch back + * if (ev->controller_to encoders + */ + + use_encoders (true); + label_encoders (); +} + +void +LaunchKey4::set_encoder_titles_to_route_names () +{ + /* Set encoder "title" fields to show current bank */ + bool first = true; + + for (int n = 0; n < 8; ++n) { + if (stripable[n]) { + set_display_target (DisplayTarget (0x15 + n), 0, stripable[n]->name(), first); + first = false; + } else { + set_display_target (DisplayTarget (0x15 + n), 0, string(), true); + } + } +} + +void +LaunchKey4::in_msecs (int msecs, std::function func) +{ + Glib::RefPtr timeout = Glib::TimeoutSource::create (msecs); // milliseconds + timeout->connect (sigc::bind_return (func, false)); + timeout->attach (main_loop()->get_context()); +} + +void +LaunchKey4::scene_press () +{ + if (shift_pressed) { + trigger_stop_all (true); /* immediate stop */ + } else { + trigger_cue_row (scroll_y_offset); + } +} diff --git a/libs/surfaces/launchkey_4/launchkey_4.h b/libs/surfaces/launchkey_4/launchkey_4.h new file mode 100644 index 0000000000..68d2b412f0 --- /dev/null +++ b/libs/surfaces/launchkey_4/launchkey_4.h @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2016-2018 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __ardour_lk4_h__ +#define __ardour_lk4_h__ + +#include +#include +#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 "gtkmm2ext/colors.h" + +#include "midi_surface/midi_byte_array.h" +#include "midi_surface/midi_surface.h" + +namespace MIDI { + class Parser; + class Port; +} + +namespace ARDOUR { + class AutomationControl; + class Plugin; + class PluginInsert; + class Port; + class MidiBuffer; + class MidiTrack; + class Trigger; +} + +#ifdef LAUNCHPAD_MINI +#define LAUNCHPAD_NAMESPACE LP_MINI +#else +#define LAUNCHPAD_NAMESPACE LP_X +#endif + +namespace ArdourSurface { namespace LAUNCHPAD_NAMESPACE { + +class LK4_GUI; + +class LaunchKey4 : public MIDISurface +{ + public: + /* use hex for these constants, because we'll see them (as note numbers + and CC numbers) in hex within MIDI messages when debugging. + */ + + enum ButtonID { + Button1 = 0x25, + Button2 = 0x26, + Button3 = 0x27, + Button4 = 0x28, + Button5 = 0x29, + Button6 = 0x2a, + Button7 = 0x2b, + Button8 = 0x2c, + Button9 = 0x2d, + + Volume = 0x0b, + Custom1 = 0x0c, + Custom2 = 0x0d, + Custom3 = 0x0e, + Custom4 = 0x0f, + PartA = 0x10, + PartB = 0x11, + Split = 0x12, + Layer = 0x13, + Shift = 0x13, + // Settings = 0x23, + TrackLeft = 0x67, + TrackRight =0x66, + Up = 0x6a, + Down = 0x6b, + CaptureMidi = 0x3, + Undo = 0x4d, + Quantize = 0x4b, + Metronome = 0x4c, + // Stop = 0x34 .. sends Stop + // Play = 0x36 .. sends Play + Play = 0x73, + Stop = 0x74, + RecEnable = 0x75, + Loop = 0x76, + Function = 0x69, + Scene = 0x68, + EncUp = 0x33, + EncDown = 0x44, + }; + + enum KnobID { + Knob1 = 0x55, + Knob2 = 0x56, + Knob3 = 0x57, + Knob4 = 0x58, + Knob5 = 0x59, + Knob6 = 0x5a, + Knob7 = 0x5b, + Knob8 = 0x5c, + }; + + LaunchKey4 (ARDOUR::Session&); + ~LaunchKey4 (); + + static bool available (); + static bool match_usb (uint16_t, uint16_t); + static bool probe (std::string&, std::string&); + + std::string input_port_name () const; + std::string output_port_name () const; + + bool has_editor () const { return true; } + void* get_gui () const; + void tear_down_gui (); + + int set_active (bool yn); + XMLNode& get_state() const; + int set_state (const XMLNode & node, int version); + + private: + enum FaderBank { + VolumeFaders, + PanFaders, + SendAFaders, + SendBFaders, + }; + + struct Pad { + + enum ColorMode { + Static = 0x0, + Flashing = 0x1, + Pulsing = 0x2 + }; + + typedef void (LaunchKey4::*PadMethod)(Pad&, int velocity); + + Pad (int pid, int xx, int yy) + : id (pid) + , x (xx) + , y (yy) + { + } + + Pad () : id (-1), x (-1), y (-1) + { + } + + + int id; + int x; + int y; + + sigc::connection timeout_connection; + }; + + void relax (Pad& p); + void relax (Pad&, int); + + std::set consumed; + + Pad pads[16]; + void build_pad_map (); + + typedef std::map ColorMap; + ColorMap color_map; + void build_color_map (); + int find_closest_palette_color (uint32_t); + + typedef std::map NearestMap; + NearestMap nearest_map; + + int begin_using_device (); + int stop_using_device (); + int device_acquire () { return 0; } + void device_release () { } + void run_event_loop (); + void stop_event_loop (); + + void finish_begin_using_device (); + + void stripable_selection_changed (); + void select_stripable (int col); + std::weak_ptr _current_pad_target; + + void handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes*); + void handle_midi_controller_message_chnF (MIDI::Parser&, MIDI::EventTwoBytes*); + void handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes*); + void handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes*); + void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count); + + MIDI::Port* _daw_in_port; + MIDI::Port* _daw_out_port; + std::shared_ptr _daw_in; + std::shared_ptr _daw_out; + + void port_registration_handler (); + int ports_acquire (); + void ports_release (); + void connect_daw_ports (); + + void daw_write (const MidiByteArray&); + void daw_write (MIDI::byte const *, size_t); + + void reconnect_for_programmer (); + void reconnect_for_session (); + + mutable LK4_GUI* _gui; + void build_gui (); + + void maybe_start_press_timeout (Pad& pad); + void start_press_timeout (Pad& pad); + bool long_press_timeout (int pad_id); + + void button_press (int button); + void button_release (int button); + + void trigger_property_change (PBD::PropertyChange, ARDOUR::Trigger*); + void trigger_pad_light (Pad& pad, std::shared_ptr r, ARDOUR::Trigger* t); + PBD::ScopedConnectionList trigger_connections; + + void display_session_layout (); + void transport_state_changed (); + void record_state_changed (); + + void map_selection (); + void map_mute_solo (); + void map_rec_enable (); + void map_triggers (); + + void map_triggerbox (int col); + + void route_property_change (PBD::PropertyChange const &, int x); + PBD::ScopedConnectionList route_connections; + + void fader_move (int which, int val); + void automation_control_change (int n, std::weak_ptr); + PBD::ScopedConnectionList control_connections; + FaderBank current_fader_bank; + bool revert_layout_on_fader_release; + + void use_encoders (bool); + void encoder (int which, int step); + void knob (int which, int value); + + int scroll_x_offset; + int scroll_y_offset; + + uint16_t device_pid; + + enum DisplayTarget { + StationaryDisplay = 0x20, + GlobalTemporaryDisplay = 0x21, + DAWPadFunctionDisplay = 0x22, + DawDrumrackModeDisplay = 0x23, + MixerPotMode = 0x24, + PluginPotMode = 0x25, + SendPotMode = 0x26, + TransportPotMode = 0x27, + FaderMode = 0x28, + }; + + void select_display_target (DisplayTarget dt); + void set_display_target (DisplayTarget dt, int field, std::string const &, bool display = false); + void configure_display (DisplayTarget dt, int config); + void set_plugin_encoder_name (int encoder, int field, std::string const &); + + void set_daw_mode (bool); + int mode_channel; + + enum PadFunction { + MuteSolo, + Triggers, + }; + + PadFunction pad_function; + void set_pad_function (PadFunction); + void pad_mute_solo (Pad&); + void pad_trigger (Pad&, int velocity); + void pad_release (Pad&); + + bool shift_pressed; + bool layer_pressed; + + void function_press (); + void undo_press (); + void metronome_press (); + void quantize_press (); + void button_left (); + void button_right (); + void button_down (); + void button_up (); + + /* stripables */ + + int32_t bank_start; + PBD::ScopedConnectionList stripable_connections; + std::shared_ptr stripable[8]; + void stripables_added (); + void stripable_property_change (PBD::PropertyChange const& what_changed, uint32_t which); + void switch_bank (uint32_t); + + void solo_changed (); + void mute_changed (uint32_t which); + void rec_enable_changed (uint32_t which); + + enum LightingMode { + Off, + Solid, + Flash, + Pulse, + }; + + void light_button (int which, LightingMode, int color_index); + void light_pad (int pid, LightingMode, int color_index); + + enum ButtonMode { + ButtonsRecEnable, + ButtonsSelect + }; + + ButtonMode button_mode; + + void toggle_button_mode (); + void show_selection (int which); + void show_rec_enable (int which); + void show_mute (int which); + void show_solo (int which); + + enum EncoderMode { + EncoderPlugins, + EncoderMixer, + EncoderSendA, + EncoderTransport + }; + + EncoderMode encoder_mode; + int encoder_bank; + void set_encoder_bank (int); + void set_encoder_mode (EncoderMode); + void set_encoder_titles_to_route_names (); + void setup_screen_for_encoder_plugins (); + void label_encoders (); + void show_encoder_value (int which, std::shared_ptr plugin, int control, std::shared_ptr ac, bool display); + + void encoder_plugin (int which, int step); + void encoder_mixer (int which, int step); + void encoder_pan (int which, int step); + void encoder_level (int which, int step); + void encoder_senda (int which, int step); + void encoder_transport (int which, int step); + + void transport_shuttle (int step); + void zoom (int step); + void loop_start_move (int step); + void loop_end_move (int step); + void jump_to_marker (int step); + + void light_pad (int pad_id, int color_index); + void unlight_pad (int pad_id); + void all_pads (int color_index); + void all_pads_out (); + + void show_scene_ids (); + void scene_press (); + + void in_msecs (int msecs, std::function func); + + std::weak_ptr controls[24]; + std::weak_ptr current_plugin; + void plugin_selected (std::weak_ptr); + uint32_t num_plugin_controls; +}; + + +} } /* namespaces */ + +#endif /* __ardour_lk4_h__ */ diff --git a/libs/surfaces/launchkey_4/launchkey_4_interface.cc b/libs/surfaces/launchkey_4/launchkey_4_interface.cc new file mode 100644 index 0000000000..b958da3d6b --- /dev/null +++ b/libs/surfaces/launchkey_4/launchkey_4_interface.cc @@ -0,0 +1,88 @@ +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include "pbd/error.h" + +#include "ardour/rc_configuration.h" + +#include "control_protocol/control_protocol.h" +#include "launchkey_4.h" + +#ifdef LAUNCHPAD_MINI +#define LAUNCHPAD_NAMESPACE LP_MINI +#else +#define LAUNCHPAD_NAMESPACE LP_X +#endif + +using namespace ARDOUR; +using namespace PBD; +using namespace ArdourSurface::LAUNCHPAD_NAMESPACE; + +static ControlProtocol* +new_lk4 (Session* s) +{ + LaunchKey4 * lk4 = nullptr; + + try { + lk4 = new LaunchKey4 (*s); + /* do not set active here - wait for set_state() */ + } + catch (std::exception & e) { + error << "Error instantiating LaunchKey 4 support: " << e.what() << endmsg; + delete lk4; + lk4 = nullptr; + } + + return lk4; +} + +static void +delete_lk4 (ControlProtocol* cp) +{ + try + { + delete cp; + } + catch ( std::exception & e ) + { + std::cout << "Exception caught trying to finalize LaunchKey 4 support: " << e.what() << std::endl; + } +} + +static bool +probe_lk4_midi_protocol () +{ + std::string i, o; + return LaunchKey4::probe (i, o); +} + + +static ControlProtocolDescriptor lk4_descriptor = { + /* name */ "Novation LaunchKey 4", + /* id */ "uri://ardour.org/surfaces/launchkey4:0", + /* module */ 0, + /* available */ 0, + /* probe_port */ probe_lk4_midi_protocol, + /* match usb */ 0, // LaunchKey4::match_usb, + /* initialize */ new_lk4, + /* destroy */ delete_lk4, +}; + +extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () { return &lk4_descriptor; } diff --git a/libs/surfaces/launchkey_4/wscript b/libs/surfaces/launchkey_4/wscript new file mode 100644 index 0000000000..8397377cd2 --- /dev/null +++ b/libs/surfaces/launchkey_4/wscript @@ -0,0 +1,52 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import os + +lpxm_sources = [ + 'launchkey_4.cc', + 'gui.cc', +] + +def options(opt): + pass + +def configure(conf): + autowaf.check_pkg(conf, 'pangomm-1.4', uselib_store='PANGOMM', atleast_version='1.4', mandatory=True) + autowaf.check_pkg(conf, 'cairomm-1.0', uselib_store='CAIROMM', atleast_version='1.8.4', mandatory=True) + + +def build(bld): +# obj = bld(features = 'cxx cxxshlib') +# obj.source = list(lpxm_sources) +# obj.source += [ 'launchkey_4_interface.cc' ] +# obj.defines = [ 'PACKAGE="ardour_launchkey_mini"' ] +# obj.defines += [ 'ARDOURSURFACE_DLL_EXPORTS' ] +# obj.defines += [ 'LAUNCHKEY_MINI' ] +# obj.includes = ['.', ] +# obj.name = 'libardour_launchkey_mini' +# obj.target = 'ardour_launchkey_mini' +# obj.uselib = 'CAIROMM PANGOMM USB SIGCPP XML OSX' +# obj.use = 'libardour libardour_cp libardour_midisurface libgtkmm2ext libpbd libevoral libcanvas libtemporal' +# obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces') +# if bld.is_defined('YTK'): +# obj.use += ' libytkmm' +# obj.uselib += ' GLIBMM GIOMM' +# else: +# obj.uselib += ' GTKMM' + + obj = bld(features = 'cxx cxxshlib') + obj.source = list(lpxm_sources) + obj.source += [ 'launchkey_4_interface.cc' ] + obj.defines = [ 'PACKAGE="ardour_launchpad_x"' ] + obj.defines += [ 'ARDOURSURFACE_DLL_EXPORTS' ] + obj.includes = ['.', ] + obj.name = 'libardour_launchkey_4' + obj.target = 'ardour_launchkey_4' + obj.uselib = 'CAIROMM PANGOMM USB SIGCPP XML OSX' + obj.use = 'libardour libardour_cp libardour_midisurface libgtkmm2ext libpbd libevoral libcanvas libtemporal' + obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces') + if bld.is_defined('YTK'): + obj.use += ' libytkmm' + obj.uselib += ' GLIBMM GIOMM' + else: + obj.uselib += ' GTKMM' diff --git a/libs/surfaces/wscript b/libs/surfaces/wscript index b0ca408cdb..5a5fe03564 100644 --- a/libs/surfaces/wscript +++ b/libs/surfaces/wscript @@ -22,7 +22,8 @@ children = [ 'osc', 'console1', 'launchpad_pro', - 'launchpad_x' + 'launchpad_x', + 'launchkey_4' ] def options(opt): @@ -81,6 +82,7 @@ def build(bld): bld.recurse('console1') bld.recurse('launchpad_pro') bld.recurse('launchpad_x') + bld.recurse('launchkey_4') if bld.is_defined('BUILD_WIIMOTE'): bld.recurse('wiimote')