Add Launch Control XL control surface support

This commit is contained in:
Térence Clastres 2018-08-07 03:41:12 +02:00
parent f4c1166651
commit 8c7a1e004b
14 changed files with 2693 additions and 3 deletions

View file

@ -13,7 +13,7 @@ export GTK2_RC_FILES=/nonexistent
# can find all the components. # 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_PANNER_PATH=$libs/panners
export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:. export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:.
export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:. export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:.

View file

@ -87,9 +87,9 @@ namespace PBD {
LIBARDOUR_API extern DebugBits VCA; LIBARDOUR_API extern DebugBits VCA;
LIBARDOUR_API extern DebugBits Push2; LIBARDOUR_API extern DebugBits Push2;
LIBARDOUR_API extern DebugBits US2400; LIBARDOUR_API extern DebugBits US2400;
LIBARDOUR_API extern DebugBits LaunchControlXL;
} }
} }
#endif /* __ardour_debug_h__ */ #endif /* __ardour_debug_h__ */

View file

@ -84,3 +84,4 @@ PBD::DebugBits PBD::DEBUG::CC121 = PBD::new_debug_bit ("cc121");
PBD::DebugBits PBD::DEBUG::VCA = PBD::new_debug_bit ("vca"); PBD::DebugBits PBD::DEBUG::VCA = PBD::new_debug_bit ("vca");
PBD::DebugBits PBD::DEBUG::Push2 = PBD::new_debug_bit ("push2"); PBD::DebugBits PBD::DEBUG::Push2 = PBD::new_debug_bit ("push2");
PBD::DebugBits PBD::DEBUG::US2400 = PBD::new_debug_bit ("us2400"); PBD::DebugBits PBD::DEBUG::US2400 = PBD::new_debug_bit ("us2400");
PBD::DebugBits PBD::DEBUG::LaunchControlXL = PBD::new_debug_bit("launchcontrolxl");

View file

@ -0,0 +1,487 @@
/*
Copyright (C) 2016 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <algorithm>
#include "ardour/debug.h"
#include "ardour/mute_control.h"
#include "ardour/session.h"
#include "ardour/solo_control.h"
#include "launch_control_xl.h"
using namespace ArdourSurface;
using namespace ARDOUR;
using namespace PBD;
using std::cerr;
void
LaunchControlXL::build_maps ()
{
/* Knobs */
Knob* knob;
#define MAKE_KNOB(i,cc, index, color) \
knob = new Knob ((i), (cc), (index), (color), (*this)); \
cc_knob_map.insert (std::make_pair (knob->controller_number(), knob)); \
id_knob_map.insert (std::make_pair (knob->id(), knob))
for (uint8_t n = 0; n < 8; ++n) {
MAKE_KNOB (static_cast<KnobID>(n), (n + 13), n, LEDColor::RedFull);
MAKE_KNOB (static_cast<KnobID>(n + 8), (n + 29), (n + 8), LEDColor::GreenFull);
MAKE_KNOB (static_cast<KnobID>(n + 16), (n + 49), (n + 16), LEDColor::Yellow);
}
/* Faders */
Fader* fader;
#define MAKE_FADER(i,cc) \
fader = new Fader ((i), (cc)); \
cc_fader_map.insert (std::make_pair (fader->controller_number(), fader)); \
id_fader_map.insert (std::make_pair (fader->id(), fader))
for (uint8_t n = 0; n < 8; ++n) {
MAKE_FADER (static_cast<FaderID>(n), (n + 77) );
}
/* Buttons */
ControllerButton *controller_button;
NoteButton *note_button;
#define MAKE_TRACK_BUTTON_PRESS(i,nn,index,color,p) \
note_button = new TrackButton ((i), (nn), (index), (color), (p), (*this)); \
nn_note_button_map.insert (std::make_pair (note_button->note_number(), note_button)); \
id_note_button_map.insert (std::make_pair (note_button->id(), note_button))
#define MAKE_SELECT_BUTTON_PRESS(i,cc,index,p) \
controller_button = new SelectButton ((i), (cc), (index), (p), (*this)); \
cc_controller_button_map.insert (std::make_pair (controller_button->controller_number(), controller_button)); \
id_controller_button_map.insert (std::make_pair (controller_button->id(), controller_button))
#define MAKE_TRACK_STATE_BUTTON_PRESS(i,nn,index,p) \
note_button = new TrackStateButton ((i), (nn), (index), (p), (*this)); \
nn_note_button_map.insert (std::make_pair (note_button->note_number(), note_button)); \
id_note_button_map.insert (std::make_pair (note_button->id(), note_button))
#define MAKE_TRACK_STATE_BUTTON_PRESS_RELEASE_LONG(i,nn,index, p,r,l) \
note_button = new TrackStateButton ((i), (nn), (index), (p), (r), (l), (*this)); \
nn_note_button_map.insert (std::make_pair (note_button->note_number(), note_button)); \
id_note_button_map.insert (std::make_pair (note_button->id(), note_button))
MAKE_TRACK_BUTTON_PRESS(Focus1, 41, 24, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_1);
MAKE_TRACK_BUTTON_PRESS(Focus2, 42, 25, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_2);
MAKE_TRACK_BUTTON_PRESS(Focus3, 43, 26, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_3);
MAKE_TRACK_BUTTON_PRESS(Focus4, 44, 27, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_4);
MAKE_TRACK_BUTTON_PRESS(Focus5, 57, 28, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_5);
MAKE_TRACK_BUTTON_PRESS(Focus6, 58, 29, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_6);
MAKE_TRACK_BUTTON_PRESS(Focus7, 59, 30, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_7);
MAKE_TRACK_BUTTON_PRESS(Focus8, 60, 31, LEDColor::GreenFull, &LaunchControlXL::button_track_focus_8);
MAKE_TRACK_BUTTON_PRESS(Control1, 73, 32, LEDColor::Yellow, &LaunchControlXL::button_track_control_1);
MAKE_TRACK_BUTTON_PRESS(Control2, 74, 33, LEDColor::Yellow, &LaunchControlXL::button_track_control_2);
MAKE_TRACK_BUTTON_PRESS(Control3, 75, 34, LEDColor::Yellow, &LaunchControlXL::button_track_control_3);
MAKE_TRACK_BUTTON_PRESS(Control4, 76, 35, LEDColor::Yellow, &LaunchControlXL::button_track_control_4);
MAKE_TRACK_BUTTON_PRESS(Control5, 89, 36, LEDColor::Yellow, &LaunchControlXL::button_track_control_5);
MAKE_TRACK_BUTTON_PRESS(Control6, 90, 37, LEDColor::Yellow, &LaunchControlXL::button_track_control_6);
MAKE_TRACK_BUTTON_PRESS(Control7, 91, 38, LEDColor::Yellow, &LaunchControlXL::button_track_control_7);
MAKE_TRACK_BUTTON_PRESS(Control8, 92, 39, LEDColor::Yellow, &LaunchControlXL::button_track_control_8);
MAKE_SELECT_BUTTON_PRESS(SelectUp, 104, 44, &LaunchControlXL::button_select_up);
MAKE_SELECT_BUTTON_PRESS(SelectDown, 105, 45, &LaunchControlXL::button_select_down);
MAKE_SELECT_BUTTON_PRESS(SelectLeft, 106, 46, &LaunchControlXL::button_select_left);
MAKE_SELECT_BUTTON_PRESS(SelectRight, 107, 47, &LaunchControlXL::button_select_right);
MAKE_TRACK_STATE_BUTTON_PRESS_RELEASE_LONG(Device, 105, 40, &LaunchControlXL::relax, &LaunchControlXL::button_device, &LaunchControlXL::button_device_long_press);;
MAKE_TRACK_STATE_BUTTON_PRESS(Mute, 106, 41, &LaunchControlXL::button_mute);
MAKE_TRACK_STATE_BUTTON_PRESS(Solo, 107, 42, &LaunchControlXL::button_solo);
MAKE_TRACK_STATE_BUTTON_PRESS(Record, 108, 43, &LaunchControlXL::button_record);
}
std::string
LaunchControlXL::button_name_by_id (ButtonID id)
{
switch (id) {
case Device:
return "Device";
case Mute:
return "Mute";
case Solo:
return "Solo";
case Record:
return "Record";
case SelectUp:
return "Select Up";
case SelectDown:
return "Select Down";
case SelectRight:
return "Select Right";
case SelectLeft:
return "Select Left";
case Focus1:
return "Focus 1";
case Focus2:
return "Focus 2";
case Focus3:
return "Focus 3";
case Focus4:
return "Focus 4";
case Focus5:
return "Focus 5";
case Focus6:
return "Focus 6";
case Focus7:
return "Focus 7";
case Focus8:
return "Focus 8";
case Control1:
return "Control 1";
case Control2:
return "Control 2";
case Control3:
return "Control 3";
case Control4:
return "Control 4";
case Control5:
return "Control 5";
case Control6:
return "Control 6";
case Control7:
return "Control 7";
case Control8:
return "Control 8";
default:
break;
}
return "???";
}
std::string
LaunchControlXL::knob_name_by_id (KnobID id)
{
switch (id) {
case SendA1:
return "SendA 1";
case SendA2:
return "SendA 2";
case SendA3:
return "SendA 3";
case SendA4:
return "SendA 4";
case SendA5:
return "SendA 5";
case SendA6:
return "SendA 6";
case SendA7:
return "SendA 7";
case SendA8:
return "SendA 8";
case SendB1:
return "SendB 1";
case SendB2:
return "SendB 2";
case SendB3:
return "SendB 3";
case SendB4:
return "SendB 4";
case SendB5:
return "SendB 5";
case SendB6:
return "SendB 6";
case SendB7:
return "SendB 7";
case SendB8:
return "SendB 8";
case Pan1:
return "Pan 1";
case Pan2:
return "Pan 2";
case Pan3:
return "Pan 3";
case Pan4:
return "Pan 4";
case Pan5:
return "Pan 5";
case Pan6:
return "Pan 6";
case Pan7:
return "Pan 7";
case Pan8:
return "Pan 8";
default:
break;
}
return "???";
}
std::string
LaunchControlXL::fader_name_by_id (FaderID id)
{
switch (id) {
case Fader1:
return "Fader 1";
case Fader2:
return "Fader 2";
case Fader3:
return "Fader 3";
case Fader4:
return "Fader 4";
case Fader5:
return "Fader 5";
case Fader6:
return "Fader 6";
case Fader7:
return "Fader 7";
case Fader8:
return "Fader 8";
default:
break;
}
return "???";
}
LaunchControlXL::TrackButton*
LaunchControlXL::track_button_by_number(uint8_t n, uint8_t first, uint8_t middle)
{
NNNoteButtonMap::iterator b;
if ( n < 5) {
b = nn_note_button_map.find (first + n);
}
else {
b = nn_note_button_map.find (middle + n);
}
TrackButton* button;
if (b != nn_note_button_map.end()) {
button = static_cast<TrackButton*>(b->second);
}
return button;
}
void
LaunchControlXL::button_track_focus(uint8_t n)
{
if (!stripable[n]) {
return;
}
TrackButton* b = focus_button_by_number(n);
if (b == 0) {
return;
}
if ( stripable[n]->is_selected() ) {
b->set_color(LEDColor::AmberFull);
}
else {
b->set_color(LEDColor::AmberLow);
}
write (b->state_msg());
}
boost::shared_ptr<AutomationControl>
LaunchControlXL::get_ac_by_state(uint8_t n) {
boost::shared_ptr<AutomationControl> ac;
switch(track_mode()) {
case TrackMute:
ac = stripable[n]->mute_control();
break;
case TrackSolo:
ac = stripable[n]->solo_control();
break;
case TrackRecord:
ac = stripable[n]->rec_enable_control();
break;
default:
break;
}
return ac;
}
void
LaunchControlXL::update_track_control_led(uint8_t n)
{
TrackButton* b = control_button_by_number(n);
if (!stripable[n] || !b) {
return;
}
boost::shared_ptr<AutomationControl> ac = get_ac_by_state(n);
switch(track_mode()) {
case TrackMute:
if (ac->get_value()) {
b->set_color(LEDColor::AmberFull);
}
else {
b->set_color(LEDColor::AmberLow);
}
break;
case TrackSolo:
if (ac && stripable[n] != master ) {
if (ac->get_value()) {
b->set_color(LEDColor::GreenFull);
}
else {
b->set_color(LEDColor::GreenLow);
}
}
else {
b->set_color(LEDColor::Off);
}
break;
case TrackRecord:
if (ac) {
if (ac->get_value()) {
b->set_color(LEDColor::RedFull);
}
else {
b->set_color(LEDColor::RedLow);
}
}
else {
}
break;
default:
break;
}
if (ac) {
write (b->state_msg());
}
}
void
LaunchControlXL::solo_mute_rec_changed(uint32_t n) {
if (!stripable[n]) {
return;
}
update_track_control_led(n);
}
void
LaunchControlXL::button_track_control(uint8_t n) {
if (!stripable[n]) {
return;
}
boost::shared_ptr<AutomationControl> ac = get_ac_by_state(n);
if (ac) {
session->set_control (ac, !ac->get_value(), PBD::Controllable::UseGroup);
}
}
void
LaunchControlXL::button_track_mode(TrackMode state)
{
set_track_mode(state);
for (uint8_t n = 0; n < 8; ++n) {
update_track_control_led(n);
}
TrackStateButton* mute = static_cast<TrackStateButton*>(id_note_button_map[Mute]);
TrackStateButton* solo = static_cast<TrackStateButton*>(id_note_button_map[Solo]);
TrackStateButton* record = static_cast<TrackStateButton*>(id_note_button_map[Record]);
write(mute->state_msg( (state == TrackMute) ));
write(solo->state_msg( (state == TrackSolo) ));
write(record->state_msg( (state == TrackRecord) ));
}
void
LaunchControlXL::button_select_left()
{
switch_bank (max (0, bank_start - 1));
}
void
LaunchControlXL::button_select_right()
{
switch_bank (max (0, bank_start + 1));
}
void
LaunchControlXL::button_select_up()
{
}
void
LaunchControlXL::button_select_down()
{
}
void
LaunchControlXL::button_device()
{
}
void
LaunchControlXL::button_device_long_press()
{
}
bool
LaunchControlXL::button_long_press_timeout (ButtonID id, Button* button)
{
if (buttons_down.find (id) != buttons_down.end()) {
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("long press timeout for %1, invoking method\n", id));
(this->*button->long_press_method) ();
} else {
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("long press timeout for %1, expired/cancelled\n", id));
/* release happened and somehow we were not cancelled */
}
/* whichever button this was, we've used it ... don't invoke the
release action.
*/
consumed.insert (id);
return false; /* don't get called again */
}
void
LaunchControlXL::start_press_timeout (Button* button, ButtonID id)
{
Glib::RefPtr<Glib::TimeoutSource> timeout = Glib::TimeoutSource::create (500); // milliseconds
button->timeout_connection = timeout->connect (sigc::bind (sigc::mem_fun (*this, &LaunchControlXL::button_long_press_timeout), id, button));
timeout->attach (main_loop()->get_context());
}

View file

@ -0,0 +1,268 @@
/*
Copyright (C) 2015 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <gtkmm/alignment.h>
#include <gtkmm/label.h>
#include <gtkmm/liststore.h>
#include "pbd/unwind.h"
#include "pbd/strsplit.h"
#include "pbd/file_utils.h"
#include "gtkmm2ext/bindings.h"
#include "gtkmm2ext/gui_thread.h"
#include "gtkmm2ext/utils.h"
#include "ardour/audioengine.h"
#include "ardour/filesystem_paths.h"
#include "ardour/parameter_descriptor.h"
#include "launch_control_xl.h"
#include "gui.h"
#include "pbd/i18n.h"
using namespace PBD;
using namespace ARDOUR;
using namespace ArdourSurface;
using namespace std;
using namespace Gtk;
using namespace Gtkmm2ext;
void*
LaunchControlXL::get_gui () const
{
if (!gui) {
const_cast<LaunchControlXL*>(this)->build_gui ();
}
static_cast<Gtk::VBox*>(gui)->show_all();
return gui;
}
void
LaunchControlXL::tear_down_gui ()
{
if (gui) {
Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
if (w) {
w->hide();
delete w;
}
}
delete gui;
gui = 0;
}
void
LaunchControlXL::build_gui ()
{
gui = new LCXLGUI (*this);
}
/*--------------------*/
LCXLGUI::LCXLGUI (LaunchControlXL& p)
: lcxl (p)
, table (2, 5)
, action_table (5, 4)
, ignore_active_change (false)
{
set_border_width (12);
table.set_row_spacings (4);
table.set_col_spacings (6);
table.set_border_width (12);
table.set_homogeneous (false);
std::string data_file_path;
string name = "push2-small.png";
Searchpath spath(ARDOUR::ardour_data_search_path());
spath.add_subdirectory_to_paths ("icons");
find_file (spath, name, data_file_path);
if (!data_file_path.empty()) {
image.set (data_file_path);
hpacker.pack_start (image, false, false);
}
Gtk::Label* l;
int row = 0;
input_combo.pack_start (midi_port_columns.short_name);
output_combo.pack_start (midi_port_columns.short_name);
input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &LCXLGUI::active_port_changed), &input_combo, true));
output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &LCXLGUI::active_port_changed), &output_combo, false));
l = manage (new Gtk::Label);
l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
l->set_alignment (1.0, 0.5);
table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
row++;
l = manage (new Gtk::Label);
l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
l->set_alignment (1.0, 0.5);
table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
row++;
hpacker.pack_start (table, true, true);
set_spacing (12);
pack_start (hpacker, false, false);
/* update the port connection combos */
update_port_combos ();
/* catch future changes to connection state */
ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, invalidator (*this), boost::bind (&LCXLGUI::connection_handler, this), gui_context());
lcxl.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&LCXLGUI::connection_handler, this), gui_context());
}
LCXLGUI::~LCXLGUI ()
{
}
void
LCXLGUI::connection_handler ()
{
/* ignore all changes to combobox active strings here, because we're
updating them to match a new ("external") reality - we were called
because port connections have changed.
*/
PBD::Unwinder<bool> ici (ignore_active_change, true);
update_port_combos ();
}
void
LCXLGUI::update_port_combos ()
{
vector<string> midi_inputs;
vector<string> midi_outputs;
ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
bool input_found = false;
bool output_found = false;
int n;
input_combo.set_model (input);
output_combo.set_model (output);
Gtk::TreeModel::Children children = input->children();
Gtk::TreeModel::Children::iterator i;
i = children.begin();
++i; /* skip "Disconnected" */
for (n = 1; i != children.end(); ++i, ++n) {
string port_name = (*i)[midi_port_columns.full_name];
if (lcxl.input_port()->connected_to (port_name)) {
input_combo.set_active (n);
input_found = true;
break;
}
}
if (!input_found) {
input_combo.set_active (0); /* disconnected */
}
children = output->children();
i = children.begin();
++i; /* skip "Disconnected" */
for (n = 1; i != children.end(); ++i, ++n) {
string port_name = (*i)[midi_port_columns.full_name];
if (lcxl.output_port()->connected_to (port_name)) {
output_combo.set_active (n);
output_found = true;
break;
}
}
if (!output_found) {
output_combo.set_active (0); /* disconnected */
}
}
Glib::RefPtr<Gtk::ListStore>
LCXLGUI::build_midi_port_list (vector<string> const & ports, bool for_input)
{
Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
TreeModel::Row row;
row = *store->append ();
row[midi_port_columns.full_name] = string();
row[midi_port_columns.short_name] = _("Disconnected");
for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
row = *store->append ();
row[midi_port_columns.full_name] = *p;
std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
if (pn.empty ()) {
pn = (*p).substr ((*p).find (':') + 1);
}
row[midi_port_columns.short_name] = pn;
}
return store;
}
void
LCXLGUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
{
if (ignore_active_change) {
return;
}
TreeModel::iterator active = combo->get_active ();
string new_port = (*active)[midi_port_columns.full_name];
if (new_port.empty()) {
if (for_input) {
lcxl.input_port()->disconnect_all ();
} else {
lcxl.output_port()->disconnect_all ();
}
return;
}
if (for_input) {
if (!lcxl.input_port()->connected_to (new_port)) {
lcxl.input_port()->disconnect_all ();
lcxl.input_port()->connect (new_port);
}
} else {
if (!lcxl.output_port()->connected_to (new_port)) {
lcxl.output_port()->disconnect_all ();
lcxl.output_port()->connect (new_port);
}
}
}

View file

@ -0,0 +1,100 @@
/*
Copyright (C) 2015 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_launch_control_gui_h__
#define __ardour_launch_control_gui_h__
#include <vector>
#include <string>
#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/combobox.h>
#include <gtkmm/image.h>
#include <gtkmm/table.h>
#include <gtkmm/treestore.h>
#include <gtkmm/spinbutton.h>
#include <gtkmm/notebook.h>
namespace Gtk {
class CellRendererCombo;
class ListStore;
}
#include "ardour/mode.h"
#include "launch_control_xl.h"
namespace ArdourSurface {
class LCXLGUI : public Gtk::VBox
{
public:
LCXLGUI (LaunchControlXL&);
~LCXLGUI ();
private:
LaunchControlXL& lcxl;
PBD::ScopedConnectionList lcxl_connections;
Gtk::HBox hpacker;
Gtk::Table table;
Gtk::Table action_table;
Gtk::ComboBox input_combo;
Gtk::ComboBox output_combo;
Gtk::Image image;
void update_port_combos ();
PBD::ScopedConnection connection_change_connection;
void connection_handler ();
PBD::ScopedConnection port_reg_connection;
struct MidiPortColumns : public Gtk::TreeModel::ColumnRecord {
MidiPortColumns() {
add (short_name);
add (full_name);
}
Gtk::TreeModelColumn<std::string> short_name;
Gtk::TreeModelColumn<std::string> full_name;
};
MidiPortColumns midi_port_columns;
bool ignore_active_change;
Glib::RefPtr<Gtk::ListStore> build_midi_port_list (std::vector<std::string> const & ports, bool for_input);
void active_port_changed (Gtk::ComboBox*,bool for_input);
struct ActionColumns : public Gtk::TreeModel::ColumnRecord {
ActionColumns() {
add (name);
add (path);
}
Gtk::TreeModelColumn<std::string> name;
Gtk::TreeModelColumn<std::string> path;
};
ActionColumns action_columns;
Glib::RefPtr<Gtk::TreeStore> available_action_model;
std::map<std::string,std::string> action_map; // map from action names to paths
};
}
#endif /* __ardour_launch_control_gui_h__ */

View file

@ -0,0 +1,92 @@
/*
Copyright (C) 2017 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdexcept>
#include "pbd/error.h"
#include "ardour/rc_configuration.h"
#include "control_protocol/control_protocol.h"
#include "launch_control_xl.h"
using namespace ARDOUR;
using namespace PBD;
using namespace std;
using namespace ArdourSurface;
static ControlProtocol*
new_launch_control_xl (ControlProtocolDescriptor*, Session* s)
{
LaunchControlXL * lcxl = 0;
try {
lcxl = new LaunchControlXL (*s);
/* do not set active here - wait for set_state() */
}
catch (exception & e) {
error << "Error instantiating LaunchControlXL support: " << e.what() << endmsg;
delete lcxl;
lcxl = 0;
}
return lcxl;
}
static void
delete_launch_control_xl (ControlProtocolDescriptor*, ControlProtocol* cp)
{
try
{
delete cp;
}
catch ( exception & e )
{
cout << "Exception caught trying to finalize LaunchControlXL support: " << e.what() << endl;
}
}
/**
This is called on startup to check whether the lib should be loaded.
So anything that can be changed in the UI should not be used here to
prevent loading of the lib.
*/
static bool
probe_launch_control_xl (ControlProtocolDescriptor*)
{
return LaunchControlXL::probe();
}
static ControlProtocolDescriptor launch_control_xl_descriptor = {
/*name : */ "Novation Launch Control XL",
/*id : */ "uri://ardour.org/surfaces/launch_control_xl:0",
/*ptr : */ 0,
/*module : */ 0,
/*mandatory : */ 0,
// actually, the surface does support feedback, but all this
// flag does is show a submenu on the UI, which is useless for the mackie
// because feedback is always on. In any case, who'd want to use the
// mcu without the motorised sliders doing their thing?
/*supports_feedback : */ true,
/*probe : */ probe_launch_control_xl,
/*initialize : */ new_launch_control_xl,
/*destroy : */ delete_launch_control_xl,
};
extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () { return &launch_control_xl_descriptor; }

View file

@ -0,0 +1,898 @@
/*
Copyright (C) 2016 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include <pthread.h>
#include "pbd/compose.h"
#include "pbd/convert.h"
#include "pbd/debug.h"
#include "pbd/failed_constructor.h"
#include "pbd/file_utils.h"
#include "pbd/search_path.h"
#include "pbd/enumwriter.h"
#include "midi++/parser.h"
#include "temporal/time.h"
#include "temporal/bbt_time.h"
#include "ardour/amp.h"
#include "ardour/async_midi_port.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/midiport_manager.h"
#include "ardour/midi_track.h"
#include "ardour/midi_port.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "ardour/types_convert.h"
#include "ardour/vca_manager.h"
#include "gtkmm2ext/gui_thread.h"
#include "gui.h"
#include "launch_control_xl.h"
#include "pbd/i18n.h"
#ifdef PLATFORM_WINDOWS
#define random() rand()
#endif
using namespace ARDOUR;
using namespace std;
using namespace PBD;
using namespace Glib;
using namespace ArdourSurface;
#include "pbd/abstract_ui.cc" // instantiate template
/* init global object */
LaunchControlXL* lcxl = 0;
LaunchControlXL::LaunchControlXL (ARDOUR::Session& s)
: ControlProtocol (s, string (X_("Novation Launch Control XL")))
, AbstractUI<LaunchControlRequest> (name())
, in_use (false)
, _track_mode(TrackMute)
, _template_number(8) // default template (factory 1)
, bank_start (0)
, connection_state (ConnectionState (0))
, gui (0)
, in_range_select (false)
{
lcxl = this;
/* we're going to need this */
build_maps ();
/* master cannot be removed, so no need to connect to going-away signal */
master = session->master_out ();
/* the master bus will always be on the last channel on the lcxl */
stripable[7] = master;
run_event_loop ();
/* Ports exist for the life of this instance */
ports_acquire ();
/* catch arrival and departure of LaunchControlXL itself */
ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::port_registration_handler, this), this);
/* Catch port connections and disconnections */
ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::connection_handler, this, _1, _2, _3, _4, _5), this);
/* Launch Control XL ports might already be there */
port_registration_handler ();
session->RouteAdded.connect (session_connections, invalidator(*this), boost::bind (&LaunchControlXL::stripables_added, this), lcxl);
session->vca_manager().VCAAdded.connect (session_connections, invalidator (*this), boost::bind (&LaunchControlXL::stripables_added, this), lcxl);
switch_bank (bank_start);
}
LaunchControlXL::~LaunchControlXL ()
{
DEBUG_TRACE (DEBUG::LaunchControlXL, "Launch Control XL control surface object being destroyed\n");
/* do this before stopping the event loop, so that we don't get any notifications */
port_reg_connection.disconnect ();
port_connection.disconnect ();
stop_using_device ();
ports_release ();
stop_event_loop ();
}
void
LaunchControlXL::run_event_loop ()
{
DEBUG_TRACE (DEBUG::LaunchControlXL, "start event loop\n");
BaseUI::run ();
}
void
LaunchControlXL::stop_event_loop ()
{
DEBUG_TRACE (DEBUG::LaunchControlXL, "stop event loop\n");
BaseUI::quit ();
}
int
LaunchControlXL::begin_using_device ()
{
DEBUG_TRACE (DEBUG::LaunchControlXL, "begin using device\n");
switch_template(template_number()); // first factory template
connect_session_signals ();
init_buttons (true);
in_use = true;
return 0;
}
int
LaunchControlXL::stop_using_device ()
{
DEBUG_TRACE (DEBUG::LaunchControlXL, "stop using device\n");
if (!in_use) {
DEBUG_TRACE (DEBUG::LaunchControlXL, "nothing to do, device not in use\n");
return 0;
}
init_buttons (false);
session_connections.drop_connections ();
in_use = false;
return 0;
}
int
LaunchControlXL::ports_acquire ()
{
DEBUG_TRACE (DEBUG::LaunchControlXL, "acquiring ports\n");
/* setup ports */
_async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Launch Control XL in"), true);
_async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Launch Control XL out"), true);
if (_async_in == 0 || _async_out == 0) {
DEBUG_TRACE (DEBUG::LaunchControlXL, "cannot register ports\n");
return -1;
}
/* We do not add our ports to the input/output bundles because we don't
* want users wiring them by hand. They could use JACK tools if they
* really insist on that (and use JACK)
*/
_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get();
session->BundleAddedOrRemoved ();
connect_to_parser ();
/* Connect input port to event loop */
AsyncMIDIPort* asp;
asp = static_cast<AsyncMIDIPort*> (_input_port);
asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &LaunchControlXL::midi_input_handler), _input_port));
asp->xthread().attach (main_loop()->get_context());
return 0;
}
void
LaunchControlXL::ports_release ()
{
DEBUG_TRACE (DEBUG::LaunchControlXL, "releasing ports\n");
/* wait for button data to be flushed */
AsyncMIDIPort* asp;
asp = static_cast<AsyncMIDIPort*> (_output_port);
asp->drain (10000, 500000);
{
Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
AudioEngine::instance()->unregister_port (_async_in);
AudioEngine::instance()->unregister_port (_async_out);
}
_async_in.reset ((ARDOUR::Port*) 0);
_async_out.reset ((ARDOUR::Port*) 0);
_input_port = 0;
_output_port = 0;
}
list<boost::shared_ptr<ARDOUR::Bundle> >
LaunchControlXL::bundles ()
{
list<boost::shared_ptr<ARDOUR::Bundle> > b;
if (_output_bundle) {
b.push_back (_output_bundle);
}
return b;
}
void
LaunchControlXL::init_buttons (bool startup)
{
if (startup) {
button_track_mode(track_mode());
}
}
bool
LaunchControlXL::probe ()
{
return true;
}
void*
LaunchControlXL::request_factory (uint32_t num_requests)
{
/* AbstractUI<T>::request_buffer_factory() is a template method only
instantiated in this source module. To provide something visible for
use in the interface/descriptor, we have this static method that is
template-free.
*/
return request_buffer_factory (num_requests);
}
void
LaunchControlXL::do_request (LaunchControlRequest * req)
{
if (req->type == CallSlot) {
call_slot (MISSING_INVALIDATOR, req->the_slot);
} else if (req->type == Quit) {
stop_using_device ();
}
}
int
LaunchControlXL::set_active (bool yn)
{
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active init with yn: '%1'\n", yn));
if (yn == active()) {
return 0;
}
if (yn) {
if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
begin_using_device ();
} else {
/* begin_using_device () will get called once we're connected */
}
} else {
/* Control Protocol Manager never calls us with false, but
* insteads destroys us.
*/
}
ControlProtocol::set_active (yn);
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active done with yn: '%1'\n", yn));
return 0;
}
void
LaunchControlXL::write (const MidiByteArray& data)
{
/* immediate delivery */
_output_port->write (&data[0], data.size(), 0);
}
/* Device to Ardour message handling */
bool
LaunchControlXL::midi_input_handler (IOCondition ioc, MIDI::Port* port)
{
if (ioc & ~IO_IN) {
DEBUG_TRACE (DEBUG::LaunchControlXL, "MIDI port closed\n");
return false;
}
if (ioc & IO_IN) {
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("something happened on %1\n", port->name()));
AsyncMIDIPort* asp = static_cast<AsyncMIDIPort*>(port);
if (asp) {
asp->clear ();
}
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("data available on %1\n", port->name()));
if (in_use) {
samplepos_t now = AudioEngine::instance()->sample_time();
port->parse (now);
}
}
return true;
}
void
LaunchControlXL::connect_to_parser ()
{
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Connecting to signals on port %1\n", _input_port->name()));
MIDI::Parser* p = _input_port->parser();
/* Incoming sysex */
p->sysex.connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_sysex, this, _1, _2, _3));
for (MIDI::channel_t n = 0; n < 16; ++n) {
/* Controller */
p->channel_controller[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_controller_message, this, _1, _2, n));
/* Button messages are NoteOn */
p->channel_note_on[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_note_on_message, this, _1, _2, n));
/* Button messages are NoteOn but libmidi++ sends note-on w/velocity = 0 as note-off so catch them too */
p->channel_note_off[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_note_off_message, this, _1, _2, n));
}
}
void
LaunchControlXL::handle_midi_sysex (MIDI::Parser&, MIDI::byte* raw_bytes, size_t sz)
{
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Sysex, %1 bytes\n", sz));
if (sz < 8) {
return;
}
MidiByteArray msg (sz, raw_bytes);
MidiByteArray lcxl_sysex_header (6, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11);
if (!lcxl_sysex_header.compare_n (msg, 6)) {
return;
}
switch (msg[6]) {
case 0x77: /* template change */
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Template change: %1 n", msg[7]));
_template_number = msg[7];
break;
}
}
void
LaunchControlXL::handle_button_message(Button* button, MIDI::EventTwoBytes* ev)
{
if (ev->value) {
/* any press cancels any pending long press timeouts */
for (set<ButtonID>::iterator x = buttons_down.begin(); x != buttons_down.end(); ++x) {
ControllerButton* cb = id_controller_button_map[*x];
NoteButton* nb = id_note_button_map[*x];
if (cb != 0) {
cb->timeout_connection.disconnect();
}
else if (nb != 0) {
nb->timeout_connection.disconnect();
}
}
buttons_down.insert(button->id());
DEBUG_TRACE(DEBUG::LaunchControlXL, string_compose("button pressed: %1\n", LaunchControlXL::button_name_by_id(button->id())));
start_press_timeout(button, button->id());
}
else {
DEBUG_TRACE(DEBUG::LaunchControlXL, string_compose("button depressed: %1\n", LaunchControlXL::button_name_by_id(button->id())));
buttons_down.erase(button->id());
button->timeout_connection.disconnect();
}
set<ButtonID>::iterator c = consumed.find(button->id());
if (c == consumed.end()) {
if (ev->value == 0) {
(this->*button->release_method)();
}
else {
(this->*button->press_method)();
}
}
else {
DEBUG_TRACE(DEBUG::LaunchControlXL, "button was consumed, ignored\n");
consumed.erase(c);
}
}
void
LaunchControlXL::handle_knob_message (Knob* knob)
{
uint8_t chan = knob->id() % 8; // get the strip channel number
if (!stripable[chan]) {
return;
}
boost::shared_ptr<AutomationControl> ac;
if (knob->id() < 8) { // sendA
ac = stripable[chan]->trim_control();
}
else if (knob->id() >= 8 && knob->id() < 16) { // sendB
ac = stripable[chan]->pan_width_control();
}
else if (knob->id() >= 16 && knob->id() < 24) { // pan
ac = stripable[chan]->pan_azimuth_control();
}
if (ac) {
ac->set_value ( ac->interface_to_internal( knob->value() / 127.0), PBD::Controllable::UseGroup );
}
}
void
LaunchControlXL::handle_fader_message (Fader* fader)
{
if (!stripable[fader->id()]) {
return;
}
boost::shared_ptr<AutomationControl> ac = stripable[fader->id()]->gain_control();
if (ac) {
ac->set_value ( ac->interface_to_internal( fader->value() / 127.0), PBD::Controllable::UseGroup );
}
}
void
LaunchControlXL::handle_midi_controller_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev, MIDI::channel_t chan)
{
_template_number = (int)chan;
if (template_number() < 8) {
return; // only treat factory templates
}
// DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
CCControllerButtonMap::iterator b = cc_controller_button_map.find (ev->controller_number);
CCFaderMap::iterator f = cc_fader_map.find (ev->controller_number);
CCKnobMap::iterator k = cc_knob_map.find (ev->controller_number);
if (b != cc_controller_button_map.end()) {
Button* button = b->second;
handle_button_message(button, ev);
}
else if (f != cc_fader_map.end()) {
Fader* fader = f->second;
fader->set_value(ev->value);
handle_fader_message(fader);
}
else if (k != cc_knob_map.end()) {
Knob* knob = k->second;
knob->set_value(ev->value);
handle_knob_message(knob);
}
}
void
LaunchControlXL::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev, MIDI::channel_t chan)
{
_template_number = (int)chan;
if (template_number() < 8) {
return; // only treat factory templates
}
//DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Note On %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
NNNoteButtonMap::iterator b = nn_note_button_map.find (ev->controller_number);
if (b != nn_note_button_map.end()) {
Button* button = b->second;
handle_button_message(button, ev);
}
}
void LaunchControlXL::handle_midi_note_off_message(MIDI::Parser & parser, MIDI::EventTwoBytes *ev, MIDI::channel_t chan)
{
//DEBUG_TRACE(DEBUG::LaunchControlXL, string_compose("Note Off %1 (velocity %2)\n",(int)ev->note_number, (int)ev->velocity));
handle_midi_note_on_message(parser, ev, chan); /* we handle both case in handle_midi_note_on_message */
}
/* Ardour session signals connection */
void
LaunchControlXL::thread_init ()
{
pthread_set_name (event_loop_name().c_str());
PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
set_thread_priority ();
}
void
LaunchControlXL::connect_session_signals()
{
// receive transport state changed
session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_transport_state_changed, this), this);
session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_loop_state_changed, this), this);
// receive punch-in and punch-out
Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_parameter_changed, this, _1), this);
session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_parameter_changed, this, _1), this);
// receive rude solo changed
//session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_solo_active_changed, this, _1), this);
// receive record state toggled
//session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::notify_record_state_changed, this), this);
}
void
LaunchControlXL::notify_transport_state_changed ()
{ /*
Button* b = id_button_map[Play];
if (session->transport_rolling()) {
b->set_state (LED::OneShot24th);
b->set_color (LED::GreenFull);
} else {
disable any blink on FixedLength from pending edit range op
Button* fl = id_button_map[FixedLength];
fl->set_color (LED::Black);
fl->set_state (LED::NoTransition);
write (fl->state_msg());
b->set_color (LED::White);
b->set_state (LED::NoTransition);
}
write (b->state_msg()); */
}
void
LaunchControlXL::notify_loop_state_changed ()
{
}
void
LaunchControlXL::notify_parameter_changed (std::string param)
{ /*
IDButtonMap::iterator b;
if (param == "clicking") {
if ((b = id_button_map.find (Metronome)) == id_button_map.end()) {
return;
}
if (Config->get_clicking()) {
b->second->set_state (LED::Blinking4th);
b->second->set_color (LED::White);
} else {
b->second->set_color (LED::White);
b->second->set_state (LED::NoTransition);
}
write (b->second->state_msg ()) ;
} */
}
/* connection handling */
XMLNode&
LaunchControlXL::get_state()
{
XMLNode& node (ControlProtocol::get_state());
XMLNode* child;
child = new XMLNode (X_("Input"));
child->add_child_nocopy (_async_in->get_state());
node.add_child_nocopy (*child);
child = new XMLNode (X_("Output"));
child->add_child_nocopy (_async_out->get_state());
node.add_child_nocopy (*child);
return node;
}
int
LaunchControlXL::set_state (const XMLNode & node, int version)
{
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("LaunchControlXL::set_state: active %1\n", active()));
int retval = 0;
if (ControlProtocol::set_state (node, version)) {
return -1;
}
XMLNode* child;
if ((child = node.child (X_("Input"))) != 0) {
XMLNode* portnode = child->child (Port::state_node_name.c_str());
if (portnode) {
_async_in->set_state (*portnode, version);
}
}
if ((child = node.child (X_("Output"))) != 0) {
XMLNode* portnode = child->child (Port::state_node_name.c_str());
if (portnode) {
_async_out->set_state (*portnode, version);
}
}
return retval;
}
void
LaunchControlXL::port_registration_handler ()
{
if (!_async_in && !_async_out) {
/* ports not registered yet */
return;
}
if (_async_in->connected() && _async_out->connected()) {
/* don't waste cycles here */
return;
}
#ifdef __APPLE__
/* the origin of the numeric magic identifiers is known only to Ableton
and may change in time. This is part of how CoreMIDI works.
*/
string input_port_name = X_("system:midi_capture_1319078870");
string output_port_name = X_("system:midi_playback_3409210341");
#else
string input_port_name = X_("Novation Launch Control XL MIDI 1 in");
string output_port_name = X_("Novation Launch Control XL MIDI 1 out");
#endif
vector<string> in;
vector<string> out;
AudioEngine::instance()->get_ports (string_compose (".*%1", input_port_name), DataType::MIDI, PortFlags (IsPhysical|IsOutput), in);
AudioEngine::instance()->get_ports (string_compose (".*%1", output_port_name), DataType::MIDI, PortFlags (IsPhysical|IsInput), out);
if (!in.empty() && !out.empty()) {
cerr << "LaunchControlXL: both ports found\n";
cerr << "\tconnecting to " << in.front() << " + " << out.front() << endl;
if (!_async_in->connected()) {
AudioEngine::instance()->connect (_async_in->name(), in.front());
}
if (!_async_out->connected()) {
AudioEngine::instance()->connect (_async_out->name(), out.front());
}
}
}
bool
LaunchControlXL::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
{
DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::connection_handler start\n");
if (!_input_port || !_output_port) {
return false;
}
string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_in)->name());
string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_out)->name());
if (ni == name1 || ni == name2) {
if (yn) {
connection_state |= InputConnected;
} else {
connection_state &= ~InputConnected;
}
} else if (no == name1 || no == name2) {
if (yn) {
connection_state |= OutputConnected;
} else {
connection_state &= ~OutputConnected;
}
} else {
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
// not our ports
return false;
}
DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("our ports changed connection state: %1 -> %2 connected ? %3\n",
name1, name2, yn));
if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
/* XXX this is a horrible hack. Without a short sleep here,
something prevents the device wakeup messages from being
sent and/or the responses from being received.
*/
g_usleep (100000);
DEBUG_TRACE (DEBUG::LaunchControlXL, "device now connected for both input and output\n");
begin_using_device ();
} else {
DEBUG_TRACE (DEBUG::LaunchControlXL, "Device disconnected (input or output or both) or not yet fully connected\n");
stop_using_device ();
}
ConnectionChange (); /* emit signal for our GUI */
DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::connection_handler end\n");
return true; /* connection status changed */
}
boost::shared_ptr<Port>
LaunchControlXL::output_port()
{
return _async_out;
}
boost::shared_ptr<Port>
LaunchControlXL::input_port()
{
return _async_in;
}
/* Stripables handling */
void
LaunchControlXL::stripable_selection_changed () // we don't need it but it's needs to be declared...
{
}
void
LaunchControlXL::stripable_property_change (PropertyChange const& what_changed, uint32_t which)
{
if (what_changed.contains (Properties::hidden)) {
switch_bank (bank_start);
}
if (what_changed.contains (Properties::selected)) {
if (!stripable[which]) {
return;
}
if (which < 8) {
button_track_focus( (uint8_t)which );
}
}
}
void
LaunchControlXL::switch_template (uint8_t t)
{
MidiByteArray msg (9, 0xf0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x77, t, 0xf7);
write (msg);
}
void
LaunchControlXL::switch_bank (uint32_t base)
{
SelectButton* sl = static_cast<SelectButton*>(id_controller_button_map[SelectLeft]);
SelectButton* sr = static_cast<SelectButton*>(id_controller_button_map[SelectRight]);
if (sl && sr) {
write(sl->state_msg( (base) ));
write(sr->state_msg( !(base) ));
}
stripable_connections.drop_connections ();
/* work backwards so we can tell if we should actually switch banks */
boost::shared_ptr<Stripable> s[8];
uint32_t different = 0;
for (int n = 0; n < 7; ++n) {
s[n] = session->get_remote_nth_stripable (base+n, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
if (s[n] != stripable[n]) {
different++;
}
}
if (!s[0]) {
/* not even the first stripable exists, do nothing */
for (int n = 0; n < 7; ++n) {
stripable[n].reset ();
}
return;
}
for (int n = 0; n < 7; ++n) {
stripable[n] = s[n];
}
/* at least one stripable in this bank */
bank_start = base;
for (int n = 0; n < 8; ++n) {
if (stripable[n]) {
/* stripable goes away? refill the bank, starting at the same point */
stripable[n]->DropReferences.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::switch_bank, this, bank_start), lcxl);
stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::stripable_property_change, this, _1, n), lcxl);
stripable[n]->solo_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::solo_changed, this, n), lcxl);
stripable[n]->mute_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::mute_changed, this, n), lcxl);
if (stripable[n]->rec_enable_control()) {
stripable[n]->rec_enable_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&LaunchControlXL::rec_changed, this, n), lcxl);
}
button_track_focus(n);
update_track_control_led(n);
}
}
}
void
LaunchControlXL::stripables_added ()
{
DEBUG_TRACE (DEBUG::LaunchControlXL, "LaunchControlXL::new stripable added!\n");
/* reload current bank */
switch_bank (bank_start);
}
void LaunchControlXL::set_track_mode (TrackMode mode) {
_track_mode = mode;
// now do led stuffs to signify the change
switch(mode) {
case TrackMute:
break;
case TrackSolo:
break;
case TrackRecord:
break;
default:
break;
}
}

View file

@ -0,0 +1,545 @@
/*
Copyright (C) 2016 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_launch_control_h__
#define __ardour_launch_control_h__
#include <vector>
#include <map>
#include <stack>
#include <list>
#include <set>
#define ABSTRACT_UI_EXPORTS
#include "pbd/abstract_ui.h"
#include "midi++/types.h"
#include "ardour/mode.h"
#include "ardour/types.h"
#include "control_protocol/control_protocol.h"
#include "control_protocol/types.h"
#include "midi_byte_array.h"
namespace MIDI {
class Parser;
class Port;
} // namespace MIDI
namespace ARDOUR {
class AsyncMIDIPort;
class Port;
class MidiBuffer;
class MidiTrack;
} // namespace ARDOUR
namespace ArdourSurface {
struct LaunchControlRequest : public BaseUI::BaseRequestObject {
public:
LaunchControlRequest() {}
~LaunchControlRequest() {}
};
class LCXLGUI;
class LaunchControlMenu;
class LaunchControlXL : public ARDOUR::ControlProtocol,
public AbstractUI<LaunchControlRequest> {
public:
enum TrackMode {
TrackMute,
TrackSolo,
TrackRecord
};
enum ButtonID {
Focus1 = 0,
Focus2,
Focus3,
Focus4,
Focus5,
Focus6,
Focus7,
Focus8,
Control1,
Control2,
Control3,
Control4,
Control5,
Control6,
Control7,
Control8,
Device,
Mute,
Solo,
Record,
SelectUp,
SelectDown,
SelectLeft,
SelectRight
};
enum FaderID {
Fader1 = 0,
Fader2,
Fader3,
Fader4,
Fader5,
Fader6,
Fader7,
Fader8
};
enum KnobID {
SendA1 = 0,
SendA2,
SendA3,
SendA4,
SendA5,
SendA6,
SendA7,
SendA8,
SendB1,
SendB2,
SendB3,
SendB4,
SendB5,
SendB6,
SendB7,
SendB8,
Pan1,
Pan2,
Pan3,
Pan4,
Pan5,
Pan6,
Pan7,
Pan8
};
enum LEDFlag { Normal = 0xC, Blink = 0x8, DoubleBuffering = 0x0 };
/*
enum LEDState {
Toggle = 0x7F,
Momentary = 0x0,
}; */
enum LEDColor { Off=0, RedLow = 1, RedFull = 3, GreenLow = 16, GreenFull = 48, Yellow = 50, AmberLow = 17, AmberFull = 51};
struct Controller {
Controller(uint8_t cn, uint8_t val = 0) : _controller_number(cn), _value(val) {}
uint8_t controller_number() const { return _controller_number; }
uint8_t value() const { return _value; }
void set_value(uint8_t val) { _value = val; }
protected:
uint8_t _controller_number;
uint8_t _value;
};
struct LED {
LED(uint8_t i, LEDColor c, LaunchControlXL& l) : _index(i), _color(c), _flag(LEDFlag::Normal), lcxl(&l) {}
LED(uint8_t i, LEDColor c, LEDFlag f, LaunchControlXL& lcxl) : _index(i), _color(c), _flag(f) {}
LEDColor color() const { return _color; }
LEDFlag flag() const { return _flag; }
uint8_t index() const { return _index; }
void set_flag(LEDFlag f) { _flag = f; }
virtual MidiByteArray state_msg(bool light) const = 0;
protected:
uint8_t _index;
LEDColor _color;
LEDFlag _flag;
MidiByteArray _state_msg;
LaunchControlXL* lcxl;
};
struct MultiColorLED : public LED {
MultiColorLED(uint8_t i, LEDColor c, LaunchControlXL& l) : LED(i, c, l) {}
MultiColorLED(uint8_t i, LEDColor c, LEDFlag f, LaunchControlXL& l )
: LED(i, c, f, l) {}
void set_color(LEDColor c) { _color = c; }
};
struct Button {
Button(ButtonID id)
: press_method(&LaunchControlXL::relax),
release_method(&LaunchControlXL::relax),
long_press_method(&LaunchControlXL::relax), _id(id) {}
Button(ButtonID id, void (LaunchControlXL::*press)())
: press_method(press),
release_method(&LaunchControlXL::relax),
long_press_method(&LaunchControlXL::relax), _id(id) {}
Button(ButtonID id, void (LaunchControlXL::*press)(),
void (LaunchControlXL::*release)())
: press_method(press), release_method(release),
long_press_method(&LaunchControlXL::relax), _id(id) {}
Button(ButtonID id, void (LaunchControlXL::*press)(),
void (LaunchControlXL::*release)(),
void (LaunchControlXL::*long_press)())
: press_method(press), release_method(release),
long_press_method(long_press), _id(id) {}
virtual ~Button() {}
ButtonID id() const { return _id; }
void (LaunchControlXL::*press_method)();
void (LaunchControlXL::*release_method)();
void (LaunchControlXL::*long_press_method)();
sigc::connection timeout_connection;
protected:
ButtonID _id;
};
struct ControllerButton : public Button {
ControllerButton(ButtonID id, uint8_t cn,
void (LaunchControlXL::*press)())
: Button(id, press), _controller_number(cn) {}
ControllerButton(ButtonID id, uint8_t cn,
void (LaunchControlXL::*press)(),
void (LaunchControlXL::*release)())
: Button(id, press, release), _controller_number(cn) {}
uint8_t controller_number() const { return _controller_number; }
private:
uint8_t _controller_number;
};
struct NoteButton : public Button {
NoteButton(ButtonID id, uint8_t cn,
void (LaunchControlXL::*press)())
: Button(id, press), _note_number(cn) {}
NoteButton(ButtonID id, uint8_t cn,
void (LaunchControlXL::*press)(),
void (LaunchControlXL::*release)())
: Button(id, press, release), _note_number(cn) {}
NoteButton(ButtonID id, uint8_t cn,
void (LaunchControlXL::*press)(),
void (LaunchControlXL::*release)(),
void (LaunchControlXL::*release_long)())
: Button(id, press, release, release_long), _note_number(cn) {}
uint8_t note_number() const { return _note_number; }
private:
uint8_t _note_number;
};
struct TrackButton : public NoteButton, public MultiColorLED {
TrackButton(ButtonID id, uint8_t nn, uint8_t index, LEDColor color,
void (LaunchControlXL::*press)(), LaunchControlXL& l)
: NoteButton(id, nn, press), MultiColorLED(index, color, l) {}
TrackButton(ButtonID id, uint8_t nn, uint8_t index, LEDColor color,
void (LaunchControlXL::*press)(),
void (LaunchControlXL::*release)(),
LaunchControlXL& l)
: NoteButton(id, nn, press, release), MultiColorLED(index, color, l) {}
MidiByteArray state_msg(bool light = true) const;
};
struct SelectButton : public ControllerButton, public LED {
SelectButton(ButtonID id, uint8_t cn, uint8_t index, void (LaunchControlXL::*press)(), LaunchControlXL& l)
: ControllerButton(id, cn, press), LED(index, LEDColor::RedFull, l) {}
MidiByteArray state_msg(bool light) const;
};
struct TrackStateButton : public NoteButton, public LED {
TrackStateButton(ButtonID id, uint8_t nn, uint8_t index, void (LaunchControlXL::*press)(), LaunchControlXL& l)
: NoteButton(id, nn, press), LED(index, LEDColor::Yellow, l) {}
TrackStateButton(ButtonID id, uint8_t nn, uint8_t index, void (LaunchControlXL::*press)(),
void (LaunchControlXL::*release)(),
LaunchControlXL& l)
: NoteButton(id, nn, press, release), LED(index, LEDColor::Yellow, l) {}
TrackStateButton(ButtonID id, uint8_t nn, uint8_t index, void (LaunchControlXL::*press)(),
void (LaunchControlXL::*release)(),
void (LaunchControlXL::*release_long)(),
LaunchControlXL& l)
: NoteButton(id, nn, press, release, release_long), LED(index, LEDColor::Yellow, l) {}
MidiByteArray state_msg(bool light) const;
};
struct Fader : public Controller {
Fader(FaderID id, uint8_t cn)
: Controller(cn, 0), _id(id) {} // minimal value
FaderID id() const { return _id; }
void controller_changed(Controller* controller);
private:
FaderID _id;
};
struct Knob : public Controller, public MultiColorLED {
Knob(KnobID id, uint8_t cn, uint8_t index, LEDColor color, LaunchControlXL& l)
: Controller(cn, 64), MultiColorLED(index, color, l), _id(id) {} // knob 50/50 value
KnobID id() const { return _id; }
MidiByteArray state_msg(bool light = true) const;
private:
KnobID _id;
};
public:
LaunchControlXL(ARDOUR::Session &);
~LaunchControlXL();
static bool probe();
static void *request_factory(uint32_t);
std::list<boost::shared_ptr<ARDOUR::Bundle>> bundles();
bool has_editor() const { return true; }
void *get_gui() const;
void tear_down_gui();
int set_active(bool yn);
XMLNode &get_state();
int set_state(const XMLNode &node, int version);
PBD::Signal0<void> ConnectionChange;
boost::shared_ptr<ARDOUR::Port> input_port();
boost::shared_ptr<ARDOUR::Port> output_port();
Button *button_by_id(ButtonID);
static std::string button_name_by_id(ButtonID);
static std::string knob_name_by_id(KnobID);
static std::string fader_name_by_id(FaderID);
void write(const MidiByteArray &);
TrackMode track_mode() const { return _track_mode; }
void set_track_mode(TrackMode mode);
uint8_t template_number() const { return _template_number; }
private:
bool in_use;
TrackMode _track_mode;
uint8_t _template_number;
void do_request(LaunchControlRequest *);
int begin_using_device();
int stop_using_device();
int ports_acquire();
void ports_release();
void run_event_loop();
void stop_event_loop();
void relax() {}
/* map of NoteButtons by NoteNumber */
typedef std::map<int, NoteButton *> NNNoteButtonMap;
NNNoteButtonMap nn_note_button_map;
/* map of NoteButtons by ButtonID */
typedef std::map<ButtonID, NoteButton *> IDNoteButtonMap;
IDNoteButtonMap id_note_button_map;
/* map of ControllerNoteButtons by CC */
typedef std::map<int, ControllerButton *> CCControllerButtonMap;
CCControllerButtonMap cc_controller_button_map;
/* map of ControllerButtons by ButtonID */
typedef std::map<ButtonID, ControllerButton *> IDControllerButtonMap;
IDControllerButtonMap id_controller_button_map;
/* map of Fader by CC */
typedef std::map<int, Fader *> CCFaderMap;
CCFaderMap cc_fader_map;
/* map of Fader by FaderID */
typedef std::map<FaderID, Fader *> IDFaderMap;
IDFaderMap id_fader_map;
/* map of Knob by CC */
typedef std::map<int, Knob *> CCKnobMap;
CCKnobMap cc_knob_map;
/* map of Knob by KnobID */
typedef std::map<KnobID, Knob *> IDKnobMap;
IDKnobMap id_knob_map;
std::set<ButtonID> buttons_down;
std::set<ButtonID> consumed;
bool button_long_press_timeout(ButtonID id, Button *button);
void start_press_timeout(Button *, ButtonID);
void init_buttons(bool startup);
void switch_template(uint8_t t);
void build_maps();
// Bundle to represent our input ports
boost::shared_ptr<ARDOUR::Bundle> _input_bundle;
// Bundle to represent our output ports
boost::shared_ptr<ARDOUR::Bundle> _output_bundle;
MIDI::Port *_input_port;
MIDI::Port *_output_port;
boost::shared_ptr<ARDOUR::Port> _async_in;
boost::shared_ptr<ARDOUR::Port> _async_out;
void connect_to_parser();
void handle_button_message(Button* button, MIDI::EventTwoBytes *);
void handle_fader_message(Fader* fader);
void handle_knob_message(Knob* knob);
void handle_midi_controller_message(MIDI::Parser &, MIDI::EventTwoBytes *, MIDI::channel_t chan);
void handle_midi_note_on_message(MIDI::Parser &, MIDI::EventTwoBytes *, MIDI::channel_t chan);
void handle_midi_note_off_message(MIDI::Parser &, MIDI::EventTwoBytes *, MIDI::channel_t chan);
void handle_midi_sysex(MIDI::Parser &, MIDI::byte *, size_t count);
bool midi_input_handler(Glib::IOCondition ioc, MIDI::Port *port);
void thread_init();
PBD::ScopedConnectionList session_connections;
void connect_session_signals();
void notify_transport_state_changed();
void notify_loop_state_changed();
void notify_parameter_changed(std::string);
/* Button methods */
TrackButton* track_button_by_number(uint8_t n, uint8_t first, uint8_t middle);
TrackButton* focus_button_by_number(uint8_t n) { return track_button_by_number(n, 41, 57) ; }
TrackButton* control_button_by_number(uint8_t n) { return track_button_by_number(n, 73, 89) ; }
void button_device();
void button_device_long_press();
void button_track_mode(TrackMode state);
void button_mute() { button_track_mode(TrackMode::TrackMute); }
void button_solo() { button_track_mode(TrackMode::TrackSolo); }
void button_record() { button_track_mode(TrackMode::TrackRecord); }
void button_select_up();
void button_select_down();
void button_select_left();
void button_select_right();
void button_track_focus(uint8_t n);
void button_track_control(uint8_t n);
boost::shared_ptr<ARDOUR::AutomationControl> get_ac_by_state(uint8_t n);
void update_track_control_led(uint8_t n);
void button_track_focus_1() { ControlProtocol::ToggleStripableSelection (stripable[0]); }
void button_track_focus_2() { ControlProtocol::ToggleStripableSelection (stripable[1]); }
void button_track_focus_3() { ControlProtocol::ToggleStripableSelection (stripable[2]); }
void button_track_focus_4() { ControlProtocol::ToggleStripableSelection (stripable[3]); }
void button_track_focus_5() { ControlProtocol::ToggleStripableSelection (stripable[4]); }
void button_track_focus_6() { ControlProtocol::ToggleStripableSelection (stripable[5]); }
void button_track_focus_7() { ControlProtocol::ToggleStripableSelection (stripable[6]); }
void button_track_focus_8() { ControlProtocol::ToggleStripableSelection (stripable[7]); }
void button_track_control_1() { button_track_control(0); }
void button_track_control_2() { button_track_control(1); }
void button_track_control_3() { button_track_control(2); }
void button_track_control_4() { button_track_control(3); }
void button_track_control_5() { button_track_control(4); }
void button_track_control_6() { button_track_control(5); }
void button_track_control_7() { button_track_control(6); }
void button_track_control_8() { button_track_control(7); }
/* stripables */
int32_t bank_start;
PBD::ScopedConnectionList stripable_connections;
boost::shared_ptr<ARDOUR::Stripable> stripable[8];
void stripables_added ();
void stripable_property_change (PBD::PropertyChange const& what_changed, uint32_t which);
void switch_bank (uint32_t base);
void solo_changed (uint32_t n) { solo_mute_rec_changed(n); }
void mute_changed (uint32_t n) { solo_mute_rec_changed(n); }
void rec_changed (uint32_t n) { solo_mute_rec_changed(n); }
void solo_mute_rec_changed (uint32_t n);
/* special Stripable */
boost::shared_ptr<ARDOUR::Stripable> master;
PBD::ScopedConnection port_reg_connection;
void port_registration_handler();
enum ConnectionState { InputConnected = 0x1, OutputConnected = 0x2 };
int connection_state;
bool connection_handler(boost::weak_ptr<ARDOUR::Port>, std::string name1,
boost::weak_ptr<ARDOUR::Port>, std::string name2,
bool yn);
PBD::ScopedConnection port_connection;
void connected();
/* GUI */
mutable LCXLGUI *gui;
void build_gui();
void stripable_selection_changed();
bool in_range_select;
};
} // namespace ArdourSurface
#endif /* __ardour_launch_control_h__ */

View file

@ -0,0 +1,62 @@
#include <algorithm>
#include "launch_control_xl.h"
#include "pbd/compose.h"
#include "pbd/convert.h"
#include "pbd/debug.h"
#include "pbd/failed_constructor.h"
#include "pbd/file_utils.h"
#include "pbd/search_path.h"
#include "pbd/enumwriter.h"
#include "midi++/parser.h"
#include "temporal/time.h"
#include "temporal/bbt_time.h"
#include "ardour/amp.h"
#include "ardour/async_midi_port.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/midiport_manager.h"
#include "ardour/midi_track.h"
#include "ardour/midi_port.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "ardour/types_convert.h"
#include "ardour/vca_manager.h"
#include "gui.h"
#include "pbd/i18n.h"
using namespace ArdourSurface;
using namespace ARDOUR;
using namespace std;
using namespace PBD;
MidiByteArray
LaunchControlXL::SelectButton::state_msg(bool light) const {
uint8_t velocity = ( color() + flag() ) * light;
return MidiByteArray (11, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x78, lcxl->template_number(), index(), velocity, 0xF7);
}
MidiByteArray
LaunchControlXL::TrackButton::state_msg(bool light) const {
uint8_t velocity = ( color() + flag() ) * light;
return MidiByteArray (11, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x78, lcxl->template_number(), index(), velocity, 0xF7);
}
MidiByteArray
LaunchControlXL::TrackStateButton::state_msg(bool light) const {
uint8_t velocity = ( color() + flag() ) * light;
return MidiByteArray (11, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x78, lcxl->template_number(), index(), velocity, 0xF7);
}
MidiByteArray
LaunchControlXL::Knob::state_msg(bool light) const {
uint8_t velocity = ( color() + flag() ) * light;
return MidiByteArray (11, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11, 0x78, lcxl->template_number(), index(), velocity, 0xF7);
}

View file

@ -0,0 +1,115 @@
/*
Copyright (C) 2006,2007 John Anderson
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "midi_byte_array.h"
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
#include <cstdarg>
#include <iomanip>
#include <stdexcept>
using namespace std;
MidiByteArray::MidiByteArray (size_t size, MIDI::byte array[])
: std::vector<MIDI::byte>()
{
for (size_t i = 0; i < size; ++i)
{
push_back (array[i]);
}
}
MidiByteArray::MidiByteArray (size_t count, MIDI::byte first, ...)
: vector<MIDI::byte>()
{
push_back (first);
va_list var_args;
va_start (var_args, first);
for (size_t i = 1; i < count; ++i)
{
MIDI::byte b = va_arg (var_args, int);
push_back (b);
}
va_end (var_args);
}
void MidiByteArray::copy (size_t count, MIDI::byte * arr)
{
for (size_t i = 0; i < count; ++i) {
push_back (arr[i]);
}
}
MidiByteArray & operator << (MidiByteArray & mba, const MIDI::byte & b)
{
mba.push_back (b);
return mba;
}
MidiByteArray & operator << (MidiByteArray & mba, const MidiByteArray & barr)
{
back_insert_iterator<MidiByteArray> bit (mba);
copy (barr.begin(), barr.end(), bit);
return mba;
}
ostream & operator << (ostream & os, const MidiByteArray & mba)
{
os << "[";
char fill = os.fill('0');
for (MidiByteArray::const_iterator it = mba.begin(); it != mba.end(); ++it) {
if (it != mba.begin()) os << " ";
os << hex << setw(2) << (int)*it;
}
os.fill (fill);
os << dec;
os << "]";
return os;
}
MidiByteArray & operator << (MidiByteArray & mba, const std::string & st)
{
/* note that this assumes that "st" is ASCII encoded
*/
mba.insert (mba.end(), st.begin(), st.end());
return mba;
}
bool
MidiByteArray::compare_n (const MidiByteArray& other, MidiByteArray::size_type n) const
{
MidiByteArray::const_iterator us = begin();
MidiByteArray::const_iterator them = other.begin();
while (n && us != end() && them != other.end()) {
if ((*us) != (*them)) {
return false;
}
--n;
++us;
++them;
}
return true;
}

View file

@ -0,0 +1,78 @@
/*
Copyright (C) 2006,2007 John Anderson
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef midi_byte_array_h
#define midi_byte_array_h
#include <iostream>
#include <vector>
#include <boost/shared_array.hpp>
//#include <midi++/types.h>
namespace MIDI {
typedef unsigned char byte;
}
/**
To make building arrays of bytes easier. Thusly:
MidiByteArray mba;
mba << 0xf0 << 0x00 << 0xf7;
MidiByteArray buf;
buf << mba;
MidiByteArray direct( 3, 0xf0, 0x00, 0xf7 );
cout << mba << endl;
cout << buf << endl;
cout << direct << endl;
will all result in "f0 00 f7" being output to stdout
*/
class MidiByteArray : public std::vector<MIDI::byte>
{
public:
MidiByteArray() : std::vector<MIDI::byte>() {}
MidiByteArray( size_t count, MIDI::byte array[] );
bool compare_n (const MidiByteArray& other, MidiByteArray::size_type len) const;
/**
Accepts a preceding count, and then a list of bytes
*/
MidiByteArray( size_t count, MIDI::byte first, ... );
/// copy the given number of bytes from the given array
void copy( size_t count, MIDI::byte arr[] );
};
/// append the given byte to the end of the array
MidiByteArray & operator << ( MidiByteArray & mba, const MIDI::byte & b );
/// append the given string to the end of the array
MidiByteArray & operator << ( MidiByteArray & mba, const std::string & );
/// append the given array to the end of this array
MidiByteArray & operator << ( MidiByteArray & mba, const MidiByteArray & barr );
/// output the bytes as hex to the given stream
std::ostream & operator << ( std::ostream & os, const MidiByteArray & mba );
#endif

View file

@ -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()

View file

@ -27,6 +27,7 @@ children = [
'generic_midi', 'generic_midi',
'mackie', 'mackie',
'us2400', 'us2400',
'launch_control_xl',
] ]
def options(opt): def options(opt):
@ -50,7 +51,7 @@ def configure(conf):
children += [ 'push2' ] children += [ 'push2' ]
else: else:
print ('You are missing the libusb-1.0 development package needed to compile Push2 support') 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: if conf.is_defined('HAVE_HIDAPI') and Options.options.maschine:
children += [ 'maschine2' ] children += [ 'maschine2' ]
conf.define('BUILD_MASCHINE', 1) conf.define('BUILD_MASCHINE', 1)
@ -85,6 +86,7 @@ def build(bld):
bld.recurse('cc121') bld.recurse('cc121')
bld.recurse('mackie') bld.recurse('mackie')
bld.recurse('us2400') bld.recurse('us2400')
bld.recurse('launch_control_xl')
if bld.is_defined ('HAVE_LO'): if bld.is_defined ('HAVE_LO'):
bld.recurse('osc') bld.recurse('osc')