mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-06 14:54:56 +01:00
Add Launch Control XL control surface support
This commit is contained in:
parent
f4c1166651
commit
8c7a1e004b
14 changed files with 2693 additions and 3 deletions
|
|
@ -13,7 +13,7 @@ export GTK2_RC_FILES=/nonexistent
|
|||
# can find all the components.
|
||||
#
|
||||
|
||||
export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/us2400:$libs/surfaces/wiimote:$libs/surfaces/push2:$libs/surfaces/maschine2:$libs/surfaces/cc121
|
||||
export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/us2400:$libs/surfaces/wiimote:$libs/surfaces/push2:$libs/surfaces/maschine2:$libs/surfaces/cc121:$libs/surfaces/launch_control_xl
|
||||
export ARDOUR_PANNER_PATH=$libs/panners
|
||||
export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:.
|
||||
export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:.
|
||||
|
|
|
|||
|
|
@ -87,9 +87,9 @@ namespace PBD {
|
|||
LIBARDOUR_API extern DebugBits VCA;
|
||||
LIBARDOUR_API extern DebugBits Push2;
|
||||
LIBARDOUR_API extern DebugBits US2400;
|
||||
LIBARDOUR_API extern DebugBits LaunchControlXL;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __ardour_debug_h__ */
|
||||
|
||||
|
|
|
|||
|
|
@ -84,3 +84,4 @@ PBD::DebugBits PBD::DEBUG::CC121 = PBD::new_debug_bit ("cc121");
|
|||
PBD::DebugBits PBD::DEBUG::VCA = PBD::new_debug_bit ("vca");
|
||||
PBD::DebugBits PBD::DEBUG::Push2 = PBD::new_debug_bit ("push2");
|
||||
PBD::DebugBits PBD::DEBUG::US2400 = PBD::new_debug_bit ("us2400");
|
||||
PBD::DebugBits PBD::DEBUG::LaunchControlXL = PBD::new_debug_bit("launchcontrolxl");
|
||||
|
|
|
|||
487
libs/surfaces/launch_control_xl/controllers.cc
Normal file
487
libs/surfaces/launch_control_xl/controllers.cc
Normal 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());
|
||||
}
|
||||
268
libs/surfaces/launch_control_xl/gui.cc
Normal file
268
libs/surfaces/launch_control_xl/gui.cc
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
libs/surfaces/launch_control_xl/gui.h
Normal file
100
libs/surfaces/launch_control_xl/gui.h
Normal 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__ */
|
||||
92
libs/surfaces/launch_control_xl/interface.cc
Normal file
92
libs/surfaces/launch_control_xl/interface.cc
Normal 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; }
|
||||
898
libs/surfaces/launch_control_xl/launch_control_xl.cc
Normal file
898
libs/surfaces/launch_control_xl/launch_control_xl.cc
Normal 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;
|
||||
}
|
||||
}
|
||||
545
libs/surfaces/launch_control_xl/launch_control_xl.h
Normal file
545
libs/surfaces/launch_control_xl/launch_control_xl.h
Normal 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__ */
|
||||
62
libs/surfaces/launch_control_xl/leds.cc
Normal file
62
libs/surfaces/launch_control_xl/leds.cc
Normal 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);
|
||||
}
|
||||
115
libs/surfaces/launch_control_xl/midi_byte_array.cc
Normal file
115
libs/surfaces/launch_control_xl/midi_byte_array.cc
Normal 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;
|
||||
}
|
||||
|
||||
78
libs/surfaces/launch_control_xl/midi_byte_array.h
Normal file
78
libs/surfaces/launch_control_xl/midi_byte_array.h
Normal 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
|
||||
42
libs/surfaces/launch_control_xl/wscript
Normal file
42
libs/surfaces/launch_control_xl/wscript
Normal 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()
|
||||
|
|
@ -27,6 +27,7 @@ children = [
|
|||
'generic_midi',
|
||||
'mackie',
|
||||
'us2400',
|
||||
'launch_control_xl',
|
||||
]
|
||||
|
||||
def options(opt):
|
||||
|
|
@ -85,6 +86,7 @@ def build(bld):
|
|||
bld.recurse('cc121')
|
||||
bld.recurse('mackie')
|
||||
bld.recurse('us2400')
|
||||
bld.recurse('launch_control_xl')
|
||||
|
||||
if bld.is_defined ('HAVE_LO'):
|
||||
bld.recurse('osc')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue